2021-09-08 23:09:18 +02:00
/*
2024-10-04 13:19:50 +02:00
* Copyright ( c ) 2021 , Andreas Kling < andreas @ ladybird . org >
2022-03-31 21:55:01 +02:00
* Copyright ( c ) 2022 , the SerenityOS developers .
2021-09-08 23:09:18 +02:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2024-01-18 13:02:24 -07:00
# include <LibCore/EventLoop.h>
2021-09-08 23:09:18 +02:00
# include <LibJS/Runtime/VM.h>
# include <LibWeb/Bindings/MainThreadVM.h>
2024-09-29 23:38:49 +02:00
# include <LibWeb/CSS/FontFaceSet.h>
# include <LibWeb/CSS/StyleComputer.h>
2021-10-03 16:07:21 +02:00
# include <LibWeb/DOM/Document.h>
2024-11-26 23:58:07 +01:00
# include <LibWeb/DOM/Element.h>
2021-11-18 15:01:28 +01:00
# include <LibWeb/HTML/BrowsingContext.h>
2021-09-08 23:09:18 +02:00
# include <LibWeb/HTML/EventLoop/EventLoop.h>
2025-01-04 23:12:55 +13:00
# include <LibWeb/HTML/Scripting/Agent.h>
2021-10-14 16:12:53 +01:00
# include <LibWeb/HTML/Scripting/Environments.h>
2024-09-29 23:38:49 +02:00
# include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
2024-07-03 22:12:28 +10:00
# include <LibWeb/HTML/TraversableNavigable.h>
2022-03-07 23:08:26 +01:00
# include <LibWeb/HTML/Window.h>
2021-10-03 16:28:14 +02:00
# include <LibWeb/HighResolutionTime/Performance.h>
2022-10-04 21:30:29 +01:00
# include <LibWeb/HighResolutionTime/TimeOrigin.h>
2025-06-16 16:41:29 +02:00
# include <LibWeb/IndexedDB/Internal/Algorithms.h>
2024-02-18 19:40:11 +01:00
# include <LibWeb/Page/Page.h>
2024-11-26 23:58:07 +01:00
# include <LibWeb/Painting/PaintableBox.h>
2025-06-11 10:44:44 +02:00
# include <LibWeb/Painting/ViewportPaintable.h>
2022-09-07 20:30:31 +02:00
# include <LibWeb/Platform/EventLoopPlugin.h>
# include <LibWeb/Platform/Timer.h>
2021-09-08 23:09:18 +02:00
namespace Web : : HTML {
2024-11-15 04:01:23 +13:00
GC_DEFINE_ALLOCATOR ( EventLoop ) ;
2024-04-06 10:16:04 -07:00
2024-07-09 02:59:25 -06:00
EventLoop : : EventLoop ( Type type )
: m_type ( type )
2021-09-08 23:09:18 +02:00
{
2024-11-14 06:13:46 +13:00
m_task_queue = heap ( ) . allocate < TaskQueue > ( * this ) ;
m_microtask_queue = heap ( ) . allocate < TaskQueue > ( * this ) ;
2024-10-25 09:56:48 +02:00
2024-11-15 04:01:23 +13:00
m_rendering_task_function = GC : : create_function ( heap ( ) , [ this ] {
2024-10-25 09:56:48 +02:00
update_the_rendering ( ) ;
} ) ;
2021-09-08 23:09:18 +02:00
}
2022-03-14 13:21:51 -06:00
EventLoop : : ~ EventLoop ( ) = default ;
2021-09-08 23:09:18 +02:00
2024-04-04 12:06:50 +02:00
void EventLoop : : visit_edges ( Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
visitor . visit ( m_task_queue ) ;
visitor . visit ( m_microtask_queue ) ;
visitor . visit ( m_currently_running_task ) ;
2024-10-21 20:54:39 +13:00
visitor . visit ( m_backup_incumbent_realm_stack ) ;
2024-10-25 09:56:48 +02:00
visitor . visit ( m_rendering_task_function ) ;
2024-10-30 21:37:08 +13:00
visitor . visit ( m_system_event_loop_timer ) ;
2024-04-04 12:06:50 +02:00
}
2021-09-09 00:38:43 +02:00
void EventLoop : : schedule ( )
{
if ( ! m_system_event_loop_timer ) {
2024-11-15 04:01:23 +13:00
m_system_event_loop_timer = Platform : : Timer : : create_single_shot ( heap ( ) , 0 , GC : : create_function ( heap ( ) , [ this ] {
2021-09-09 00:38:43 +02:00
process ( ) ;
2024-10-30 21:42:05 +13:00
} ) ) ;
2021-09-09 00:38:43 +02:00
}
if ( ! m_system_event_loop_timer - > is_active ( ) )
m_system_event_loop_timer - > restart ( ) ;
}
2021-09-08 23:09:18 +02:00
EventLoop & main_thread_event_loop ( )
{
2025-04-20 16:49:34 +12:00
return * static_cast < HTML : : Agent * > ( Bindings : : main_thread_vm ( ) . agent ( ) ) - > event_loop ;
2021-09-08 23:09:18 +02:00
}
2021-09-08 23:50:32 +02:00
// https://html.spec.whatwg.org/multipage/webappapis.html#spin-the-event-loop
2024-11-15 04:01:23 +13:00
void EventLoop : : spin_until ( GC : : Ref < GC : : Function < bool ( ) > > goal_condition )
2021-09-08 23:50:32 +02:00
{
2024-01-18 13:02:24 -07:00
// 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.
// 1. Let task be the event loop's currently running task.
// 2. Let task source be task's source.
2021-09-08 23:50:32 +02:00
2021-10-03 14:54:01 +02:00
// 3. Let old stack be a copy of the JavaScript execution context stack.
// 4. Empty the JavaScript execution context stack.
2024-04-04 12:06:50 +02:00
auto & vm = this - > vm ( ) ;
vm . save_execution_context_stack ( ) ;
vm . clear_execution_context_stack ( ) ;
2021-09-08 23:50:32 +02:00
2021-10-03 14:54:01 +02:00
// 5. Perform a microtask checkpoint.
perform_a_microtask_checkpoint ( ) ;
2021-09-08 23:50:32 +02:00
2021-10-03 14:54:01 +02:00
// 6. In parallel:
2024-01-18 13:02:24 -07:00
// 1. Wait until the condition goal is met.
2021-10-03 14:54:01 +02:00
// 2. Queue a task on task source to:
// 1. Replace the JavaScript execution context stack with old stack.
// 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.
2021-09-08 23:50:32 +02:00
2024-11-15 04:01:23 +13:00
Platform : : EventLoopPlugin : : the ( ) . spin_until ( GC : : create_function ( heap ( ) , [ this , goal_condition ] {
2024-10-31 05:23:43 +13:00
if ( goal_condition - > function ( ) ( ) )
2024-01-18 13:02:24 -07:00
return true ;
2024-04-04 12:06:50 +02:00
if ( m_task_queue - > has_runnable_tasks ( ) ) {
2024-01-18 13:02:24 -07:00
schedule ( ) ;
// FIXME: Remove the platform event loop plugin so that this doesn't look out of place
Core : : EventLoop : : current ( ) . wake ( ) ;
}
2024-10-31 05:23:43 +13:00
return goal_condition - > function ( ) ( ) ;
2024-10-31 01:06:56 +13:00
} ) ) ;
2024-01-18 13:02:24 -07:00
2024-04-04 12:06:50 +02:00
vm . restore_execution_context_stack ( ) ;
2021-09-08 23:50:32 +02:00
2021-10-03 14:54:01 +02:00
// 7. Stop task, allowing whatever algorithm that invoked it to resume.
// NOTE: This is achieved by returning from the function.
2021-09-08 23:50:32 +02:00
}
2024-11-15 04:01:23 +13:00
void EventLoop : : spin_processing_tasks_with_source_until ( Task : : Source source , GC : : Ref < GC : : Function < bool ( ) > > goal_condition )
2024-03-20 15:41:45 +01:00
{
2024-04-04 12:06:50 +02:00
auto & vm = this - > vm ( ) ;
vm . save_execution_context_stack ( ) ;
vm . clear_execution_context_stack ( ) ;
2024-03-20 15:41:45 +01:00
perform_a_microtask_checkpoint ( ) ;
// NOTE: HTML event loop processing steps could run a task with arbitrary source
m_skip_event_loop_processing_steps = true ;
2024-11-15 04:01:23 +13:00
Platform : : EventLoopPlugin : : the ( ) . spin_until ( GC : : create_function ( heap ( ) , [ this , source , goal_condition ] {
2024-10-31 05:23:43 +13:00
if ( goal_condition - > function ( ) ( ) )
2024-03-20 15:41:45 +01:00
return true ;
2024-04-04 12:06:50 +02:00
if ( m_task_queue - > has_runnable_tasks ( ) ) {
auto tasks = m_task_queue - > take_tasks_matching ( [ & ] ( auto & task ) {
2024-04-02 17:16:01 +02:00
return task . source ( ) = = source & & task . is_runnable ( ) ;
2024-03-20 15:41:45 +01:00
} ) ;
2024-04-04 12:06:50 +02:00
for ( auto & task : tasks ) {
2024-03-20 15:41:45 +01:00
m_currently_running_task = task . ptr ( ) ;
task - > execute ( ) ;
m_currently_running_task = nullptr ;
}
}
// FIXME: Remove the platform event loop plugin so that this doesn't look out of place
Core : : EventLoop : : current ( ) . wake ( ) ;
2024-10-31 05:23:43 +13:00
return goal_condition - > function ( ) ( ) ;
2024-10-31 01:06:56 +13:00
} ) ) ;
2024-03-20 15:41:45 +01:00
m_skip_event_loop_processing_steps = false ;
schedule ( ) ;
2024-04-04 12:06:50 +02:00
vm . restore_execution_context_stack ( ) ;
2024-03-20 15:41:45 +01:00
}
2021-09-09 00:18:04 +02:00
// https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model
void EventLoop : : process ( )
{
2024-03-20 15:41:45 +01:00
if ( m_skip_event_loop_processing_steps )
return ;
2024-10-03 17:44:57 +02:00
// 1. Let oldestTask and taskStartTime be null.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Task > oldest_task ;
2024-10-03 17:44:57 +02:00
[[maybe_unused]] double task_start_time = 0 ;
2022-03-31 21:55:01 +02:00
2024-10-03 17:44:57 +02:00
// 2. If the event loop has a task queue with at least one runnable task, then:
if ( m_task_queue - > has_runnable_tasks ( ) ) {
// 1. Let taskQueue be one such task queue, chosen in an implementation-defined manner.
auto task_queue = m_task_queue ;
2022-03-31 21:55:01 +02:00
2024-10-03 17:44:57 +02:00
// 2. Set taskStartTime to the unsafe shared current time.
task_start_time = HighResolutionTime : : unsafe_shared_current_time ( ) ;
2021-09-09 00:18:04 +02:00
2024-10-03 17:44:57 +02:00
// 3. Set oldestTask to the first runnable task in taskQueue, and remove it from taskQueue.
oldest_task = task_queue - > take_first_runnable ( ) ;
// FIXME: 4. If oldestTask's document is not null, then record task start time given taskStartTime and oldestTask's document.
2021-09-09 00:18:04 +02:00
2022-03-31 21:55:01 +02:00
// 5. Set the event loop's currently running task to oldestTask.
2021-09-26 14:36:20 +02:00
m_currently_running_task = oldest_task . ptr ( ) ;
2021-09-09 00:38:43 +02:00
2022-03-31 21:55:01 +02:00
// 6. Perform oldestTask's steps.
2021-09-26 14:36:20 +02:00
oldest_task - > execute ( ) ;
2021-09-09 00:18:04 +02:00
2022-03-31 21:55:01 +02:00
// 7. Set the event loop's currently running task back to null.
2021-09-26 14:36:20 +02:00
m_currently_running_task = nullptr ;
2024-10-03 17:44:57 +02:00
// 8. Perform a microtask checkpoint.
perform_a_microtask_checkpoint ( ) ;
2021-09-26 14:36:20 +02:00
}
2021-09-09 00:18:04 +02:00
2024-10-03 17:44:57 +02:00
// 3. Let taskEndTime be the unsafe shared current time. [HRT]
[[maybe_unused]] auto task_end_time = HighResolutionTime : : unsafe_shared_current_time ( ) ;
2021-09-09 00:18:04 +02:00
2024-10-03 17:44:57 +02:00
// 4. If oldestTask is not null, then:
if ( oldest_task ) {
// FIXME: 1. Let top-level browsing contexts be an empty set.
// FIXME: 2. For each environment settings object settings of oldestTask's script evaluation environment settings object set:
// FIXME: 2.1. Let global be settings's global object.
// FIXME: 2.2. If global is not a Window object, then continue.
// FIXME: 2.3. If global's browsing context is null, then continue.
// FIXME: 2.4. Let tlbc be global's browsing context's top-level browsing context.
// FIXME: 2.5. If tlbc is not null, then append it to top-level browsing contexts.
// FIXME: 3. Report long tasks, passing in taskStartTime, taskEndTime, top-level browsing contexts, and oldestTask.
// FIXME: 4. If oldestTask's document is not null, then record task end time given taskEndTime and oldestTask's document.
2024-09-29 17:25:26 +02:00
}
2024-10-03 17:44:57 +02:00
// 5. If this is a window event loop that has no runnable task in this event loop's task queues, then:
if ( m_type = = Type : : Window & & ! m_task_queue - > has_runnable_tasks ( ) ) {
// 1. Set this event loop's last idle period start time to the unsafe shared current time.
m_last_idle_period_start_time = HighResolutionTime : : unsafe_shared_current_time ( ) ;
2021-09-09 00:18:04 +02:00
2024-10-03 17:44:57 +02:00
// 2. Let computeDeadline be the following steps:
// Implemented in EventLoop::compute_deadline()
2021-09-09 00:18:04 +02:00
2024-10-03 17:44:57 +02:00
// 3. For each win of the same-loop windows for this event loop, perform the start an idle period algorithm for win with the following step: return the result of calling computeDeadline, coarsened given win's relevant settings object's cross-origin isolated capability. [REQUESTIDLECALLBACK]
for ( auto & win : same_loop_windows ( ) ) {
win - > start_an_idle_period ( ) ;
}
}
2021-09-09 00:18:04 +02:00
2024-10-03 17:44:57 +02:00
// If there are eligible tasks in the queue, schedule a new round of processing. :^)
if ( m_task_queue - > has_runnable_tasks ( ) | | ( ! m_microtask_queue - > is_empty ( ) & & ! m_performing_a_microtask_checkpoint ) ) {
schedule ( ) ;
}
}
2021-09-09 00:18:04 +02:00
2024-10-03 17:44:57 +02:00
// https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model
void EventLoop : : queue_task_to_update_the_rendering ( )
{
// FIXME: 1. Wait until at least one navigable whose active document's relevant agent's event loop is eventLoop might have a rendering opportunity.
// 2. Set eventLoop's last render opportunity time to the unsafe shared current time.
m_last_render_opportunity_time = HighResolutionTime : : unsafe_shared_current_time ( ) ;
2024-10-06 15:16:35 +02:00
// OPTIMIZATION: If there are already rendering tasks in the queue, we don't need to queue another one.
if ( m_task_queue - > has_rendering_tasks ( ) ) {
return ;
}
2024-10-03 17:44:57 +02:00
// 3. For each navigable that has a rendering opportunity, queue a global task on the rendering task source given navigable's active window to update the rendering:
for ( auto & navigable : all_navigables ( ) ) {
if ( ! navigable - > is_traversable ( ) )
continue ;
if ( ! navigable - > has_a_rendering_opportunity ( ) )
continue ;
auto document = navigable - > active_document ( ) ;
if ( ! document )
continue ;
if ( document - > is_decoded_svg ( ) )
continue ;
2024-10-25 09:56:48 +02:00
queue_global_task ( Task : : Source : : Rendering , * navigable - > active_window ( ) , * m_rendering_task_function ) ;
}
}
2024-10-03 17:44:57 +02:00
2025-02-14 23:25:12 +01:00
void EventLoop : : process_input_events ( ) const
{
auto process_input_events_queue = [ & ] ( Page & page ) {
auto & page_client = page . client ( ) ;
auto & input_events_queue = page_client . input_event_queue ( ) ;
while ( ! input_events_queue . is_empty ( ) ) {
auto event = input_events_queue . dequeue ( ) ;
auto result = event . event . visit (
[ & ] ( KeyEvent const & key_event ) {
switch ( key_event . type ) {
case KeyEvent : : Type : : KeyDown :
return page . handle_keydown ( key_event . key , key_event . modifiers , key_event . code_point , key_event . repeat ) ;
case KeyEvent : : Type : : KeyUp :
return page . handle_keyup ( key_event . key , key_event . modifiers , key_event . code_point , key_event . repeat ) ;
}
VERIFY_NOT_REACHED ( ) ;
} ,
[ & ] ( MouseEvent const & mouse_event ) {
switch ( mouse_event . type ) {
case MouseEvent : : Type : : MouseDown :
return page . handle_mousedown ( mouse_event . position , mouse_event . screen_position , mouse_event . button , mouse_event . buttons , mouse_event . modifiers ) ;
case MouseEvent : : Type : : MouseUp :
return page . handle_mouseup ( mouse_event . position , mouse_event . screen_position , mouse_event . button , mouse_event . buttons , mouse_event . modifiers ) ;
case MouseEvent : : Type : : MouseMove :
return page . handle_mousemove ( mouse_event . position , mouse_event . screen_position , mouse_event . buttons , mouse_event . modifiers ) ;
2025-07-28 12:57:31 -04:00
case MouseEvent : : Type : : MouseLeave :
return page . handle_mouseleave ( ) ;
2025-02-14 23:25:12 +01:00
case MouseEvent : : Type : : MouseWheel :
return page . handle_mousewheel ( mouse_event . position , mouse_event . screen_position , mouse_event . button , mouse_event . buttons , mouse_event . modifiers , mouse_event . wheel_delta_x , mouse_event . wheel_delta_y ) ;
case MouseEvent : : Type : : DoubleClick :
return page . handle_doubleclick ( mouse_event . position , mouse_event . screen_position , mouse_event . button , mouse_event . buttons , mouse_event . modifiers ) ;
}
VERIFY_NOT_REACHED ( ) ;
} ,
[ & ] ( Web : : DragEvent & drag_event ) {
return page . handle_drag_and_drop_event ( drag_event . type , drag_event . position , drag_event . screen_position , drag_event . button , drag_event . buttons , drag_event . modifiers , move ( drag_event . files ) ) ;
} ) ;
for ( size_t i = 0 ; i < event . coalesced_event_count ; + + i )
page_client . report_finished_handling_input_event ( event . page_id , EventResult : : Dropped ) ;
page_client . report_finished_handling_input_event ( event . page_id , result ) ;
}
} ;
auto documents_of_traversable_navigables = documents_in_this_event_loop_matching ( [ & ] ( auto const & document ) {
if ( document . is_decoded_svg ( ) )
return false ;
if ( ! document . navigable ( ) )
return false ;
if ( ! document . navigable ( ) - > is_traversable ( ) )
return false ;
return true ;
} ) ;
for ( auto const & document : documents_of_traversable_navigables ) {
process_input_events_queue ( document - > page ( ) ) ;
}
}
2025-02-04 13:01:46 +01:00
// https://html.spec.whatwg.org/multipage/webappapis.html#update-the-rendering
2024-10-25 09:56:48 +02:00
void EventLoop : : update_the_rendering ( )
{
2025-02-27 15:41:15 +01:00
VERIFY ( ! m_running_rendering_task ) ;
m_running_rendering_task = true ;
2024-10-25 09:56:48 +02:00
ScopeGuard const guard = [ this ] {
2025-02-27 15:41:15 +01:00
m_running_rendering_task = false ;
2024-10-25 09:56:48 +02:00
} ;
2025-02-14 23:25:12 +01:00
process_input_events ( ) ;
2025-01-24 20:10:20 +00:00
// 1. Let frameTimestamp be eventLoop's last render opportunity time.
auto frame_timestamp = m_last_render_opportunity_time ;
2024-10-25 09:56:48 +02:00
// FIXME: 2. Let docs be all fully active Document objects whose relevant agent's event loop is eventLoop, sorted arbitrarily except that the following conditions must be met:
// 3. Filter non-renderable documents: Remove from docs any Document object doc for which any of the following are true:
2024-11-24 15:47:18 +01:00
auto docs = documents_in_this_event_loop_matching ( [ & ] ( auto const & document ) {
if ( ! document . is_fully_active ( ) )
return false ;
2021-09-09 00:18:04 +02:00
2025-02-04 19:09:14 +01:00
// doc is render-blocked;
if ( document . is_render_blocked ( ) ) {
return false ;
}
2021-09-09 00:18:04 +02:00
2024-10-25 09:56:48 +02:00
// doc's visibility state is "hidden";
2024-11-24 15:47:18 +01:00
if ( document . hidden ( ) )
return false ;
2021-09-09 00:18:04 +02:00
2024-10-25 09:56:48 +02:00
// FIXME: doc's rendering is suppressed for view transitions; or
2021-10-03 16:28:14 +02:00
2024-11-24 15:47:18 +01:00
auto navigable = document . navigable ( ) ;
if ( ! navigable )
return false ;
2024-10-25 09:56:48 +02:00
// doc's node navigable doesn't currently have a rendering opportunity.
if ( ! navigable - > has_a_rendering_opportunity ( ) )
2024-11-24 15:47:18 +01:00
return false ;
2024-03-24 13:22:01 +01:00
2024-11-24 15:47:18 +01:00
return true ;
2024-10-25 09:56:48 +02:00
} ) ;
2021-09-09 00:18:04 +02:00
2024-10-25 09:56:48 +02:00
// FIXME: 4. Unnecessary rendering: Remove from docs any Document object doc for which all of the following are true:
2021-09-09 00:18:04 +02:00
2024-10-25 09:56:48 +02:00
// 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.
2021-09-09 00:18:04 +02:00
2024-10-25 09:56:48 +02:00
// FIXME: 6. For each doc of docs, reveal doc.
2021-09-09 00:18:04 +02:00
2024-10-25 09:56:48 +02:00
// FIXME: 7. For each doc of docs, flush autofocus candidates for doc if its node navigable is a top-level traversable.
2021-09-09 00:18:04 +02:00
2024-10-25 09:56:48 +02:00
// 8. For each doc of docs, run the resize steps for doc. [CSSOMVIEW]
for ( auto & document : docs ) {
document - > run_the_resize_steps ( ) ;
}
2021-09-09 00:18:04 +02:00
2024-10-25 09:56:48 +02:00
// 9. For each doc of docs, run the scroll steps for doc. [CSSOMVIEW]
for ( auto & document : docs ) {
document - > run_the_scroll_steps ( ) ;
}
2021-09-09 00:18:04 +02:00
2024-10-25 09:56:48 +02:00
// 10. For each doc of docs, evaluate media queries and report changes for doc. [CSSOMVIEW]
for ( auto & document : docs ) {
document - > evaluate_media_queries_and_report_changes ( ) ;
}
2021-09-09 00:18:04 +02:00
2024-10-25 09:56:48 +02:00
// 11. For each doc of docs, update animations and send events for doc, passing in relative high resolution time given frameTimestamp and doc's relevant global object as the timestamp [WEBANIMATIONS]
for ( auto & document : docs ) {
2025-01-24 20:10:20 +00:00
document - > update_animations_and_send_events ( HighResolutionTime : : relative_high_resolution_time ( frame_timestamp , relevant_global_object ( * document ) ) ) ;
2024-10-25 09:56:48 +02:00
} ;
2021-09-09 00:18:04 +02:00
2024-10-25 09:56:48 +02:00
// FIXME: 12. For each doc of docs, run the fullscreen steps for doc. [FULLSCREEN]
2021-09-09 00:18:04 +02:00
2024-10-25 09:56:48 +02:00
// FIXME: 13. For each doc of 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:
2021-09-09 00:18:04 +02:00
2024-10-25 09:56:48 +02:00
// 14. For each doc of docs, run the animation frame callbacks for doc, passing in the relative high resolution time given frameTimestamp and doc's relevant global object as the timestamp.
for ( auto & document : docs ) {
2025-01-24 20:10:20 +00:00
auto now = HighResolutionTime : : relative_high_resolution_time ( frame_timestamp , relevant_global_object ( * document ) ) ;
2024-10-25 09:56:48 +02:00
run_animation_frame_callbacks ( * document , now ) ;
}
2021-09-09 00:18:04 +02:00
2024-10-25 09:56:48 +02:00
// FIXME: 15. Let unsafeStyleAndLayoutStartTime be the unsafe shared current time.
2024-02-19 05:10:05 +01:00
2024-10-25 09:56:48 +02:00
// 16. For each doc of docs:
for ( auto & document : docs ) {
// 1. Let resizeObserverDepth be 0.
size_t resize_observer_depth = 0 ;
2024-02-19 05:10:05 +01:00
2024-10-25 09:56:48 +02:00
// 2. While true:
while ( true ) {
// 1. Recalculate styles and update layout for doc.
// NOTE: Recalculation of styles is handled by update_layout()
2025-03-05 20:50:05 +01:00
document - > update_layout ( DOM : : UpdateLayoutReason : : HTMLEventLoopRenderingUpdate ) ;
2024-02-19 05:10:05 +01:00
2024-11-26 23:58:07 +01:00
// 2. Let hadInitialVisibleContentVisibilityDetermination be false.
bool had_initial_visible_content_visibility_determination = false ;
// 3. For each element element with 'auto' used value of 'content-visibility':
auto * document_element = document - > document_element ( ) ;
if ( document_element ) {
2025-06-11 10:44:44 +02:00
for ( auto & paintable_box : document - > paintable ( ) - > paintable_boxes_with_auto_content_visibility ( ) ) {
auto & element = as < DOM : : Element > ( * paintable_box - > dom_node ( ) ) ;
2024-11-26 23:58:07 +01:00
// 1. Let checkForInitialDetermination be true if element's proximity to the viewport is not determined and it is not relevant to the user. Otherwise, let checkForInitialDetermination be false.
bool check_for_initial_determination = element . proximity_to_the_viewport ( ) = = Web : : DOM : : ProximityToTheViewport : : NotDetermined & & ! element . is_relevant_to_the_user ( ) ;
// 2. Determine proximity to the viewport for element.
element . determine_proximity_to_the_viewport ( ) ;
// 3. If checkForInitialDetermination is true and element is now relevant to the user, then set hadInitialVisibleContentVisibilityDetermination to true.
if ( check_for_initial_determination & & element . is_relevant_to_the_user ( ) ) {
had_initial_visible_content_visibility_determination = true ;
}
2025-06-11 10:44:44 +02:00
}
2024-11-26 23:58:07 +01:00
}
// 4. If hadInitialVisibleContentVisibilityDetermination is true, then continue.
if ( had_initial_visible_content_visibility_determination )
continue ;
2021-09-09 00:18:04 +02:00
2024-10-25 09:56:48 +02:00
// 5. Gather active resize observations at depth resizeObserverDepth for doc.
document - > gather_active_observations_at_depth ( resize_observer_depth ) ;
2021-09-09 00:18:04 +02:00
2024-10-25 09:56:48 +02:00
// 6. If doc has active resize observations:
if ( document - > has_active_resize_observations ( ) ) {
// 1. Set resizeObserverDepth to the result of broadcasting active resize observations given doc.
resize_observer_depth = document - > broadcast_active_resize_observations ( ) ;
2024-07-03 22:12:28 +10:00
2024-10-25 09:56:48 +02:00
// 2. Continue.
continue ;
}
2022-03-31 21:55:01 +02:00
2024-10-25 09:56:48 +02:00
// 7. Otherwise, break.
break ;
}
2022-03-31 21:55:01 +02:00
2024-10-25 09:56:48 +02:00
// 3. If doc has skipped resize observations, then deliver resize loop error given doc.
if ( document - > has_skipped_resize_observations ( ) ) {
// FIXME: Deliver resize loop error.
}
}
2021-09-09 00:18:04 +02:00
2024-10-25 09:56:48 +02:00
// FIXME: 17. For each doc of docs, if the focused area of doc is not a focusable area, then run the focusing steps for doc's viewport, and set doc's relevant global object's navigation API's focus changed during ongoing navigation to false.
2021-09-09 00:18:04 +02:00
2024-10-25 09:56:48 +02:00
// FIXME: 18. For each doc of docs, perform pending transition operations for doc. [CSSVIEWTRANSITIONS]
2021-09-09 00:18:04 +02:00
2024-10-25 09:56:48 +02:00
// 19. For each doc of docs, run the update intersection observations steps for doc, passing in the relative high resolution time given now and doc's relevant global object as the timestamp. [INTERSECTIONOBSERVER]
for ( auto & document : docs ) {
2025-01-24 20:10:20 +00:00
auto now = HighResolutionTime : : relative_high_resolution_time ( frame_timestamp , relevant_global_object ( * document ) ) ;
2024-10-25 09:56:48 +02:00
document - > run_the_update_intersection_observations_steps ( now ) ;
}
2021-09-09 00:38:43 +02:00
2024-10-25 09:56:48 +02:00
// FIXME: 20. For each doc of docs, record rendering time for doc given unsafeStyleAndLayoutStartTime.
2024-03-28 16:53:27 +01:00
2024-10-25 09:56:48 +02:00
// FIXME: 21. For each doc of docs, mark paint timing for doc.
2024-09-29 23:38:49 +02:00
2024-10-25 09:56:48 +02:00
// 22. For each doc of docs, update the rendering or user interface of doc and its node navigable to reflect the current state.
for ( auto & document : docs ) {
auto navigable = document - > navigable ( ) ;
2025-02-01 19:33:18 +01:00
if ( ! navigable - > is_traversable ( ) )
continue ;
auto traversable = navigable - > traversable_navigable ( ) ;
2025-06-26 22:33:58 +02:00
traversable - > process_screenshot_requests ( ) ;
if ( ! navigable - > needs_repaint ( ) )
continue ;
navigable - > paint_next_frame ( ) ;
2024-10-25 09:56:48 +02:00
}
2024-10-03 17:44:57 +02:00
2024-10-25 09:56:48 +02:00
// 23. For each doc of docs, process top layer removals given doc.
for ( auto & document : docs ) {
document - > process_top_layer_removals ( ) ;
}
for ( auto & document : docs ) {
if ( document - > readiness ( ) = = HTML : : DocumentReadyState : : Complete & & document - > style_computer ( ) . number_of_css_font_faces_with_loading_in_progress ( ) = = 0 ) {
2024-10-24 20:39:18 +13:00
HTML : : TemporaryExecutionContext context ( document - > realm ( ) , HTML : : TemporaryExecutionContext : : CallbacksEnabled : : Yes ) ;
2024-10-25 09:56:48 +02:00
document - > fonts ( ) - > resolve_ready_promise ( ) ;
}
2024-10-03 17:44:57 +02:00
}
2021-09-09 00:18:04 +02:00
}
2024-04-05 11:07:32 -04:00
// https://html.spec.whatwg.org/multipage/webappapis.html#queue-a-task
2024-11-15 04:01:23 +13:00
TaskID queue_a_task ( HTML : : Task : : Source source , GC : : Ptr < EventLoop > event_loop , GC : : Ptr < DOM : : Document > document , GC : : Ref < GC : : Function < void ( ) > > steps )
2024-04-05 11:07:32 -04:00
{
// 1. If event loop was not given, set event loop to the implied event loop.
if ( ! event_loop )
event_loop = main_thread_event_loop ( ) ;
// FIXME: 2. If document was not given, set document to the implied document.
// 3. Let task be a new task.
// 4. Set task's steps to steps.
// 5. Set task's source to source.
// 6. Set task's document to the document.
// 7. Set task's script evaluation environment settings object set to an empty set.
auto task = HTML : : Task : : create ( event_loop - > vm ( ) , source , document , steps ) ;
// 8. Let queue be the task queue to which source is associated on event loop.
2024-07-24 22:30:30 +04:00
auto & queue = source = = HTML : : Task : : Source : : Microtask ? event_loop - > microtask_queue ( ) : event_loop - > task_queue ( ) ;
2024-04-05 11:07:32 -04:00
// 9. Append task to queue.
queue . add ( task ) ;
return queue . last_added_task ( ) - > id ( ) ;
}
2021-10-14 16:12:53 +01:00
// https://html.spec.whatwg.org/multipage/webappapis.html#queue-a-global-task
2024-11-15 04:01:23 +13:00
TaskID queue_global_task ( HTML : : Task : : Source source , JS : : Object & global_object , GC : : Ref < GC : : Function < void ( ) > > steps )
2021-10-14 16:12:53 +01:00
{
// 1. Let event loop be global's relevant agent's event loop.
2025-01-04 23:12:55 +13:00
auto & event_loop = relevant_agent ( global_object ) . event_loop ;
2021-10-14 16:12:53 +01:00
// 2. Let document be global's associated Document, if global is a Window object; otherwise null.
DOM : : Document * document { nullptr } ;
2022-08-28 13:42:07 +02:00
if ( is < HTML : : Window > ( global_object ) ) {
2025-01-21 09:12:05 -05:00
auto & window_object = as < HTML : : Window > ( global_object ) ;
2022-09-21 17:45:18 +01:00
document = & window_object . associated_document ( ) ;
2021-10-14 16:12:53 +01:00
}
// 3. Queue a task given source, event loop, document, and steps.
2024-04-05 11:07:32 -04:00
return queue_a_task ( source , * event_loop , document , steps ) ;
2021-10-14 16:12:53 +01:00
}
2025-02-04 13:01:46 +01:00
// https://html.spec.whatwg.org/multipage/webappapis.html#queue-a-microtask
2024-11-15 04:01:23 +13:00
void queue_a_microtask ( DOM : : Document const * document , GC : : Ref < GC : : Function < void ( ) > > steps )
2021-09-26 14:36:20 +02:00
{
// 1. If event loop was not given, set event loop to the implied event loop.
auto & event_loop = HTML : : main_thread_event_loop ( ) ;
// FIXME: 2. If document was not given, set document to the implied document.
// 3. Let microtask be a new task.
// 4. Set microtask's steps to steps.
// 5. Set microtask's source to the microtask task source.
// 6. Set microtask's document to document.
2024-04-04 12:06:50 +02:00
auto & vm = event_loop . vm ( ) ;
2024-04-13 16:32:39 +02:00
auto microtask = HTML : : Task : : create ( vm , HTML : : Task : : Source : : Microtask , document , steps ) ;
2021-09-26 14:36:20 +02:00
// FIXME: 7. Set microtask's script evaluation environment settings object set to an empty set.
// 8. Enqueue microtask on event loop's microtask queue.
2024-04-04 12:06:50 +02:00
event_loop . microtask_queue ( ) . enqueue ( microtask ) ;
2021-09-26 14:36:20 +02:00
}
2022-03-28 03:21:57 +04:30
void perform_a_microtask_checkpoint ( )
{
main_thread_event_loop ( ) . perform_a_microtask_checkpoint ( ) ;
}
2025-02-04 13:01:46 +01:00
// https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint
2021-09-26 14:36:20 +02:00
void EventLoop : : perform_a_microtask_checkpoint ( )
{
2025-01-19 13:13:59 -05:00
if ( execution_paused ( ) )
return ;
2025-01-03 12:10:53 +13:00
// NOTE: This assertion is per requirement 9.5 of the ECMA-262 spec, see: https://tc39.es/ecma262/#sec-jobs
// > At some future point in time, when there is no running context in the agent for which the job is scheduled and that agent's execution context stack is empty...
VERIFY ( vm ( ) . execution_context_stack ( ) . is_empty ( ) ) ;
2021-09-26 14:36:20 +02:00
// 1. If the event loop's performing a microtask checkpoint is true, then return.
if ( m_performing_a_microtask_checkpoint )
return ;
// 2. Set the event loop's performing a microtask checkpoint to true.
m_performing_a_microtask_checkpoint = true ;
// 3. While the event loop's microtask queue is not empty:
2024-04-04 12:06:50 +02:00
while ( ! m_microtask_queue - > is_empty ( ) ) {
2021-09-26 14:36:20 +02:00
// 1. Let oldestMicrotask be the result of dequeuing from the event loop's microtask queue.
2024-04-04 12:06:50 +02:00
auto oldest_microtask = m_microtask_queue - > dequeue ( ) ;
2021-09-26 14:36:20 +02:00
// 2. Set the event loop's currently running task to oldestMicrotask.
m_currently_running_task = oldest_microtask ;
// 3. Run oldestMicrotask.
oldest_microtask - > execute ( ) ;
// 4. Set the event loop's currently running task back to null.
m_currently_running_task = nullptr ;
}
2024-10-23 18:59:19 +13:00
// 4. For each environment settings object settingsObject whose responsible event loop is this event loop, notify about rejected promises given settingsObject's global object.
2025-06-18 20:05:32 +12:00
auto environments = GC : : RootVector { heap ( ) , m_related_environment_settings_objects } ;
for ( auto & environment_settings_object : environments ) {
2025-08-22 11:59:47 +01:00
auto & global = as < HTML : : UniversalGlobalScopeMixin > ( environment_settings_object - > global_object ( ) ) ;
global . notify_about_rejected_promises ( { } ) ;
2024-10-23 18:59:19 +13:00
}
2021-09-26 14:36:20 +02:00
2025-06-16 16:41:29 +02:00
// 5. Cleanup Indexed Database transactions.
IndexedDB : : cleanup_indexed_database_transactions ( * this ) ;
2021-09-26 14:36:20 +02:00
2022-02-06 03:32:26 +00:00
// 6. Perform ClearKeptObjects().
vm ( ) . finish_execution_generation ( ) ;
2021-09-26 14:36:20 +02:00
// 7. Set the event loop's performing a microtask checkpoint to false.
m_performing_a_microtask_checkpoint = false ;
2024-10-23 18:59:19 +13:00
// FIXME: 8. Record timing info for microtask checkpoint.
2021-09-26 14:36:20 +02:00
}
2024-11-24 16:46:42 +00:00
Vector < GC : : Root < DOM : : Document > > EventLoop : : documents_in_this_event_loop_matching ( Function < bool ( DOM : : Document & ) > callback ) const
2021-10-03 16:07:21 +02:00
{
2024-11-15 04:01:23 +13:00
Vector < GC : : Root < DOM : : Document > > documents ;
2021-10-03 16:07:21 +02:00
for ( auto & document : m_documents ) {
VERIFY ( document ) ;
2024-08-18 21:25:04 +02:00
if ( document - > is_decoded_svg ( ) )
continue ;
2024-11-24 15:47:18 +01:00
if ( ! callback ( * document ) )
continue ;
2024-11-15 04:01:23 +13:00
documents . append ( GC : : make_root ( * document ) ) ;
2021-10-03 16:07:21 +02:00
}
return documents ;
}
void EventLoop : : register_document ( Badge < DOM : : Document > , DOM : : Document & document )
{
m_documents . append ( & document ) ;
}
void EventLoop : : unregister_document ( Badge < DOM : : Document > , DOM : : Document & document )
{
bool did_remove = m_documents . remove_first_matching ( [ & ] ( auto & entry ) { return entry . ptr ( ) = = & document ; } ) ;
VERIFY ( did_remove ) ;
}
2024-10-21 20:54:39 +13:00
void EventLoop : : push_onto_backup_incumbent_realm_stack ( JS : : Realm & realm )
2021-10-14 16:12:53 +01:00
{
2024-10-21 20:54:39 +13:00
m_backup_incumbent_realm_stack . append ( realm ) ;
2021-10-14 16:12:53 +01:00
}
2024-10-21 20:54:39 +13:00
void EventLoop : : pop_backup_incumbent_realm_stack ( )
2021-10-14 16:12:53 +01:00
{
2024-10-21 20:54:39 +13:00
m_backup_incumbent_realm_stack . take_last ( ) ;
2021-10-14 16:12:53 +01:00
}
2024-10-21 20:54:39 +13:00
JS : : Realm & EventLoop : : top_of_backup_incumbent_realm_stack ( )
2021-10-14 16:12:53 +01:00
{
2024-10-21 20:54:39 +13:00
return m_backup_incumbent_realm_stack . last ( ) ;
2021-10-14 16:12:53 +01:00
}
void EventLoop : : register_environment_settings_object ( Badge < EnvironmentSettingsObject > , EnvironmentSettingsObject & environment_settings_object )
{
2024-04-04 12:06:50 +02:00
m_related_environment_settings_objects . append ( & environment_settings_object ) ;
2021-10-14 16:12:53 +01:00
}
void EventLoop : : unregister_environment_settings_object ( Badge < EnvironmentSettingsObject > , EnvironmentSettingsObject & environment_settings_object )
{
2024-04-04 12:06:50 +02:00
bool did_remove = m_related_environment_settings_objects . remove_first_matching ( [ & ] ( auto & entry ) { return entry = = & environment_settings_object ; } ) ;
2021-10-14 16:12:53 +01:00
VERIFY ( did_remove ) ;
}
2022-03-31 21:55:01 +02:00
// https://html.spec.whatwg.org/multipage/webappapis.html#same-loop-windows
2024-11-15 04:01:23 +13:00
Vector < GC : : Root < HTML : : Window > > EventLoop : : same_loop_windows ( ) const
2022-03-31 21:55:01 +02:00
{
2024-11-15 04:01:23 +13:00
Vector < GC : : Root < HTML : : Window > > windows ;
2024-11-24 15:47:18 +01:00
for ( auto & document : documents_in_this_event_loop_matching ( [ ] ( auto & document ) { return document . is_fully_active ( ) ; } ) ) {
windows . append ( GC : : make_root ( document - > window ( ) ) ) ;
2022-09-25 16:38:21 -06:00
}
2022-03-31 21:55:01 +02:00
return windows ;
}
// https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model:last-idle-period-start-time
double EventLoop : : compute_deadline ( ) const
{
// 1. Let deadline be this event loop's last idle period start time plus 50.
auto deadline = m_last_idle_period_start_time + 50 ;
// 2. Let hasPendingRenders be false.
auto has_pending_renders = false ;
// 3. For each windowInSameLoop of the same-loop windows for this event loop:
for ( auto & window : same_loop_windows ( ) ) {
// 1. If windowInSameLoop's map of animation frame callbacks is not empty,
// or if the user agent believes that the windowInSameLoop might have pending rendering updates,
// set hasPendingRenders to true.
2022-08-28 13:42:07 +02:00
if ( window - > has_animation_frame_callbacks ( ) )
2022-03-31 21:55:01 +02:00
has_pending_renders = true ;
// FIXME: 2. Let timerCallbackEstimates be the result of getting the values of windowInSameLoop's map of active timers.
// FIXME: 3. For each timeoutDeadline of timerCallbackEstimates, if timeoutDeadline is less than deadline, set deadline to timeoutDeadline.
}
// 4. If hasPendingRenders is true, then:
if ( has_pending_renders ) {
// 1. Let nextRenderDeadline be this event loop's last render opportunity time plus (1000 divided by the current refresh rate).
// FIXME: Hardcoded to 60Hz
auto next_render_deadline = m_last_render_opportunity_time + ( 1000.0 / 60.0 ) ;
// 2. If nextRenderDeadline is less than deadline, then return nextRenderDeadline.
if ( next_render_deadline < deadline )
return next_render_deadline ;
}
// 5. Return deadline.
return deadline ;
}
2024-11-05 08:54:10 -05:00
EventLoop : : PauseHandle : : PauseHandle ( EventLoop & event_loop , JS : : Object const & global , HighResolutionTime : : DOMHighResTimeStamp time_before_pause )
: event_loop ( event_loop )
, global ( global )
, time_before_pause ( time_before_pause )
{
}
EventLoop : : PauseHandle : : ~ PauseHandle ( )
{
event_loop - > unpause ( { } , * global , time_before_pause ) ;
}
// https://html.spec.whatwg.org/multipage/webappapis.html#pause
EventLoop : : PauseHandle EventLoop : : pause ( )
{
2025-01-19 13:13:59 -05:00
m_execution_paused = true ;
2024-11-05 08:54:10 -05:00
// 1. Let global be the current global object.
auto & global = current_principal_global_object ( ) ;
// 2. Let timeBeforePause be the current high resolution time given global.
auto time_before_pause = HighResolutionTime : : current_high_resolution_time ( global ) ;
// 3. If necessary, update the rendering or user interface of any Document or navigable to reflect the current state.
2025-02-27 15:41:15 +01:00
if ( ! m_running_rendering_task )
2024-11-05 08:54:10 -05:00
update_the_rendering ( ) ;
// 4. Wait until the condition goal is met. While a user agent has a paused task, the corresponding event loop must
// not run further tasks, and any script in the currently running task must block. User agents should remain
// responsive to user input while paused, however, albeit in a reduced capacity since the event loop will not be
// doing anything.
return PauseHandle { * this , global , time_before_pause } ;
}
void EventLoop : : unpause ( Badge < PauseHandle > , JS : : Object const & global , HighResolutionTime : : DOMHighResTimeStamp time_before_pause )
{
m_execution_paused = false ;
// FIXME: 5. Record pause duration given the duration from timeBeforePause to the current high resolution time given global.
[[maybe_unused]] auto pause_duration = HighResolutionTime : : current_high_resolution_time ( global ) - time_before_pause ;
}
2021-09-08 23:09:18 +02:00
}