2022-12-12 12:01:09 +01:00
/*
2024-10-04 13:19:50 +02:00
* Copyright ( c ) 2022 , Andreas Kling < andreas @ ladybird . org >
2025-01-28 11:07:42 +01:00
* Copyright ( c ) 2025 , Jelle Raaijmakers < jelle @ ladybird . org >
2025-06-26 22:33:58 +02:00
* Copyright ( c ) 2023 - 2025 , Aliaksandr Kalenik < kalenik . aliaksandr @ gmail . com >
2022-12-12 12:01:09 +01:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2023-07-09 11:40:17 +03:30
# include <AK/QuickSort.h>
2025-11-23 13:07:35 +01:00
# include <LibGfx/Bitmap.h>
2024-09-19 22:34:53 +02:00
# include <LibGfx/SkiaBackendContext.h>
2022-12-17 14:26:48 +01:00
# include <LibWeb/Bindings/MainThreadVM.h>
# include <LibWeb/DOM/Document.h>
2025-06-22 20:26:03 +02:00
# include <LibWeb/Geolocation/GeolocationCoordinates.h>
2026-02-11 07:33:58 +01:00
# include <LibWeb/HTML/BrowsingContext.h>
2022-12-17 14:26:48 +01:00
# include <LibWeb/HTML/BrowsingContextGroup.h>
# include <LibWeb/HTML/DocumentState.h>
2026-02-11 07:33:58 +01:00
# include <LibWeb/HTML/History.h>
# include <LibWeb/HTML/NavigableContainer.h>
2023-09-27 22:59:57 -06:00
# include <LibWeb/HTML/Navigation.h>
2023-04-13 10:23:59 +03:00
# include <LibWeb/HTML/NavigationParams.h>
2024-02-07 09:03:47 -07:00
# include <LibWeb/HTML/Parser/HTMLParser.h>
2022-12-12 12:01:09 +01:00
# include <LibWeb/HTML/SessionHistoryEntry.h>
2025-07-17 13:40:50 -04:00
# include <LibWeb/HTML/StructuredSerialize.h>
2022-12-12 12:01:09 +01:00
# include <LibWeb/HTML/TraversableNavigable.h>
2023-10-08 11:59:40 +02:00
# include <LibWeb/HTML/Window.h>
2025-06-26 22:33:58 +02:00
# include <LibWeb/Layout/Viewport.h>
2023-09-19 19:16:50 +02:00
# include <LibWeb/Page/Page.h>
2025-06-27 04:39:16 +02:00
# include <LibWeb/Painting/PaintableBox.h>
2023-04-13 10:23:59 +03:00
# include <LibWeb/Platform/EventLoopPlugin.h>
2026-03-28 00:08:48 +01:00
# include <LibWeb/Platform/Timer.h>
2022-12-12 12:01:09 +01:00
namespace Web : : HTML {
2024-11-15 04:01:23 +13:00
GC_DEFINE_ALLOCATOR ( TraversableNavigable ) ;
2023-11-19 19:47:52 +01:00
2025-01-28 11:07:42 +01:00
TraversableNavigable : : TraversableNavigable ( GC : : Ref < Page > page )
2025-06-26 22:33:58 +02:00
: Navigable ( page , page - > client ( ) . is_svg_page_client ( ) )
2025-06-11 18:51:22 +02:00
, m_storage_shed ( StorageAPI : : StorageShed : : create ( page - > heap ( ) ) )
2025-01-28 11:07:42 +01:00
, m_session_history_traversal_queue ( vm ( ) . heap ( ) . allocate < SessionHistoryTraversalQueue > ( ) )
{
2023-08-22 15:42:56 +02:00
}
2022-12-12 12:01:09 +01:00
TraversableNavigable : : ~ TraversableNavigable ( ) = default ;
void TraversableNavigable : : visit_edges ( Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
2025-06-22 20:26:03 +02:00
if ( m_emulated_position_data . has < GC : : Ref < Geolocation : : GeolocationCoordinates > > ( ) )
visitor . visit ( m_emulated_position_data . get < GC : : Ref < Geolocation : : GeolocationCoordinates > > ( ) ) ;
2024-04-10 04:43:51 +02:00
visitor . visit ( m_session_history_traversal_queue ) ;
2025-06-11 18:51:22 +02:00
visitor . visit ( m_storage_shed ) ;
2026-03-29 12:06:39 +02:00
visitor . visit ( m_apply_history_step_state ) ;
visitor . visit ( m_paused_apply_history_step_state ) ;
2022-12-12 12:01:09 +01:00
}
2022-12-17 14:26:48 +01:00
static OrderedHashTable < TraversableNavigable * > & user_agent_top_level_traversable_set ( )
{
static OrderedHashTable < TraversableNavigable * > set ;
return set ;
}
// https://html.spec.whatwg.org/multipage/document-sequences.html#creating-a-new-top-level-browsing-context
2026-03-30 16:39:50 +02:00
BrowsingContextAndDocument create_a_new_top_level_browsing_context_and_document ( GC : : Ref < Page > page )
2022-12-17 14:26:48 +01:00
{
// 1. Let group and document be the result of creating a new browsing context group and document.
2026-03-30 16:39:50 +02:00
auto [ group , document ] = BrowsingContextGroup : : create_a_new_browsing_context_group_and_document ( page ) ;
2022-12-17 14:26:48 +01:00
// 2. Return group's browsing context set[0] and document.
return BrowsingContextAndDocument { * * group - > browsing_context_set ( ) . begin ( ) , document } ;
}
// https://html.spec.whatwg.org/multipage/document-sequences.html#creating-a-new-top-level-traversable
2026-03-30 16:39:50 +02:00
GC : : Ref < TraversableNavigable > TraversableNavigable : : create_a_new_top_level_traversable ( GC : : Ref < Page > page , GC : : Ptr < HTML : : BrowsingContext > opener , String target_name )
2022-12-17 14:26:48 +01:00
{
auto & vm = Bindings : : main_thread_vm ( ) ;
// 1. Let document be null.
2024-11-15 04:01:23 +13:00
GC : : Ptr < DOM : : Document > document = nullptr ;
2022-12-17 14:26:48 +01:00
// 2. If opener is null, then set document to the second return value of creating a new top-level browsing context and document.
if ( ! opener ) {
2026-03-30 16:39:50 +02:00
document = create_a_new_top_level_browsing_context_and_document ( page ) . document ;
2022-12-17 14:26:48 +01:00
}
// 3. Otherwise, set document to the second return value of creating a new auxiliary browsing context and document given opener.
else {
2026-03-30 16:39:50 +02:00
document = BrowsingContext : : create_a_new_auxiliary_browsing_context_and_document ( page , * opener ) . document ;
2022-12-17 14:26:48 +01:00
}
// 4. Let documentState be a new document state, with
2026-04-03 12:53:39 +02:00
auto document_state = DocumentState : : create ( ) ;
2022-12-17 14:26:48 +01:00
2026-03-31 16:56:18 +02:00
// document: document (now owned by Navigable::m_active_document, not DocumentState)
2022-12-17 14:26:48 +01:00
2023-09-22 17:31:25 -06:00
// initiator origin: null if opener is null; otherwise, document's origin
2024-10-05 15:33:34 +13:00
document_state - > set_initiator_origin ( opener ? Optional < URL : : Origin > { } : document - > origin ( ) ) ;
2023-09-22 17:31:25 -06:00
// origin: document's origin
document_state - > set_origin ( document - > origin ( ) ) ;
2022-12-17 14:26:48 +01:00
// navigable target name: targetName
document_state - > set_navigable_target_name ( target_name ) ;
2023-09-22 17:31:25 -06:00
// about base URL: document's about base URL
document_state - > set_about_base_url ( document - > about_base_url ( ) ) ;
2022-12-17 14:26:48 +01:00
// 5. Let traversable be a new traversable navigable.
2024-11-14 06:13:46 +13:00
auto traversable = vm . heap ( ) . allocate < TraversableNavigable > ( page ) ;
2022-12-17 14:26:48 +01:00
// 6. Initialize the navigable traversable given documentState.
2026-03-31 16:56:18 +02:00
traversable - > initialize_navigable ( document_state , nullptr , * document ) ;
2022-12-17 14:26:48 +01:00
// 7. Let initialHistoryEntry be traversable's active session history entry.
auto initial_history_entry = traversable - > active_session_history_entry ( ) ;
VERIFY ( initial_history_entry ) ;
// 8. Set initialHistoryEntry's step to 0.
2024-03-27 15:59:12 +01:00
initial_history_entry - > set_step ( 0 ) ;
2022-12-17 14:26:48 +01:00
// 9. Append initialHistoryEntry to traversable's session history entries.
traversable - > m_session_history_entries . append ( * initial_history_entry ) ;
2025-02-27 00:22:12 +01:00
traversable - > set_has_session_history_entry_and_ready_for_navigation ( ) ;
2022-12-17 14:26:48 +01:00
// FIXME: 10. If opener is non-null, then legacy-clone a traversable storage shed given opener's top-level traversable and traversable. [STORAGE]
// 11. Append traversable to the user agent's top-level traversable set.
user_agent_top_level_traversable_set ( ) . set ( traversable ) ;
// 12. Return traversable.
return traversable ;
}
// https://html.spec.whatwg.org/multipage/document-sequences.html#create-a-fresh-top-level-traversable
2026-03-30 16:39:50 +02:00
GC : : Ref < TraversableNavigable > TraversableNavigable : : create_a_fresh_top_level_traversable ( GC : : Ref < Page > page , URL : : URL const & initial_navigation_url , Variant < Empty , String , POSTResource > initial_navigation_post_resource )
2022-12-17 14:26:48 +01:00
{
// 1. Let traversable be the result of creating a new top-level traversable given null and the empty string.
2026-03-30 16:39:50 +02:00
auto traversable = create_a_new_top_level_traversable ( page , nullptr , { } ) ;
2024-02-07 09:03:47 -07:00
page - > set_top_level_traversable ( traversable ) ;
2022-12-17 14:26:48 +01:00
2025-06-22 20:26:03 +02:00
// AD-HOC: Set the default top-level emulated position data for the traversable, which points to Market St. SF.
// FIXME: We should not emulate by default, but ask the user what to do. E.g. disable Geolocation, set an emulated
// position, or allow Ladybird to engage with the system's geolocation services. This is completely separate
// from the permission model for "powerful features" such as Geolocation.
auto & realm = traversable - > active_document ( ) - > realm ( ) ;
auto emulated_position_coordinates = realm . create < Geolocation : : GeolocationCoordinates > (
realm ,
Geolocation : : CoordinatesData {
. accuracy = 100.0 ,
. latitude = 37.7647658 ,
. longitude = - 122.4345892 ,
. altitude = 60.0 ,
. altitude_accuracy = 10.0 ,
. heading = 0.0 ,
. speed = 0.0 ,
} ) ;
traversable - > set_emulated_position_data ( emulated_position_coordinates ) ;
2024-02-07 09:03:47 -07:00
// AD-HOC: Mark the about:blank document as finished parsing if we're only going to about:blank
// Skip the initial navigation as well. This matches the behavior of the window open steps.
if ( url_matches_about_blank ( initial_navigation_url ) ) {
2024-11-15 04:01:23 +13:00
Platform : : EventLoopPlugin : : the ( ) . deferred_invoke ( GC : : create_function ( traversable - > heap ( ) , [ traversable , initial_navigation_url ] {
2024-02-07 09:03:47 -07:00
// FIXME: We do this other places too when creating a new about:blank document. Perhaps it's worth a spec issue?
HTML : : HTMLParser : : the_end ( * traversable - > active_document ( ) ) ;
// FIXME: If we perform the URL and history update steps here, we start hanging tests and the UI process will
// try to load() the initial URLs passed on the command line before we finish processing the events here.
// However, because we call this before the PageClient is fully initialized... that gets awkward.
2024-10-31 02:39:29 +13:00
} ) ) ;
2024-02-07 09:03:47 -07:00
}
else {
// 2. Navigate traversable to initialNavigationURL using traversable's active document, with documentResource set to initialNavigationPostResource.
2026-03-30 16:39:50 +02:00
MUST ( traversable - > navigate ( { . url = initial_navigation_url ,
2024-02-07 09:03:47 -07:00
. source_document = * traversable - > active_document ( ) ,
. document_resource = initial_navigation_post_resource } ) ) ;
}
2022-12-17 14:26:48 +01:00
// 3. Return traversable.
return traversable ;
}
2022-12-12 12:01:09 +01:00
// https://html.spec.whatwg.org/multipage/document-sequences.html#top-level-traversable
bool TraversableNavigable : : is_top_level_traversable ( ) const
{
// A top-level traversable is a traversable navigable with a null parent.
return parent ( ) = = nullptr ;
}
2023-04-13 10:11:31 +03:00
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#getting-all-used-history-steps
Vector < int > TraversableNavigable : : get_all_used_history_steps ( ) const
{
// FIXME: 1. Assert: this is running within traversable's session history traversal queue.
// 2. Let steps be an empty ordered set of non-negative integers.
OrderedHashTable < int > steps ;
// 3. Let entryLists be the ordered set « traversable's session history entries ».
2026-04-03 12:53:39 +02:00
Vector < Vector < NonnullRefPtr < SessionHistoryEntry > > > entry_lists { session_history_entries ( ) } ;
2023-04-13 10:11:31 +03:00
// 4. For each entryList of entryLists:
while ( ! entry_lists . is_empty ( ) ) {
auto entry_list = entry_lists . take_first ( ) ;
// 1. For each entry of entryList:
for ( auto & entry : entry_list ) {
// 1. Append entry's step to steps.
2024-03-27 15:59:12 +01:00
steps . set ( entry - > step ( ) . get < int > ( ) ) ;
2023-04-13 10:11:31 +03:00
// 2. For each nestedHistory of entry's document state's nested histories, append nestedHistory's entries list to entryLists.
2024-03-27 15:59:12 +01:00
for ( auto & nested_history : entry - > document_state ( ) - > nested_histories ( ) )
2023-04-13 10:11:31 +03:00
entry_lists . append ( nested_history . entries ) ;
}
}
// 5. Return steps, sorted.
auto sorted_steps = steps . values ( ) ;
quick_sort ( sorted_steps ) ;
return sorted_steps ;
}
2023-04-13 10:18:46 +03:00
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#getting-the-history-object-length-and-index
TraversableNavigable : : HistoryObjectLengthAndIndex TraversableNavigable : : get_the_history_object_length_and_index ( int step ) const
{
// 1. Let steps be the result of getting all used history steps within traversable.
auto steps = get_all_used_history_steps ( ) ;
// 2. Let scriptHistoryLength be the size of steps.
auto script_history_length = steps . size ( ) ;
// 3. Assert: steps contains step.
VERIFY ( steps . contains_slow ( step ) ) ;
// 4. Let scriptHistoryIndex be the index of step in steps.
auto script_history_index = * steps . find_first_index ( step ) ;
// 5. Return (scriptHistoryLength, scriptHistoryIndex).
return HistoryObjectLengthAndIndex {
. script_history_length = script_history_length ,
. script_history_index = script_history_index
} ;
}
2023-06-19 21:08:26 +03:00
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#getting-the-used-step
int TraversableNavigable : : get_the_used_step ( int step ) const
{
// 1. Let steps be the result of getting all used history steps within traversable.
auto steps = get_all_used_history_steps ( ) ;
// 2. Return the greatest item in steps that is less than or equal to step.
VERIFY ( ! steps . is_empty ( ) ) ;
Optional < int > result ;
for ( size_t i = 0 ; i < steps . size ( ) ; i + + ) {
if ( steps [ i ] < = step ) {
if ( ! result . has_value ( ) | | ( result . value ( ) < steps [ i ] ) ) {
result = steps [ i ] ;
}
}
}
return result . value ( ) ;
}
2023-06-19 21:11:30 +03:00
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#get-all-navigables-whose-current-session-history-entry-will-change-or-reload
2024-11-15 04:01:23 +13:00
Vector < GC : : Root < Navigable > > TraversableNavigable : : get_all_navigables_whose_current_session_history_entry_will_change_or_reload ( int target_step ) const
2023-06-19 21:11:30 +03:00
{
// 1. Let results be an empty list.
2024-11-15 04:01:23 +13:00
Vector < GC : : Root < Navigable > > results ;
2023-06-19 21:11:30 +03:00
// 2. Let navigablesToCheck be « traversable ».
2024-11-15 04:01:23 +13:00
Vector < GC : : Root < Navigable > > navigables_to_check ;
2023-06-19 21:11:30 +03:00
navigables_to_check . append ( const_cast < TraversableNavigable & > ( * this ) ) ;
// 3. For each navigable of navigablesToCheck:
while ( ! navigables_to_check . is_empty ( ) ) {
auto navigable = navigables_to_check . take_first ( ) ;
// 1. Let targetEntry be the result of getting the target history entry given navigable and targetStep.
auto target_entry = navigable - > get_the_target_history_entry ( target_step ) ;
2025-07-04 10:53:28 +05:30
// 2. If targetEntry is not navigable's current session history entry or targetEntry's document state's reload
// pending is true, then append navigable to results.
// AD-HOC: We don't want to choose a navigable that has ongoing traversal.
if ( ( target_entry ! = navigable - > current_session_history_entry ( ) | | target_entry - > document_state ( ) - > reload_pending ( ) ) & & ! navigable - > ongoing_navigation ( ) . has < Traversal > ( ) ) {
2023-06-19 21:11:30 +03:00
results . append ( * navigable ) ;
}
2025-07-04 10:53:28 +05:30
// 3. If targetEntry's document is navigable's document, and targetEntry's document state's reload pending is
// false, then extend navigablesToCheck with the child navigables of navigable.
2026-03-31 16:56:18 +02:00
if ( target_entry - > document_state ( ) - > document_id ( ) = = navigable - > active_document_id ( ) & & ! target_entry - > document_state ( ) - > reload_pending ( ) ) {
2023-06-19 21:11:30 +03:00
navigables_to_check . extend ( navigable - > child_navigables ( ) ) ;
}
}
// 4. Return results.
return results ;
}
2023-09-27 22:59:57 -06:00
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#getting-all-navigables-that-only-need-history-object-length/index-update
2024-11-15 04:01:23 +13:00
Vector < GC : : Root < Navigable > > TraversableNavigable : : get_all_navigables_that_only_need_history_object_length_index_update ( int target_step ) const
2023-09-27 22:59:57 -06:00
{
// NOTE: Other navigables might not be impacted by the traversal. For example, if the response is a 204, the currently active document will remain.
// Additionally, going 'back' after a 204 will change the current session history entry, but the active session history entry will already be correct.
// 1. Let results be an empty list.
2024-11-15 04:01:23 +13:00
Vector < GC : : Root < Navigable > > results ;
2023-09-27 22:59:57 -06:00
// 2. Let navigablesToCheck be « traversable ».
2024-11-15 04:01:23 +13:00
Vector < GC : : Root < Navigable > > navigables_to_check ;
2023-09-27 22:59:57 -06:00
navigables_to_check . append ( const_cast < TraversableNavigable & > ( * this ) ) ;
// 3. For each navigable of navigablesToCheck:
while ( ! navigables_to_check . is_empty ( ) ) {
auto navigable = navigables_to_check . take_first ( ) ;
// 1. Let targetEntry be the result of getting the target history entry given navigable and targetStep.
auto target_entry = navigable - > get_the_target_history_entry ( target_step ) ;
// 2. If targetEntry is navigable's current session history entry and targetEntry's document state's reload pending is false, then:
2024-03-27 15:59:12 +01:00
if ( target_entry = = navigable - > current_session_history_entry ( ) & & ! target_entry - > document_state ( ) - > reload_pending ( ) ) {
2023-09-27 22:59:57 -06:00
// 1. Append navigable to results.
results . append ( navigable ) ;
// 2. Extend navigablesToCheck with navigable's child navigables.
navigables_to_check . extend ( navigable - > child_navigables ( ) ) ;
}
}
// 4. Return results.
return results ;
}
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#getting-all-navigables-that-might-experience-a-cross-document-traversal
2024-11-15 04:01:23 +13:00
Vector < GC : : Root < Navigable > > TraversableNavigable : : get_all_navigables_that_might_experience_a_cross_document_traversal ( int target_step ) const
2023-09-27 22:59:57 -06:00
{
// NOTE: From traversable's session history traversal queue's perspective, these documents are candidates for going cross-document during the
// traversal described by targetStep. They will not experience a cross-document traversal if the status code for their target document is
// HTTP 204 No Content.
// Note that if a given navigable might experience a cross-document traversal, this algorithm will return navigable but not its child navigables.
// Those would end up unloaded, not traversed.
// 1. Let results be an empty list.
2024-11-15 04:01:23 +13:00
Vector < GC : : Root < Navigable > > results ;
2023-09-27 22:59:57 -06:00
// 2. Let navigablesToCheck be « traversable ».
2024-11-15 04:01:23 +13:00
Vector < GC : : Root < Navigable > > navigables_to_check ;
2023-09-27 22:59:57 -06:00
navigables_to_check . append ( const_cast < TraversableNavigable & > ( * this ) ) ;
// 3. For each navigable of navigablesToCheck:
while ( ! navigables_to_check . is_empty ( ) ) {
auto navigable = navigables_to_check . take_first ( ) ;
// 1. Let targetEntry be the result of getting the target history entry given navigable and targetStep.
auto target_entry = navigable - > get_the_target_history_entry ( target_step ) ;
// 2. If targetEntry's document is not navigable's document or targetEntry's document state's reload pending is true, then append navigable to results.
// NOTE: Although navigable's active history entry can change synchronously, the new entry will always have the same Document,
// so accessing navigable's document is reliable.
2026-03-31 16:56:18 +02:00
if ( target_entry - > document_state ( ) - > document_id ( ) ! = navigable - > active_document_id ( ) | | target_entry - > document_state ( ) - > reload_pending ( ) ) {
2023-09-27 22:59:57 -06:00
results . append ( navigable ) ;
}
// 3. Otherwise, extend navigablesToCheck with navigable's child navigables.
// Adding child navigables to navigablesToCheck means those navigables will also be checked by this loop.
// Child navigables are only checked if the navigable's active document will not change as part of this traversal.
else {
navigables_to_check . extend ( navigable - > child_navigables ( ) ) ;
}
}
// 4. Return results.
return results ;
}
2024-03-31 23:42:51 +02:00
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#deactivate-a-document-for-a-cross-document-navigation
2026-04-03 12:53:39 +02:00
static void deactivate_a_document_for_cross_document_navigation ( GC : : Ref < DOM : : Document > displayed_document , Optional < UserNavigationInvolvement > , NonnullRefPtr < SessionHistoryEntry > target_entry , GC : : Ptr < DOM : : Document > populated_document , GC : : Ref < GC : : Function < void ( ) > > after_potential_unloads )
2024-03-31 23:42:51 +02:00
{
// 1. Let navigable be displayedDocument's node navigable.
auto navigable = displayed_document - > navigable ( ) ;
// 2. Let potentiallyTriggerViewTransition be false.
auto potentially_trigger_view_transition = false ;
// FIXME: 3. Let isBrowserUINavigation be true if userNavigationInvolvement is "browser UI"; otherwise false.
// FIXME: 4. Set potentiallyTriggerViewTransition to the result of calling can navigation trigger a cross-document
// view-transition? given displayedDocument, targetEntry's document, navigationType, and isBrowserUINavigation.
// 5. If potentiallyTriggerViewTransition is false, then:
if ( ! potentially_trigger_view_transition ) {
2025-11-13 14:05:19 +01:00
// FIXME: 1. Let firePageSwapBeforeUnload be the following step
2024-03-31 23:42:51 +02:00
// 1. Fire the pageswap event given displayedDocument, targetEntry, navigationType, and null.
// 2. Set the ongoing navigation for navigable to null.
navigable - > set_ongoing_navigation ( { } ) ;
// 3. Unload a document and its descendants given displayedDocument, targetEntry's document, afterPotentialUnloads, and firePageSwapBeforeUnload.
2026-03-26 22:48:49 +01:00
( void ) target_entry ; // FIXME: Used by pageswap and view-transition steps above.
displayed_document - > unload_a_document_and_its_descendants ( populated_document , after_potential_unloads ) ;
2024-03-31 23:42:51 +02:00
}
// FIXME: 6. Otherwise, queue a global task on the navigation and traversal task source given navigable's active window to run the steps:
else {
// FIXME: 1. Let proceedWithNavigationAfterViewTransitionCapture be the following step:
// 1. Append the following session history traversal steps to navigable's traversable navigable:
// 1. Set the ongoing navigation for navigable to null.
// 2. Unload a document and its descendants given displayedDocument, targetEntry's document, and afterPotentialUnloads.
// FIXME: 2. Let viewTransition be the result of setting up a cross-document view-transition given displayedDocument,
// targetEntry's document, navigationType, and proceedWithNavigationAfterViewTransitionCapture.
// FIXME: 3. Fire the pageswap event given displayedDocument, targetEntry, navigationType, and viewTransition.
// FIXME: 4. If viewTransition is null, then run proceedWithNavigationAfterViewTransitionCapture.
TODO ( ) ;
}
}
2024-04-21 15:30:58 +02:00
struct ChangingNavigableContinuationState : public JS : : Cell {
2024-11-15 04:01:23 +13:00
GC_CELL ( ChangingNavigableContinuationState , JS : : Cell ) ;
GC_DECLARE_ALLOCATOR ( ChangingNavigableContinuationState ) ;
2024-04-21 15:30:58 +02:00
2024-11-15 04:01:23 +13:00
GC : : Ptr < DOM : : Document > displayed_document ;
2026-03-31 16:56:18 +02:00
Optional < UniqueNodeID > displayed_document_id ;
2026-04-03 12:53:39 +02:00
RefPtr < SessionHistoryEntry > target_entry ;
2024-11-15 04:01:23 +13:00
GC : : Ptr < Navigable > navigable ;
2024-04-21 15:30:58 +02:00
bool update_only = false ;
2026-03-26 22:48:49 +01:00
GC : : Ptr < PopulateSessionHistoryEntryDocumentOutput > population_output ;
2026-03-31 16:56:18 +02:00
GC : : Ptr < DOM : : Document > resolved_document ;
2026-03-26 22:48:49 +01:00
Optional < URL : : Origin > old_origin ;
2024-04-21 15:30:58 +02:00
virtual void visit_edges ( Cell : : Visitor & visitor ) override
{
Base : : visit_edges ( visitor ) ;
visitor . visit ( displayed_document ) ;
visitor . visit ( navigable ) ;
2026-03-26 22:48:49 +01:00
visitor . visit ( population_output ) ;
2026-03-31 16:56:18 +02:00
visitor . visit ( resolved_document ) ;
2024-04-21 15:30:58 +02:00
}
} ;
2024-11-15 04:01:23 +13:00
GC_DEFINE_ALLOCATOR ( ChangingNavigableContinuationState ) ;
2024-04-21 15:30:58 +02:00
2026-03-29 12:06:39 +02:00
class ApplyHistoryStepState : public GC : : Cell {
GC_CELL ( ApplyHistoryStepState , GC : : Cell ) ;
GC_DECLARE_ALLOCATOR ( ApplyHistoryStepState ) ;
2023-04-13 10:23:59 +03:00
2026-03-29 12:06:39 +02:00
public :
static constexpr int TIMEOUT_MS = 15000 ;
2023-04-13 10:23:59 +03:00
2026-03-29 12:06:39 +02:00
ApplyHistoryStepState (
GC : : Ref < TraversableNavigable > traversable , int step , int target_step ,
GC : : Ptr < SourceSnapshotParams > source_snapshot_params ,
UserNavigationInvolvement user_involvement ,
Optional < Bindings : : NavigationType > navigation_type ,
TraversableNavigable : : SynchronousNavigation synchronous_navigation ,
2026-03-31 16:56:18 +02:00
GC : : Ptr < DOM : : Document > pending_document ,
2026-03-29 12:06:39 +02:00
GC : : Ref < OnApplyHistoryStepComplete > on_complete )
: m_traversable ( traversable )
, m_step ( step )
, m_target_step ( target_step )
, m_source_snapshot_params ( source_snapshot_params )
, m_user_involvement ( user_involvement )
, m_navigation_type ( navigation_type )
, m_synchronous_navigation ( synchronous_navigation )
2026-03-31 16:56:18 +02:00
, m_pending_document ( pending_document )
2026-03-29 12:06:39 +02:00
, m_on_complete ( on_complete )
, m_timeout ( Platform : : Timer : : create_single_shot ( heap ( ) , TIMEOUT_MS , GC : : create_function ( heap ( ) , [ this ] {
if ( m_phase ! = Phase : : Completed ) {
dbgln ( " FIXME: ApplyHistoryStepState timed out in phase {} step={} changing={}/{} completed={}/{} cont={}/{} non_changing={}/{} url={} " ,
to_underlying ( m_phase ) , m_step ,
m_changing_navigables . size ( ) , m_changing_navigables . size ( ) ,
m_completed_change_jobs , m_changing_navigables . size ( ) ,
m_continuation_index , m_continuations . size ( ) ,
m_completed_non_changing_jobs , m_non_changing_navigables . size ( ) ,
m_traversable - > active_document ( ) ? m_traversable - > active_document ( ) - > url ( ) : URL : : URL { } ) ;
}
} ) ) )
{
m_timeout - > start ( ) ;
}
2023-04-13 10:23:59 +03:00
2026-03-29 12:06:39 +02:00
void start ( ) ;
2023-04-13 10:23:59 +03:00
2026-03-29 12:06:39 +02:00
void did_receive_continuation ( GC : : Ref < ChangingNavigableContinuationState > continuation )
{
m_continuations . append ( continuation ) ;
signal_progress ( ) ;
}
void signal_progress ( )
{
switch ( m_phase ) {
case Phase : : WaitingForDocumentPopulation :
// Population progress is tracked by m_continuations.size() + m_completed_change_jobs.
// The caller either appended a continuation or incremented m_completed_change_jobs before calling.
break ;
case Phase : : ProcessingContinuations :
case Phase : : WaitingForChangeJobCompletion :
+ + m_completed_change_jobs ;
break ;
case Phase : : WaitingForNonChangingJobs :
+ + m_completed_non_changing_jobs ;
break ;
case Phase : : Completed :
return ;
2023-09-27 22:59:57 -06:00
}
2026-03-29 12:06:39 +02:00
try_advance ( ) ;
2023-09-27 22:59:57 -06:00
}
2026-03-29 12:06:39 +02:00
enum class Phase {
WaitingForDocumentPopulation ,
ProcessingContinuations ,
WaitingForChangeJobCompletion ,
WaitingForNonChangingJobs ,
Completed ,
} ;
2024-10-02 23:56:26 +01:00
2026-03-29 12:06:39 +02:00
private :
void visit_edges ( Cell : : Visitor & visitor ) override
{
Base : : visit_edges ( visitor ) ;
visitor . visit ( m_traversable ) ;
visitor . visit ( m_source_snapshot_params ) ;
2026-03-31 16:56:18 +02:00
visitor . visit ( m_pending_document ) ;
2026-03-29 12:06:39 +02:00
visitor . visit ( m_on_complete ) ;
visitor . visit ( m_timeout ) ;
visitor . visit ( m_changing_navigables ) ;
visitor . visit ( m_non_changing_navigables ) ;
visitor . visit ( m_continuations ) ;
for ( auto & navigable : m_navigables_that_must_wait_before_handling_sync_navigation )
visitor . visit ( navigable ) ;
2024-10-02 23:56:26 +01:00
}
2023-04-13 10:23:59 +03:00
2026-03-29 12:06:39 +02:00
void try_advance ( )
{
switch ( m_phase ) {
case Phase : : WaitingForDocumentPopulation :
if ( m_continuations . size ( ) + m_completed_change_jobs = = m_changing_navigables . size ( ) ) {
m_phase = Phase : : ProcessingContinuations ;
process_continuations ( ) ;
}
break ;
case Phase : : ProcessingContinuations :
case Phase : : WaitingForChangeJobCompletion :
if ( m_completed_change_jobs = = m_changing_navigables . size ( ) & & m_continuation_index > = m_continuations . size ( ) ) {
m_phase = Phase : : WaitingForNonChangingJobs ;
enter_waiting_for_non_changing_jobs ( ) ;
}
break ;
case Phase : : WaitingForNonChangingJobs :
if ( m_completed_non_changing_jobs = = m_non_changing_navigables . size ( ) )
complete ( ) ;
break ;
case Phase : : Completed :
break ;
}
}
2026-03-28 00:08:48 +01:00
2026-03-29 12:06:39 +02:00
void process_continuations ( ) ;
void enter_waiting_for_non_changing_jobs ( ) ;
void complete ( ) ;
Phase m_phase { Phase : : WaitingForDocumentPopulation } ;
GC : : Ref < TraversableNavigable > m_traversable ;
int m_step ;
int m_target_step ;
GC : : Ptr < SourceSnapshotParams > m_source_snapshot_params ;
UserNavigationInvolvement m_user_involvement ;
Optional < Bindings : : NavigationType > m_navigation_type ;
TraversableNavigable : : SynchronousNavigation m_synchronous_navigation ;
2026-03-31 16:56:18 +02:00
GC : : Ptr < DOM : : Document > m_pending_document ;
2026-03-29 12:06:39 +02:00
GC : : Ptr < OnApplyHistoryStepComplete > m_on_complete ;
GC : : Ref < Platform : : Timer > m_timeout ;
Vector < GC : : Ref < Navigable > > m_changing_navigables ;
Vector < GC : : Ref < Navigable > > m_non_changing_navigables ;
size_t m_completed_change_jobs { 0 } ;
Vector < GC : : Ref < ChangingNavigableContinuationState > > m_continuations ;
size_t m_continuation_index { 0 } ;
RefPtr < Core : : Promise < Empty > > m_pending_sync_nav_promise ;
HashTable < GC : : Ref < Navigable > > m_navigables_that_must_wait_before_handling_sync_navigation ;
size_t m_completed_non_changing_jobs { 0 } ;
} ;
GC_DEFINE_ALLOCATOR ( ApplyHistoryStepState ) ;
2023-04-13 10:23:59 +03:00
2026-03-29 12:06:39 +02:00
void ApplyHistoryStepState : : start ( )
{
2023-09-27 22:59:57 -06:00
// 7. Let nonchangingNavigablesThatStillNeedUpdates be the result of getting all navigables that only need history object length/index update given traversable and targetStep.
2026-03-29 12:06:39 +02:00
auto non_changing_navigables = m_traversable - > get_all_navigables_that_only_need_history_object_length_index_update ( m_target_step ) ;
for ( auto & nav : non_changing_navigables )
m_non_changing_navigables . append ( * nav ) ;
2023-04-13 10:23:59 +03:00
// 8. For each navigable of changingNavigables:
2026-03-29 12:06:39 +02:00
auto changing_navigables = m_traversable - > get_all_navigables_whose_current_session_history_entry_will_change_or_reload ( m_target_step ) ;
2023-04-13 10:23:59 +03:00
for ( auto & navigable : changing_navigables ) {
// 1. Let targetEntry be the result of getting the target history entry given navigable and targetStep.
2026-03-29 12:06:39 +02:00
auto target_entry = navigable - > get_the_target_history_entry ( m_target_step ) ;
2023-04-13 10:23:59 +03:00
// 2. Set navigable's current session history entry to targetEntry.
navigable - > set_current_session_history_entry ( target_entry ) ;
// 3. Set navigable's ongoing navigation to "traversal".
2026-03-29 12:06:39 +02:00
navigable - > set_ongoing_navigation ( HTML : : Navigable : : Traversal : : Tag ) ;
2023-04-13 10:23:59 +03:00
2026-03-29 12:06:39 +02:00
m_changing_navigables . append ( * navigable ) ;
}
2023-04-13 10:23:59 +03:00
2026-03-29 12:06:39 +02:00
// 12. For each navigable of changingNavigables, queue a global task on the navigation and traversal task source.
for ( auto & navigable : m_changing_navigables ) {
2026-02-10 18:36:29 +01:00
// AD-HOC: If the navigable has been destroyed, or has no active window, skip it.
// We must increment completed_change_jobs here rather than relying on the queued
// task, because Document::destroy() removes tasks associated with a document from
// the task queue, which can cause those tasks to never run.
if ( navigable - > has_been_destroyed ( ) | | ! navigable - > active_window ( ) ) {
2026-03-29 12:06:39 +02:00
+ + m_completed_change_jobs ;
signal_progress ( ) ;
2024-10-05 15:16:48 +02:00
continue ;
2026-02-10 18:36:29 +01:00
}
2026-03-29 12:06:39 +02:00
queue_global_task ( Task : : Source : : NavigationAndTraversal , * navigable - > active_window ( ) , GC : : create_function ( heap ( ) , [ this , navigable ] {
2023-09-05 23:36:20 +02:00
// NOTE: This check is not in the spec but we should not continue navigation if navigable has been destroyed.
2024-04-05 02:46:32 +02:00
if ( navigable - > has_been_destroyed ( ) ) {
2026-03-29 12:06:39 +02:00
+ + m_completed_change_jobs ;
signal_progress ( ) ;
2023-09-05 23:36:20 +02:00
return ;
2024-04-05 02:46:32 +02:00
}
2023-09-05 23:36:20 +02:00
2023-04-13 10:23:59 +03:00
// 1. Let displayedEntry be navigable's active session history entry.
auto displayed_entry = navigable - > active_session_history_entry ( ) ;
// 2. Let targetEntry be navigable's current session history entry.
auto target_entry = navigable - > current_session_history_entry ( ) ;
// 3. Let changingNavigableContinuation be a changing navigable continuation state with:
2026-03-29 12:06:39 +02:00
auto changing_navigable_continuation = heap ( ) . allocate < ChangingNavigableContinuationState > ( ) ;
2026-03-31 16:56:18 +02:00
changing_navigable_continuation - > displayed_document = navigable - > active_document ( ) ;
changing_navigable_continuation - > displayed_document_id = navigable - > active_document_id ( ) ;
2024-04-21 15:30:58 +02:00
changing_navigable_continuation - > target_entry = target_entry ;
changing_navigable_continuation - > navigable = navigable ;
changing_navigable_continuation - > update_only = false ;
2026-03-26 22:48:49 +01:00
changing_navigable_continuation - > population_output = nullptr ;
2023-04-13 10:23:59 +03:00
// 4. If displayedEntry is targetEntry and targetEntry's document state's reload pending is false, then:
2026-03-29 12:06:39 +02:00
if ( m_synchronous_navigation = = TraversableNavigable : : SynchronousNavigation : : Yes & & ! target_entry - > document_state ( ) - > reload_pending ( ) ) {
2023-04-13 10:23:59 +03:00
// 1. Set changingNavigableContinuation's update-only to true.
2024-04-21 15:30:58 +02:00
changing_navigable_continuation - > update_only = true ;
2026-03-31 16:56:18 +02:00
changing_navigable_continuation - > resolved_document = navigable - > active_document ( ) ;
2023-04-13 10:23:59 +03:00
// 2. Enqueue changingNavigableContinuation on changingNavigableContinuations.
2026-03-29 12:06:39 +02:00
did_receive_continuation ( changing_navigable_continuation ) ;
2023-04-13 10:23:59 +03:00
// 3. Abort these steps.
return ;
}
2024-03-28 12:19:57 +01:00
// 5. Switch on navigationType:
2026-03-29 12:06:39 +02:00
if ( m_navigation_type . has_value ( ) ) {
switch ( m_navigation_type . value ( ) ) {
2024-03-28 12:19:57 +01:00
case Bindings : : NavigationType : : Reload :
// - "reload": Assert: targetEntry's document state's reload pending is true.
VERIFY ( target_entry - > document_state ( ) - > reload_pending ( ) ) ;
break ;
case Bindings : : NavigationType : : Traverse :
// - "traverse": Assert: targetEntry's document state's ever populated is true.
VERIFY ( target_entry - > document_state ( ) - > ever_populated ( ) ) ;
break ;
case Bindings : : NavigationType : : Replace :
// FIXME: Add ever populated check
// - "replace": Assert: targetEntry's step is displayedEntry's step and targetEntry's document state's ever populated is false.
VERIFY ( target_entry - > step ( ) = = displayed_entry - > step ( ) ) ;
break ;
case Bindings : : NavigationType : : Push :
// FIXME: Add ever populated check, and fix the bug where top level traversable's step is not updated when a child navigable navigates
// - "push": Assert: targetEntry's step is displayedEntry's step + 1 and targetEntry's document state's ever populated is false.
2025-02-10 15:20:01 +13:00
VERIFY ( target_entry ! = displayed_entry ) ;
2024-03-28 12:19:57 +01:00
VERIFY ( target_entry - > step ( ) . get < int > ( ) > displayed_entry - > step ( ) . get < int > ( ) ) ;
break ;
}
}
// 6. Let oldOrigin be targetEntry's document state's origin.
2024-03-27 15:59:12 +01:00
auto old_origin = target_entry - > document_state ( ) - > origin ( ) ;
2023-04-13 10:23:59 +03:00
2024-10-13 15:03:40 +13:00
// 7. If all of the following are true:
// * navigable is not traversable;
// * targetEntry is not navigable's current session history entry; and
// * oldOrigin is the same as navigable's current session history entry's document state's origin,
// then:
if ( ! navigable - > is_traversable ( )
& & target_entry ! = navigable - > current_session_history_entry ( )
& & old_origin = = navigable - > current_session_history_entry ( ) - > document_state ( ) - > origin ( ) ) {
2025-01-07 14:58:48 +00:00
// 1. Let navigation be navigable's active window's navigation API.
2026-03-29 12:06:39 +02:00
auto navigation = m_traversable - > active_window ( ) - > navigation ( ) ;
2024-10-13 15:03:40 +13:00
2025-01-07 14:58:48 +00:00
// 2. Fire a traverse navigate event at navigation given targetEntry and userInvolvement.
2026-03-29 12:06:39 +02:00
navigation - > fire_a_traverse_navigate_event ( * target_entry , m_user_involvement ) ;
2024-10-13 15:03:40 +13:00
}
2024-03-28 12:19:57 +01:00
2026-03-31 16:56:18 +02:00
auto after_document_populated = GC : : create_function ( heap ( ) , [ this , old_origin , changing_navigable_continuation , target_entry , navigable ] ( GC : : Ptr < PopulateSessionHistoryEntryDocumentOutput > output ) mutable {
2026-03-26 22:48:49 +01:00
changing_navigable_continuation - > population_output = output ;
changing_navigable_continuation - > old_origin = old_origin ;
2024-04-12 20:13:54 +02:00
2026-03-31 16:56:18 +02:00
// Compute the resolved document: pending_document (from finalize path),
// population output (from traversal path), or active document (same-document).
GC : : Ptr < DOM : : Document > resolved_document ;
if ( m_pending_document )
resolved_document = m_pending_document ;
else if ( output & & output - > document )
resolved_document = output - > document ;
else
resolved_document = navigable - > active_document ( ) ;
changing_navigable_continuation - > resolved_document = resolved_document ;
2023-04-13 10:23:59 +03:00
// 1. If targetEntry's document is null, then set changingNavigableContinuation's update-only to true.
2026-03-31 16:56:18 +02:00
bool has_fresh_document = m_pending_document | | ( output & & output - > document ) ;
if ( ! has_fresh_document & & target_entry - > document_state ( ) - > document_id ( ) ! = navigable - > active_document_id ( ) )
2024-04-21 15:30:58 +02:00
changing_navigable_continuation - > update_only = true ;
2023-04-13 10:23:59 +03:00
2026-03-26 22:48:49 +01:00
// 2. If targetEntry's document's origin is not oldOrigin, then set targetEntry's classic history API state to StructuredSerializeForStorage(null).
// 3. If all of the following are true:
// - navigable's parent is null;
// - targetEntry's document's browsing context is not an auxiliary browsing context whose opener browsing context is non-null; and
// - targetEntry's document's origin is not oldOrigin,
// then set targetEntry's document state's navigable target name to the empty string.
// NOTE: Steps 2-3 are deferred to after_potential_unload to avoid exposing mutations during unload.
2023-04-13 10:23:59 +03:00
// 4. Enqueue changingNavigableContinuation on changingNavigableContinuations.
2026-03-29 12:06:39 +02:00
did_receive_continuation ( changing_navigable_continuation ) ;
} ) ;
2023-04-13 10:23:59 +03:00
2024-03-28 12:19:57 +01:00
// 8. If targetEntry's document is null, or targetEntry's document state's reload pending is true, then:
2026-03-31 16:56:18 +02:00
bool needs_population = ! m_pending_document
& & ( target_entry - > document_state ( ) - > document_id ( ) ! = navigable - > active_document_id ( )
| | target_entry - > document_state ( ) - > reload_pending ( ) ) ;
if ( needs_population ) {
2023-04-13 10:23:59 +03:00
// FIXME: 1. Let navTimingType be "back_forward" if targetEntry's document is null; otherwise "reload".
2023-09-21 13:47:19 -06:00
// 2. Let targetSnapshotParams be the result of snapshotting target snapshot params given navigable.
auto target_snapshot_params = navigable - > snapshot_target_snapshot_params ( ) ;
2023-04-13 10:23:59 +03:00
// 3. Let potentiallyTargetSpecificSourceSnapshotParams be sourceSnapshotParams.
2026-03-29 12:06:39 +02:00
auto potentially_target_specific_source_snapshot_params = m_source_snapshot_params ;
2023-04-13 10:23:59 +03:00
2023-08-25 20:34:50 -06:00
// 4. If potentiallyTargetSpecificSourceSnapshotParams is null, then set it to the result of snapshotting source snapshot params given navigable's active document.
2026-03-29 12:06:39 +02:00
if ( ! potentially_target_specific_source_snapshot_params )
2023-08-25 20:34:50 -06:00
potentially_target_specific_source_snapshot_params = navigable - > active_document ( ) - > snapshot_source_snapshot_params ( ) ;
2023-04-13 10:23:59 +03:00
// 5. Set targetEntry's document state's reload pending to false.
2024-03-27 15:59:12 +01:00
target_entry - > document_state ( ) - > set_reload_pending ( false ) ;
2023-04-13 10:23:59 +03:00
2023-08-14 15:07:57 +02:00
// 6. Let allowPOST be targetEntry's document state's reload pending.
2024-03-27 15:59:12 +01:00
auto allow_POST = target_entry - > document_state ( ) - > reload_pending ( ) ;
2023-04-13 10:23:59 +03:00
2024-04-12 20:13:54 +02:00
// https://github.com/whatwg/html/issues/9869
2026-03-26 22:48:49 +01:00
// Population runs in a deferred task, during which sync navigations can mutate
// the live entry. Snapshot the input fields now so population reads stable values.
auto input_url = target_entry - > url ( ) ;
auto input_document_resource = target_entry - > document_state ( ) - > resource ( ) ;
auto input_request_referrer = target_entry - > document_state ( ) - > request_referrer ( ) ;
auto input_request_referrer_policy = target_entry - > document_state ( ) - > request_referrer_policy ( ) ;
auto input_initiator_origin = target_entry - > document_state ( ) - > initiator_origin ( ) ;
auto input_origin = target_entry - > document_state ( ) - > origin ( ) ;
auto input_history_policy_container = target_entry - > document_state ( ) - > history_policy_container ( ) ;
auto input_about_base_url = target_entry - > document_state ( ) - > about_base_url ( ) ;
auto input_navigable_target_name = target_entry - > document_state ( ) - > navigable_target_name ( ) ;
auto input_ever_populated = target_entry - > document_state ( ) - > ever_populated ( ) ;
2024-04-12 20:13:54 +02:00
2023-04-13 10:23:59 +03:00
// 7. In parallel, attempt to populate the history entry's document for targetEntry, given navigable, potentiallyTargetSpecificSourceSnapshotParams,
2025-01-07 14:58:48 +00:00
// targetSnapshotParams, userInvolvement, with allowPOST set to allowPOST and completionSteps set to
// queue a global task on the navigation and traversal task source given navigable's active window to
// run afterDocumentPopulated.
2026-03-29 12:06:39 +02:00
Platform : : EventLoopPlugin : : the ( ) . deferred_invoke ( GC : : create_function ( heap ( ) , [ input_url = move ( input_url ) , input_document_resource = move ( input_document_resource ) , input_request_referrer = move ( input_request_referrer ) , input_request_referrer_policy , input_initiator_origin = move ( input_initiator_origin ) , input_origin = move ( input_origin ) , input_history_policy_container = move ( input_history_policy_container ) , input_about_base_url = move ( input_about_base_url ) , input_navigable_target_name = move ( input_navigable_target_name ) , input_ever_populated , potentially_target_specific_source_snapshot_params , target_snapshot_params , this , allow_POST , navigable , after_document_populated , user_involvement = m_user_involvement ] {
2025-07-04 10:53:28 +05:30
navigable - > populate_session_history_entry_document (
2026-03-29 12:06:39 +02:00
move ( input_url ) , move ( input_document_resource ) , move ( input_request_referrer ) ,
input_request_referrer_policy , move ( input_initiator_origin ) , move ( input_origin ) ,
input_history_policy_container , move ( input_about_base_url ) , move ( input_navigable_target_name ) ,
false , input_ever_populated ,
* potentially_target_specific_source_snapshot_params , target_snapshot_params ,
2026-04-01 05:27:22 +02:00
user_involvement , { } , Navigable : : NullOrError { } ,
2026-03-29 12:06:39 +02:00
ContentSecurityPolicy : : Directives : : Directive : : NavigationType : : Other , allow_POST ,
2026-03-26 22:48:49 +01:00
GC : : create_function ( this - > heap ( ) , [ this , after_document_populated ] ( GC : : Ptr < PopulateSessionHistoryEntryDocumentOutput > output ) {
2026-03-29 12:06:39 +02:00
VERIFY ( m_traversable - > active_window ( ) ) ;
queue_global_task ( Task : : Source : : NavigationAndTraversal , * m_traversable - > active_window ( ) , GC : : create_function ( heap ( ) , [ after_document_populated , output ] ( ) {
2026-03-26 22:48:49 +01:00
after_document_populated - > function ( ) ( output ) ;
2025-07-04 10:53:28 +05:30
} ) ) ;
2025-09-30 17:15:55 +01:00
} ) ) ;
2024-10-31 02:39:29 +13:00
} ) ) ;
2023-04-13 10:23:59 +03:00
}
// Otherwise, run afterDocumentPopulated immediately.
else {
2026-03-29 12:06:39 +02:00
after_document_populated - > function ( ) ( nullptr ) ;
2023-04-13 10:23:59 +03:00
}
2024-04-16 22:04:01 +02:00
} ) ) ;
2023-04-13 10:23:59 +03:00
}
2026-03-29 12:06:39 +02:00
try_advance ( ) ;
}
2023-04-13 10:23:59 +03:00
2026-03-29 12:06:39 +02:00
void ApplyHistoryStepState : : process_continuations ( )
{
for ( ; ; ) {
2023-11-03 19:18:18 -06:00
// NOTE: Synchronous navigations that are intended to take place before this traversal jump the queue at this point,
// so they can be added to the correct place in traversable's session history entries before this traversal
// potentially unloads their document. More details can be found here (https://html.spec.whatwg.org/multipage/browsing-the-web.html#sync-navigation-steps-queue-jumping-examples)
// 1. If traversable's running nested apply history step is false, then:
2026-03-29 12:06:39 +02:00
if ( ! m_traversable - > m_paused_apply_history_step_state ) {
2023-11-03 19:18:18 -06:00
// 1. While traversable's session history traversal queue's algorithm set contains one or more synchronous
// navigation steps with a target navigable not contained in navigablesThatMustWaitBeforeHandlingSyncNavigation:
// 1. Let steps be the first item in traversable's session history traversal queue's algorithm set
// that is synchronous navigation steps with a target navigable not contained in navigablesThatMustWaitBeforeHandlingSyncNavigation.
// 2. Remove steps from traversable's session history traversal queue's algorithm set.
2026-03-29 12:06:39 +02:00
while ( true ) {
auto entry = m_traversable - > m_session_history_traversal_queue - > first_synchronous_navigation_steps_with_target_navigable_not_contained_in ( m_navigables_that_must_wait_before_handling_sync_navigation ) ;
if ( ! entry )
break ;
2023-11-03 19:18:18 -06:00
2026-03-29 12:06:39 +02:00
VERIFY ( ! m_traversable - > m_paused_apply_history_step_state ) ;
m_traversable - > m_paused_apply_history_step_state = this ;
2023-11-03 19:18:18 -06:00
// 4. Run steps.
2026-03-29 12:06:39 +02:00
auto promise = Core : : Promise < Empty > : : construct ( ) ;
entry - > execute_steps ( promise ) ;
// GC safety: `this` is kept alive by m_paused_apply_history_step_state (visited).
// The promise is kept alive by m_pending_sync_nav_promise (RefPtr).
VERIFY ( ! m_pending_sync_nav_promise ) ;
m_pending_sync_nav_promise = promise ;
promise - > when_resolved ( [ this ] ( Empty ) {
// 5. Set traversable's running nested apply history step to false.
VERIFY ( m_pending_sync_nav_promise ) ;
m_pending_sync_nav_promise = nullptr ;
m_traversable - > m_apply_history_step_state = this ;
m_traversable - > m_paused_apply_history_step_state = nullptr ;
process_continuations ( ) ;
} ) ;
return ;
2023-11-03 19:18:18 -06:00
}
}
2023-04-13 10:23:59 +03:00
2026-03-29 12:06:39 +02:00
if ( m_continuation_index = = m_continuations . size ( ) ) {
if ( m_phase = = Phase : : ProcessingContinuations ) {
m_phase = Phase : : WaitingForChangeJobCompletion ;
try_advance ( ) ;
}
return ;
}
2023-04-13 10:23:59 +03:00
// 3. If changingNavigableContinuation is nothing, then continue.
2026-03-29 12:06:39 +02:00
auto continuation = m_continuations [ m_continuation_index + + ] ;
2023-04-13 10:23:59 +03:00
// 4. Let displayedDocument be changingNavigableContinuation's displayed document.
2026-03-29 12:06:39 +02:00
auto displayed_document = continuation - > displayed_document ;
2023-04-13 10:23:59 +03:00
// 5. Let targetEntry be changingNavigableContinuation's target entry.
2026-03-29 12:06:39 +02:00
auto population_output = continuation - > population_output ;
auto old_origin = continuation - > old_origin ;
2023-04-13 10:23:59 +03:00
// 6. Let navigable be changingNavigableContinuation's navigable.
2026-03-29 12:06:39 +02:00
auto navigable = continuation - > navigable ;
2023-04-13 10:23:59 +03:00
2026-03-26 17:06:55 +01:00
// AD-HOC: We should not continue navigation if navigable has been destroyed.
2026-03-29 12:06:39 +02:00
if ( navigable - > has_been_destroyed ( ) ) {
signal_progress ( ) ;
2023-09-05 23:36:20 +02:00
continue ;
2026-03-29 12:06:39 +02:00
}
2026-03-26 17:06:55 +01:00
// AD-HOC: The displayed document may have been destroyed during the nested step execution above.
2026-03-29 12:06:39 +02:00
if ( ! displayed_document - > navigable ( ) ) {
signal_progress ( ) ;
2026-03-26 17:06:55 +01:00
continue ;
2026-03-29 12:06:39 +02:00
}
2026-03-26 17:06:55 +01:00
2026-04-03 16:06:16 +02:00
VERIFY ( m_target_step = = m_traversable - > get_the_used_step ( m_step ) ) ;
2024-10-14 07:45:19 +02:00
2024-03-31 23:42:51 +02:00
// 7. Let (scriptHistoryLength, scriptHistoryIndex) be the result of getting the history object length and index given traversable and targetStep.
2026-03-29 12:06:39 +02:00
auto history_object_length_and_index = m_traversable - > get_the_history_object_length_and_index ( m_target_step ) ;
2023-09-23 22:59:27 +02:00
auto script_history_length = history_object_length_and_index . script_history_length ;
auto script_history_index = history_object_length_and_index . script_history_index ;
2023-04-13 10:23:59 +03:00
2024-03-31 23:42:51 +02:00
// 8. Append navigable to navigablesThatMustWaitBeforeHandlingSyncNavigation.
2026-03-29 12:06:39 +02:00
m_navigables_that_must_wait_before_handling_sync_navigation . set ( * navigable ) ;
2023-04-13 10:23:59 +03:00
2024-03-31 23:42:51 +02:00
// 9. Let entriesForNavigationAPI be the result of getting session history entries for the navigation API given navigable and targetStep.
2026-03-29 12:06:39 +02:00
auto entries_for_navigation_api = m_traversable - > get_session_history_entries_for_the_navigation_api ( * navigable , m_target_step ) ;
2023-09-27 22:59:57 -06:00
2024-11-08 11:06:33 +00:00
// NOTE: Steps 10 and 11 come after step 12.
2024-03-31 23:42:51 +02:00
// 12. In both cases, let afterPotentialUnloads be the following steps:
2026-03-29 12:06:39 +02:00
bool const update_only = continuation - > update_only ;
2026-04-03 12:53:39 +02:00
RefPtr < SessionHistoryEntry > const target_entry = continuation - > target_entry ;
2026-03-31 16:56:18 +02:00
auto const displayed_document_id = continuation - > displayed_document_id ;
auto after_potential_unload = GC : : create_function ( heap ( ) , [ this , navigable , update_only , target_entry , continuation , population_output , old_origin , displayed_document_id , script_history_length , script_history_index , entries_for_navigation_api = move ( entries_for_navigation_api ) , navigation_type = m_navigation_type ] {
2026-03-26 22:48:49 +01:00
if ( population_output )
population_output - > apply_to ( * target_entry ) ;
2026-03-31 16:56:18 +02:00
// Post-population adjustments — only run when a fresh document was produced
// (not for 204/205 no-document outcomes where resolved_document is the old active document).
bool has_fresh_document = m_pending_document | | ( population_output & & population_output - > document ) ;
if ( has_fresh_document ) {
auto resolved_document = continuation - > resolved_document ;
2026-03-26 22:48:49 +01:00
// 2. If targetEntry's document's origin is not oldOrigin, then set targetEntry's classic history API state to StructuredSerializeForStorage(null).
2026-03-31 16:56:18 +02:00
if ( resolved_document - > origin ( ) ! = old_origin ) {
2026-03-26 22:48:49 +01:00
auto & vm = navigable - > vm ( ) ;
target_entry - > set_classic_history_api_state ( MUST ( structured_serialize_for_storage ( vm , JS : : js_null ( ) ) ) ) ;
}
// 3. If all of the following are true:
// - navigable's parent is null;
// - targetEntry's document's browsing context is not an auxiliary browsing context whose opener browsing context is non-null; and
// - targetEntry's document's origin is not oldOrigin,
// then set targetEntry's document state's navigable target name to the empty string.
if ( navigable - > parent ( ) = = nullptr
2026-03-31 16:56:18 +02:00
& & ! ( resolved_document - > browsing_context ( ) - > is_auxiliary ( ) & & resolved_document - > browsing_context ( ) - > opener_browsing_context ( ) ! = nullptr )
2026-03-26 22:48:49 +01:00
& & target_entry - > document_state ( ) - > origin ( ) ! = old_origin ) {
target_entry - > document_state ( ) - > set_navigable_target_name ( String { } ) ;
}
2024-04-12 20:13:54 +02:00
}
2024-11-08 11:06:33 +00:00
// 1. Let previousEntry be navigable's active session history entry.
2026-03-29 12:06:39 +02:00
auto previous_entry = navigable - > active_session_history_entry ( ) ;
2024-11-08 11:06:33 +00:00
// 2. If changingNavigableContinuation's update-only is false, then activate history entry targetEntry for navigable.
2026-03-31 16:56:18 +02:00
auto resolved_document = continuation - > resolved_document ;
2024-04-21 12:49:58 +02:00
if ( ! update_only )
2026-03-31 16:56:18 +02:00
navigable - > activate_history_entry ( * target_entry , * resolved_document ) ;
2024-03-31 23:42:51 +02:00
2024-11-08 11:06:33 +00:00
// 3. Let updateDocument be an algorithm step which performs update document for history step application given
2024-03-31 23:42:51 +02:00
// targetEntry's document, targetEntry, changingNavigableContinuation's update-only, scriptHistoryLength,
2024-11-08 11:06:33 +00:00
// scriptHistoryIndex, navigationType, entriesForNavigationAPI, and previousEntry.
2026-03-31 16:56:18 +02:00
auto update_document = [ script_history_length , script_history_index , entries_for_navigation_api = move ( entries_for_navigation_api ) , target_entry , update_only , navigation_type , previous_entry , resolved_document ] {
resolved_document - > update_for_history_step_application ( * target_entry , update_only , script_history_length , script_history_index , navigation_type , entries_for_navigation_api , previous_entry , navigation_type . has_value ( ) ) ;
2024-04-21 15:07:59 +02:00
} ;
2023-04-13 10:23:59 +03:00
2026-03-29 12:06:39 +02:00
// 4. If targetEntry's document is equal to displayedDocument, then perform updateDocument.
2026-03-31 16:56:18 +02:00
// NOTE: We compare against the pre-activation displayed_document_id (not the current
// active entry) because activate_history_entry() has already updated the active entry above.
if ( target_entry - > document_state ( ) - > document_id ( ) = = displayed_document_id ) {
2023-09-27 22:59:57 -06:00
update_document ( ) ;
}
// 5. Otherwise, queue a global task on the navigation and traversal task source given targetEntry's document's relevant global object to perform updateDocument
else {
2026-03-31 16:56:18 +02:00
queue_global_task ( Task : : Source : : NavigationAndTraversal , relevant_global_object ( * resolved_document ) , GC : : create_function ( heap ( ) , move ( update_document ) ) ) ;
2023-09-27 22:59:57 -06:00
}
2023-04-13 10:23:59 +03:00
2023-09-27 22:59:57 -06:00
// 6. Increment completedChangeJobs.
2026-03-29 12:06:39 +02:00
signal_progress ( ) ;
2023-04-13 10:23:59 +03:00
} ) ;
2024-03-31 23:42:51 +02:00
// 10. If changingNavigableContinuation's update-only is true, or targetEntry's document is displayedDocument, then:
2026-03-31 16:56:18 +02:00
if ( continuation - > update_only | | continuation - > resolved_document . ptr ( ) = = displayed_document . ptr ( ) ) {
2024-03-31 23:42:51 +02:00
// 1. Set the ongoing navigation for navigable to null.
navigable - > set_ongoing_navigation ( { } ) ;
// 2. Queue a global task on the navigation and traversal task source given navigable's active window to perform afterPotentialUnloads.
2024-04-16 22:04:01 +02:00
queue_global_task ( Task : : Source : : NavigationAndTraversal , * navigable - > active_window ( ) , after_potential_unload ) ;
2024-03-31 23:42:51 +02:00
}
// 11. Otherwise:
else {
// 1. Assert: navigationType is not null.
2026-03-29 12:06:39 +02:00
VERIFY ( m_navigation_type . has_value ( ) ) ;
2024-03-31 23:42:51 +02:00
2025-01-07 14:58:48 +00:00
// 2. Deactivate displayedDocument, given userInvolvement, targetEntry, navigationType, and afterPotentialUnloads.
2026-03-31 16:56:18 +02:00
deactivate_a_document_for_cross_document_navigation ( * displayed_document , m_user_involvement , * target_entry , continuation - > resolved_document , after_potential_unload ) ;
2024-03-31 23:42:51 +02:00
}
2023-04-13 10:23:59 +03:00
}
2026-03-29 12:06:39 +02:00
}
2023-04-13 10:23:59 +03:00
2026-03-29 12:06:39 +02:00
void ApplyHistoryStepState : : enter_waiting_for_non_changing_jobs ( )
{
2026-04-03 16:06:16 +02:00
VERIFY ( m_target_step = = m_traversable - > get_the_used_step ( m_step ) ) ;
2024-11-03 11:22:22 +01:00
2023-11-03 19:18:18 -06:00
// 17. Let (scriptHistoryLength, scriptHistoryIndex) be the result of getting the history object length and index given traversable and targetStep.
2026-03-29 12:06:39 +02:00
auto length_and_index = m_traversable - > get_the_history_object_length_and_index ( m_target_step ) ;
auto script_history_length = length_and_index . script_history_length ;
auto script_history_index = length_and_index . script_history_index ;
2023-11-03 19:18:18 -06:00
// 18. For each navigable of nonchangingNavigablesThatStillNeedUpdates, queue a global task on the navigation and traversal task source given navigable's active window to run the steps:
2026-03-29 12:06:39 +02:00
for ( auto & navigable : m_non_changing_navigables ) {
2024-11-23 10:52:36 +01:00
// AD-HOC: This check is not in the spec but we should not continue navigation if navigable has been destroyed,
// or if there's no active window.
if ( navigable - > has_been_destroyed ( ) | | ! navigable - > active_window ( ) ) {
2026-03-29 12:06:39 +02:00
+ + m_completed_non_changing_jobs ;
2024-03-27 14:53:13 +01:00
continue ;
}
2026-03-29 12:06:39 +02:00
// AD-HOC: Queue with null document instead of using queue_global_task.
// Tasks associated with a document are only runnable when fully active.
// In the async state machine, documents can become non-fully-active between
// queue time and execution, causing the task to be permanently stuck.
// A null-document task is always runnable; we check validity inside.
queue_a_task ( Task : : Source : : NavigationAndTraversal , nullptr , nullptr , GC : : create_function ( heap ( ) , [ this , navigable , script_history_length , script_history_index ] {
if ( navigable - > has_been_destroyed ( ) | | ! navigable - > active_window ( ) | | ! navigable - > active_document ( ) - > is_fully_active ( ) ) {
signal_progress ( ) ;
2023-11-03 19:18:18 -06:00
return ;
}
2023-04-13 10:23:59 +03:00
2023-11-03 19:18:18 -06:00
// 1. Let document be navigable's active document.
auto document = navigable - > active_document ( ) ;
2023-04-13 10:23:59 +03:00
2023-11-03 19:18:18 -06:00
// 2. Set document's history object's index to scriptHistoryIndex.
document - > history ( ) - > m_index = script_history_index ;
2023-04-13 10:23:59 +03:00
2023-11-03 19:18:18 -06:00
// 3. Set document's history object's length to scriptHistoryLength.
document - > history ( ) - > m_length = script_history_length ;
// 4. Increment completedNonchangingJobs.
2026-03-29 12:06:39 +02:00
signal_progress ( ) ;
2024-04-16 22:04:01 +02:00
} ) ) ;
2023-11-03 19:18:18 -06:00
}
2023-04-13 10:23:59 +03:00
2026-03-29 12:06:39 +02:00
try_advance ( ) ;
}
void ApplyHistoryStepState : : complete ( )
{
if ( m_phase = = Phase : : Completed )
return ;
m_phase = Phase : : Completed ;
m_timeout - > stop ( ) ;
2023-04-13 10:23:59 +03:00
// 20. Set traversable's current session history step to targetStep.
2026-03-29 12:06:39 +02:00
m_traversable - > m_current_session_history_step = m_target_step ;
2023-09-27 22:59:57 -06:00
2024-04-13 23:12:55 +02:00
// Not in the spec:
2026-03-29 12:06:39 +02:00
auto back_enabled = m_traversable - > m_current_session_history_step > 0 ;
VERIFY ( m_traversable - > m_session_history_entries . size ( ) > 0 ) ;
auto forward_enabled = m_traversable - > can_go_forward ( ) ;
m_traversable - > page ( ) . client ( ) . page_did_update_navigation_buttons_state ( back_enabled , forward_enabled ) ;
m_traversable - > page ( ) . client ( ) . page_did_change_url ( m_traversable - > current_session_history_entry ( ) - > url ( ) ) ;
// Clear state BEFORE on_complete, because on_complete may resolve a promise
// that triggers the next session history traversal queue entry.
// For nested states, the outer state is restored by the when_resolved callback
// on the sync nav step's promise in process_continuations().
m_traversable - > m_apply_history_step_state = nullptr ;
2024-04-14 10:27:20 +02:00
2023-09-27 22:59:57 -06:00
// 21. Return "applied".
2026-03-29 12:06:39 +02:00
if ( m_on_complete )
m_on_complete - > function ( ) ( HistoryStepResult : : Applied ) ;
}
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#apply-the-history-step
void TraversableNavigable : : apply_the_history_step (
int step ,
bool check_for_cancelation ,
GC : : Ptr < SourceSnapshotParams > source_snapshot_params ,
GC : : Ptr < Navigable > initiator_to_check ,
UserNavigationInvolvement user_involvement ,
Optional < Bindings : : NavigationType > navigation_type ,
SynchronousNavigation synchronous_navigation ,
2026-03-31 16:56:18 +02:00
GC : : Ptr < DOM : : Document > pending_document ,
2026-03-29 12:06:39 +02:00
GC : : Ref < OnApplyHistoryStepComplete > on_complete )
{
// FIXME: 1. Assert: This is running within traversable's session history traversal queue.
VERIFY ( ! m_apply_history_step_state | | m_paused_apply_history_step_state ) ;
// 2. Let targetStep be the result of getting the used step given traversable and step.
auto target_step = get_the_used_step ( step ) ;
// 3. If initiatorToCheck is not null, then:
if ( initiator_to_check ! = nullptr ) {
// 1. Assert: sourceSnapshotParams is not null.
VERIFY ( source_snapshot_params ) ;
// 2. For each navigable of get all navigables whose current session history entry will change or reload:
// if initiatorToCheck is not allowed by sandboxing to navigate navigable given sourceSnapshotParams, then return "initiator-disallowed".
for ( auto const & navigable : get_all_navigables_whose_current_session_history_entry_will_change_or_reload ( target_step ) ) {
if ( ! initiator_to_check - > allowed_by_sandboxing_to_navigate ( * navigable , * source_snapshot_params ) ) {
on_complete - > function ( ) ( HistoryStepResult : : InitiatorDisallowed ) ;
return ;
}
}
}
// 4. Let navigablesCrossingDocuments be the result of getting all navigables that might experience a cross-document traversal given traversable and targetStep.
auto navigables_crossing_documents = get_all_navigables_that_might_experience_a_cross_document_traversal ( target_step ) ;
// 5. If checkForCancelation is true, and the result of checking if unloading is canceled given navigablesCrossingDocuments, traversable, targetStep,
// and userInvolvement is not "continue", then return that result.
if ( check_for_cancelation ) {
check_if_unloading_is_canceled ( navigables_crossing_documents , * this , target_step , user_involvement ,
2026-03-31 16:56:18 +02:00
GC : : create_function ( heap ( ) , [ this , step , target_step , source_snapshot_params , user_involvement , navigation_type , synchronous_navigation , pending_document , on_complete ] ( CheckIfUnloadingIsCanceledResult result ) mutable {
2026-03-29 12:06:39 +02:00
if ( result = = CheckIfUnloadingIsCanceledResult : : CanceledByBeforeUnload ) {
on_complete - > function ( ) ( HistoryStepResult : : CanceledByBeforeUnload ) ;
return ;
}
if ( result = = CheckIfUnloadingIsCanceledResult : : CanceledByNavigate ) {
on_complete - > function ( ) ( HistoryStepResult : : CanceledByNavigate ) ;
return ;
}
2026-03-31 16:56:18 +02:00
apply_the_history_step_after_unload_check ( step , target_step , source_snapshot_params , user_involvement , navigation_type , synchronous_navigation , pending_document , on_complete ) ;
2026-03-29 12:06:39 +02:00
} ) ) ;
return ;
}
// 6. Let changingNavigables be the result of get all navigables whose current session history entry will change or reload given traversable and targetStep.
2026-03-31 16:56:18 +02:00
apply_the_history_step_after_unload_check ( step , target_step , source_snapshot_params , user_involvement , navigation_type , synchronous_navigation , pending_document , on_complete ) ;
2026-03-29 12:06:39 +02:00
}
void TraversableNavigable : : apply_the_history_step_after_unload_check (
int step ,
int target_step ,
GC : : Ptr < SourceSnapshotParams > source_snapshot_params ,
UserNavigationInvolvement user_involvement ,
Optional < Bindings : : NavigationType > navigation_type ,
SynchronousNavigation synchronous_navigation ,
2026-03-31 16:56:18 +02:00
GC : : Ptr < DOM : : Document > pending_document ,
2026-03-29 12:06:39 +02:00
GC : : Ref < GC : : Function < void ( HistoryStepResult ) > > on_complete )
{
auto state = heap ( ) . allocate < ApplyHistoryStepState > ( * this , step , target_step , source_snapshot_params ,
2026-03-31 16:56:18 +02:00
user_involvement , navigation_type , synchronous_navigation , pending_document , on_complete ) ;
2026-03-29 12:06:39 +02:00
VERIFY ( ! m_apply_history_step_state | | m_paused_apply_history_step_state ) ;
m_apply_history_step_state = state ;
state - > start ( ) ;
2023-09-27 22:56:11 -06:00
}
2026-03-28 00:08:48 +01:00
class CheckUnloadingCanceledState : public GC : : Cell {
GC_CELL ( CheckUnloadingCanceledState , GC : : Cell ) ;
GC_DECLARE_ALLOCATOR ( CheckUnloadingCanceledState ) ;
public :
using Result = TraversableNavigable : : CheckIfUnloadingIsCanceledResult ;
static constexpr int TIMEOUT_MS = 15000 ;
CheckUnloadingCanceledState (
GC : : Ptr < TraversableNavigable > traversable ,
Optional < UserNavigationInvolvement > user_involvement ,
GC : : Ref < GC : : Function < void ( Result ) > > callback )
: m_traversable ( traversable )
, m_user_involvement ( user_involvement )
, m_callback ( callback )
, m_timeout ( Platform : : Timer : : create_single_shot ( heap ( ) , TIMEOUT_MS , GC : : create_function ( heap ( ) , [ this ] {
if ( ! m_completed ) {
dbgln ( " FIXME: check_if_unloading_is_canceled timed out " ) ;
finish ( Result : : Continue ) ;
2024-10-02 23:56:26 +01:00
}
2026-03-28 00:08:48 +01:00
} ) ) )
{
m_timeout - > start ( ) ;
}
2024-10-02 23:56:26 +01:00
2026-03-28 00:08:48 +01:00
virtual void visit_edges ( Visitor & visitor ) override
{
Base : : visit_edges ( visitor ) ;
for ( auto & doc : m_phase2_documents )
visitor . visit ( doc ) ;
visitor . visit ( m_traversable ) ;
visitor . visit ( m_callback ) ;
visitor . visit ( m_timeout ) ;
}
2024-10-02 23:56:26 +01:00
2026-03-28 00:08:48 +01:00
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#checking-if-unloading-is-canceled
void start ( Vector < GC : : Root < Navigable > > const & navigables_that_need_before_unload , Optional < int > target_step )
{
// 1. Let documentsToFireBeforeunload be the active document of each item in navigablesThatNeedBeforeUnload.
for ( auto & navigable : navigables_that_need_before_unload )
m_phase2_documents . append ( * navigable - > active_document ( ) ) ;
2024-10-02 23:56:26 +01:00
2026-03-28 00:08:48 +01:00
// 2. Let unloadPromptShown be false.
2024-10-02 23:56:26 +01:00
2026-03-28 00:08:48 +01:00
// 3. Let finalStatus be "continue".
2024-10-02 23:56:26 +01:00
2026-03-28 00:08:48 +01:00
// 4. If traversable was given, then:
if ( m_traversable ) {
// 1. Assert: targetStep and userInvolvementForNavigateEvent were given.
// NOTE: This assertion is enforced by the caller.
2024-10-02 23:56:26 +01:00
2026-03-28 00:08:48 +01:00
// 2. Let targetEntry be the result of getting the target history entry given traversable and targetStep.
m_target_entry = m_traversable - > get_the_target_history_entry ( target_step . value ( ) ) ;
// 3. If targetEntry is not traversable's current session history entry, and targetEntry's document state's origin is not the same as
// traversable's current session history entry's document state's origin, then:
if ( m_target_entry ! = m_traversable - > current_session_history_entry ( ) & & m_target_entry - > document_state ( ) - > origin ( ) ! = m_traversable - > current_session_history_entry ( ) - > document_state ( ) - > origin ( ) ) {
2024-10-02 23:56:26 +01:00
2026-03-28 00:08:48 +01:00
// 1. Let eventsFired be false.
2024-10-02 23:56:26 +01:00
2026-03-28 00:08:48 +01:00
// 2. Let needsBeforeunload be true if navigablesThatNeedBeforeUnload contains traversable; otherwise false.
m_needs_beforeunload = navigables_that_need_before_unload . find_if ( [ this ] ( auto const & navigable ) {
return navigable . ptr ( ) = = m_traversable . ptr ( ) ;
} ) ! = navigables_that_need_before_unload . end ( ) ;
// 3. If needsBeforeunload is true, then remove traversable's active document from documentsToFireBeforeunload.
if ( m_needs_beforeunload ) {
m_phase2_documents . remove_first_matching ( [ this ] ( auto & document ) {
return document . ptr ( ) = = m_traversable - > active_document ( ) . ptr ( ) ;
} ) ;
}
start_phase1 ( ) ;
return ;
}
2024-10-02 23:56:26 +01:00
}
2026-03-28 00:08:48 +01:00
start_phase2 ( ) ;
2024-10-02 23:56:26 +01:00
}
2026-03-28 00:08:48 +01:00
private :
void start_phase1 ( )
{
// 4. Queue a global task on the navigation and traversal task source given traversable's active window to perform the following steps:
VERIFY ( m_traversable - > active_window ( ) ) ;
queue_global_task ( Task : : Source : : NavigationAndTraversal , * m_traversable - > active_window ( ) , GC : : create_function ( heap ( ) , [ this ] {
// 1. if needsBeforeunload is true, then:
if ( m_needs_beforeunload ) {
// 1. Let (unloadPromptShownForThisDocument, unloadPromptCanceledByThisDocument) be the result of running the steps to fire beforeunload given traversable's active document and false.
auto [ unload_prompt_shown_for_this_document , unload_prompt_canceled_by_this_document ] = m_traversable - > active_document ( ) - > steps_to_fire_beforeunload ( false ) ;
// 2. If unloadPromptShownForThisDocument is true, then set unloadPromptShown to true.
if ( unload_prompt_shown_for_this_document )
m_unload_prompt_shown = true ;
// 3. If unloadPromptCanceledByThisDocument is true, then set finalStatus to "canceled-by-beforeunload".
if ( unload_prompt_canceled_by_this_document )
m_final_status = Result : : CanceledByBeforeUnload ;
}
// 2. If finalStatus is "canceled-by-beforeunload", then abort these steps.
if ( m_final_status = = Result : : CanceledByBeforeUnload ) {
finish ( m_final_status ) ;
return ;
}
2024-10-02 23:56:26 +01:00
2026-03-28 00:08:48 +01:00
// 3. Let navigation be traversable's active window's navigation API.
VERIFY ( m_traversable - > active_window ( ) ) ;
auto navigation = m_traversable - > active_window ( ) - > navigation ( ) ;
2024-10-02 23:56:26 +01:00
2026-03-28 00:08:48 +01:00
// 4. Let navigateEventResult be the result of firing a traverse navigate event at navigation given targetEntry and userInvolvementForNavigateEvent.
VERIFY ( m_target_entry ) ;
auto navigate_event_result = navigation - > fire_a_traverse_navigate_event ( * m_target_entry , * m_user_involvement ) ;
2024-10-02 23:56:26 +01:00
2026-03-28 00:08:48 +01:00
// 5. If navigateEventResult is false, then set finalStatus to "canceled-by-navigate".
if ( ! navigate_event_result )
m_final_status = Result : : CanceledByNavigate ;
2024-10-02 23:56:26 +01:00
2026-03-28 00:08:48 +01:00
// 6. Set eventsFired to true.
2024-10-02 23:56:26 +01:00
2026-03-28 00:08:48 +01:00
phase1_completed ( ) ;
2024-10-02 23:56:26 +01:00
} ) ) ;
}
2026-03-28 00:08:48 +01:00
void phase1_completed ( )
{
// 5. Wait for eventsFired to be true.
// 6. If finalStatus is not "continue", then return finalStatus.
if ( m_final_status ! = Result : : Continue ) {
finish ( m_final_status ) ;
return ;
}
start_phase2 ( ) ;
}
void start_phase2 ( )
{
if ( m_phase2_documents . is_empty ( ) ) {
finish ( m_final_status ) ;
return ;
}
// 5. Let totalTasks be the size of documentsToFireBeforeunload.
// 6. Let completedTasks be 0.
m_remaining_phase2_tasks = m_phase2_documents . size ( ) ;
// 7. For each document of documentsToFireBeforeunload, queue a global task on the navigation and traversal task source given document's relevant global object to run the steps:
for ( auto & document : m_phase2_documents ) {
2026-04-09 19:47:27 +01:00
// AD-HOC: Queue with a null document instead of using queue_global_task. Tasks associated with a document
// are only runnable when fully active. In the async state machine, documents can become non
// fully-active between queue and execution time, causing the task to be permanently stuck.
// A null-document task is always runnable; we check validity inside.
queue_a_task ( Task : : Source : : NavigationAndTraversal , nullptr , nullptr , GC : : create_function ( heap ( ) , [ this , document ] {
if ( document - > has_been_destroyed ( ) | | ! document - > is_fully_active ( ) ) {
did_complete_phase2_task ( ) ;
return ;
}
2026-03-28 00:08:48 +01:00
// 1. Let (unloadPromptShownForThisDocument, unloadPromptCanceledByThisDocument) be the result of running the steps to fire beforeunload given document and unloadPromptShown.
auto [ unload_prompt_shown_for_this_document , unload_prompt_canceled_by_this_document ] = document - > steps_to_fire_beforeunload ( m_unload_prompt_shown ) ;
// 2. If unloadPromptShownForThisDocument is true, then set unloadPromptShown to true.
if ( unload_prompt_shown_for_this_document )
m_unload_prompt_shown = true ;
2024-10-02 23:56:26 +01:00
2026-03-28 00:08:48 +01:00
// 3. If unloadPromptCanceledByThisDocument is true, then set finalStatus to "canceled-by-beforeunload".
if ( unload_prompt_canceled_by_this_document )
m_final_status = Result : : CanceledByBeforeUnload ;
// 4. Increment completedTasks.
did_complete_phase2_task ( ) ;
} ) ) ;
}
}
void did_complete_phase2_task ( )
{
VERIFY ( m_remaining_phase2_tasks > 0 ) ;
if ( - - m_remaining_phase2_tasks > 0 )
return ;
// 8. Wait for completedTasks to be totalTasks.
// 9. Return finalStatus.
finish ( m_final_status ) ;
}
void finish ( Result final_result )
{
if ( m_completed )
return ;
m_completed = true ;
m_timeout - > stop ( ) ;
m_callback - > function ( ) ( final_result ) ;
}
Result m_final_status { Result : : Continue } ;
bool m_unload_prompt_shown { false } ;
bool m_completed { false } ;
bool m_needs_beforeunload { false } ;
size_t m_remaining_phase2_tasks { 0 } ;
Vector < GC : : Ref < DOM : : Document > > m_phase2_documents ;
GC : : Ptr < TraversableNavigable > m_traversable ;
2026-04-03 12:53:39 +02:00
RefPtr < SessionHistoryEntry > m_target_entry ;
2026-03-28 00:08:48 +01:00
Optional < UserNavigationInvolvement > m_user_involvement ;
GC : : Ref < GC : : Function < void ( Result ) > > m_callback ;
GC : : Ref < Platform : : Timer > m_timeout ;
} ;
GC_DEFINE_ALLOCATOR ( CheckUnloadingCanceledState ) ;
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#checking-if-unloading-is-canceled
void TraversableNavigable : : check_if_unloading_is_canceled (
Vector < GC : : Root < Navigable > > navigables_that_need_before_unload ,
GC : : Ptr < TraversableNavigable > traversable ,
Optional < int > target_step ,
Optional < UserNavigationInvolvement > user_involvement_for_navigate_events ,
GC : : Ref < GC : : Function < void ( CheckIfUnloadingIsCanceledResult ) > > callback )
{
auto state = heap ( ) . allocate < CheckUnloadingCanceledState > (
traversable ,
user_involvement_for_navigate_events ,
callback ) ;
state - > start ( navigables_that_need_before_unload , target_step ) ;
2024-10-02 23:56:26 +01:00
}
2026-03-28 00:08:48 +01:00
void TraversableNavigable : : check_if_unloading_is_canceled ( Vector < GC : : Root < Navigable > > navigables_that_need_before_unload , GC : : Ref < GC : : Function < void ( CheckIfUnloadingIsCanceledResult ) > > callback )
2024-10-02 23:56:26 +01:00
{
2026-03-28 00:08:48 +01:00
check_if_unloading_is_canceled ( move ( navigables_that_need_before_unload ) , { } , { } , { } , callback ) ;
2024-10-02 23:56:26 +01:00
}
2026-04-03 12:53:39 +02:00
Vector < NonnullRefPtr < SessionHistoryEntry > > TraversableNavigable : : get_session_history_entries_for_the_navigation_api ( GC : : Ref < Navigable > navigable , int target_step )
2023-09-27 22:56:11 -06:00
{
// 1. Let rawEntries be the result of getting session history entries for navigable.
auto raw_entries = navigable - > get_session_history_entries ( ) ;
if ( raw_entries . is_empty ( ) )
return { } ;
// 2. Let entriesForNavigationAPI be a new empty list.
2026-04-03 12:53:39 +02:00
Vector < NonnullRefPtr < SessionHistoryEntry > > entries_for_navigation_api ;
2023-09-27 22:56:11 -06:00
// 3. Let startingIndex be the index of the session history entry in rawEntries who has the greatest step less than or equal to targetStep.
// FIXME: Use min/max_element algorithm or some such here
int starting_index = 0 ;
auto max_step = 0 ;
for ( auto i = 0u ; i < raw_entries . size ( ) ; + + i ) {
auto const & entry = raw_entries [ i ] ;
2024-03-27 15:59:12 +01:00
if ( entry - > step ( ) . has < int > ( ) ) {
auto step = entry - > step ( ) . get < int > ( ) ;
2023-09-27 22:56:11 -06:00
if ( step < = target_step & & step > max_step ) {
starting_index = static_cast < int > ( i ) ;
}
}
}
// 4. Append rawEntries[startingIndex] to entriesForNavigationAPI.
entries_for_navigation_api . append ( raw_entries [ starting_index ] ) ;
// 5. Let startingOrigin be rawEntries[startingIndex]'s document state's origin.
2024-03-27 15:59:12 +01:00
auto starting_origin = raw_entries [ starting_index ] - > document_state ( ) - > origin ( ) ;
2023-09-27 22:56:11 -06:00
// 6. Let i be startingIndex − 1.
auto i = starting_index - 1 ;
// 7. While i > 0:
while ( i > 0 ) {
auto & entry = raw_entries [ static_cast < unsigned > ( i ) ] ;
// 1. If rawEntries[i]'s document state's origin is not same origin with startingOrigin, then break.
2024-03-27 15:59:12 +01:00
auto entry_origin = entry - > document_state ( ) - > origin ( ) ;
2023-09-27 22:56:11 -06:00
if ( starting_origin . has_value ( ) & & entry_origin . has_value ( ) & & ! entry_origin - > is_same_origin ( * starting_origin ) )
break ;
// 2. Prepend rawEntries[i] to entriesForNavigationAPI.
entries_for_navigation_api . prepend ( entry ) ;
// 3. Set i to i − 1.
- - i ;
}
// 8. Set i to startingIndex + 1.
i = starting_index + 1 ;
// 9. While i < rawEntries's size:
while ( i < static_cast < int > ( raw_entries . size ( ) ) ) {
auto & entry = raw_entries [ static_cast < unsigned > ( i ) ] ;
// 1. If rawEntries[i]'s document state's origin is not same origin with startingOrigin, then break.
2024-03-27 15:59:12 +01:00
auto entry_origin = entry - > document_state ( ) - > origin ( ) ;
2023-09-27 22:56:11 -06:00
if ( starting_origin . has_value ( ) & & entry_origin . has_value ( ) & & ! entry_origin - > is_same_origin ( * starting_origin ) )
break ;
// 2. Append rawEntries[i] to entriesForNavigationAPI.
entries_for_navigation_api . append ( entry ) ;
// 3. Set i to i + 1.
+ + i ;
}
// 10. Return entriesForNavigationAPI.
return entries_for_navigation_api ;
2023-04-13 10:23:59 +03:00
}
2023-04-06 12:24:09 +03:00
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#clear-the-forward-session-history
void TraversableNavigable : : clear_the_forward_session_history ( )
{
// FIXME: 1. Assert: this is running within navigable's session history traversal queue.
// 2. Let step be the navigable's current session history step.
auto step = current_session_history_step ( ) ;
// 3. Let entryLists be the ordered set « navigable's session history entries ».
2026-04-03 12:53:39 +02:00
Vector < Vector < NonnullRefPtr < SessionHistoryEntry > > & > entry_lists ;
2023-04-06 12:24:09 +03:00
entry_lists . append ( session_history_entries ( ) ) ;
// 4. For each entryList of entryLists:
while ( ! entry_lists . is_empty ( ) ) {
auto & entry_list = entry_lists . take_first ( ) ;
// 1. Remove every session history entry from entryList that has a step greater than step.
entry_list . remove_all_matching ( [ step ] ( auto & entry ) {
2024-03-27 15:59:12 +01:00
return entry - > step ( ) . template get < int > ( ) > step ;
2023-04-06 12:24:09 +03:00
} ) ;
// 2. For each entry of entryList:
for ( auto & entry : entry_list ) {
// 1. For each nestedHistory of entry's document state's nested histories, append nestedHistory's entries list to entryLists.
2024-03-27 15:59:12 +01:00
for ( auto & nested_history : entry - > document_state ( ) - > nested_histories ( ) ) {
2023-04-06 12:24:09 +03:00
entry_lists . append ( nested_history . entries ) ;
}
}
}
}
2024-04-16 12:18:48 +02:00
bool TraversableNavigable : : can_go_forward ( ) const
{
auto step = current_session_history_step ( ) ;
2026-04-03 12:53:39 +02:00
Vector < Vector < NonnullRefPtr < SessionHistoryEntry > > const & > entry_lists ;
2024-04-16 12:18:48 +02:00
entry_lists . append ( session_history_entries ( ) ) ;
while ( ! entry_lists . is_empty ( ) ) {
auto const & entry_list = entry_lists . take_first ( ) ;
for ( auto const & entry : entry_list ) {
if ( entry - > step ( ) . template get < int > ( ) > step )
return true ;
for ( auto & nested_history : entry - > document_state ( ) - > nested_histories ( ) )
entry_lists . append ( nested_history . entries ) ;
}
}
return false ;
}
2023-04-07 00:03:15 +03:00
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#traverse-the-history-by-a-delta
2024-11-25 14:30:12 +00:00
void TraversableNavigable : : traverse_the_history_by_delta ( int delta , GC : : Ptr < DOM : : Document > source_document )
2023-04-07 00:03:15 +03:00
{
2023-09-27 22:59:57 -06:00
// 1. Let sourceSnapshotParams and initiatorToCheck be null.
2024-11-25 14:30:12 +00:00
GC : : Ptr < SourceSnapshotParams > source_snapshot_params = nullptr ;
2024-11-15 04:01:23 +13:00
GC : : Ptr < Navigable > initiator_to_check = nullptr ;
2023-09-27 22:59:57 -06:00
// 2. Let userInvolvement be "browser UI".
UserNavigationInvolvement user_involvement = UserNavigationInvolvement : : BrowserUI ;
2023-04-07 00:03:15 +03:00
2023-09-27 22:59:57 -06:00
// 1. If sourceDocument is given, then:
2024-11-25 14:30:12 +00:00
if ( source_document ) {
2023-09-27 22:59:57 -06:00
// 1. Set sourceSnapshotParams to the result of snapshotting source snapshot params given sourceDocument.
source_snapshot_params = source_document - > snapshot_source_snapshot_params ( ) ;
2023-04-07 00:03:15 +03:00
2023-09-27 22:59:57 -06:00
// 2. Set initiatorToCheck to sourceDocument's node navigable.
initiator_to_check = source_document - > navigable ( ) ;
// 3. Set userInvolvement to "none".
user_involvement = UserNavigationInvolvement : : None ;
}
// 4. Append the following session history traversal steps to traversable:
2026-03-29 12:06:39 +02:00
append_session_history_traversal_steps ( GC : : create_function ( heap ( ) , [ this , delta , source_snapshot_params , initiator_to_check , user_involvement ] ( NonnullRefPtr < Core : : Promise < Empty > > signal ) {
2023-07-25 01:22:57 +02:00
// 1. Let allSteps be the result of getting all used history steps for traversable.
auto all_steps = get_all_used_history_steps ( ) ;
2023-04-07 00:03:15 +03:00
2023-07-25 01:22:57 +02:00
// 2. Let currentStepIndex be the index of traversable's current session history step within allSteps.
auto current_step_index = * all_steps . find_first_index ( current_session_history_step ( ) ) ;
2023-04-07 00:03:15 +03:00
2023-07-25 01:22:57 +02:00
// 3. Let targetStepIndex be currentStepIndex plus delta
auto target_step_index = current_step_index + delta ;
2023-04-07 00:03:15 +03:00
2023-07-25 01:22:57 +02:00
// 4. If allSteps[targetStepIndex] does not exist, then abort these steps.
if ( target_step_index > = all_steps . size ( ) ) {
2026-03-29 12:06:39 +02:00
signal - > resolve ( { } ) ;
return ;
2023-07-25 01:22:57 +02:00
}
2023-04-07 00:03:15 +03:00
2023-09-27 22:59:57 -06:00
// 5. Apply the traverse history step allSteps[targetStepIndex] to traversable, given sourceSnapshotParams,
// initiatorToCheck, and userInvolvement.
2026-03-28 00:08:48 +01:00
apply_the_traverse_history_step ( all_steps [ target_step_index ] , source_snapshot_params , initiator_to_check , user_involvement ,
2026-03-29 12:06:39 +02:00
GC : : create_function ( heap ( ) , [ signal ] ( HistoryStepResult ) {
signal - > resolve ( { } ) ;
2026-03-28 00:08:48 +01:00
} ) ) ;
2024-08-18 17:45:56 +12:00
} ) ) ;
2023-04-07 00:03:15 +03:00
}
2023-09-03 22:20:32 +02:00
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#update-for-navigable-creation/destruction
2026-03-29 12:06:39 +02:00
void TraversableNavigable : : update_for_navigable_creation_or_destruction ( GC : : Ref < OnApplyHistoryStepComplete > on_complete )
2023-04-07 00:08:39 +03:00
{
2023-09-03 22:20:32 +02:00
// 1. Let step be traversable's current session history step.
auto step = current_session_history_step ( ) ;
2024-03-28 12:19:57 +01:00
// 2. Return the result of applying the history step to traversable given false, null, null, null, and null.
2026-03-31 16:56:18 +02:00
apply_the_history_step ( step , false , { } , { } , UserNavigationInvolvement : : None , { } , SynchronousNavigation : : No , nullptr , on_complete ) ;
2023-09-03 22:20:32 +02:00
}
2023-04-07 00:08:39 +03:00
2023-09-03 22:20:32 +02:00
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#apply-the-reload-history-step
2026-03-29 12:06:39 +02:00
void TraversableNavigable : : apply_the_reload_history_step ( UserNavigationInvolvement user_involvement , GC : : Ref < GC : : Function < void ( HistoryStepResult ) > > on_complete )
2023-09-03 22:20:32 +02:00
{
// 1. Let step be traversable's current session history step.
auto step = current_session_history_step ( ) ;
2024-03-28 12:19:57 +01:00
// 2. Return the result of applying the history step step to traversable given true, null, null, null, and "reload".
2026-03-31 16:56:18 +02:00
apply_the_history_step ( step , true , { } , { } , user_involvement , Bindings : : NavigationType : : Reload , SynchronousNavigation : : No , nullptr , on_complete ) ;
2023-09-03 22:20:32 +02:00
}
2025-01-07 14:58:48 +00:00
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#apply-the-push/replace-history-step
2026-03-31 16:56:18 +02:00
void TraversableNavigable : : apply_the_push_or_replace_history_step ( int step , HistoryHandlingBehavior history_handling , UserNavigationInvolvement user_involvement , SynchronousNavigation synchronous_navigation , GC : : Ptr < DOM : : Document > pending_document , GC : : Ref < OnApplyHistoryStepComplete > on_complete )
2023-09-03 22:20:32 +02:00
{
2025-01-07 14:58:48 +00:00
// 1. Return the result of applying the history step step to traversable given false, null, null, userInvolvement, and historyHandling.
2024-03-28 12:19:57 +01:00
auto navigation_type = history_handling = = HistoryHandlingBehavior : : Replace ? Bindings : : NavigationType : : Replace : Bindings : : NavigationType : : Push ;
2026-03-31 16:56:18 +02:00
apply_the_history_step ( step , false , { } , { } , user_involvement , navigation_type , synchronous_navigation , pending_document , on_complete ) ;
2023-09-27 22:59:57 -06:00
}
2026-03-29 12:06:39 +02:00
void TraversableNavigable : : apply_the_traverse_history_step ( int step , GC : : Ptr < SourceSnapshotParams > source_snapshot_params , GC : : Ptr < Navigable > initiator_to_check , UserNavigationInvolvement user_involvement , GC : : Ref < GC : : Function < void ( HistoryStepResult ) > > on_complete )
2023-09-27 22:59:57 -06:00
{
2024-03-28 12:19:57 +01:00
// 1. Return the result of applying the history step step to traversable given true, sourceSnapshotParams, initiatorToCheck, userInvolvement, and "traverse".
2026-03-31 16:56:18 +02:00
apply_the_history_step ( step , true , source_snapshot_params , initiator_to_check , user_involvement , Bindings : : NavigationType : : Traverse , SynchronousNavigation : : No , nullptr , on_complete ) ;
2023-04-07 00:08:39 +03:00
}
2022-12-17 10:37:46 +01:00
// https://html.spec.whatwg.org/multipage/document-sequences.html#close-a-top-level-traversable
void TraversableNavigable : : close_top_level_traversable ( )
{
2024-09-20 15:49:33 +01:00
// 1. If traversable's is closing is true, then return.
2024-10-09 06:36:15 -04:00
if ( is_closing ( ) )
return ;
2026-03-26 15:32:03 +01:00
// AD-HOC: Set the is closing flag to prevent re-entrant calls from queuing duplicate session history steps.
set_closing ( true ) ;
2024-10-09 06:36:15 -04:00
// 2. Definitely close traversable.
definitely_close_top_level_traversable ( ) ;
}
2024-09-20 15:49:33 +01:00
2024-10-09 06:36:15 -04:00
// https://html.spec.whatwg.org/multipage/document-sequences.html#definitely-close-a-top-level-traversable
void TraversableNavigable : : definitely_close_top_level_traversable ( )
{
VERIFY ( is_top_level_traversable ( ) ) ;
// 1. Let toUnload be traversable's active document's inclusive descendant navigables.
2022-12-17 10:37:46 +01:00
auto to_unload = active_document ( ) - > inclusive_descendant_navigables ( ) ;
2025-07-08 12:54:42 +01:00
// 2. If the result of checking if unloading is canceled for toUnload is not "continue", then return.
2026-03-28 00:08:48 +01:00
check_if_unloading_is_canceled ( move ( to_unload ) , GC : : create_function ( heap ( ) , [ this ] ( CheckIfUnloadingIsCanceledResult result ) {
if ( result ! = CheckIfUnloadingIsCanceledResult : : Continue )
return ;
// 3. Append the following session history traversal steps to traversable:
2026-03-29 12:06:39 +02:00
append_session_history_traversal_steps ( GC : : create_function ( heap ( ) , [ this ] ( NonnullRefPtr < Core : : Promise < Empty > > signal ) {
2026-03-28 00:08:48 +01:00
// 1. Let afterAllUnloads be an algorithm step which destroys traversable.
auto after_all_unloads = GC : : create_function ( heap ( ) , [ this ] {
destroy_top_level_traversable ( ) ;
} ) ;
2022-12-17 10:37:46 +01:00
2026-03-28 00:08:48 +01:00
// 2. Unload a document and its descendants given traversable's active document, null, and afterAllUnloads.
active_document ( ) - > unload_a_document_and_its_descendants ( { } , after_all_unloads ) ;
2026-03-29 12:06:39 +02:00
signal - > resolve ( { } ) ;
2026-03-28 00:08:48 +01:00
} ) ) ;
2024-09-20 15:49:33 +01:00
} ) ) ;
2022-12-17 10:37:46 +01:00
}
2022-12-17 10:19:35 +01:00
// https://html.spec.whatwg.org/multipage/document-sequences.html#destroy-a-top-level-traversable
void TraversableNavigable : : destroy_top_level_traversable ( )
{
VERIFY ( is_top_level_traversable ( ) ) ;
// 1. Let browsingContext be traversable's active browsing context.
auto browsing_context = active_browsing_context ( ) ;
2026-03-31 16:56:18 +02:00
// 2. For each historyEntry in traversable's session history entries:
// NOTE: Without bfcache, only the active document is alive, so we only need to destroy it.
if ( active_document ( ) )
active_document ( ) - > destroy_a_document_and_its_descendants ( ) ;
2022-12-17 10:19:35 +01:00
// 3. Remove browsingContext.
2024-02-05 03:26:05 -07:00
if ( ! browsing_context ) {
dbgln ( " TraversableNavigable::destroy_top_level_traversable: No browsing context? " ) ;
} else {
browsing_context - > remove ( ) ;
}
2022-12-17 10:19:35 +01:00
2024-02-02 18:00:48 -07:00
// 4. Remove traversable from the user interface (e.g., close or hide its tab in a tabbed browser).
page ( ) . client ( ) . page_did_close_top_level_traversable ( ) ;
2022-12-17 10:19:35 +01:00
// 5. Remove traversable from the user agent's top-level traversable set.
user_agent_top_level_traversable_set ( ) . remove ( this ) ;
2024-02-12 17:36:38 -07:00
2026-03-13 14:07:05 +01:00
// FIXME: 6. Invoke WebDriver BiDi navigable destroyed with traversable.
2024-02-12 17:36:38 -07:00
// FIXME: Figure out why we need to do this... we shouldn't be leaking Navigables for all time.
// However, without this, we can keep stale destroyed traversables around.
set_has_been_destroyed ( ) ;
2026-04-01 10:18:55 +02:00
remove_from_all_navigables ( ) ;
2022-12-17 10:19:35 +01:00
}
2023-08-20 14:28:33 +02:00
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#finalize-a-same-document-navigation
2026-04-03 12:53:39 +02:00
void finalize_a_same_document_navigation ( GC : : Ref < TraversableNavigable > traversable , GC : : Ref < Navigable > target_navigable , NonnullRefPtr < SessionHistoryEntry > target_entry , RefPtr < SessionHistoryEntry > entry_to_replace , HistoryHandlingBehavior history_handling , UserNavigationInvolvement user_involvement , GC : : Ref < OnApplyHistoryStepComplete > on_complete )
2023-08-20 14:28:33 +02:00
{
2023-09-05 23:36:20 +02:00
// NOTE: This is not in the spec but we should not navigate destroyed navigable.
2026-03-29 12:06:39 +02:00
if ( target_navigable - > has_been_destroyed ( ) ) {
on_complete - > function ( ) ( HistoryStepResult : : Applied ) ;
2023-09-05 23:36:20 +02:00
return ;
2026-03-29 12:06:39 +02:00
}
2023-09-05 23:36:20 +02:00
2023-08-20 14:28:33 +02:00
// FIXME: 1. Assert: this is running on traversable's session history traversal queue.
// 2. If targetNavigable's active session history entry is not targetEntry, then return.
2026-03-04 19:38:37 -06:00
// FIXME: This is a workaround for a spec issue where the early return loses replace entries.
// Revisit when https://github.com/whatwg/html/issues/10232 is resolved.
2023-08-20 14:28:33 +02:00
if ( target_navigable - > active_session_history_entry ( ) ! = target_entry ) {
2026-03-04 19:38:37 -06:00
if ( entry_to_replace ) {
auto & target_entries = target_navigable - > get_session_history_entries ( ) ;
if ( auto it = target_entries . find ( * entry_to_replace ) ; it ! = target_entries . end ( ) ) {
target_entry - > set_step ( entry_to_replace - > step ( ) ) ;
* it = target_entry ;
}
}
2026-03-29 12:06:39 +02:00
on_complete - > function ( ) ( HistoryStepResult : : Applied ) ;
2023-08-20 14:28:33 +02:00
return ;
}
// 3. Let targetStep be null.
Optional < int > target_step ;
// 4. Let targetEntries be the result of getting session history entries for targetNavigable.
auto & target_entries = target_navigable - > get_session_history_entries ( ) ;
// 5. If entryToReplace is null, then:
2024-04-05 17:39:33 +02:00
// FIXME: Checking containment of entryToReplace should not be needed.
// For more details see https://github.com/whatwg/html/issues/10232#issuecomment-2037543137
2026-04-03 12:53:39 +02:00
if ( ! entry_to_replace | | ! target_entries . contains_slow ( NonnullRefPtr { * entry_to_replace } ) ) {
2023-08-20 14:28:33 +02:00
// 1. Clear the forward session history of traversable.
traversable - > clear_the_forward_session_history ( ) ;
// 2. Set targetStep to traversable's current session history step + 1.
target_step = traversable - > current_session_history_step ( ) + 1 ;
// 3. Set targetEntry's step to targetStep.
2024-03-27 15:59:12 +01:00
target_entry - > set_step ( * target_step ) ;
2023-08-20 14:28:33 +02:00
// 4. Append targetEntry to targetEntries.
target_entries . append ( target_entry ) ;
} else {
// 1. Replace entryToReplace with targetEntry in targetEntries.
* ( target_entries . find ( * entry_to_replace ) ) = target_entry ;
// 2. Set targetEntry's step to entryToReplace's step.
2024-03-27 15:59:12 +01:00
target_entry - > set_step ( entry_to_replace - > step ( ) ) ;
2023-08-20 14:28:33 +02:00
// 3. Set targetStep to traversable's current session history step.
target_step = traversable - > current_session_history_step ( ) ;
}
2025-01-07 14:58:48 +00:00
// 6. Apply the push/replace history step targetStep to traversable given historyHandling and userInvolvement.
2026-03-31 16:56:18 +02:00
traversable - > apply_the_push_or_replace_history_step ( * target_step , history_handling , user_involvement , TraversableNavigable : : SynchronousNavigation : : Yes , nullptr , on_complete ) ;
2023-08-20 14:28:33 +02:00
}
2023-09-19 20:24:18 +02:00
// https://html.spec.whatwg.org/multipage/interaction.html#system-visibility-state
void TraversableNavigable : : set_system_visibility_state ( VisibilityState visibility_state )
{
if ( m_system_visibility_state = = visibility_state )
return ;
m_system_visibility_state = visibility_state ;
2025-06-24 11:18:14 +01:00
// When a user agent determines that the system visibility state for
2023-09-19 20:24:18 +02:00
// traversable navigable traversable has changed to newState, it must run the following steps:
2023-09-27 09:29:47 +02:00
// 1. Let navigables be the inclusive descendant navigables of traversable's active document.
2023-09-19 20:24:18 +02:00
auto navigables = active_document ( ) - > inclusive_descendant_navigables ( ) ;
// 2. For each navigable of navigables:
for ( auto & navigable : navigables ) {
// 1. Let document be navigable's active document.
auto document = navigable - > active_document ( ) ;
VERIFY ( document ) ;
// 2. Queue a global task on the user interaction task source given document's relevant global object
// to update the visibility state of document with newState.
2024-11-15 04:01:23 +13:00
queue_global_task ( Task : : Source : : UserInteraction , relevant_global_object ( * document ) , GC : : create_function ( heap ( ) , [ visibility_state , document ] {
2023-09-19 20:24:18 +02:00
document - > update_the_visibility_state ( visibility_state ) ;
2024-04-16 22:04:01 +02:00
} ) ) ;
2023-09-19 20:24:18 +02:00
}
}
2024-04-26 16:59:04 +02:00
// https://html.spec.whatwg.org/multipage/interaction.html#currently-focused-area-of-a-top-level-traversable
2024-11-15 04:01:23 +13:00
GC : : Ptr < DOM : : Node > TraversableNavigable : : currently_focused_area ( )
2024-04-26 16:59:04 +02:00
{
// 1. If traversable does not have system focus, then return null.
if ( ! is_focused ( ) )
return nullptr ;
// 2. Let candidate be traversable's active document.
auto candidate = active_document ( ) ;
// 3. While candidate's focused area is a navigable container with a non-null content navigable:
// set candidate to the active document of that navigable container's content navigable.
2025-08-21 17:09:40 +02:00
while ( candidate - > focused_area ( )
& & is < NavigableContainer > ( candidate - > focused_area ( ) . ptr ( ) )
& & as < NavigableContainer > ( * candidate - > focused_area ( ) ) . content_navigable ( ) ) {
candidate = as < NavigableContainer > ( * candidate - > focused_area ( ) ) . content_navigable ( ) - > active_document ( ) ;
2024-04-26 16:59:04 +02:00
}
// 4. If candidate's focused area is non-null, set candidate to candidate's focused area.
2025-08-21 17:09:40 +02:00
if ( candidate - > focused_area ( ) ) {
2024-04-26 16:59:04 +02:00
// NOTE: We return right away here instead of assigning to candidate,
// since that would require compromising type safety.
2025-08-21 17:09:40 +02:00
return candidate - > focused_area ( ) ;
2024-04-26 16:59:04 +02:00
}
// 5. Return candidate.
return candidate ;
}
2025-06-22 20:26:03 +02:00
// https://w3c.github.io/geolocation/#dfn-emulated-position-data
Geolocation : : EmulatedPositionData const & TraversableNavigable : : emulated_position_data ( ) const
{
VERIFY ( is_top_level_traversable ( ) ) ;
return m_emulated_position_data ;
}
// https://w3c.github.io/geolocation/#dfn-emulated-position-data
void TraversableNavigable : : set_emulated_position_data ( Geolocation : : EmulatedPositionData data )
{
VERIFY ( is_top_level_traversable ( ) ) ;
m_emulated_position_data = data ;
}
2025-06-26 22:33:58 +02:00
void TraversableNavigable : : process_screenshot_requests ( )
{
auto & client = page ( ) . client ( ) ;
while ( ! m_screenshot_tasks . is_empty ( ) ) {
auto task = m_screenshot_tasks . dequeue ( ) ;
if ( task . node_id . has_value ( ) ) {
auto * dom_node = DOM : : Node : : from_unique_id ( * task . node_id ) ;
2026-02-26 11:42:39 +01:00
if ( dom_node )
dom_node - > document ( ) . update_layout ( DOM : : UpdateLayoutReason : : ProcessScreenshot ) ;
2025-06-26 22:33:58 +02:00
if ( ! dom_node | | ! dom_node - > paintable_box ( ) ) {
client . page_did_take_screenshot ( { } ) ;
continue ;
}
auto rect = page ( ) . enclosing_device_rect ( dom_node - > paintable_box ( ) - > absolute_border_box_rect ( ) ) ;
2025-12-16 02:19:00 +00:00
auto bitmap_or_error = Gfx : : Bitmap : : create ( Gfx : : BitmapFormat : : BGRA8888 , rect . size ( ) . to_type < int > ( ) ) ;
if ( bitmap_or_error . is_error ( ) ) {
client . page_did_take_screenshot ( { } ) ;
continue ;
}
auto bitmap = bitmap_or_error . release_value ( ) ;
2025-06-27 04:39:16 +02:00
auto painting_surface = Gfx : : PaintingSurface : : wrap_bitmap ( * bitmap ) ;
2025-06-26 22:33:58 +02:00
PaintConfig paint_config { . canvas_fill_rect = rect . to_type < int > ( ) } ;
2026-01-26 12:59:43 +01:00
render_screenshot ( painting_surface , paint_config , [ bitmap , & client ] {
2025-06-27 04:39:16 +02:00
client . page_did_take_screenshot ( bitmap - > to_shareable_bitmap ( ) ) ;
2025-06-26 22:33:58 +02:00
} ) ;
} else {
2026-02-26 11:42:39 +01:00
active_document ( ) - > update_layout ( DOM : : UpdateLayoutReason : : ProcessScreenshot ) ;
2025-06-26 22:33:58 +02:00
auto scrollable_overflow_rect = active_document ( ) - > layout_node ( ) - > paintable_box ( ) - > scrollable_overflow_rect ( ) ;
auto rect = page ( ) . enclosing_device_rect ( scrollable_overflow_rect . value ( ) ) ;
2025-12-16 02:19:00 +00:00
auto bitmap_or_error = Gfx : : Bitmap : : create ( Gfx : : BitmapFormat : : BGRA8888 , rect . size ( ) . to_type < int > ( ) ) ;
if ( bitmap_or_error . is_error ( ) ) {
client . page_did_take_screenshot ( { } ) ;
continue ;
}
auto bitmap = bitmap_or_error . release_value ( ) ;
2025-06-27 04:39:16 +02:00
auto painting_surface = Gfx : : PaintingSurface : : wrap_bitmap ( * bitmap ) ;
2025-06-26 22:33:58 +02:00
PaintConfig paint_config { . paint_overlay = true , . canvas_fill_rect = rect . to_type < int > ( ) } ;
2026-01-26 12:59:43 +01:00
render_screenshot ( painting_surface , paint_config , [ bitmap , & client ] {
2025-06-27 04:39:16 +02:00
client . page_did_take_screenshot ( bitmap - > to_shareable_bitmap ( ) ) ;
2025-06-26 22:33:58 +02:00
} ) ;
}
}
}
2022-12-12 12:01:09 +01:00
}