Coverage Report

Created: 2025-11-16 07:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibWeb/HTML/Navigation.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
3
 *
4
 * SPDX-License-Identifier: BSD-2-Clause
5
 */
6
7
#include <LibJS/Heap/Heap.h>
8
#include <LibJS/Runtime/Realm.h>
9
#include <LibJS/Runtime/VM.h>
10
#include <LibWeb/Bindings/ExceptionOrUtils.h>
11
#include <LibWeb/Bindings/Intrinsics.h>
12
#include <LibWeb/Bindings/NavigationPrototype.h>
13
#include <LibWeb/DOM/AbortController.h>
14
#include <LibWeb/DOM/Document.h>
15
#include <LibWeb/HTML/DocumentState.h>
16
#include <LibWeb/HTML/ErrorEvent.h>
17
#include <LibWeb/HTML/History.h>
18
#include <LibWeb/HTML/NavigateEvent.h>
19
#include <LibWeb/HTML/Navigation.h>
20
#include <LibWeb/HTML/NavigationCurrentEntryChangeEvent.h>
21
#include <LibWeb/HTML/NavigationDestination.h>
22
#include <LibWeb/HTML/NavigationHistoryEntry.h>
23
#include <LibWeb/HTML/NavigationTransition.h>
24
#include <LibWeb/HTML/Scripting/ExceptionReporter.h>
25
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
26
#include <LibWeb/HTML/TraversableNavigable.h>
27
#include <LibWeb/HTML/Window.h>
28
#include <LibWeb/WebIDL/AbstractOperations.h>
29
#include <LibWeb/XHR/FormData.h>
30
31
namespace Web::HTML {
32
33
JS_DEFINE_ALLOCATOR(Navigation);
34
JS_DEFINE_ALLOCATOR(NavigationAPIMethodTracker);
35
36
static NavigationResult navigation_api_method_tracker_derived_result(JS::NonnullGCPtr<NavigationAPIMethodTracker> api_method_tracker);
37
38
NavigationAPIMethodTracker::NavigationAPIMethodTracker(JS::NonnullGCPtr<Navigation> navigation,
39
    Optional<String> key,
40
    JS::Value info,
41
    Optional<SerializationRecord> serialized_state,
42
    JS::GCPtr<NavigationHistoryEntry> commited_to_entry,
43
    JS::NonnullGCPtr<WebIDL::Promise> committed_promise,
44
    JS::NonnullGCPtr<WebIDL::Promise> finished_promise)
45
0
    : navigation(navigation)
46
0
    , key(move(key))
47
0
    , info(info)
48
0
    , serialized_state(move(serialized_state))
49
0
    , commited_to_entry(commited_to_entry)
50
0
    , committed_promise(committed_promise)
51
0
    , finished_promise(finished_promise)
52
0
{
53
0
}
54
55
void NavigationAPIMethodTracker::visit_edges(Cell::Visitor& visitor)
56
0
{
57
0
    Base::visit_edges(visitor);
58
0
    visitor.visit(navigation);
59
0
    visitor.visit(info);
60
0
    visitor.visit(commited_to_entry);
61
0
    visitor.visit(committed_promise);
62
0
    visitor.visit(finished_promise);
63
0
}
64
65
JS::NonnullGCPtr<Navigation> Navigation::create(JS::Realm& realm)
66
0
{
67
0
    return realm.heap().allocate<Navigation>(realm, realm);
68
0
}
69
70
Navigation::Navigation(JS::Realm& realm)
71
0
    : DOM::EventTarget(realm)
72
0
{
73
0
}
74
75
0
Navigation::~Navigation() = default;
76
77
void Navigation::initialize(JS::Realm& realm)
78
0
{
79
0
    Base::initialize(realm);
80
0
    WEB_SET_PROTOTYPE_FOR_INTERFACE(Navigation);
81
0
}
82
83
void Navigation::visit_edges(JS::Cell::Visitor& visitor)
84
0
{
85
0
    Base::visit_edges(visitor);
86
0
    visitor.visit(m_entry_list);
87
0
    visitor.visit(m_transition);
88
0
    visitor.visit(m_ongoing_navigate_event);
89
0
    visitor.visit(m_ongoing_api_method_tracker);
90
0
    visitor.visit(m_upcoming_non_traverse_api_method_tracker);
91
0
    visitor.visit(m_upcoming_traverse_api_method_trackers);
92
0
}
93
94
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-navigation-entries
95
Vector<JS::NonnullGCPtr<NavigationHistoryEntry>> Navigation::entries() const
96
0
{
97
    // The entries() method steps are:
98
99
    // 1. If this has entries and events disabled, then return the empty list.
100
0
    if (has_entries_and_events_disabled())
101
0
        return {};
102
103
    // 2. Return this's entry list.
104
    //    NOTE: Recall that because of Web IDL's sequence type conversion rules,
105
    //          this will create a new JavaScript array object on each call.
106
    //          That is, navigation.entries() !== navigation.entries().
107
0
    return m_entry_list;
108
0
}
109
110
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigation-current-entry
111
JS::GCPtr<NavigationHistoryEntry> Navigation::current_entry() const
112
0
{
113
    // The current entry of a Navigation navigation is the result of running the following steps:
114
115
    // 1. If navigation has entries and events disabled, then return null.
116
0
    if (has_entries_and_events_disabled())
117
0
        return nullptr;
118
119
    // 2. Assert: navigation's current entry index is not −1.
120
0
    VERIFY(m_current_entry_index != -1);
121
122
    // 3. Return navigation's entry list[navigation's current entry index].
123
0
    return m_entry_list[m_current_entry_index];
124
0
}
125
126
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-navigation-updatecurrententry
127
WebIDL::ExceptionOr<void> Navigation::update_current_entry(NavigationUpdateCurrentEntryOptions options)
128
0
{
129
    // The updateCurrentEntry(options) method steps are:
130
131
    // 1. Let current be the current entry of this.
132
0
    auto current = current_entry();
133
134
    // 2. If current is null, then throw an "InvalidStateError" DOMException.
135
0
    if (current == nullptr)
136
0
        return WebIDL::InvalidStateError::create(realm(), "Cannot update current NavigationHistoryEntry when there is no current entry"_string);
137
138
    // 3. Let serializedState be StructuredSerializeForStorage(options["state"]), rethrowing any exceptions.
139
0
    auto serialized_state = TRY(structured_serialize_for_storage(vm(), options.state));
140
141
    // 4. Set current's session history entry's navigation API state to serializedState.
142
0
    current->session_history_entry().set_navigation_api_state(serialized_state);
143
144
    // 5. Fire an event named currententrychange at this using NavigationCurrentEntryChangeEvent,
145
    //    with its navigationType attribute initialized to null and its from initialized to current.
146
0
    NavigationCurrentEntryChangeEventInit event_init = {};
147
0
    event_init.navigation_type = {};
148
0
    event_init.from = current;
149
0
    dispatch_event(HTML::NavigationCurrentEntryChangeEvent::construct_impl(realm(), HTML::EventNames::currententrychange, event_init));
150
151
0
    return {};
152
0
}
153
154
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-navigation-cangoback
155
bool Navigation::can_go_back() const
156
0
{
157
    // The canGoBack getter steps are:
158
159
    // 1. If this has entries and events disabled, then return false.
160
0
    if (has_entries_and_events_disabled())
161
0
        return false;
162
163
    // 2. Assert: navigation's current entry index is not −1.
164
0
    VERIFY(m_current_entry_index != -1);
165
166
    // 3. If this's current entry index is 0, then return false.
167
    // 4. Return true.
168
0
    return (m_current_entry_index != 0);
169
0
}
170
171
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-navigation-cangoforward
172
bool Navigation::can_go_forward() const
173
0
{
174
    // The canGoForward getter steps are:
175
176
    // 1. If this has entries and events disabled, then return false.
177
0
    if (has_entries_and_events_disabled())
178
0
        return false;
179
180
    // 2. Assert: navigation's current entry index is not −1.
181
0
    VERIFY(m_current_entry_index != -1);
182
183
    // 3. If this's current entry index is equal to this's entry list's size, then return false.
184
    // 4. Return true.
185
0
    return (m_current_entry_index != static_cast<i64>(m_entry_list.size()));
186
0
}
187
188
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#history-handling-behavior
189
HistoryHandlingBehavior to_history_handling_behavior(Bindings::NavigationHistoryBehavior b)
190
0
{
191
    // A history handling behavior is a NavigationHistoryBehavior that is either "push" or "replace",
192
    // i.e., that has been resolved away from any initial "auto" value.
193
0
    VERIFY(b != Bindings::NavigationHistoryBehavior::Auto);
194
195
0
    switch (b) {
196
0
    case Bindings::NavigationHistoryBehavior::Push:
197
0
        return HistoryHandlingBehavior::Push;
198
0
    case Bindings::NavigationHistoryBehavior::Replace:
199
0
        return HistoryHandlingBehavior::Replace;
200
0
    case Bindings::NavigationHistoryBehavior::Auto:
201
0
        VERIFY_NOT_REACHED();
202
0
    };
203
0
    VERIFY_NOT_REACHED();
204
0
}
205
206
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#history-handling-behavior
207
Bindings::NavigationHistoryBehavior to_navigation_history_behavior(HistoryHandlingBehavior b)
208
0
{
209
    // A history handling behavior is a NavigationHistoryBehavior that is either "push" or "replace",
210
    // i.e., that has been resolved away from any initial "auto" value.
211
212
0
    switch (b) {
213
0
    case HistoryHandlingBehavior::Push:
214
0
        return Bindings::NavigationHistoryBehavior::Push;
215
0
    case HistoryHandlingBehavior::Replace:
216
0
        return Bindings::NavigationHistoryBehavior::Replace;
217
0
    }
218
0
    VERIFY_NOT_REACHED();
219
0
}
220
221
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-navigation-navigate
222
WebIDL::ExceptionOr<NavigationResult> Navigation::navigate(String url, NavigationNavigateOptions const& options)
223
0
{
224
0
    auto& realm = this->realm();
225
0
    auto& vm = this->vm();
226
    // The navigate(options) method steps are:
227
228
    // 1. Parse url relative to this's relevant settings object.
229
    //    If that returns failure, then return an early error result for a "SyntaxError" DOMException.
230
    //    Otherwise, let urlRecord be the resulting URL record.
231
0
    auto url_record = relevant_settings_object(*this).parse_url(url);
232
0
    if (!url_record.is_valid())
233
0
        return early_error_result(WebIDL::SyntaxError::create(realm, "Cannot navigate to Invalid URL"_string));
234
235
    // 2. Let document be this's relevant global object's associated Document.
236
0
    auto& document = verify_cast<HTML::Window>(relevant_global_object(*this)).associated_document();
237
238
    // 3. If options["history"] is "push", and the navigation must be a replace given urlRecord and document,
239
    //    then return an early error result for a "NotSupportedError" DOMException.
240
0
    if (options.history == Bindings::NavigationHistoryBehavior::Push && navigation_must_be_a_replace(url_record, document))
241
0
        return early_error_result(WebIDL::NotSupportedError::create(realm, "Navigation must be a replace, but push was requested"_string));
242
243
    // 4. Let state be options["state"], if it exists; otherwise, undefined.
244
0
    auto state = options.state.value_or(JS::js_undefined());
245
246
    // 5. Let serializedState be StructuredSerializeForStorage(state).
247
    //    If this throws an exception, then return an early error result for that exception.
248
    // FIXME: Fix this spec grammaro in the note
249
    // NOTE: It is importantly to perform this step early, since serialization can invoke web developer code,
250
    //       which in turn might change various things we check in later steps.
251
0
    auto serialized_state_or_error = structured_serialize_for_storage(vm, state);
252
0
    if (serialized_state_or_error.is_error()) {
253
0
        return early_error_result(serialized_state_or_error.release_error());
254
0
    }
255
256
0
    auto serialized_state = serialized_state_or_error.release_value();
257
258
    // 6. If document is not fully active, then return an early error result for an "InvalidStateError" DOMException.
259
0
    if (!document.is_fully_active())
260
0
        return early_error_result(WebIDL::InvalidStateError::create(realm, "Document is not fully active"_string));
261
262
    // 7. If document's unload counter is greater than 0, then return an early error result for an "InvalidStateError" DOMException.
263
0
    if (document.unload_counter() > 0)
264
0
        return early_error_result(WebIDL::InvalidStateError::create(realm, "Document already unloaded"_string));
265
266
    // 8. Let info be options["info"], if it exists; otherwise, undefined.
267
0
    auto info = options.info.value_or(JS::js_undefined());
268
269
    // 9. Let apiMethodTracker be the result of maybe setting the upcoming non-traverse API method tracker for this
270
    //    given info and serializedState.
271
0
    auto api_method_tracker = maybe_set_the_upcoming_non_traverse_api_method_tracker(info, serialized_state);
272
273
    // 10. Navigate document's node navigable to urlRecord using document,
274
    //     with historyHandling set to options["history"] and navigationAPIState set to serializedState.
275
    // FIXME: Fix spec typo here
276
    // NOTE: Unlike location.assign() and friends, which are exposed across origin-domain boundaries,
277
    //       navigation.navigate() can only be accessed by code with direct synchronous access to the
278
    //       window.navigation property. Thus, we avoid the complications about attributing the source document
279
    //       of the navigation, and we don't need to deal with the allowed by sandboxing to navigate check and its
280
    //       acccompanying exceptionsEnabled flag. We just treat all navigations as if they come from the Document
281
    //       corresponding to this Navigation object itself (i.e., document).
282
0
    TRY(document.navigable()->navigate({ .url = url_record, .source_document = document, .history_handling = options.history, .navigation_api_state = move(serialized_state) }));
283
284
    // 11. If this's upcoming non-traverse API method tracker is apiMethodTracker, then:
285
    // NOTE: If the upcoming non-traverse API method tracker is still apiMethodTracker, this means that the navigate
286
    //       algorithm bailed out before ever getting to the inner navigate event firing algorithm which would promote
287
    //       that upcoming API method tracker to ongoing.
288
0
    if (m_upcoming_non_traverse_api_method_tracker == api_method_tracker) {
289
0
        m_upcoming_non_traverse_api_method_tracker = nullptr;
290
0
        return early_error_result(WebIDL::AbortError::create(realm, "Navigation aborted"_string));
291
0
    }
292
293
    // 12. Return a navigation API method tracker-derived result for apiMethodTracker.
294
0
    return navigation_api_method_tracker_derived_result(api_method_tracker);
295
0
}
296
297
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-navigation-reload
298
WebIDL::ExceptionOr<NavigationResult> Navigation::reload(NavigationReloadOptions const& options)
299
0
{
300
0
    auto& realm = this->realm();
301
0
    auto& vm = this->vm();
302
    // The reload(options) method steps are:
303
304
    // 1. Let document be this's relevant global object's associated Document.
305
0
    auto& document = verify_cast<HTML::Window>(relevant_global_object(*this)).associated_document();
306
307
    // 2. Let serializedState be StructuredSerializeForStorage(undefined).
308
0
    auto serialized_state = MUST(structured_serialize_for_storage(vm, JS::js_undefined()));
309
310
    // 3. If options["state"] exists, then set serializedState to StructuredSerializeForStorage(options["state"]).
311
    //    If this throws an exception, then return an early error result for that exception.
312
    // NOTE: It is importantly to perform this step early, since serialization can invoke web developer
313
    //       code, which in turn might change various things we check in later steps.
314
0
    if (options.state.has_value()) {
315
0
        auto serialized_state_or_error = structured_serialize_for_storage(vm, options.state.value());
316
0
        if (serialized_state_or_error.is_error())
317
0
            return early_error_result(serialized_state_or_error.release_error());
318
0
        serialized_state = serialized_state_or_error.release_value();
319
0
    }
320
321
    // 4. Otherwise:
322
0
    else {
323
        // 1. Let current be the current entry of this.
324
0
        auto current = current_entry();
325
326
        // 2. If current is not null, then set serializedState to current's session history entry's navigation API state.
327
0
        if (current != nullptr)
328
0
            serialized_state = current->session_history_entry().navigation_api_state();
329
0
    }
330
331
    // 5. If document is not fully active, then return an early error result for an "InvalidStateError" DOMException.
332
0
    if (!document.is_fully_active())
333
0
        return early_error_result(WebIDL::InvalidStateError::create(realm, "Document is not fully active"_string));
334
335
    // 6. If document's unload counter is greater than 0, then return an early error result for an "InvalidStateError" DOMException.
336
0
    if (document.unload_counter() > 0)
337
0
        return early_error_result(WebIDL::InvalidStateError::create(realm, "Document already unloaded"_string));
338
339
    // 7. Let info be options["info"], if it exists; otherwise, undefined.
340
0
    auto info = options.info.value_or(JS::js_undefined());
341
342
    // 8. Let apiMethodTracker be the result of maybe setting the upcoming non-traverse API method tracker for this given info and serializedState.
343
0
    auto api_method_tracker = maybe_set_the_upcoming_non_traverse_api_method_tracker(info, serialized_state);
344
345
    // 9. Reload document's node navigable with navigationAPIState set to serializedState.
346
    // FIXME: Pass serialized_state to reload
347
0
    document.navigable()->reload();
348
349
0
    return navigation_api_method_tracker_derived_result(api_method_tracker);
350
0
}
351
352
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-navigation-traverseto
353
WebIDL::ExceptionOr<NavigationResult> Navigation::traverse_to(String key, NavigationOptions const& options)
354
0
{
355
0
    auto& realm = this->realm();
356
    // The traverseTo(key, options) method steps are:
357
358
    // 1. If this's current entry index is −1, then return an early error result for an "InvalidStateError" DOMException.
359
0
    if (m_current_entry_index == -1)
360
0
        return early_error_result(WebIDL::InvalidStateError::create(realm, "Cannot traverseTo: no current session history entry"_string));
361
362
    // 2. If this's entry list does not contain a NavigationHistoryEntry whose session history entry's navigation API key equals key,
363
    //    then return an early error result for an "InvalidStateError" DOMException.
364
0
    auto it = m_entry_list.find_if([&key](auto const& entry) {
365
0
        return entry->session_history_entry().navigation_api_key() == key;
366
0
    });
367
0
    if (it == m_entry_list.end())
368
0
        return early_error_result(WebIDL::InvalidStateError::create(realm, "Cannot traverseTo: key not found in session history list"_string));
369
370
    // 3. Return the result of performing a navigation API traversal given this, key, and options.
371
0
    return perform_a_navigation_api_traversal(key, options);
372
0
}
373
374
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#performing-a-navigation-api-traversal
375
WebIDL::ExceptionOr<NavigationResult> Navigation::back(NavigationOptions const& options)
376
0
{
377
0
    auto& realm = this->realm();
378
    // The back(options) method steps are:
379
380
    // 1. If this's current entry index is −1 or 0, then return an early error result for an "InvalidStateError" DOMException.
381
0
    if (m_current_entry_index == -1 || m_current_entry_index == 0)
382
0
        return early_error_result(WebIDL::InvalidStateError::create(realm, "Cannot navigate back: no previous session history entry"_string));
383
384
    // 2. Let key be this's entry list[this's current entry index − 1]'s session history entry's navigation API key.
385
0
    auto key = m_entry_list[m_current_entry_index - 1]->session_history_entry().navigation_api_key();
386
387
    // 3. Return the result of performing a navigation API traversal given this, key, and options.
388
0
    return perform_a_navigation_api_traversal(key, options);
389
0
}
390
391
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-navigation-forward
392
WebIDL::ExceptionOr<NavigationResult> Navigation::forward(NavigationOptions const& options)
393
0
{
394
0
    auto& realm = this->realm();
395
    // The forward(options) method steps are:
396
397
    // 1. If this's current entry index is −1 or is equal to this's entry list's size − 1,
398
    //    then return an early error result for an "InvalidStateError" DOMException.
399
0
    if (m_current_entry_index == -1 || m_current_entry_index == static_cast<i64>(m_entry_list.size() - 1))
400
0
        return early_error_result(WebIDL::InvalidStateError::create(realm, "Cannot navigate forward: no next session history entry"_string));
401
402
    // 2. Let key be this's entry list[this's current entry index + 1]'s session history entry's navigation API key.
403
0
    auto key = m_entry_list[m_current_entry_index + 1]->session_history_entry().navigation_api_key();
404
405
    // 3. Return the result of performing a navigation API traversal given this, key, and options.
406
0
    return perform_a_navigation_api_traversal(key, options);
407
0
}
408
409
void Navigation::set_onnavigate(WebIDL::CallbackType* event_handler)
410
0
{
411
0
    set_event_handler_attribute(HTML::EventNames::navigate, event_handler);
412
0
}
413
414
WebIDL::CallbackType* Navigation::onnavigate()
415
0
{
416
0
    return event_handler_attribute(HTML::EventNames::navigate);
417
0
}
418
419
void Navigation::set_onnavigatesuccess(WebIDL::CallbackType* event_handler)
420
0
{
421
0
    set_event_handler_attribute(HTML::EventNames::navigatesuccess, event_handler);
422
0
}
423
424
WebIDL::CallbackType* Navigation::onnavigatesuccess()
425
0
{
426
0
    return event_handler_attribute(HTML::EventNames::navigatesuccess);
427
0
}
428
429
void Navigation::set_onnavigateerror(WebIDL::CallbackType* event_handler)
430
0
{
431
0
    set_event_handler_attribute(HTML::EventNames::navigateerror, event_handler);
432
0
}
433
434
WebIDL::CallbackType* Navigation::onnavigateerror()
435
0
{
436
0
    return event_handler_attribute(HTML::EventNames::navigateerror);
437
0
}
438
439
void Navigation::set_oncurrententrychange(WebIDL::CallbackType* event_handler)
440
0
{
441
0
    set_event_handler_attribute(HTML::EventNames::currententrychange, event_handler);
442
0
}
443
444
WebIDL::CallbackType* Navigation::oncurrententrychange()
445
0
{
446
0
    return event_handler_attribute(HTML::EventNames::currententrychange);
447
0
}
448
449
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#has-entries-and-events-disabled
450
bool Navigation::has_entries_and_events_disabled() const
451
0
{
452
    // A Navigation navigation has entries and events disabled if the following steps return true:
453
454
    // 1. Let document be navigation's relevant global object's associated Document.
455
0
    auto const& document = verify_cast<HTML::Window>(relevant_global_object(*this)).associated_document();
456
457
    // 2. If document is not fully active, then return true.
458
0
    if (!document.is_fully_active())
459
0
        return true;
460
461
    // 3. If document's is initial about:blank is true, then return true.
462
0
    if (document.is_initial_about_blank())
463
0
        return true;
464
465
    // 4. If document's origin is opaque, then return true.
466
0
    if (document.origin().is_opaque())
467
0
        return true;
468
469
    // 5. Return false.
470
0
    return false;
471
0
}
472
473
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#getting-the-navigation-api-entry-index
474
i64 Navigation::get_the_navigation_api_entry_index(SessionHistoryEntry const& she) const
475
0
{
476
    // To get the navigation API entry index of a session history entry she within a Navigation navigation:
477
478
    // 1. Let index be 0.
479
0
    i64 index = 0;
480
481
    // 2. For each nhe of navigation's entry list:
482
0
    for (auto const& nhe : m_entry_list) {
483
        // 1. If nhe's session history entry is equal to she, then return index.
484
0
        if (&nhe->session_history_entry() == &she)
485
0
            return index;
486
487
        // 2. Increment index by 1.
488
0
        ++index;
489
0
    }
490
491
    // 3. Return −1.
492
0
    return -1;
493
0
}
494
495
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigation-api-early-error-result
496
NavigationResult Navigation::early_error_result(AnyException e)
497
0
{
498
0
    auto& vm = this->vm();
499
500
    // An early error result for an exception e is a NavigationResult dictionary instance given by
501
    // «[ "committed" → a promise rejected with e, "finished" → a promise rejected with e ]».
502
0
    auto throw_completion = Bindings::dom_exception_to_throw_completion(vm, e);
503
0
    return {
504
0
        .committed = WebIDL::create_rejected_promise(realm(), *throw_completion.value())->promise(),
505
0
        .finished = WebIDL::create_rejected_promise(realm(), *throw_completion.value())->promise(),
506
0
    };
507
0
}
508
509
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigation-api-method-tracker-derived-result
510
NavigationResult navigation_api_method_tracker_derived_result(JS::NonnullGCPtr<NavigationAPIMethodTracker> api_method_tracker)
511
0
{
512
    // A navigation API method tracker-derived result for a navigation API method tracker is a NavigationResult
513
    /// dictionary instance given by «[ "committed" apiMethodTracker's committed promise, "finished" → apiMethodTracker's finished promise ]».
514
0
    return {
515
0
        api_method_tracker->committed_promise->promise(),
516
0
        api_method_tracker->finished_promise->promise(),
517
0
    };
518
0
}
519
520
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#upcoming-non-traverse-api-method-tracker
521
JS::NonnullGCPtr<NavigationAPIMethodTracker> Navigation::maybe_set_the_upcoming_non_traverse_api_method_tracker(JS::Value info, Optional<SerializationRecord> serialized_state)
522
0
{
523
0
    auto& realm = relevant_realm(*this);
524
0
    auto& vm = this->vm();
525
    // To maybe set the upcoming non-traverse API method tracker given a Navigation navigation,
526
    // a JavaScript value info, and a serialized state-or-null serializedState:
527
528
    // 1. Let committedPromise and finishedPromise be new promises created in navigation's relevant realm.
529
0
    auto committed_promise = WebIDL::create_promise(realm);
530
0
    auto finished_promise = WebIDL::create_promise(realm);
531
532
    // 2. Mark as handled finishedPromise.
533
    // NOTE: The web developer doesn’t necessarily care about finishedPromise being rejected:
534
    //       - They might only care about committedPromise.
535
    //       - They could be doing multiple synchronous navigations within the same task,
536
    //         in which case all but the last will be aborted (causing their finishedPromise to reject).
537
    //         This could be an application bug, but also could just be an emergent feature of disparate
538
    //         parts of the application overriding each others' actions.
539
    //       - They might prefer to listen to other transition-failure signals instead of finishedPromise, e.g.,
540
    //         the navigateerror event, or the navigation.transition.finished promise.
541
    //       As such, we mark it as handled to ensure that it never triggers unhandledrejection events.
542
0
    WebIDL::mark_promise_as_handled(finished_promise);
543
544
    // 3. Let apiMethodTracker be a new navigation API method tracker with:
545
    //     navigation object: navigation
546
    //     key:               null
547
    //     info:              info
548
    //     serialized state:  serializedState
549
    //     comitted-to entry: null
550
    //     comitted promise:  committedPromise
551
    //     finished promise:  finishedPromise
552
0
    auto api_method_tracker = vm.heap().allocate_without_realm<NavigationAPIMethodTracker>(
553
0
        /* .navigation = */ *this,
554
0
        /* .key = */ OptionalNone {},
555
0
        /* .info = */ info,
556
0
        /* .serialized_state = */ move(serialized_state),
557
0
        /* .commited_to_entry = */ nullptr,
558
0
        /* .committed_promise = */ committed_promise,
559
0
        /* .finished_promise = */ finished_promise);
560
561
    // 4. Assert: navigation's upcoming non-traverse API method tracker is null.
562
0
    VERIFY(m_upcoming_non_traverse_api_method_tracker == nullptr);
563
564
    // 5. If navigation does not have entries and events disabled,
565
    //    then set navigation's upcoming non-traverse API method tracker to apiMethodTracker.
566
    // NOTE: If navigation has entries and events disabled, then committedPromise and finishedPromise will never fulfill
567
    //      (since we never create a NavigationHistoryEntry object for such Documents, and so we have nothing to resolve them with);
568
    //      there is no NavigationHistoryEntry to apply serializedState to; and there is no navigate event to include info with.
569
    //      So, we don't need to track this API method call after all.
570
0
    if (!has_entries_and_events_disabled())
571
0
        m_upcoming_non_traverse_api_method_tracker = api_method_tracker;
572
573
    // 6. Return apiMethodTracker.
574
0
    return api_method_tracker;
575
0
}
576
577
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#add-an-upcoming-traverse-api-method-tracker
578
JS::NonnullGCPtr<NavigationAPIMethodTracker> Navigation::add_an_upcoming_traverse_api_method_tracker(String destination_key, JS::Value info)
579
0
{
580
0
    auto& vm = this->vm();
581
0
    auto& realm = relevant_realm(*this);
582
    // To add an upcoming traverse API method tracker given a Navigation navigation, a string destinationKey, and a JavaScript value info:
583
584
    // 1. Let committedPromise and finishedPromise be new promises created in navigation's relevant realm.
585
0
    auto committed_promise = WebIDL::create_promise(realm);
586
0
    auto finished_promise = WebIDL::create_promise(realm);
587
588
    // 2. Mark as handled finishedPromise.
589
    // NOTE: See the previous discussion about why this is done
590
    //       https://html.spec.whatwg.org/multipage/nav-history-apis.html#note-mark-as-handled-navigation-api-finished
591
0
    WebIDL::mark_promise_as_handled(*finished_promise);
592
593
    // 3. Let apiMethodTracker be a new navigation API method tracker with:
594
    //     navigation object: navigation
595
    //     key:               destinationKey
596
    //     info:              info
597
    //     serialized state:  null
598
    //     comitted-to entry: null
599
    //     comitted promise:  committedPromise
600
    //     finished promise:  finishedPromise
601
0
    auto api_method_tracker = vm.heap().allocate_without_realm<NavigationAPIMethodTracker>(
602
0
        /* .navigation = */ *this,
603
0
        /* .key = */ destination_key,
604
0
        /* .info = */ info,
605
0
        /* .serialized_state = */ OptionalNone {},
606
0
        /* .commited_to_entry = */ nullptr,
607
0
        /* .committed_promise = */ committed_promise,
608
0
        /* .finished_promise = */ finished_promise);
609
610
    // 4. Set navigation's upcoming traverse API method trackers[key] to apiMethodTracker.
611
    // FIXME: Fix spec typo key --> destinationKey
612
0
    m_upcoming_traverse_api_method_trackers.set(destination_key, api_method_tracker);
613
614
    // 5. Return apiMethodTracker.
615
0
    return api_method_tracker;
616
0
}
617
618
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#performing-a-navigation-api-traversal
619
WebIDL::ExceptionOr<NavigationResult> Navigation::perform_a_navigation_api_traversal(String key, NavigationOptions const& options)
620
0
{
621
0
    auto& realm = this->realm();
622
    // To perform a navigation API traversal given a Navigation navigation, a string key, and a NavigationOptions options:
623
624
    // 1. Let document be this's relevant global object's associated Document.
625
0
    auto& document = verify_cast<HTML::Window>(relevant_global_object(*this)).associated_document();
626
627
    // 2. If document is not fully active, then return an early error result for an "InvalidStateError" DOMException.
628
0
    if (!document.is_fully_active())
629
0
        return early_error_result(WebIDL::InvalidStateError::create(realm, "Document is not fully active"_string));
630
631
    // 3. If document's unload counter is greater than 0, then return an early error result for an "InvalidStateError" DOMException.
632
0
    if (document.unload_counter() > 0)
633
0
        return early_error_result(WebIDL::InvalidStateError::create(realm, "Document already unloaded"_string));
634
635
    // 4. Let current be the current entry of navigation.
636
0
    auto current = current_entry();
637
638
    // 5. If key equals current's session history entry's navigation API key, then return
639
    //    «[ "committed" → a promise resolved with current, "finished" → a promise resolved with current ]».
640
0
    if (key == current->session_history_entry().navigation_api_key()) {
641
0
        return NavigationResult {
642
0
            .committed = WebIDL::create_resolved_promise(realm, current)->promise(),
643
0
            .finished = WebIDL::create_resolved_promise(realm, current)->promise()
644
0
        };
645
0
    }
646
647
    // 6. If navigation's upcoming traverse API method trackers[key] exists,
648
    //    then return a navigation API method tracker-derived result for navigation's upcoming traverse API method trackers[key].
649
0
    if (auto maybe_tracker = m_upcoming_traverse_api_method_trackers.get(key); maybe_tracker.has_value())
650
0
        return navigation_api_method_tracker_derived_result(maybe_tracker.value());
651
652
    // 7. Let info be options["info"], if it exists; otherwise, undefined.
653
0
    auto info = options.info.value_or(JS::js_undefined());
654
655
    // 8. Let apiMethodTracker be the result of adding an upcoming traverse API method tracker for navigation given key and info.
656
0
    auto api_method_tracker = add_an_upcoming_traverse_api_method_tracker(key, info);
657
658
    // 9. Let navigable be document's node navigable.
659
0
    auto navigable = document.navigable();
660
661
    // 10. Let traversable be navigable's traversable navigable.
662
0
    auto traversable = navigable->traversable_navigable();
663
664
    // 11. Let sourceSnapshotParams be the result of snapshotting source snapshot params given document.
665
0
    auto source_snapshot_params = document.snapshot_source_snapshot_params();
666
667
    // 12. Append the following session history traversal steps to traversable:
668
0
    traversable->append_session_history_traversal_steps(JS::create_heap_function(heap(), [key, api_method_tracker, navigable, source_snapshot_params, traversable, this] {
669
        // 1. Let navigableSHEs be the result of getting session history entries given navigable.
670
0
        auto navigable_shes = navigable->get_session_history_entries();
671
672
        // 2. Let targetSHE be the session history entry in navigableSHEs whose navigation API key is key. If no such entry exists, then:
673
0
        auto it = navigable_shes.find_if([&key](auto const& entry) {
674
0
            return entry->navigation_api_key() == key;
675
0
        });
676
0
        if (it == navigable_shes.end()) {
677
            // NOTE: This path is taken if navigation's entry list was outdated compared to navigableSHEs,
678
            //       which can occur for brief periods while all the relevant threads and processes are being synchronized in reaction to a history change.
679
680
            // 1. Queue a global task on the navigation and traversal task source given navigation's relevant global object
681
            //    to reject the finished promise for apiMethodTracker with an "InvalidStateError" DOMException.
682
0
            queue_global_task(HTML::Task::Source::NavigationAndTraversal, relevant_global_object(*this), JS::create_heap_function(heap(), [this, api_method_tracker] {
683
0
                auto& reject_realm = relevant_realm(*this);
684
0
                TemporaryExecutionContext execution_context { relevant_settings_object(*this) };
685
0
                WebIDL::reject_promise(reject_realm, api_method_tracker->finished_promise,
686
0
                    WebIDL::InvalidStateError::create(reject_realm, "Cannot traverse with stale session history entry"_string));
687
0
            }));
688
689
            // 2. Abort these steps.
690
0
            return;
691
0
        }
692
0
        auto target_she = *it;
693
694
        // 3. If targetSHE is navigable's active session history entry, then abort these steps.
695
        // NOTE: This can occur if a previously queued traversal already took us to this session history entry.
696
        //       In that case the previous traversal will have dealt with apiMethodTracker already.
697
0
        if (target_she == navigable->active_session_history_entry())
698
0
            return;
699
700
        // 4. Let result be the result of applying the traverse history step given by targetSHE's step to traversable,
701
        //    given sourceSnapshotParams, navigable, and "none".
702
0
        auto result = traversable->apply_the_traverse_history_step(target_she->step().get<int>(), source_snapshot_params, navigable, UserNavigationInvolvement::None);
703
704
        // NOTE: When result is "canceled-by-beforeunload" or "initiator-disallowed", the navigate event was never fired,
705
        //       aborting the ongoing navigation would not be correct; it would result in a navigateerror event without a
706
        //       preceding navigate event. In the "canceled-by-navigate" case, navigate is fired, but the inner navigate event
707
        //       firing algorithm will take care of aborting the ongoing navigation.
708
709
        // 5. If result is "canceled-by-beforeunload", then queue a global task on the navigation and traversal task source
710
        //    given navigation's relevant global object to reject the finished promise for apiMethodTracker with a
711
        //    new "AbortError" DOMException created in navigation's relevant realm.
712
0
        auto& realm = relevant_realm(*this);
713
0
        auto& global = relevant_global_object(*this);
714
0
        if (result == TraversableNavigable::HistoryStepResult::CanceledByBeforeUnload) {
715
0
            queue_global_task(Task::Source::NavigationAndTraversal, global, JS::create_heap_function(heap(), [this, api_method_tracker, &realm] {
716
0
                TemporaryExecutionContext execution_context { relevant_settings_object(*this) };
717
0
                reject_the_finished_promise(api_method_tracker, WebIDL::AbortError::create(realm, "Navigation cancelled by beforeunload"_string));
718
0
            }));
719
0
        }
720
721
        // 6. If result is "initiator-disallowed", then queue a global task on the navigation and traversal task source
722
        //    given navigation's relevant global object to reject the finished promise for apiMethodTracker with a
723
        //    new "SecurityError" DOMException created in navigation's relevant realm.
724
0
        if (result == TraversableNavigable::HistoryStepResult::InitiatorDisallowed) {
725
0
            queue_global_task(Task::Source::NavigationAndTraversal, global, JS::create_heap_function(heap(), [this, api_method_tracker, &realm] {
726
0
                TemporaryExecutionContext execution_context { relevant_settings_object(*this) };
727
0
                reject_the_finished_promise(api_method_tracker, WebIDL::SecurityError::create(realm, "Navigation disallowed from this origin"_string));
728
0
            }));
729
0
        }
730
0
    }));
731
732
    // 13. Return a navigation API method tracker-derived result for apiMethodTracker.
733
0
    return navigation_api_method_tracker_derived_result(api_method_tracker);
734
0
}
735
736
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#abort-the-ongoing-navigation
737
void Navigation::abort_the_ongoing_navigation(JS::GCPtr<WebIDL::DOMException> error)
738
0
{
739
0
    auto& realm = relevant_realm(*this);
740
741
    // To abort the ongoing navigation given a Navigation navigation and an optional DOMException error:
742
743
    // 1. Let event be navigation's ongoing navigate event.
744
0
    auto event = ongoing_navigate_event();
745
746
    // 2. Assert: event is not null.
747
0
    VERIFY(event != nullptr);
748
749
    // 3. Set navigation's focus changed during ongoing navigation to false.
750
0
    m_focus_changed_during_ongoing_navigation = false;
751
752
    // 4. Set navigation's suppress normal scroll restoration during ongoing navigation to false.
753
0
    m_suppress_scroll_restoration_during_ongoing_navigation = false;
754
755
    // 5. If error was not given, then let error be a new "AbortError" DOMException created in navigation's relevant realm.
756
0
    if (!error)
757
0
        error = WebIDL::AbortError::create(realm, "Navigation aborted"_string);
758
759
0
    VERIFY(error);
760
761
    // 6. If event's dispatch flag is set, then set event's canceled flag to true.
762
0
    if (event->dispatched())
763
0
        event->set_cancelled(true);
764
765
    // 7. Signal abort on event's abort controller given error.
766
0
    event->abort_controller()->abort(error);
767
768
    // 8. Set navigation's ongoing navigate event to null.
769
0
    m_ongoing_navigate_event = nullptr;
770
771
    // 9. Fire an event named navigateerror at navigation using ErrorEvent, with error initialized to error,
772
    //   and message, filename, lineno, and colno initialized to appropriate values that can be extracted
773
    //   from error and the current JavaScript stack in the same underspecified way that the report the exception algorithm does.
774
0
    ErrorEventInit event_init = {};
775
0
    event_init.error = error;
776
    // FIXME: Extract information from the exception and the JS context in the wishy-washy way the spec says here.
777
0
    event_init.filename = String {};
778
0
    event_init.colno = 0;
779
0
    event_init.lineno = 0;
780
0
    event_init.message = String {};
781
782
0
    dispatch_event(ErrorEvent::create(realm, EventNames::navigateerror, event_init));
783
784
    // 10. If navigation's ongoing API method tracker is non-null, then reject the finished promise for apiMethodTracker with error.
785
0
    if (m_ongoing_api_method_tracker != nullptr)
786
0
        WebIDL::reject_promise(realm, m_ongoing_api_method_tracker->finished_promise, error);
787
788
    // 11. If navigation's transition is not null, then:
789
0
    if (m_transition != nullptr) {
790
        // 1. Reject navigation's transition's finished promise with error.
791
0
        m_transition->finished()->reject(error);
792
793
        // 2. Set navigation's transition to null.
794
0
        m_transition = nullptr;
795
0
    }
796
0
}
797
798
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#promote-an-upcoming-api-method-tracker-to-ongoing
799
void Navigation::promote_an_upcoming_api_method_tracker_to_ongoing(Optional<String> destination_key)
800
0
{
801
    // 1. Assert: navigation's ongoing API method tracker is null.
802
0
    VERIFY(m_ongoing_api_method_tracker == nullptr);
803
804
    // 2. If destinationKey is not null, then:
805
0
    if (destination_key.has_value()) {
806
        // 1. Assert: navigation's upcoming non-traverse API method tracker is null.
807
0
        VERIFY(m_upcoming_non_traverse_api_method_tracker == nullptr);
808
809
        // 2. If navigation's upcoming traverse API method trackers[destinationKey] exists, then:
810
0
        if (auto tracker = m_upcoming_traverse_api_method_trackers.get(destination_key.value()); tracker.has_value()) {
811
            // 1. Set navigation's ongoing API method tracker to navigation's upcoming traverse API method trackers[destinationKey].
812
0
            m_ongoing_api_method_tracker = tracker.value();
813
814
            // 2. Remove navigation's upcoming traverse API method trackers[destinationKey].
815
0
            m_upcoming_traverse_api_method_trackers.remove(destination_key.value());
816
0
        }
817
0
    }
818
819
    // 3. Otherwise:
820
0
    else {
821
        // 1. Set navigation's ongoing API method tracker to navigation's upcoming non-traverse API method tracker.
822
0
        m_ongoing_api_method_tracker = m_upcoming_non_traverse_api_method_tracker;
823
824
        // 2. Set navigation's upcoming non-traverse API method tracker to null.
825
0
        m_upcoming_non_traverse_api_method_tracker = nullptr;
826
0
    }
827
0
}
828
829
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigation-api-method-tracker-clean-up
830
void Navigation::clean_up(JS::NonnullGCPtr<NavigationAPIMethodTracker> api_method_tracker)
831
0
{
832
    // 1. Let navigation be apiMethodTracker's navigation object.
833
0
    VERIFY(api_method_tracker->navigation == this);
834
835
    // 2. If navigation's ongoing API method tracker is apiMethodTracker, then set navigation's ongoing API method tracker to null.
836
0
    if (m_ongoing_api_method_tracker == api_method_tracker) {
837
0
        m_ongoing_api_method_tracker = nullptr;
838
0
    }
839
    // 3. Otherwise:
840
0
    else {
841
        // 1. Let key be apiMethodTracker's key.
842
0
        auto& key = api_method_tracker->key;
843
844
        // 2. Assert: key is not null.
845
0
        VERIFY(key.has_value());
846
847
        // 3. Assert: navigation's upcoming traverse API method trackers[key] exists.
848
0
        VERIFY(m_upcoming_traverse_api_method_trackers.contains(*key));
849
850
        // 4. Remove navigation's upcoming traverse API method trackers[key].
851
0
        m_upcoming_traverse_api_method_trackers.remove(*key);
852
0
    }
853
0
}
854
855
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#resolve-the-finished-promise
856
void Navigation::resolve_the_finished_promise(JS::NonnullGCPtr<NavigationAPIMethodTracker> api_method_tracker)
857
0
{
858
0
    auto& realm = this->realm();
859
860
    // 1. Resolve apiMethodTracker's committed promise with its committed-to entry.
861
    // NOTE: Usually, notify about the committed-to entry has previously been called on apiMethodTracker,
862
    //       and so this will do nothing. However, in some cases resolve the finished promise is called
863
    //       directly, in which case this step is necessary.
864
0
    WebIDL::resolve_promise(realm, api_method_tracker->committed_promise, api_method_tracker->commited_to_entry);
865
866
    // 2. Resolve apiMethodTracker's finished promise with its committed-to entry.
867
0
    WebIDL::resolve_promise(realm, api_method_tracker->finished_promise, api_method_tracker->commited_to_entry);
868
869
    // 3. Clean up apiMethodTracker.
870
0
    clean_up(api_method_tracker);
871
0
}
872
873
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#reject-the-finished-promise
874
void Navigation::reject_the_finished_promise(JS::NonnullGCPtr<NavigationAPIMethodTracker> api_method_tracker, JS::Value exception)
875
0
{
876
0
    auto& realm = this->realm();
877
878
    // 1. Reject apiMethodTracker's committed promise with exception.
879
    // NOTE: This will do nothing if apiMethodTracker's committed promise was previously resolved
880
    //       via notify about the committed-to entry.
881
0
    WebIDL::reject_promise(realm, api_method_tracker->committed_promise, exception);
882
883
    // 2. Reject apiMethodTracker's finished promise with exception.
884
0
    WebIDL::reject_promise(realm, api_method_tracker->finished_promise, exception);
885
886
    // 3. Clean up apiMethodTracker.
887
0
    clean_up(api_method_tracker);
888
0
}
889
890
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#notify-about-the-committed-to-entry
891
void Navigation::notify_about_the_committed_to_entry(JS::NonnullGCPtr<NavigationAPIMethodTracker> api_method_tracker, JS::NonnullGCPtr<NavigationHistoryEntry> nhe)
892
0
{
893
0
    auto& realm = this->realm();
894
895
    // 1. Set apiMethodTracker's committed-to entry to nhe.
896
0
    api_method_tracker->commited_to_entry = nhe;
897
898
    // 2. If apiMethodTracker's serialized state is not null, then set nhe's session history entry's navigation API state to apiMethodTracker's serialized state.'
899
    // NOTE: If it's null, then we're traversing to nhe via navigation.traverseTo(), which does not allow changing the state.
900
0
    if (api_method_tracker->serialized_state.has_value()) {
901
        // NOTE: At this point, apiMethodTracker's serialized state is no longer needed.
902
        //       Implementations might want to clear it out to avoid keeping it alive for the lifetime of the navigation API method tracker.
903
0
        nhe->session_history_entry().set_navigation_api_state(*api_method_tracker->serialized_state);
904
0
        api_method_tracker->serialized_state = {};
905
0
    }
906
907
    // 3. Resolve apiMethodTracker's committed promise with nhe.
908
0
    TemporaryExecutionContext execution_context { relevant_settings_object(*this) };
909
0
    WebIDL::resolve_promise(realm, api_method_tracker->committed_promise, nhe);
910
0
}
911
912
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#inner-navigate-event-firing-algorithm
913
bool Navigation::inner_navigate_event_firing_algorithm(
914
    Bindings::NavigationType navigation_type,
915
    JS::NonnullGCPtr<NavigationDestination> destination,
916
    UserNavigationInvolvement user_involvement,
917
    Optional<Vector<XHR::FormDataEntry>&> form_data_entry_list,
918
    Optional<String> download_request_filename,
919
    Optional<SerializationRecord> classic_history_api_state)
920
0
{
921
    // NOTE: Specification assumes that ongoing navigation event is cancelled before dispatching next navigation event.
922
0
    if (m_ongoing_navigate_event)
923
0
        abort_the_ongoing_navigation();
924
925
0
    auto& realm = relevant_realm(*this);
926
927
    // 1. If navigation has entries and events disabled, then:
928
    // NOTE: These assertions holds because traverseTo(), back(), and forward() will immediately fail when entries and events are disabled
929
    //       (since there are no entries to traverse to), and if our starting point is instead navigate() or reload(),
930
    //       then we avoided setting the upcoming non-traverse API method tracker in the first place.
931
0
    if (has_entries_and_events_disabled()) {
932
        // 1. Assert: navigation's ongoing API method tracker is null.
933
0
        VERIFY(m_ongoing_api_method_tracker == nullptr);
934
935
        // 2. Assert: navigation's upcoming non-traverse API method tracker is null.
936
0
        VERIFY(m_upcoming_non_traverse_api_method_tracker == nullptr);
937
938
        // 3. Assert: navigation's upcoming traverse API method trackers is empty.
939
0
        VERIFY(m_upcoming_traverse_api_method_trackers.is_empty());
940
941
        // 4. Return true.
942
0
        return true;
943
0
    }
944
945
    // 2. Let destinationKey be null.
946
0
    Optional<String> destination_key = {};
947
948
    // 3. If destination's entry is non-null, then set destinationKey to destination's entry's key.
949
0
    if (destination->navigation_history_entry() != nullptr)
950
0
        destination_key = destination->navigation_history_entry()->key();
951
952
    // 4. Assert: destinationKey is not the empty string.
953
0
    VERIFY(destination_key != ""sv);
954
955
    // 5. Promote an upcoming API method tracker to ongoing given navigation and destinationKey.
956
0
    promote_an_upcoming_api_method_tracker_to_ongoing(destination_key);
957
958
    // 6. Let apiMethodTracker be navigation's ongoing API method tracker.
959
0
    auto api_method_tracker = m_ongoing_api_method_tracker;
960
961
    // 7. Let navigable be navigation's relevant global object's navigable.
962
0
    auto& relevant_global_object = verify_cast<HTML::Window>(Web::HTML::relevant_global_object(*this));
963
0
    auto navigable = relevant_global_object.navigable();
964
965
    // 8. Let document be navigation's relevant global object's associated Document.
966
0
    auto& document = relevant_global_object.associated_document();
967
968
    // Note: We create the Event in this algorithm instead of passing it in,
969
    //       and have all the following "initialize" steps set up the event init
970
0
    NavigateEventInit event_init = {};
971
972
    // 9.  If document can have its URL rewritten to destination's URL,
973
    //     and either destination's is same document is true or navigationType is not "traverse",
974
    //     then initialize event's canIntercept to true. Otherwise, initialize it to false.
975
0
    event_init.can_intercept = can_have_its_url_rewritten(document, destination->raw_url()) && (destination->same_document() || navigation_type != Bindings::NavigationType::Traverse);
976
977
    // 10. Let traverseCanBeCanceled be true if all of the following are true:
978
    //      - navigable is a top-level traversable;
979
    //      - destination's is same document is true; and
980
    //      - either userInvolvement is not "browser UI", or navigation's relevant global object has history-action activation.
981
    //     Otherwise, let it be false.
982
0
    bool const traverse_can_be_canceled = navigable->is_top_level_traversable()
983
0
        && destination->same_document()
984
0
        && (user_involvement != UserNavigationInvolvement::BrowserUI || relevant_global_object.has_history_action_activation());
985
986
    // 11. If either:
987
    //      - navigationType is not "traverse"; or
988
    //      - traverseCanBeCanceled is true
989
    //     then initialize event's cancelable to true. Otherwise, initialize it to false.
990
0
    event_init.cancelable = (navigation_type != Bindings::NavigationType::Traverse) || traverse_can_be_canceled;
991
992
    // 12. Initialize event's type to "navigate".
993
    // AD-HOC: Happens later, when calling the factory function
994
995
    // 13. Initialize event's navigationType to navigationType.
996
0
    event_init.navigation_type = navigation_type;
997
998
    // 14. Initialize event's destination to destination.
999
0
    event_init.destination = destination;
1000
1001
    // 15. Initialize event's downloadRequest to downloadRequestFilename.
1002
0
    event_init.download_request = move(download_request_filename);
1003
1004
    // 16. If apiMethodTracker is not null, then initialize event's info to apiMethodTracker's info. Otherwise, initialize it to undefined.
1005
    // NOTE: At this point apiMethodTracker's info is no longer needed and can be nulled out instead of keeping it alive for the lifetime of the navigation API method tracker.
1006
0
    if (api_method_tracker) {
1007
0
        event_init.info = api_method_tracker->info;
1008
0
        api_method_tracker->info = JS::js_undefined();
1009
0
    } else {
1010
0
        event_init.info = JS::js_undefined();
1011
0
    }
1012
1013
    // FIXME: 17: Initialize event's hasUAVisualTransition to true if a visual transition, to display a cached rendered state
1014
    //     of the document's latest entry, was done by the user agent. Otherwise, initialize it to false.
1015
0
    event_init.has_ua_visual_transition = false;
1016
1017
    // 18. Set event's abort controller to a new AbortController created in navigation's relevant realm.
1018
    // AD-HOC: Set on the NavigateEvent later after construction
1019
0
    auto abort_controller = MUST(DOM::AbortController::construct_impl(realm));
1020
1021
    // 19. Initialize event's signal to event's abort controller's signal.
1022
0
    event_init.signal = abort_controller->signal();
1023
1024
    // 20. Let currentURL be document's URL.
1025
0
    auto current_url = document.url();
1026
1027
    // 21. If all of the following are true:
1028
    //  - event's classic history API state is null;
1029
    //  - destination's is same document is true;
1030
    //  - destination's URL equals currentURL with exclude fragments set to true; and
1031
    //  - destination's URL's fragment is not identical to currentURL's fragment,
1032
    //  then initialize event's hashChange to true. Otherwise, initialize it to false.
1033
0
    event_init.hash_change = (!classic_history_api_state.has_value()
1034
0
        && destination->same_document()
1035
0
        && destination->raw_url().equals(current_url, URL::ExcludeFragment::Yes)
1036
0
        && destination->raw_url().fragment() != current_url.fragment());
1037
1038
    // 22. If userInvolvement is not "none", then initialize event's userInitiated to true. Otherwise, initialize it to false.
1039
0
    event_init.user_initiated = user_involvement != UserNavigationInvolvement::None;
1040
1041
    // 23. If formDataEntryList is not null, then initialize event's formData to a new FormData created in navigation's relevant realm,
1042
    //     associated to formDataEntryList. Otherwise, initialize it to null.
1043
0
    if (form_data_entry_list.has_value()) {
1044
0
        event_init.form_data = MUST(XHR::FormData::construct_impl(realm, form_data_entry_list.release_value()));
1045
0
    } else {
1046
0
        event_init.form_data = nullptr;
1047
0
    }
1048
1049
    // AD-HOC: *Now* we have all the info required to create the event
1050
0
    auto event = NavigateEvent::construct_impl(realm, EventNames::navigate, event_init);
1051
0
    event->set_abort_controller(abort_controller);
1052
1053
    // AD-HOC: This is supposed to be set in "fire a <type> navigate event", and is only non-null when
1054
    //         we're doing a push or replace. We set it here because we create the event here
1055
0
    event->set_classic_history_api_state(move(classic_history_api_state));
1056
1057
    // 24. Assert: navigation's ongoing navigate event is null.
1058
0
    VERIFY(m_ongoing_navigate_event == nullptr);
1059
1060
    // 25. Set navigation's ongoing navigate event to event.
1061
0
    m_ongoing_navigate_event = event;
1062
1063
    // 26. Set navigation's focus changed during ongoing navigation to false.
1064
0
    m_focus_changed_during_ongoing_navigation = false;
1065
1066
    // 27. Set navigation's suppress normal scroll restoration during ongoing navigation to false.
1067
0
    m_suppress_scroll_restoration_during_ongoing_navigation = false;
1068
1069
    // 28. Let dispatchResult be the result of dispatching event at navigation.
1070
0
    auto dispatch_result = dispatch_event(*event);
1071
1072
    // 29. If dispatchResult is false:
1073
0
    if (!dispatch_result) {
1074
        // 1. If navigationType is "traverse", then consume history-action user activation given navigation's relevant global object.
1075
0
        if (navigation_type == Bindings::NavigationType::Traverse)
1076
0
            relevant_global_object.consume_history_action_user_activation();
1077
1078
        // 2. If event's abort controller's signal is not aborted, then abort the ongoing navigation given navigation.
1079
0
        if (!event->abort_controller()->signal()->aborted())
1080
0
            abort_the_ongoing_navigation();
1081
1082
        // 3. Return false.
1083
0
        return false;
1084
0
    }
1085
1086
    // 30. Let endResultIsSameDocument be true if event's interception state
1087
    //     is not "none" or event's destination's is same document is true.
1088
0
    bool const end_result_is_same_document = (event->interception_state() != NavigateEvent::InterceptionState::None) || event->destination()->same_document();
1089
1090
    // 31. Prepare to run script given navigation's relevant settings object.
1091
    // NOTE: There's a massive spec note here
1092
0
    TemporaryExecutionContext execution_context { relevant_settings_object(*this), TemporaryExecutionContext::CallbacksEnabled::Yes };
1093
1094
    // 32. If event's interception state is not "none":
1095
0
    if (event->interception_state() != NavigateEvent::InterceptionState::None) {
1096
        // 1. Set event's interception state to "committed".
1097
0
        event->set_interception_state(NavigateEvent::InterceptionState::Committed);
1098
1099
        // 2. Let fromNHE be the current entry of navigation.
1100
0
        auto from_nhe = current_entry();
1101
1102
        // 3. Assert: fromNHE is not null.
1103
0
        VERIFY(from_nhe != nullptr);
1104
1105
        // 4. Set navigation's transition to a new NavigationTransition created in navigation's relevant realm,
1106
        //    whose navigation type is navigationType, from entry is fromNHE, and whose finished promise is a new promise
1107
        //    created in navigation's relevant realm.
1108
0
        m_transition = NavigationTransition::create(realm, navigation_type, *from_nhe, JS::Promise::create(realm));
1109
1110
        // 5. Mark as handled navigation's transition's finished promise.
1111
0
        m_transition->finished()->set_is_handled();
1112
1113
        // 6. If navigationType is "traverse", then set navigation's suppress normal scroll restoration during ongoing navigation to true.
1114
        // NOTE: If event's scroll behavior was set to "after-transition", then scroll restoration will happen as part of finishing
1115
        //       the relevant NavigateEvent. Otherwise, there will be no scroll restoration. That is, no navigation which is intercepted
1116
        //       by intercept() goes through the normal scroll restoration process; scroll restoration for such navigations
1117
        //       is either done manually, by the web developer, or is done after the transition.
1118
0
        if (navigation_type == Bindings::NavigationType::Traverse)
1119
0
            m_suppress_scroll_restoration_during_ongoing_navigation = true;
1120
1121
        // FIXME: Fix spec typo "serialied"
1122
        // 7. If navigationType is "push" or "replace", then run the URL and history update steps given document and
1123
        //    event's destination's URL, with serialiedData set to event's classic history API state and historyHandling
1124
        //    set to navigationType.
1125
0
        if (navigation_type == Bindings::NavigationType::Push || navigation_type == Bindings::NavigationType::Replace) {
1126
0
            auto history_handling = navigation_type == Bindings::NavigationType::Push ? HistoryHandlingBehavior::Push : HistoryHandlingBehavior::Replace;
1127
0
            perform_url_and_history_update_steps(document, event->destination()->raw_url(), event->classic_history_api_state(), history_handling);
1128
0
        }
1129
        // Big spec note about reload here
1130
0
    }
1131
1132
    // 33. If endResultIsSameDocument is true:
1133
0
    if (end_result_is_same_document) {
1134
        // 1. Let promisesList be an empty list.
1135
0
        JS::MarkedVector<JS::NonnullGCPtr<WebIDL::Promise>> promises_list(realm.heap());
1136
1137
        // 2. For each handler of event's navigation handler list:
1138
0
        for (auto const& handler : event->navigation_handler_list()) {
1139
            // 1. Append the result of invoking handler with an empty arguments list to promisesList.
1140
0
            auto result = WebIDL::invoke_callback(handler, {});
1141
            // This *should* be equivalent to converting a promise to a promise capability
1142
0
            promises_list.append(WebIDL::create_resolved_promise(realm, result.value().value()));
1143
0
        }
1144
1145
        // 3. If promisesList's size is 0, then set promisesList to « a promise resolved with undefined ».
1146
        // NOTE: There is a subtle timing difference between how waiting for all schedules its success and failure
1147
        //       steps when given zero promises versus ≥1 promises. For most uses of waiting for all, this does not matter.
1148
        //       However, with this API, there are so many events and promise handlers which could fire around the same time
1149
        //       that the difference is pretty easily observable: it can cause the event/promise handler sequence to vary.
1150
        //       (Some of the events and promises involved include: navigatesuccess / navigateerror, currententrychange,
1151
        //       dispose, apiMethodTracker's promises, and the navigation.transition.finished promise.)
1152
0
        if (promises_list.size() == 0) {
1153
0
            promises_list.append(WebIDL::create_resolved_promise(realm, JS::js_undefined()));
1154
0
        }
1155
1156
        // 4. Wait for all of promisesList, with the following success steps:
1157
0
        WebIDL::wait_for_all(
1158
0
            realm, promises_list, [event, this, api_method_tracker](auto const&) -> void {
1159
1160
                // FIXME: Spec issue: Event's relevant global objects' *associated document*
1161
                // 1. If event's relevant global object is not fully active, then abort these steps.
1162
0
                auto& relevant_global_object = verify_cast<HTML::Window>(HTML::relevant_global_object(*event));
1163
0
                auto& realm = event->realm();
1164
0
                if (!relevant_global_object.associated_document().is_fully_active())
1165
0
                    return;
1166
1167
                // 2. If event's abort controller's signal is aborted, then abort these steps.
1168
0
                if (event->abort_controller()->signal()->aborted())
1169
0
                    return;
1170
1171
                // 3. Assert: event equals navigation's ongoing navigate event.
1172
0
                VERIFY(event == m_ongoing_navigate_event);
1173
1174
                // 4. Set navigation's ongoing navigate event to null.
1175
0
                m_ongoing_navigate_event = nullptr;
1176
1177
                // 5. Finish event given true.
1178
0
                event->finish(true);
1179
1180
                // FIXME: Implement https://dom.spec.whatwg.org/#concept-event-fire somewhere
1181
                // 6. Fire an event named navigatesuccess at navigation.
1182
0
                dispatch_event(DOM::Event::create(realm, EventNames::navigatesuccess));
1183
1184
                // 7. If apiMethodTracker is non-null, then resolve the finished promise for apiMethodTracker.
1185
0
                if (api_method_tracker != nullptr)
1186
0
                    resolve_the_finished_promise(*api_method_tracker);
1187
1188
                // 8. If navigation's transition is not null, then resolve navigation's transition's finished promise with undefined.
1189
0
                if (m_transition != nullptr)
1190
0
                    m_transition->finished()->fulfill(JS::js_undefined());
1191
1192
                // 9. Set navigation's transition to null.
1193
0
                m_transition = nullptr; },
1194
            // and the following failure steps given reason rejectionReason:
1195
0
            [event, this, api_method_tracker](JS::Value rejection_reason) -> void {
1196
                // FIXME: Spec issue: Event's relevant global objects' *associated document*
1197
                // 1. If event's relevant global object is not fully active, then abort these steps.
1198
0
                auto& relevant_global_object = verify_cast<HTML::Window>(HTML::relevant_global_object(*event));
1199
0
                auto& realm = event->realm();
1200
0
                if (!relevant_global_object.associated_document().is_fully_active())
1201
0
                    return;
1202
1203
                // 2. If event's abort controller's signal is aborted, then abort these steps.
1204
0
                if (event->abort_controller()->signal()->aborted())
1205
0
                    return;
1206
1207
                // 3. Assert: event equals navigation's ongoing navigate event.
1208
0
                VERIFY(event == m_ongoing_navigate_event);
1209
1210
                // 4. Set navigation's ongoing navigate event to null.
1211
0
                m_ongoing_navigate_event = nullptr;
1212
1213
                // 5. Finish event given false.
1214
0
                event->finish(false);
1215
1216
                // 6. Let errorInfo be the result of extracting error information from rejectionReason.
1217
0
                ErrorEventInit event_init = {};
1218
0
                event_init.error = rejection_reason;
1219
                // FIXME: Extract information from the exception and the JS context in the wishy-washy way the spec says here.
1220
0
                event_init.filename = String {};
1221
0
                event_init.colno = 0;
1222
0
                event_init.lineno = 0;
1223
0
                event_init.message = String {};
1224
1225
                // 7. Fire an event named navigateerror at navigation using ErrorEvent,with additional attributes initialized according to errorInfo.
1226
0
                dispatch_event(ErrorEvent::create(realm, EventNames::navigateerror, event_init));
1227
1228
                // 8. If apiMethodTracker is non-null, then reject the finished promise for apiMethodTracker with rejectionReason.
1229
0
                if (api_method_tracker != nullptr)
1230
0
                    reject_the_finished_promise(*api_method_tracker, rejection_reason);
1231
1232
                // 9. If navigation's transition is not null, then reject navigation's transition's finished promise with rejectionReason.
1233
0
                if (m_transition)
1234
0
                    m_transition->finished()->reject(rejection_reason);
1235
1236
                // 10. Set navigation's transition to null.
1237
0
                m_transition = nullptr;
1238
0
            });
1239
0
    }
1240
1241
    // 34. Otherwise, if apiMethodTracker is non-null, then clean up apiMethodTracker.
1242
0
    else if (api_method_tracker != nullptr) {
1243
0
        clean_up(*api_method_tracker);
1244
0
    }
1245
1246
    // 35. Clean up after running script given navigation's relevant settings object.
1247
    // Handled by TemporaryExecutionContext destructor from step 31
1248
1249
    // 36. If event's interception state is "none", then return true.
1250
    // 37. Return false.
1251
0
    return event->interception_state() == NavigateEvent::InterceptionState::None;
1252
0
}
1253
1254
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#fire-a-traverse-navigate-event
1255
bool Navigation::fire_a_traverse_navigate_event(JS::NonnullGCPtr<SessionHistoryEntry> destination_she, UserNavigationInvolvement user_involvement)
1256
0
{
1257
0
    auto& realm = relevant_realm(*this);
1258
0
    auto& vm = this->vm();
1259
1260
    // 1. Let event be the result of creating an event given NavigateEvent, in navigation's relevant realm.
1261
    // 2. Set event's classic history API state to null.
1262
    // AD-HOC: These are handled in the inner algorithm
1263
1264
    // 3. Let destination be a new NavigationDestination created in navigation's relevant realm.
1265
0
    auto destination = NavigationDestination::create(realm);
1266
1267
    // 4. Set destination's URL to destinationSHE's URL.
1268
0
    destination->set_url(destination_she->url());
1269
1270
    // 5. Let destinationNHE be the NavigationHistoryEntry in navigation's entry list whose session history entry is destinationSHE,
1271
    //    or null if no such NavigationHistoryEntry exists.
1272
0
    auto destination_nhe = m_entry_list.find_if([destination_she](auto& nhe) {
1273
0
        return &nhe->session_history_entry() == destination_she;
1274
0
    });
1275
1276
    // 6. If destinationNHE is non-null, then:
1277
0
    if (destination_nhe != m_entry_list.end()) {
1278
        // 1. Set destination's entry to destinationNHE.
1279
0
        destination->set_entry(*destination_nhe);
1280
1281
        // 2. Set destination's state to destinationSHE's navigation API state.
1282
0
        destination->set_state(destination_she->navigation_api_state());
1283
0
    }
1284
1285
    // 7. Otherwise:
1286
0
    else {
1287
        // 1. Set destination's entry to null.
1288
0
        destination->set_entry(nullptr);
1289
1290
        // 2. Set destination's state to StructuredSerializeForStorage(null).
1291
0
        destination->set_state(MUST(structured_serialize_for_storage(vm, JS::js_null())));
1292
0
    }
1293
1294
    // 8. Set destination's is same document to true if destinationSHE's document is equal to
1295
    //    navigation's relevant global object's associated Document; otherwise false.
1296
0
    destination->set_is_same_document(destination_she->document() == &verify_cast<Window>(relevant_global_object(*this)).associated_document());
1297
1298
    // 9. Return the result of performing the inner navigate event firing algorithm given navigation, "traverse", event, destination, userInvolvement, null, and null.
1299
    // AD-HOC: We don't pass the event, but we do pass the classic_history_api state at the end to be set later
1300
0
    return inner_navigate_event_firing_algorithm(Bindings::NavigationType::Traverse, destination, user_involvement, {}, {}, {});
1301
0
}
1302
1303
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#fire-a-push/replace/reload-navigate-event
1304
bool Navigation::fire_a_push_replace_reload_navigate_event(
1305
    Bindings::NavigationType navigation_type,
1306
    URL::URL destination_url,
1307
    bool is_same_document,
1308
    UserNavigationInvolvement user_involvement,
1309
    Optional<Vector<XHR::FormDataEntry>&> form_data_entry_list,
1310
    Optional<SerializationRecord> navigation_api_state,
1311
    Optional<SerializationRecord> classic_history_api_state)
1312
0
{
1313
0
    auto& realm = relevant_realm(*this);
1314
0
    auto& vm = this->vm();
1315
1316
    // This fulfills the entry requirement: an optional serialized state navigationAPIState (default StructuredSerializeForStorage(null))
1317
0
    if (!navigation_api_state.has_value())
1318
0
        navigation_api_state = MUST(structured_serialize_for_storage(vm, JS::js_null()));
1319
1320
    // 1. Let event be the result of creating an event given NavigateEvent, in navigation's relevant realm.
1321
    // 2. Set event's classic history API state to classicHistoryAPIState.
1322
    // AD-HOC: These are handled in the inner algorithm
1323
1324
    // 3. Let destination be a new NavigationDestination created in navigation's relevant realm.
1325
0
    auto destination = NavigationDestination::create(realm);
1326
1327
    // 4. Set destination's URL to destinationURL.
1328
0
    destination->set_url(destination_url);
1329
1330
    // 5. Set destination's entry to null.
1331
0
    destination->set_entry(nullptr);
1332
1333
    // 6. Set destination's state to navigationAPIState.
1334
0
    destination->set_state(*navigation_api_state);
1335
1336
    // 7. Set destination's is same document to isSameDocument.
1337
0
    destination->set_is_same_document(is_same_document);
1338
1339
    // 8. Return the result of performing the inner navigate event firing algorithm given navigation,
1340
    //    navigationType, event, destination, userInvolvement, formDataEntryList, and null.
1341
    // AD-HOC: We don't pass the event, but we do pass the classic_history_api state at the end to be set later
1342
0
    return inner_navigate_event_firing_algorithm(navigation_type, destination, user_involvement, move(form_data_entry_list), {}, move(classic_history_api_state));
1343
0
}
1344
1345
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#fire-a-download-request-navigate-event
1346
bool Navigation::fire_a_download_request_navigate_event(URL::URL destination_url, UserNavigationInvolvement user_involvement, String filename)
1347
0
{
1348
0
    auto& realm = relevant_realm(*this);
1349
0
    auto& vm = this->vm();
1350
1351
    // 1. Let event be the result of creating an event given NavigateEvent, in navigation's relevant realm.
1352
    // 2. Set event's classic history API state to classicHistoryAPIState.
1353
    // AD-HOC: These are handled in the inner algorithm
1354
1355
    // 3. Let destination be a new NavigationDestination created in navigation's relevant realm.
1356
0
    auto destination = NavigationDestination::create(realm);
1357
1358
    // 4. Set destination's URL to destinationURL.
1359
0
    destination->set_url(destination_url);
1360
1361
    // 5. Set destination's entry to null.
1362
0
    destination->set_entry(nullptr);
1363
1364
    // 6. Set destination's state to StructuredSerializeForStorage(null).
1365
0
    destination->set_state(MUST(structured_serialize_for_storage(vm, JS::js_null())));
1366
1367
    // 7. Set destination's is same document to false.
1368
0
    destination->set_is_same_document(false);
1369
1370
    // 8. Return the result of performing the inner navigate event firing algorithm given navigation,
1371
    //   "push", event, destination, userInvolvement, null, and filename.
1372
    // AD-HOC: We don't pass the event, but we do pass the classic_history_api state at the end to be set later
1373
0
    return inner_navigate_event_firing_algorithm(Bindings::NavigationType::Push, destination, user_involvement, {}, move(filename), {});
1374
0
}
1375
1376
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#initialize-the-navigation-api-entries-for-a-new-document
1377
void Navigation::initialize_the_navigation_api_entries_for_a_new_document(Vector<JS::NonnullGCPtr<SessionHistoryEntry>> const& new_shes, JS::NonnullGCPtr<SessionHistoryEntry> initial_she)
1378
0
{
1379
0
    auto& realm = relevant_realm(*this);
1380
1381
    // 1. Assert: navigation's entry list is empty.
1382
0
    VERIFY(m_entry_list.is_empty());
1383
1384
    // 2. Assert: navigation's current entry index is −1.
1385
0
    VERIFY(m_current_entry_index == -1);
1386
1387
    // 3. If navigation has entries and events disabled, then return.
1388
0
    if (has_entries_and_events_disabled())
1389
0
        return;
1390
1391
    // 4. For each newSHE of newSHEs:
1392
0
    for (auto const& new_she : new_shes) {
1393
        // 1. Let newNHE be a new NavigationHistoryEntry created in the relevant realm of navigation.
1394
        // 2. Set newNHE's session history entry to newSHE.
1395
0
        auto new_nhe = NavigationHistoryEntry::create(realm, new_she);
1396
1397
        // 3. Append newNHE to navigation's entry list.
1398
0
        m_entry_list.append(new_nhe);
1399
0
    }
1400
1401
    // 5. Set navigation's current entry index to the result of getting the navigation API entry index of initialSHE within navigation.
1402
0
    m_current_entry_index = get_the_navigation_api_entry_index(*initial_she);
1403
0
}
1404
1405
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#update-the-navigation-api-entries-for-a-same-document-navigation
1406
void Navigation::update_the_navigation_api_entries_for_a_same_document_navigation(JS::NonnullGCPtr<SessionHistoryEntry> destination_she, Bindings::NavigationType navigation_type)
1407
0
{
1408
0
    auto& realm = relevant_realm(*this);
1409
1410
    // 1. If navigation has entries and events disabled, then return.
1411
0
    if (has_entries_and_events_disabled())
1412
0
        return;
1413
1414
    // 2. Let oldCurrentNHE be the current entry of navigation.
1415
0
    auto old_current_nhe = current_entry();
1416
1417
    // 3. Let disposedNHEs be a new empty list.
1418
0
    Vector<JS::NonnullGCPtr<NavigationHistoryEntry>> disposed_nhes;
1419
1420
    // 4. If navigationType is "traverse", then:
1421
0
    if (navigation_type == Bindings::NavigationType::Traverse) {
1422
        // 1. Set navigation's current entry index to the result of getting the navigation API entry index of destinationSHE within navigation.
1423
0
        m_current_entry_index = get_the_navigation_api_entry_index(destination_she);
1424
1425
        // 2. Assert: navigation's current entry index is not −1.
1426
        // NOTE: This algorithm is only called for same-document traversals.
1427
        //       Cross-document traversals will instead call either initialize the navigation API entries for a new document
1428
        //       or update the navigation API entries for reactivation
1429
0
        VERIFY(m_current_entry_index != -1);
1430
0
    }
1431
1432
    // 5. Otherwise, if navigationType is "push", then:
1433
0
    else if (navigation_type == Bindings::NavigationType::Push) {
1434
        // 1. Set navigation's current entry index to navigation's current entry index + 1.
1435
0
        m_current_entry_index++;
1436
1437
        // 2. Let i be navigation's current entry index.
1438
0
        auto i = m_current_entry_index;
1439
1440
        // 3. While i < navigation's entry list's size:
1441
0
        while (i < static_cast<i64>(m_entry_list.size())) {
1442
            // 1. Append navigation's entry list[i] to disposedNHEs.
1443
0
            disposed_nhes.append(m_entry_list[i]);
1444
1445
            // 2. Set i to i + 1.
1446
0
            ++i;
1447
0
        }
1448
1449
        // 4. Remove all items in disposedNHEs from navigation's entry list.
1450
0
        m_entry_list.remove(m_current_entry_index, m_entry_list.size() - m_current_entry_index);
1451
0
    }
1452
1453
    // 6. Otherwise, if navigationType is "replace", then:
1454
0
    else if (navigation_type == Bindings::NavigationType::Replace) {
1455
0
        VERIFY(old_current_nhe != nullptr);
1456
1457
        // 1. Append oldCurrentNHE to disposedNHEs.
1458
0
        disposed_nhes.append(*old_current_nhe);
1459
0
    }
1460
1461
    // 7. If navigationType is "push" or "replace", then:
1462
0
    if (navigation_type == Bindings::NavigationType::Push || navigation_type == Bindings::NavigationType::Replace) {
1463
        // 1. Let newNHE be a new NavigationHistoryEntry created in the relevant realm of navigation.
1464
        // 2. Set newNHE's session history entry to destinationSHE.
1465
0
        auto new_nhe = NavigationHistoryEntry::create(realm, destination_she);
1466
1467
0
        VERIFY(m_current_entry_index != -1);
1468
1469
        // 3. Set navigation's entry list[navigation's current entry index] to newNHE.
1470
0
        if (m_current_entry_index < static_cast<i64>(m_entry_list.size()))
1471
0
            m_entry_list[m_current_entry_index] = new_nhe;
1472
0
        else {
1473
0
            VERIFY(m_current_entry_index == static_cast<i64>(m_entry_list.size()));
1474
0
            m_entry_list.append(new_nhe);
1475
0
        }
1476
0
    }
1477
1478
    // 8. If navigation's ongoing API method tracker is non-null, then notify about the committed-to entry
1479
    //    given navigation's ongoing API method tracker and the current entry of navigation.
1480
    // NOTE: It is important to do this before firing the dispose or currententrychange events,
1481
    //       since event handlers could start another navigation, or otherwise change the value of
1482
    //       navigation's ongoing API method tracker.
1483
0
    if (m_ongoing_api_method_tracker != nullptr)
1484
0
        notify_about_the_committed_to_entry(*m_ongoing_api_method_tracker, *current_entry());
1485
1486
    // 9. Prepare to run script given navigation's relevant settings object.
1487
0
    relevant_settings_object(*this).prepare_to_run_script();
1488
1489
    // 10. Fire an event named currententrychange at navigation using NavigationCurrentEntryChangeEvent,
1490
    //     with its navigationType attribute initialized to navigationType and its from initialized to oldCurrentNHE.
1491
0
    NavigationCurrentEntryChangeEventInit event_init = {};
1492
0
    event_init.navigation_type = navigation_type;
1493
0
    event_init.from = old_current_nhe;
1494
0
    dispatch_event(NavigationCurrentEntryChangeEvent::construct_impl(realm, EventNames::currententrychange, event_init));
1495
1496
    // 11. For each disposedNHE of disposedNHEs:
1497
0
    for (auto& disposed_nhe : disposed_nhes) {
1498
        // 1. Fire an event named dispose at disposedNHE.
1499
0
        disposed_nhe->dispatch_event(DOM::Event::create(realm, EventNames::dispose, {}));
1500
0
    }
1501
1502
    // 12. Clean up after running script given navigation's relevant settings object.
1503
0
    relevant_settings_object(*this).clean_up_after_running_script();
1504
0
}
1505
1506
}