From fdbdb0ecd2837d10ff350363d820c23af7a074f0 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Fri, 24 Apr 2026 08:36:55 -0400 Subject: [PATCH] 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. --- Libraries/LibWebView/ErrorHTML.h | 89 +++++++++++++++++++++ Libraries/LibWebView/ViewImplementation.cpp | 34 ++++---- Libraries/LibWebView/ViewImplementation.h | 2 + UI/AppKit/Interface/TabController.mm | 2 + UI/Qt/LocationEdit.cpp | 17 ++-- UI/Qt/LocationEdit.h | 6 +- UI/Qt/Tab.cpp | 9 ++- 7 files changed, 129 insertions(+), 30 deletions(-) create mode 100644 Libraries/LibWebView/ErrorHTML.h diff --git a/Libraries/LibWebView/ErrorHTML.h b/Libraries/LibWebView/ErrorHTML.h new file mode 100644 index 00000000000..5a74b547b4d --- /dev/null +++ b/Libraries/LibWebView/ErrorHTML.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2026-present, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace WebView { + +// FIXME: Move these to an HTML file in Base/res/ladybird. + +constexpr inline auto ERROR_HTML_HEADER = R"~~~( + + + + + Error! + + + +
+ {} +

{}

+
+)~~~"sv; + +constexpr inline auto ERROR_HTML_FOOTER = R"~~~( + + +)~~~"sv; + +constexpr inline auto ERROR_SVG = R"~~~( + + + + +)~~~"sv; + +constexpr inline auto CRASH_ERROR_SVG = R"~~~( + + + + +)~~~"sv; + +} diff --git a/Libraries/LibWebView/ViewImplementation.cpp b/Libraries/LibWebView/ViewImplementation.cpp index e07b18bcd3e..26e5dfc8b26 100644 --- a/Libraries/LibWebView/ViewImplementation.cpp +++ b/Libraries/LibWebView/ViewImplementation.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -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("

If you were trying to enter a search query, please enable search in settings.

"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(""sv); - builder.append("Error!"sv); - builder.append("
"sv); - builder.append(""sv); - builder.append(""sv); - builder.append(""sv); - auto escaped_url = escape_html_entities(m_url.to_byte_string()); - builder.append("

Ladybird flew off-course!

"sv); + builder.appendff(ERROR_HTML_HEADER, CRASH_ERROR_SVG, "Ladybird flew off-course!"sv); builder.appendff("

The web page {} has crashed.

You can reload the page to try again.

", escaped_url, escaped_url); - builder.append(""sv); - load_html(builder.to_byte_string()); + builder.append(ERROR_HTML_FOOTER); + load_html(builder.string_view()); } } diff --git a/Libraries/LibWebView/ViewImplementation.h b/Libraries/LibWebView/ViewImplementation.h index b1e66bb19f4..ea76892ca9c 100644 --- a/Libraries/LibWebView/ViewImplementation.h +++ b/Libraries/LibWebView/ViewImplementation.h @@ -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); diff --git a/UI/AppKit/Interface/TabController.mm b/UI/AppKit/Interface/TabController.mm index 112c2b94f63..ad65f27da8e 100644 --- a/UI/AppKit/Interface/TabController.mm +++ b/UI/AppKit/Interface/TabController.mm @@ -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; diff --git a/UI/Qt/LocationEdit.cpp b/UI/Qt/LocationEdit.cpp index f8f4427328d..a3488f2949b 100644 --- a/UI/Qt/LocationEdit.cpp +++ b/UI/Qt/LocationEdit.cpp @@ -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) { 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); } } diff --git a/UI/Qt/LocationEdit.h b/UI/Qt/LocationEdit.h index 1fa5d7913d8..072a58e0c8f 100644 --- a/UI/Qt/LocationEdit.h +++ b/UI/Qt/LocationEdit.h @@ -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() const { return m_url; } + void set_url(Optional); 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 m_url; bool m_url_is_hidden { false }; bool m_is_applying_inline_autocomplete { false }; diff --git a/UI/Qt/Tab.cpp b/UI/Qt/Tab.cpp index c98e6c16345..9ff39eb3862 100644 --- a/UI/Qt/Tab.cpp +++ b/UI/Qt/Tab.cpp @@ -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()