-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
/
TraversableNavigable.cpp
1170 lines (927 loc) Β· 61.2 KB
/
TraversableNavigable.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/QuickSort.h>
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/HTML/BrowsingContextGroup.h>
#include <LibWeb/HTML/DocumentState.h>
#include <LibWeb/HTML/Navigation.h>
#include <LibWeb/HTML/NavigationParams.h>
#include <LibWeb/HTML/Parser/HTMLParser.h>
#include <LibWeb/HTML/SessionHistoryEntry.h>
#include <LibWeb/HTML/TraversableNavigable.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/Page/Page.h>
#include <LibWeb/Platform/EventLoopPlugin.h>
namespace Web::HTML {
JS_DEFINE_ALLOCATOR(TraversableNavigable);
TraversableNavigable::TraversableNavigable(JS::NonnullGCPtr<Page> page)
: Navigable(page)
, m_session_history_traversal_queue(vm().heap().allocate_without_realm<SessionHistoryTraversalQueue>())
{
}
TraversableNavigable::~TraversableNavigable() = default;
void TraversableNavigable::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_session_history_entries);
visitor.visit(m_session_history_traversal_queue);
}
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
WebIDL::ExceptionOr<BrowsingContextAndDocument> create_a_new_top_level_browsing_context_and_document(JS::NonnullGCPtr<Page> page)
{
// 1. Let group and document be the result of creating a new browsing context group and document.
auto [group, document] = TRY(BrowsingContextGroup::create_a_new_browsing_context_group_and_document(page));
// 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
WebIDL::ExceptionOr<JS::NonnullGCPtr<TraversableNavigable>> TraversableNavigable::create_a_new_top_level_traversable(JS::NonnullGCPtr<Page> page, JS::GCPtr<HTML::BrowsingContext> opener, String target_name)
{
auto& vm = Bindings::main_thread_vm();
// 1. Let document be null.
JS::GCPtr<DOM::Document> document = nullptr;
// 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) {
document = TRY(create_a_new_top_level_browsing_context_and_document(page)).document;
}
// 3. Otherwise, set document to the second return value of creating a new auxiliary browsing context and document given opener.
else {
document = TRY(BrowsingContext::create_a_new_auxiliary_browsing_context_and_document(page, *opener)).document;
}
// 4. Let documentState be a new document state, with
auto document_state = vm.heap().allocate_without_realm<DocumentState>();
// document: document
document_state->set_document(document);
// initiator origin: null if opener is null; otherwise, document's origin
document_state->set_initiator_origin(opener ? Optional<Origin> {} : document->origin());
// origin: document's origin
document_state->set_origin(document->origin());
// navigable target name: targetName
document_state->set_navigable_target_name(target_name);
// about base URL: document's about base URL
document_state->set_about_base_url(document->about_base_url());
// 5. Let traversable be a new traversable navigable.
auto traversable = vm.heap().allocate_without_realm<TraversableNavigable>(page);
// 6. Initialize the navigable traversable given documentState.
TRY_OR_THROW_OOM(vm, traversable->initialize_navigable(document_state, nullptr));
// 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.
initial_history_entry->set_step(0);
// 9. Append initialHistoryEntry to traversable's session history entries.
traversable->m_session_history_entries.append(*initial_history_entry);
// 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
WebIDL::ExceptionOr<JS::NonnullGCPtr<TraversableNavigable>> TraversableNavigable::create_a_fresh_top_level_traversable(JS::NonnullGCPtr<Page> page, URL::URL const& initial_navigation_url, Variant<Empty, String, POSTResource> initial_navigation_post_resource)
{
// 1. Let traversable be the result of creating a new top-level traversable given null and the empty string.
auto traversable = TRY(create_a_new_top_level_traversable(page, nullptr, {}));
page->set_top_level_traversable(traversable);
// 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)) {
Platform::EventLoopPlugin::the().deferred_invoke([traversable, initial_navigation_url] {
// 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.
});
}
else {
// 2. Navigate traversable to initialNavigationURL using traversable's active document, with documentResource set to initialNavigationPostResource.
TRY(traversable->navigate({ .url = initial_navigation_url,
.source_document = *traversable->active_document(),
.document_resource = initial_navigation_post_resource }));
}
// 3. Return traversable.
return traversable;
}
// 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;
}
// 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 Β».
Vector<Vector<JS::NonnullGCPtr<SessionHistoryEntry>>> entry_lists { session_history_entries() };
// 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.
steps.set(entry->step().get<int>());
// 2. For each nestedHistory of entry's document state's nested histories, append nestedHistory's entries list to entryLists.
for (auto& nested_history : entry->document_state()->nested_histories())
entry_lists.append(nested_history.entries);
}
}
// 5. Return steps, sorted.
auto sorted_steps = steps.values();
quick_sort(sorted_steps);
return sorted_steps;
}
// 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
};
}
// 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();
}
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#get-all-navigables-whose-current-session-history-entry-will-change-or-reload
Vector<JS::Handle<Navigable>> TraversableNavigable::get_all_navigables_whose_current_session_history_entry_will_change_or_reload(int target_step) const
{
// 1. Let results be an empty list.
Vector<JS::Handle<Navigable>> results;
// 2. Let navigablesToCheck be Β« traversable Β».
Vector<JS::Handle<Navigable>> navigables_to_check;
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 not navigable's current session history entry or targetEntry's document state's reload pending is true, then append navigable to results.
if (target_entry != navigable->current_session_history_entry() || target_entry->document_state()->reload_pending()) {
results.append(*navigable);
}
// 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.
if (target_entry->document() == navigable->active_document() && !target_entry->document_state()->reload_pending()) {
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-only-need-history-object-length/index-update
Vector<JS::Handle<Navigable>> TraversableNavigable::get_all_navigables_that_only_need_history_object_length_index_update(int target_step) const
{
// 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.
Vector<JS::Handle<Navigable>> results;
// 2. Let navigablesToCheck be Β« traversable Β».
Vector<JS::Handle<Navigable>> navigables_to_check;
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:
if (target_entry == navigable->current_session_history_entry() && !target_entry->document_state()->reload_pending()) {
// 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
Vector<JS::Handle<Navigable>> TraversableNavigable::get_all_navigables_that_might_experience_a_cross_document_traversal(int target_step) const
{
// 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.
Vector<JS::Handle<Navigable>> results;
// 2. Let navigablesToCheck be Β« traversable Β».
Vector<JS::Handle<Navigable>> navigables_to_check;
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.
if (target_entry->document() != navigable->active_document() || target_entry->document_state()->reload_pending()) {
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;
}
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#deactivate-a-document-for-a-cross-document-navigation
static void deactivate_a_document_for_cross_document_navigation(JS::NonnullGCPtr<DOM::Document> displayed_document, Optional<UserNavigationInvolvement>, JS::NonnullGCPtr<SessionHistoryEntry> target_entry, JS::NonnullGCPtr<JS::HeapFunction<void()>> after_potential_unloads)
{
// 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) {
// FIXME 1. Let firePageSwapBeforeUnload be the following step
// 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.
displayed_document->unload_a_document_and_its_descendants(target_entry->document(), after_potential_unloads);
}
// 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();
}
}
struct ChangingNavigableContinuationState : public JS::Cell {
JS_CELL(ChangingNavigableContinuationState, JS::Cell);
JS_DECLARE_ALLOCATOR(ChangingNavigableContinuationState);
JS::GCPtr<DOM::Document> displayed_document;
JS::GCPtr<SessionHistoryEntry> target_entry;
JS::GCPtr<Navigable> navigable;
bool update_only = false;
JS::GCPtr<SessionHistoryEntry> populated_target_entry;
bool populated_cloned_target_session_history_entry = false;
virtual void visit_edges(Cell::Visitor& visitor) override
{
Base::visit_edges(visitor);
visitor.visit(displayed_document);
visitor.visit(target_entry);
visitor.visit(navigable);
visitor.visit(populated_target_entry);
}
};
JS_DEFINE_ALLOCATOR(ChangingNavigableContinuationState);
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#apply-the-history-step
TraversableNavigable::HistoryStepResult TraversableNavigable::apply_the_history_step(
int step,
bool check_for_cancelation,
IGNORE_USE_IN_ESCAPING_LAMBDA Optional<SourceSnapshotParams> source_snapshot_params,
JS::GCPtr<Navigable> initiator_to_check,
Optional<UserNavigationInvolvement> user_involvement_for_navigate_events,
IGNORE_USE_IN_ESCAPING_LAMBDA Optional<Bindings::NavigationType> navigation_type,
IGNORE_USE_IN_ESCAPING_LAMBDA SynchronousNavigation synchronous_navigation)
{
auto& vm = this->vm();
// FIXME: 1. Assert: This is running within traversable's session history traversal queue.
// 2. Let targetStep be the result of getting the used step given traversable and step.
auto target_step = get_the_used_step(step);
// Note: Calling this early so we can re-use the same list in 3.2 and 6.
auto change_or_reload_navigables = get_all_navigables_whose_current_session_history_entry_will_change_or_reload(target_step);
// 3. If initiatorToCheck is not null, then:
if (initiator_to_check != nullptr) {
// 1. Assert: sourceSnapshotParams is not null.
VERIFY(source_snapshot_params.has_value());
// 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 : change_or_reload_navigables) {
if (!initiator_to_check->allowed_by_sandboxing_to_navigate(*navigable, *source_snapshot_params))
return HistoryStepResult::InitiatorDisallowed;
}
}
// 4. Let navigablesCrossingDocuments be the result of getting all navigables that might experience a cross-document traversal given traversable and targetStep.
[[maybe_unused]] auto navigables_crossing_documents = get_all_navigables_that_might_experience_a_cross_document_traversal(target_step);
// 5. FIXME: If checkForCancelation is true, and the result of checking if unloading is canceled given navigablesCrossingDocuments, traversable, targetStep,
// and userInvolvementForNavigateEvents is not "continue", then return that result.
(void)check_for_cancelation;
// 6. Let changingNavigables be the result of get all navigables whose current session history entry will change or reload given traversable and targetStep.
auto changing_navigables = move(change_or_reload_navigables);
// 7. Let nonchangingNavigablesThatStillNeedUpdates be the result of getting all navigables that only need history object length/index update given traversable and targetStep.
auto non_changing_navigables_that_still_need_updates = get_all_navigables_that_only_need_history_object_length_index_update(target_step);
// 8. For each navigable of changingNavigables:
for (auto& navigable : changing_navigables) {
// 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. 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".
navigable->set_ongoing_navigation(Traversal::Tag);
}
// 9. Let totalChangeJobs be the size of changingNavigables.
auto total_change_jobs = changing_navigables.size();
// 10. Let completedChangeJobs be 0.
IGNORE_USE_IN_ESCAPING_LAMBDA size_t completed_change_jobs = 0;
// 11. Let changingNavigableContinuations be an empty queue of changing navigable continuation states.
// NOTE: This queue is used to split the operations on changingNavigables into two parts. Specifically, changingNavigableContinuations holds data for the second part.
IGNORE_USE_IN_ESCAPING_LAMBDA Queue<JS::Handle<ChangingNavigableContinuationState>> changing_navigable_continuations;
// 12. For each navigable of changingNavigables, queue a global task on the navigation and traversal task source of navigable's active window to run the steps:
for (auto& navigable : changing_navigables) {
queue_global_task(Task::Source::NavigationAndTraversal, *navigable->active_window(), JS::create_heap_function(heap(), [&] {
// NOTE: This check is not in the spec but we should not continue navigation if navigable has been destroyed.
if (navigable->has_been_destroyed()) {
completed_change_jobs++;
return;
}
// 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:
auto changing_navigable_continuation = vm.heap().allocate_without_realm<ChangingNavigableContinuationState>();
changing_navigable_continuation->displayed_document = displayed_entry->document();
changing_navigable_continuation->target_entry = target_entry;
changing_navigable_continuation->navigable = navigable;
changing_navigable_continuation->update_only = false;
changing_navigable_continuation->populated_target_entry = nullptr;
changing_navigable_continuation->populated_cloned_target_session_history_entry = false;
// 4. If displayedEntry is targetEntry and targetEntry's document state's reload pending is false, then:
if (synchronous_navigation == SynchronousNavigation::Yes && !target_entry->document_state()->reload_pending()) {
// 1. Set changingNavigableContinuation's update-only to true.
changing_navigable_continuation->update_only = true;
// 2. Enqueue changingNavigableContinuation on changingNavigableContinuations.
changing_navigable_continuations.enqueue(move(changing_navigable_continuation));
// 3. Abort these steps.
return;
}
// 5. Switch on navigationType:
if (navigation_type.has_value()) {
switch (navigation_type.value()) {
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.
VERIFY(target_entry->step().get<int>() > displayed_entry->step().get<int>());
break;
}
}
// 6. Let oldOrigin be targetEntry's document state's origin.
auto old_origin = target_entry->document_state()->origin();
// FIXME: 7. If navigable is not traversable, and 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 fire a traverse navigate event given targetEntry and userInvolvementForNavigateEvents.
auto after_document_populated = [old_origin, changing_navigable_continuation, &changing_navigable_continuations, &vm, &navigable](bool populated_cloned_target_she, JS::NonnullGCPtr<SessionHistoryEntry> target_entry) mutable {
changing_navigable_continuation->populated_target_entry = target_entry;
changing_navigable_continuation->populated_cloned_target_session_history_entry = populated_cloned_target_she;
// 1. If targetEntry's document is null, then set changingNavigableContinuation's update-only to true.
if (!target_entry->document()) {
changing_navigable_continuation->update_only = true;
}
else {
// 2. If targetEntry's document's origin is not oldOrigin, then set targetEntry's classic history API state to StructuredSerializeForStorage(null).
if (target_entry->document()->origin() != old_origin) {
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
&& target_entry->document()->browsing_context()->opener_browsing_context() == nullptr
&& target_entry->document_state()->origin() != old_origin) {
target_entry->document_state()->set_navigable_target_name(String {});
}
}
// 4. Enqueue changingNavigableContinuation on changingNavigableContinuations.
changing_navigable_continuations.enqueue(move(changing_navigable_continuation));
};
// 8. If targetEntry's document is null, or targetEntry's document state's reload pending is true, then:
if (!target_entry->document() || target_entry->document_state()->reload_pending()) {
// FIXME: 1. Let navTimingType be "back_forward" if targetEntry's document is null; otherwise "reload".
// 2. Let targetSnapshotParams be the result of snapshotting target snapshot params given navigable.
auto target_snapshot_params = navigable->snapshot_target_snapshot_params();
// 3. Let potentiallyTargetSpecificSourceSnapshotParams be sourceSnapshotParams.
Optional<SourceSnapshotParams> potentially_target_specific_source_snapshot_params = source_snapshot_params;
// 4. If potentiallyTargetSpecificSourceSnapshotParams is null, then set it to the result of snapshotting source snapshot params given navigable's active document.
if (!potentially_target_specific_source_snapshot_params.has_value()) {
potentially_target_specific_source_snapshot_params = navigable->active_document()->snapshot_source_snapshot_params();
}
// 5. Set targetEntry's document state's reload pending to false.
target_entry->document_state()->set_reload_pending(false);
// 6. Let allowPOST be targetEntry's document state's reload pending.
auto allow_POST = target_entry->document_state()->reload_pending();
// https://github.com/whatwg/html/issues/9869
// Reloading requires population of the active session history entry, making it inactive.
// This results in a situation where tasks that unload the previous document and activate a new
// document cannot run. To resolve this, the target entry is cloned before it is populated.
// After the unloading of the previous document is completed, all fields potentially affected by the
// population are copied from the cloned target entry to the actual target entry.
auto populated_target_entry = target_entry->clone();
// 7. In parallel, attempt to populate the history entry's document for targetEntry, given navigable, potentiallyTargetSpecificSourceSnapshotParams,
// targetSnapshotParams, 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.
Platform::EventLoopPlugin::the().deferred_invoke([populated_target_entry, potentially_target_specific_source_snapshot_params, target_snapshot_params, this, allow_POST, navigable, after_document_populated = JS::create_heap_function(this->heap(), move(after_document_populated))] {
navigable->populate_session_history_entry_document(populated_target_entry, *potentially_target_specific_source_snapshot_params, target_snapshot_params, {}, Empty {}, CSPNavigationType::Other, allow_POST, [this, after_document_populated, populated_target_entry]() mutable {
queue_global_task(Task::Source::NavigationAndTraversal, *active_window(), JS::create_heap_function(this->heap(), [after_document_populated, populated_target_entry]() mutable {
after_document_populated->function()(true, populated_target_entry);
}));
})
.release_value_but_fixme_should_propagate_errors();
});
}
// Otherwise, run afterDocumentPopulated immediately.
else {
after_document_populated(false, *target_entry);
}
}));
}
auto check_if_document_population_tasks_completed = JS::SafeFunction<bool()>([&] {
return changing_navigable_continuations.size() + completed_change_jobs == total_change_jobs;
});
if (synchronous_navigation == SynchronousNavigation::Yes) {
// NOTE: Synchronous navigation should never require document population, so it is safe to process only NavigationAndTraversal source.
main_thread_event_loop().spin_processing_tasks_with_source_until(Task::Source::NavigationAndTraversal, move(check_if_document_population_tasks_completed));
} else {
// NOTE: Process all task sources while waiting because reloading or back/forward navigation might require fetching to populate a document.
main_thread_event_loop().spin_until(move(check_if_document_population_tasks_completed));
}
// 13. Let navigablesThatMustWaitBeforeHandlingSyncNavigation be an empty set.
Vector<JS::GCPtr<Navigable>> navigables_that_must_wait_before_handling_sync_navigation;
// 14. While completedChangeJobs does not equal totalChangeJobs:
while (!changing_navigable_continuations.is_empty()) {
// 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:
if (!m_running_nested_apply_history_step) {
// 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.
for (auto entry = m_session_history_traversal_queue->first_synchronous_navigation_steps_with_target_navigable_not_contained_in(navigables_that_must_wait_before_handling_sync_navigation);
entry;
entry = m_session_history_traversal_queue->first_synchronous_navigation_steps_with_target_navigable_not_contained_in(navigables_that_must_wait_before_handling_sync_navigation)) {
// 3. Set traversable's running nested apply history step to true.
m_running_nested_apply_history_step = true;
// 4. Run steps.
entry->execute_steps();
// 5. Set traversable's running nested apply history step to false.
m_running_nested_apply_history_step = false;
}
}
// 2. Let changingNavigableContinuation be the result of dequeuing from changingNavigableContinuations.
auto changing_navigable_continuation = changing_navigable_continuations.dequeue();
// 3. If changingNavigableContinuation is nothing, then continue.
// 4. Let displayedDocument be changingNavigableContinuation's displayed document.
auto displayed_document = changing_navigable_continuation->displayed_document;
// 5. Let targetEntry be changingNavigableContinuation's target entry.
JS::GCPtr<SessionHistoryEntry> const populated_target_entry = changing_navigable_continuation->populated_target_entry;
// 6. Let navigable be changingNavigableContinuation's navigable.
auto navigable = changing_navigable_continuation->navigable;
// NOTE: This check is not in the spec but we should not continue navigation if navigable has been destroyed.
if (navigable->has_been_destroyed())
continue;
// 7. Let (scriptHistoryLength, scriptHistoryIndex) be the result of getting the history object length and index given traversable and targetStep.
auto history_object_length_and_index = get_the_history_object_length_and_index(target_step);
auto script_history_length = history_object_length_and_index.script_history_length;
auto script_history_index = history_object_length_and_index.script_history_index;
// 8. Append navigable to navigablesThatMustWaitBeforeHandlingSyncNavigation.
navigables_that_must_wait_before_handling_sync_navigation.append(*navigable);
// 9. Let entriesForNavigationAPI be the result of getting session history entries for the navigation API given navigable and targetStep.
auto entries_for_navigation_api = get_session_history_entries_for_the_navigation_api(*navigable, target_step);
// 12. In both cases, let afterPotentialUnloads be the following steps:
bool const update_only = changing_navigable_continuation->update_only;
JS::GCPtr<SessionHistoryEntry> const target_entry = changing_navigable_continuation->target_entry;
bool const populated_cloned_target_session_history_entry = changing_navigable_continuation->populated_cloned_target_session_history_entry;
auto after_potential_unload = JS::create_heap_function(this->heap(), [navigable, update_only, target_entry, populated_target_entry, populated_cloned_target_session_history_entry, displayed_document, &completed_change_jobs, script_history_length, script_history_index, entries_for_navigation_api = move(entries_for_navigation_api), &heap = this->heap()] {
if (populated_cloned_target_session_history_entry) {
target_entry->set_document_state(populated_target_entry->document_state());
target_entry->set_url(populated_target_entry->url());
target_entry->set_classic_history_api_state(populated_target_entry->classic_history_api_state());
}
// 1. If changingNavigableContinuation's update-only is false, then activate history entry targetEntry for navigable.
if (!update_only)
navigable->activate_history_entry(*target_entry);
// 2. Let updateDocument be an algorithm step which performs update document for history step application given
// targetEntry's document, targetEntry, changingNavigableContinuation's update-only, scriptHistoryLength,
// scriptHistoryIndex, navigationType, entriesForNavigationAPI, and displayedEntry.
auto update_document = [script_history_length, script_history_index, entries_for_navigation_api = move(entries_for_navigation_api), target_entry, update_only] {
target_entry->document()->update_for_history_step_application(*target_entry, update_only, script_history_length, script_history_index, entries_for_navigation_api);
};
// 3. If targetEntry's document is equal to displayedDocument, then perform updateDocument.
if (target_entry->document().ptr() == displayed_document.ptr()) {
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 {
queue_global_task(Task::Source::NavigationAndTraversal, relevant_global_object(*target_entry->document()), JS::create_heap_function(heap, move(update_document)));
}
// 6. Increment completedChangeJobs.
completed_change_jobs++;
});
// 10. If changingNavigableContinuation's update-only is true, or targetEntry's document is displayedDocument, then:
if (changing_navigable_continuation->update_only || populated_target_entry->document().ptr() == displayed_document.ptr()) {
// 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.
queue_global_task(Task::Source::NavigationAndTraversal, *navigable->active_window(), after_potential_unload);
}
// 11. Otherwise:
else {
// 1. Assert: navigationType is not null.
VERIFY(navigation_type.has_value());
// 2. Deactivate displayedDocument, given userNavigationInvolvement, targetEntry, navigationType, and afterPotentialUnloads.
deactivate_a_document_for_cross_document_navigation(*displayed_document, user_involvement_for_navigate_events, *populated_target_entry, after_potential_unload);
}
}
main_thread_event_loop().spin_processing_tasks_with_source_until(Task::Source::NavigationAndTraversal, [&] {
return completed_change_jobs == total_change_jobs;
});
// 15. Let totalNonchangingJobs be the size of nonchangingNavigablesThatStillNeedUpdates.
auto total_non_changing_jobs = non_changing_navigables_that_still_need_updates.size();
// 16. Let completedNonchangingJobs be 0.
IGNORE_USE_IN_ESCAPING_LAMBDA auto completed_non_changing_jobs = 0u;
// 17. Let (scriptHistoryLength, scriptHistoryIndex) be the result of getting the history object length and index given traversable and targetStep.
auto length_and_index = get_the_history_object_length_and_index(target_step);
IGNORE_USE_IN_ESCAPING_LAMBDA auto script_history_length = length_and_index.script_history_length;
IGNORE_USE_IN_ESCAPING_LAMBDA auto script_history_index = length_and_index.script_history_index;
// 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:
for (auto& navigable : non_changing_navigables_that_still_need_updates) {
if (navigable->has_been_destroyed()) {
++completed_non_changing_jobs;
continue;
}
queue_global_task(Task::Source::NavigationAndTraversal, *navigable->active_window(), JS::create_heap_function(heap(), [&] {
// NOTE: This check is not in the spec but we should not continue navigation if navigable has been destroyed.
if (navigable->has_been_destroyed()) {
++completed_non_changing_jobs;
return;
}
// 1. Let document be navigable's active document.
auto document = navigable->active_document();
// 2. Set document's history object's index to scriptHistoryIndex.
document->history()->m_index = script_history_index;
// 3. Set document's history object's length to scriptHistoryLength.
document->history()->m_length = script_history_length;
// 4. Increment completedNonchangingJobs.
++completed_non_changing_jobs;
}));
}
// 19. Wait for completedNonchangingJobs to equal totalNonchangingJobs.
// AD-HOC: Since currently populate_session_history_entry_document does not run in parallel
// we call spin_until to interrupt execution of this function and let document population
// to complete.
main_thread_event_loop().spin_processing_tasks_with_source_until(Task::Source::NavigationAndTraversal, [&] {
return completed_non_changing_jobs == total_non_changing_jobs;
});
// 20. Set traversable's current session history step to targetStep.
m_current_session_history_step = target_step;
// Not in the spec:
auto back_enabled = m_current_session_history_step > 0;
VERIFY(m_session_history_entries.size() > 0);
auto forward_enabled = can_go_forward();
page().client().page_did_update_navigation_buttons_state(back_enabled, forward_enabled);
page().client().page_did_change_url(current_session_history_entry()->url());
// 21. Return "applied".
return HistoryStepResult::Applied;
}
Vector<JS::NonnullGCPtr<SessionHistoryEntry>> TraversableNavigable::get_session_history_entries_for_the_navigation_api(JS::NonnullGCPtr<Navigable> navigable, int target_step)
{
// 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.
Vector<JS::NonnullGCPtr<SessionHistoryEntry>> entries_for_navigation_api;
// 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];
if (entry->step().has<int>()) {
auto step = entry->step().get<int>();
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.
auto starting_origin = raw_entries[starting_index]->document_state()->origin();
// 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.
auto entry_origin = entry->document_state()->origin();
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.
auto entry_origin = entry->document_state()->origin();
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;
}
// 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 Β».
Vector<Vector<JS::NonnullGCPtr<SessionHistoryEntry>>&> entry_lists;
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) {
return entry->step().template get<int>() > step;
});
// 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.
for (auto& nested_history : entry->document_state()->nested_histories()) {
entry_lists.append(nested_history.entries);
}
}
}
}
bool TraversableNavigable::can_go_forward() const
{
auto step = current_session_history_step();
Vector<Vector<JS::NonnullGCPtr<SessionHistoryEntry>> const&> entry_lists;
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;
}
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#traverse-the-history-by-a-delta
void TraversableNavigable::traverse_the_history_by_delta(int delta, Optional<DOM::Document&> source_document)
{
// 1. Let sourceSnapshotParams and initiatorToCheck be null.
Optional<SourceSnapshotParams> source_snapshot_params = {};
JS::GCPtr<Navigable> initiator_to_check = nullptr;
// 2. Let userInvolvement be "browser UI".
UserNavigationInvolvement user_involvement = UserNavigationInvolvement::BrowserUI;
// 1. If sourceDocument is given, then:
if (source_document.has_value()) {
// 1. Set sourceSnapshotParams to the result of snapshotting source snapshot params given sourceDocument.
source_snapshot_params = source_document->snapshot_source_snapshot_params();
// 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:
append_session_history_traversal_steps([this, delta, source_snapshot_params = move(source_snapshot_params), initiator_to_check, user_involvement] {
// 1. Let allSteps be the result of getting all used history steps for traversable.
auto all_steps = get_all_used_history_steps();
// 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());
// 3. Let targetStepIndex be currentStepIndex plus delta
auto target_step_index = current_step_index + delta;
// 4. If allSteps[targetStepIndex] does not exist, then abort these steps.
if (target_step_index >= all_steps.size()) {
return;
}
// 5. Apply the traverse history step allSteps[targetStepIndex] to traversable, given sourceSnapshotParams,
// initiatorToCheck, and userInvolvement.
apply_the_traverse_history_step(all_steps[target_step_index], source_snapshot_params, initiator_to_check, user_involvement);
});
}
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#update-for-navigable-creation/destruction
TraversableNavigable::HistoryStepResult TraversableNavigable::update_for_navigable_creation_or_destruction()
{
// 1. Let step be traversable's current session history step.
auto step = current_session_history_step();
// 2. Return the result of applying the history step to traversable given false, null, null, null, and null.
return apply_the_history_step(step, false, {}, {}, {}, {}, SynchronousNavigation::No);
}
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#apply-the-reload-history-step
TraversableNavigable::HistoryStepResult TraversableNavigable::apply_the_reload_history_step()
{
// 1. Let step be traversable's current session history step.
auto step = current_session_history_step();
// 2. Return the result of applying the history step step to traversable given true, null, null, null, and "reload".
return apply_the_history_step(step, true, {}, {}, {}, Bindings::NavigationType::Reload, SynchronousNavigation::No);
}
TraversableNavigable::HistoryStepResult TraversableNavigable::apply_the_push_or_replace_history_step(int step, HistoryHandlingBehavior history_handling, SynchronousNavigation synchronous_navigation)
{
// 1. Return the result of applying the history step step to traversable given false, null, null, null, and historyHandling.
auto navigation_type = history_handling == HistoryHandlingBehavior::Replace ? Bindings::NavigationType::Replace : Bindings::NavigationType::Push;
return apply_the_history_step(step, false, {}, {}, {}, navigation_type, synchronous_navigation);
}