ladybird/Libraries/LibWeb/HTML/EventLoop/Task.cpp
Andreas Kling 187f8c5460 LibWeb: Run queued HTML tasks after associated document is destroyed
Before this change, tasks associated with a destroyed document would get
stuck in the task queue forever, since document-associated tasks are not
allowed to run when their document isn't fully active (and destroyed
documents never become fully active again). This caused everything
captured by task callbacks to leak.

We now treat tasks for destroyed documents as runnable immediately,
which gets them out of the queue.

This fixes another massive GC leak on Speedometer.
2025-02-07 16:53:11 +01:00

80 lines
1.9 KiB
C++

/*
* Copyright (c) 2021-2024, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/IDAllocator.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/HTML/EventLoop/Task.h>
namespace Web::HTML {
GC_DEFINE_ALLOCATOR(Task);
static IDAllocator s_unique_task_source_allocator { static_cast<int>(Task::Source::UniqueTaskSourceStart) };
[[nodiscard]] static TaskID allocate_task_id()
{
static u64 next_task_id = 1;
return next_task_id++;
}
GC::Ref<Task> Task::create(JS::VM& vm, Source source, GC::Ptr<DOM::Document const> document, GC::Ref<GC::Function<void()>> steps)
{
return vm.heap().allocate<Task>(source, document, move(steps));
}
Task::Task(Source source, GC::Ptr<DOM::Document const> document, GC::Ref<GC::Function<void()>> steps)
: m_id(allocate_task_id())
, m_source(source)
, m_steps(steps)
, m_document(document)
{
}
Task::~Task() = default;
void Task::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_steps);
visitor.visit(m_document);
}
void Task::execute()
{
m_steps->function()();
}
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-task-runnable
bool Task::is_runnable() const
{
// A task is runnable if its document is either null or fully active.
if (!m_document)
return true;
// AD-HOC: If the document has been destroyed, we'll consider the task runnable.
// Otherwise it would get stuck here forever, since a destroyed document never becomes fully active again.
if (m_document->has_been_destroyed())
return true;
return m_document->is_fully_active();
}
DOM::Document const* Task::document() const
{
return m_document.ptr();
}
UniqueTaskSource::UniqueTaskSource()
: source(static_cast<Task::Source>(s_unique_task_source_allocator.allocate()))
{
}
UniqueTaskSource::~UniqueTaskSource()
{
s_unique_task_source_allocator.deallocate(static_cast<int>(source));
}
}