Coverage Report

Created: 2026-07-25 07:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibWeb/HTML/Navigable.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2022-2024, Andreas Kling <kling@serenityos.org>
3
 * Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
4
 *
5
 * SPDX-License-Identifier: BSD-2-Clause
6
 */
7
8
#include <LibWeb/Crypto/Crypto.h>
9
#include <LibWeb/DOM/Document.h>
10
#include <LibWeb/DOM/DocumentLoading.h>
11
#include <LibWeb/DOM/Event.h>
12
#include <LibWeb/DOM/Range.h>
13
#include <LibWeb/Fetch/Fetching/Fetching.h>
14
#include <LibWeb/Fetch/Infrastructure/FetchAlgorithms.h>
15
#include <LibWeb/Fetch/Infrastructure/FetchController.h>
16
#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
17
#include <LibWeb/Fetch/Infrastructure/URL.h>
18
#include <LibWeb/HTML/BrowsingContext.h>
19
#include <LibWeb/HTML/DocumentState.h>
20
#include <LibWeb/HTML/HTMLIFrameElement.h>
21
#include <LibWeb/HTML/HistoryHandlingBehavior.h>
22
#include <LibWeb/HTML/Navigable.h>
23
#include <LibWeb/HTML/Navigation.h>
24
#include <LibWeb/HTML/NavigationObserver.h>
25
#include <LibWeb/HTML/NavigationParams.h>
26
#include <LibWeb/HTML/POSTResource.h>
27
#include <LibWeb/HTML/Parser/HTMLParser.h>
28
#include <LibWeb/HTML/SandboxingFlagSet.h>
29
#include <LibWeb/HTML/Scripting/ClassicScript.h>
30
#include <LibWeb/HTML/SessionHistoryEntry.h>
31
#include <LibWeb/HTML/StructuredSerialize.h>
32
#include <LibWeb/HTML/TraversableNavigable.h>
33
#include <LibWeb/HTML/Window.h>
34
#include <LibWeb/HTML/WindowProxy.h>
35
#include <LibWeb/Infra/Strings.h>
36
#include <LibWeb/Layout/Node.h>
37
#include <LibWeb/Loader/GeneratedPagesLoader.h>
38
#include <LibWeb/Page/Page.h>
39
#include <LibWeb/Painting/Paintable.h>
40
#include <LibWeb/Painting/ViewportPaintable.h>
41
#include <LibWeb/Platform/EventLoopPlugin.h>
42
#include <LibWeb/Selection/Selection.h>
43
#include <LibWeb/XHR/FormData.h>
44
45
namespace Web::HTML {
46
47
JS_DEFINE_ALLOCATOR(Navigable);
48
49
class ResponseHolder : public JS::Cell {
50
    JS_CELL(ResponseHolder, JS::Cell);
51
    JS_DECLARE_ALLOCATOR(ResponseHolder);
52
53
public:
54
    [[nodiscard]] static JS::NonnullGCPtr<ResponseHolder> create(JS::VM& vm)
55
0
    {
56
0
        return vm.heap().allocate_without_realm<ResponseHolder>();
57
0
    }
58
59
0
    [[nodiscard]] JS::GCPtr<Fetch::Infrastructure::Response> response() const { return m_response; }
60
0
    void set_response(JS::GCPtr<Fetch::Infrastructure::Response> response) { m_response = response; }
61
62
    virtual void visit_edges(Cell::Visitor& visitor) override
63
0
    {
64
0
        Base::visit_edges(visitor);
65
0
        visitor.visit(m_response);
66
0
    }
67
68
private:
69
    JS::GCPtr<Fetch::Infrastructure::Response> m_response;
70
};
71
72
JS_DEFINE_ALLOCATOR(ResponseHolder);
73
74
HashTable<Navigable*>& all_navigables()
75
0
{
76
0
    static HashTable<Navigable*> set;
77
0
    return set;
78
0
}
79
80
// https://html.spec.whatwg.org/multipage/document-sequences.html#child-navigable
81
Vector<JS::Handle<Navigable>> Navigable::child_navigables() const
82
0
{
83
0
    Vector<JS::Handle<Navigable>> results;
84
0
    for (auto& entry : all_navigables()) {
85
0
        if (entry->current_session_history_entry()->step() == SessionHistoryEntry::Pending::Tag)
86
0
            continue;
87
0
        if (entry->parent() == this)
88
0
            results.append(entry);
89
0
    }
90
91
0
    return results;
92
0
}
93
94
bool Navigable::is_traversable() const
95
0
{
96
0
    return is<TraversableNavigable>(*this);
97
0
}
98
99
bool Navigable::is_ancestor_of(JS::NonnullGCPtr<Navigable> other) const
100
0
{
101
0
    for (auto ancestor = other->parent(); ancestor; ancestor = ancestor->parent()) {
102
0
        if (ancestor == this)
103
0
            return true;
104
0
    }
105
0
    return false;
106
0
}
107
108
Navigable::Navigable(JS::NonnullGCPtr<Page> page)
109
0
    : m_page(page)
110
0
    , m_event_handler({}, *this)
111
0
{
112
0
    all_navigables().set(this);
113
0
}
114
115
Navigable::~Navigable()
116
0
{
117
0
    all_navigables().remove(this);
118
0
}
119
120
void Navigable::visit_edges(Cell::Visitor& visitor)
121
0
{
122
0
    Base::visit_edges(visitor);
123
0
    visitor.visit(m_page);
124
0
    visitor.visit(m_parent);
125
0
    visitor.visit(m_current_session_history_entry);
126
0
    visitor.visit(m_active_session_history_entry);
127
0
    visitor.visit(m_container);
128
0
    visitor.visit(m_navigation_observers);
129
0
    m_event_handler.visit_edges(visitor);
130
0
}
131
132
void Navigable::set_delaying_load_events(bool value)
133
0
{
134
0
    if (value) {
135
0
        auto document = container_document();
136
0
        VERIFY(document);
137
0
        m_delaying_the_load_event.emplace(*document);
138
0
    } else {
139
0
        m_delaying_the_load_event.clear();
140
0
    }
141
0
}
142
143
JS::GCPtr<Navigable> Navigable::navigable_with_active_document(JS::NonnullGCPtr<DOM::Document> document)
144
0
{
145
0
    for (auto* navigable : all_navigables()) {
146
0
        if (navigable->active_document() == document)
147
0
            return navigable;
148
0
    }
149
0
    return nullptr;
150
0
}
151
152
// https://html.spec.whatwg.org/multipage/document-sequences.html#initialize-the-navigable
153
ErrorOr<void> Navigable::initialize_navigable(JS::NonnullGCPtr<DocumentState> document_state, JS::GCPtr<Navigable> parent)
154
0
{
155
0
    static int next_id = 0;
156
0
    m_id = String::number(next_id++);
157
158
    // 1. Assert: documentState's document is non-null.
159
0
    VERIFY(document_state->document());
160
161
    // 2. Let entry be a new session history entry, with
162
0
    JS::NonnullGCPtr<SessionHistoryEntry> entry = *heap().allocate_without_realm<SessionHistoryEntry>();
163
    // URL: document's URL
164
0
    entry->set_url(document_state->document()->url());
165
    // document state: documentState
166
0
    entry->set_document_state(document_state);
167
168
    // 3. Set navigable's current session history entry to entry.
169
0
    m_current_session_history_entry = entry;
170
171
    // 4. Set navigable's active session history entry to entry.
172
0
    m_active_session_history_entry = entry;
173
174
    // 5. Set navigable's parent to parent.
175
0
    m_parent = parent;
176
177
0
    return {};
178
0
}
179
180
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#getting-the-target-history-entry
181
JS::GCPtr<SessionHistoryEntry> Navigable::get_the_target_history_entry(int target_step) const
182
0
{
183
    // 1. Let entries be the result of getting session history entries for navigable.
184
0
    auto& entries = get_session_history_entries();
185
186
    // 2. Return the item in entries that has the greatest step less than or equal to step.
187
0
    JS::GCPtr<SessionHistoryEntry> result = nullptr;
188
0
    for (auto& entry : entries) {
189
0
        auto entry_step = entry->step().get<int>();
190
0
        if (entry_step <= target_step) {
191
0
            if (!result || result->step().get<int>() < entry_step) {
192
0
                result = entry;
193
0
            }
194
0
        }
195
0
    }
196
197
0
    return result;
198
0
}
199
200
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#activate-history-entry
201
void Navigable::activate_history_entry(JS::GCPtr<SessionHistoryEntry> entry)
202
0
{
203
    // FIXME: 1. Save persisted state to the navigable's active session history entry.
204
205
    // 2. Let newDocument be entry's document.
206
0
    JS::GCPtr<DOM::Document> new_document = entry->document().ptr();
207
208
    // 3. Assert: newDocument's is initial about:blank is false, i.e., we never traverse
209
    //    back to the initial about:blank Document because it always gets replaced when we
210
    //    navigate away from it.
211
0
    VERIFY(!new_document->is_initial_about_blank());
212
213
    // 4. Set navigable's active session history entry to entry.
214
0
    m_active_session_history_entry = entry;
215
216
    // 5. Make active newDocument.
217
0
    new_document->make_active();
218
219
0
    if (m_ongoing_navigation.has<Empty>()) {
220
0
        for (auto navigation_observer : m_navigation_observers) {
221
0
            if (navigation_observer->navigation_complete())
222
0
                navigation_observer->navigation_complete()->function()();
223
0
        }
224
0
    }
225
0
}
226
227
// https://html.spec.whatwg.org/multipage/document-sequences.html#nav-document
228
JS::GCPtr<DOM::Document> Navigable::active_document()
229
0
{
230
    // A navigable's active document is its active session history entry's document.
231
0
    return m_active_session_history_entry->document();
232
0
}
233
234
// https://html.spec.whatwg.org/multipage/document-sequences.html#nav-bc
235
JS::GCPtr<BrowsingContext> Navigable::active_browsing_context()
236
0
{
237
    // A navigable's active browsing context is its active document's browsing context.
238
    // If this navigable is a traversable navigable, then its active browsing context will be a top-level browsing context.
239
0
    if (auto document = active_document())
240
0
        return document->browsing_context();
241
0
    return nullptr;
242
0
}
243
244
// https://html.spec.whatwg.org/multipage/document-sequences.html#nav-wp
245
JS::GCPtr<HTML::WindowProxy> Navigable::active_window_proxy()
246
0
{
247
    // A navigable's active WindowProxy is its active browsing context's associated WindowProxy.
248
0
    if (auto browsing_context = active_browsing_context())
249
0
        return browsing_context->window_proxy();
250
0
    return nullptr;
251
0
}
252
253
// https://html.spec.whatwg.org/multipage/document-sequences.html#nav-window
254
JS::GCPtr<HTML::Window> Navigable::active_window()
255
0
{
256
    // A navigable's active window is its active WindowProxy's [[Window]].
257
0
    if (auto window_proxy = active_window_proxy())
258
0
        return window_proxy->window();
259
0
    return nullptr;
260
0
}
261
262
// https://html.spec.whatwg.org/multipage/document-sequences.html#nav-target
263
String Navigable::target_name() const
264
0
{
265
    // A navigable's target name is its active session history entry's document state's navigable target name.
266
0
    return active_session_history_entry()->document_state()->navigable_target_name();
267
0
}
268
269
// https://html.spec.whatwg.org/multipage/document-sequences.html#nav-container
270
JS::GCPtr<NavigableContainer> Navigable::container() const
271
0
{
272
    // The container of a navigable navigable is the navigable container whose nested navigable is navigable, or null if there is no such element.
273
0
    return NavigableContainer::navigable_container_with_content_navigable(const_cast<Navigable&>(*this));
274
0
}
275
276
// https://html.spec.whatwg.org/multipage/document-sequences.html#nav-container-document
277
JS::GCPtr<DOM::Document> Navigable::container_document() const
278
0
{
279
0
    auto container = this->container();
280
281
    // 1. If navigable's container is null, then return null.
282
0
    if (!container)
283
0
        return nullptr;
284
285
    // 2. Return navigable's container's node document.
286
0
    return container->document();
287
0
}
288
289
// https://html.spec.whatwg.org/multipage/document-sequences.html#nav-traversable
290
JS::GCPtr<TraversableNavigable> Navigable::traversable_navigable() const
291
0
{
292
    // 1. Let navigable be inputNavigable.
293
0
    auto navigable = const_cast<Navigable*>(this);
294
295
    // 2. While navigable is not a traversable navigable, set navigable to navigable's parent.
296
0
    while (navigable && !is<TraversableNavigable>(*navigable))
297
0
        navigable = navigable->parent();
298
299
    // 3. Return navigable.
300
0
    return static_cast<TraversableNavigable*>(navigable);
301
0
}
302
303
// https://html.spec.whatwg.org/multipage/document-sequences.html#nav-top
304
JS::GCPtr<TraversableNavigable> Navigable::top_level_traversable()
305
0
{
306
    // 1. Let navigable be inputNavigable.
307
0
    auto navigable = this;
308
309
    // 2. While navigable's parent is not null, set navigable to navigable's parent.
310
0
    while (navigable->parent())
311
0
        navigable = navigable->parent();
312
313
    // 3. Return navigable.
314
0
    return verify_cast<TraversableNavigable>(navigable);
315
0
}
316
317
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#set-the-ongoing-navigation
318
void Navigable::set_ongoing_navigation(Variant<Empty, Traversal, String> ongoing_navigation)
319
0
{
320
    // 1. If navigable's ongoing navigation is equal to newValue, then return.
321
0
    if (m_ongoing_navigation == ongoing_navigation)
322
0
        return;
323
324
    // 2. Inform the navigation API about aborting navigation given navigable.
325
0
    inform_the_navigation_api_about_aborting_navigation();
326
327
    // 3. Set navigable's ongoing navigation to newValue.
328
0
    m_ongoing_navigation = ongoing_navigation;
329
0
}
330
331
// https://html.spec.whatwg.org/multipage/document-sequences.html#the-rules-for-choosing-a-navigable
332
Navigable::ChosenNavigable Navigable::choose_a_navigable(StringView name, TokenizedFeature::NoOpener no_opener, ActivateTab activate_tab, Optional<TokenizedFeature::Map const&> window_features)
333
0
{
334
    // NOTE: Implementation for step 7 here.
335
0
    JS::GCPtr<Navigable> same_name_navigable = nullptr;
336
0
    if (!Infra::is_ascii_case_insensitive_match(name, "_blank"sv)) {
337
0
        for (auto& n : all_navigables()) {
338
0
            if (n->target_name() == name && !n->has_been_destroyed()) {
339
0
                same_name_navigable = n;
340
0
            }
341
0
        }
342
0
    }
343
344
    // 1. Let chosen be null.
345
0
    JS::GCPtr<Navigable> chosen = nullptr;
346
347
    // 2. Let windowType be "existing or none".
348
0
    auto window_type = WindowType::ExistingOrNone;
349
350
    // 3. Let sandboxingFlagSet be current's active document's active sandboxing flag set.
351
0
    auto sandboxing_flag_set = active_document()->active_sandboxing_flag_set();
352
353
    // 4. If name is the empty string or an ASCII case-insensitive match for "_self", then set chosen to currentNavigable.
354
0
    if (name.is_empty() || Infra::is_ascii_case_insensitive_match(name, "_self"sv)) {
355
0
        chosen = this;
356
0
    }
357
358
    // 5. Otherwise, if name is an ASCII case-insensitive match for "_parent",
359
    //    set chosen to currentNavigable's parent, if any, and currentNavigable otherwise.
360
0
    else if (Infra::is_ascii_case_insensitive_match(name, "_parent"sv)) {
361
0
        if (auto parent = this->parent())
362
0
            chosen = parent;
363
0
        else
364
0
            chosen = this;
365
0
    }
366
367
    // 6. Otherwise, if name is an ASCII case-insensitive match for "_top",
368
    //    set chosen to currentNavigable's traversable navigable.
369
0
    else if (Infra::is_ascii_case_insensitive_match(name, "_top"sv)) {
370
0
        chosen = traversable_navigable();
371
0
    }
372
373
    //  7. Otherwise, if name is not an ASCII case-insensitive match for "_blank",
374
    //     there exists a navigable whose target name is the same as name, currentNavigable's
375
    //     active browsing context is familiar with that navigable's active browsing context,
376
    //     and the user agent determines that the two browsing contexts are related enough that
377
    //     it is ok if they reach each other, set chosen to that navigable. If there are multiple
378
    //     matching navigables, the user agent should pick one in some arbitrary consistent manner,
379
    //     such as the most recently opened, most recently focused, or more closely related, and set
380
    //     chosen to it.
381
0
    else if (same_name_navigable != nullptr && (active_browsing_context()->is_familiar_with(*same_name_navigable->active_browsing_context()))) {
382
        // FIXME: Handle multiple name-match case
383
        // FIXME: When are these contexts 'not related enough' ?
384
0
        chosen = same_name_navigable;
385
0
    }
386
387
    // 8. Otherwise, a new top-level traversable is being requested, and what happens depends on the
388
    // user agent's configuration and abilities — it is determined by the rules given for the first
389
    // applicable option from the following list:
390
0
    else {
391
        // --> If current's active window does not have transient activation and the user agent has been configured to
392
        //     not show popups (i.e., the user agent has a "popup blocker" enabled)
393
0
        if (active_window() && !active_window()->has_transient_activation() && traversable_navigable()->page().should_block_pop_ups()) {
394
            // FIXME: The user agent may inform the user that a popup has been blocked.
395
0
            dbgln("Pop-up blocked!");
396
0
        }
397
398
        // --> If sandboxingFlagSet has the sandboxed auxiliary navigation browsing context flag set
399
0
        else if (has_flag(sandboxing_flag_set, SandboxingFlagSet::SandboxedAuxiliaryNavigation)) {
400
            // FIXME: The user agent may report to a developer console that a popup has been blocked.
401
0
            dbgln("Pop-up blocked!");
402
0
        }
403
404
        // --> If the user agent has been configured such that in this instance it will create a new top-level traversable
405
0
        else if (true) { // FIXME: When is this the case?
406
            // 1. Consume user activation of currentNavigable's active window.
407
0
            active_window()->consume_user_activation();
408
409
            // 2. Set windowType to "new and unrestricted".
410
0
            window_type = WindowType::NewAndUnrestricted;
411
412
            // 3. Let currentDocument be currentNavigable's active document.
413
0
            auto current_document = active_document();
414
415
            // 4. If currentDocument's opener policy's value is "same-origin" or "same-origin-plus-COEP",
416
            //    and currentDocument's origin is not same origin with currentDocument's relevant settings object's top-level origin, then:
417
0
            if ((current_document->opener_policy().value == OpenerPolicyValue::SameOrigin || current_document->opener_policy().value == OpenerPolicyValue::SameOriginPlusCOEP)
418
0
                && !current_document->origin().is_same_origin(relevant_settings_object(*current_document).top_level_origin)) {
419
420
                // 1. Set noopener to true.
421
0
                no_opener = TokenizedFeature::NoOpener::Yes;
422
423
                // 2. Set name to "_blank".
424
0
                name = "_blank"sv;
425
426
                // 3. Set windowType to "new with no opener".
427
0
                window_type = WindowType::NewWithNoOpener;
428
0
            }
429
            // NOTE: In the presence of an opener policy,
430
            //       nested documents that are cross-origin with their top-level browsing context's active document always set noopener to true.
431
432
            // 5. Let chosen be null.
433
0
            chosen = nullptr;
434
435
            // 6. Let targetName be the empty string.
436
0
            String target_name;
437
438
            // 7. If name is not an ASCII case-insensitive match for "_blank", then set targetName to name.
439
0
            if (!Infra::is_ascii_case_insensitive_match(name, "_blank"sv))
440
0
                target_name = MUST(String::from_utf8(name));
441
442
0
            auto create_new_traversable_closure = [this, no_opener, target_name, activate_tab, window_features](JS::GCPtr<BrowsingContext> opener) -> JS::NonnullGCPtr<Navigable> {
443
0
                auto hints = WebViewHints::from_tokenised_features(window_features.value_or({}), traversable_navigable()->page());
444
0
                auto [page, window_handle] = traversable_navigable()->page().client().page_did_request_new_web_view(activate_tab, hints, no_opener);
445
0
                auto traversable = TraversableNavigable::create_a_new_top_level_traversable(*page, opener, target_name).release_value_but_fixme_should_propagate_errors();
446
0
                page->set_top_level_traversable(traversable);
447
0
                traversable->set_window_handle(window_handle);
448
0
                return traversable;
449
0
            };
450
0
            auto create_new_traversable = JS::create_heap_function(heap(), move(create_new_traversable_closure));
451
452
            // 8. If noopener is true, then set chosen to the result of creating a new top-level traversable given null and targetName.
453
0
            if (no_opener == TokenizedFeature::NoOpener::Yes) {
454
0
                chosen = create_new_traversable->function()(nullptr);
455
0
            }
456
457
            // 9. Otherwise:
458
0
            else {
459
                // 1. Set chosen to the result of creating a new top-level traversable given currentNavigable's active browsing context and targetName.
460
0
                chosen = create_new_traversable->function()(active_browsing_context());
461
462
                // 2. If sandboxingFlagSet's sandboxed navigation browsing context flag is set,
463
                //    then set chosen's active browsing context's one permitted sandboxed navigator to currentNavigable's active browsing context.
464
0
                if (has_flag(sandboxing_flag_set, SandboxingFlagSet::SandboxedNavigation))
465
0
                    chosen->active_browsing_context()->set_the_one_permitted_sandboxed_navigator(active_browsing_context());
466
0
            }
467
468
            // 10. If sandboxingFlagSet's sandbox propagates to auxiliary browsing contexts flag is set,
469
            //     then all the flags that are set in sandboxingFlagSet must be set in chosen's active browsing context's popup sandboxing flag set.
470
0
            if (has_flag(sandboxing_flag_set, SandboxingFlagSet::SandboxPropagatesToAuxiliaryBrowsingContexts))
471
0
                chosen->active_browsing_context()->set_popup_sandboxing_flag_set(chosen->active_browsing_context()->popup_sandboxing_flag_set() | sandboxing_flag_set);
472
0
        }
473
474
        // --> If the user agent has been configured such that in this instance t will reuse current
475
0
        else if (false) { // FIXME: When is this the case?
476
            // Set chosen to current.
477
0
            chosen = *this;
478
0
        }
479
480
        // --> If the user agent has been configured such that in this instance it will not find a browsing context
481
0
        else if (false) { // FIXME: When is this the case?
482
            // Do nothing.
483
0
        }
484
0
    }
485
486
0
    return { chosen.ptr(), window_type };
487
0
}
488
489
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#getting-session-history-entries
490
Vector<JS::NonnullGCPtr<SessionHistoryEntry>>& Navigable::get_session_history_entries() const
491
0
{
492
    // 1. Let traversable be navigable's traversable navigable.
493
0
    auto traversable = traversable_navigable();
494
495
    // FIXME 2. Assert: this is running within traversable's session history traversal queue.
496
497
    // 3. If navigable is traversable, return traversable's session history entries.
498
0
    if (this == traversable)
499
0
        return traversable->session_history_entries();
500
501
    // 4. Let docStates be an empty ordered set of document states.
502
0
    Vector<JS::GCPtr<DocumentState>> doc_states;
503
504
    // 5. For each entry of traversable's session history entries, append entry's document state to docStates.
505
0
    for (auto& entry : traversable->session_history_entries())
506
0
        doc_states.append(entry->document_state());
507
508
    // 6. For each docState of docStates:
509
0
    while (!doc_states.is_empty()) {
510
0
        auto doc_state = doc_states.take_first();
511
512
        // 1. For each nestedHistory of docState's nested histories:
513
0
        for (auto& nested_history : doc_state->nested_histories()) {
514
            // 1. If nestedHistory's id equals navigable's id, return nestedHistory's entries.
515
0
            if (nested_history.id == id())
516
0
                return nested_history.entries;
517
518
            // 2. For each entry of nestedHistory's entries, append entry's document state to docStates.
519
0
            for (auto& entry : nested_history.entries)
520
0
                doc_states.append(entry->document_state());
521
0
        }
522
0
    }
523
524
0
    VERIFY_NOT_REACHED();
525
0
}
526
527
// https://html.spec.whatwg.org/multipage/browsers.html#determining-navigation-params-policy-container
528
static PolicyContainer determine_navigation_params_policy_container(URL::URL const& response_url,
529
    Optional<PolicyContainer> history_policy_container,
530
    Optional<PolicyContainer> initiator_policy_container,
531
    Optional<PolicyContainer> parent_policy_container,
532
    Optional<PolicyContainer> response_policy_container)
533
0
{
534
    // NOTE: The clone a policy container AO is just a C++ copy
535
536
    // 1. If historyPolicyContainer is not null, then:
537
0
    if (history_policy_container.has_value()) {
538
        // FIXME: 1. Assert: responseURL requires storing the policy container in history.
539
540
        // 2. Return a clone of historyPolicyContainer.
541
0
        return *history_policy_container;
542
0
    }
543
544
    // 2. If responseURL is about:srcdoc, then:
545
0
    if (response_url == "about:srcdoc"sv) {
546
        // 1. Assert: parentPolicyContainer is not null.
547
0
        VERIFY(parent_policy_container.has_value());
548
549
        // 2. Return a clone of parentPolicyContainer.
550
0
        return *parent_policy_container;
551
0
    }
552
553
    // 3. If responseURL is local and initiatorPolicyContainer is not null, then return a clone of initiatorPolicyContainer.
554
0
    if (Fetch::Infrastructure::is_local_url(response_url) && initiator_policy_container.has_value())
555
0
        return *initiator_policy_container;
556
557
    // 4. If responsePolicyContainer is not null, then return responsePolicyContainer.
558
    // FIXME: File a spec issue to say "a clone of" here for consistency
559
0
    if (response_policy_container.has_value())
560
0
        return *response_policy_container;
561
562
    // 5. Return a new policy container.
563
0
    return {};
564
0
}
565
566
// https://html.spec.whatwg.org/multipage/browsers.html#obtain-coop
567
static OpenerPolicy obtain_an_opener_policy(JS::NonnullGCPtr<Fetch::Infrastructure::Response>, Fetch::Infrastructure::Request::ReservedClientType const& reserved_client)
568
0
{
569
570
    // 1. Let policy be a new opener policy.
571
0
    OpenerPolicy policy = {};
572
573
    // AD-HOC: We don't yet setup environments in all cases
574
0
    if (!reserved_client)
575
0
        return policy;
576
577
0
    auto& reserved_environment = *reserved_client;
578
579
    // 2. If reservedEnvironment is a non-secure context, then return policy.
580
0
    if (is_non_secure_context(reserved_environment))
581
0
        return policy;
582
583
    // FIXME: We don't yet have the technology to extract structured data from Fetch headers
584
    // FIXME: 3. Let parsedItem be the result of getting a structured field value given `Cross-Origin-Opener-Policy` and "item" from response's header list.
585
    // FIXME: 4. If parsedItem is not null, then:
586
    //     FIXME: nested steps...
587
    // FIXME: 5. Set parsedItem to the result of getting a structured field value given `Cross-Origin-Opener-Policy-Report-Only` and "item" from response's header list.
588
    // FIXME: 6. If parsedItem is not null, then:
589
    //     FIXME: nested steps...
590
591
    // 7. Return policy.
592
0
    return policy;
593
0
}
594
595
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#attempt-to-create-a-non-fetch-scheme-document
596
static JS::GCPtr<DOM::Document> attempt_to_create_a_non_fetch_scheme_document(NonFetchSchemeNavigationParams const& params)
597
0
{
598
    // 1. Let url be navigationParams's URL.
599
0
    auto url = params.url;
600
601
    // 2. Let navigable be navigationParams's navigable.
602
0
    auto navigable = params.navigable;
603
604
    // 3. If url is to be handled using a mechanism that does not affect navigable, e.g., because url's scheme is handled externally, then:
605
0
    if (navigable && navigable->page().client().handle_non_fetch_scheme(params.url)) {
606
        // 1. Hand-off to external software given url, navigable, navigationParams's target snapshot sandboxing flags, navigationParams's source snapshot has
607
        // transient activation, and navigationParams's initiator origin.
608
        // FIXME: We only send the URL.
609
        // 2. Return null.
610
0
        return nullptr;
611
0
    }
612
613
    // 4. Handle url by displaying some sort of inline content, e.g., an error message because the specified scheme is not one of the supported protocols, or an]
614
    // inline prompt to allow the user to select a registered handler for the given scheme. Return the result of displaying the inline content given navigable,
615
    // navigationParams's id, navigationParams's navigation timing type, and navigationParams's user involvement.
616
0
    auto error_message = MUST(String::formatted("Unsupported scheme: {}", url.scheme()));
617
0
    auto error_html = load_error_page(url, error_message).release_value_but_fixme_should_propagate_errors();
618
0
    auto document = create_document_for_inline_content(navigable, params.id, [error_html](auto& document) {
619
0
        auto parser = HTML::HTMLParser::create(document, error_html, "utf-8"sv);
620
0
        document.set_url(URL::URL("about:error"));
621
0
        parser->run();
622
0
    });
623
0
    document->make_unsalvageable("navigation-failure"_string);
624
0
    return document;
625
0
}
626
627
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#create-navigation-params-from-a-srcdoc-resource
628
static WebIDL::ExceptionOr<JS::NonnullGCPtr<NavigationParams>> create_navigation_params_from_a_srcdoc_resource(JS::GCPtr<SessionHistoryEntry> entry, JS::GCPtr<Navigable> navigable, TargetSnapshotParams const& target_snapshot_params, Optional<String> navigation_id)
629
0
{
630
0
    auto& vm = navigable->vm();
631
0
    VERIFY(navigable->active_window());
632
0
    auto& realm = navigable->active_window()->realm();
633
634
    // 1. Let documentResource be entry's document state's resource.
635
0
    auto document_resource = entry->document_state()->resource();
636
0
    VERIFY(document_resource.has<String>());
637
638
    // 2. Let response be a new response with
639
    //    URL: about:srcdoc
640
    //    header list: (`Content-Type`, `text/html`)
641
    //    body: the UTF-8 encoding of documentResource, as a body
642
0
    auto response = Fetch::Infrastructure::Response::create(vm);
643
0
    response->url_list().append(URL::URL("about:srcdoc"));
644
645
0
    auto header = Fetch::Infrastructure::Header::from_string_pair("Content-Type"sv, "text/html"sv);
646
0
    response->header_list()->append(move(header));
647
648
0
    response->set_body(TRY(Fetch::Infrastructure::byte_sequence_as_body(realm, document_resource.get<String>().bytes())));
649
650
    // 3. Let responseOrigin be the result of determining the origin given response's URL, targetSnapshotParams's sandboxing flags, and entry's document state's origin.
651
0
    auto response_origin = determine_the_origin(response->url(), target_snapshot_params.sandboxing_flags, entry->document_state()->origin());
652
653
    // 4. Let coop be a new opener policy.
654
0
    OpenerPolicy coop = {};
655
656
    // 5. Let coopEnforcementResult be a new opener policy enforcement result with
657
    //    url: response's URL
658
    //    origin: responseOrigin
659
    //    opener policy: coop
660
0
    OpenerPolicyEnforcementResult coop_enforcement_result {
661
0
        .url = *response->url(),
662
0
        .origin = response_origin,
663
0
        .opener_policy = coop
664
0
    };
665
666
    // 6. Let policyContainer be the result of determining navigation params policy container given response's URL,
667
    //    entry's document state's history policy container, null, navigable's container document's policy container, and null.
668
0
    Optional<PolicyContainer> history_policy_container = entry->document_state()->history_policy_container().visit(
669
0
        [](PolicyContainer const& c) -> Optional<PolicyContainer> { return c; },
670
0
        [](DocumentState::Client) -> Optional<PolicyContainer> { return {}; });
671
0
    PolicyContainer policy_container;
672
0
    if (navigable->container()) {
673
        // NOTE: Specification assumes that only navigables corresponding to iframes can be navigated to about:srcdoc.
674
        //       We also use srcdoc to implement load_html() for top level navigables so we need to null check container
675
        //       because it might be null.
676
0
        policy_container = determine_navigation_params_policy_container(*response->url(), history_policy_container, {}, navigable->container_document()->policy_container(), {});
677
0
    }
678
679
    // 7. Return a new navigation params, with
680
    //    id: navigationId
681
    //    navigable: navigable
682
    //    request: null
683
    //    response: response
684
    //    fetch controller: null
685
    //    commit early hints: null
686
    //    COOP enforcement result: coopEnforcementResult
687
    //    reserved environment: null
688
    //    origin: responseOrigin
689
    //    policy container: policyContainer
690
    //    final sandboxing flag set: targetSnapshotParams's sandboxing flags
691
    //    opener policy: coop
692
    //    FIXME: navigation timing type: navTimingType
693
    //    about base URL: entry's document state's about base URL
694
0
    auto navigation_params = vm.heap().allocate_without_realm<NavigationParams>();
695
0
    navigation_params->id = move(navigation_id);
696
0
    navigation_params->navigable = navigable;
697
0
    navigation_params->response = response;
698
0
    navigation_params->coop_enforcement_result = move(coop_enforcement_result);
699
0
    navigation_params->origin = move(response_origin);
700
0
    navigation_params->policy_container = policy_container;
701
0
    navigation_params->final_sandboxing_flag_set = target_snapshot_params.sandboxing_flags;
702
0
    navigation_params->opener_policy = move(coop);
703
0
    navigation_params->about_base_url = entry->document_state()->about_base_url();
704
705
0
    return navigation_params;
706
0
}
707
708
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#create-navigation-params-by-fetching
709
static WebIDL::ExceptionOr<Navigable::NavigationParamsVariant> create_navigation_params_by_fetching(JS::GCPtr<SessionHistoryEntry> entry, JS::GCPtr<Navigable> navigable, SourceSnapshotParams const& source_snapshot_params, TargetSnapshotParams const& target_snapshot_params, CSPNavigationType csp_navigation_type, Optional<String> navigation_id)
710
0
{
711
0
    auto& vm = navigable->vm();
712
0
    VERIFY(navigable->active_window());
713
0
    auto& realm = navigable->active_window()->realm();
714
0
    auto& active_document = *navigable->active_document();
715
716
0
    (void)csp_navigation_type;
717
718
    // FIXME: 1. Assert: this is running in parallel.
719
720
    // 2. Let documentResource be entry's document state's resource.
721
0
    auto document_resource = entry->document_state()->resource();
722
723
    // 3. Let request be a new request, with
724
    //    url: entry's URL
725
    //    client: sourceSnapshotParams's fetch client
726
    //    destination: "document"
727
    //    credentials mode: "include"
728
    //    use-URL-credentials flag: set
729
    //    redirect mode: "manual"
730
    //    replaces client id: navigable's active document's relevant settings object's id
731
    //    mode: "navigate"
732
    //    referrer: entry's document state's request referrer
733
    //    referrer policy: entry's document state's request referrer policy
734
0
    auto request = Fetch::Infrastructure::Request::create(vm);
735
0
    request->set_url(entry->url());
736
0
    request->set_client(source_snapshot_params.fetch_client);
737
0
    request->set_destination(Fetch::Infrastructure::Request::Destination::Document);
738
0
    request->set_credentials_mode(Fetch::Infrastructure::Request::CredentialsMode::Include);
739
0
    request->set_use_url_credentials(true);
740
0
    request->set_redirect_mode(Fetch::Infrastructure::Request::RedirectMode::Manual);
741
0
    request->set_replaces_client_id(active_document.relevant_settings_object().id);
742
0
    request->set_mode(Fetch::Infrastructure::Request::Mode::Navigate);
743
0
    request->set_referrer(entry->document_state()->request_referrer());
744
745
    // 4. If documentResource is a POST resource, then:
746
0
    if (auto* post_resource = document_resource.get_pointer<POSTResource>()) {
747
        // 1. Set request's method to `POST`.
748
0
        request->set_method(TRY_OR_THROW_OOM(vm, ByteBuffer::copy("POST"sv.bytes())));
749
750
        // 2. Set request's body to documentResource's request body.
751
0
        request->set_body(document_resource.get<POSTResource>().request_body.value());
752
753
        // 3. Set `Content-Type` to documentResource's request content-type in request's header list.
754
0
        auto request_content_type = [&]() {
755
0
            switch (post_resource->request_content_type) {
756
0
            case POSTResource::RequestContentType::ApplicationXWWWFormUrlencoded:
757
0
                return "application/x-www-form-urlencoded"sv;
758
0
            case POSTResource::RequestContentType::MultipartFormData:
759
0
                return "multipart/form-data"sv;
760
0
            case POSTResource::RequestContentType::TextPlain:
761
0
                return "text/plain"sv;
762
0
            default:
763
0
                VERIFY_NOT_REACHED();
764
0
            }
765
0
        }();
766
767
0
        StringBuilder request_content_type_buffer;
768
0
        if (!post_resource->request_content_type_directives.is_empty()) {
769
0
            request_content_type_buffer.append(request_content_type);
770
771
0
            for (auto const& directive : post_resource->request_content_type_directives)
772
0
                request_content_type_buffer.appendff("; {}={}", directive.type, directive.value);
773
774
0
            request_content_type = request_content_type_buffer.string_view();
775
0
        }
776
777
0
        auto header = Fetch::Infrastructure::Header::from_string_pair("Content-Type"sv, request_content_type);
778
0
        request->header_list()->append(move(header));
779
0
    }
780
781
    // 5. If entry's document state's reload pending is true, then set request's reload-navigation flag.
782
0
    if (entry->document_state()->reload_pending())
783
0
        request->set_reload_navigation(true);
784
785
    // 6. Otherwise, if entry's document state's ever populated is true, then set request's history-navigation flag.
786
0
    if (entry->document_state()->ever_populated())
787
0
        request->set_history_navigation(true);
788
789
    // 7. If sourceSnapshotParams's has transient activation is true, then set request's user-activation to true.
790
0
    if (source_snapshot_params.has_transient_activation)
791
0
        request->set_user_activation(true);
792
793
    // 8. If navigable's container is non-null:
794
0
    if (navigable->container() != nullptr) {
795
        // 1. If the navigable's container has a browsing context scope origin, then set request's origin to that browsing context scope origin.
796
        // FIXME: From "browsing context scope origin": This definition is broken and needs investigation to see what it was intended to express: see issue #4703.
797
        //        The referenced issue suggests that it is a no-op to retrieve the browsing context scope origin.
798
799
        // 2. Set request's destination to navigable's container's local name.
800
        // FIXME: Are there other container types? If so, we need a helper here
801
0
        Web::Fetch::Infrastructure::Request::Destination destination = is<HTMLIFrameElement>(*navigable->container()) ? Web::Fetch::Infrastructure::Request::Destination::IFrame
802
0
                                                                                                                      : Web::Fetch::Infrastructure::Request::Destination::Object;
803
0
        request->set_destination(destination);
804
805
        // 3. If sourceSnapshotParams's fetch client is navigable's container document's relevant settings object,
806
        //    then set request's initiator type to navigable's container's local name.
807
        // NOTE: This ensure that only container-initiated navigations are reported to resource timing.
808
0
        if (source_snapshot_params.fetch_client == &navigable->container_document()->relevant_settings_object()) {
809
            // FIXME: Are there other container types? If so, we need a helper here
810
0
            Web::Fetch::Infrastructure::Request::InitiatorType initiator_type = is<HTMLIFrameElement>(*navigable->container()) ? Web::Fetch::Infrastructure::Request::InitiatorType::IFrame
811
0
                                                                                                                               : Web::Fetch::Infrastructure::Request::InitiatorType::Object;
812
0
            request->set_initiator_type(initiator_type);
813
0
        }
814
0
    }
815
816
    // 9. Let response be null.
817
    // NOTE: We use a heap-allocated cell to hold the response pointer because the processResponse callback below
818
    //       might use it after this stack is freed.
819
0
    auto response_holder = ResponseHolder::create(vm);
820
821
    // 10. Let responseOrigin be null.
822
0
    Optional<URL::Origin> response_origin;
823
824
    // 11. Let fetchController be null.
825
0
    JS::GCPtr<Fetch::Infrastructure::FetchController> fetch_controller = nullptr;
826
827
    // 12. Let coopEnforcementResult be a new opener policy enforcement result, with
828
    // - url: navigable's active document's URL
829
    // - origin: navigable's active document's origin
830
    // - opener policy: navigable's active document's opener policy
831
    // - current context is navigation source: true if navigable's active document's origin is same origin with
832
    //                                         entry's document state's initiator origin otherwise false
833
0
    OpenerPolicyEnforcementResult coop_enforcement_result = {
834
0
        .url = active_document.url(),
835
0
        .origin = active_document.origin(),
836
0
        .opener_policy = active_document.opener_policy(),
837
0
        .current_context_is_navigation_source = entry->document_state()->initiator_origin().has_value() && active_document.origin().is_same_origin(*entry->document_state()->initiator_origin())
838
0
    };
839
840
    // 13. Let finalSandboxFlags be an empty sandboxing flag set.
841
0
    SandboxingFlagSet final_sandbox_flags = {};
842
843
    // 14. Let responsePolicyContainer be null.
844
0
    Optional<PolicyContainer> response_policy_container = {};
845
846
    // 15. Let responseCOOP be a new opener policy.
847
0
    OpenerPolicy response_coop = {};
848
849
    // 16. Let locationURL be null.
850
0
    ErrorOr<Optional<URL::URL>> location_url { OptionalNone {} };
851
852
    // 17. Let currentURL be request's current URL.
853
0
    URL::URL current_url = request->current_url();
854
855
    // 18. Let commitEarlyHints be null.
856
0
    Function<void(DOM::Document&)> commit_early_hints = nullptr;
857
858
    // 19. While true:
859
0
    while (true) {
860
        // FIXME: 1. If request's reserved client is not null and currentURL's origin is not the same as request's reserved client's creation URL's origin, then:
861
        // FIXME: 2. If request's reserved client is null, then:
862
        // FIXME: 3. If the result of should navigation request of type be blocked by Content Security Policy? given request and cspNavigationType is "Blocked", then set response to a network error and break. [CSP]
863
864
        // 4. Set response to null.
865
0
        response_holder->set_response(nullptr);
866
867
        // 5. If fetchController is null, then set fetchController to the result of fetching request,
868
        //    with processEarlyHintsResponse set to processEarlyHintsResponseas defined below, processResponse
869
        //    set to processResponse as defined below, and useParallelQueue set to true.
870
0
        if (!fetch_controller) {
871
            // FIXME: Let processEarlyHintsResponse be the following algorithm given a response earlyResponse:
872
873
            // Let processResponse be the following algorithm given a response fetchedResponse:
874
0
            auto process_response = [response_holder](JS::NonnullGCPtr<Fetch::Infrastructure::Response> fetch_response) {
875
                // 1. Set response to fetchedResponse.
876
0
                response_holder->set_response(fetch_response);
877
0
            };
878
879
0
            fetch_controller = TRY(Fetch::Fetching::fetch(
880
0
                realm,
881
0
                request,
882
0
                Fetch::Infrastructure::FetchAlgorithms::create(vm,
883
0
                    {
884
0
                        .process_request_body_chunk_length = {},
885
0
                        .process_request_end_of_body = {},
886
0
                        .process_early_hints_response = {},
887
0
                        .process_response = move(process_response),
888
0
                        .process_response_end_of_body = {},
889
0
                        .process_response_consume_body = {},
890
0
                    }),
891
0
                Fetch::Fetching::UseParallelQueue::Yes));
892
0
        }
893
        // 6. Otherwise, process the next manual redirect for fetchController.
894
0
        else {
895
0
            fetch_controller->process_next_manual_redirect();
896
0
        }
897
898
        // 7. Wait until either response is non-null, or navigable's ongoing navigation changes to no longer equal navigationId.
899
0
        HTML::main_thread_event_loop().spin_until([&]() {
900
0
            if (response_holder->response() != nullptr)
901
0
                return true;
902
903
0
            if (navigation_id.has_value() && (!navigable->ongoing_navigation().has<String>() || navigable->ongoing_navigation().get<String>() != *navigation_id))
904
0
                return true;
905
906
0
            return false;
907
0
        });
908
        // If the latter condition occurs, then abort fetchController, and return. Otherwise, proceed onward.
909
0
        if (navigation_id.has_value() && (!navigable->ongoing_navigation().has<String>() || navigable->ongoing_navigation().get<String>() != *navigation_id)) {
910
0
            fetch_controller->abort(realm, {});
911
0
            return Empty {};
912
0
        }
913
914
        // 8. If request's body is null, then set entry's document state's resource to null.
915
0
        if (!request->body().has<Empty>()) {
916
0
            entry->document_state()->set_resource(Empty {});
917
0
        }
918
919
        // FIXME 9. Set responsePolicyContainer to the result of creating a policy container from a fetch response given response and request's reserved client.
920
        // FIXME 10. Set finalSandboxFlags to the union of targetSnapshotParams's sandboxing flags and responsePolicyContainer's CSP list's CSP-derived sandboxing flags.
921
922
        // 11. Set responseOrigin to the result of determining the origin given response's URL, finalSandboxFlags, and entry's document state's initiator origin.
923
0
        response_origin = determine_the_origin(response_holder->response()->url(), final_sandbox_flags, entry->document_state()->initiator_origin());
924
925
        // 12. If navigable is a top-level traversable, then:
926
0
        if (navigable->is_top_level_traversable()) {
927
            // 1. Set responseCOOP to the result of obtaining an opener policy given response and request's reserved client.
928
0
            response_coop = obtain_an_opener_policy(*response_holder->response(), request->reserved_client());
929
930
            // FIXME: 2. Set coopEnforcementResult to the result of enforcing the response's opener policy given navigable's active browsing context,
931
            //    response's URL, responseOrigin, responseCOOP, coopEnforcementResult and request's referrer.
932
933
            // FIXME: 3. If finalSandboxFlags is not empty and responseCOOP's value is not "unsafe-none", then set response to an appropriate network error and break.
934
            // NOTE: This results in a network error as one cannot simultaneously provide a clean slate to a response
935
            //       using opener policy and sandbox the result of navigating to that response.
936
0
        }
937
938
        // 13. FIXME If response is not a network error, navigable is a child navigable, and the result of performing a cross-origin resource policy check
939
        //    with navigable's container document's origin, navigable's container document's relevant settings object, request's destination, response,
940
        //    and true is blocked, then set response to a network error and break.
941
        // NOTE: Here we're running the cross-origin resource policy check against the parent navigable rather than navigable itself
942
        //       This is because we care about the same-originness of the embedded content against the parent context, not the navigation source.
943
944
        // 14. Set locationURL to response's location URL given currentURL's fragment.
945
0
        location_url = response_holder->response()->location_url(current_url.fragment());
946
947
0
        VERIFY(!location_url.is_error());
948
949
        // 15. If locationURL is failure or null, then break.
950
0
        if (location_url.is_error() || !location_url.value().has_value()) {
951
0
            break;
952
0
        }
953
954
        // 16. Assert: locationURL is a URL.
955
0
        VERIFY(location_url.value()->is_valid());
956
957
        // 17. Set entry's classic history API state to StructuredSerializeForStorage(null).
958
0
        entry->set_classic_history_api_state(MUST(structured_serialize_for_storage(vm, JS::js_null())));
959
960
        // 18. Let oldDocState be entry's document state.
961
0
        auto old_doc_state = entry->document_state();
962
963
        // 19. Set entry's document state to a new document state, with
964
        // history policy container: a clone of the oldDocState's history policy container if it is non-null; null otherwise
965
        // request referrer: oldDocState's request referrer
966
        // request referrer policy: oldDocState's request referrer policy
967
        // origin: oldDocState's origin
968
        // resource: oldDocState's resource
969
        // ever populated: oldDocState's ever populated
970
        // navigable target name: oldDocState's navigable target name
971
0
        auto new_document_state = navigable->heap().allocate_without_realm<DocumentState>();
972
0
        new_document_state->set_history_policy_container(old_doc_state->history_policy_container());
973
0
        new_document_state->set_request_referrer(old_doc_state->request_referrer());
974
0
        new_document_state->set_request_referrer_policy(old_doc_state->request_referrer_policy());
975
0
        new_document_state->set_origin(old_doc_state->origin());
976
0
        new_document_state->set_resource(old_doc_state->resource());
977
0
        new_document_state->set_ever_populated(old_doc_state->ever_populated());
978
0
        new_document_state->set_navigable_target_name(old_doc_state->navigable_target_name());
979
0
        entry->set_document_state(new_document_state);
980
981
        // 20. If locationURL's scheme is not an HTTP(S) scheme, then:
982
0
        if (!Fetch::Infrastructure::is_http_or_https_scheme(location_url.value()->scheme())) {
983
            // 1. Set entry's document state's resource to null.
984
0
            entry->document_state()->set_resource(Empty {});
985
986
            // 2. Break.
987
0
            break;
988
0
        }
989
990
        // 21. Set currentURL to locationURL.
991
0
        current_url = location_url.value().value();
992
993
        // 22. Set entry's URL to currentURL.
994
0
        entry->set_url(current_url);
995
0
    }
996
997
    // 20. If locationURL is a URL whose scheme is not a fetch scheme, then return a new non-fetch scheme navigation params, with
998
0
    if (!location_url.is_error() && location_url.value().has_value() && !Fetch::Infrastructure::is_fetch_scheme(location_url.value().value().scheme())) {
999
        // - id: navigationId
1000
        // - navigable: navigable
1001
        // - URL: locationURL
1002
        // - target snapshot sandboxing flags: targetSnapshotParams's sandboxing flags
1003
        // - source snapshot has transient activation: sourceSnapshotParams's has transient activation
1004
        // - initiator origin: responseOrigin
1005
        // FIXME: - navigation timing type: navTimingType
1006
0
        auto navigation_params = vm.heap().allocate_without_realm<NonFetchSchemeNavigationParams>();
1007
0
        navigation_params->id = navigation_id;
1008
0
        navigation_params->navigable = navigable;
1009
0
        navigation_params->url = location_url.release_value().value();
1010
0
        navigation_params->target_snapshot_sandboxing_flags = target_snapshot_params.sandboxing_flags;
1011
0
        navigation_params->source_snapshot_has_transient_activation = source_snapshot_params.has_transient_activation;
1012
0
        navigation_params->initiator_origin = move(*response_origin);
1013
0
        return navigation_params;
1014
0
    }
1015
1016
    // 21. If any of the following are true:
1017
    //       - response is a network error;
1018
    //       - locationURL is failure; or
1019
    //       - locationURL is a URL whose scheme is a fetch scheme
1020
    //     then return null.
1021
0
    if (response_holder->response()->is_network_error()) {
1022
        // AD-HOC: We pass the error message if we have one in NullWithError
1023
0
        if (response_holder->response()->network_error_message().has_value() && !response_holder->response()->network_error_message().value().is_null())
1024
0
            return response_holder->response()->network_error_message().value();
1025
0
        else
1026
0
            return Empty {};
1027
0
    } else if (location_url.is_error() || (location_url.value().has_value() && Fetch::Infrastructure::is_fetch_scheme(location_url.value().value().scheme())))
1028
0
        return Empty {};
1029
1030
    // 22. Assert: locationURL is null and response is not a network error.
1031
0
    VERIFY(!location_url.value().has_value());
1032
0
    VERIFY(!response_holder->response()->is_network_error());
1033
1034
    // 23. Let resultPolicyContainer be the result of determining navigation params policy container given response's URL,
1035
    //     entry's document state's history policy container, sourceSnapshotParams's source policy container, null, and responsePolicyContainer.
1036
0
    Optional<PolicyContainer> history_policy_container = entry->document_state()->history_policy_container().visit(
1037
0
        [](PolicyContainer const& c) -> Optional<PolicyContainer> { return c; },
1038
0
        [](DocumentState::Client) -> Optional<PolicyContainer> { return {}; });
1039
0
    auto result_policy_container = determine_navigation_params_policy_container(*response_holder->response()->url(), history_policy_container, source_snapshot_params.source_policy_container, {}, response_policy_container);
1040
1041
    // 24. If navigable's container is an iframe, and response's timing allow passed flag is set, then set container's pending resource-timing start time to null.
1042
0
    if (navigable->container() && is<HTML::HTMLIFrameElement>(*navigable->container()) && response_holder->response()->timing_allow_passed())
1043
0
        static_cast<HTML::HTMLIFrameElement&>(*navigable->container()).set_pending_resource_start_time({});
1044
1045
    // 25. Return a new navigation params, with
1046
    //     id: navigationId
1047
    //     navigable: navigable
1048
    //     request: request
1049
    //     response: response
1050
    //     fetch controller: fetchController
1051
    //     commit early hints: commitEarlyHints
1052
    //     opener policy: responseCOOP
1053
    //     reserved environment: request's reserved client
1054
    //     origin: responseOrigin
1055
    //     policy container: resultPolicyContainer
1056
    //     final sandboxing flag set: finalSandboxFlags
1057
    //     COOP enforcement result: coopEnforcementResult
1058
    //     FIXME: navigation timing type: navTimingType
1059
    //     about base URL: entry's document state's about base URL
1060
0
    auto navigation_params = vm.heap().allocate_without_realm<NavigationParams>();
1061
0
    navigation_params->id = navigation_id;
1062
0
    navigation_params->navigable = navigable;
1063
0
    navigation_params->request = request;
1064
0
    navigation_params->response = *response_holder->response();
1065
0
    navigation_params->fetch_controller = fetch_controller;
1066
0
    navigation_params->commit_early_hints = move(commit_early_hints);
1067
0
    navigation_params->coop_enforcement_result = coop_enforcement_result;
1068
0
    navigation_params->reserved_environment = request->reserved_client();
1069
0
    navigation_params->origin = *response_origin;
1070
0
    navigation_params->policy_container = result_policy_container;
1071
0
    navigation_params->final_sandboxing_flag_set = final_sandbox_flags;
1072
0
    navigation_params->opener_policy = response_coop;
1073
0
    navigation_params->about_base_url = entry->document_state()->about_base_url();
1074
0
    return navigation_params;
1075
0
}
1076
1077
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#attempt-to-populate-the-history-entry's-document
1078
WebIDL::ExceptionOr<void> Navigable::populate_session_history_entry_document(
1079
    JS::GCPtr<SessionHistoryEntry> entry,
1080
    SourceSnapshotParams const& source_snapshot_params,
1081
    TargetSnapshotParams const& target_snapshot_params,
1082
    Optional<String> navigation_id,
1083
    Navigable::NavigationParamsVariant navigation_params,
1084
    CSPNavigationType csp_navigation_type,
1085
    bool allow_POST,
1086
    JS::GCPtr<JS::HeapFunction<void()>> completion_steps)
1087
0
{
1088
    // AD-HOC: Not in the spec but subsequent steps will fail if the navigable doesn't have an active window.
1089
0
    if (!active_window())
1090
0
        return {};
1091
1092
    // FIXME: 1. Assert: this is running in parallel.
1093
1094
    // 2. Assert: if navigationParams is non-null, then navigationParams's response is non-null.
1095
0
    if (!navigation_params.has<Empty>() && !navigation_params.has<NullWithError>())
1096
0
        VERIFY(navigation_params.has<JS::NonnullGCPtr<NavigationParams>>() && navigation_params.get<JS::NonnullGCPtr<NavigationParams>>()->response);
1097
1098
    // 3. Let currentBrowsingContext be navigable's active browsing context.
1099
0
    [[maybe_unused]] auto current_browsing_context = active_browsing_context();
1100
1101
    // 4. Let documentResource be entry's document state's resource.
1102
0
    auto document_resource = entry->document_state()->resource();
1103
1104
    // 5. If navigationParams is null, then:
1105
0
    if (navigation_params.has<Empty>() || navigation_params.has<NullWithError>()) {
1106
        // 1. If documentResource is a string, then set navigationParams to the result
1107
        //    of creating navigation params from a srcdoc resource given entry, navigable,
1108
        //    targetSnapshotParams, navigationId, and navTimingType.
1109
0
        if (document_resource.has<String>()) {
1110
0
            navigation_params = TRY(create_navigation_params_from_a_srcdoc_resource(entry, this, target_snapshot_params, navigation_id));
1111
0
        }
1112
        // 2. Otherwise, if all of the following are true:
1113
        //    - entry's URL's scheme is a fetch scheme; and
1114
        //    - documentResource is null, or allowPOST is true and documentResource's request body is not failure (FIXME: check if request body is not failure)
1115
        // then set navigationParams to the result of creating navigation params by fetching given entry, navigable, sourceSnapshotParams, targetSnapshotParams, cspNavigationType, navigationId, and navTimingType.
1116
0
        else if (Fetch::Infrastructure::is_fetch_scheme(entry->url().scheme()) && (document_resource.has<Empty>() || allow_POST)) {
1117
0
            navigation_params = TRY(create_navigation_params_by_fetching(entry, this, source_snapshot_params, target_snapshot_params, csp_navigation_type, navigation_id));
1118
0
        }
1119
        // 3. Otherwise, if entry's URL's scheme is not a fetch scheme, then set navigationParams to a new non-fetch scheme navigation params, with:
1120
0
        else if (!Fetch::Infrastructure::is_fetch_scheme(entry->url().scheme())) {
1121
            // - id: navigationId
1122
            // - navigable: navigable
1123
            // - URL: entry's URL
1124
            // - target snapshot sandboxing flags: targetSnapshotParams's sandboxing flags
1125
            // - source snapshot has transient activation: sourceSnapshotParams's has transient activation
1126
            // - initiator origin: entry's document state's initiator origin
1127
            // FIXME: - navigation timing type: navTimingType
1128
0
            auto non_fetching_scheme_navigation_params = vm().heap().allocate_without_realm<NonFetchSchemeNavigationParams>();
1129
0
            non_fetching_scheme_navigation_params->id = navigation_id;
1130
0
            non_fetching_scheme_navigation_params->navigable = this;
1131
0
            non_fetching_scheme_navigation_params->url = entry->url();
1132
0
            non_fetching_scheme_navigation_params->target_snapshot_sandboxing_flags = target_snapshot_params.sandboxing_flags;
1133
0
            non_fetching_scheme_navigation_params->source_snapshot_has_transient_activation = source_snapshot_params.has_transient_activation;
1134
0
            non_fetching_scheme_navigation_params->initiator_origin = *entry->document_state()->initiator_origin();
1135
0
            navigation_params = non_fetching_scheme_navigation_params;
1136
0
        }
1137
0
    }
1138
1139
    // AD-HOC: Not in the spec but subsequent steps will fail if the navigable doesn't have an active window.
1140
0
    if (!active_window())
1141
0
        return {};
1142
1143
    // 6. Queue a global task on the navigation and traversal task source, given navigable's active window, to run these steps:
1144
0
    queue_global_task(Task::Source::NavigationAndTraversal, *active_window(), JS::create_heap_function(heap(), [this, entry, navigation_params = move(navigation_params), navigation_id, completion_steps]() mutable {
1145
        // NOTE: This check is not in the spec but we should not continue navigation if navigable has been destroyed.
1146
0
        if (has_been_destroyed())
1147
0
            return;
1148
1149
        // 1. If navigable's ongoing navigation no longer equals navigationId, then run completionSteps and abort these steps.
1150
0
        if (navigation_id.has_value() && (!ongoing_navigation().has<String>() || ongoing_navigation().get<String>() != *navigation_id)) {
1151
0
            if (completion_steps)
1152
0
                completion_steps->function()();
1153
0
            return;
1154
0
        }
1155
1156
        // 2. Let saveExtraDocumentState be true.
1157
0
        auto saveExtraDocumentState = true;
1158
1159
        // 3. If navigationParams is a non-fetch scheme navigation params, then:
1160
0
        if (navigation_params.has<JS::NonnullGCPtr<NonFetchSchemeNavigationParams>>()) {
1161
            // 1. Set entry's document state's document to the result of running attempt to create a non-fetch scheme document given navigationParams.
1162
0
            entry->document_state()->set_document(attempt_to_create_a_non_fetch_scheme_document(navigation_params.get<JS::NonnullGCPtr<NonFetchSchemeNavigationParams>>()));
1163
0
            if (entry->document()) {
1164
0
                entry->document_state()->set_ever_populated(true);
1165
0
            }
1166
1167
            // 2. Set saveExtraDocumentState to false.
1168
0
            saveExtraDocumentState = false;
1169
0
        }
1170
        // 4. Otherwise, if any of the following are true:
1171
        //  - navigationParams is null;
1172
        //  - FIXME: the result of should navigation response to navigation request of type in target be blocked by Content Security Policy? given navigationParams's request, navigationParams's response, navigationParams's policy container's CSP list, cspNavigationType, and navigable is "Blocked";
1173
        //  - FIXME: navigationParams's reserved environment is non-null and the result of checking a navigation response's adherence to its embedder policy given navigationParams's response, navigable, and navigationParams's policy container's embedder policy is false; or
1174
        //  - FIXME: the result of checking a navigation response's adherence to `X-Frame-Options` given navigationParams's response, navigable, navigationParams's policy container's CSP list, and navigationParams's origin is false,
1175
0
        else if (navigation_params.has<Empty>() || navigation_params.has<NullWithError>()) {
1176
            // 1. Set entry's document state's document to the result of creating a document for inline content that doesn't have a DOM, given navigable, null, and navTimingType. The inline content should indicate to the user the sort of error that occurred.
1177
0
            auto error_message = navigation_params.has<NullWithError>() ? navigation_params.get<NullWithError>() : "Unknown error"sv;
1178
1179
0
            auto error_html = load_error_page(entry->url(), error_message).release_value_but_fixme_should_propagate_errors();
1180
0
            entry->document_state()->set_document(create_document_for_inline_content(this, navigation_id, [error_html](auto& document) {
1181
0
                auto parser = HTML::HTMLParser::create(document, error_html, "utf-8"sv);
1182
0
                document.set_url(URL::URL("about:error"));
1183
0
                parser->run();
1184
0
            }));
1185
1186
            // 2. Make document unsalvageable given entry's document state's document and "navigation-failure".
1187
0
            entry->document()->make_unsalvageable("navigation-failure"_string);
1188
1189
            // 3. Set saveExtraDocumentState to false.
1190
0
            saveExtraDocumentState = false;
1191
1192
            // 4. If navigationParams is not null, then:
1193
0
            if (!navigation_params.has<Empty>() && !navigation_params.has<NullWithError>()) {
1194
                // FIXME: 1. Run the environment discarding steps for navigationParams's reserved environment.
1195
                // FIXME: 2. Invoke WebDriver BiDi navigation failed with currentBrowsingContext and a new WebDriver BiDi navigation status whose id is navigationId, status is "canceled", and url is navigationParams's response's URL.
1196
0
            }
1197
0
        }
1198
        // FIXME: 5. Otherwise, if navigationParams's response has a `Content-Disposition`
1199
        //            header specifying the attachment disposition type, then:
1200
        // 6. Otherwise, if navigationParams's response's status is not 204 and is not 205, then set entry's document state's document to the result of
1201
        //    loading a document given navigationParams, sourceSnapshotParams, and entry's document state's initiator origin.
1202
0
        else if (auto const& response = navigation_params.get<JS::NonnullGCPtr<NavigationParams>>()->response; response->status() != 204 && response->status() != 205) {
1203
0
            auto document = load_document(navigation_params.get<JS::NonnullGCPtr<NavigationParams>>());
1204
0
            entry->document_state()->set_document(document);
1205
0
        }
1206
1207
        // 7. If entry's document state's document is not null, then:
1208
0
        if (entry->document()) {
1209
            // 1. Set entry's document state's ever populated to true.
1210
0
            entry->document_state()->set_ever_populated(true);
1211
1212
            // 2. If saveExtraDocumentState is true:
1213
0
            if (saveExtraDocumentState) {
1214
                // 1. Let document be entry's document state's document.
1215
0
                auto document = entry->document();
1216
1217
                // 2. Set entry's document state's origin to document's origin.
1218
0
                entry->document_state()->set_origin(document->origin());
1219
1220
                // FIXME: 3. If document's URL requires storing the policy container in history, then:
1221
0
            }
1222
1223
            // 3. If entry's document state's request referrer is "client", and navigationParams is a navigation params (i.e., neither null nor a non-fetch scheme navigation params), then:
1224
0
            if (entry->document_state()->request_referrer() == Fetch::Infrastructure::Request::Referrer::Client
1225
0
                && (!navigation_params.has<Empty>() && !navigation_params.has<NullWithError>() && navigation_params.has<JS::NonnullGCPtr<NonFetchSchemeNavigationParams>>())) {
1226
                // 1. Assert: navigationParams's request is not null.
1227
0
                VERIFY(navigation_params.has<JS::NonnullGCPtr<NavigationParams>>() && navigation_params.get<JS::NonnullGCPtr<NavigationParams>>()->request);
1228
1229
                // 2. Set entry's document state's request referrer to navigationParams's request's referrer.
1230
0
                entry->document_state()->set_request_referrer(navigation_params.get<JS::NonnullGCPtr<NavigationParams>>()->request->referrer());
1231
0
            }
1232
0
        }
1233
1234
        // 8. Run completionSteps.
1235
0
        if (completion_steps)
1236
0
            completion_steps->function()();
1237
0
    }));
1238
1239
0
    return {};
1240
0
}
1241
1242
// To navigate a navigable navigable to a URL url using a Document sourceDocument,
1243
// with an optional POST resource, string, or null documentResource (default null),
1244
// an optional response-or-null response (default null), an optional boolean exceptionsEnabled (default false),
1245
// an optional NavigationHistoryBehavior historyHandling (default "auto"),
1246
// an optional serialized state-or-null navigationAPIState (default null),
1247
// an optional entry list or null formDataEntryList (default null),
1248
// an optional referrer policy referrerPolicy (default the empty string),
1249
// and an optional user navigation involvement userInvolvement (default "none"):
1250
1251
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigate
1252
WebIDL::ExceptionOr<void> Navigable::navigate(NavigateParams params)
1253
0
{
1254
    // AD-HOC: Not in the spec but subsequent steps will fail if the navigable doesn't have an active window.
1255
0
    if (!active_window())
1256
0
        return {};
1257
1258
0
    auto const& url = params.url;
1259
0
    auto source_document = params.source_document;
1260
0
    auto const& document_resource = params.document_resource;
1261
0
    auto response = params.response;
1262
0
    auto exceptions_enabled = params.exceptions_enabled;
1263
0
    auto history_handling = params.history_handling;
1264
0
    auto const& navigation_api_state = params.navigation_api_state;
1265
0
    auto const& form_data_entry_list = params.form_data_entry_list;
1266
0
    auto referrer_policy = params.referrer_policy;
1267
0
    auto user_involvement = params.user_involvement;
1268
0
    auto& active_document = *this->active_document();
1269
0
    auto& realm = active_document.realm();
1270
0
    auto& vm = this->vm();
1271
1272
    // 1. Let cspNavigationType be "form-submission" if formDataEntryList is non-null; otherwise "other".
1273
0
    auto csp_navigation_type = form_data_entry_list.has_value() ? CSPNavigationType::FormSubmission : CSPNavigationType::Other;
1274
1275
    // 2. Let sourceSnapshotParams be the result of snapshotting source snapshot params given sourceDocument.
1276
0
    auto source_snapshot_params = source_document->snapshot_source_snapshot_params();
1277
1278
    // 3. Let initiatorOriginSnapshot be sourceDocument's origin.
1279
0
    auto initiator_origin_snapshot = source_document->origin();
1280
1281
    // 4. Let initiatorBaseURLSnapshot be sourceDocument's document base URL.
1282
0
    auto initiator_base_url_snapshot = source_document->base_url();
1283
1284
    // 5. If sourceDocument's node navigable is not allowed by sandboxing to navigate navigable given and sourceSnapshotParams, then:
1285
0
    if (!source_document->navigable()->allowed_by_sandboxing_to_navigate(*this, source_snapshot_params)) {
1286
        // 1. If exceptionsEnabled is true, then throw a "SecurityError" DOMException.
1287
0
        if (exceptions_enabled) {
1288
0
            return WebIDL::SecurityError::create(realm, "Source document's node navigable is not allowed to navigate"_string);
1289
0
        }
1290
1291
        // 2 Return.
1292
0
        return {};
1293
0
    }
1294
1295
    // 6. Let navigationId be the result of generating a random UUID.
1296
0
    String navigation_id = TRY_OR_THROW_OOM(vm, Crypto::generate_random_uuid());
1297
1298
    // FIXME: 7. If the surrounding agent is equal to navigable's active document's relevant agent, then continue these steps.
1299
    //           Otherwise, queue a global task on the navigation and traversal task source given navigable's active window to continue these steps.
1300
1301
    // 8. If navigable's active document's unload counter is greater than 0,
1302
    //    then invoke WebDriver BiDi navigation failed with a WebDriver BiDi navigation status whose id is navigationId,
1303
    //    status is "canceled", and url is url, and return.
1304
0
    if (active_document.unload_counter() > 0) {
1305
        // FIXME: invoke WebDriver BiDi navigation failed with a WebDriver BiDi navigation status whose id is navigationId,
1306
        //        status is "canceled", and url is url
1307
0
        return {};
1308
0
    }
1309
1310
    // 9. If historyHandling is "auto", then:
1311
0
    if (history_handling == Bindings::NavigationHistoryBehavior::Auto) {
1312
        // FIXME: Fix spec typo targetNavigable --> navigable
1313
        // 1. If url equals navigable's active document's URL,
1314
        //     and initiatorOriginSnapshot is same origin with targetNavigable's active document's origin,
1315
        //     then set historyHandling to "replace".
1316
0
        if (url == active_document.url() && initiator_origin_snapshot.is_same_origin(active_document.origin()))
1317
0
            history_handling = Bindings::NavigationHistoryBehavior::Replace;
1318
1319
        // 2. Otherwise, set historyHandling to "push".
1320
0
        else
1321
0
            history_handling = Bindings::NavigationHistoryBehavior::Push;
1322
0
    }
1323
1324
    // 10. If the navigation must be a replace given url and navigable's active document, then set historyHandling to "replace".
1325
0
    if (navigation_must_be_a_replace(url, active_document))
1326
0
        history_handling = Bindings::NavigationHistoryBehavior::Replace;
1327
1328
    // 11. If all of the following are true:
1329
    //    - documentResource is null;
1330
    //    - response is null;
1331
    //    - url equals navigable's active session history entry's URL with exclude fragments set to true; and
1332
    //    - url's fragment is non-null
1333
0
    if (document_resource.has<Empty>()
1334
0
        && !response
1335
0
        && url.equals(active_session_history_entry()->url(), URL::ExcludeFragment::Yes)
1336
0
        && url.fragment().has_value()) {
1337
        // 1. Navigate to a fragment given navigable, url, historyHandling, userInvolvement, navigationAPIState, and navigationId.
1338
0
        TRY(navigate_to_a_fragment(url, to_history_handling_behavior(history_handling), user_involvement, navigation_api_state, navigation_id));
1339
1340
        // 2. Return.
1341
0
        return {};
1342
0
    }
1343
1344
    // 12. If navigable's parent is non-null, then set navigable's is delaying load events to true.
1345
0
    if (parent() != nullptr)
1346
0
        set_delaying_load_events(true);
1347
1348
    // 13. Let targetBrowsingContext be navigable's active browsing context.
1349
0
    [[maybe_unused]] auto target_browsing_context = active_browsing_context();
1350
1351
    // 14. Let targetSnapshotParams be the result of snapshotting target snapshot params given navigable.
1352
0
    auto target_snapshot_params = snapshot_target_snapshot_params();
1353
1354
    // FIXME: 15. Invoke WebDriver BiDi navigation started with targetBrowsingContext, and a new WebDriver BiDi navigation status whose id is navigationId, url is url, and status is "pending".
1355
1356
    // 16. If navigable's ongoing navigation is "traversal", then:
1357
0
    if (ongoing_navigation().has<Traversal>()) {
1358
        // FIXME: 1. Invoke WebDriver BiDi navigation failed with targetBrowsingContext and a new WebDriver BiDi navigation status whose id is navigationId, status is "canceled", and url is url.
1359
1360
        // 2. Return.
1361
0
        return {};
1362
0
    }
1363
1364
    // 17. Set navigable's ongoing navigation to navigationId.
1365
0
    set_ongoing_navigation(navigation_id);
1366
1367
    // 18. If url's scheme is "javascript", then:
1368
0
    if (url.scheme() == "javascript"sv) {
1369
        // 1. Queue a global task on the navigation and traversal task source given navigable's active window to navigate to a javascript: URL given navigable, url, historyHandling, initiatorOriginSnapshot, and cspNavigationType.
1370
0
        VERIFY(active_window());
1371
0
        queue_global_task(Task::Source::NavigationAndTraversal, *active_window(), JS::create_heap_function(heap(), [this, url, history_handling, initiator_origin_snapshot, csp_navigation_type, navigation_id] {
1372
0
            (void)navigate_to_a_javascript_url(url, to_history_handling_behavior(history_handling), initiator_origin_snapshot, csp_navigation_type, navigation_id);
1373
0
        }));
1374
1375
        // 2. Return.
1376
0
        return {};
1377
0
    }
1378
1379
    // 19. If all of the following are true:
1380
    //     - userInvolvement is not "browser UI";
1381
    //     - navigable's active document's origin is same origin-domain with sourceDocument's origin;
1382
    //     - navigable's active document's is initial about:blank is false; and
1383
    //     - url's scheme is a fetch scheme
1384
    //     then:
1385
0
    if (user_involvement != UserNavigationInvolvement::BrowserUI && active_document.origin().is_same_origin_domain(source_document->origin()) && !active_document.is_initial_about_blank() && Fetch::Infrastructure::is_fetch_scheme(url.scheme())) {
1386
        // 1. Let navigation be navigable's active window's navigation API.
1387
0
        VERIFY(active_window());
1388
0
        auto navigation = active_window()->navigation();
1389
1390
        // 2. Let entryListForFiring be formDataEntryList if documentResource is a POST resource; otherwise, null.
1391
0
        auto entry_list_for_firing = [&]() -> Optional<Vector<XHR::FormDataEntry>&> {
1392
0
            if (document_resource.has<POSTResource>())
1393
0
                return form_data_entry_list;
1394
0
            return {};
1395
0
        }();
1396
1397
        // 3. Let navigationAPIStateForFiring be navigationAPIState if navigationAPIState is not null;
1398
        //    otherwise, StructuredSerializeForStorage(undefined).
1399
0
        auto navigation_api_state_for_firing = navigation_api_state.value_or(MUST(structured_serialize_for_storage(vm, JS::js_undefined())));
1400
1401
        // FIXME: 4. Let continue be the result of firing a push/replace/reload navigate event at navigation
1402
        //           with navigationType set to historyHandling, isSameDocument set to false, userInvolvement set to userInvolvement,
1403
        //           formDataEntryList set to entryListForFiring, destinationURL set to url, and navigationAPIState set to navigationAPIStateForFiring.
1404
0
        (void)navigation;
1405
0
        (void)entry_list_for_firing;
1406
0
        (void)navigation_api_state_for_firing;
1407
1408
        // FIXME: 5. If continue is false, then return.
1409
0
    }
1410
1411
0
    if (is_top_level_traversable()) {
1412
0
        active_browsing_context()->page().client().page_did_start_loading(url, false);
1413
0
    }
1414
1415
    // 20. In parallel, run these steps:
1416
0
    Platform::EventLoopPlugin::the().deferred_invoke([this, source_snapshot_params, target_snapshot_params, csp_navigation_type, document_resource, url, navigation_id, referrer_policy, initiator_origin_snapshot, response, history_handling, initiator_base_url_snapshot] {
1417
        // AD-HOC: Not in the spec but subsequent steps will fail if the navigable doesn't have an active window.
1418
0
        if (!active_window()) {
1419
0
            set_delaying_load_events(false);
1420
0
            return;
1421
0
        }
1422
1423
        // 1. Let unloadPromptCanceled be the result of checking if unloading is user-canceled for navigable's active document's inclusive descendant navigables.
1424
0
        auto unload_prompt_canceled = traversable_navigable()->check_if_unloading_is_canceled(this->active_document()->inclusive_descendant_navigables());
1425
1426
        // 2. If unloadPromptCanceled is true, or navigable's ongoing navigation is no longer navigationId, then:
1427
0
        if (unload_prompt_canceled != TraversableNavigable::CheckIfUnloadingIsCanceledResult::Continue || !ongoing_navigation().has<String>() || ongoing_navigation().get<String>() != navigation_id) {
1428
            // FIXME: 1. Invoke WebDriver BiDi navigation failed with targetBrowsingContext and a new WebDriver BiDi navigation status whose id is navigationId, status is "canceled", and url is url.
1429
1430
            // 2. Abort these steps.
1431
0
            set_delaying_load_events(false);
1432
0
            return;
1433
0
        }
1434
1435
        // AD-HOC: Not in the spec but subsequent steps will fail if the navigable doesn't have an active window.
1436
0
        if (!active_window()) {
1437
0
            set_delaying_load_events(false);
1438
0
            return;
1439
0
        }
1440
1441
        // 3. Queue a global task on the navigation and traversal task source given navigable's active window to abort a document and its descendants given navigable's active document.
1442
0
        queue_global_task(Task::Source::NavigationAndTraversal, *active_window(), JS::create_heap_function(heap(), [this] {
1443
0
            VERIFY(this->active_document());
1444
0
            this->active_document()->abort_a_document_and_its_descendants();
1445
0
        }));
1446
1447
        // 4. Let documentState be a new document state with
1448
        //    request referrer policy: referrerPolicy
1449
        //    initiator origin: initiatorOriginSnapshot
1450
        //    resource: documentResource
1451
        //    navigable target name: navigable's target name
1452
0
        JS::NonnullGCPtr<DocumentState> document_state = *heap().allocate_without_realm<DocumentState>();
1453
0
        document_state->set_request_referrer_policy(referrer_policy);
1454
0
        document_state->set_initiator_origin(initiator_origin_snapshot);
1455
0
        document_state->set_resource(document_resource);
1456
0
        document_state->set_navigable_target_name(target_name());
1457
1458
        // 5. If url matches about:blank or is about:srcdoc, then set documentState's origin to documentState's initiator origin.
1459
0
        if (url_matches_about_blank(url) || url_matches_about_srcdoc(url)) {
1460
            // document_resource cannot have an Empty if the url is about:srcdoc since we rely on document_resource
1461
            // having a String to call create_navigation_params_from_a_srcdoc_resource
1462
0
            if (url_matches_about_srcdoc(url) && document_resource.has<Empty>()) {
1463
0
                document_state->set_resource({ String {} });
1464
0
            }
1465
            // 1. Set documentState's origin to initiatorOriginSnapshot.
1466
0
            document_state->set_origin(document_state->initiator_origin());
1467
1468
            // 2. Set documentState's about base URL to initiatorBaseURLSnapshot.
1469
0
            document_state->set_about_base_url(initiator_base_url_snapshot);
1470
0
        }
1471
1472
        // 6. Let historyEntry be a new session history entry, with its URL set to url and its document state set to documentState.
1473
0
        JS::NonnullGCPtr<SessionHistoryEntry> history_entry = *heap().allocate_without_realm<SessionHistoryEntry>();
1474
0
        history_entry->set_url(url);
1475
0
        history_entry->set_document_state(document_state);
1476
1477
        // 7. Let navigationParams be null.
1478
0
        NavigationParamsVariant navigation_params = Empty {};
1479
1480
        // FIXME: 8. If response is non-null:
1481
0
        if (response) {
1482
0
        }
1483
1484
        // 9. Attempt to populate the history entry's document
1485
        //     for historyEntry, given navigable, "navigate", sourceSnapshotParams,
1486
        //     targetSnapshotParams, navigationId, navigationParams, cspNavigationType, with allowPOST
1487
        //     set to true and completionSteps set to the following step:
1488
0
        populate_session_history_entry_document(history_entry, source_snapshot_params, target_snapshot_params, navigation_id, navigation_params, csp_navigation_type, true, JS::create_heap_function(heap(), [this, history_entry, history_handling, navigation_id] {
1489
            // 1.     Append session history traversal steps to navigable's traversable to finalize a cross-document navigation given navigable, historyHandling, and historyEntry.
1490
0
            traversable_navigable()->append_session_history_traversal_steps(JS::create_heap_function(heap(), [this, history_entry, history_handling, navigation_id] {
1491
0
                if (this->has_been_destroyed()) {
1492
                    // NOTE: This check is not in the spec but we should not continue navigation if navigable has been destroyed.
1493
0
                    set_delaying_load_events(false);
1494
0
                    return;
1495
0
                }
1496
0
                if (this->ongoing_navigation() != navigation_id) {
1497
                    // NOTE: This check is not in the spec but we should not continue navigation if ongoing navigation id has changed.
1498
0
                    set_delaying_load_events(false);
1499
0
                    return;
1500
0
                }
1501
0
                finalize_a_cross_document_navigation(*this, to_history_handling_behavior(history_handling), history_entry);
1502
0
            }));
1503
0
        })).release_value_but_fixme_should_propagate_errors();
1504
0
    });
1505
1506
0
    return {};
1507
0
}
1508
1509
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigate-fragid
1510
WebIDL::ExceptionOr<void> Navigable::navigate_to_a_fragment(URL::URL const& url, HistoryHandlingBehavior history_handling, UserNavigationInvolvement user_involvement, Optional<SerializationRecord> navigation_api_state, String navigation_id)
1511
0
{
1512
0
    (void)navigation_id;
1513
1514
    // 1. Let navigation be navigable's active window's navigation API.
1515
0
    VERIFY(active_window());
1516
0
    auto navigation = active_window()->navigation();
1517
1518
    // 2. Let destinationNavigationAPIState be navigable's active session history entry's navigation API state.
1519
    // 3. If navigationAPIState is not null, then set destinationNavigationAPIState to navigationAPIState.
1520
0
    auto destination_navigation_api_state = navigation_api_state.has_value() ? *navigation_api_state : active_session_history_entry()->navigation_api_state();
1521
1522
    // 4. Let continue be the result of firing a push/replace/reload navigate event at navigation with navigationType set to historyHandling, isSameDocument set to true,
1523
    //    userInvolvement set to userInvolvement, and destinationURL set to url, and navigationAPIState set to destinationNavigationAPIState.
1524
0
    auto navigation_type = history_handling == HistoryHandlingBehavior::Push ? Bindings::NavigationType::Push : Bindings::NavigationType::Replace;
1525
0
    bool const continue_ = navigation->fire_a_push_replace_reload_navigate_event(navigation_type, url, true, user_involvement, {}, destination_navigation_api_state);
1526
1527
    // 5. If continue is false, then return.
1528
0
    if (!continue_)
1529
0
        return {};
1530
1531
    // 6. Let historyEntry be a new session history entry, with
1532
    //      URL: url
1533
    //      document state: navigable's active session history entry's document state
1534
    //      navigation API state: destinationNavigationAPIState
1535
    //      scroll restoration mode: navigable's active session history entry's scroll restoration mode
1536
0
    JS::NonnullGCPtr<SessionHistoryEntry> history_entry = heap().allocate_without_realm<SessionHistoryEntry>();
1537
0
    history_entry->set_url(url);
1538
0
    history_entry->set_document_state(active_session_history_entry()->document_state());
1539
0
    history_entry->set_navigation_api_state(destination_navigation_api_state);
1540
0
    history_entry->set_scroll_restoration_mode(active_session_history_entry()->scroll_restoration_mode());
1541
1542
    // 7. Let entryToReplace be navigable's active session history entry if historyHandling is "replace", otherwise null.
1543
0
    auto entry_to_replace = history_handling == HistoryHandlingBehavior::Replace ? active_session_history_entry() : nullptr;
1544
1545
    // 8. Let history be navigable's active document's history object.
1546
0
    auto history = active_document()->history();
1547
1548
    // 9. Let scriptHistoryIndex be history's index.
1549
0
    auto script_history_index = history->m_index;
1550
1551
    // 10. Let scriptHistoryLength be history's length.
1552
0
    auto script_history_length = history->m_length;
1553
1554
    // 11. If historyHandling is "push", then:
1555
0
    if (history_handling == HistoryHandlingBehavior::Push) {
1556
        // 1. Set history's state to null.
1557
0
        history->set_state(JS::js_null());
1558
1559
        // 2. Increment scriptHistoryIndex.
1560
0
        script_history_index++;
1561
1562
        // 3. Set scriptHistoryLength to scriptHistoryIndex + 1.
1563
0
        script_history_length = script_history_index + 1;
1564
0
    }
1565
1566
    // 12. Set navigable's active session history entry to historyEntry.
1567
0
    m_active_session_history_entry = history_entry;
1568
1569
    // 13. Update document for history step application given navigable's active document, historyEntry, true, scriptHistoryIndex, and scriptHistoryLength.
1570
    // AD HOC: Skip updating the navigation api entries twice here
1571
0
    active_document()->update_for_history_step_application(*history_entry, true, script_history_length, script_history_index, navigation_type, {}, {}, false);
1572
1573
    // 14. Update the navigation API entries for a same-document navigation given navigation, historyEntry, and historyHandling.
1574
0
    navigation->update_the_navigation_api_entries_for_a_same_document_navigation(history_entry, navigation_type);
1575
1576
    // 15. Scroll to the fragment given navigable's active document.
1577
    // FIXME: Specification doesn't say when document url needs to update during fragment navigation
1578
0
    active_document()->set_url(url);
1579
0
    active_document()->scroll_to_the_fragment();
1580
1581
    // 16. Let traversable be navigable's traversable navigable.
1582
0
    auto traversable = traversable_navigable();
1583
1584
    // 17. Append the following session history synchronous navigation steps involving navigable to traversable:
1585
0
    traversable->append_session_history_synchronous_navigation_steps(*this, JS::create_heap_function(heap(), [this, traversable, history_entry, entry_to_replace, navigation_id, history_handling] {
1586
        // 1. Finalize a same-document navigation given traversable, navigable, historyEntry, and entryToReplace.
1587
0
        finalize_a_same_document_navigation(*traversable, *this, history_entry, entry_to_replace, history_handling);
1588
1589
        // FIXME: 2. Invoke WebDriver BiDi fragment navigated with navigable's active browsing context and a new WebDriver BiDi
1590
        //            navigation status whose id is navigationId, url is url, and status is "complete".
1591
0
        (void)navigation_id;
1592
0
    }));
1593
1594
0
    return {};
1595
0
}
1596
1597
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#evaluate-a-javascript:-url
1598
WebIDL::ExceptionOr<JS::GCPtr<DOM::Document>> Navigable::evaluate_javascript_url(URL::URL const& url, URL::Origin const& new_document_origin, String navigation_id)
1599
0
{
1600
0
    auto& vm = this->vm();
1601
0
    VERIFY(active_window());
1602
0
    auto& realm = active_window()->realm();
1603
1604
    // 1. Let urlString be the result of running the URL serializer on url.
1605
0
    auto url_string = url.serialize();
1606
1607
    // 2. Let encodedScriptSource be the result of removing the leading "javascript:" from urlString.
1608
0
    auto encoded_script_source = url_string.substring_view(11, url_string.length() - 11);
1609
1610
    // 3. Let scriptSource be the UTF-8 decoding of the percent-decoding of encodedScriptSource.
1611
0
    auto script_source = URL::percent_decode(encoded_script_source);
1612
1613
    // 4. Let settings be targetNavigable's active document's relevant settings object.
1614
0
    auto& settings = active_document()->relevant_settings_object();
1615
1616
    // 5. Let baseURL be settings's API base URL.
1617
0
    auto base_url = settings.api_base_url();
1618
1619
    // 6. Let script be the result of creating a classic script given scriptSource, settings, baseURL, and the default classic script fetch options.
1620
0
    auto script = HTML::ClassicScript::create("(javascript url)", script_source, settings, base_url);
1621
1622
    // 7. Let evaluationStatus be the result of running the classic script script.
1623
0
    auto evaluation_status = script->run();
1624
1625
    // 8. Let result be null.
1626
0
    String result;
1627
1628
    // 9. If evaluationStatus is a normal completion, and evaluationStatus.[[Value]] is a String, then set result to evaluationStatus.[[Value]].
1629
0
    if (evaluation_status.type() == JS::Completion::Type::Normal && evaluation_status.value().has_value() && evaluation_status.value()->is_string()) {
1630
0
        result = evaluation_status.value()->as_string().utf8_string();
1631
0
    } else {
1632
        // 10. Otherwise, return null.
1633
0
        return nullptr;
1634
0
    }
1635
1636
    // 11. Let response be a new response with
1637
    //     URL: targetNavigable's active document's URL
1638
    //     header list: «(`Content-Type`, `text/html;charset=utf-8`)»
1639
    //     body: the UTF-8 encoding of result, as a body
1640
0
    auto response = Fetch::Infrastructure::Response::create(vm);
1641
0
    response->url_list().append(active_document()->url());
1642
1643
0
    auto header = Fetch::Infrastructure::Header::from_string_pair("Content-Type"sv, "text/html"sv);
1644
0
    response->header_list()->append(move(header));
1645
1646
0
    response->set_body(TRY(Fetch::Infrastructure::byte_sequence_as_body(realm, result.bytes())));
1647
1648
    // 12. Let policyContainer be targetNavigable's active document's policy container.
1649
0
    auto const& policy_container = active_document()->policy_container();
1650
1651
    // FIXME: 13. Let finalSandboxFlags be policyContainer's CSP list's CSP-derived sandboxing flags.
1652
0
    auto final_sandbox_flags = SandboxingFlagSet {};
1653
1654
    // 14. Let coop be targetNavigable's active document's opener policy.
1655
0
    auto const& coop = active_document()->opener_policy();
1656
1657
    // 15. Let coopEnforcementResult be a new opener policy enforcement result with
1658
    //     url: url
1659
    //     origin: newDocumentOrigin
1660
    //     opener policy: coop
1661
0
    OpenerPolicyEnforcementResult coop_enforcement_result {
1662
0
        .url = url,
1663
0
        .origin = new_document_origin,
1664
0
        .opener_policy = coop,
1665
0
    };
1666
1667
    // 16. Let navigationParams be a new navigation params, with
1668
    //     id: navigationId
1669
    //     navigable: targetNavigable
1670
    //     request: null
1671
    //     response: response
1672
    //     fetch controller: null
1673
    //     commit early hints: null
1674
    //     COOP enforcement result: coopEnforcementResult
1675
    //     reserved environment: null
1676
    //     origin: newDocumentOrigin
1677
    //     policy container: policyContainer
1678
    //     final sandboxing flag set: finalSandboxFlags
1679
    //     opener policy: coop
1680
    // FIXME: navigation timing type: "navigate"
1681
    //     about base URL: targetNavigable's active document's about base URL
1682
0
    auto navigation_params = vm.heap().allocate_without_realm<NavigationParams>();
1683
0
    navigation_params->id = navigation_id;
1684
0
    navigation_params->navigable = this;
1685
0
    navigation_params->request = {};
1686
0
    navigation_params->response = response;
1687
0
    navigation_params->fetch_controller = nullptr;
1688
0
    navigation_params->commit_early_hints = nullptr;
1689
0
    navigation_params->coop_enforcement_result = move(coop_enforcement_result);
1690
0
    navigation_params->reserved_environment = {};
1691
0
    navigation_params->origin = new_document_origin;
1692
0
    navigation_params->policy_container = policy_container;
1693
0
    navigation_params->final_sandboxing_flag_set = final_sandbox_flags;
1694
0
    navigation_params->opener_policy = coop;
1695
0
    navigation_params->about_base_url = active_document()->about_base_url();
1696
1697
    // 17. Return the result of loading an HTML document given navigationParams.
1698
0
    return load_document(navigation_params);
1699
0
}
1700
1701
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigate-to-a-javascript:-url
1702
WebIDL::ExceptionOr<void> Navigable::navigate_to_a_javascript_url(URL::URL const& url, HistoryHandlingBehavior history_handling, URL::Origin const& initiator_origin, CSPNavigationType csp_navigation_type, String navigation_id)
1703
0
{
1704
    // 1. Assert: historyHandling is "replace".
1705
0
    VERIFY(history_handling == HistoryHandlingBehavior::Replace);
1706
1707
    // 2. Set the ongoing navigation for targetNavigable to null.
1708
0
    set_ongoing_navigation({});
1709
1710
    // 3. If initiatorOrigin is not same origin-domain with targetNavigable's active document's origin, then return.
1711
0
    if (!initiator_origin.is_same_origin_domain(active_document()->origin()))
1712
0
        return {};
1713
1714
    // FIXME: 4. Let request be a new request whose URL is url.
1715
1716
    // FIXME: 5. If the result of should navigation request of type be blocked by Content Security Policy? given request and cspNavigationType is "Blocked", then return.
1717
0
    (void)csp_navigation_type;
1718
1719
    // 6. Let newDocument be the result of evaluating a javascript: URL given targetNavigable, url, and initiatorOrigin.
1720
0
    auto new_document = TRY(evaluate_javascript_url(url, initiator_origin, navigation_id));
1721
1722
    // 7. If newDocument is null, then return.
1723
0
    if (!new_document) {
1724
        // NOTE: In this case, some JavaScript code was executed, but no new Document was created, so we will not perform a navigation.
1725
0
        return {};
1726
0
    }
1727
1728
    // 8. Assert: initiatorOrigin is newDocument's origin.
1729
0
    VERIFY(initiator_origin == new_document->origin());
1730
1731
    // 9. Let entryToReplace be targetNavigable's active session history entry.
1732
0
    auto entry_to_replace = active_session_history_entry();
1733
1734
    // 10. Let oldDocState be entryToReplace's document state.
1735
0
    auto old_doc_state = entry_to_replace->document_state();
1736
1737
    // 11. Let documentState be a new document state with
1738
    //     document: newDocument
1739
    //     history policy container: a clone of the oldDocState's history policy container if it is non-null; null otherwise
1740
    //     request referrer: oldDocState's request referrer
1741
    //     request referrer policy: oldDocState's request referrer policy
1742
    //     initiator origin: initiatorOrigin
1743
    //     origin: initiatorOrigin
1744
    //     about base URL: oldDocState's about base URL
1745
    //     resource: null
1746
    //     ever populated: true
1747
    //     navigable target name: oldDocState's navigable target name
1748
0
    JS::NonnullGCPtr<DocumentState> document_state = *heap().allocate_without_realm<DocumentState>();
1749
0
    document_state->set_document(new_document);
1750
0
    document_state->set_history_policy_container(old_doc_state->history_policy_container());
1751
0
    document_state->set_request_referrer(old_doc_state->request_referrer());
1752
0
    document_state->set_request_referrer_policy(old_doc_state->request_referrer_policy());
1753
0
    document_state->set_initiator_origin(initiator_origin);
1754
0
    document_state->set_origin(initiator_origin);
1755
0
    document_state->set_about_base_url(old_doc_state->about_base_url());
1756
0
    document_state->set_ever_populated(true);
1757
0
    document_state->set_navigable_target_name(old_doc_state->navigable_target_name());
1758
1759
    // 12. Let historyEntry be a new session history entry, with
1760
    //     URL: entryToReplace's URL
1761
    //     document state: documentState
1762
0
    JS::NonnullGCPtr<SessionHistoryEntry> history_entry = *heap().allocate_without_realm<SessionHistoryEntry>();
1763
0
    history_entry->set_url(entry_to_replace->url());
1764
0
    history_entry->set_document_state(document_state);
1765
1766
    // 13. Append session history traversal steps to targetNavigable's traversable to finalize a cross-document navigation with targetNavigable, historyHandling, and historyEntry.
1767
0
    traversable_navigable()->append_session_history_traversal_steps(JS::create_heap_function(heap(), [this, history_entry, history_handling, navigation_id] {
1768
0
        finalize_a_cross_document_navigation(*this, history_handling, history_entry);
1769
0
    }));
1770
1771
0
    return {};
1772
0
}
1773
1774
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#reload
1775
void Navigable::reload()
1776
0
{
1777
    // 1. Set navigable's active session history entry's document state's reload pending to true.
1778
0
    active_session_history_entry()->document_state()->set_reload_pending(true);
1779
1780
    // 2. Let traversable be navigable's traversable navigable.
1781
0
    auto traversable = traversable_navigable();
1782
1783
    // 3. Append the following session history traversal steps to traversable:
1784
0
    traversable->append_session_history_traversal_steps(JS::create_heap_function(heap(), [traversable] {
1785
        // 1. Apply the reload history step to traversable.
1786
0
        traversable->apply_the_reload_history_step();
1787
0
    }));
1788
0
}
1789
1790
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#the-navigation-must-be-a-replace
1791
bool navigation_must_be_a_replace(URL::URL const& url, DOM::Document const& document)
1792
0
{
1793
0
    return url.scheme() == "javascript"sv || document.is_initial_about_blank();
1794
0
}
1795
1796
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#allowed-to-navigate
1797
bool Navigable::allowed_by_sandboxing_to_navigate(Navigable const& target, SourceSnapshotParams const& source_snapshot_params)
1798
0
{
1799
0
    auto& source = *this;
1800
1801
0
    auto is_ancestor_of = [](Navigable const& a, Navigable const& b) {
1802
0
        for (auto parent = b.parent(); parent; parent = parent->parent()) {
1803
0
            if (parent.ptr() == &a)
1804
0
                return true;
1805
0
        }
1806
0
        return false;
1807
0
    };
1808
1809
    // A navigable source is allowed by sandboxing to navigate a second navigable target,
1810
    // given a source snapshot params sourceSnapshotParams, if the following steps return true:
1811
1812
    // 1. If source is target, then return true.
1813
0
    if (&source == &target)
1814
0
        return true;
1815
1816
    // 2. If source is an ancestor of target, then return true.
1817
0
    if (is_ancestor_of(source, target))
1818
0
        return true;
1819
1820
    // 3. If target is an ancestor of source, then:
1821
0
    if (is_ancestor_of(target, source)) {
1822
1823
        // 1. If target is not a top-level traversable, then return true.
1824
0
        if (!target.is_top_level_traversable())
1825
0
            return true;
1826
1827
        // 2. If sourceSnapshotParams's has transient activation is true, and sourceSnapshotParams's sandboxing flags's
1828
        //    sandboxed top-level navigation with user activation browsing context flag is set, then return false.
1829
0
        if (source_snapshot_params.has_transient_activation && has_flag(source_snapshot_params.sandboxing_flags, SandboxingFlagSet::SandboxedTopLevelNavigationWithUserActivation))
1830
0
            return false;
1831
1832
        // 3. If sourceSnapshotParams's has transient activation is false, and sourceSnapshotParams's sandboxing flags's
1833
        //    sandboxed top-level navigation without user activation browsing context flag is set, then return false.
1834
0
        if (!source_snapshot_params.has_transient_activation && has_flag(source_snapshot_params.sandboxing_flags, SandboxingFlagSet::SandboxedTopLevelNavigationWithoutUserActivation))
1835
0
            return false;
1836
1837
        // 4. Return true.
1838
0
        return true;
1839
0
    }
1840
1841
    // 4. If target is a top-level traversable:
1842
0
    if (target.is_top_level_traversable()) {
1843
        // FIXME: 1. If source is the one permitted sandboxed navigator of target, then return true.
1844
1845
        // 2. If sourceSnapshotParams's sandboxing flags's sandboxed navigation browsing context flag is set, then return false.
1846
0
        if (has_flag(source_snapshot_params.sandboxing_flags, SandboxingFlagSet::SandboxedNavigation))
1847
0
            return false;
1848
1849
        // 3. Return true.
1850
0
        return true;
1851
0
    }
1852
1853
    // 5. If sourceSnapshotParams's sandboxing flags's sandboxed navigation browsing context flag is set, then return false.
1854
    // 6. Return true.
1855
0
    return !has_flag(source_snapshot_params.sandboxing_flags, SandboxingFlagSet::SandboxedNavigation);
1856
0
}
1857
1858
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#snapshotting-target-snapshot-params
1859
TargetSnapshotParams Navigable::snapshot_target_snapshot_params()
1860
0
{
1861
    // To snapshot target snapshot params given a navigable targetNavigable, return a new target snapshot params
1862
    // with sandboxing flags set to the result of determining the creation sandboxing flags given targetNavigable's
1863
    // active browsing context and targetNavigable's container.
1864
1865
0
    return { determine_the_creation_sandboxing_flags(*active_browsing_context(), container()) };
1866
0
}
1867
1868
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#finalize-a-cross-document-navigation
1869
void finalize_a_cross_document_navigation(JS::NonnullGCPtr<Navigable> navigable, HistoryHandlingBehavior history_handling, JS::NonnullGCPtr<SessionHistoryEntry> history_entry)
1870
0
{
1871
    // NOTE: This is not in the spec but we should not navigate destroyed navigable.
1872
0
    if (navigable->has_been_destroyed())
1873
0
        return;
1874
1875
    // 1. FIXME: Assert: this is running on navigable's traversable navigable's session history traversal queue.
1876
1877
    // 2. Set navigable's is delaying load events to false.
1878
0
    navigable->set_delaying_load_events(false);
1879
1880
    // 3. If historyEntry's document is null, then return.
1881
0
    if (!history_entry->document())
1882
0
        return;
1883
1884
    // 4. If all of the following are true:
1885
    //    - navigable's parent is null;
1886
    //    - historyEntry's document's browsing context is not an auxiliary browsing context whose opener browsing context is non-null; and
1887
    //    - historyEntry's document's origin is not navigable's active document's origin
1888
    //    then set historyEntry's document state's navigable target name to the empty string.
1889
0
    if (navigable->parent() == nullptr && history_entry->document()->browsing_context()->opener_browsing_context() != nullptr && history_entry->document()->origin() != navigable->active_document()->origin())
1890
0
        history_entry->document_state()->set_navigable_target_name(String {});
1891
1892
    // 5. Let entryToReplace be navigable's active session history entry if historyHandling is "replace", otherwise null.
1893
0
    auto entry_to_replace = history_handling == HistoryHandlingBehavior::Replace ? navigable->active_session_history_entry() : nullptr;
1894
1895
    // 6. Let traversable be navigable's traversable navigable.
1896
0
    auto traversable = navigable->traversable_navigable();
1897
1898
    // 7. Let targetStep be null.
1899
0
    int target_step;
1900
1901
    // 8. Let targetEntries be the result of getting session history entries for navigable.
1902
0
    auto& target_entries = navigable->get_session_history_entries();
1903
1904
    // 9. If entryToReplace is null, then:
1905
0
    if (entry_to_replace == nullptr) {
1906
        // 1. Clear the forward session history of traversable.
1907
0
        traversable->clear_the_forward_session_history();
1908
1909
        // 2. Set targetStep to traversable's current session history step + 1.
1910
0
        target_step = traversable->current_session_history_step() + 1;
1911
1912
        // 3. Set historyEntry's step to targetStep.
1913
0
        history_entry->set_step(target_step);
1914
1915
        // 4. Append historyEntry to targetEntries.
1916
0
        target_entries.append(history_entry);
1917
0
    } else {
1918
        // 1. Replace entryToReplace with historyEntry in targetEntries.
1919
0
        *(target_entries.find(*entry_to_replace)) = history_entry;
1920
1921
        // 2. Set historyEntry's step to entryToReplace's step.
1922
0
        history_entry->set_step(entry_to_replace->step());
1923
1924
        // 3. If historyEntry's document state's origin is same origin with entryToReplace's document state's origin,
1925
        //    then set historyEntry's navigation API key to entryToReplace's navigation API key.
1926
0
        if (history_entry->document_state()->origin().has_value() && entry_to_replace->document_state()->origin().has_value() && history_entry->document_state()->origin()->is_same_origin(*entry_to_replace->document_state()->origin())) {
1927
0
            history_entry->set_navigation_api_key(entry_to_replace->navigation_api_key());
1928
0
        }
1929
1930
        // 4. Set targetStep to traversable's current session history step.
1931
0
        target_step = traversable->current_session_history_step();
1932
0
    }
1933
1934
    // 10. Apply the push/replace history step targetStep to traversable.
1935
0
    traversable->apply_the_push_or_replace_history_step(target_step, history_handling, TraversableNavigable::SynchronousNavigation::No);
1936
0
}
1937
1938
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#url-and-history-update-steps
1939
void perform_url_and_history_update_steps(DOM::Document& document, URL::URL new_url, Optional<SerializationRecord> serialized_data, HistoryHandlingBehavior history_handling)
1940
0
{
1941
    // 1. Let navigable be document's node navigable.
1942
0
    auto navigable = document.navigable();
1943
1944
    // 2. Let activeEntry be navigable's active session history entry.
1945
0
    auto active_entry = navigable->active_session_history_entry();
1946
1947
    // 3. Let newEntry be a new session history entry, with
1948
    //      URL: newURL
1949
    //      serialized state: if serializedData is not null, serializedData; otherwise activeEntry's classic history API state
1950
    //      document state: activeEntry's document state
1951
    //      scroll restoration mode: activeEntry's scroll restoration mode
1952
    // FIXME: persisted user state: activeEntry's persisted user state
1953
0
    JS::NonnullGCPtr<SessionHistoryEntry> new_entry = document.heap().allocate_without_realm<SessionHistoryEntry>();
1954
0
    new_entry->set_url(new_url);
1955
0
    new_entry->set_classic_history_api_state(serialized_data.value_or(active_entry->classic_history_api_state()));
1956
0
    new_entry->set_document_state(active_entry->document_state());
1957
0
    new_entry->set_scroll_restoration_mode(active_entry->scroll_restoration_mode());
1958
1959
    // 4. If document's is initial about:blank is true, then set historyHandling to "replace".
1960
0
    if (document.is_initial_about_blank()) {
1961
0
        history_handling = HistoryHandlingBehavior::Replace;
1962
0
    }
1963
1964
    // 5. Let entryToReplace be activeEntry if historyHandling is "replace", otherwise null.
1965
0
    auto entry_to_replace = history_handling == HistoryHandlingBehavior::Replace ? active_entry : nullptr;
1966
1967
    // 6. If historyHandling is "push", then:
1968
0
    if (history_handling == HistoryHandlingBehavior::Push) {
1969
        // 1. Increment document's history object's index.
1970
0
        document.history()->m_index++;
1971
1972
        // 2. Set document's history object's length to its index + 1.
1973
0
        document.history()->m_length = document.history()->m_index + 1;
1974
0
    }
1975
1976
    // If serializedData is not null, then restore the history object state given document and newEntry.
1977
0
    if (serialized_data.has_value())
1978
0
        document.restore_the_history_object_state(new_entry);
1979
1980
    // 8. Set document's URL to newURL.
1981
0
    document.set_url(new_url);
1982
1983
    // 9. Set document's latest entry to newEntry.
1984
0
    document.set_latest_entry(new_entry);
1985
1986
    // 10. Set navigable's active session history entry to newEntry.
1987
0
    navigable->set_active_session_history_entry(new_entry);
1988
1989
    // 11. Update the navigation API entries for a same-document navigation given document's relevant global object's navigation API, newEntry, and historyHandling.
1990
0
    auto& relevant_global_object = verify_cast<Window>(HTML::relevant_global_object(document));
1991
0
    auto navigation_type = history_handling == HistoryHandlingBehavior::Push ? Bindings::NavigationType::Push : Bindings::NavigationType::Replace;
1992
0
    relevant_global_object.navigation()->update_the_navigation_api_entries_for_a_same_document_navigation(new_entry, navigation_type);
1993
1994
    // 12. Let traversable be navigable's traversable navigable.
1995
0
    auto traversable = navigable->traversable_navigable();
1996
1997
    // 13. Append the following session history synchronous navigation steps involving navigable to traversable:
1998
0
    traversable->append_session_history_synchronous_navigation_steps(*navigable, JS::create_heap_function(document.realm().heap(), [traversable, navigable, new_entry, entry_to_replace, history_handling] {
1999
        // 1. Finalize a same-document navigation given traversable, navigable, newEntry, and entryToReplace.
2000
0
        finalize_a_same_document_navigation(*traversable, *navigable, new_entry, entry_to_replace, history_handling);
2001
2002
        // 2. FIXME: Invoke WebDriver BiDi history updated with navigable.
2003
0
    }));
2004
0
}
2005
2006
void Navigable::scroll_offset_did_change()
2007
0
{
2008
    // https://w3c.github.io/csswg-drafts/cssom-view-1/#scrolling-events
2009
    // Whenever a viewport gets scrolled (whether in response to user interaction or by an API), the user agent must run these steps:
2010
2011
    // 1. Let doc be the viewport’s associated Document.
2012
0
    auto doc = active_document();
2013
0
    VERIFY(doc);
2014
2015
    // 2. If doc is already in doc’s pending scroll event targets, abort these steps.
2016
0
    for (auto& target : doc->pending_scroll_event_targets()) {
2017
0
        if (target.ptr() == doc)
2018
0
            return;
2019
0
    }
2020
2021
    // 3. Append doc to doc’s pending scroll event targets.
2022
0
    doc->pending_scroll_event_targets().append(*doc);
2023
0
}
2024
2025
CSSPixelRect Navigable::to_top_level_rect(CSSPixelRect const& a_rect)
2026
0
{
2027
0
    auto rect = a_rect;
2028
0
    rect.set_location(to_top_level_position(a_rect.location()));
2029
0
    return rect;
2030
0
}
2031
2032
CSSPixelPoint Navigable::to_top_level_position(CSSPixelPoint a_position)
2033
0
{
2034
0
    auto position = a_position;
2035
0
    for (auto ancestor = parent(); ancestor; ancestor = ancestor->parent()) {
2036
0
        if (is<TraversableNavigable>(*ancestor))
2037
0
            break;
2038
0
        if (!ancestor->container())
2039
0
            return {};
2040
0
        if (!ancestor->container()->paintable())
2041
0
            return {};
2042
0
        position.translate_by(ancestor->container()->paintable()->box_type_agnostic_position());
2043
0
    }
2044
0
    return position;
2045
0
}
2046
2047
void Navigable::set_viewport_size(CSSPixelSize size)
2048
0
{
2049
0
    if (m_size == size)
2050
0
        return;
2051
2052
0
    m_size = size;
2053
0
    if (auto document = active_document()) {
2054
        // NOTE: Resizing the viewport changes the reference value for viewport-relative CSS lengths.
2055
0
        document->invalidate_style(DOM::StyleInvalidationReason::NavigableSetViewportSize);
2056
0
        document->set_needs_layout();
2057
0
    }
2058
0
    set_needs_display();
2059
2060
0
    if (auto document = active_document()) {
2061
0
        document->inform_all_viewport_clients_about_the_current_viewport_rect();
2062
2063
        // Schedule the HTML event loop to ensure that a `resize` event gets fired.
2064
0
        HTML::main_thread_event_loop().schedule();
2065
0
    }
2066
0
}
2067
2068
void Navigable::perform_scroll_of_viewport(CSSPixelPoint new_position)
2069
0
{
2070
0
    if (m_viewport_scroll_offset != new_position) {
2071
0
        m_viewport_scroll_offset = new_position;
2072
0
        scroll_offset_did_change();
2073
0
        set_needs_display();
2074
2075
0
        if (auto document = active_document())
2076
0
            document->inform_all_viewport_clients_about_the_current_viewport_rect();
2077
0
    }
2078
2079
    // Schedule the HTML event loop to ensure that a `resize` event gets fired.
2080
0
    HTML::main_thread_event_loop().schedule();
2081
0
}
2082
2083
void Navigable::set_needs_display()
2084
0
{
2085
0
    set_needs_display(viewport_rect());
2086
0
}
2087
2088
void Navigable::set_needs_display(CSSPixelRect const&)
2089
0
{
2090
    // FIXME: Ignore updates outside the visible viewport rect.
2091
    //        This requires accounting for fixed-position elements in the input rect, which we don't do yet.
2092
2093
0
    m_needs_repaint = true;
2094
2095
0
    if (is<TraversableNavigable>(*this)) {
2096
        // Schedule the main thread event loop, which will, in turn, schedule a repaint.
2097
0
        Web::HTML::main_thread_event_loop().schedule();
2098
0
        return;
2099
0
    }
2100
2101
0
    if (container() && container()->paintable())
2102
0
        container()->paintable()->set_needs_display();
2103
0
}
2104
2105
// https://html.spec.whatwg.org/#rendering-opportunity
2106
bool Navigable::has_a_rendering_opportunity() const
2107
0
{
2108
    // A navigable has a rendering opportunity if the user agent is currently able to present
2109
    // the contents of the navigable to the user,
2110
    // accounting for hardware refresh rate constraints and user agent throttling for performance reasons,
2111
    // but considering content presentable even if it's outside the viewport.
2112
2113
    // A navigable has no rendering opportunities if its active document is render-blocked
2114
    // or if it is suppressed for view transitions;
2115
    // otherwise, rendering opportunities are determined based on hardware constraints
2116
    // such as display refresh rates and other factors such as page performance
2117
    // or whether the document's visibility state is "visible".
2118
    // Rendering opportunities typically occur at regular intervals.
2119
2120
    // FIXME: Return `false` here if we're an inactive browser tab.
2121
0
    auto browsing_context = const_cast<Navigable*>(this)->active_browsing_context();
2122
0
    if (!browsing_context)
2123
0
        return false;
2124
0
    return browsing_context->page().client().is_ready_to_paint();
2125
0
}
2126
2127
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#inform-the-navigation-api-about-aborting-navigation
2128
void Navigable::inform_the_navigation_api_about_aborting_navigation()
2129
0
{
2130
    // FIXME: 1. If this algorithm is running on navigable's active window's relevant agent's event loop, then continue on to the following steps.
2131
    // Otherwise, queue a global task on the navigation and traversal task source given navigable's active window to run the following steps.
2132
2133
    // AD-HOC: Not in the spec but subsequent steps will fail if the navigable doesn't have an active window.
2134
0
    if (!active_window())
2135
0
        return;
2136
2137
0
    queue_global_task(Task::Source::NavigationAndTraversal, *active_window(), JS::create_heap_function(heap(), [this] {
2138
        // 2. Let navigation be navigable's active window's navigation API.
2139
0
        VERIFY(active_window());
2140
0
        auto navigation = active_window()->navigation();
2141
2142
        // 3. If navigation's ongoing navigate event is null, then return.
2143
0
        if (navigation->ongoing_navigate_event() == nullptr)
2144
0
            return;
2145
2146
        // 4. Abort the ongoing navigation given navigation.
2147
0
        navigation->abort_the_ongoing_navigation();
2148
0
    }));
2149
0
}
2150
2151
void Navigable::record_display_list(Painting::DisplayListRecorder& display_list_recorder, PaintConfig config)
2152
0
{
2153
0
    auto document = active_document();
2154
0
    if (!document)
2155
0
        return;
2156
2157
0
    auto const& page = traversable_navigable()->page();
2158
0
    auto viewport_rect = page.css_to_device_rect(this->viewport_rect());
2159
0
    Gfx::IntRect bitmap_rect { {}, viewport_rect.size().to_type<int>() };
2160
2161
0
    auto background_color = document->background_color();
2162
2163
0
    display_list_recorder.fill_rect(bitmap_rect, background_color);
2164
0
    if (!document->paintable()) {
2165
0
        VERIFY_NOT_REACHED();
2166
0
    }
2167
2168
0
    Web::PaintContext context(display_list_recorder, page.palette(), page.client().device_pixels_per_css_pixel());
2169
0
    context.set_device_viewport_rect(viewport_rect);
2170
0
    context.set_should_show_line_box_borders(config.should_show_line_box_borders);
2171
0
    context.set_should_paint_overlay(config.paint_overlay);
2172
0
    context.set_has_focus(config.has_focus);
2173
2174
0
    document->update_paint_and_hit_testing_properties_if_needed();
2175
2176
0
    auto& viewport_paintable = *document->paintable();
2177
2178
    // NOTE: We only need to refresh the scroll state for traversables because they are responsible
2179
    //       for tracking the state of all nested navigables.
2180
0
    if (is_traversable()) {
2181
0
        viewport_paintable.refresh_scroll_state();
2182
0
        viewport_paintable.refresh_clip_state();
2183
0
    }
2184
2185
0
    viewport_paintable.paint_all_phases(context);
2186
2187
    // FIXME: Support scrollable frames inside iframes.
2188
0
    if (is_traversable()) {
2189
0
        Vector<Gfx::IntPoint> scroll_offsets_by_frame_id;
2190
0
        scroll_offsets_by_frame_id.resize(viewport_paintable.scroll_state.size());
2191
0
        for (auto [_, scrollable_frame] : viewport_paintable.scroll_state) {
2192
0
            auto scroll_offset = context.rounded_device_point(scrollable_frame->offset).to_type<int>();
2193
0
            scroll_offsets_by_frame_id[scrollable_frame->id] = scroll_offset;
2194
0
        }
2195
0
        display_list_recorder.display_list().apply_scroll_offsets(scroll_offsets_by_frame_id);
2196
0
        display_list_recorder.display_list().mark_unnecessary_commands();
2197
0
    }
2198
2199
0
    m_needs_repaint = false;
2200
0
}
2201
2202
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#event-uni
2203
UserNavigationInvolvement user_navigation_involvement(DOM::Event const& event)
2204
0
{
2205
    // For convenience at certain call sites, the user navigation involvement for an Event event is defined as follows:
2206
2207
    // 1. Assert: this algorithm is being called as part of an activation behavior definition.
2208
    // 2. Assert: event's type is "click".
2209
0
    VERIFY(event.type() == "click"_fly_string);
2210
2211
    // 3. If event's isTrusted is initialized to true, then return "activation".
2212
    // 4. Return "none".
2213
0
    return event.is_trusted() ? UserNavigationInvolvement::Activation : UserNavigationInvolvement::None;
2214
0
}
2215
2216
bool Navigable::is_focused() const
2217
0
{
2218
0
    return &m_page->focused_navigable() == this;
2219
0
}
2220
2221
static String visible_text_in_range(DOM::Range const& range)
2222
0
{
2223
    // NOTE: This is an adaption of Range stringification, but we skip over DOM nodes that don't have a corresponding layout node.
2224
0
    StringBuilder builder;
2225
2226
0
    if (range.start_container() == range.end_container() && is<DOM::Text>(*range.start_container())) {
2227
0
        if (!range.start_container()->layout_node())
2228
0
            return String {};
2229
0
        return MUST(static_cast<DOM::Text const&>(*range.start_container()).data().substring_from_byte_offset(range.start_offset(), range.end_offset() - range.start_offset()));
2230
0
    }
2231
2232
0
    if (is<DOM::Text>(*range.start_container()) && range.start_container()->layout_node())
2233
0
        builder.append(static_cast<DOM::Text const&>(*range.start_container()).data().bytes_as_string_view().substring_view(range.start_offset()));
2234
2235
0
    for (DOM::Node const* node = range.start_container(); node != range.end_container()->next_sibling(); node = node->next_in_pre_order()) {
2236
0
        if (is<DOM::Text>(*node) && range.contains_node(*node) && node->layout_node())
2237
0
            builder.append(static_cast<DOM::Text const&>(*node).data());
2238
0
    }
2239
2240
0
    if (is<DOM::Text>(*range.end_container()) && range.end_container()->layout_node())
2241
0
        builder.append(static_cast<DOM::Text const&>(*range.end_container()).data().bytes_as_string_view().substring_view(0, range.end_offset()));
2242
2243
0
    return MUST(builder.to_string());
2244
0
}
2245
2246
String Navigable::selected_text() const
2247
0
{
2248
0
    auto document = const_cast<Navigable*>(this)->active_document();
2249
0
    if (!document)
2250
0
        return String {};
2251
0
    auto selection = const_cast<DOM::Document&>(*document).get_selection();
2252
0
    auto range = selection->range();
2253
0
    if (!range)
2254
0
        return String {};
2255
0
    return visible_text_in_range(*range);
2256
0
}
2257
2258
void Navigable::select_all()
2259
0
{
2260
0
    auto document = active_document();
2261
0
    if (!document)
2262
0
        return;
2263
2264
0
    auto selection = document->get_selection();
2265
0
    if (!selection)
2266
0
        return;
2267
2268
0
    if (auto position = document->cursor_position(); position && position->node()->is_editable()) {
2269
0
        auto& node = *position->node();
2270
0
        auto node_length = node.length();
2271
2272
0
        (void)selection->set_base_and_extent(node, 0, node, node_length);
2273
0
        document->set_cursor_position(DOM::Position::create(document->realm(), node, node_length));
2274
0
    } else if (auto* body = document->body()) {
2275
0
        (void)selection->select_all_children(*body);
2276
0
    }
2277
0
}
2278
2279
void Navigable::paste(String const& text)
2280
0
{
2281
0
    auto document = active_document();
2282
0
    if (!document)
2283
0
        return;
2284
2285
0
    m_event_handler.handle_paste(text);
2286
0
}
2287
2288
void Navigable::register_navigation_observer(Badge<NavigationObserver>, NavigationObserver& navigation_observer)
2289
0
{
2290
0
    auto result = m_navigation_observers.set(navigation_observer);
2291
0
    VERIFY(result == AK::HashSetResult::InsertedNewEntry);
2292
0
}
2293
2294
void Navigable::unregister_navigation_observer(Badge<NavigationObserver>, NavigationObserver& navigation_observer)
2295
0
{
2296
0
    bool was_removed = m_navigation_observers.remove(navigation_observer);
2297
0
    VERIFY(was_removed);
2298
0
}
2299
2300
}