From 2c13504bfc22970fd4f9afee73e76e835f4f1596 Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Tue, 8 Apr 2025 03:56:35 +0200 Subject: [PATCH] LibWebView+RequestServer: Add some UI for DNS settings --- Base/res/ladybird/about-pages/settings.html | 123 +++++++++++++++++- Libraries/LibWebView/Application.cpp | 34 ++++- Libraries/LibWebView/Application.h | 7 +- Libraries/LibWebView/HelperProcess.cpp | 2 +- Libraries/LibWebView/Options.h | 2 +- Libraries/LibWebView/Settings.cpp | 89 ++++++++++++- Libraries/LibWebView/Settings.h | 21 ++- Libraries/LibWebView/WebUI/SettingsUI.cpp | 9 ++ Libraries/LibWebView/WebUI/SettingsUI.h | 2 + .../RequestServer/ConnectionFromClient.cpp | 7 + Services/RequestServer/ConnectionFromClient.h | 1 + Services/RequestServer/RequestServer.ipc | 1 + 12 files changed, 284 insertions(+), 14 deletions(-) diff --git a/Base/res/ladybird/about-pages/settings.html b/Base/res/ladybird/about-pages/settings.html index d238ae383e0..0477a65fcfb 100644 --- a/Base/res/ladybird/about-pages/settings.html +++ b/Base/res/ladybird/about-pages/settings.html @@ -74,6 +74,11 @@ padding: 15px 20px; } + .inner-header { + font-size: 16px; + font-weight: 500; + } + .card-body { padding: 20px; } @@ -378,6 +383,46 @@ +
+
Network
+
+
+
+ DNS Settings + +
+
+
+ + +
+ +
+
+
+
+
@@ -487,6 +532,12 @@ const siteSettingsTitle = document.querySelector("#site-settings-title"); const doNotTrackToggle = document.querySelector("#do-not-track-toggle"); const restoreDefaults = document.querySelector("#restore-defaults"); + const dnsUpstream = document.querySelector("#dns-upstream"); + const dnsServer = document.querySelector("#dns-server"); + const dnsPort = document.querySelector("#dns-port"); + const dnsType = document.querySelector("#dns-type"); + const customDnsSettings = document.querySelector("#custom-dns-settings"); + const dnsForciblyEnabled = document.querySelector("#dns-forcibly-enabled"); // FIXME: When we support per-glyph font fallbacks, replace these SVGs with analogous code points. // https://github.com/LadybirdBrowser/ladybird/issues/864 @@ -539,6 +590,8 @@ } doNotTrackToggle.checked = window.settings.doNotTrack; + + loadDnsSettings(window.settings.dnsSettings); }; const containsValidURL = input => { @@ -989,6 +1042,68 @@ }); }); + // DNS settings + const loadDnsSettings = settings => { + dnsUpstream.value = settings.mode; + + if (settings.mode === "custom") { + dnsServer.value = settings.server; + dnsPort.value = settings.port; + dnsType.value = settings.type; + customDnsSettings.classList.remove("hidden"); + } else { + dnsServer.value = ""; + dnsType.value = "udp"; + dnsPort.value = "53"; + customDnsSettings.classList.add("hidden"); + } + + if (settings.forciblyEnabled) { + dnsForciblyEnabled.classList.remove("hidden"); + dnsUpstream.setAttribute("disabled", ""); + dnsServer.setAttribute("disabled", ""); + dnsPort.setAttribute("disabled", ""); + dnsType.setAttribute("disabled", ""); + } else { + dnsForciblyEnabled.classList.add("hidden"); + dnsUpstream.removeAttribute("disabled"); + dnsServer.removeAttribute("disabled"); + dnsPort.removeAttribute("disabled"); + dnsType.removeAttribute("disabled"); + } + }; + + dnsUpstream.addEventListener("change", () => { + if (dnsUpstream.value === "custom") { + customDnsSettings.classList.remove("hidden"); + if (dnsServer.value !== "" && dnsPort.value !== "") { + updateDnsSettings(); + } + } else { + customDnsSettings.classList.add("hidden"); + ladybird.sendMessage("setDNSSettings", { mode: "system" }); + } + }); + + function updateDnsSettings() { + if (dnsUpstream.value === "custom") { + dnsPort.placeholder = dnsType.value === "tls" ? "853" : "53"; + if ((dnsPort.value | 0) === 0) dnsPort.value = dnsPort.placeholder; + + const settings = { + mode: "custom", + server: dnsServer.value, + port: dnsPort.value | 0, + type: dnsType.value, + }; + ladybird.sendMessage("setDNSSettings", settings); + } + } + + dnsServer.addEventListener("change", updateDnsSettings); + dnsPort.addEventListener("change", updateDnsSettings); + dnsType.addEventListener("change", updateDnsSettings); + autoplaySettings.addEventListener("click", event => { showSiteSettings("Autoplay", window.settings.autoplay); event.stopPropagation(); @@ -1003,7 +1118,7 @@ }); // FIXME: This should be replaced once we support popover light dismissal. - document.querySelectorAll("dialog").forEach((dialog) => + document.querySelectorAll("dialog").forEach(dialog => dialog.addEventListener("click", event => { const rect = dialog.getBoundingClientRect(); @@ -1015,8 +1130,8 @@ ) { dialog.close(); } - } - )); + }) + ); document.addEventListener("WebUILoaded", () => { ladybird.sendMessage("loadAvailableEngines"); @@ -1033,6 +1148,8 @@ loadEngines(Engine.autocomplete, event.detail.data.autocomplete); } else if (event.detail.name === "forciblyEnableSiteSettings") { forciblyEnableSiteSettings(event.detail.data); + } else if (event.detail.name === "loadDNSSettings") { + loadDnsSettings(event.detail.data); } }); diff --git a/Libraries/LibWebView/Application.cpp b/Libraries/LibWebView/Application.cpp index 3c5a448afc8..c3f9a60d18c 100644 --- a/Libraries/LibWebView/Application.cpp +++ b/Libraries/LibWebView/Application.cpp @@ -27,7 +27,8 @@ namespace WebView { Application* Application::s_the = nullptr; Application::Application() - : m_settings(Settings::create({})) + : SettingsObserver(AddToObservers::No) // Application::the() is not set yet. + , m_settings(Settings::create({})) { VERIFY(!s_the); s_the = this; @@ -51,10 +52,13 @@ Application::Application() m_process_manager.on_process_exited = [this](Process&& process) { process_did_exit(move(process)); }; + + SettingsObserver::complete_delayed_registration(); } Application::~Application() { + SettingsObserver::complete_delayed_unregistration(); s_the = nullptr; } @@ -164,13 +168,16 @@ void Application::initialize(Main::Arguments const& arguments) .debug_helper_process = move(debug_process_type), .profile_helper_process = move(profile_process_type), .dns_settings = (dns_server_address.has_value() - ? (use_dns_over_tls + ? Optional { use_dns_over_tls ? DNSSettings(DNSOverTLS(dns_server_address.release_value(), *dns_server_port)) - : DNSSettings(DNSOverUDP(dns_server_address.release_value(), *dns_server_port))) - : SystemDNS {}), + : DNSSettings(DNSOverUDP(dns_server_address.release_value(), *dns_server_port)) } + : OptionalNone()), .devtools_port = devtools_port, }; + if (m_browser_options.dns_settings.has_value()) + m_settings.set_dns_settings(m_browser_options.dns_settings.value(), true); + if (webdriver_content_ipc_path.has_value()) m_browser_options.webdriver_content_ipc_path = *webdriver_content_ipc_path; @@ -710,4 +717,23 @@ void Application::request_console_messages(DevTools::TabDescription const& descr view->js_console_request_messages(start_index); } +void Application::dns_settings_changed() +{ + if (!m_request_server_client) + return; + auto dns_settings = settings().dns_settings(); + auto& rs_client = *m_request_server_client; + dns_settings.visit( + [&](SystemDNS) { + rs_client.async_set_use_system_dns(); + }, + [&](DNSOverTLS const& dns_over_tls) { + dbgln("Setting DNS server to {}:{} with TLS", dns_over_tls.server_address, dns_over_tls.port); + rs_client.async_set_dns_server(dns_over_tls.server_address, dns_over_tls.port, true); + }, + [&](DNSOverUDP const& dns_over_udp) { + dbgln("Setting DNS server to {}:{}", dns_over_udp.server_address, dns_over_udp.port); + rs_client.async_set_dns_server(dns_over_udp.server_address, dns_over_udp.port, false); + }); +} } diff --git a/Libraries/LibWebView/Application.h b/Libraries/LibWebView/Application.h index fcda9dcdec0..f7f09e4dfa2 100644 --- a/Libraries/LibWebView/Application.h +++ b/Libraries/LibWebView/Application.h @@ -26,7 +26,8 @@ namespace WebView { -class Application : public DevTools::DevToolsDelegate { +class Application : public DevTools::DevToolsDelegate + , public SettingsObserver { AK_MAKE_NONCOPYABLE(Application); public: @@ -128,6 +129,9 @@ private: virtual void stop_listening_for_console_messages(DevTools::TabDescription const&) const override; virtual void request_console_messages(DevTools::TabDescription const&, i32) const override; + // ^SettingsObserver + virtual void dns_settings_changed() override; + static Application* s_the; Settings m_settings; @@ -152,7 +156,6 @@ private: OwnPtr m_devtools; } SWIFT_IMMORTAL_REFERENCE; - } #define WEB_VIEW_APPLICATION(ApplicationType) \ diff --git a/Libraries/LibWebView/HelperProcess.cpp b/Libraries/LibWebView/HelperProcess.cpp index 124c247d852..c3af333b38b 100644 --- a/Libraries/LibWebView/HelperProcess.cpp +++ b/Libraries/LibWebView/HelperProcess.cpp @@ -200,7 +200,7 @@ ErrorOr> launch_request_server_process() } auto client = TRY(launch_server_process("RequestServer"sv, move(arguments))); - WebView::Application::browser_options().dns_settings.visit( + WebView::Application::settings().dns_settings().visit( [](WebView::SystemDNS) {}, [&](WebView::DNSOverTLS const& dns_over_tls) { dbgln("Setting DNS server to {}:{} with TLS", dns_over_tls.server_address, dns_over_tls.port); diff --git a/Libraries/LibWebView/Options.h b/Libraries/LibWebView/Options.h index 88e242dff1a..cc848a663ab 100644 --- a/Libraries/LibWebView/Options.h +++ b/Libraries/LibWebView/Options.h @@ -71,7 +71,7 @@ struct BrowserOptions { Optional debug_helper_process {}; Optional profile_helper_process {}; Optional webdriver_content_ipc_path {}; - DNSSettings dns_settings { SystemDNS {} }; + Optional dns_settings {}; u16 devtools_port { default_devtools_port }; }; diff --git a/Libraries/LibWebView/Settings.cpp b/Libraries/LibWebView/Settings.cpp index aab10c43b68..fdf69a04efe 100644 --- a/Libraries/LibWebView/Settings.cpp +++ b/Libraries/LibWebView/Settings.cpp @@ -41,6 +41,8 @@ static constexpr auto autoplay_key = "autoplay"sv; static constexpr auto do_not_track_key = "doNotTrack"sv; +static constexpr auto dns_settings_key = "dnsSettings"sv; + static ErrorOr read_settings_file(StringView settings_path) { auto settings_file = Core::File::open(settings_path, Core::File::OpenMode::Read); @@ -134,9 +136,40 @@ Settings Settings::create(Badge) if (auto do_not_track = settings_json.value().get_bool(do_not_track_key); do_not_track.has_value()) settings.m_do_not_track = *do_not_track ? DoNotTrack::Yes : DoNotTrack::No; + if (auto dns_settings = settings_json.value().get(dns_settings_key); dns_settings.has_value()) + settings.m_dns_settings = parse_dns_settings(*dns_settings); + return settings; } +DNSSettings Settings::parse_dns_settings(JsonValue const& dns_settings) +{ + if (!dns_settings.is_object()) + return SystemDNS {}; + + auto& dns_settings_object = dns_settings.as_object(); + auto mode = dns_settings_object.get_string("mode"sv); + if (mode.has_value()) { + if (*mode == "system"sv) + return SystemDNS {}; + if (*mode == "custom"sv) { + auto server = dns_settings_object.get_string("server"sv); + auto port = dns_settings_object.get_u16("port"sv); + auto type = dns_settings_object.get_string("type"sv); + + if (server.has_value() && port.has_value() && type.has_value()) { + if (*type == "tls"sv) + return DNSOverTLS { .server_address = server->to_byte_string(), .port = *port }; + if (*type == "udp"sv) + return DNSOverUDP { .server_address = server->to_byte_string(), .port = *port }; + } + } + } + + dbgln("Invalid DNS settings in parse_dns_settings, falling back to system DNS"); + return SystemDNS {}; +} + Settings::Settings(ByteString settings_path) : m_settings_path(move(settings_path)) , m_new_tab_page_url(URL::about_newtab()) @@ -202,6 +235,28 @@ JsonValue Settings::serialize_json() const settings.set(do_not_track_key, m_do_not_track == DoNotTrack::Yes); + // dnsSettings :: { mode: "system" } | { mode: "custom", server: string, port: u16, type: "udp" | "tls", forciblyEnabled: bool } + JsonObject dns_settings; + m_dns_settings.visit( + [&](SystemDNS) { + dns_settings.set("mode"sv, "system"sv); + }, + [&](DNSOverTLS const& dot) { + dns_settings.set("mode"sv, "custom"sv); + dns_settings.set("server"sv, dot.server_address.view()); + dns_settings.set("port"sv, dot.port); + dns_settings.set("type"sv, "tls"sv); + dns_settings.set("forciblyEnabled"sv, m_dns_override_by_command_line); + }, + [&](DNSOverUDP const& dns) { + dns_settings.set("mode"sv, "custom"sv); + dns_settings.set("server"sv, dns.server_address.view()); + dns_settings.set("port"sv, dns.port); + dns_settings.set("type"sv, "udp"sv); + dns_settings.set("forciblyEnabled"sv, m_dns_override_by_command_line); + }); + settings.set(dns_settings_key, move(dns_settings)); + return settings; } @@ -214,6 +269,7 @@ void Settings::restore_defaults() m_autocomplete_engine.clear(); m_autoplay = SiteSetting {}; m_do_not_track = DoNotTrack::No; + m_dns_settings = SystemDNS {}; persist_settings(); @@ -224,6 +280,7 @@ void Settings::restore_defaults() observer.autocomplete_engine_changed(); observer.autoplay_settings_changed(); observer.do_not_track_changed(); + observer.dns_settings_changed(); } } @@ -395,6 +452,18 @@ void Settings::set_do_not_track(DoNotTrack do_not_track) observer.do_not_track_changed(); } +void Settings::set_dns_settings(DNSSettings const& dns_settings, bool override_by_command_line) +{ + m_dns_settings = dns_settings; + m_dns_override_by_command_line = override_by_command_line; + + if (!override_by_command_line) + persist_settings(); + + for (auto& observer : m_observers) + observer.dns_settings_changed(); +} + void Settings::persist_settings() { auto settings = serialize_json(); @@ -416,13 +485,29 @@ void Settings::remove_observer(Badge, SettingsObserver& observ VERIFY(was_removed); } -SettingsObserver::SettingsObserver() +SettingsObserver::SettingsObserver(AddToObservers add_to_observers) { - Settings::add_observer({}, *this); + if (add_to_observers == AddToObservers::Yes) + Settings::add_observer({}, *this); + else + m_registration_was_delayed = true; } SettingsObserver::~SettingsObserver() { + if (!m_registration_was_delayed) + Settings::remove_observer({}, *this); +} + +void SettingsObserver::complete_delayed_registration() +{ + VERIFY(m_registration_was_delayed); + Settings::add_observer({}, *this); +} + +void SettingsObserver::complete_delayed_unregistration() +{ + VERIFY(m_registration_was_delayed); Settings::remove_observer({}, *this); } diff --git a/Libraries/LibWebView/Settings.h b/Libraries/LibWebView/Settings.h index 5a4138f124e..deefe1afb76 100644 --- a/Libraries/LibWebView/Settings.h +++ b/Libraries/LibWebView/Settings.h @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace WebView { @@ -31,7 +32,11 @@ enum class DoNotTrack { class SettingsObserver { public: - SettingsObserver(); + enum class AddToObservers { + No, + Yes, + }; + explicit SettingsObserver(AddToObservers = AddToObservers::Yes); virtual ~SettingsObserver(); virtual void new_tab_page_url_changed() { } @@ -40,6 +45,14 @@ public: virtual void autocomplete_engine_changed() { } virtual void autoplay_settings_changed() { } virtual void do_not_track_changed() { } + virtual void dns_settings_changed() { } + +protected: + void complete_delayed_registration(); + void complete_delayed_unregistration(); + +private: + bool m_registration_was_delayed { false }; }; class Settings { @@ -76,6 +89,10 @@ public: DoNotTrack do_not_track() const { return m_do_not_track; } void set_do_not_track(DoNotTrack); + static DNSSettings parse_dns_settings(JsonValue const&); + DNSSettings const& dns_settings() const { return m_dns_settings; } + void set_dns_settings(DNSSettings const&, bool override_by_command_line = false); + static void add_observer(Badge, SettingsObserver&); static void remove_observer(Badge, SettingsObserver&); @@ -95,6 +112,8 @@ private: Optional m_autocomplete_engine; SiteSetting m_autoplay; DoNotTrack m_do_not_track { DoNotTrack::No }; + DNSSettings m_dns_settings { SystemDNS() }; + bool m_dns_override_by_command_line { false }; Vector m_observers; }; diff --git a/Libraries/LibWebView/WebUI/SettingsUI.cpp b/Libraries/LibWebView/WebUI/SettingsUI.cpp index ec2269a4833..19a05a21016 100644 --- a/Libraries/LibWebView/WebUI/SettingsUI.cpp +++ b/Libraries/LibWebView/WebUI/SettingsUI.cpp @@ -63,6 +63,10 @@ void SettingsUI::register_interfaces() register_interface("setDoNotTrack"sv, [this](auto const& data) { set_do_not_track(data); }); + + register_interface("setDNSSettings"sv, [this](auto const& data) { + set_dns_settings(data); + }); } void SettingsUI::load_current_settings() @@ -263,4 +267,9 @@ void SettingsUI::set_do_not_track(JsonValue const& do_not_track) WebView::Application::settings().set_do_not_track(do_not_track.as_bool() ? DoNotTrack::Yes : DoNotTrack::No); } +void SettingsUI::set_dns_settings(JsonValue const& dns_settings) +{ + Application::settings().set_dns_settings(Settings::parse_dns_settings(dns_settings)); + load_current_settings(); +} } diff --git a/Libraries/LibWebView/WebUI/SettingsUI.h b/Libraries/LibWebView/WebUI/SettingsUI.h index b7202ea2b8a..a956ce47974 100644 --- a/Libraries/LibWebView/WebUI/SettingsUI.h +++ b/Libraries/LibWebView/WebUI/SettingsUI.h @@ -35,6 +35,8 @@ private: void remove_all_site_setting_filters(JsonValue const&); void set_do_not_track(JsonValue const&); + + void set_dns_settings(JsonValue const&); }; } diff --git a/Services/RequestServer/ConnectionFromClient.cpp b/Services/RequestServer/ConnectionFromClient.cpp index 69ab2adfa40..393e50a2889 100644 --- a/Services/RequestServer/ConnectionFromClient.cpp +++ b/Services/RequestServer/ConnectionFromClient.cpp @@ -376,6 +376,13 @@ void ConnectionFromClient::set_dns_server(ByteString host_or_address, u16 port, default_resolver()->dns.reset_connection(); } +void ConnectionFromClient::set_use_system_dns() +{ + g_dns_info.server_hostname = {}; + g_dns_info.server_address = {}; + default_resolver()->dns.reset_connection(); +} + #ifdef AK_OS_WINDOWS void ConnectionFromClient::start_request(i32, ByteString, URL::URL, HTTP::HeaderMap, ByteBuffer, Core::ProxyData) { diff --git a/Services/RequestServer/ConnectionFromClient.h b/Services/RequestServer/ConnectionFromClient.h index 41f12474b68..5fc9ced3304 100644 --- a/Services/RequestServer/ConnectionFromClient.h +++ b/Services/RequestServer/ConnectionFromClient.h @@ -41,6 +41,7 @@ private: virtual Messages::RequestServer::ConnectNewClientResponse connect_new_client() override; virtual Messages::RequestServer::IsSupportedProtocolResponse is_supported_protocol(ByteString) override; virtual void set_dns_server(ByteString host_or_address, u16 port, bool use_tls) override; + virtual void set_use_system_dns() override; virtual void start_request(i32 request_id, ByteString, URL::URL, HTTP::HeaderMap, ByteBuffer, Core::ProxyData) override; virtual Messages::RequestServer::StopRequestResponse stop_request(i32) override; virtual Messages::RequestServer::SetCertificateResponse set_certificate(i32, ByteString, ByteString) override; diff --git a/Services/RequestServer/RequestServer.ipc b/Services/RequestServer/RequestServer.ipc index 64a43833842..9bbbe99925f 100644 --- a/Services/RequestServer/RequestServer.ipc +++ b/Services/RequestServer/RequestServer.ipc @@ -10,6 +10,7 @@ endpoint RequestServer // use_tls: enable DNS over TLS set_dns_server(ByteString host_or_address, u16 port, bool use_tls) =| + set_use_system_dns() =| // Test if a specific protocol is supported, e.g "http" is_supported_protocol(ByteString protocol) => (bool supported)