From 84139a10d2fa8cc226bc603c2abda2396ad38055 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Wed, 13 Sep 2023 15:03:44 +0200 Subject: [PATCH] LibWeb: Implement navigation to `javascript:` URLs for navigables Implements following algorithms from the specification: https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigate-to-a-javascript:-url https://html.spec.whatwg.org/multipage/browsing-the-web.html#evaluate-a-javascript:-url --- Userland/Libraries/LibWeb/HTML/Navigable.cpp | 178 ++++++++++++++++++- Userland/Libraries/LibWeb/HTML/Navigable.h | 3 +- 2 files changed, 175 insertions(+), 6 deletions(-) diff --git a/Userland/Libraries/LibWeb/HTML/Navigable.cpp b/Userland/Libraries/LibWeb/HTML/Navigable.cpp index fb6ea215a9..de46f32546 100644 --- a/Userland/Libraries/LibWeb/HTML/Navigable.cpp +++ b/Userland/Libraries/LibWeb/HTML/Navigable.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -937,8 +938,8 @@ WebIDL::ExceptionOr Navigable::navigate( // 18. If url's scheme is "javascript", then: if (url.scheme() == "javascript"sv) { // 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. - queue_global_task(Task::Source::NavigationAndTraversal, *active_window(), [this, url, history_handling, initiator_origin_snapshot, csp_navigation_type] { - (void)navigate_to_a_javascript_url(url, to_history_handling_behavior(history_handling), initiator_origin_snapshot, csp_navigation_type); + queue_global_task(Task::Source::NavigationAndTraversal, *active_window(), [this, url, history_handling, initiator_origin_snapshot, csp_navigation_type, navigation_id] { + (void)navigate_to_a_javascript_url(url, to_history_handling_behavior(history_handling), initiator_origin_snapshot, csp_navigation_type, navigation_id); }); // 2. Return. @@ -1105,11 +1106,178 @@ WebIDL::ExceptionOr Navigable::navigate_to_a_fragment(AK::URL const& url, return {}; } -WebIDL::ExceptionOr Navigable::navigate_to_a_javascript_url(AK::URL const&, HistoryHandlingBehavior, Origin const& initiator_origin, CSPNavigationType csp_navigation_type) +// https://html.spec.whatwg.org/multipage/browsing-the-web.html#evaluate-a-javascript:-url +WebIDL::ExceptionOr> Navigable::evaluate_javascript_url(AK::URL const& url, Origin const& new_document_origin, String navigation_id) { - (void)initiator_origin; + auto& vm = this->vm(); + auto& realm = active_window()->realm(); + + // 1. Let urlString be the result of running the URL serializer on url. + auto url_string = url.serialize(); + + // 2. Let encodedScriptSource be the result of removing the leading "javascript:" from urlString. + auto encoded_script_source = url_string.substring_view(11, url_string.length() - 11); + + // FIXME: 3. Let scriptSource be the UTF-8 decoding of the percent-decoding of encodedScriptSource. + + // 4. Let settings be targetNavigable's active document's relevant settings object. + auto& settings = active_document()->relevant_settings_object(); + + // 5. Let baseURL be settings's API base URL. + auto base_url = settings.api_base_url(); + + // 6. Let script be the result of creating a classic script given scriptSource, settings, baseURL, and the default classic script fetch options. + auto script = HTML::ClassicScript::create("(javascript url)", encoded_script_source, settings, base_url); + + // 7. Let evaluationStatus be the result of running the classic script script. + auto evaluation_status = script->run(); + + // 8. Let result be null. + String result; + + // 9. If evaluationStatus is a normal completion, and evaluationStatus.[[Value]] is a String, then set result to evaluationStatus.[[Value]]. + if (evaluation_status.type() == JS::Completion::Type::Normal && evaluation_status.value()->is_string()) { + result = evaluation_status.value()->as_string().utf8_string(); + } else { + // 10. Otherwise, return null. + return nullptr; + } + + // 11. Let response be a new response with + // URL: targetNavigable's active document's URL + // header list: «(`Content-Type`, `text/html;charset=utf-8`)» + // body: the UTF-8 encoding of result, as a body + auto response = Fetch::Infrastructure::Response::create(vm); + response->url_list().append(active_document()->url()); + auto header = TRY_OR_THROW_OOM(vm, Fetch::Infrastructure::Header::from_string_pair("Content-Type"sv, "text/html"sv)); + TRY_OR_THROW_OOM(vm, response->header_list()->append(move(header))); + response->set_body(TRY(Fetch::Infrastructure::byte_sequence_as_body(realm, result.bytes()))); + + // 12. Let policyContainer be targetNavigable's active document's policy container. + auto const& policy_container = active_document()->policy_container(); + + // FIXME: 13. Let finalSandboxFlags be policyContainer's CSP list's CSP-derived sandboxing flags. + + // FIXME: 14. Let coop be targetNavigable's active document's cross-origin opener policy. + auto const& coop = active_document()->cross_origin_opener_policy(); + + // 15. Let coopEnforcementResult be a new cross-origin opener policy enforcement result with + // url: url + // origin: newDocumentOrigin + // cross-origin opener policy: coop + CrossOriginOpenerPolicyEnforcementResult coop_enforcement_result { + .url = url, + .origin = new_document_origin, + .cross_origin_opener_policy = coop, + }; + + // 16. Let navigationParams be a new navigation params, with + // id: navigationId + // navigable: targetNavigable + // request: null + // response: response + // fetch controller: null + // commit early hints: null + // COOP enforcement result: coopEnforcementResult + // reserved environment: null + // origin: newDocumentOrigin + // policy container: policyContainer + // final sandboxing flag set: finalSandboxFlags + // cross-origin opener policy: coop + // navigation timing type: "navigate" + // about base URL: targetNavigable's active document's about base URL + NavigationParams navigation_params { + .id = navigation_id, + .request = {}, + .response = response, + .origin = new_document_origin, + .policy_container = policy_container, + .cross_origin_opener_policy = coop, + .coop_enforcement_result = move(coop_enforcement_result), + .reserved_environment = {}, + .browsing_context = active_browsing_context(), + .navigable = this, + .fetch_controller = nullptr, + }; + + // 17. Return the result of loading an HTML document given navigationParams. + return load_document(navigation_params); +} + +// https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigate-to-a-javascript:-url +WebIDL::ExceptionOr Navigable::navigate_to_a_javascript_url(AK::URL const& url, HistoryHandlingBehavior history_handling, Origin const& initiator_origin, CSPNavigationType csp_navigation_type, String navigation_id) +{ + // 1. Assert: historyHandling is "replace". + VERIFY(history_handling == HistoryHandlingBehavior::Replace); + + // 2. Set the ongoing navigation for targetNavigable to null. + set_ongoing_navigation({}); + + // 3. If initiatorOrigin is not same origin-domain with targetNavigable's active document's origin, then return. + if (!initiator_origin.is_same_origin_domain(active_document()->origin())) + return {}; + + // FIXME: 4. Let request be a new request whose URL is url. + + // 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. (void)csp_navigation_type; - TODO(); + + // 6. Let newDocument be the result of evaluating a javascript: URL given targetNavigable, url, and initiatorOrigin. + auto new_document = TRY(evaluate_javascript_url(url, initiator_origin, navigation_id)); + + // 7. If newDocument is null, then return. + if (!new_document) { + // NOTE: In this case, some JavaScript code was executed, but no new Document was created, so we will not perform a navigation. + return {}; + } + + // 8. Assert: initiatorOrigin is newDocument's origin. + VERIFY(initiator_origin == new_document->origin()); + + // 9. Let entryToReplace be targetNavigable's active session history entry. + auto entry_to_replace = active_session_history_entry(); + + // 10. Let oldDocState be entryToReplace's document state. + auto old_doc_state = entry_to_replace->document_state; + + // 11. Let documentState be a new document state with + // document: newDocument + // history policy container: a clone of the oldDocState's history policy container if it is non-null; null otherwise + // request referrer: oldDocState's request referrer + // request referrer policy: oldDocState's request referrer policy + // initiator origin: initiatorOrigin + // origin: initiatorOrigin + // about base URL: oldDocState's about base URL + // resource: null + // ever populated: true + // navigable target name: oldDocState's navigable target name + JS::NonnullGCPtr document_state = *heap().allocate_without_realm(); + document_state->set_document(new_document); + document_state->set_history_policy_container(old_doc_state->history_policy_container()); + document_state->set_request_referrer(old_doc_state->request_referrer()); + document_state->set_request_referrer_policy(old_doc_state->request_referrer_policy()); + document_state->set_initiator_origin(initiator_origin); + document_state->set_origin(initiator_origin); + document_state->set_ever_populated(true); + document_state->set_navigable_target_name(old_doc_state->navigable_target_name()); + + // 12. Let historyEntry be a new session history entry, with + // URL: entryToReplace's URL + // document state: documentState + JS::NonnullGCPtr history_entry = *heap().allocate_without_realm(); + history_entry->url = entry_to_replace->url; + history_entry->document_state = document_state; + + // 13. Append session history traversal steps to targetNavigable's traversable to finalize a cross-document navigation with targetNavigable, historyHandling, and historyEntry. + traversable_navigable()->append_session_history_traversal_steps([this, history_entry, history_handling, navigation_id] { + if (this->ongoing_navigation() != navigation_id) { + // NOTE: This check is not in the spec but we should not continue navigation if ongoing navigation id has changed. + return; + } + finalize_a_cross_document_navigation(*this, history_handling, history_entry); + }); + + return {}; } // https://html.spec.whatwg.org/multipage/browsing-the-web.html#reload diff --git a/Userland/Libraries/LibWeb/HTML/Navigable.h b/Userland/Libraries/LibWeb/HTML/Navigable.h index b8366ea200..ea7832c4fc 100644 --- a/Userland/Libraries/LibWeb/HTML/Navigable.h +++ b/Userland/Libraries/LibWeb/HTML/Navigable.h @@ -123,7 +123,8 @@ public: WebIDL::ExceptionOr navigate_to_a_fragment(AK::URL const&, HistoryHandlingBehavior, String navigation_id); - WebIDL::ExceptionOr navigate_to_a_javascript_url(AK::URL const&, HistoryHandlingBehavior, Origin const& initiator_origin, CSPNavigationType csp_navigation_type); + WebIDL::ExceptionOr> evaluate_javascript_url(AK::URL const&, Origin const& new_document_origin, String navigation_id); + WebIDL::ExceptionOr navigate_to_a_javascript_url(AK::URL const&, HistoryHandlingBehavior, Origin const& initiator_origin, CSPNavigationType csp_navigation_type, String navigation_id); void reload();