diff --git a/Userland/Libraries/LibWeb/Animations/Animation.cpp b/Userland/Libraries/LibWeb/Animations/Animation.cpp index 7b0baf9fc375e2..400be2a3bc6735 100644 --- a/Userland/Libraries/LibWeb/Animations/Animation.cpp +++ b/Userland/Libraries/LibWeb/Animations/Animation.cpp @@ -1097,7 +1097,7 @@ void Animation::update_finished_state(DidSeek did_seek, SynchronouslyNotify sync // steps: if (current_finished_state && !m_is_finished) { // 1. Let finish notification steps refer to the following procedure: - JS::SafeFunction finish_notification_steps = [&]() { + auto finish_notification_steps = JS::create_heap_function(heap(), [this, &realm]() { // 1. If animation’s play state is not equal to finished, abort these steps. if (play_state() != Bindings::AnimationPlayState::Finished) return; @@ -1135,13 +1135,14 @@ void Animation::update_finished_state(DidSeek did_seek, SynchronouslyNotify sync else { // Manually create a task so its ID can be saved auto& document = verify_cast(realm.global_object()).associated_document(); - auto task = HTML::Task::create(HTML::Task::Source::DOMManipulation, &document, [this, finish_event]() { - dispatch_event(finish_event); - }); + auto task = HTML::Task::create(vm(), HTML::Task::Source::DOMManipulation, &document, + JS::create_heap_function(heap(), [this, finish_event]() { + dispatch_event(finish_event); + })); m_pending_finish_microtask_id = task->id(); - HTML::main_thread_event_loop().task_queue().add(move(task)); + HTML::main_thread_event_loop().task_queue().add(task); } - }; + }); // 2. If synchronously notify is true, cancel any queued microtask to run the finish notification steps for this // animation, and run the finish notification steps immediately. @@ -1151,13 +1152,13 @@ void Animation::update_finished_state(DidSeek did_seek, SynchronouslyNotify sync return task.id() == id; }); } - finish_notification_steps(); + finish_notification_steps->function()(); } // Otherwise, if synchronously notify is false, queue a microtask to run finish notification steps for // animation unless there is already a microtask queued to run those steps for animation. else if (!m_pending_finish_microtask_id.has_value()) { auto& document = verify_cast(realm.global_object()).associated_document(); - auto task = HTML::Task::create(HTML::Task::Source::DOMManipulation, &document, move(finish_notification_steps)); + auto task = HTML::Task::create(vm(), HTML::Task::Source::DOMManipulation, &document, move(finish_notification_steps)); m_pending_finish_microtask_id = task->id(); HTML::main_thread_event_loop().task_queue().add(move(task)); } diff --git a/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp b/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp index ca3ccc326dc17e..b94390e0610f6a 100644 --- a/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp +++ b/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp @@ -86,6 +86,9 @@ ErrorOr initialize_main_thread_vm() // 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_without_realm(); + // These strings could potentially live on the VM similar to CommonPropertyNames. DOM::MutationType::initialize_strings(); HTML::AttributeNames::initialize_strings(); @@ -103,8 +106,6 @@ ErrorOr initialize_main_thread_vm() XHR::EventNames::initialize_strings(); XLink::AttributeNames::initialize_strings(); - static_cast(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 { // 1. If O is a WindowProxy object, or implements Location, then return Completion { [[Type]]: throw, [[Value]]: a new TypeError }. diff --git a/Userland/Libraries/LibWeb/Bindings/MainThreadVM.h b/Userland/Libraries/LibWeb/Bindings/MainThreadVM.h index fb5d4cd877e106..bdafaea76157fe 100644 --- a/Userland/Libraries/LibWeb/Bindings/MainThreadVM.h +++ b/Userland/Libraries/LibWeb/Bindings/MainThreadVM.h @@ -40,7 +40,7 @@ struct WebEngineCustomData final : public JS::VM::CustomData { virtual void spin_event_loop_until(JS::SafeFunction goal_condition) override; - HTML::EventLoop event_loop; + JS::Handle event_loop; // FIXME: These should only be on similar-origin window agents, but we don't currently differentiate agent types. diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp index 9549165a7570f5..c62b04ee7eda3a 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.cpp +++ b/Userland/Libraries/LibWeb/DOM/Document.cpp @@ -3166,7 +3166,7 @@ void Document::unload(JS::GCPtr) auto intend_to_store_in_bfcache = false; // 6. Let eventLoop be oldDocument's relevant agent's event loop. - auto& event_loop = verify_cast(*vm.custom_data()).event_loop; + auto& event_loop = *verify_cast(*vm.custom_data()).event_loop; // 7. Increase eventLoop's termination nesting level by 1. event_loop.increment_termination_nesting_level(); diff --git a/Userland/Libraries/LibWeb/DOM/Element.cpp b/Userland/Libraries/LibWeb/DOM/Element.cpp index c1134513340ba7..e65095028f4cef 100644 --- a/Userland/Libraries/LibWeb/DOM/Element.cpp +++ b/Userland/Libraries/LibWeb/DOM/Element.cpp @@ -785,9 +785,9 @@ void Element::make_html_uppercased_qualified_name() } // https://html.spec.whatwg.org/multipage/webappapis.html#queue-an-element-task -int Element::queue_an_element_task(HTML::Task::Source source, JS::SafeFunction steps) +int Element::queue_an_element_task(HTML::Task::Source source, Function steps) { - auto task = HTML::Task::create(source, &document(), move(steps)); + auto task = HTML::Task::create(vm(), source, &document(), JS::create_heap_function(heap(), move(steps))); auto id = task->id(); HTML::main_thread_event_loop().task_queue().add(move(task)); diff --git a/Userland/Libraries/LibWeb/DOM/Element.h b/Userland/Libraries/LibWeb/DOM/Element.h index f1aaf2bd07e278..24c6f94b5d9e46 100644 --- a/Userland/Libraries/LibWeb/DOM/Element.h +++ b/Userland/Libraries/LibWeb/DOM/Element.h @@ -190,7 +190,8 @@ class Element void set_custom_properties(Optional, HashMap custom_properties); [[nodiscard]] HashMap const& custom_properties(Optional) const; - int queue_an_element_task(HTML::Task::Source, JS::SafeFunction); + // NOTE: The function is wrapped in a JS::HeapFunction immediately. + int queue_an_element_task(HTML::Task::Source, Function); bool is_void_element() const; bool serializes_as_void() const; diff --git a/Userland/Libraries/LibWeb/Fetch/Infrastructure/Task.cpp b/Userland/Libraries/LibWeb/Fetch/Infrastructure/Task.cpp index 54e0a733c77011..78cd6ead47fab9 100644 --- a/Userland/Libraries/LibWeb/Fetch/Infrastructure/Task.cpp +++ b/Userland/Libraries/LibWeb/Fetch/Infrastructure/Task.cpp @@ -11,7 +11,7 @@ namespace Web::Fetch::Infrastructure { // https://fetch.spec.whatwg.org/#queue-a-fetch-task -int queue_fetch_task(JS::Object& task_destination, JS::SafeFunction algorithm) +int queue_fetch_task(JS::Object& task_destination, Function algorithm) { // FIXME: 1. If taskDestination is a parallel queue, then enqueue algorithm to taskDestination. @@ -21,7 +21,7 @@ int queue_fetch_task(JS::Object& task_destination, JS::SafeFunction algo // AD-HOC: This overload allows tracking the queued task within the fetch controller so that we may cancel queued tasks // when the spec indicates that we must stop an ongoing fetch. -int queue_fetch_task(JS::NonnullGCPtr fetch_controller, JS::Object& task_destination, JS::SafeFunction algorithm) +int queue_fetch_task(JS::NonnullGCPtr fetch_controller, JS::Object& task_destination, Function algorithm) { auto fetch_task_id = fetch_controller->next_fetch_task_id(); diff --git a/Userland/Libraries/LibWeb/Fetch/Infrastructure/Task.h b/Userland/Libraries/LibWeb/Fetch/Infrastructure/Task.h index c305accd798b47..451ca758addc4c 100644 --- a/Userland/Libraries/LibWeb/Fetch/Infrastructure/Task.h +++ b/Userland/Libraries/LibWeb/Fetch/Infrastructure/Task.h @@ -17,7 +17,7 @@ namespace Web::Fetch::Infrastructure { // FIXME: 'or a parallel queue' using TaskDestination = Variant>; -int queue_fetch_task(JS::Object&, JS::SafeFunction); -int queue_fetch_task(JS::NonnullGCPtr, JS::Object&, JS::SafeFunction); +int queue_fetch_task(JS::Object&, Function); +int queue_fetch_task(JS::NonnullGCPtr, JS::Object&, Function); } diff --git a/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp b/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp index 57f6ca830abbbe..7819878a4a4f8b 100644 --- a/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp +++ b/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp @@ -22,13 +22,24 @@ namespace Web::HTML { EventLoop::EventLoop() - : m_task_queue(*this) - , m_microtask_queue(*this) { + m_task_queue = heap().allocate_without_realm(*this); + m_microtask_queue = heap().allocate_without_realm(*this); } EventLoop::~EventLoop() = default; +void EventLoop::visit_edges(Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_task_queue); + visitor.visit(m_microtask_queue); + visitor.visit(m_currently_running_task); + + for (auto& settings : m_backup_incumbent_settings_object_stack) + visitor.visit(settings); +} + void EventLoop::schedule() { if (!m_system_event_loop_timer) { @@ -41,15 +52,9 @@ void EventLoop::schedule() m_system_event_loop_timer->restart(); } -void EventLoop::set_vm(JS::VM& vm) -{ - VERIFY(!m_vm); - m_vm = &vm; -} - 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())->event_loop; } // https://html.spec.whatwg.org/multipage/webappapis.html#spin-the-event-loop @@ -62,8 +67,9 @@ void EventLoop::spin_until(JS::SafeFunction goal_condition) // 3. Let old stack be a copy of the JavaScript execution context stack. // 4. Empty the JavaScript execution context stack. - m_vm->save_execution_context_stack(); - m_vm->clear_execution_context_stack(); + auto& vm = this->vm(); + vm.save_execution_context_stack(); + vm.clear_execution_context_stack(); // 5. Perform a microtask checkpoint. perform_a_microtask_checkpoint(); @@ -78,7 +84,7 @@ void EventLoop::spin_until(JS::SafeFunction goal_condition) Platform::EventLoopPlugin::the().spin_until([&] { if (goal_condition()) return true; - if (m_task_queue.has_runnable_tasks()) { + if (m_task_queue->has_runnable_tasks()) { schedule(); // FIXME: Remove the platform event loop plugin so that this doesn't look out of place Core::EventLoop::current().wake(); @@ -86,7 +92,7 @@ void EventLoop::spin_until(JS::SafeFunction goal_condition) return goal_condition(); }); - m_vm->restore_execution_context_stack(); + vm.restore_execution_context_stack(); // 7. Stop task, allowing whatever algorithm that invoked it to resume. // NOTE: This is achieved by returning from the function. @@ -94,8 +100,9 @@ void EventLoop::spin_until(JS::SafeFunction goal_condition) void EventLoop::spin_processing_tasks_with_source_until(Task::Source source, JS::SafeFunction goal_condition) { - m_vm->save_execution_context_stack(); - m_vm->clear_execution_context_stack(); + auto& vm = this->vm(); + vm.save_execution_context_stack(); + vm.clear_execution_context_stack(); perform_a_microtask_checkpoint(); @@ -105,12 +112,12 @@ void EventLoop::spin_processing_tasks_with_source_until(Task::Source source, JS: Platform::EventLoopPlugin::the().spin_until([&] { if (goal_condition()) return true; - if (m_task_queue.has_runnable_tasks()) { - auto tasks = m_task_queue.take_tasks_matching([&](auto& task) { + if (m_task_queue->has_runnable_tasks()) { + auto tasks = m_task_queue->take_tasks_matching([&](auto& task) { return task.source() == source && task.is_runnable(); }); - for (auto& task : tasks.value()) { + for (auto& task : tasks) { m_currently_running_task = task.ptr(); task->execute(); m_currently_running_task = nullptr; @@ -126,7 +133,7 @@ void EventLoop::spin_processing_tasks_with_source_until(Task::Source source, JS: schedule(); - m_vm->restore_execution_context_stack(); + vm.restore_execution_context_stack(); } // https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model @@ -138,7 +145,7 @@ void EventLoop::process() // An event loop must continually run through the following steps for as long as it exists: // 1. Let oldestTask be null. - OwnPtr oldest_task; + JS::GCPtr oldest_task; // 2. Let taskStartTime be the current high resolution time. // FIXME: 'current high resolution time' in hr-time-3 takes a global object, @@ -149,7 +156,7 @@ void EventLoop::process() // 3. Let taskQueue be one of the event loop's task queues, chosen in an implementation-defined manner, // with the constraint that the chosen task queue must contain at least one runnable task. // If there is no such task queue, then jump to the microtasks step below. - auto& task_queue = m_task_queue; + auto& task_queue = *m_task_queue; // 4. Set oldestTask to the first runnable task in taskQueue, and remove it from taskQueue. oldest_task = task_queue.take_first_runnable(); @@ -333,7 +340,7 @@ void EventLoop::process() // - this event loop's microtask queue is empty // - hasARenderingOpportunity is false // FIXME: has_a_rendering_opportunity is always true - if (m_type == Type::Window && !task_queue.has_runnable_tasks() && m_microtask_queue.is_empty() /*&& !has_a_rendering_opportunity*/) { + if (m_type == Type::Window && !task_queue.has_runnable_tasks() && m_microtask_queue->is_empty() /*&& !has_a_rendering_opportunity*/) { // 1. Set this event loop's last idle period start time to the current high resolution time. m_last_idle_period_start_time = HighResolutionTime::unsafe_shared_current_time(); @@ -356,7 +363,7 @@ void EventLoop::process() // FIXME: 2. If there are no tasks in the event loop's task queues and the WorkerGlobalScope object's closing flag is true, then destroy the event loop, aborting these steps, resuming the run a worker steps described in the Web workers section below. // If there are eligible tasks in the queue, schedule a new round of processing. :^) - if (m_task_queue.has_runnable_tasks() || (!m_microtask_queue.is_empty() && !m_performing_a_microtask_checkpoint)) + if (m_task_queue->has_runnable_tasks() || (!m_microtask_queue->is_empty() && !m_performing_a_microtask_checkpoint)) schedule(); // For each doc of docs, process top layer removals given doc. @@ -366,7 +373,7 @@ void EventLoop::process() } // https://html.spec.whatwg.org/multipage/webappapis.html#queue-a-global-task -int queue_global_task(HTML::Task::Source source, JS::Object& global_object, JS::SafeFunction steps) +int queue_global_task(HTML::Task::Source source, JS::Object& global_object, Function steps) { // 1. Let event loop be global's relevant agent's event loop. auto& global_custom_data = verify_cast(*global_object.vm().custom_data()); @@ -380,13 +387,14 @@ int queue_global_task(HTML::Task::Source source, JS::Object& global_object, JS:: } // 3. Queue a task given source, event loop, document, and steps. - event_loop.task_queue().add(HTML::Task::create(source, document, move(steps))); + auto& vm = global_object.vm(); + event_loop->task_queue().add(HTML::Task::create(vm, source, document, JS::create_heap_function(vm.heap(), move(steps)))); - return event_loop.task_queue().last_added_task()->id(); + return event_loop->task_queue().last_added_task()->id(); } // https://html.spec.whatwg.org/#queue-a-microtask -void queue_a_microtask(DOM::Document const* document, JS::SafeFunction steps) +void queue_a_microtask(DOM::Document const* document, Function steps) { // 1. If event loop was not given, set event loop to the implied event loop. auto& event_loop = HTML::main_thread_event_loop(); @@ -397,12 +405,13 @@ void queue_a_microtask(DOM::Document const* document, JS::SafeFunction s // 4. Set microtask's steps to steps. // 5. Set microtask's source to the microtask task source. // 6. Set microtask's document to document. - auto microtask = HTML::Task::create(HTML::Task::Source::Microtask, document, move(steps)); + auto& vm = event_loop.vm(); + auto microtask = HTML::Task::create(vm, HTML::Task::Source::Microtask, document, JS::create_heap_function(vm.heap(), move(steps))); // FIXME: 7. Set microtask's script evaluation environment settings object set to an empty set. // 8. Enqueue microtask on event loop's microtask queue. - event_loop.microtask_queue().enqueue(move(microtask)); + event_loop.microtask_queue().enqueue(microtask); } void perform_a_microtask_checkpoint() @@ -421,9 +430,9 @@ void EventLoop::perform_a_microtask_checkpoint() m_performing_a_microtask_checkpoint = true; // 3. While the event loop's microtask queue is not empty: - while (!m_microtask_queue.is_empty()) { + while (!m_microtask_queue->is_empty()) { // 1. Let oldestMicrotask be the result of dequeuing from the event loop's microtask queue. - auto oldest_microtask = m_microtask_queue.dequeue(); + auto oldest_microtask = m_microtask_queue->dequeue(); // 2. Set the event loop's currently running task to oldestMicrotask. m_currently_running_task = oldest_microtask; @@ -486,12 +495,12 @@ EnvironmentSettingsObject& EventLoop::top_of_backup_incumbent_settings_object_st void EventLoop::register_environment_settings_object(Badge, EnvironmentSettingsObject& environment_settings_object) { - m_related_environment_settings_objects.append(environment_settings_object); + m_related_environment_settings_objects.append(&environment_settings_object); } void EventLoop::unregister_environment_settings_object(Badge, EnvironmentSettingsObject& environment_settings_object) { - bool did_remove = m_related_environment_settings_objects.remove_first_matching([&](auto& entry) { return entry.ptr() == &environment_settings_object; }); + bool did_remove = m_related_environment_settings_objects.remove_first_matching([&](auto& entry) { return entry == &environment_settings_object; }); VERIFY(did_remove); } diff --git a/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.h b/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.h index d587837e7c7f39..8594928890104f 100644 --- a/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.h +++ b/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.h @@ -15,7 +15,9 @@ namespace Web::HTML { -class EventLoop { +class EventLoop : public JS::Cell { + JS_CELL(EventLoop, Cell); + public: enum class Type { // https://html.spec.whatwg.org/multipage/webappapis.html#window-event-loop @@ -26,16 +28,15 @@ class EventLoop { Worklet, }; - EventLoop(); - ~EventLoop(); + virtual ~EventLoop() override; Type type() const { return m_type; } - TaskQueue& task_queue() { return m_task_queue; } - TaskQueue const& task_queue() const { return m_task_queue; } + TaskQueue& task_queue() { return *m_task_queue; } + TaskQueue const& task_queue() const { return *m_task_queue; } - TaskQueue& microtask_queue() { return m_microtask_queue; } - TaskQueue const& microtask_queue() const { return m_microtask_queue; } + TaskQueue& microtask_queue() { return *m_microtask_queue; } + TaskQueue const& microtask_queue() const { return *m_microtask_queue; } void spin_until(JS::SafeFunction goal_condition); void spin_processing_tasks_with_source_until(Task::Source, JS::SafeFunction goal_condition); @@ -48,11 +49,6 @@ class EventLoop { Task const* currently_running_task() const { return m_currently_running_task; } - JS::VM& vm() { return *m_vm; } - JS::VM const& vm() const { return *m_vm; } - - void set_vm(JS::VM&); - void schedule(); void perform_a_microtask_checkpoint(); @@ -79,21 +75,23 @@ class EventLoop { bool execution_paused() const { return m_execution_paused; } private: + EventLoop(); + + virtual void visit_edges(Visitor&) override; + Type m_type { Type::Window }; - TaskQueue m_task_queue; - TaskQueue m_microtask_queue; + JS::GCPtr m_task_queue; + JS::GCPtr m_microtask_queue; // https://html.spec.whatwg.org/multipage/webappapis.html#currently-running-task - Task* m_currently_running_task { nullptr }; + JS::GCPtr m_currently_running_task { nullptr }; // https://html.spec.whatwg.org/multipage/webappapis.html#last-render-opportunity-time double m_last_render_opportunity_time { 0 }; // https://html.spec.whatwg.org/multipage/webappapis.html#last-idle-period-start-time double m_last_idle_period_start_time { 0 }; - JS::VM* m_vm { nullptr }; - RefPtr m_system_event_loop_timer; // https://html.spec.whatwg.org/#performing-a-microtask-checkpoint @@ -102,7 +100,8 @@ class EventLoop { Vector> m_documents; // Used to implement step 4 of "perform a microtask checkpoint". - Vector> m_related_environment_settings_objects; + // NOTE: These are weak references! ESO registers and unregisters itself from the event loop manually. + Vector> m_related_environment_settings_objects; // https://html.spec.whatwg.org/multipage/webappapis.html#backup-incumbent-settings-object-stack Vector> m_backup_incumbent_settings_object_stack; @@ -116,8 +115,8 @@ class EventLoop { }; EventLoop& main_thread_event_loop(); -int queue_global_task(HTML::Task::Source, JS::Object&, JS::SafeFunction steps); -void queue_a_microtask(DOM::Document const*, JS::SafeFunction steps); +int queue_global_task(HTML::Task::Source, JS::Object&, Function steps); +void queue_a_microtask(DOM::Document const*, Function steps); void perform_a_microtask_checkpoint(); } diff --git a/Userland/Libraries/LibWeb/HTML/EventLoop/Task.cpp b/Userland/Libraries/LibWeb/HTML/EventLoop/Task.cpp index b77c7792322f26..9be925bb90280d 100644 --- a/Userland/Libraries/LibWeb/HTML/EventLoop/Task.cpp +++ b/Userland/Libraries/LibWeb/HTML/EventLoop/Task.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Andreas Kling + * Copyright (c) 2021-2024, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ @@ -10,25 +10,41 @@ namespace Web::HTML { +JS_DEFINE_ALLOCATOR(Task); + static IDAllocator s_unique_task_source_allocator { static_cast(Task::Source::UniqueTaskSourceStart) }; static IDAllocator s_task_id_allocator; -Task::Task(Source source, DOM::Document const* document, JS::SafeFunction steps) +JS::NonnullGCPtr Task::create(JS::VM& vm, Source source, JS::GCPtr document, JS::NonnullGCPtr> steps) +{ + return vm.heap().allocate_without_realm(source, document, move(steps)); +} + +Task::Task(Source source, JS::GCPtr document, JS::NonnullGCPtr> steps) : m_id(s_task_id_allocator.allocate()) , m_source(source) - , m_steps(move(steps)) - , m_document(JS::make_handle(document)) + , m_steps(steps) + , m_document(document) { } -Task::~Task() +Task::~Task() = default; + +void Task::finalize() { s_unique_task_source_allocator.deallocate(m_id); } +void Task::visit_edges(Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_steps); + visitor.visit(m_document); +} + void Task::execute() { - m_steps(); + m_steps->function()(); } // https://html.spec.whatwg.org/#concept-task-runnable diff --git a/Userland/Libraries/LibWeb/HTML/EventLoop/Task.h b/Userland/Libraries/LibWeb/HTML/EventLoop/Task.h index 5a7e88264cd037..58322d1906485b 100644 --- a/Userland/Libraries/LibWeb/HTML/EventLoop/Task.h +++ b/Userland/Libraries/LibWeb/HTML/EventLoop/Task.h @@ -1,15 +1,13 @@ /* - * Copyright (c) 2021-2022, Andreas Kling + * Copyright (c) 2021-2024, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once -#include -#include -#include -#include +#include +#include #include #include @@ -17,7 +15,10 @@ namespace Web::HTML { struct UniqueTaskSource; -class Task { +class Task final : public JS::Cell { + JS_CELL(Task, Cell); + JS_DECLARE_ALLOCATOR(Task); + public: // https://html.spec.whatwg.org/multipage/webappapis.html#generic-task-sources enum class Source { @@ -59,11 +60,10 @@ class Task { UniqueTaskSourceStart }; - static NonnullOwnPtr create(Source source, DOM::Document const* document, JS::SafeFunction steps) - { - return adopt_own(*new Task(source, document, move(steps))); - } - ~Task(); + static JS::NonnullGCPtr create(JS::VM&, Source, JS::GCPtr, JS::NonnullGCPtr> steps); + + virtual ~Task() override; + virtual void finalize() override; int id() const { return m_id; } Source source() const { return m_source; } @@ -74,12 +74,14 @@ class Task { bool is_runnable() const; private: - Task(Source, DOM::Document const*, JS::SafeFunction steps); + Task(Source, JS::GCPtr, JS::NonnullGCPtr> steps); + + virtual void visit_edges(Visitor&) override; int m_id { 0 }; Source m_source { Source::Unspecified }; - JS::SafeFunction m_steps; - JS::Handle m_document; + JS::NonnullGCPtr> m_steps; + JS::GCPtr m_document; }; struct UniqueTaskSource { diff --git a/Userland/Libraries/LibWeb/HTML/EventLoop/TaskQueue.cpp b/Userland/Libraries/LibWeb/HTML/EventLoop/TaskQueue.cpp index f70ce761cf0136..828105299a0813 100644 --- a/Userland/Libraries/LibWeb/HTML/EventLoop/TaskQueue.cpp +++ b/Userland/Libraries/LibWeb/HTML/EventLoop/TaskQueue.cpp @@ -1,9 +1,10 @@ /* - * Copyright (c) 2021, Andreas Kling + * Copyright (c) 2021-2024, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include @@ -16,15 +17,23 @@ TaskQueue::TaskQueue(HTML::EventLoop& event_loop) TaskQueue::~TaskQueue() = default; -void TaskQueue::add(NonnullOwnPtr task) +void TaskQueue::visit_edges(Visitor& visitor) { - m_tasks.append(move(task)); - m_event_loop.schedule(); + Base::visit_edges(visitor); + visitor.visit(m_event_loop); + for (auto& task : m_tasks) + visitor.visit(task); } -OwnPtr TaskQueue::take_first_runnable() +void TaskQueue::add(JS::NonnullGCPtr task) { - if (m_event_loop.execution_paused()) + m_tasks.append(task); + m_event_loop->schedule(); +} + +JS::GCPtr TaskQueue::take_first_runnable() +{ + if (m_event_loop->execution_paused()) return nullptr; for (size_t i = 0; i < m_tasks.size(); ++i) { @@ -36,7 +45,7 @@ OwnPtr TaskQueue::take_first_runnable() bool TaskQueue::has_runnable_tasks() const { - if (m_event_loop.execution_paused()) + if (m_event_loop->execution_paused()) return false; for (auto& task : m_tasks) { @@ -53,15 +62,15 @@ void TaskQueue::remove_tasks_matching(Function filter) }); } -ErrorOr>> TaskQueue::take_tasks_matching(Function filter) +JS::MarkedVector> TaskQueue::take_tasks_matching(Function filter) { - Vector> matching_tasks; + JS::MarkedVector> matching_tasks(heap()); for (size_t i = 0; i < m_tasks.size();) { auto& task = m_tasks.at(i); if (filter(*task)) { - TRY(matching_tasks.try_append(move(task))); + matching_tasks.append(task); m_tasks.remove(i); } else { ++i; diff --git a/Userland/Libraries/LibWeb/HTML/EventLoop/TaskQueue.h b/Userland/Libraries/LibWeb/HTML/EventLoop/TaskQueue.h index 3800e83c649ffe..41471f7ad5baa8 100644 --- a/Userland/Libraries/LibWeb/HTML/EventLoop/TaskQueue.h +++ b/Userland/Libraries/LibWeb/HTML/EventLoop/TaskQueue.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Andreas Kling + * Copyright (c) 2021-2024, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ @@ -7,24 +7,27 @@ #pragma once #include +#include #include namespace Web::HTML { -class TaskQueue { +class TaskQueue : public JS::Cell { + JS_CELL(TaskQueue, Cell); + public: explicit TaskQueue(HTML::EventLoop&); - ~TaskQueue(); + virtual ~TaskQueue() override; bool is_empty() const { return m_tasks.is_empty(); } bool has_runnable_tasks() const; - void add(NonnullOwnPtr); - OwnPtr take_first_runnable(); + void add(JS::NonnullGCPtr); + JS::GCPtr take_first_runnable(); - void enqueue(NonnullOwnPtr task) { add(move(task)); } - OwnPtr dequeue() + void enqueue(JS::NonnullGCPtr task) { add(task); } + JS::GCPtr dequeue() { if (m_tasks.is_empty()) return {}; @@ -32,14 +35,16 @@ class TaskQueue { } void remove_tasks_matching(Function); - ErrorOr>> take_tasks_matching(Function); + JS::MarkedVector> take_tasks_matching(Function); Task const* last_added_task() const; private: - HTML::EventLoop& m_event_loop; + virtual void visit_edges(Visitor&) override; + + JS::NonnullGCPtr m_event_loop; - Vector> m_tasks; + Vector> m_tasks; }; } diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp index 02169d69116e7f..92f50568dd1217 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp @@ -74,7 +74,7 @@ void HTMLMediaElement::finalize() } // https://html.spec.whatwg.org/multipage/media.html#queue-a-media-element-task -void HTMLMediaElement::queue_a_media_element_task(JS::SafeFunction steps) +void HTMLMediaElement::queue_a_media_element_task(Function steps) { // To queue a media element task with a media element element and a series of steps steps, queue an element task on the media element's // media element event task source given element and steps. @@ -469,16 +469,14 @@ double HTMLMediaElement::effective_media_volume() const // https://html.spec.whatwg.org/multipage/media.html#media-element-load-algorithm WebIDL::ExceptionOr HTMLMediaElement::load_element() { - auto& vm = this->vm(); - m_first_data_load_event_since_load_start = true; // FIXME: 1. Abort any already-running instance of the resource selection algorithm for this element. // 2. Let pending tasks be a list of all tasks from the media element's media element event task source in one of the task queues. - [[maybe_unused]] auto pending_tasks = TRY_OR_THROW_OOM(vm, HTML::main_thread_event_loop().task_queue().take_tasks_matching([&](auto& task) { + [[maybe_unused]] auto pending_tasks = HTML::main_thread_event_loop().task_queue().take_tasks_matching([&](auto& task) { return task.source() == media_element_event_task_source(); - })); + }); // FIXME: 3. For each task in pending tasks that would resolve pending play promises or reject pending play promises, immediately resolve or // reject those promises in the order the corresponding tasks were queued. diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h index 8c5f90ce1f15c1..2d57931850776d 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h +++ b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h @@ -40,7 +40,8 @@ class HTMLMediaElement : public HTMLElement { virtual bool is_focusable() const override { return true; } - void queue_a_media_element_task(JS::SafeFunction steps); + // NOTE: The function is wrapped in a JS::HeapFunction immediately. + void queue_a_media_element_task(Function); JS::GCPtr error() const { return m_error; } WebIDL::ExceptionOr set_decoder_error(String error_message); diff --git a/Userland/Libraries/LibWeb/HTML/Scripting/Environments.cpp b/Userland/Libraries/LibWeb/HTML/Scripting/Environments.cpp index a9894898205dfb..3b1795bb25834e 100644 --- a/Userland/Libraries/LibWeb/HTML/Scripting/Environments.cpp +++ b/Userland/Libraries/LibWeb/HTML/Scripting/Environments.cpp @@ -42,6 +42,7 @@ void EnvironmentSettingsObject::initialize(JS::Realm& realm) void EnvironmentSettingsObject::visit_edges(Cell::Visitor& visitor) { Base::visit_edges(visitor); + visitor.visit(m_responsible_event_loop); visitor.visit(target_browsing_context); visitor.visit(m_module_map); visitor.ignore(m_outstanding_rejected_promises_weak_set); @@ -84,8 +85,8 @@ EventLoop& EnvironmentSettingsObject::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 = event_loop; + return *event_loop; } // https://html.spec.whatwg.org/multipage/webappapis.html#check-if-we-can-run-script diff --git a/Userland/Libraries/LibWeb/HTML/Scripting/Environments.h b/Userland/Libraries/LibWeb/HTML/Scripting/Environments.h index 8dfeffc4d8ec5c..0d1dd7255fa7de 100644 --- a/Userland/Libraries/LibWeb/HTML/Scripting/Environments.h +++ b/Userland/Libraries/LibWeb/HTML/Scripting/Environments.h @@ -122,7 +122,7 @@ struct EnvironmentSettingsObject NonnullOwnPtr m_realm_execution_context; JS::GCPtr m_module_map; - EventLoop* m_responsible_event_loop { nullptr }; + JS::GCPtr m_responsible_event_loop; // https://html.spec.whatwg.org/multipage/webappapis.html#outstanding-rejected-promises-weak-set // The outstanding rejected promises weak set must not create strong references to any of its members, and implementations are free to limit its size, e.g. by removing old entries from it when new ones are added. diff --git a/Userland/Libraries/LibWeb/WebAudio/AudioContext.cpp b/Userland/Libraries/LibWeb/WebAudio/AudioContext.cpp index 33d2e7e7448b0e..943496c984c5a0 100644 --- a/Userland/Libraries/LibWeb/WebAudio/AudioContext.cpp +++ b/Userland/Libraries/LibWeb/WebAudio/AudioContext.cpp @@ -289,9 +289,9 @@ WebIDL::ExceptionOr> AudioContext::close() return JS::NonnullGCPtr { verify_cast(*promise->promise()) }; } -void AudioContext::queue_a_media_element_task(JS::SafeFunction steps) +void AudioContext::queue_a_media_element_task(Function steps) { - auto task = HTML::Task::create(m_media_element_event_task_source.source, HTML::current_settings_object().responsible_document(), move(steps)); + auto task = HTML::Task::create(vm(), m_media_element_event_task_source.source, HTML::current_settings_object().responsible_document(), JS::create_heap_function(heap(), move(steps))); HTML::main_thread_event_loop().task_queue().add(move(task)); } diff --git a/Userland/Libraries/LibWeb/WebAudio/AudioContext.h b/Userland/Libraries/LibWeb/WebAudio/AudioContext.h index 835c0c4040dc14..b0930c497cccbc 100644 --- a/Userland/Libraries/LibWeb/WebAudio/AudioContext.h +++ b/Userland/Libraries/LibWeb/WebAudio/AudioContext.h @@ -54,7 +54,7 @@ class AudioContext final : public BaseAudioContext { bool m_suspended_by_user = false; HTML::UniqueTaskSource m_media_element_event_task_source {}; - void queue_a_media_element_task(JS::SafeFunction steps); + void queue_a_media_element_task(Function steps); bool start_rendering_audio_graph(); };