LibWeb: Port EventLoop::spin_XXX to HeapFunction

This commit is contained in:
Shannon Booth 2024-10-31 05:23:43 +13:00 committed by Alexander Kalenik
parent 29cea5bd24
commit 1c18b900e2
Notes: github-actions[bot] 2024-10-30 19:57:02 +00:00
14 changed files with 68 additions and 65 deletions

View file

@ -606,9 +606,9 @@ WebIDL::ExceptionOr<Document*> Document::open(Optional<String> const&, Optional<
// because subsequent steps will modify "initial about:blank" to false, which would cause
// initial navigation to fail in case it was "about:blank".
if (auto navigable = this->navigable(); navigable && navigable->container() && !navigable->container()->content_navigable_initialized()) {
HTML::main_thread_event_loop().spin_processing_tasks_with_source_until(HTML::Task::Source::NavigationAndTraversal, [navigable_container = navigable->container()] {
HTML::main_thread_event_loop().spin_processing_tasks_with_source_until(HTML::Task::Source::NavigationAndTraversal, JS::create_heap_function(heap(), [navigable_container = navigable->container()] {
return navigable_container->content_navigable_initialized();
});
}));
}
// 1. If document is an XML document, then throw an "InvalidStateError" DOMException exception.
@ -3451,9 +3451,9 @@ void Document::destroy_a_document_and_its_descendants(JS::GCPtr<JS::HeapFunction
}
// 5. Wait until numberDestroyed equals childNavigable's size.
HTML::main_thread_event_loop().spin_until([&] {
HTML::main_thread_event_loop().spin_until(JS::create_heap_function(heap(), [&] {
return number_destroyed == child_navigables.size();
});
}));
// 6. Queue a global task on the navigation and traversal task source given document's relevant global object to perform the following steps:
HTML::queue_global_task(HTML::Task::Source::NavigationAndTraversal, relevant_global_object(*this), JS::create_heap_function(heap(), [after_all_destruction = move(after_all_destruction), this] {
@ -3675,9 +3675,9 @@ void Document::unload_a_document_and_its_descendants(JS::GCPtr<Document> new_doc
}));
}
HTML::main_thread_event_loop().spin_until([&] {
HTML::main_thread_event_loop().spin_until(JS::create_heap_function(heap(), [&] {
return number_unloaded == unloaded_documents_count;
});
}));
destroy_a_document_and_its_descendants(move(after_all_unloads));
}

View file

@ -332,15 +332,15 @@ WebIDL::ExceptionOr<JS::GCPtr<PendingResponse>> main_fetch(JS::Realm& realm, Inf
request->current_url().set_scheme("https"_string);
}
JS::SafeFunction<WebIDL::ExceptionOr<JS::NonnullGCPtr<PendingResponse>>()> get_response = [&realm, &vm, &fetch_params, request]() -> WebIDL::ExceptionOr<JS::NonnullGCPtr<PendingResponse>> {
auto get_response = JS::create_heap_function(vm.heap(), [&realm, &vm, &fetch_params, request]() -> WebIDL::ExceptionOr<JS::NonnullGCPtr<PendingResponse>> {
dbgln_if(WEB_FETCH_DEBUG, "Fetch: Running 'main fetch' get_response() function");
// -> fetchParamss preloaded response candidate is not null
if (!fetch_params.preloaded_response_candidate().has<Empty>()) {
// 1. Wait until fetchParamss preloaded response candidate is not "pending".
HTML::main_thread_event_loop().spin_until([&] {
HTML::main_thread_event_loop().spin_until(JS::create_heap_function(vm.heap(), [&] {
return !fetch_params.preloaded_response_candidate().has<Infrastructure::FetchParams::PreloadedResponseCandidatePendingTag>();
});
}));
// 2. Assert: fetchParamss preloaded response candidate is a response.
VERIFY(fetch_params.preloaded_response_candidate().has<JS::NonnullGCPtr<Infrastructure::Response>>());
@ -426,13 +426,13 @@ WebIDL::ExceptionOr<JS::GCPtr<PendingResponse>> main_fetch(JS::Realm& realm, Inf
// 2. Return the result of running HTTP fetch given fetchParams.
return http_fetch(realm, fetch_params);
}
};
});
if (recursive == Recursive::Yes) {
// 12. If response is null, then set response to the result of running the steps corresponding to the first
// matching statement:
auto pending_response = !response
? TRY(get_response())
? TRY(get_response->function()())
: PendingResponse::create(vm, request, *response);
// 13. If recursive is true, then return response.
@ -440,12 +440,12 @@ WebIDL::ExceptionOr<JS::GCPtr<PendingResponse>> main_fetch(JS::Realm& realm, Inf
}
// 11. If recursive is false, then run the remaining steps in parallel.
Platform::EventLoopPlugin::the().deferred_invoke(JS::create_heap_function(realm.heap(), [&realm, &vm, &fetch_params, request, response, get_response = move(get_response)] {
Platform::EventLoopPlugin::the().deferred_invoke(JS::create_heap_function(realm.heap(), [&realm, &vm, &fetch_params, request, response, get_response] {
// 12. If response is null, then set response to the result of running the steps corresponding to the first
// matching statement:
auto pending_response = PendingResponse::create(vm, request, Infrastructure::Response::create(vm));
if (!response) {
auto pending_response_or_error = get_response();
auto pending_response_or_error = get_response->function()();
if (pending_response_or_error.is_error())
return;
pending_response = pending_response_or_error.release_value();

View file

@ -69,7 +69,7 @@ EventLoop& main_thread_event_loop()
}
// https://html.spec.whatwg.org/multipage/webappapis.html#spin-the-event-loop
void EventLoop::spin_until(JS::SafeFunction<bool()> goal_condition)
void EventLoop::spin_until(JS::NonnullGCPtr<JS::HeapFunction<bool()>> goal_condition)
{
// FIXME: The spec wants us to do the rest of the enclosing algorithm (i.e. the caller)
// in the context of the currently running task on entry. That's not possible with this implementation.
@ -92,15 +92,15 @@ void EventLoop::spin_until(JS::SafeFunction<bool()> goal_condition)
// 2. Perform any steps that appear after this spin the event loop instance in the original algorithm.
// NOTE: This is achieved by returning from the function.
Platform::EventLoopPlugin::the().spin_until(JS::create_heap_function(heap(), [&] {
if (goal_condition())
Platform::EventLoopPlugin::the().spin_until(JS::create_heap_function(heap(), [this, goal_condition] {
if (goal_condition->function()())
return true;
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();
}
return goal_condition();
return goal_condition->function()();
}));
vm.restore_execution_context_stack();
@ -109,7 +109,7 @@ void EventLoop::spin_until(JS::SafeFunction<bool()> goal_condition)
// NOTE: This is achieved by returning from the function.
}
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::NonnullGCPtr<JS::HeapFunction<bool()>> goal_condition)
{
auto& vm = this->vm();
vm.save_execution_context_stack();
@ -120,8 +120,8 @@ void EventLoop::spin_processing_tasks_with_source_until(Task::Source source, JS:
// NOTE: HTML event loop processing steps could run a task with arbitrary source
m_skip_event_loop_processing_steps = true;
Platform::EventLoopPlugin::the().spin_until(JS::create_heap_function(heap(), [&] {
if (goal_condition())
Platform::EventLoopPlugin::the().spin_until(JS::create_heap_function(heap(), [this, source, goal_condition] {
if (goal_condition->function()())
return true;
if (m_task_queue->has_runnable_tasks()) {
auto tasks = m_task_queue->take_tasks_matching([&](auto& task) {
@ -137,7 +137,7 @@ void EventLoop::spin_processing_tasks_with_source_until(Task::Source source, JS:
// FIXME: Remove the platform event loop plugin so that this doesn't look out of place
Core::EventLoop::current().wake();
return goal_condition();
return goal_condition->function()();
}));
m_skip_event_loop_processing_steps = false;

View file

@ -10,7 +10,6 @@
#include <AK/WeakPtr.h>
#include <LibCore/Forward.h>
#include <LibJS/Forward.h>
#include <LibJS/SafeFunction.h>
#include <LibWeb/HTML/EventLoop/TaskQueue.h>
namespace Web::HTML {
@ -39,8 +38,8 @@ public:
TaskQueue& microtask_queue() { return *m_microtask_queue; }
TaskQueue const& microtask_queue() const { return *m_microtask_queue; }
void spin_until(JS::SafeFunction<bool()> goal_condition);
void spin_processing_tasks_with_source_until(Task::Source, JS::SafeFunction<bool()> goal_condition);
void spin_until(JS::NonnullGCPtr<JS::HeapFunction<bool()>> goal_condition);
void spin_processing_tasks_with_source_until(Task::Source, JS::NonnullGCPtr<JS::HeapFunction<bool()>> goal_condition);
void process();
void queue_task_to_update_the_rendering();

View file

@ -298,9 +298,9 @@ void EventSource::reestablish_the_connection()
}));
// 2. Wait a delay equal to the reconnection time of the event source.
HTML::main_thread_event_loop().spin_until([&, delay_start = MonotonicTime::now()]() {
HTML::main_thread_event_loop().spin_until(JS::create_heap_function(heap(), [&, delay_start = MonotonicTime::now()]() {
return (MonotonicTime::now() - delay_start) >= m_reconnection_time;
});
}));
// 3. Optionally, wait some more. In particular, if the previous attempt failed, then user agents might introduce
// an exponential backoff delay to avoid overloading a potentially already overloaded server. Alternatively, if
@ -309,7 +309,7 @@ void EventSource::reestablish_the_connection()
// 4. Wait until the aforementioned task has run, if it has not yet run.
if (!initial_task_has_run) {
HTML::main_thread_event_loop().spin_until([&]() { return initial_task_has_run; });
HTML::main_thread_event_loop().spin_until(JS::create_heap_function(heap(), [&]() { return initial_task_has_run; }));
}
// 5. Queue a task to run the following steps:

View file

@ -862,7 +862,7 @@ WebIDL::ExceptionOr<void> HTMLMediaElement::select_resource()
});
// 7. Wait for the task queued by the previous step to have executed.
HTML::main_thread_event_loop().spin_until([&]() { return ran_media_element_task; });
HTML::main_thread_event_loop().spin_until(JS::create_heap_function(heap(), [&]() { return ran_media_element_task; }));
};
// 1. ⌛ If the src attribute's value is the empty string, then end the synchronous section, and jump down to the failed with attribute step below.
@ -1580,7 +1580,7 @@ void HTMLMediaElement::seek_element(double playback_position, MediaSeekMode seek
// available, and, if it is, until it has decoded enough data to play back that position.
m_seek_in_progress = true;
on_seek(playback_position, seek_mode);
HTML::main_thread_event_loop().spin_until([&]() { return !m_seek_in_progress; });
HTML::main_thread_event_loop().spin_until(JS::create_heap_function(heap(), [&]() { return !m_seek_in_progress; }));
// FIXME: 13. Await a stable state. The synchronous section consists of all the remaining steps of this algorithm. (Steps in the
// synchronous section are marked with ⌛.)

View file

@ -86,7 +86,7 @@ void HTMLScriptElement::execute_script()
// https://html.spec.whatwg.org/multipage/document-lifecycle.html#read-html
// Before any script execution occurs, the user agent must wait for scripts may run for the newly-created document to be true for document.
if (!m_document->ready_to_run_scripts())
main_thread_event_loop().spin_until([&] { return m_document->ready_to_run_scripts(); });
main_thread_event_loop().spin_until(JS::create_heap_function(heap(), [&] { return m_document->ready_to_run_scripts(); }));
// 1. Let document be el's node document.
JS::NonnullGCPtr<DOM::Document> document = this->document();

View file

@ -883,7 +883,7 @@ static WebIDL::ExceptionOr<Navigable::NavigationParamsVariant> create_navigation
}
// 7. Wait until either response is non-null, or navigable's ongoing navigation changes to no longer equal navigationId.
HTML::main_thread_event_loop().spin_until([&]() {
HTML::main_thread_event_loop().spin_until(JS::create_heap_function(vm.heap(), [&]() {
if (response_holder->response() != nullptr)
return true;
@ -891,7 +891,7 @@ static WebIDL::ExceptionOr<Navigable::NavigationParamsVariant> create_navigation
return true;
return false;
});
}));
// If the latter condition occurs, then abort fetchController, and return. Otherwise, proceed onward.
if (navigation_id.has_value() && (!navigable->ongoing_navigation().has<String>() || navigable->ongoing_navigation().get<String>() != *navigation_id)) {
fetch_controller->abort(realm, {});

View file

@ -231,6 +231,8 @@ void HTMLParser::run(const URL::URL& url, HTMLTokenizer::StopAtInsertionPoint st
// https://html.spec.whatwg.org/multipage/parsing.html#the-end
void HTMLParser::the_end(JS::NonnullGCPtr<DOM::Document> document, JS::GCPtr<HTMLParser> parser)
{
auto& heap = document->heap();
// Once the user agent stops parsing the document, the user agent must run the following steps:
// NOTE: This is a static method because the spec sometimes wants us to "act as if the user agent had stopped
@ -281,10 +283,10 @@ void HTMLParser::the_end(JS::NonnullGCPtr<DOM::Document> document, JS::GCPtr<HTM
while (!document->scripts_to_execute_when_parsing_has_finished().is_empty()) {
// 1. Spin the event loop until the first script in the list of scripts that will execute when the document has finished parsing
// has its "ready to be parser-executed" flag set and the parser's Document has no style sheet that is blocking scripts.
main_thread_event_loop().spin_until([&] {
main_thread_event_loop().spin_until(JS::create_heap_function(heap, [&] {
return document->scripts_to_execute_when_parsing_has_finished().first()->is_ready_to_be_parser_executed()
&& !document->has_a_style_sheet_that_is_blocking_scripts();
});
}));
// 2. Execute the first script in the list of scripts that will execute when the document has finished parsing.
document->scripts_to_execute_when_parsing_has_finished().first()->execute_script();
@ -294,7 +296,7 @@ void HTMLParser::the_end(JS::NonnullGCPtr<DOM::Document> document, JS::GCPtr<HTM
}
// 6. Queue a global task on the DOM manipulation task source given the Document's relevant global object to run the following substeps:
queue_global_task(HTML::Task::Source::DOMManipulation, *document, JS::create_heap_function(document->heap(), [document = document] {
queue_global_task(HTML::Task::Source::DOMManipulation, *document, JS::create_heap_function(heap, [document = document] {
// 1. Set the Document's load timing info's DOM content loaded event start time to the current high resolution time given the Document's relevant global object.
document->load_timing_info().dom_content_loaded_event_start_time = HighResolutionTime::current_high_resolution_time(relevant_global_object(*document));
@ -312,14 +314,14 @@ void HTMLParser::the_end(JS::NonnullGCPtr<DOM::Document> document, JS::GCPtr<HTM
}));
// 7. Spin the event loop until the set of scripts that will execute as soon as possible and the list of scripts that will execute in order as soon as possible are empty.
main_thread_event_loop().spin_until([&] {
main_thread_event_loop().spin_until(JS::create_heap_function(heap, [&] {
return document->scripts_to_execute_as_soon_as_possible().is_empty();
});
}));
// 8. Spin the event loop until there is nothing that delays the load event in the Document.
main_thread_event_loop().spin_until([&] {
main_thread_event_loop().spin_until(JS::create_heap_function(heap, [&] {
return !document->anything_is_delaying_the_load_event();
});
}));
// 9. Queue a global task on the DOM manipulation task source given the Document's relevant global object to run the following steps:
queue_global_task(HTML::Task::Source::DOMManipulation, *document, JS::create_heap_function(document->heap(), [document = document] {
@ -2940,9 +2942,9 @@ void HTMLParser::handle_text(HTMLToken& token)
if (m_document->has_a_style_sheet_that_is_blocking_scripts() || the_script->is_ready_to_be_parser_executed() == false) {
// spin the event loop until the parser's Document has no style sheet that is blocking scripts
// and the script's ready to be parser-executed becomes true.
main_thread_event_loop().spin_until([&] {
main_thread_event_loop().spin_until(JS::create_heap_function(heap(), [&] {
return !m_document->has_a_style_sheet_that_is_blocking_scripts() && the_script->is_ready_to_be_parser_executed();
});
}));
}
// 6. If this parser has been aborted in the meantime, return.

View file

@ -505,9 +505,9 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<ClassicScript>> fetch_a_classic_worker_impo
// 5. Pause until response is not null.
auto& event_loop = settings_object.responsible_event_loop();
event_loop.spin_until([&]() {
event_loop.spin_until(JS::create_heap_function(vm.heap(), [&]() -> bool {
return response;
});
}));
// 6. Set response to response's unsafe response.
response = response->unsafe_response();

View file

@ -659,16 +659,16 @@ TraversableNavigable::HistoryStepResult TraversableNavigable::apply_the_history_
}));
}
auto check_if_document_population_tasks_completed = JS::SafeFunction<bool()>([&] {
auto check_if_document_population_tasks_completed = JS::create_heap_function(heap(), [&] {
return changing_navigable_continuations.size() + completed_change_jobs == total_change_jobs;
});
if (synchronous_navigation == SynchronousNavigation::Yes) {
// NOTE: Synchronous navigation should never require document population, so it is safe to process only NavigationAndTraversal source.
main_thread_event_loop().spin_processing_tasks_with_source_until(Task::Source::NavigationAndTraversal, move(check_if_document_population_tasks_completed));
main_thread_event_loop().spin_processing_tasks_with_source_until(Task::Source::NavigationAndTraversal, check_if_document_population_tasks_completed);
} else {
// NOTE: Process all task sources while waiting because reloading or back/forward navigation might require fetching to populate a document.
main_thread_event_loop().spin_until(move(check_if_document_population_tasks_completed));
main_thread_event_loop().spin_until(check_if_document_population_tasks_completed);
}
// 13. Let navigablesThatMustWaitBeforeHandlingSyncNavigation be an empty set.
@ -788,9 +788,9 @@ TraversableNavigable::HistoryStepResult TraversableNavigable::apply_the_history_
}
}
main_thread_event_loop().spin_processing_tasks_with_source_until(Task::Source::NavigationAndTraversal, [&] {
main_thread_event_loop().spin_processing_tasks_with_source_until(Task::Source::NavigationAndTraversal, JS::create_heap_function(heap(), [&] {
return completed_change_jobs == total_change_jobs;
});
}));
// 15. Let totalNonchangingJobs be the size of nonchangingNavigablesThatStillNeedUpdates.
auto total_non_changing_jobs = non_changing_navigables_that_still_need_updates.size();
@ -836,9 +836,9 @@ TraversableNavigable::HistoryStepResult TraversableNavigable::apply_the_history_
// AD-HOC: Since currently populate_session_history_entry_document does not run in parallel
// we call spin_until to interrupt execution of this function and let document population
// to complete.
main_thread_event_loop().spin_processing_tasks_with_source_until(Task::Source::NavigationAndTraversal, [&] {
main_thread_event_loop().spin_processing_tasks_with_source_until(Task::Source::NavigationAndTraversal, JS::create_heap_function(heap(), [&] {
return completed_non_changing_jobs == total_non_changing_jobs;
});
}));
// 20. Set traversable's current session history step to targetStep.
m_current_session_history_step = target_step;
@ -941,9 +941,9 @@ TraversableNavigable::CheckIfUnloadingIsCanceledResult TraversableNavigable::che
}));
// 6. Wait for eventsFired to be true.
main_thread_event_loop().spin_until([&] {
main_thread_event_loop().spin_until(JS::create_heap_function(heap(), [&] {
return events_fired;
});
}));
// 7. If finalStatus is not "continue", then return finalStatus.
if (final_status != CheckIfUnloadingIsCanceledResult::Continue)
@ -977,9 +977,9 @@ TraversableNavigable::CheckIfUnloadingIsCanceledResult TraversableNavigable::che
}
// 8. Wait for completedTasks to be totalTasks.
main_thread_event_loop().spin_until([&] {
main_thread_event_loop().spin_until(JS::create_heap_function(heap(), [&] {
return completed_tasks == total_tasks;
});
}));
// 9. Return finalStatus.
return final_status;

View file

@ -61,7 +61,7 @@ void SVGScriptElement::process_the_script_element()
// https://html.spec.whatwg.org/multipage/document-lifecycle.html#read-html
// Before any script execution occurs, the user agent must wait for scripts may run for the newly-created document to be true for document.
if (!m_document->ready_to_run_scripts())
HTML::main_thread_event_loop().spin_until([&] { return m_document->ready_to_run_scripts(); });
HTML::main_thread_event_loop().spin_until(JS::create_heap_function(heap(), [&] { return m_document->ready_to_run_scripts(); }));
// FIXME: Support non-inline scripts.
auto& settings_object = document().relevant_settings_object();

View file

@ -359,7 +359,7 @@ static void update(JS::VM& vm, JS::NonnullGCPtr<Job> job)
// FIXME: This feels.. uncomfortable but it should work to block the current task until the fetch has progressed past our processResponse hook or aborted
auto& event_loop = job->client ? job->client->responsible_event_loop() : HTML::main_thread_event_loop();
event_loop.spin_until([fetch_controller, &realm, &process_response_completion_result]() -> bool {
event_loop.spin_until(JS::create_heap_function(realm.heap(), [fetch_controller, &realm, &process_response_completion_result]() -> bool {
if (process_response_completion_result.has_value())
return true;
if (fetch_controller->state() == Fetch::Infrastructure::FetchController::State::Terminated || fetch_controller->state() == Fetch::Infrastructure::FetchController::State::Aborted) {
@ -367,7 +367,7 @@ static void update(JS::VM& vm, JS::NonnullGCPtr<Job> job)
return true;
}
return false;
});
}));
return process_response_completion_result.release_value();
};

View file

@ -177,9 +177,9 @@ void XMLDocumentBuilder::element_end(const XML::Name& name)
// 2. Spin the event loop until the parser's Document has no style sheet that is blocking scripts and the pending parsing-blocking script's "ready to be parser-executed" flag is set.
if (m_document->has_a_style_sheet_that_is_blocking_scripts() || !pending_parsing_blocking_script->is_ready_to_be_parser_executed()) {
HTML::main_thread_event_loop().spin_until([&] {
HTML::main_thread_event_loop().spin_until(JS::create_heap_function(script_element.heap(), [&] {
return !m_document->has_a_style_sheet_that_is_blocking_scripts() && pending_parsing_blocking_script->is_ready_to_be_parser_executed();
});
}));
}
// 3. Unblock this instance of the XML parser, such that tasks that invoke it can again be run.
@ -231,6 +231,8 @@ void XMLDocumentBuilder::comment(StringView data)
void XMLDocumentBuilder::document_end()
{
auto& heap = m_document->heap();
// When an XML parser reaches the end of its input, it must stop parsing.
// If the active speculative HTML parser is not null, then stop the speculative HTML parser and return.
// NOTE: Noop.
@ -248,10 +250,10 @@ void XMLDocumentBuilder::document_end()
while (!m_document->scripts_to_execute_when_parsing_has_finished().is_empty()) {
// Spin the event loop until the first script in the list of scripts that will execute when the document has finished parsing has its "ready to be parser-executed" flag set
// and the parser's Document has no style sheet that is blocking scripts.
HTML::main_thread_event_loop().spin_until([&] {
HTML::main_thread_event_loop().spin_until(JS::create_heap_function(heap, [&] {
return m_document->scripts_to_execute_when_parsing_has_finished().first()->is_ready_to_be_parser_executed()
&& !m_document->has_a_style_sheet_that_is_blocking_scripts();
});
}));
// Execute the first script in the list of scripts that will execute when the document has finished parsing.
m_document->scripts_to_execute_when_parsing_has_finished().first()->execute_script();
@ -278,14 +280,14 @@ void XMLDocumentBuilder::document_end()
}));
// Spin the event loop until the set of scripts that will execute as soon as possible and the list of scripts that will execute in order as soon as possible are empty.
HTML::main_thread_event_loop().spin_until([&] {
HTML::main_thread_event_loop().spin_until(JS::create_heap_function(heap, [&] {
return m_document->scripts_to_execute_as_soon_as_possible().is_empty();
});
}));
// Spin the event loop until there is nothing that delays the load event in the Document.
HTML::main_thread_event_loop().spin_until([&] {
HTML::main_thread_event_loop().spin_until(JS::create_heap_function(heap, [&] {
return !m_document->anything_is_delaying_the_load_event();
});
}));
// Queue a global task on the DOM manipulation task source given the Document's relevant global object to run the following steps:
queue_global_task(HTML::Task::Source::DOMManipulation, m_document, JS::create_heap_function(m_document->heap(), [document = m_document] {