Coverage Report

Created: 2025-11-02 07:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
3
 * Copyright (c) 2022, the SerenityOS developers.
4
 *
5
 * SPDX-License-Identifier: BSD-2-Clause
6
 */
7
8
#include <LibCore/EventLoop.h>
9
#include <LibJS/Runtime/VM.h>
10
#include <LibWeb/Bindings/MainThreadVM.h>
11
#include <LibWeb/DOM/Document.h>
12
#include <LibWeb/HTML/BrowsingContext.h>
13
#include <LibWeb/HTML/EventLoop/EventLoop.h>
14
#include <LibWeb/HTML/Scripting/Environments.h>
15
#include <LibWeb/HTML/Window.h>
16
#include <LibWeb/HighResolutionTime/Performance.h>
17
#include <LibWeb/HighResolutionTime/TimeOrigin.h>
18
#include <LibWeb/Page/Page.h>
19
#include <LibWeb/Platform/EventLoopPlugin.h>
20
#include <LibWeb/Platform/Timer.h>
21
22
namespace Web::HTML {
23
24
JS_DEFINE_ALLOCATOR(EventLoop);
25
26
EventLoop::EventLoop(Type type)
27
0
    : m_type(type)
28
0
{
29
0
    m_task_queue = heap().allocate_without_realm<TaskQueue>(*this);
30
0
    m_microtask_queue = heap().allocate_without_realm<TaskQueue>(*this);
31
0
}
32
33
0
EventLoop::~EventLoop() = default;
34
35
void EventLoop::visit_edges(Visitor& visitor)
36
0
{
37
0
    Base::visit_edges(visitor);
38
0
    visitor.visit(m_task_queue);
39
0
    visitor.visit(m_microtask_queue);
40
0
    visitor.visit(m_currently_running_task);
41
0
    visitor.visit(m_backup_incumbent_settings_object_stack);
42
0
}
43
44
void EventLoop::schedule()
45
0
{
46
0
    if (!m_system_event_loop_timer) {
47
0
        m_system_event_loop_timer = Platform::Timer::create_single_shot(0, [this] {
48
0
            process();
49
0
        });
50
0
    }
51
52
0
    if (!m_system_event_loop_timer->is_active())
53
0
        m_system_event_loop_timer->restart();
54
0
}
55
56
EventLoop& main_thread_event_loop()
57
0
{
58
0
    return *static_cast<Bindings::WebEngineCustomData*>(Bindings::main_thread_vm().custom_data())->event_loop;
59
0
}
60
61
// https://html.spec.whatwg.org/multipage/webappapis.html#spin-the-event-loop
62
void EventLoop::spin_until(JS::SafeFunction<bool()> goal_condition)
63
0
{
64
    // FIXME: The spec wants us to do the rest of the enclosing algorithm (i.e. the caller)
65
    //    in the context of the currently running task on entry. That's not possible with this implementation.
66
    // 1. Let task be the event loop's currently running task.
67
    // 2. Let task source be task's source.
68
69
    // 3. Let old stack be a copy of the JavaScript execution context stack.
70
    // 4. Empty the JavaScript execution context stack.
71
0
    auto& vm = this->vm();
72
0
    vm.save_execution_context_stack();
73
0
    vm.clear_execution_context_stack();
74
75
    // 5. Perform a microtask checkpoint.
76
0
    perform_a_microtask_checkpoint();
77
78
    // 6. In parallel:
79
    //    1. Wait until the condition goal is met.
80
    //    2. Queue a task on task source to:
81
    //       1. Replace the JavaScript execution context stack with old stack.
82
    //       2. Perform any steps that appear after this spin the event loop instance in the original algorithm.
83
    //       NOTE: This is achieved by returning from the function.
84
85
0
    Platform::EventLoopPlugin::the().spin_until([&] {
86
0
        if (goal_condition())
87
0
            return true;
88
0
        if (m_task_queue->has_runnable_tasks()) {
89
0
            schedule();
90
            // FIXME: Remove the platform event loop plugin so that this doesn't look out of place
91
0
            Core::EventLoop::current().wake();
92
0
        }
93
0
        return goal_condition();
94
0
    });
95
96
0
    vm.restore_execution_context_stack();
97
98
    // 7. Stop task, allowing whatever algorithm that invoked it to resume.
99
    // NOTE: This is achieved by returning from the function.
100
0
}
101
102
void EventLoop::spin_processing_tasks_with_source_until(Task::Source source, JS::SafeFunction<bool()> goal_condition)
103
0
{
104
0
    auto& vm = this->vm();
105
0
    vm.save_execution_context_stack();
106
0
    vm.clear_execution_context_stack();
107
108
0
    perform_a_microtask_checkpoint();
109
110
    // NOTE: HTML event loop processing steps could run a task with arbitrary source
111
0
    m_skip_event_loop_processing_steps = true;
112
113
0
    Platform::EventLoopPlugin::the().spin_until([&] {
114
0
        if (goal_condition())
115
0
            return true;
116
0
        if (m_task_queue->has_runnable_tasks()) {
117
0
            auto tasks = m_task_queue->take_tasks_matching([&](auto& task) {
118
0
                return task.source() == source && task.is_runnable();
119
0
            });
120
121
0
            for (auto& task : tasks) {
122
0
                m_currently_running_task = task.ptr();
123
0
                task->execute();
124
0
                m_currently_running_task = nullptr;
125
0
            }
126
0
        }
127
128
        // FIXME: Remove the platform event loop plugin so that this doesn't look out of place
129
0
        Core::EventLoop::current().wake();
130
0
        return goal_condition();
131
0
    });
132
133
0
    m_skip_event_loop_processing_steps = false;
134
135
0
    schedule();
136
137
0
    vm.restore_execution_context_stack();
138
0
}
139
140
// https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model
141
void EventLoop::process()
142
0
{
143
0
    if (m_skip_event_loop_processing_steps)
144
0
        return;
145
146
    // An event loop must continually run through the following steps for as long as it exists:
147
148
    // 1. Let oldestTask be null.
149
0
    JS::GCPtr<Task> oldest_task;
150
151
    // 2. Set taskStartTime to the unsafe shared current time.
152
0
    double task_start_time = HighResolutionTime::unsafe_shared_current_time();
153
154
    // 3. Let taskQueue be one of the event loop's task queues, chosen in an implementation-defined manner,
155
    //    with the constraint that the chosen task queue must contain at least one runnable task.
156
    //    If there is no such task queue, then jump to the microtasks step below.
157
0
    auto& task_queue = *m_task_queue;
158
159
    // 4. Set oldestTask to the first runnable task in taskQueue, and remove it from taskQueue.
160
0
    oldest_task = task_queue.take_first_runnable();
161
162
0
    if (oldest_task) {
163
        // 5. Set the event loop's currently running task to oldestTask.
164
0
        m_currently_running_task = oldest_task.ptr();
165
166
        // 6. Perform oldestTask's steps.
167
0
        oldest_task->execute();
168
169
        // 7. Set the event loop's currently running task back to null.
170
0
        m_currently_running_task = nullptr;
171
0
    }
172
173
    // 8. Microtasks: Perform a microtask checkpoint.
174
0
    perform_a_microtask_checkpoint();
175
176
0
    if (m_is_running_reflow_steps) {
177
        // NOTE: If we entered style-layout-repaint steps, then we need to wait for them to finish before doing next iteration.
178
0
        schedule();
179
0
        return;
180
0
    }
181
182
0
    m_is_running_reflow_steps = true;
183
0
    ScopeGuard const guard = [this] {
184
0
        m_is_running_reflow_steps = false;
185
0
    };
186
187
    // 9. Let hasARenderingOpportunity be false.
188
0
    [[maybe_unused]] bool has_a_rendering_opportunity = false;
189
190
    // FIXME: 10. Let now be the current high resolution time. [HRT]
191
192
    // FIXME: 11. If oldestTask is not null, then:
193
194
    // FIXME:     1. Let top-level browsing contexts be an empty set.
195
196
    // FIXME:     2. For each environment settings object settings of oldestTask's script evaluation environment settings object set, append setting's top-level browsing context to top-level browsing contexts.
197
198
    // FIXME:     3. Report long tasks, passing in taskStartTime, now (the end time of the task), top-level browsing contexts, and oldestTask.
199
200
    // FIXME: 12. Update the rendering: if this is a window event loop, then:
201
202
    // FIXME:     1. Let docs be all Document objects whose relevant agent's event loop is this event loop, sorted arbitrarily except that the following conditions must be met:
203
    //               - Any Document B whose browsing context's container document is A must be listed after A in the list.
204
    //               - If there are two documents A and B whose browsing contexts are both child browsing contexts whose container documents are another Document C, then the order of A and B in the list must match the shadow-including tree order of their respective browsing context containers in C's node tree.
205
    // FIXME: NOTE: The sort order specified above is missing here!
206
0
    Vector<JS::Handle<DOM::Document>> docs = documents_in_this_event_loop();
207
208
0
    auto for_each_fully_active_document_in_docs = [&](auto&& callback) {
209
0
        for (auto& document : docs) {
210
0
            if (document->is_fully_active())
211
0
                callback(*document);
212
0
        }
213
0
    };
Unexecuted instantiation: EventLoop.cpp:auto Web::HTML::EventLoop::process()::$_1::operator()<Web::HTML::EventLoop::process()::$_2>(Web::HTML::EventLoop::process()::$_2&&) const
Unexecuted instantiation: EventLoop.cpp:auto Web::HTML::EventLoop::process()::$_1::operator()<Web::HTML::EventLoop::process()::$_3>(Web::HTML::EventLoop::process()::$_3&&) const
Unexecuted instantiation: EventLoop.cpp:auto Web::HTML::EventLoop::process()::$_1::operator()<Web::HTML::EventLoop::process()::$_4>(Web::HTML::EventLoop::process()::$_4&&) const
Unexecuted instantiation: EventLoop.cpp:auto Web::HTML::EventLoop::process()::$_1::operator()<Web::HTML::EventLoop::process()::$_5>(Web::HTML::EventLoop::process()::$_5&&) const
Unexecuted instantiation: EventLoop.cpp:auto Web::HTML::EventLoop::process()::$_1::operator()<Web::HTML::EventLoop::process()::$_6>(Web::HTML::EventLoop::process()::$_6&&) const
Unexecuted instantiation: EventLoop.cpp:auto Web::HTML::EventLoop::process()::$_1::operator()<Web::HTML::EventLoop::process()::$_7>(Web::HTML::EventLoop::process()::$_7&&) const
Unexecuted instantiation: EventLoop.cpp:auto Web::HTML::EventLoop::process()::$_1::operator()<Web::HTML::EventLoop::process()::$_8>(Web::HTML::EventLoop::process()::$_8&&) const
Unexecuted instantiation: EventLoop.cpp:auto Web::HTML::EventLoop::process()::$_1::operator()<Web::HTML::EventLoop::process()::$_9>(Web::HTML::EventLoop::process()::$_9&&) const
Unexecuted instantiation: EventLoop.cpp:auto Web::HTML::EventLoop::process()::$_1::operator()<Web::HTML::EventLoop::process()::$_10>(Web::HTML::EventLoop::process()::$_10&&) const
Unexecuted instantiation: EventLoop.cpp:auto Web::HTML::EventLoop::process()::$_1::operator()<Web::HTML::EventLoop::process()::$_11>(Web::HTML::EventLoop::process()::$_11&&) const
214
215
    // AD-HOC: Since event loop processing steps do not constantly running in parallel, and
216
    //         something must trigger them, we need to manually schedule a repaint for all
217
    //         navigables that do not have a rendering opportunity at this event loop iteration.
218
    //         Otherwise their repaint will be delayed until something else will trigger event
219
    //         loop processing.
220
0
    for_each_fully_active_document_in_docs([&](DOM::Document& document) {
221
0
        auto navigable = document.navigable();
222
0
        if (navigable && !navigable->has_a_rendering_opportunity() && navigable->needs_repaint())
223
0
            schedule();
224
0
        if (navigable && navigable->has_a_rendering_opportunity())
225
0
            return;
226
0
        auto* browsing_context = document.browsing_context();
227
0
        if (!browsing_context)
228
0
            return;
229
0
        auto& page = browsing_context->page();
230
0
        page.client().schedule_repaint();
231
0
    });
232
233
    // 2. Rendering opportunities: Remove from docs all Document objects whose node navigables do not have a rendering opportunity.
234
0
    docs.remove_all_matching([&](auto& document) {
235
0
        auto navigable = document->navigable();
236
0
        return navigable && !navigable->has_a_rendering_opportunity();
237
0
    });
238
239
    // 3. If docs is not empty, then set hasARenderingOpportunity to true
240
    //    and set this event loop's last render opportunity time to taskStartTime.
241
0
    if (!docs.is_empty()) {
242
0
        has_a_rendering_opportunity = true;
243
0
        m_last_render_opportunity_time = task_start_time;
244
0
    }
245
246
    // FIXME:     4. Unnecessary rendering: Remove from docs all Document objects which meet both of the following conditions:
247
    //               - The user agent believes that updating the rendering of the Document's browsing context would have no visible effect, and
248
    //               - The Document's map of animation frame callbacks is empty.
249
    //            https://www.w3.org/TR/intersection-observer/#pending-initial-observation
250
    //            In the HTML Event Loops Processing Model, under the "Update the rendering" step, the "Unnecessary rendering" step should be
251
    //            modified to add an additional requirement for skipping the rendering update:
252
    //              - The document does not have pending initial IntersectionObserver targets.
253
254
    // FIXME:     5. Remove from docs all Document objects for which the user agent believes that it's preferable to skip updating the rendering for other reasons.
255
256
    // FIXME:     6. For each fully active Document in docs, flush autofocus candidates for that Document if its browsing context is a top-level browsing context.
257
258
    // 7. For each fully active Document in docs, run the resize steps for that Document, passing in now as the timestamp. [CSSOMVIEW]
259
0
    for_each_fully_active_document_in_docs([&](DOM::Document& document) {
260
0
        document.run_the_resize_steps();
261
0
    });
262
263
    // 8. For each fully active Document in docs, run the scroll steps for that Document, passing in now as the timestamp. [CSSOMVIEW]
264
0
    for_each_fully_active_document_in_docs([&](DOM::Document& document) {
265
0
        document.run_the_scroll_steps();
266
0
    });
267
268
    // 9. For each fully active Document in docs, evaluate media queries and report changes for that Document, passing in now as the timestamp. [CSSOMVIEW]
269
0
    for_each_fully_active_document_in_docs([&](DOM::Document& document) {
270
0
        document.evaluate_media_queries_and_report_changes();
271
0
    });
272
273
    // 10. For each fully active Document in docs, update animations and send events for that Document, passing in now as the timestamp. [WEBANIMATIONS]
274
    // Note: This is handled by the document's animation timer, however, if a document has any requestAnimationFrame callbacks, we need
275
    //       to dispatch events before that happens below. Not dispatching here would be observable.
276
0
    for_each_fully_active_document_in_docs([&](DOM::Document& document) {
277
0
        if (document.window()->animation_frame_callback_driver().has_callbacks()) {
278
0
            document.update_animations_and_send_events(document.window()->performance()->now());
279
0
        }
280
0
    });
281
282
    // FIXME:     11. For each fully active Document in docs, run the fullscreen steps for that Document, passing in now as the timestamp. [FULLSCREEN]
283
284
    // FIXME:     12. For each fully active Document in docs, if the user agent detects that the backing storage associated with a CanvasRenderingContext2D or an OffscreenCanvasRenderingContext2D, context, has been lost, then it must run the context lost steps for each such context:
285
286
    // FIXME:     13. For each fully active Document in docs, run the animation frame callbacks for that Document, passing in now as the timestamp.
287
0
    auto now = HighResolutionTime::unsafe_shared_current_time();
288
0
    for_each_fully_active_document_in_docs([&](DOM::Document& document) {
289
0
        run_animation_frame_callbacks(document, now);
290
0
    });
291
292
    // FIXME: This step is implemented following the latest specification, while the rest of this method uses an outdated spec.
293
    // NOTE: Gathering and broadcasting of resize observations need to happen after evaluating media queries but before
294
    //       updating intersection observations steps.
295
0
    for_each_fully_active_document_in_docs([&](DOM::Document& document) {
296
        // 1. Let resizeObserverDepth be 0.
297
0
        size_t resize_observer_depth = 0;
298
299
        // 2. While true:
300
0
        while (true) {
301
            // 1. Recalculate styles and update layout for doc.
302
            // NOTE: Recalculation of styles is handled by update_layout()
303
0
            document.update_layout();
304
305
            // FIXME: 2. Let hadInitialVisibleContentVisibilityDetermination be false.
306
            // FIXME: 3. For each element element with 'auto' used value of 'content-visibility':
307
            // FIXME: 4. If hadInitialVisibleContentVisibilityDetermination is true, then continue.
308
309
            // 5. Gather active resize observations at depth resizeObserverDepth for doc.
310
0
            document.gather_active_observations_at_depth(resize_observer_depth);
311
312
            // 6. If doc has active resize observations:
313
0
            if (document.has_active_resize_observations()) {
314
                // 1. Set resizeObserverDepth to the result of broadcasting active resize observations given doc.
315
0
                resize_observer_depth = document.broadcast_active_resize_observations();
316
317
                // 2. Continue.
318
0
                continue;
319
0
            }
320
321
            // 7. Otherwise, break.
322
0
            break;
323
0
        }
324
325
        // 3. If doc has skipped resize observations, then deliver resize loop error given doc.
326
0
        if (document.has_skipped_resize_observations()) {
327
            // FIXME: Deliver resize loop error.
328
0
        }
329
0
    });
330
331
    // 14. For each fully active Document in docs, run the update intersection observations steps for that Document, passing in now as the timestamp. [INTERSECTIONOBSERVER]
332
0
    for_each_fully_active_document_in_docs([&](DOM::Document& document) {
333
0
        document.run_the_update_intersection_observations_steps(now);
334
0
    });
335
336
    // FIXME:     15. Invoke the mark paint timing algorithm for each Document object in docs.
337
338
    // 16. For each fully active Document in docs, update the rendering or user interface of that Document and its browsing context to reflect the current state.
339
0
    for_each_fully_active_document_in_docs([&](DOM::Document& document) {
340
0
        auto navigable = document.navigable();
341
0
        if (navigable && navigable->needs_repaint()) {
342
0
            auto* browsing_context = document.browsing_context();
343
0
            auto& page = browsing_context->page();
344
0
            if (navigable->is_traversable()) {
345
0
                VERIFY(page.client().is_ready_to_paint());
346
0
                page.client().paint_next_frame();
347
0
            }
348
0
        }
349
0
    });
350
351
    // 13. If all of the following are true
352
    // - this is a window event loop
353
    // - there is no task in this event loop's task queues whose document is fully active
354
    // - this event loop's microtask queue is empty
355
    // - hasARenderingOpportunity is false
356
    // FIXME: has_a_rendering_opportunity is always true
357
0
    if (m_type == Type::Window && !task_queue.has_runnable_tasks() && m_microtask_queue->is_empty() /*&& !has_a_rendering_opportunity*/) {
358
        // 1. Set this event loop's last idle period start time to the unsafe shared current time.
359
0
        m_last_idle_period_start_time = HighResolutionTime::unsafe_shared_current_time();
360
361
        // 2. Let computeDeadline be the following steps:
362
        // NOTE: instead of passing around a function we use this event loop, which has compute_deadline()
363
364
        // 3. For each win of the same-loop windows for this event loop,
365
        //    perform the start an idle period algorithm for win with computeDeadline. [REQUESTIDLECALLBACK]
366
0
        for (auto& win : same_loop_windows())
367
0
            win->start_an_idle_period();
368
0
    }
369
370
    // FIXME: 14. If this is a worker event loop, then:
371
372
    // FIXME:     1. If this event loop's agent's single realm's global object is a supported DedicatedWorkerGlobalScope and the user agent believes that it would benefit from having its rendering updated at this time, then:
373
    // FIXME:        1. Let now be the current high resolution time. [HRT]
374
    // FIXME:        2. Run the animation frame callbacks for that DedicatedWorkerGlobalScope, passing in now as the timestamp.
375
    // FIXME:        3. Update the rendering of that dedicated worker to reflect the current state.
376
377
    // 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.
378
379
    // If there are eligible tasks in the queue, schedule a new round of processing. :^)
380
0
    if (m_task_queue->has_runnable_tasks() || (!m_microtask_queue->is_empty() && !m_performing_a_microtask_checkpoint))
381
0
        schedule();
382
383
    // For each doc of docs, process top layer removals given doc.
384
0
    for_each_fully_active_document_in_docs([&](DOM::Document& document) {
385
0
        document.process_top_layer_removals();
386
0
    });
387
0
}
388
389
// https://html.spec.whatwg.org/multipage/webappapis.html#queue-a-task
390
TaskID queue_a_task(HTML::Task::Source source, JS::GCPtr<EventLoop> event_loop, JS::GCPtr<DOM::Document> document, JS::NonnullGCPtr<JS::HeapFunction<void()>> steps)
391
0
{
392
    // 1. If event loop was not given, set event loop to the implied event loop.
393
0
    if (!event_loop)
394
0
        event_loop = main_thread_event_loop();
395
396
    // FIXME: 2. If document was not given, set document to the implied document.
397
398
    // 3. Let task be a new task.
399
    // 4. Set task's steps to steps.
400
    // 5. Set task's source to source.
401
    // 6. Set task's document to the document.
402
    // 7. Set task's script evaluation environment settings object set to an empty set.
403
0
    auto task = HTML::Task::create(event_loop->vm(), source, document, steps);
404
405
    // 8. Let queue be the task queue to which source is associated on event loop.
406
0
    auto& queue = source == HTML::Task::Source::Microtask ? event_loop->microtask_queue() : event_loop->task_queue();
407
408
    // 9. Append task to queue.
409
0
    queue.add(task);
410
411
0
    return queue.last_added_task()->id();
412
0
}
413
414
// https://html.spec.whatwg.org/multipage/webappapis.html#queue-a-global-task
415
TaskID queue_global_task(HTML::Task::Source source, JS::Object& global_object, JS::NonnullGCPtr<JS::HeapFunction<void()>> steps)
416
0
{
417
    // 1. Let event loop be global's relevant agent's event loop.
418
0
    auto& global_custom_data = verify_cast<Bindings::WebEngineCustomData>(*global_object.vm().custom_data());
419
0
    auto& event_loop = global_custom_data.event_loop;
420
421
    // 2. Let document be global's associated Document, if global is a Window object; otherwise null.
422
0
    DOM::Document* document { nullptr };
423
0
    if (is<HTML::Window>(global_object)) {
424
0
        auto& window_object = verify_cast<HTML::Window>(global_object);
425
0
        document = &window_object.associated_document();
426
0
    }
427
428
    // 3. Queue a task given source, event loop, document, and steps.
429
0
    return queue_a_task(source, *event_loop, document, steps);
430
0
}
431
432
// https://html.spec.whatwg.org/#queue-a-microtask
433
void queue_a_microtask(DOM::Document const* document, JS::NonnullGCPtr<JS::HeapFunction<void()>> steps)
434
0
{
435
    // 1. If event loop was not given, set event loop to the implied event loop.
436
0
    auto& event_loop = HTML::main_thread_event_loop();
437
438
    // FIXME: 2. If document was not given, set document to the implied document.
439
440
    // 3. Let microtask be a new task.
441
    // 4. Set microtask's steps to steps.
442
    // 5. Set microtask's source to the microtask task source.
443
    // 6. Set microtask's document to document.
444
0
    auto& vm = event_loop.vm();
445
0
    auto microtask = HTML::Task::create(vm, HTML::Task::Source::Microtask, document, steps);
446
447
    // FIXME: 7. Set microtask's script evaluation environment settings object set to an empty set.
448
449
    // 8. Enqueue microtask on event loop's microtask queue.
450
0
    event_loop.microtask_queue().enqueue(microtask);
451
0
}
452
453
void perform_a_microtask_checkpoint()
454
0
{
455
0
    main_thread_event_loop().perform_a_microtask_checkpoint();
456
0
}
457
458
// https://html.spec.whatwg.org/#perform-a-microtask-checkpoint
459
void EventLoop::perform_a_microtask_checkpoint()
460
0
{
461
    // 1. If the event loop's performing a microtask checkpoint is true, then return.
462
0
    if (m_performing_a_microtask_checkpoint)
463
0
        return;
464
465
    // 2. Set the event loop's performing a microtask checkpoint to true.
466
0
    m_performing_a_microtask_checkpoint = true;
467
468
    // 3. While the event loop's microtask queue is not empty:
469
0
    while (!m_microtask_queue->is_empty()) {
470
        // 1. Let oldestMicrotask be the result of dequeuing from the event loop's microtask queue.
471
0
        auto oldest_microtask = m_microtask_queue->dequeue();
472
473
        // 2. Set the event loop's currently running task to oldestMicrotask.
474
0
        m_currently_running_task = oldest_microtask;
475
476
        // 3. Run oldestMicrotask.
477
0
        oldest_microtask->execute();
478
479
        // 4. Set the event loop's currently running task back to null.
480
0
        m_currently_running_task = nullptr;
481
0
    }
482
483
    // 4. For each environment settings object settingsObject whose responsible event loop is this event loop, notify about rejected promises given settingsObject's global object.
484
0
    for (auto& environment_settings_object : m_related_environment_settings_objects) {
485
0
        auto* global = dynamic_cast<HTML::WindowOrWorkerGlobalScopeMixin*>(&environment_settings_object->global_object());
486
0
        VERIFY(global);
487
0
        global->notify_about_rejected_promises({});
488
0
    }
489
490
    // FIXME: 5. Cleanup Indexed Database transactions.
491
492
    // 6. Perform ClearKeptObjects().
493
0
    vm().finish_execution_generation();
494
495
    // 7. Set the event loop's performing a microtask checkpoint to false.
496
0
    m_performing_a_microtask_checkpoint = false;
497
498
    // FIXME: 8. Record timing info for microtask checkpoint.
499
0
}
500
501
Vector<JS::Handle<DOM::Document>> EventLoop::documents_in_this_event_loop() const
502
0
{
503
0
    Vector<JS::Handle<DOM::Document>> documents;
504
0
    for (auto& document : m_documents) {
505
0
        VERIFY(document);
506
0
        if (document->is_decoded_svg())
507
0
            continue;
508
0
        documents.append(JS::make_handle(*document));
509
0
    }
510
0
    return documents;
511
0
}
512
513
void EventLoop::register_document(Badge<DOM::Document>, DOM::Document& document)
514
0
{
515
0
    m_documents.append(&document);
516
0
}
517
518
void EventLoop::unregister_document(Badge<DOM::Document>, DOM::Document& document)
519
0
{
520
0
    bool did_remove = m_documents.remove_first_matching([&](auto& entry) { return entry.ptr() == &document; });
521
0
    VERIFY(did_remove);
522
0
}
523
524
void EventLoop::push_onto_backup_incumbent_settings_object_stack(Badge<EnvironmentSettingsObject>, EnvironmentSettingsObject& environment_settings_object)
525
0
{
526
0
    m_backup_incumbent_settings_object_stack.append(environment_settings_object);
527
0
}
528
529
void EventLoop::pop_backup_incumbent_settings_object_stack(Badge<EnvironmentSettingsObject>)
530
0
{
531
0
    m_backup_incumbent_settings_object_stack.take_last();
532
0
}
533
534
EnvironmentSettingsObject& EventLoop::top_of_backup_incumbent_settings_object_stack()
535
0
{
536
0
    return m_backup_incumbent_settings_object_stack.last();
537
0
}
538
539
void EventLoop::register_environment_settings_object(Badge<EnvironmentSettingsObject>, EnvironmentSettingsObject& environment_settings_object)
540
0
{
541
0
    m_related_environment_settings_objects.append(&environment_settings_object);
542
0
}
543
544
void EventLoop::unregister_environment_settings_object(Badge<EnvironmentSettingsObject>, EnvironmentSettingsObject& environment_settings_object)
545
0
{
546
0
    bool did_remove = m_related_environment_settings_objects.remove_first_matching([&](auto& entry) { return entry == &environment_settings_object; });
547
0
    VERIFY(did_remove);
548
0
}
549
550
// https://html.spec.whatwg.org/multipage/webappapis.html#same-loop-windows
551
Vector<JS::Handle<HTML::Window>> EventLoop::same_loop_windows() const
552
0
{
553
0
    Vector<JS::Handle<HTML::Window>> windows;
554
0
    for (auto& document : documents_in_this_event_loop()) {
555
0
        if (document->is_fully_active())
556
0
            windows.append(JS::make_handle(document->window()));
557
0
    }
558
0
    return windows;
559
0
}
560
561
// https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model:last-idle-period-start-time
562
double EventLoop::compute_deadline() const
563
0
{
564
    // 1. Let deadline be this event loop's last idle period start time plus 50.
565
0
    auto deadline = m_last_idle_period_start_time + 50;
566
    // 2. Let hasPendingRenders be false.
567
0
    auto has_pending_renders = false;
568
    // 3. For each windowInSameLoop of the same-loop windows for this event loop:
569
0
    for (auto& window : same_loop_windows()) {
570
        // 1. If windowInSameLoop's map of animation frame callbacks is not empty,
571
        //    or if the user agent believes that the windowInSameLoop might have pending rendering updates,
572
        //    set hasPendingRenders to true.
573
0
        if (window->has_animation_frame_callbacks())
574
0
            has_pending_renders = true;
575
        // FIXME: 2. Let timerCallbackEstimates be the result of getting the values of windowInSameLoop's map of active timers.
576
        // FIXME: 3. For each timeoutDeadline of timerCallbackEstimates, if timeoutDeadline is less than deadline, set deadline to timeoutDeadline.
577
0
    }
578
    // 4. If hasPendingRenders is true, then:
579
0
    if (has_pending_renders) {
580
        // 1. Let nextRenderDeadline be this event loop's last render opportunity time plus (1000 divided by the current refresh rate).
581
        // FIXME: Hardcoded to 60Hz
582
0
        auto next_render_deadline = m_last_render_opportunity_time + (1000.0 / 60.0);
583
        // 2. If nextRenderDeadline is less than deadline, then return nextRenderDeadline.
584
0
        if (next_render_deadline < deadline)
585
0
            return next_render_deadline;
586
0
    }
587
    // 5. Return deadline.
588
0
    return deadline;
589
0
}
590
591
}