LibWebView+UI: Show an error page when we cannot sanitize a URL

Previously, if search was disabled, entering non-URL text would just
silently drop the search query (and on Qt, we would reload the current
URL). We now detect that the query did not result in a navigation and
load an error page instead, which directs the user to enable search.
This commit is contained in:
Timothy Flynn
2026-04-24 08:36:55 -04:00
committed by Andreas Kling
parent 83eda625d5
commit fdbdb0ecd2
Notes: github-actions[bot] 2026-04-24 18:18:24 +00:00
7 changed files with 129 additions and 30 deletions

View File

@@ -0,0 +1,89 @@
/*
* Copyright (c) 2026-present, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/StringView.h>
namespace WebView {
// FIXME: Move these to an HTML file in Base/res/ladybird.
constexpr inline auto ERROR_HTML_HEADER = R"~~~(
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Error!</title>
<style>
:root {{
color-scheme: light dark;
font-family: system-ui, sans-serif;
}}
body {{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
box-sizing: border-box;
margin: 0;
padding: 1rem;
text-align: center;
}}
header {{
display: flex;
flex-direction: column;
align-items: center;
gap: 2rem;
margin-bottom: 1rem;
}}
svg {{
height: 64px;
width: auto;
stroke: currentColor;
fill: none;
stroke-width: 1.5;
stroke-linecap: round;
stroke-linejoin: round;
}}
h1 {{
margin: 0;
font-size: 1.5rem;
}}
p {{
font-size: 1rem;
color: #555;
}}
</style>
</head>
<body>
<header>
{}
<h1>{}</h1>
</header>
)~~~"sv;
constexpr inline auto ERROR_HTML_FOOTER = R"~~~(
</body>
</html>
)~~~"sv;
constexpr inline auto ERROR_SVG = R"~~~(
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 17.5 21.5">
<path d="M11.75.75h-9c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-13l-5-5z" />
<path d="M10.75.75v4c0 1.1.9 2 2 2h4M5.75 9.75v2M11.75 9.75v2M5.75 16.75c1-2.67 5-2.67 6 0" />
</svg>
)~~~"sv;
constexpr inline auto CRASH_ERROR_SVG = R"~~~(
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 17.5 21.5">
<path class="b" d="M11.75.75h-9c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-13l-5-5z" />
<path class="b" d="M10.75.75v4c0 1.1.9 2 2 2h4M4.75 9.75l2 2M10.75 9.75l2 2M12.75 9.75l-2 2M6.75 9.75l-2 2M5.75 16.75c1-2.67 5-2.67 6 0" />
</svg>
)~~~"sv;
}

View File

@@ -17,6 +17,7 @@
#include <LibWeb/Infra/Strings.h>
#include <LibWebView/Application.h>
#include <LibWebView/BookmarkStore.h>
#include <LibWebView/ErrorHTML.h>
#include <LibWebView/HelperProcess.h>
#include <LibWebView/HistoryStore.h>
#include <LibWebView/Menu.h>
@@ -186,6 +187,17 @@ void ViewImplementation::load_html(StringView html)
client().async_load_html(page_id(), html);
}
void ViewImplementation::load_navigation_error_page(StringView text)
{
auto message = MUST(String::formatted("Failed to load \"{}\"", text));
StringBuilder builder;
builder.appendff(ERROR_HTML_HEADER, ERROR_SVG, message);
builder.append("<p>If you were trying to enter a search query, please enable search in <a href=\"about:settings#search\">settings</a>.</p>"sv);
builder.append(ERROR_HTML_FOOTER);
load_html(builder.string_view());
}
void ViewImplementation::reload()
{
client().async_reload(page_id());
@@ -692,25 +704,13 @@ void ViewImplementation::handle_web_content_process_crash(LoadErrorPage load_err
handle_resize();
if (load_error_page == LoadErrorPage::Yes) {
auto escaped_url = escape_html_entities(m_url.serialize());
StringBuilder builder;
builder.append("<!DOCTYPE html>"sv);
builder.append("<html lang=\"en\"><head><meta charset=\"UTF-8\"><title>Error!</title><style>"
":root { color-scheme: light dark; font-family: system-ui, sans-serif; }"
"body { display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; box-sizing: border-box; margin: 0; padding: 1rem; text-align: center; }"
"header { display: flex; flex-direction: column; align-items: center; gap: 2rem; margin-bottom: 1rem; }"
"svg { height: 64px; width: auto; stroke: currentColor; fill: none; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }"
"h1 { margin: 0; font-size: 1.5rem; }"
"p { font-size: 1rem; color: #555; }"
"</style></head><body>"sv);
builder.append("<header>"sv);
builder.append("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 17.5 21.5\">"sv);
builder.append("<path class=\"b\" d=\"M11.75.75h-9c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-13l-5-5z\"/>"sv);
builder.append("<path class=\"b\" d=\"M10.75.75v4c0 1.1.9 2 2 2h4M4.75 9.75l2 2M10.75 9.75l2 2M12.75 9.75l-2 2M6.75 9.75l-2 2M5.75 16.75c1-2.67 5-2.67 6 0\"/></svg>"sv);
auto escaped_url = escape_html_entities(m_url.to_byte_string());
builder.append("<h1>Ladybird flew off-course!</h1>"sv);
builder.appendff(ERROR_HTML_HEADER, CRASH_ERROR_SVG, "Ladybird flew off-course!"sv);
builder.appendff("<p>The web page <a href=\"{}\">{}</a> has crashed.<br><br>You can reload the page to try again.</p>", escaped_url, escaped_url);
builder.append("</body></html>"sv);
load_html(builder.to_byte_string());
builder.append(ERROR_HTML_FOOTER);
load_html(builder.string_view());
}
}

View File

@@ -79,6 +79,8 @@ public:
void load(URL::URL const&);
void load_html(StringView);
void load_navigation_error_page(StringView);
void reload();
void traverse_the_history_by_delta(int delta);

View File

@@ -609,6 +609,8 @@ static NSInteger autocomplete_suggestion_index(NSString* suggestion_text, Vector
if (auto url = WebView::sanitize_url(location, WebView::Application::settings().search_engine()); url.has_value()) {
[self loadURL:*url];
} else {
[[[self tab] web_view] view].load_navigation_error_page(location);
}
self.current_inline_autocomplete_suggestion = nil;

View File

@@ -180,8 +180,8 @@ LocationEdit::LocationEdit(QWidget* parent)
auto ctrl_held = QApplication::keyboardModifiers() & Qt::ControlModifier;
auto append_tld = ctrl_held ? WebView::AppendTLD::Yes : WebView::AppendTLD::No;
if (auto url = WebView::sanitize_url(query, WebView::Application::settings().search_engine(), append_tld); url.has_value())
set_url(url.release_value());
auto url = WebView::sanitize_url(query, WebView::Application::settings().search_engine(), append_tld);
set_url(AK::move(url));
});
connect(this, &QLineEdit::textEdited, this, [this] {
@@ -229,8 +229,8 @@ void LocationEdit::focusOutEvent(QFocusEvent* event)
if (m_url_is_hidden) {
m_url_is_hidden = false;
if (text().isEmpty())
setText(qstring_from_ak_string(m_url.serialize()));
if (text().isEmpty() && m_url.has_value())
setText(qstring_from_ak_string(m_url->serialize()));
}
if (event->reason() != Qt::PopupFocusReason) {
@@ -245,7 +245,8 @@ void LocationEdit::keyPressEvent(QKeyEvent* event)
if (m_autocomplete->close())
return;
reset_autocomplete_state();
setText(qstring_from_ak_string(m_url.serialize()));
if (m_url.has_value())
setText(qstring_from_ak_string(m_url->serialize()));
clearFocus();
return;
}
@@ -331,14 +332,14 @@ void LocationEdit::highlight_location()
QCoreApplication::sendEvent(this, &event);
}
void LocationEdit::set_url(URL::URL url)
void LocationEdit::set_url(Optional<URL::URL> url)
{
m_url = AK::move(url);
if (m_url_is_hidden) {
clear();
} else {
setText(qstring_from_ak_string(m_url.serialize()));
} else if (m_url.has_value()) {
setText(qstring_from_ak_string(m_url->serialize()));
setCursorPosition(0);
}
}

View File

@@ -27,8 +27,8 @@ class LocationEdit final
public:
explicit LocationEdit(QWidget*);
URL::URL const& url() const { return m_url; }
void set_url(URL::URL);
Optional<URL::URL const&> url() const { return m_url; }
void set_url(Optional<URL::URL>);
bool url_is_hidden() const { return m_url_is_hidden; }
void set_url_is_hidden(bool url_is_hidden) { m_url_is_hidden = url_is_hidden; }
@@ -52,7 +52,7 @@ private:
Autocomplete* m_autocomplete { nullptr };
URL::URL m_url;
Optional<URL::URL> m_url;
bool m_url_is_hidden { false };
bool m_is_applying_inline_autocomplete { false };

View File

@@ -485,9 +485,14 @@ void Tab::load_html(StringView html)
void Tab::location_edit_return_pressed()
{
if (m_location_edit->text().isEmpty())
auto text = m_location_edit->text();
if (text.isEmpty())
return;
navigate(m_location_edit->url());
if (auto url = m_location_edit->url(); url.has_value())
navigate(*url);
else
view().load_navigation_error_page(ak_string_from_qstring(text));
}
void Tab::open_file()