ladybird/Libraries/LibWeb/HTML/EventLoop/TaskQueue.cpp
Jelle Raaijmakers a5000d07c0 LibWeb: Prevent running permanently unrunnable tasks in EventLoop
In `::spin_processing_tasks_with_source_until()`, we would first take a
set of tasks based on a filter, and then run them one by one. If there
was more than one task matched and put in that vector, they could
interfere with each other's runnability by making later tasks
permanently unrunnable.

The `::take_tasks_matching()` API is a footgun - remove it in favor of
an API that takes tasks one by one, performing the runnability check
just in time.
2026-03-26 18:48:27 +01:00

115 lines
2.7 KiB
C++

/*
* Copyright (c) 2021-2025, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGC/RootVector.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/HTML/EventLoop/EventLoop.h>
#include <LibWeb/HTML/EventLoop/TaskQueue.h>
namespace Web::HTML {
GC_DEFINE_ALLOCATOR(TaskQueue);
TaskQueue::TaskQueue(HTML::EventLoop& event_loop)
: m_event_loop(event_loop)
{
}
TaskQueue::~TaskQueue() = default;
void TaskQueue::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_event_loop);
visitor.visit(m_tasks);
}
void TaskQueue::add(GC::Ref<Task> task)
{
// AD-HOC: Don't enqueue tasks for temporary (inert) documents used for fragment parsing.
// FIXME: There's ongoing spec work to remove such documents: https://github.com/whatwg/html/pull/11970
if (task->document() && task->document()->is_temporary_document_for_fragment_parsing())
return;
m_tasks.append(task);
m_event_loop->schedule();
}
GC::Ptr<Task> TaskQueue::take_first_runnable()
{
if (m_event_loop->execution_paused())
return nullptr;
for (size_t i = 0; i < m_tasks.size();) {
if (m_event_loop->running_rendering_task() && m_tasks[i]->source() == Task::Source::Rendering) {
++i;
continue;
}
if (m_tasks[i]->is_runnable())
return m_tasks.take(i);
if (m_tasks[i]->is_permanently_unrunnable()) {
m_tasks.remove(i);
continue;
}
++i;
}
return nullptr;
}
bool TaskQueue::has_runnable_tasks() const
{
if (m_event_loop->execution_paused())
return false;
for (auto& task : m_tasks) {
if (m_event_loop->running_rendering_task() && task->source() == Task::Source::Rendering)
continue;
if (task->is_runnable())
return true;
}
return false;
}
void TaskQueue::remove_tasks_matching(Function<bool(HTML::Task const&)> filter)
{
m_tasks.remove_all_matching(filter);
}
GC::Ptr<Task> TaskQueue::take_first_runnable_matching(Function<bool(HTML::Task const&)> filter)
{
for (size_t i = 0; i < m_tasks.size();) {
auto& task = m_tasks.at(i);
if (task->is_runnable() && filter(*task))
return m_tasks.take(i);
if (task->is_permanently_unrunnable()) {
m_tasks.remove(i);
continue;
}
++i;
}
return nullptr;
}
Task const* TaskQueue::last_added_task() const
{
if (m_tasks.is_empty())
return nullptr;
return m_tasks.last();
}
bool TaskQueue::has_rendering_tasks() const
{
return m_tasks.contains([](auto const& task) { return task->source() == Task::Source::Rendering; });
}
}