From 8eb74bf747c5a8e618a82a55edbd2898ee958d82 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Wed, 20 May 2026 16:42:08 +0200 Subject: [PATCH] LibWeb+LibWebView: Preserve crashed page URLs on crash Load the browser-generated crash page as a synthetic response for the URL that was active when WebContent exited. This keeps the session history entry, response URL, and created Document aligned with the same navigation URL, so reload targets the original page without creating a local document for an HTTP(S) history entry. Suppress history metadata updates from the generated page and declare an inert rel=icon. Fallback favicon loading now follows the HTML condition that no link with the icon keyword exists, which avoids the credentialed /favicon.ico request from the crashed origin. --- Libraries/LibWeb/HTML/HTMLLinkElement.cpp | 14 ++++ Libraries/LibWeb/HTML/HTMLLinkElement.h | 1 + Libraries/LibWeb/HTML/Navigable.cpp | 75 +++++++++++++++++++- Libraries/LibWeb/Page/Page.cpp | 27 +++++++ Libraries/LibWeb/Page/Page.h | 1 + Libraries/LibWebView/ErrorHTML.h | 7 ++ Libraries/LibWebView/ViewImplementation.cpp | 25 +++++-- Libraries/LibWebView/ViewImplementation.h | 3 + Libraries/LibWebView/WebContentClient.cpp | 42 +++++++---- Services/WebContent/ConnectionFromClient.cpp | 6 ++ Services/WebContent/ConnectionFromClient.h | 1 + Services/WebContent/WebContentServer.ipc | 1 + 12 files changed, 183 insertions(+), 20 deletions(-) diff --git a/Libraries/LibWeb/HTML/HTMLLinkElement.cpp b/Libraries/LibWeb/HTML/HTMLLinkElement.cpp index f5bce1a5ef..b633088055 100644 --- a/Libraries/LibWeb/HTML/HTMLLinkElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLLinkElement.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -151,6 +152,11 @@ bool HTMLLinkElement::has_loaded_icon() const return m_relationship & Relationship::Icon && m_loaded_icon.has_value(); } +bool HTMLLinkElement::has_icon_keyword() const +{ + return m_relationship & Relationship::Icon; +} + void HTMLLinkElement::attribute_changed(FlyString const& name, Optional const& old_value, Optional const& value, Optional const& namespace_) { Base::attribute_changed(name, old_value, value, namespace_); @@ -993,6 +999,14 @@ void HTMLLinkElement::load_fallback_favicon_if_needed(GC::Ref doc return; if (!document->url().scheme().is_one_of("http"sv, "https"sv)) return; + auto icon_link_elements = DOM::HTMLCollection::create(*document, DOM::HTMLCollection::Scope::Descendants, [](DOM::Element const& element) { + if (!is(element)) + return false; + + return static_cast(element).has_icon_keyword(); + }); + if (icon_link_elements->length() != 0) + return; // AD-HOC: Don't load fallback favicon for auxiliary browsing contexts (popup windows). // This matches the behavior observed in Chrome and Firefox, and avoids unnecessary network requests diff --git a/Libraries/LibWeb/HTML/HTMLLinkElement.h b/Libraries/LibWeb/HTML/HTMLLinkElement.h index 35c41ad4d8..edfec0f05d 100644 --- a/Libraries/LibWeb/HTML/HTMLLinkElement.h +++ b/Libraries/LibWeb/HTML/HTMLLinkElement.h @@ -38,6 +38,7 @@ public: GC::Ref sizes(); bool has_loaded_icon() const; + bool has_icon_keyword() const; bool load_favicon_and_use_if_window_is_active(); static void load_fallback_favicon_if_needed(GC::Ref); diff --git a/Libraries/LibWeb/HTML/Navigable.cpp b/Libraries/LibWeb/HTML/Navigable.cpp index b0fdf39aa7..6b7defa8fc 100644 --- a/Libraries/LibWeb/HTML/Navigable.cpp +++ b/Libraries/LibWeb/HTML/Navigable.cpp @@ -2165,8 +2165,81 @@ void Navigable::begin_navigation(NavigateParams params) // 7. Let navigationParams be null. NavigationParamsVariant navigation_params = Navigable::NullOrError {}; - // FIXME: 8. If response is non-null: + // 8. If response is non-null: if (response) { + auto response_url = response->url(); + VERIFY(response_url.has_value()); + + // 1. Let sourcePolicyContainer be a clone of the sourceDocument's policy container, if + // sourceDocument is not null; otherwise null. + auto source_policy_container = source_snapshot_params->source_policy_container; + + // 2. Let policyContainer be the result of determining navigation params policy container given + // response's URL, null, sourcePolicyContainer, navigable's container document's policy container, + // and null. + GC::Ptr parent_policy_container; + if (auto container_document = this->container_document()) + parent_policy_container = container_document->policy_container(); + else if (*response_url == URL::about_srcdoc()) { + // NOTE: Specification assumes that only navigables corresponding to iframes can be navigated to about:srcdoc. + // We also use srcdoc to implement load_html() for top level navigables so we need a policy container + // because the navigable might not have a container. + parent_policy_container = heap().allocate(heap()); + } + auto policy_container = determine_navigation_params_policy_container(*response_url, heap(), {}, source_policy_container, parent_policy_container, {}); + + // 3. Let finalSandboxFlags be the union of targetSnapshotParams's sandboxing flags and + // policyContainer's CSP list's CSP-derived sandboxing flags. + auto final_sandbox_flags = target_snapshot_params.sandboxing_flags | policy_container->csp_list->csp_derived_sandboxing_flags(); + + // 4. Let responseOrigin be the result of determining the origin given response's URL, + // finalSandboxFlags, and documentState's initiator origin. + auto response_origin = determine_the_origin(response_url, final_sandbox_flags, document_state->initiator_origin()); + + // 5. Let coop be a new opener policy. + OpenerPolicy response_coop = {}; + + // 6. Let coopEnforcementResult be a new opener policy enforcement result with + // url: response's URL + // origin: responseOrigin + // opener policy: coop + OpenerPolicyEnforcementResult coop_enforcement_result { + .url = *response_url, + .origin = response_origin, + .opener_policy = response_coop, + }; + + // 7. Set navigationParams to a new navigation params, with + // id: navigationId + // navigable: navigable + // request: null + // response: response + // fetch controller: null + // commit early hints: null + // COOP enforcement result: coopEnforcementResult + // reserved environment: null + // origin: responseOrigin + // policy container: policyContainer + // final sandboxing flag set: finalSandboxFlags + // opener policy: coop + // FIXME: navigation timing type: "navigate" + // about base URL: documentState's about base URL + // user involvement: userInvolvement + navigation_params = heap().allocate( + navigation_id, + this, + nullptr, + response, + nullptr, + nullptr, + move(coop_enforcement_result), + nullptr, + move(response_origin), + policy_container, + final_sandbox_flags, + response_coop, + document_state->about_base_url(), + user_involvement); } // 9. Attempt to populate the history entry's document for historyEntry, given navigable, "navigate", diff --git a/Libraries/LibWeb/Page/Page.cpp b/Libraries/LibWeb/Page/Page.cpp index 157b5315a6..9126d0d2db 100644 --- a/Libraries/LibWeb/Page/Page.cpp +++ b/Libraries/LibWeb/Page/Page.cpp @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include #include #include @@ -128,6 +130,31 @@ void Page::load_html(StringView html) .user_involvement = HTML::UserNavigationInvolvement::BrowserUI }); } +void Page::load_html(StringView html, URL::URL const& url) +{ + // FIXME: #23909 Figure out why GC threshold does not stay low when repeatedly loading html from the WebView + heap().collect_garbage(); + + auto document = top_level_traversable()->active_document(); + auto& realm = document->realm(); + auto html_string = String::from_utf8(html).release_value_but_fixme_should_propagate_errors(); + + auto response = Fetch::Infrastructure::Response::create(realm.vm()); + response->url_list().append(url); + response->header_list()->append({ "Content-Type"sv, "text/html"sv }); + response->set_body(Fetch::Infrastructure::byte_sequence_as_body(realm, html_string.bytes())); + + HTML::Navigable::NavigateParams params { .url = url, + .source_document = *document, + .response = response, + .user_involvement = HTML::UserNavigationInvolvement::BrowserUI }; + + if (url == URL::about_srcdoc()) + params.document_resource = move(html_string); + + (void)top_level_traversable()->navigate(move(params)); +} + void Page::reload() { top_level_traversable()->reload(); diff --git a/Libraries/LibWeb/Page/Page.h b/Libraries/LibWeb/Page/Page.h index cdc0865ae2..087fcc301c 100644 --- a/Libraries/LibWeb/Page/Page.h +++ b/Libraries/LibWeb/Page/Page.h @@ -97,6 +97,7 @@ public: void load(URL::URL const&); void load_html(StringView); + void load_html(StringView, URL::URL const&); void reload(); diff --git a/Libraries/LibWebView/ErrorHTML.h b/Libraries/LibWebView/ErrorHTML.h index 5a74b547b4..7a8d6689c8 100644 --- a/Libraries/LibWebView/ErrorHTML.h +++ b/Libraries/LibWebView/ErrorHTML.h @@ -18,6 +18,7 @@ constexpr inline auto ERROR_HTML_HEADER = R"~~~( Error! +{}