LibWeb: Forbid interleaving execution of HTML tasks with same source

From HTML spec https://html.spec.whatwg.org/#definitions-3
"... Note that in this setup, the processing model still enforces that
the user agent would never process events from any one task source out
of order."

I can't come up with an example that is fixed by this change. However,
debugging a bug caused by violating this assumption from the spec is
likely to be very painful.
This commit is contained in:
Aliaksandr Kalenik
2024-04-09 19:41:28 +02:00
committed by Andreas Kling
parent ba633882bf
commit 664611bae4
3 changed files with 46 additions and 1 deletions

View File

@@ -27,6 +27,9 @@ EventLoop::EventLoop()
{
m_task_queue = heap().allocate_without_realm<TaskQueue>(*this);
m_microtask_queue = heap().allocate_without_realm<TaskQueue>(*this);
for (size_t i = 0; i < m_blocked_task_sources.size(); ++i)
m_blocked_task_sources[i] = false;
}
EventLoop::~EventLoop() = default;
@@ -59,6 +62,31 @@ EventLoop& main_thread_event_loop()
return *static_cast<Bindings::WebEngineCustomData*>(Bindings::main_thread_vm().custom_data())->event_loop;
}
bool EventLoop::is_task_source_blocked(Task::Source source) const
{
if (source == Task::Source::Unspecified)
return false;
if (static_cast<size_t>(to_underlying(source)) < m_blocked_task_sources.size())
return m_blocked_task_sources[to_underlying(source)];
return false;
}
void EventLoop::block_task_source(Task::Source source)
{
if (source == Task::Source::Unspecified)
return;
if (static_cast<size_t>(to_underlying(source)) < m_blocked_task_sources.size())
m_blocked_task_sources[to_underlying(source)] = true;
}
void EventLoop::unblock_task_source(Task::Source source)
{
if (source == Task::Source::Unspecified)
return;
if (static_cast<size_t>(to_underlying(source)) < m_blocked_task_sources.size())
m_blocked_task_sources[to_underlying(source)] = false;
}
// https://html.spec.whatwg.org/multipage/webappapis.html#spin-the-event-loop
void EventLoop::spin_until(JS::SafeFunction<bool()> goal_condition)
{
@@ -119,11 +147,13 @@ void EventLoop::spin_processing_tasks_with_source_until(Task::Source source, JS:
return task.source() == source && task.is_runnable();
});
block_task_source(source);
for (auto& task : tasks) {
m_currently_running_task = task.ptr();
task->execute();
m_currently_running_task = nullptr;
}
unblock_task_source(source);
}
// FIXME: Remove the platform event loop plugin so that this doesn't look out of place
@@ -164,6 +194,8 @@ void EventLoop::process()
oldest_task = task_queue.take_first_runnable();
if (oldest_task) {
block_task_source(oldest_task->source());
// 5. Set the event loop's currently running task to oldestTask.
m_currently_running_task = oldest_task.ptr();
@@ -172,6 +204,8 @@ void EventLoop::process()
// 7. Set the event loop's currently running task back to null.
m_currently_running_task = nullptr;
unblock_task_source(oldest_task->source());
}
// 8. Microtasks: Perform a microtask checkpoint.