LibWeb: Make EventLoop, TaskQueue, and Task GC-allocated

...and use HeapFunction instead of SafeFunction for task steps.

Since there is only one EventLoop per process, it lives as a global
handle in the VM custom data.

This makes it much easier to reason about lifetimes of tasks, task
steps, and random stuff captured by them.
This commit is contained in:
Andreas Kling 2024-04-04 12:06:50 +02:00
commit 2ef37c0b06
Notes: sideshowbarker 2024-07-17 02:14:39 +09:00
20 changed files with 167 additions and 124 deletions

View file

@ -1097,7 +1097,7 @@ void Animation::update_finished_state(DidSeek did_seek, SynchronouslyNotify sync
// steps: // steps:
if (current_finished_state && !m_is_finished) { if (current_finished_state && !m_is_finished) {
// 1. Let finish notification steps refer to the following procedure: // 1. Let finish notification steps refer to the following procedure:
JS::SafeFunction<void()> finish_notification_steps = [&]() { auto finish_notification_steps = JS::create_heap_function(heap(), [this, &realm]() {
// 1. If animations play state is not equal to finished, abort these steps. // 1. If animations play state is not equal to finished, abort these steps.
if (play_state() != Bindings::AnimationPlayState::Finished) if (play_state() != Bindings::AnimationPlayState::Finished)
return; return;
@ -1135,13 +1135,14 @@ void Animation::update_finished_state(DidSeek did_seek, SynchronouslyNotify sync
else { else {
// Manually create a task so its ID can be saved // Manually create a task so its ID can be saved
auto& document = verify_cast<HTML::Window>(realm.global_object()).associated_document(); auto& document = verify_cast<HTML::Window>(realm.global_object()).associated_document();
auto task = HTML::Task::create(HTML::Task::Source::DOMManipulation, &document, [this, finish_event]() { auto task = HTML::Task::create(vm(), HTML::Task::Source::DOMManipulation, &document,
dispatch_event(finish_event); JS::create_heap_function(heap(), [this, finish_event]() {
}); dispatch_event(finish_event);
}));
m_pending_finish_microtask_id = task->id(); 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 // 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. // 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; 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 // 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. // animation unless there is already a microtask queued to run those steps for animation.
else if (!m_pending_finish_microtask_id.has_value()) { else if (!m_pending_finish_microtask_id.has_value()) {
auto& document = verify_cast<HTML::Window>(realm.global_object()).associated_document(); auto& document = verify_cast<HTML::Window>(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(); 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(move(task));
} }

View file

@ -86,6 +86,9 @@ ErrorOr<void> initialize_main_thread_vm()
// This avoids doing an exhaustive garbage collection on process exit. // This avoids doing an exhaustive garbage collection on process exit.
s_main_thread_vm->ref(); s_main_thread_vm->ref();
auto& custom_data = verify_cast<WebEngineCustomData>(*s_main_thread_vm->custom_data());
custom_data.event_loop = s_main_thread_vm->heap().allocate_without_realm<HTML::EventLoop>();
// These strings could potentially live on the VM similar to CommonPropertyNames. // These strings could potentially live on the VM similar to CommonPropertyNames.
DOM::MutationType::initialize_strings(); DOM::MutationType::initialize_strings();
HTML::AttributeNames::initialize_strings(); HTML::AttributeNames::initialize_strings();
@ -103,8 +106,6 @@ ErrorOr<void> initialize_main_thread_vm()
XHR::EventNames::initialize_strings(); XHR::EventNames::initialize_strings();
XLink::AttributeNames::initialize_strings(); XLink::AttributeNames::initialize_strings();
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 // 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> { 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 }. // 1. If O is a WindowProxy object, or implements Location, then return Completion { [[Type]]: throw, [[Value]]: a new TypeError }.

View file

@ -40,7 +40,7 @@ struct WebEngineCustomData final : public JS::VM::CustomData {
virtual void spin_event_loop_until(JS::SafeFunction<bool()> goal_condition) override; virtual void spin_event_loop_until(JS::SafeFunction<bool()> goal_condition) override;
HTML::EventLoop event_loop; JS::Handle<HTML::EventLoop> event_loop;
// FIXME: These should only be on similar-origin window agents, but we don't currently differentiate agent types. // FIXME: These should only be on similar-origin window agents, but we don't currently differentiate agent types.

View file

@ -3166,7 +3166,7 @@ void Document::unload(JS::GCPtr<Document>)
auto intend_to_store_in_bfcache = false; auto intend_to_store_in_bfcache = false;
// 6. Let eventLoop be oldDocument's relevant agent's event loop. // 6. Let eventLoop be oldDocument's relevant agent's event loop.
auto& event_loop = verify_cast<Bindings::WebEngineCustomData>(*vm.custom_data()).event_loop; auto& event_loop = *verify_cast<Bindings::WebEngineCustomData>(*vm.custom_data()).event_loop;
// 7. Increase eventLoop's termination nesting level by 1. // 7. Increase eventLoop's termination nesting level by 1.
event_loop.increment_termination_nesting_level(); event_loop.increment_termination_nesting_level();

View file

@ -785,9 +785,9 @@ void Element::make_html_uppercased_qualified_name()
} }
// https://html.spec.whatwg.org/multipage/webappapis.html#queue-an-element-task // https://html.spec.whatwg.org/multipage/webappapis.html#queue-an-element-task
int Element::queue_an_element_task(HTML::Task::Source source, JS::SafeFunction<void()> steps) int Element::queue_an_element_task(HTML::Task::Source source, Function<void()> 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(); auto id = task->id();
HTML::main_thread_event_loop().task_queue().add(move(task)); HTML::main_thread_event_loop().task_queue().add(move(task));

View file

@ -190,7 +190,8 @@ public:
void set_custom_properties(Optional<CSS::Selector::PseudoElement::Type>, HashMap<FlyString, CSS::StyleProperty> custom_properties); void set_custom_properties(Optional<CSS::Selector::PseudoElement::Type>, HashMap<FlyString, CSS::StyleProperty> custom_properties);
[[nodiscard]] HashMap<FlyString, CSS::StyleProperty> const& custom_properties(Optional<CSS::Selector::PseudoElement::Type>) const; [[nodiscard]] HashMap<FlyString, CSS::StyleProperty> const& custom_properties(Optional<CSS::Selector::PseudoElement::Type>) const;
int queue_an_element_task(HTML::Task::Source, JS::SafeFunction<void()>); // NOTE: The function is wrapped in a JS::HeapFunction immediately.
int queue_an_element_task(HTML::Task::Source, Function<void()>);
bool is_void_element() const; bool is_void_element() const;
bool serializes_as_void() const; bool serializes_as_void() const;

View file

@ -11,7 +11,7 @@
namespace Web::Fetch::Infrastructure { namespace Web::Fetch::Infrastructure {
// https://fetch.spec.whatwg.org/#queue-a-fetch-task // https://fetch.spec.whatwg.org/#queue-a-fetch-task
int queue_fetch_task(JS::Object& task_destination, JS::SafeFunction<void()> algorithm) int queue_fetch_task(JS::Object& task_destination, Function<void()> algorithm)
{ {
// FIXME: 1. If taskDestination is a parallel queue, then enqueue algorithm to taskDestination. // 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<void()> algo
// AD-HOC: This overload allows tracking the queued task within the fetch controller so that we may cancel queued tasks // 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. // when the spec indicates that we must stop an ongoing fetch.
int queue_fetch_task(JS::NonnullGCPtr<FetchController> fetch_controller, JS::Object& task_destination, JS::SafeFunction<void()> algorithm) int queue_fetch_task(JS::NonnullGCPtr<FetchController> fetch_controller, JS::Object& task_destination, Function<void()> algorithm)
{ {
auto fetch_task_id = fetch_controller->next_fetch_task_id(); auto fetch_task_id = fetch_controller->next_fetch_task_id();

View file

@ -17,7 +17,7 @@ namespace Web::Fetch::Infrastructure {
// FIXME: 'or a parallel queue' // FIXME: 'or a parallel queue'
using TaskDestination = Variant<Empty, JS::NonnullGCPtr<JS::Object>>; using TaskDestination = Variant<Empty, JS::NonnullGCPtr<JS::Object>>;
int queue_fetch_task(JS::Object&, JS::SafeFunction<void()>); int queue_fetch_task(JS::Object&, Function<void()>);
int queue_fetch_task(JS::NonnullGCPtr<FetchController>, JS::Object&, JS::SafeFunction<void()>); int queue_fetch_task(JS::NonnullGCPtr<FetchController>, JS::Object&, Function<void()>);
} }

View file

@ -22,13 +22,24 @@
namespace Web::HTML { namespace Web::HTML {
EventLoop::EventLoop() EventLoop::EventLoop()
: m_task_queue(*this)
, m_microtask_queue(*this)
{ {
m_task_queue = heap().allocate_without_realm<TaskQueue>(*this);
m_microtask_queue = heap().allocate_without_realm<TaskQueue>(*this);
} }
EventLoop::~EventLoop() = default; 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() void EventLoop::schedule()
{ {
if (!m_system_event_loop_timer) { if (!m_system_event_loop_timer) {
@ -41,15 +52,9 @@ void EventLoop::schedule()
m_system_event_loop_timer->restart(); m_system_event_loop_timer->restart();
} }
void EventLoop::set_vm(JS::VM& vm)
{
VERIFY(!m_vm);
m_vm = &vm;
}
EventLoop& main_thread_event_loop() EventLoop& main_thread_event_loop()
{ {
return static_cast<Bindings::WebEngineCustomData*>(Bindings::main_thread_vm().custom_data())->event_loop; return *static_cast<Bindings::WebEngineCustomData*>(Bindings::main_thread_vm().custom_data())->event_loop;
} }
// https://html.spec.whatwg.org/multipage/webappapis.html#spin-the-event-loop // https://html.spec.whatwg.org/multipage/webappapis.html#spin-the-event-loop
@ -62,8 +67,9 @@ void EventLoop::spin_until(JS::SafeFunction<bool()> goal_condition)
// 3. Let old stack be a copy of the JavaScript execution context stack. // 3. Let old stack be a copy of the JavaScript execution context stack.
// 4. Empty the JavaScript execution context stack. // 4. Empty the JavaScript execution context stack.
m_vm->save_execution_context_stack(); auto& vm = this->vm();
m_vm->clear_execution_context_stack(); vm.save_execution_context_stack();
vm.clear_execution_context_stack();
// 5. Perform a microtask checkpoint. // 5. Perform a microtask checkpoint.
perform_a_microtask_checkpoint(); perform_a_microtask_checkpoint();
@ -78,7 +84,7 @@ void EventLoop::spin_until(JS::SafeFunction<bool()> goal_condition)
Platform::EventLoopPlugin::the().spin_until([&] { Platform::EventLoopPlugin::the().spin_until([&] {
if (goal_condition()) if (goal_condition())
return true; return true;
if (m_task_queue.has_runnable_tasks()) { if (m_task_queue->has_runnable_tasks()) {
schedule(); schedule();
// FIXME: Remove the platform event loop plugin so that this doesn't look out of place // FIXME: Remove the platform event loop plugin so that this doesn't look out of place
Core::EventLoop::current().wake(); Core::EventLoop::current().wake();
@ -86,7 +92,7 @@ void EventLoop::spin_until(JS::SafeFunction<bool()> goal_condition)
return 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. // 7. Stop task, allowing whatever algorithm that invoked it to resume.
// NOTE: This is achieved by returning from the function. // NOTE: This is achieved by returning from the function.
@ -94,8 +100,9 @@ void EventLoop::spin_until(JS::SafeFunction<bool()> goal_condition)
void EventLoop::spin_processing_tasks_with_source_until(Task::Source source, JS::SafeFunction<bool()> goal_condition) void EventLoop::spin_processing_tasks_with_source_until(Task::Source source, JS::SafeFunction<bool()> goal_condition)
{ {
m_vm->save_execution_context_stack(); auto& vm = this->vm();
m_vm->clear_execution_context_stack(); vm.save_execution_context_stack();
vm.clear_execution_context_stack();
perform_a_microtask_checkpoint(); 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([&] { Platform::EventLoopPlugin::the().spin_until([&] {
if (goal_condition()) if (goal_condition())
return true; return true;
if (m_task_queue.has_runnable_tasks()) { if (m_task_queue->has_runnable_tasks()) {
auto tasks = m_task_queue.take_tasks_matching([&](auto& task) { auto tasks = m_task_queue->take_tasks_matching([&](auto& task) {
return task.source() == source && task.is_runnable(); return task.source() == source && task.is_runnable();
}); });
for (auto& task : tasks.value()) { for (auto& task : tasks) {
m_currently_running_task = task.ptr(); m_currently_running_task = task.ptr();
task->execute(); task->execute();
m_currently_running_task = nullptr; m_currently_running_task = nullptr;
@ -126,7 +133,7 @@ void EventLoop::spin_processing_tasks_with_source_until(Task::Source source, JS:
schedule(); schedule();
m_vm->restore_execution_context_stack(); vm.restore_execution_context_stack();
} }
// https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model // 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: // An event loop must continually run through the following steps for as long as it exists:
// 1. Let oldestTask be null. // 1. Let oldestTask be null.
OwnPtr<Task> oldest_task; JS::GCPtr<Task> oldest_task;
// 2. Let taskStartTime be the current high resolution time. // 2. Let taskStartTime be the current high resolution time.
// FIXME: 'current high resolution time' in hr-time-3 takes a global object, // 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, // 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. // 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. // 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. // 4. Set oldestTask to the first runnable task in taskQueue, and remove it from taskQueue.
oldest_task = task_queue.take_first_runnable(); oldest_task = task_queue.take_first_runnable();
@ -333,7 +340,7 @@ void EventLoop::process()
// - this event loop's microtask queue is empty // - this event loop's microtask queue is empty
// - hasARenderingOpportunity is false // - hasARenderingOpportunity is false
// FIXME: has_a_rendering_opportunity is always true // 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. // 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(); 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. // 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 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(); schedule();
// For each doc of docs, process top layer removals given doc. // 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 // 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<void()> steps) int queue_global_task(HTML::Task::Source source, JS::Object& global_object, Function<void()> steps)
{ {
// 1. Let event loop be global's relevant agent's event loop. // 1. Let event loop be global's relevant agent's event loop.
auto& global_custom_data = verify_cast<Bindings::WebEngineCustomData>(*global_object.vm().custom_data()); auto& global_custom_data = verify_cast<Bindings::WebEngineCustomData>(*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. // 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 // https://html.spec.whatwg.org/#queue-a-microtask
void queue_a_microtask(DOM::Document const* document, JS::SafeFunction<void()> steps) void queue_a_microtask(DOM::Document const* document, Function<void()> steps)
{ {
// 1. If event loop was not given, set event loop to the implied event loop. // 1. If event loop was not given, set event loop to the implied event loop.
auto& event_loop = HTML::main_thread_event_loop(); auto& event_loop = HTML::main_thread_event_loop();
@ -397,12 +405,13 @@ void queue_a_microtask(DOM::Document const* document, JS::SafeFunction<void()> s
// 4. Set microtask's steps to steps. // 4. Set microtask's steps to steps.
// 5. Set microtask's source to the microtask task source. // 5. Set microtask's source to the microtask task source.
// 6. Set microtask's document to document. // 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. // FIXME: 7. Set microtask's script evaluation environment settings object set to an empty set.
// 8. Enqueue microtask on event loop's microtask queue. // 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() void perform_a_microtask_checkpoint()
@ -421,9 +430,9 @@ void EventLoop::perform_a_microtask_checkpoint()
m_performing_a_microtask_checkpoint = true; m_performing_a_microtask_checkpoint = true;
// 3. While the event loop's microtask queue is not empty: // 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. // 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. // 2. Set the event loop's currently running task to oldestMicrotask.
m_currently_running_task = oldest_microtask; 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>, EnvironmentSettingsObject& environment_settings_object) void EventLoop::register_environment_settings_object(Badge<EnvironmentSettingsObject>, 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>, EnvironmentSettingsObject& environment_settings_object) void EventLoop::unregister_environment_settings_object(Badge<EnvironmentSettingsObject>, 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); VERIFY(did_remove);
} }

View file

@ -15,7 +15,9 @@
namespace Web::HTML { namespace Web::HTML {
class EventLoop { class EventLoop : public JS::Cell {
JS_CELL(EventLoop, Cell);
public: public:
enum class Type { enum class Type {
// https://html.spec.whatwg.org/multipage/webappapis.html#window-event-loop // https://html.spec.whatwg.org/multipage/webappapis.html#window-event-loop
@ -26,16 +28,15 @@ public:
Worklet, Worklet,
}; };
EventLoop(); virtual ~EventLoop() override;
~EventLoop();
Type type() const { return m_type; } Type type() const { return m_type; }
TaskQueue& task_queue() { return m_task_queue; } TaskQueue& task_queue() { return *m_task_queue; }
TaskQueue const& task_queue() const { return m_task_queue; } TaskQueue const& task_queue() const { return *m_task_queue; }
TaskQueue& microtask_queue() { return m_microtask_queue; } TaskQueue& microtask_queue() { return *m_microtask_queue; }
TaskQueue const& microtask_queue() const { return m_microtask_queue; } TaskQueue const& microtask_queue() const { return *m_microtask_queue; }
void spin_until(JS::SafeFunction<bool()> goal_condition); void spin_until(JS::SafeFunction<bool()> goal_condition);
void spin_processing_tasks_with_source_until(Task::Source, JS::SafeFunction<bool()> goal_condition); void spin_processing_tasks_with_source_until(Task::Source, JS::SafeFunction<bool()> goal_condition);
@ -48,11 +49,6 @@ public:
Task const* currently_running_task() const { return m_currently_running_task; } 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 schedule();
void perform_a_microtask_checkpoint(); void perform_a_microtask_checkpoint();
@ -79,21 +75,23 @@ public:
bool execution_paused() const { return m_execution_paused; } bool execution_paused() const { return m_execution_paused; }
private: private:
EventLoop();
virtual void visit_edges(Visitor&) override;
Type m_type { Type::Window }; Type m_type { Type::Window };
TaskQueue m_task_queue; JS::GCPtr<TaskQueue> m_task_queue;
TaskQueue m_microtask_queue; JS::GCPtr<TaskQueue> m_microtask_queue;
// https://html.spec.whatwg.org/multipage/webappapis.html#currently-running-task // https://html.spec.whatwg.org/multipage/webappapis.html#currently-running-task
Task* m_currently_running_task { nullptr }; JS::GCPtr<Task> m_currently_running_task { nullptr };
// https://html.spec.whatwg.org/multipage/webappapis.html#last-render-opportunity-time // https://html.spec.whatwg.org/multipage/webappapis.html#last-render-opportunity-time
double m_last_render_opportunity_time { 0 }; double m_last_render_opportunity_time { 0 };
// https://html.spec.whatwg.org/multipage/webappapis.html#last-idle-period-start-time // https://html.spec.whatwg.org/multipage/webappapis.html#last-idle-period-start-time
double m_last_idle_period_start_time { 0 }; double m_last_idle_period_start_time { 0 };
JS::VM* m_vm { nullptr };
RefPtr<Platform::Timer> m_system_event_loop_timer; RefPtr<Platform::Timer> m_system_event_loop_timer;
// https://html.spec.whatwg.org/#performing-a-microtask-checkpoint // https://html.spec.whatwg.org/#performing-a-microtask-checkpoint
@ -102,7 +100,8 @@ private:
Vector<WeakPtr<DOM::Document>> m_documents; Vector<WeakPtr<DOM::Document>> m_documents;
// Used to implement step 4 of "perform a microtask checkpoint". // Used to implement step 4 of "perform a microtask checkpoint".
Vector<JS::NonnullGCPtr<EnvironmentSettingsObject>> m_related_environment_settings_objects; // NOTE: These are weak references! ESO registers and unregisters itself from the event loop manually.
Vector<RawPtr<EnvironmentSettingsObject>> m_related_environment_settings_objects;
// https://html.spec.whatwg.org/multipage/webappapis.html#backup-incumbent-settings-object-stack // https://html.spec.whatwg.org/multipage/webappapis.html#backup-incumbent-settings-object-stack
Vector<JS::NonnullGCPtr<EnvironmentSettingsObject>> m_backup_incumbent_settings_object_stack; Vector<JS::NonnullGCPtr<EnvironmentSettingsObject>> m_backup_incumbent_settings_object_stack;
@ -116,8 +115,8 @@ private:
}; };
EventLoop& main_thread_event_loop(); EventLoop& main_thread_event_loop();
int queue_global_task(HTML::Task::Source, JS::Object&, JS::SafeFunction<void()> steps); int queue_global_task(HTML::Task::Source, JS::Object&, Function<void()> steps);
void queue_a_microtask(DOM::Document const*, JS::SafeFunction<void()> steps); void queue_a_microtask(DOM::Document const*, Function<void()> steps);
void perform_a_microtask_checkpoint(); void perform_a_microtask_checkpoint();
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org> * Copyright (c) 2021-2024, Andreas Kling <kling@serenityos.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -10,25 +10,41 @@
namespace Web::HTML { namespace Web::HTML {
JS_DEFINE_ALLOCATOR(Task);
static IDAllocator s_unique_task_source_allocator { static_cast<int>(Task::Source::UniqueTaskSourceStart) }; static IDAllocator s_unique_task_source_allocator { static_cast<int>(Task::Source::UniqueTaskSourceStart) };
static IDAllocator s_task_id_allocator; static IDAllocator s_task_id_allocator;
Task::Task(Source source, DOM::Document const* document, JS::SafeFunction<void()> steps) JS::NonnullGCPtr<Task> Task::create(JS::VM& vm, Source source, JS::GCPtr<DOM::Document const> document, JS::NonnullGCPtr<JS::HeapFunction<void()>> steps)
{
return vm.heap().allocate_without_realm<Task>(source, document, move(steps));
}
Task::Task(Source source, JS::GCPtr<DOM::Document const> document, JS::NonnullGCPtr<JS::HeapFunction<void()>> steps)
: m_id(s_task_id_allocator.allocate()) : m_id(s_task_id_allocator.allocate())
, m_source(source) , m_source(source)
, m_steps(move(steps)) , m_steps(steps)
, m_document(JS::make_handle(document)) , m_document(document)
{ {
} }
Task::~Task() Task::~Task() = default;
void Task::finalize()
{ {
s_unique_task_source_allocator.deallocate(m_id); 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() void Task::execute()
{ {
m_steps(); m_steps->function()();
} }
// https://html.spec.whatwg.org/#concept-task-runnable // https://html.spec.whatwg.org/#concept-task-runnable

View file

@ -1,15 +1,13 @@
/* /*
* Copyright (c) 2021-2022, Andreas Kling <kling@serenityos.org> * Copyright (c) 2021-2024, Andreas Kling <kling@serenityos.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#pragma once #pragma once
#include <AK/Function.h> #include <LibJS/Heap/Cell.h>
#include <AK/NonnullOwnPtr.h> #include <LibJS/Heap/CellAllocator.h>
#include <AK/RefPtr.h>
#include <LibJS/Heap/Handle.h>
#include <LibJS/SafeFunction.h> #include <LibJS/SafeFunction.h>
#include <LibWeb/Forward.h> #include <LibWeb/Forward.h>
@ -17,7 +15,10 @@ namespace Web::HTML {
struct UniqueTaskSource; struct UniqueTaskSource;
class Task { class Task final : public JS::Cell {
JS_CELL(Task, Cell);
JS_DECLARE_ALLOCATOR(Task);
public: public:
// https://html.spec.whatwg.org/multipage/webappapis.html#generic-task-sources // https://html.spec.whatwg.org/multipage/webappapis.html#generic-task-sources
enum class Source { enum class Source {
@ -59,11 +60,10 @@ public:
UniqueTaskSourceStart UniqueTaskSourceStart
}; };
static NonnullOwnPtr<Task> create(Source source, DOM::Document const* document, JS::SafeFunction<void()> steps) static JS::NonnullGCPtr<Task> create(JS::VM&, Source, JS::GCPtr<DOM::Document const>, JS::NonnullGCPtr<JS::HeapFunction<void()>> steps);
{
return adopt_own(*new Task(source, document, move(steps))); virtual ~Task() override;
} virtual void finalize() override;
~Task();
int id() const { return m_id; } int id() const { return m_id; }
Source source() const { return m_source; } Source source() const { return m_source; }
@ -74,12 +74,14 @@ public:
bool is_runnable() const; bool is_runnable() const;
private: private:
Task(Source, DOM::Document const*, JS::SafeFunction<void()> steps); Task(Source, JS::GCPtr<DOM::Document const>, JS::NonnullGCPtr<JS::HeapFunction<void()>> steps);
virtual void visit_edges(Visitor&) override;
int m_id { 0 }; int m_id { 0 };
Source m_source { Source::Unspecified }; Source m_source { Source::Unspecified };
JS::SafeFunction<void()> m_steps; JS::NonnullGCPtr<JS::HeapFunction<void()>> m_steps;
JS::Handle<DOM::Document const> m_document; JS::GCPtr<DOM::Document const> m_document;
}; };
struct UniqueTaskSource { struct UniqueTaskSource {

View file

@ -1,9 +1,10 @@
/* /*
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org> * Copyright (c) 2021-2024, Andreas Kling <kling@serenityos.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <LibJS/Heap/MarkedVector.h>
#include <LibWeb/HTML/EventLoop/EventLoop.h> #include <LibWeb/HTML/EventLoop/EventLoop.h>
#include <LibWeb/HTML/EventLoop/TaskQueue.h> #include <LibWeb/HTML/EventLoop/TaskQueue.h>
@ -16,15 +17,23 @@ TaskQueue::TaskQueue(HTML::EventLoop& event_loop)
TaskQueue::~TaskQueue() = default; TaskQueue::~TaskQueue() = default;
void TaskQueue::add(NonnullOwnPtr<Task> task) void TaskQueue::visit_edges(Visitor& visitor)
{ {
m_tasks.append(move(task)); Base::visit_edges(visitor);
m_event_loop.schedule(); visitor.visit(m_event_loop);
for (auto& task : m_tasks)
visitor.visit(task);
} }
OwnPtr<Task> TaskQueue::take_first_runnable() void TaskQueue::add(JS::NonnullGCPtr<Task> task)
{ {
if (m_event_loop.execution_paused()) m_tasks.append(task);
m_event_loop->schedule();
}
JS::GCPtr<Task> TaskQueue::take_first_runnable()
{
if (m_event_loop->execution_paused())
return nullptr; return nullptr;
for (size_t i = 0; i < m_tasks.size(); ++i) { for (size_t i = 0; i < m_tasks.size(); ++i) {
@ -36,7 +45,7 @@ OwnPtr<Task> TaskQueue::take_first_runnable()
bool TaskQueue::has_runnable_tasks() const bool TaskQueue::has_runnable_tasks() const
{ {
if (m_event_loop.execution_paused()) if (m_event_loop->execution_paused())
return false; return false;
for (auto& task : m_tasks) { for (auto& task : m_tasks) {
@ -53,15 +62,15 @@ void TaskQueue::remove_tasks_matching(Function<bool(HTML::Task const&)> filter)
}); });
} }
ErrorOr<Vector<NonnullOwnPtr<Task>>> TaskQueue::take_tasks_matching(Function<bool(HTML::Task const&)> filter) JS::MarkedVector<JS::NonnullGCPtr<Task>> TaskQueue::take_tasks_matching(Function<bool(HTML::Task const&)> filter)
{ {
Vector<NonnullOwnPtr<Task>> matching_tasks; JS::MarkedVector<JS::NonnullGCPtr<Task>> matching_tasks(heap());
for (size_t i = 0; i < m_tasks.size();) { for (size_t i = 0; i < m_tasks.size();) {
auto& task = m_tasks.at(i); auto& task = m_tasks.at(i);
if (filter(*task)) { if (filter(*task)) {
TRY(matching_tasks.try_append(move(task))); matching_tasks.append(task);
m_tasks.remove(i); m_tasks.remove(i);
} else { } else {
++i; ++i;

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org> * Copyright (c) 2021-2024, Andreas Kling <kling@serenityos.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -7,24 +7,27 @@
#pragma once #pragma once
#include <AK/Queue.h> #include <AK/Queue.h>
#include <LibJS/Heap/Cell.h>
#include <LibWeb/HTML/EventLoop/Task.h> #include <LibWeb/HTML/EventLoop/Task.h>
namespace Web::HTML { namespace Web::HTML {
class TaskQueue { class TaskQueue : public JS::Cell {
JS_CELL(TaskQueue, Cell);
public: public:
explicit TaskQueue(HTML::EventLoop&); explicit TaskQueue(HTML::EventLoop&);
~TaskQueue(); virtual ~TaskQueue() override;
bool is_empty() const { return m_tasks.is_empty(); } bool is_empty() const { return m_tasks.is_empty(); }
bool has_runnable_tasks() const; bool has_runnable_tasks() const;
void add(NonnullOwnPtr<HTML::Task>); void add(JS::NonnullGCPtr<HTML::Task>);
OwnPtr<HTML::Task> take_first_runnable(); JS::GCPtr<HTML::Task> take_first_runnable();
void enqueue(NonnullOwnPtr<HTML::Task> task) { add(move(task)); } void enqueue(JS::NonnullGCPtr<HTML::Task> task) { add(task); }
OwnPtr<HTML::Task> dequeue() JS::GCPtr<HTML::Task> dequeue()
{ {
if (m_tasks.is_empty()) if (m_tasks.is_empty())
return {}; return {};
@ -32,14 +35,16 @@ public:
} }
void remove_tasks_matching(Function<bool(HTML::Task const&)>); void remove_tasks_matching(Function<bool(HTML::Task const&)>);
ErrorOr<Vector<NonnullOwnPtr<Task>>> take_tasks_matching(Function<bool(HTML::Task const&)>); JS::MarkedVector<JS::NonnullGCPtr<Task>> take_tasks_matching(Function<bool(HTML::Task const&)>);
Task const* last_added_task() const; Task const* last_added_task() const;
private: private:
HTML::EventLoop& m_event_loop; virtual void visit_edges(Visitor&) override;
Vector<NonnullOwnPtr<HTML::Task>> m_tasks; JS::NonnullGCPtr<HTML::EventLoop> m_event_loop;
Vector<JS::NonnullGCPtr<HTML::Task>> m_tasks;
}; };
} }

View file

@ -74,7 +74,7 @@ void HTMLMediaElement::finalize()
} }
// https://html.spec.whatwg.org/multipage/media.html#queue-a-media-element-task // https://html.spec.whatwg.org/multipage/media.html#queue-a-media-element-task
void HTMLMediaElement::queue_a_media_element_task(JS::SafeFunction<void()> steps) void HTMLMediaElement::queue_a_media_element_task(Function<void()> 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 // 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. // 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 // https://html.spec.whatwg.org/multipage/media.html#media-element-load-algorithm
WebIDL::ExceptionOr<void> HTMLMediaElement::load_element() WebIDL::ExceptionOr<void> HTMLMediaElement::load_element()
{ {
auto& vm = this->vm();
m_first_data_load_event_since_load_start = true; m_first_data_load_event_since_load_start = true;
// FIXME: 1. Abort any already-running instance of the resource selection algorithm for this element. // 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. // 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(); 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 // 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. // reject those promises in the order the corresponding tasks were queued.

View file

@ -40,7 +40,8 @@ public:
virtual bool is_focusable() const override { return true; } virtual bool is_focusable() const override { return true; }
void queue_a_media_element_task(JS::SafeFunction<void()> steps); // NOTE: The function is wrapped in a JS::HeapFunction immediately.
void queue_a_media_element_task(Function<void()>);
JS::GCPtr<MediaError> error() const { return m_error; } JS::GCPtr<MediaError> error() const { return m_error; }
WebIDL::ExceptionOr<void> set_decoder_error(String error_message); WebIDL::ExceptionOr<void> set_decoder_error(String error_message);

View file

@ -42,6 +42,7 @@ void EnvironmentSettingsObject::initialize(JS::Realm& realm)
void EnvironmentSettingsObject::visit_edges(Cell::Visitor& visitor) void EnvironmentSettingsObject::visit_edges(Cell::Visitor& visitor)
{ {
Base::visit_edges(visitor); Base::visit_edges(visitor);
visitor.visit(m_responsible_event_loop);
visitor.visit(target_browsing_context); visitor.visit(target_browsing_context);
visitor.visit(m_module_map); visitor.visit(m_module_map);
visitor.ignore(m_outstanding_rejected_promises_weak_set); visitor.ignore(m_outstanding_rejected_promises_weak_set);
@ -84,8 +85,8 @@ EventLoop& EnvironmentSettingsObject::responsible_event_loop()
auto& vm = global_object().vm(); auto& vm = global_object().vm();
auto& event_loop = verify_cast<Bindings::WebEngineCustomData>(vm.custom_data())->event_loop; auto& event_loop = verify_cast<Bindings::WebEngineCustomData>(vm.custom_data())->event_loop;
m_responsible_event_loop = &event_loop; m_responsible_event_loop = event_loop;
return event_loop; return *event_loop;
} }
// https://html.spec.whatwg.org/multipage/webappapis.html#check-if-we-can-run-script // https://html.spec.whatwg.org/multipage/webappapis.html#check-if-we-can-run-script

View file

@ -122,7 +122,7 @@ private:
NonnullOwnPtr<JS::ExecutionContext> m_realm_execution_context; NonnullOwnPtr<JS::ExecutionContext> m_realm_execution_context;
JS::GCPtr<ModuleMap> m_module_map; JS::GCPtr<ModuleMap> m_module_map;
EventLoop* m_responsible_event_loop { nullptr }; JS::GCPtr<EventLoop> m_responsible_event_loop;
// https://html.spec.whatwg.org/multipage/webappapis.html#outstanding-rejected-promises-weak-set // 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. // 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.

View file

@ -289,9 +289,9 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::Promise>> AudioContext::close()
return JS::NonnullGCPtr { verify_cast<JS::Promise>(*promise->promise()) }; return JS::NonnullGCPtr { verify_cast<JS::Promise>(*promise->promise()) };
} }
void AudioContext::queue_a_media_element_task(JS::SafeFunction<void()> steps) void AudioContext::queue_a_media_element_task(Function<void()> 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)); HTML::main_thread_event_loop().task_queue().add(move(task));
} }

View file

@ -54,7 +54,7 @@ private:
bool m_suspended_by_user = false; bool m_suspended_by_user = false;
HTML::UniqueTaskSource m_media_element_event_task_source {}; HTML::UniqueTaskSource m_media_element_event_task_source {};
void queue_a_media_element_task(JS::SafeFunction<void()> steps); void queue_a_media_element_task(Function<void()> steps);
bool start_rendering_audio_graph(); bool start_rendering_audio_graph();
}; };