LibWebView+RequestServer: Add some UI for DNS settings

This commit is contained in:
Ali Mohammad Pur 2025-04-08 03:56:35 +02:00 committed by Tim Flynn
parent e1369aa7d6
commit 2c13504bfc
Notes: github-actions[bot] 2025-04-22 22:06:21 +00:00
12 changed files with 284 additions and 14 deletions

View file

@ -74,6 +74,11 @@
padding: 15px 20px;
}
.inner-header {
font-size: 16px;
font-weight: 500;
}
.card-body {
padding: 20px;
}
@ -378,6 +383,46 @@
</div>
</div>
<div class="card">
<div class="card-header">Network</div>
<div class="card-body">
<div class="card-group">
<div class="card-header inner-header inline-container">
<span>DNS Settings</span>
<span id="dns-forcibly-enabled" class="forcibly-enabled hidden">
This setting is controlled via the command line
</span>
</div>
<div id="dns-settings-container" class="card-body">
<div class="card-group">
<label for="dns-upstream">DNS Upstream</label>
<select id="dns-upstream">
<option value="system">System DNS</option>
<option value="custom">Custom DNS Server</option>
</select>
</div>
<div id="custom-dns-settings" class="hidden">
<div class="card-group">
<label for="dns-type">Type</label>
<select id="dns-type">
<option value="udp">UDP</option>
<option value="tls">TLS</option>
</select>
</div>
<div class="card-group">
<label for="dns-server">DNS Server (IP or hostname)</label>
<input id="dns-server" type="text" />
</div>
<div class="card-group">
<label for="dns-port">Port</label>
<input id="dns-port" type="text" placeholder="53" />
</div>
</div>
</div>
</div>
</div>
</div>
<div class="button-container">
<button id="restore-defaults" class="primary-button">Restore&nbsp;Defaults</button>
</div>
@ -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);
}
});
</script>

View file

@ -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<DNSSettings> { 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);
});
}
}

View file

@ -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<DevTools::DevToolsServer> m_devtools;
} SWIFT_IMMORTAL_REFERENCE;
}
#define WEB_VIEW_APPLICATION(ApplicationType) \

View file

@ -200,7 +200,7 @@ ErrorOr<NonnullRefPtr<Requests::RequestClient>> launch_request_server_process()
}
auto client = TRY(launch_server_process<Requests::RequestClient>("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);

View file

@ -71,7 +71,7 @@ struct BrowserOptions {
Optional<ProcessType> debug_helper_process {};
Optional<ProcessType> profile_helper_process {};
Optional<ByteString> webdriver_content_ipc_path {};
DNSSettings dns_settings { SystemDNS {} };
Optional<DNSSettings> dns_settings {};
u16 devtools_port { default_devtools_port };
};

View file

@ -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<JsonObject> 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<Application>)
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>, 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);
}

View file

@ -13,6 +13,7 @@
#include <LibURL/URL.h>
#include <LibWebView/Autocomplete.h>
#include <LibWebView/Forward.h>
#include <LibWebView/Options.h>
#include <LibWebView/SearchEngine.h>
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>, SettingsObserver&);
static void remove_observer(Badge<SettingsObserver>, SettingsObserver&);
@ -95,6 +112,8 @@ private:
Optional<AutocompleteEngine> 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<SettingsObserver&> m_observers;
};

View file

@ -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();
}
}

View file

@ -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&);
};
}

View file

@ -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)
{

View file

@ -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;

View file

@ -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)