Преглед на файлове

LibWeb: Flesh out a chunk of the HTML spec's frame navigation algorithms

Andreas Kling преди 2 години
родител
ревизия
4ee5dfbe4b

+ 417 - 0
Userland/Libraries/LibWeb/HTML/BrowsingContext.cpp

@@ -6,7 +6,9 @@
 
 #include <LibWeb/Bindings/MainThreadVM.h>
 #include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Event.h>
 #include <LibWeb/DOM/HTMLCollection.h>
+#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
 #include <LibWeb/HTML/BrowsingContext.h>
 #include <LibWeb/HTML/BrowsingContextContainer.h>
 #include <LibWeb/HTML/BrowsingContextGroup.h>
@@ -222,6 +224,7 @@ NonnullRefPtr<BrowsingContext> BrowsingContext::create_a_new_browsing_context(Pa
         .policy_container = {},
         .scroll_restoration_mode = {},
         .browsing_context_name = {},
+        .original_source_browsing_context = {},
     });
 
     // Non-standard:
@@ -797,6 +800,16 @@ DOM::Document* BrowsingContext::active_document()
     return m_active_document.cell();
 }
 
+HTML::Window* BrowsingContext::active_window()
+{
+    return m_active_document ? &m_active_document->window() : nullptr;
+}
+
+HTML::Window const* BrowsingContext::active_window() const
+{
+    return m_active_document ? &m_active_document->window() : nullptr;
+}
+
 void BrowsingContext::scroll_offset_did_change()
 {
     // https://w3c.github.io/csswg-drafts/cssom-view-1/#scrolling-events
@@ -845,4 +858,408 @@ void BrowsingContext::remove()
     // NOTE: This is done by ~BrowsingContextGroup() when the refcount reaches 0.
 }
 
+// https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigate
+DOM::ExceptionOr<void> BrowsingContext::navigate(
+    Fetch::Infrastructure::Request resource,
+    BrowsingContext& source_browsing_context,
+    bool exceptions_enabled,
+    HistoryHandlingBehavior history_handling,
+    Optional<PolicyContainer> history_policy_container,
+    String navigation_type,
+    Optional<String> navigation_id,
+    Function<void(NonnullOwnPtr<Fetch::Infrastructure::Response>)> process_response_end_of_body)
+{
+    // 1. If resource is a URL, then set resource to a new request whose URL is resource.
+    // NOTE: This function only accepts resources that are already a request, so this is irrelevant.
+
+    // 2. If resource is a request and historyHandling is "reload", then set resource's reload-navigation flag.
+    if (history_handling == HistoryHandlingBehavior::Reload)
+        resource.set_reload_navigation(true);
+
+    // 3. If the source browsing context is not allowed to navigate browsingContext, then:
+    if (!source_browsing_context.is_allowed_to_navigate(*this)) {
+        // 1. If exceptionsEnabled is given and is true, then throw a "SecurityError" DOMException.
+        if (exceptions_enabled) {
+            VERIFY(source_browsing_context.active_document());
+            return DOM::SecurityError::create(source_browsing_context.active_document()->global_object(), "Source browsing context not allowed to navigate"sv);
+        }
+
+        // FIXME: 2. Otherwise, the user agent may instead offer to open resource in a new top-level browsing context
+        //           or in the top-level browsing context of the source browsing context, at the user's option,
+        //           in which case the user agent must navigate that designated top-level browsing context
+        //           to resource as if the user had requested it independently.
+    }
+
+    // 4. If navigationId is null:
+    if (!navigation_id.has_value()) {
+        // 1. If historyHandling is "reload", and browsingContext's active document's navigation id is not null,
+        if (history_handling == HistoryHandlingBehavior::Reload && active_document()->navigation_id().has_value()) {
+            // let navigationId be browsingContext's active document's navigation id.
+            navigation_id = active_document()->navigation_id();
+        } else {
+            // Otherwise let navigation id be the result of generating a random UUID. [UUID]
+            // FIXME: Generate a UUID.
+            navigation_id = "FIXME";
+        }
+    }
+
+    // FIXME: 5. If browsingContext's active document's unload counter is greater than 0,
+    //           then invoke WebDriver BiDi navigation failed
+    //           with a WebDriver BiDi navigation status whose id is navigationId, status is "canceled", and url is resource's url
+    //           and return.
+
+    // 6. If historyHandling is "default", and any of the following are true:
+    //    - browsingContext is still on its initial about:blank Document
+    //    - resource is a request whose URL equals browsingContext's active document's URL
+    //    - resource is a request whose URL's scheme is "javascript"
+    if (history_handling == HistoryHandlingBehavior::Default
+        && (still_on_its_initial_about_blank_document()
+            || resource.url().equals(active_document()->url())
+            || resource.url().scheme() == "javascript"sv)) {
+        // then set historyHandling to "replace".
+        history_handling = HistoryHandlingBehavior::Replace;
+    }
+
+    // 7. If historyHandling is not "reload", resource is a request,
+    //    resource's URL equals browsingContext's active document's URL with exclude fragments set to true,
+    //    and resource's URL's fragment is non-null, then:
+    if (history_handling != HistoryHandlingBehavior::Reload
+        && resource.url().equals(active_document()->url(), AK::URL::ExcludeFragment::Yes)
+        && !resource.url().fragment().is_null()) {
+        // 1. Navigate to a fragment given browsingContext, resource's URL, historyHandling, and navigationId.
+        navigate_to_a_fragment(resource.url(), history_handling, *navigation_id);
+
+        // 2. Return.
+        return {};
+    }
+
+    // FIXME: 8. Let incumbentNavigationOrigin be the origin of the incumbent settings object,
+    //           or if no script was involved, the origin of the node document of the element that initiated the navigation.
+
+    // FIXME: 9. Let initiatorPolicyContainer be a clone of the source browsing context's active document's policy container.
+
+    // FIXME: 10. If resource is a request, then set resource's policy container to initiatorPolicyContainer.
+
+    // FIXME: 11. Cancel any preexisting but not yet mature attempt to navigate browsingContext,
+    //            including canceling any instances of the fetch algorithm started by those attempts.
+    //            If one of those attempts has already created and initialized a new Document object,
+    //            abort that Document also.
+    //            (Navigation attempts that have matured already have session history entries,
+    //            and are therefore handled during the update the session history with the new page algorithm, later.)
+
+    // FIXME: 12. Let unloadPromptResult be the result of calling prompt to unload with the active document of browsingContext.
+    //            If this instance of the navigation algorithm gets canceled while this step is running,
+    //            the prompt to unload algorithm must nonetheless be run to completion.
+
+    // FIXME: 13. If unloadPromptResult is "refuse", then return a new WebDriver BiDi navigation status whose id is navigationId and status is "canceled".
+
+    // FIXME: 14. Abort the active document of browsingContext.
+
+    // FIXME: 15. If browsingContext is a child browsing context, then put it in the delaying load events mode.
+    //            The user agent must take this child browsing context out of the delaying load events mode when this navigation algorithm later matures,
+    //            or when it terminates (whether due to having run all the steps, or being canceled, or being aborted),
+    //            whichever happens first.
+
+    // FIXME: 16. Let sandboxFlags be the result of determining the creation sandboxing flags given browsingContext and browsingContext's container.
+
+    // FIXME: 17. Let allowedToDownload be the result of running the allowed to download algorithm given the source browsing context and browsingContext.
+
+    // 18. Let hasTransientActivation be true if the source browsing context's active window has transient activation; otherwise false.
+    [[maybe_unused]] bool has_transient_activation = source_browsing_context.active_window()->has_transient_activation();
+
+    // FIXME: 19. Invoke WebDriver BiDi navigation started with browsingContext, and a new WebDriver BiDi navigation status whose id is navigationId, url is resource's url, and status is "pending".
+
+    // 20. Return, and continue running these steps in parallel.
+
+    // FIXME: Implement the rest of this algorithm
+    (void)history_policy_container;
+    (void)navigation_type;
+    (void)process_response_end_of_body;
+
+    // AD-HOC:
+    loader().load(resource.url(), FrameLoader::Type::IFrame);
+    return {};
+}
+
+// https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigate-fragid
+DOM::ExceptionOr<void> BrowsingContext::navigate_to_a_fragment(AK::URL const& url, HistoryHandlingBehavior history_handling, String navigation_id)
+{
+    // 1. If historyHandling is not "replace",
+    if (history_handling != HistoryHandlingBehavior::Replace) {
+        // FIXME: then remove all the entries in browsingContext's session history after the current entry.
+        //        (If the current entry is the last entry in the session history, then no entries are removed.)
+    }
+
+    // 2. Remove any tasks queued by the history traversal task source that are associated with any Document objects
+    //    in browsingContext's top-level browsing context's document family.
+    HTML::main_thread_event_loop().task_queue().remove_tasks_matching([&](HTML::Task const& task) {
+        return task.source() == Task::Source::HistoryTraversal
+            && task.document()
+            && top_level_browsing_context().document_family_contains(*task.document());
+    });
+
+    // 3. Append a new session history entry to the session history whose URL is url,
+    //    document is the current entry's document,
+    //    policy container is the current entry's policy-container
+    //    and scroll restoration mode is the current entry's scroll restoration mode.
+    m_session_history.append(SessionHistoryEntry {
+        .url = url,
+        .document = current_entry().document,
+        .serialized_state = {},
+        .policy_container = current_entry().policy_container,
+        .scroll_restoration_mode = current_entry().scroll_restoration_mode,
+        .browsing_context_name = {},
+        .original_source_browsing_context = {},
+    });
+
+    // 4. Traverse the history to the new entry, with historyHandling set to historyHandling.
+    //    This will scroll to the fragment given in what is now the document's URL.
+    TRY(traverse_the_history(m_session_history.size() - 1, history_handling));
+
+    // FIXME: 5. Invoke WebDriver BiDi fragment navigated with browsingContext,
+    //           and a new WebDriver BiDi navigation status whose id is navigationId, url is resource's url, and status is "complete".
+    (void)navigation_id;
+
+    return {};
+}
+
+// https://html.spec.whatwg.org/multipage/browsing-the-web.html#traverse-the-history
+DOM::ExceptionOr<void> BrowsingContext::traverse_the_history(size_t entry_index, HistoryHandlingBehavior history_handling, bool explicit_history_navigation)
+{
+    auto* entry = &m_session_history[entry_index];
+
+    // 1. If entry's document is null, then:
+    if (!entry->document) {
+        // 1. Assert: historyHandling is "default".
+        VERIFY(history_handling == HistoryHandlingBehavior::Default);
+
+        // 2. Let request be a new request whose URL is entry's URL.
+        auto request = Fetch::Infrastructure::Request();
+        request.set_url(entry->url);
+
+        // 3. If explicitHistoryNavigation is true, then set request's history-navigation flag.
+        if (explicit_history_navigation)
+            request.set_history_navigation(true);
+
+        // 4. Navigate the browsing context to request with historyHandling set to "entry update"
+        //    and with historyPolicyContainer set to entry's policy container.
+        //    The navigation must be done using the same source browsing context as was used the first time entry was created.
+        VERIFY(entry->original_source_browsing_context);
+        TRY(navigate(request, *entry->original_source_browsing_context, false, HistoryHandlingBehavior::EntryUpdate, entry->policy_container));
+
+        // 5. Return.
+        return {};
+    }
+
+    // FIXME: 2. Save persisted state to the current entry.
+
+    // 3. Let newDocument be entry's document.
+    JS::GCPtr<DOM::Document> new_document = entry->document.ptr();
+
+    // 4. Assert: newDocument's is initial about:blank is false,
+    //   i.e., we never traverse back to the initial about:blank Document because it always gets replaced when we navigate away from it.
+    VERIFY(!new_document->is_initial_about_blank());
+
+    // 5. If newDocument is different than the current entry's document, or historyHandling is "entry update" or "reload", then:
+    if (new_document.ptr() != current_entry().document.ptr()
+        || history_handling == HistoryHandlingBehavior::EntryUpdate) {
+        // FIXME: 1. If newDocument's suspended timer handles is not empty:
+        // FIXME:    1. Assert: newDocument's suspension time is not zero.
+        // FIXME:    2. Let suspendDuration be the current high resolution time minus newDocument's suspension time.
+        // FIXME:    3. Let activeTimers be newDocument's relevant global object's map of active timers.
+        // FIXME:    4. For each handle in newDocument's suspended timer handles, if activeTimers[handle] exists, then increase activeTimers[handle] by suspendDuration.
+    }
+
+    // 2. Remove any tasks queued by the history traversal task source
+    //    that are associated with any Document objects in the top-level browsing context's document family.
+    HTML::main_thread_event_loop().task_queue().remove_tasks_matching([&](HTML::Task const& task) {
+        return task.source() == Task::Source::HistoryTraversal
+            && task.document()
+            && top_level_browsing_context().document_family_contains(*task.document());
+    });
+
+    // 3. If newDocument's origin is not same origin with the current entry's document's origin, then:
+    if (!new_document->origin().is_same_origin(current_entry().document->origin())) {
+        // FIXME: 1. Let entriesToUpdate be all entries in the session history whose document's origin is same origin as the active document
+        //           and that are contiguous with the current entry.
+        // FIXME: 2. For each entryToUpdate of entriesToUpdate, set entryToUpdate's browsing context name to the current browsing context name.
+        // FIXME: 3. If the browsing context is a top-level browsing context, but not an auxiliary browsing context whose disowned is false, then set the browsing context's name to the empty string.
+    }
+
+    // 4. Set the active document of the browsing context to newDocument.
+    set_active_document(new_document);
+
+    // 5. If entry's browsing context name is not null, then:
+    if (entry->browsing_context_name.has_value()) {
+        // 1. Set the browsing context's name to entry's browsing context name.
+        m_name = *entry->browsing_context_name;
+
+        // FIXME: 2. Let entriesToUpdate be all entries in the session history whose document's origin is same origin as the new active document's origin and that are contiguous with entry.
+        // FIXME: 3. For each entryToUpdate of entriesToUpdate, set entryToUpdate's browsing context name to null.
+    }
+
+    // FIXME: 6. If newDocument has any form controls whose autofill field name is "off", invoke the reset algorithm of each of those elements.
+
+    // 7. If newDocument's current document readiness "complete",
+    if (new_document->ready_state() == "complete"sv) {
+        // then queue a global task on the DOM manipulation task source given newDocument's relevant global object to run the following steps:
+
+        queue_global_task(Task::Source::DOMManipulation, relevant_global_object(*new_document), [new_document = JS::make_handle(*new_document)]() mutable {
+            // 1. If newDocument's page showing flag is true, then abort these steps.
+            if (new_document->page_showing())
+                return;
+
+            // 2. Set newDocument's page showing flag to true.
+            new_document->set_page_showing(true);
+
+            // 3. Update the visibility state of newDocument to "hidden".
+            new_document->update_the_visibility_state("hidden");
+
+            // 4. Fire a page transition event named pageshow at newDocument's relevant global object with true.
+            auto& window = verify_cast<HTML::Window>(relevant_global_object(*new_document));
+            window.fire_a_page_transition_event(HTML::EventNames::pageshow, true);
+        });
+    }
+
+    // 6. Set newDocument's URL to entry's URL.
+    new_document->set_url(entry->url);
+
+    // 7. Let hashChanged be false, and let oldURL and newURL be null.
+    bool hash_changed = false;
+    Optional<AK::URL> old_url;
+    Optional<AK::URL> new_url;
+
+    // 8. If entry's URL's fragment is not identical to the current entry's URL's fragment,
+    //    and entry's document equals the current entry's document,
+    if (entry->url.fragment() != current_entry().url.fragment()
+        && entry->document.ptr() == current_entry().document.ptr()) {
+        // then set hashChanged to true, set oldURL to the current entry's URL, and set newURL to entry's URL.
+        hash_changed = true;
+        old_url = current_entry().url;
+        new_url = entry->url;
+    }
+
+    // 9. If historyHandling is "replace", then remove the entry immediately before entry in the session history.
+    if (history_handling == HistoryHandlingBehavior::Replace) {
+        // FIXME: This is gnarly.
+        m_session_history.remove(entry_index - 1);
+        entry_index--;
+        entry = &m_session_history[entry_index];
+    }
+
+    // 10. If entry's persisted user state is null, and its URL's fragment is non-null, then scroll to the fragment.
+    if (!entry->url.fragment().is_null()) {
+        // FIXME: Implement the full "scroll to the fragment" algorithm:
+        // https://html.spec.whatwg.org/multipage/browsing-the-web.html#scroll-to-the-fragment-identifier
+        scroll_to_anchor(entry->url.fragment());
+    }
+
+    // 11. Set the current entry to entry.
+    m_session_history_index = entry_index;
+
+    // 12. Let targetRealm be the current Realm Record.
+    auto* target_realm = Bindings::main_thread_vm().current_realm();
+    VERIFY(target_realm);
+
+    // FIXME: 13. Let state be null.
+    // FIXME: 14. If entry's serialized state is not null, then set state to StructuredDeserialize(entry's serialized state, targetRealm).
+    //            If this throws an exception, catch it and ignore the exception.
+    // FIXME: 15. Set newDocument's History object's state to state.
+    // FIXME: 16. Let stateChanged be true if newDocument has a latest entry, and that entry is not entry; otherwise let it be false.
+    // FIXME: 17. Set newDocument's latest entry to entry.
+    // FIXME: 18. If stateChanged is true, then fire an event named popstate at newDocument's relevant global object, using PopStateEvent, with the state attribute initialized to state.
+    // FIXME: 19. Restore persisted state from entry.
+
+    // 20. If hashChanged is true,
+    if (hash_changed) {
+        // then queue a global task on the DOM manipulation task source given newDocument's relevant global object
+        queue_global_task(Task::Source::DOMManipulation, relevant_global_object(*new_document), [new_document = JS::make_handle(*new_document)]() mutable {
+            // to fire an event named hashchange at newDocument's relevant global object,
+            // using HashChangeEvent, with the oldURL attribute initialized to oldURL
+            // and the newURL attribute initialized to newURL.
+
+            // FIXME: Implement a proper HashChangeEvent class.
+            auto event = DOM::Event::create(verify_cast<HTML::Window>(relevant_global_object(*new_document)), HTML::EventNames::hashchange);
+            new_document->dispatch_event(event);
+        });
+    }
+
+    return {};
+}
+
+// https://html.spec.whatwg.org/multipage/browsers.html#allowed-to-navigate
+bool BrowsingContext::is_allowed_to_navigate(BrowsingContext const& other) const
+{
+    VERIFY(active_window());
+    VERIFY(active_document());
+
+    // 1. If A is not the same browsing context as B,
+    //    and A is not one of the ancestor browsing contexts of B,
+    //    and B is not a top-level browsing context,
+    //    FIXME: and A's active document's active sandboxing flag set has its sandboxed navigation browsing context flag set,
+    //    then return false.
+    if (this != &other
+        && !this->is_ancestor_of(other)
+        && !other.is_top_level()) {
+        return false;
+    }
+
+    // 2. Otherwise, if B is a top-level browsing context, and is one of the ancestor browsing contexts of A, then:
+    if (other.is_top_level() && other.is_ancestor_of(*this)) {
+        // 1. If A's active window has transient activation
+        //    and A's active document's active sandboxing flag set has its sandboxed top-level navigation with user activation browsing context flag set,
+        //    then return false.
+        if (active_window()->has_transient_activation()
+            && active_document()->active_sandboxing_flag_set().flags & SandboxingFlagSet::SandboxedTopLevelNavigationWithUserActivation) {
+            return false;
+        }
+
+        // 2. Otherwise, if A's active window does not have transient activation
+        //    and A's active document's active sandboxing flag set has its sandboxed top-level navigation without user activation browsing context flag set,
+        //    then return false.
+        if (!active_window()->has_transient_activation()
+            && active_document()->active_sandboxing_flag_set().flags & SandboxingFlagSet::SandboxedTopLevelNavigationWithoutUserActivation) {
+            return false;
+        }
+    }
+
+    // 3. Otherwise, if B is a top-level browsing context,
+    //    and is neither A nor one of the ancestor browsing contexts of A,
+    //    and A's Document's active sandboxing flag set has its sandboxed navigation browsing context flag set,
+    //    and A is not the one permitted sandboxed navigator of B,
+    //    then return false.
+    if (other.is_top_level()
+        && &other != this
+        && !other.is_ancestor_of(*this)
+        && active_document()->active_sandboxing_flag_set().flags & SandboxingFlagSet::SandboxedNavigation
+        && this != other.the_one_permitted_sandboxed_navigator()) {
+        return false;
+    }
+
+    // 4. Return true.
+    return true;
+}
+
+// https://html.spec.whatwg.org/multipage/origin.html#one-permitted-sandboxed-navigator
+BrowsingContext const* BrowsingContext::the_one_permitted_sandboxed_navigator() const
+{
+    // FIXME: Implement this.
+    return nullptr;
+}
+
+// https://html.spec.whatwg.org/multipage/browsers.html#document-family
+bool BrowsingContext::document_family_contains(DOM::Document const& document) const
+{
+    HashTable<DOM::Document const*> family;
+
+    for (auto& entry : m_session_history) {
+        if (!entry.document)
+            continue;
+        if (family.set(entry.document) == AK::HashSetResult::ReplacedExistingEntry)
+            continue;
+        // FIXME: The document family of a Document object consists of the union of all the document families of the browsing contexts in the list of the descendant browsing contexts of the Document object.
+    }
+
+    return family.contains(&document);
+}
 }

+ 34 - 0
Userland/Libraries/LibWeb/HTML/BrowsingContext.h

@@ -15,6 +15,7 @@
 #include <LibGfx/Size.h>
 #include <LibWeb/DOM/Position.h>
 #include <LibWeb/HTML/BrowsingContextContainer.h>
+#include <LibWeb/HTML/HistoryHandlingBehavior.h>
 #include <LibWeb/HTML/Origin.h>
 #include <LibWeb/HTML/SessionHistoryEntry.h>
 #include <LibWeb/Loader/FrameLoader.h>
@@ -47,6 +48,9 @@ public:
 
     void set_active_document(DOM::Document*);
 
+    HTML::Window* active_window();
+    HTML::Window const* active_window() const;
+
     Page* page() { return m_page; }
     Page const* page() const { return m_page; }
 
@@ -131,6 +135,31 @@ public:
     // https://html.spec.whatwg.org/multipage/browsers.html#bcg-remove
     void remove();
 
+    // https://html.spec.whatwg.org/multipage/browsers.html#allowed-to-navigate
+    bool is_allowed_to_navigate(BrowsingContext const&) const;
+
+    // https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigate
+    DOM::ExceptionOr<void> navigate(
+        Fetch::Infrastructure::Request resource,
+        BrowsingContext& source_browsing_context,
+        bool exceptions_enabled = false,
+        HistoryHandlingBehavior history_handling = HistoryHandlingBehavior::Default,
+        Optional<PolicyContainer> history_policy_container = {},
+        String navigation_type = "other",
+        Optional<String> navigation_id = {},
+        Function<void(NonnullOwnPtr<Fetch::Infrastructure::Response>)> process_response_end_of_body = {});
+
+    // https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigate-fragid
+    DOM::ExceptionOr<void> navigate_to_a_fragment(AK::URL const&, HistoryHandlingBehavior, String navigation_id);
+
+    // https://html.spec.whatwg.org/multipage/origin.html#one-permitted-sandboxed-navigator
+    BrowsingContext const* the_one_permitted_sandboxed_navigator() const;
+
+    // https://html.spec.whatwg.org/multipage/browsing-the-web.html#traverse-the-history
+    DOM::ExceptionOr<void> traverse_the_history(size_t entry_index, HistoryHandlingBehavior = HistoryHandlingBehavior::Default, bool explicit_history_navigation = false);
+
+    bool document_family_contains(DOM::Document const&) const;
+
 private:
     explicit BrowsingContext(Page&, HTML::BrowsingContextContainer*);
 
@@ -143,6 +172,11 @@ private:
     FrameLoader m_loader;
     Web::EventHandler m_event_handler;
 
+    // https://html.spec.whatwg.org/multipage/history.html#current-entry
+    SessionHistoryEntry& current_entry() { return m_session_history[*m_session_history_index]; }
+    SessionHistoryEntry const& current_entry() const { return m_session_history[*m_session_history_index]; }
+    Optional<size_t> m_session_history_index { 0 };
+
     // https://html.spec.whatwg.org/multipage/history.html#session-history
     Vector<SessionHistoryEntry> m_session_history;
 

+ 7 - 8
Userland/Libraries/LibWeb/HTML/BrowsingContextContainer.cpp

@@ -213,14 +213,13 @@ void BrowsingContextContainer::navigate_an_iframe_or_frame(Fetch::Infrastructure
     //           given element's node document's relevant global object
     //           to finalize and report timing given response, element's node document's relevant global object, and element's local name.
 
-    // FIXME: 4. Navigate element's nested browsing context to resource,
-    //           with historyHandling set to historyHandling,
-    //           the source browsing context set to element's node document's browsing context,
-    //           and processResponseEndOfBody set to reportFrameTiming.
-    (void)history_handling;
-
-    // AD-HOC:
-    m_nested_browsing_context->loader().load(resource.url(), FrameLoader::Type::IFrame);
+    // 4. Navigate element's nested browsing context to resource,
+    //    with historyHandling set to historyHandling,
+    //    the source browsing context set to element's node document's browsing context,
+    //    FIXME: and processResponseEndOfBody set to reportFrameTiming.
+    auto* source_browsing_context = document().browsing_context();
+    VERIFY(source_browsing_context);
+    m_nested_browsing_context->navigate(move(resource), *source_browsing_context, false, history_handling);
 }
 
 }

+ 7 - 0
Userland/Libraries/LibWeb/HTML/EventLoop/TaskQueue.cpp

@@ -40,4 +40,11 @@ bool TaskQueue::has_runnable_tasks() const
     return false;
 }
 
+void TaskQueue::remove_tasks_matching(Function<bool(HTML::Task const&)> filter)
+{
+    m_tasks.remove_all_matching([&](auto& task) {
+        return filter(*task);
+    });
+}
+
 }

+ 2 - 0
Userland/Libraries/LibWeb/HTML/EventLoop/TaskQueue.h

@@ -31,6 +31,8 @@ public:
         return m_tasks.take_first();
     }
 
+    void remove_tasks_matching(Function<bool(HTML::Task const&)>);
+
 private:
     HTML::EventLoop& m_event_loop;
 

+ 2 - 0
Userland/Libraries/LibWeb/HTML/SessionHistoryEntry.h

@@ -48,6 +48,8 @@ struct SessionHistoryEntry {
 
     // FIXME: persisted user state, which is implementation-defined, initially null
     // NOTE: This is where we could remember the state of form controls, for example.
+
+    WeakPtr<BrowsingContext> original_source_browsing_context;
 };
 
 }

+ 7 - 0
Userland/Libraries/LibWeb/HTML/Window.cpp

@@ -629,6 +629,13 @@ void Window::set_name(String const& name)
     browsing_context()->set_name(name);
 }
 
+// https://html.spec.whatwg.org/multipage/interaction.html#transient-activation
+bool Window::has_transient_activation() const
+{
+    // FIXME: Implement this.
+    return false;
+}
+
 // https://w3c.github.io/requestidlecallback/#start-an-idle-period-algorithm
 void Window::start_an_idle_period()
 {

+ 3 - 0
Userland/Libraries/LibWeb/HTML/Window.h

@@ -120,6 +120,9 @@ public:
 
     AnimationFrameCallbackDriver& animation_frame_callback_driver() { return m_animation_frame_callback_driver; }
 
+    // https://html.spec.whatwg.org/multipage/interaction.html#transient-activation
+    bool has_transient_activation() const;
+
 private:
     explicit Window(JS::Realm&);
     virtual void initialize(JS::Realm&) override;