Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Libraries/LibWeb/HTML/LocalNavigable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2218,6 +2218,8 @@ void LocalNavigable::populate_session_history_entry_document(
}

auto output = heap().allocate<PopulateSessionHistoryEntryDocumentOutput>();
// NB: result's redirect fields are moved into output here; so, all code below must read them from output —
// never from the moved-from result.
output->redirected_url = move(result->redirected_url);
output->classic_history_api_state = move(result->classic_history_api_state);
output->replacement_document_state = result->replacement_document_state;
Expand Down Expand Up @@ -2259,7 +2261,9 @@ void LocalNavigable::populate_session_history_entry_document(
auto error_message = navigation_params.has<NullOrError>() ? navigation_params.get<NullOrError>().value_or("Unknown error"_utf16) : "The request was denied."_utf16;
auto error_message_utf8 = error_message.to_utf8();

auto error_url = result->redirected_url.value_or(url);
// AD-HOC: Name the URL that actually failed to load: The last URL the navigation was redirected to, if
// any — rather than the URL it started at.
auto error_url = output->redirected_url.value_or(url);
auto error_html = load_error_page(error_url, error_message_utf8).release_value_but_fixme_should_propagate_errors();
output->document = create_document_for_inline_content(this, navigation_id, user_involvement, [this, error_html](auto& document) {
auto scripting_mode = document.is_scripting_enabled() ? HTML::ParserScriptingMode::Normal : HTML::ParserScriptingMode::Disabled;
Expand Down
8 changes: 5 additions & 3 deletions Libraries/LibWebView/WebContentClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -744,9 +744,11 @@ void WebContentClient::did_finish_loading(u64 page_id, Optional<Utf16String> nav
view->m_loading_navigation_id.clear();
view->m_loading_url.clear();
auto client_url = url;
// Browser-generated pages can finish with an internal document URL.
// Keep exposing the URL accepted at load start for suppressed loads.
if (view->m_should_suppress_history_for_current_load)
// Browser-generated pages can finish with an internal document URL. Keep exposing the URL accepted at load
// start for suppressed loads. Documents created for inline error content finish with about:error; keep the URL
// the view already shows, which for a failed navigation is the URL that failed to load, including any redirects
// the navigation was taken through. Firefox/Chromium likewise never surface their internal error-document URLs.
if (view->m_should_suppress_history_for_current_load || url == URL::about_error())
client_url = view->url();
else
view->set_url({}, url);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
error page names the redirect target that failed: true
error page names the pre-redirect URL: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!doctype html>
<script src="../include.js"></script>
<iframe id="target" style="width: 1600px; height: 400px"></iframe>
<script>
// When a navigation gets redirected and the fetch of the redirect target then fails, the error page must name the
// URL that actually failed (the redirect target) — not the URL the navigation started at. See #11014.
asyncTest(async done => {
const token = crypto.randomUUID().replaceAll("-", "");
const server = httpTestServer();

// The redirect target is an https URL on the echo server's plain-HTTP port, so its TLS handshake always fails —
// which fails the navigation after the redirect.
const failingURL = `https://127.0.0.1:${internals.getEchoServerPort()}/${token}`;
const redirectURL = await server.createEcho("GET", `/redirecttotlsfailure${token}`, {
status: 302,
headers: { Location: failingURL },
});

document.getElementById("target").src = redirectURL;

// The error page is cross-origin to this document — so read its rendered text through the layout dump, which
// descends into child documents.
const dumpContains = text => internals.dumpLayoutTree(document.documentElement).includes(text);
// Bound the wait well inside the harness's 30-second per-test timeout — so a missing error page fails with a
// diagnosable message, instead of a bare test timeout.
const deadline = performance.now() + 15_000;
while (!dumpContains("Failed to load")) {
if (performance.now() > deadline)
throw new Error("Timed out waiting for the error page to appear in the layout dump");
await new Promise(resolve => setTimeout(resolve, 50));
}

println(`error page names the redirect target that failed: ${dumpContains(failingURL)}`);
println(`error page names the pre-redirect URL: ${dumpContains(redirectURL)}`);
done();
});
</script>
56 changes: 55 additions & 1 deletion Tests/LibWebView/test-webdriver-session-history.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,12 @@ def do_GET(self):
self.end_headers()
return

if self.path == "/redirect-to-tls-failure":
self.send_response(302)
self.send_header("Location", f"https://127.0.0.1:{server_port}/tls-failure")
self.end_headers()
return

if self.path == "/redirect-to-navigation-blocked":
self.send_response(302)
self.send_header("Location", f"http://localhost:{server_port}/navigation-blocked")
Expand Down Expand Up @@ -2234,6 +2240,54 @@ def run_webdriver_fragment_navigation_test(webdriver_port, url):
request(webdriver_port, "DELETE", f"/session/{session_id}")


def run_failed_redirected_navigation_shows_failed_url_test(webdriver_port, page_port, url_a):
url_redirect_to_tls_failure = f"http://localhost:{page_port}/redirect-to-tls-failure"
url_tls_failure = f"https://127.0.0.1:{page_port}/tls-failure"
session_id = create_session(webdriver_port)
log = [f"failed redirected navigation initial: {current_url(webdriver_port, session_id)}"]
load_url_from_ui(webdriver_port, session_id, url_a)
expect_url(webdriver_port, session_id, "after failed redirected navigation setup /a", url_a, log)

load_url_from_ui(webdriver_port, session_id, url_redirect_to_tls_failure)

def error_page_history_converged(snapshot):
ui = snapshot["ui"]
return (
history_entry_urls(ui) == [url_a, url_tls_failure]
and ui["webContentHistoryMatchesUI"]
and not ui["waitingToSeedWebContent"]
and not ui["waitingForWebContentSeedAck"]
and not ui["ignoringWebContentUpdatesUntilSeed"]
and not ui["reseedAfterCurrentHistoryLoad"]
)

snapshot = wait_for_session_history(
webdriver_port,
session_id,
"after failed redirected navigation",
error_page_history_converged,
log,
)
ui_current_url = snapshot["ui"]["currentURL"]
if ui_current_url != url_tls_failure:
raise AssertionError(
f"Expected the UI to show the failed URL {url_tls_failure}, got {ui_current_url}\n" + "\n".join(log)
)

web_content_entry_urls = history_entry_urls(snapshot["webContent"])
if web_content_entry_urls != [url_a, url_tls_failure]:
raise AssertionError(
f"Expected WebContent entries [{url_a}, {url_tls_failure}], got {web_content_entry_urls}\n" + "\n".join(log)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
)

request(webdriver_port, "DELETE", f"/session/{session_id}")


def run_self_contained_navigation_tests(webdriver_port, page_port, url_a):
run_webdriver_fragment_navigation_test(webdriver_port, url_a)
run_failed_redirected_navigation_shows_failed_url_test(webdriver_port, page_port, url_a)


def expect_cross_site_fragment_navigation_from_ui_loads_document(
webdriver_port,
session_id,
Expand Down Expand Up @@ -2808,7 +2862,7 @@ def run_test(webdriver_binary):
request(webdriver_port, "DELETE", f"/session/{session_id}")
session_id = None

run_webdriver_fragment_navigation_test(webdriver_port, url_a)
run_self_contained_navigation_tests(webdriver_port, page_port, url_a)

session_id = create_session(webdriver_port)
log = [f"duplicate URL crash recovery initial: {current_url(webdriver_port, session_id)}"]
Expand Down