From b169a98495c016ec172f14d482bf708a36ffc232 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Fri, 21 Mar 2025 09:18:02 -0400 Subject: [PATCH] LibWeb+LibWebView+WebContent: Introduce a basic about:settings page This adds a basic settings page to manage persistent Ladybird settings. As a first pass, this exposes settings for the new tab page URL and the default search engine. The way the search engine option works is that once search is enabled, the user must choose their default search engine; we do not apply any default automatically. Search remains disabled until this is done. There are a couple of improvements that we should make here: * Settings changes are not broadcasted to all open about:settings pages. So if two instances are open, and the user changes the search engine in one instance, the other instance will have a stale UI. * Adding an IPC per setting is going to get annoying. It would be nice if we can come up with a smaller set of IPCs to send only the relevant changed settings. --- Base/res/ladybird/about-pages/settings.html | 254 ++++++++++++++++++++ Libraries/LibWeb/CMakeLists.txt | 1 + Libraries/LibWeb/HTML/Window.cpp | 6 +- Libraries/LibWeb/Internals/Settings.cpp | 57 +++++ Libraries/LibWeb/Internals/Settings.h | 34 +++ Libraries/LibWeb/Internals/Settings.idl | 10 + Libraries/LibWeb/Page/Page.h | 6 + Libraries/LibWeb/idl_files.cmake | 1 + Libraries/LibWebView/Application.cpp | 30 +++ Libraries/LibWebView/Application.h | 3 + Libraries/LibWebView/WebContentClient.cpp | 33 +++ Libraries/LibWebView/WebContentClient.h | 5 + Services/WebContent/PageClient.cpp | 25 ++ Services/WebContent/PageClient.h | 5 + Services/WebContent/WebContentClient.ipc | 6 + UI/cmake/ResourceFiles.cmake | 1 + 16 files changed, 475 insertions(+), 2 deletions(-) create mode 100644 Base/res/ladybird/about-pages/settings.html create mode 100644 Libraries/LibWeb/Internals/Settings.cpp create mode 100644 Libraries/LibWeb/Internals/Settings.h create mode 100644 Libraries/LibWeb/Internals/Settings.idl diff --git a/Base/res/ladybird/about-pages/settings.html b/Base/res/ladybird/about-pages/settings.html new file mode 100644 index 00000000000..b482f266160 --- /dev/null +++ b/Base/res/ladybird/about-pages/settings.html @@ -0,0 +1,254 @@ + + + + Settings + + + + +
+ + + + +

Ladybird Settings

+
+ +
+
General
+
+
+ + +
+
+
+ +
+
Search
+
+
+
+ + +
+
+ +
+
+ +
+ +
+ + + + diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index c8e3c1a53e3..d452403eaf0 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -564,6 +564,7 @@ set(SOURCES Internals/Internals.cpp Internals/InternalsBase.cpp Internals/Processes.cpp + Internals/Settings.cpp IntersectionObserver/IntersectionObserver.cpp IntersectionObserver/IntersectionObserverEntry.cpp Layout/AudioBox.cpp diff --git a/Libraries/LibWeb/HTML/Window.cpp b/Libraries/LibWeb/HTML/Window.cpp index 231c5138f2e..2fe1d0b5a4b 100644 --- a/Libraries/LibWeb/HTML/Window.cpp +++ b/Libraries/LibWeb/HTML/Window.cpp @@ -61,6 +61,7 @@ #include #include #include +#include #include #include #include @@ -738,9 +739,10 @@ WebIDL::ExceptionOr Window::initialize_web_interfaces(Badge(realm), JS::default_attributes); - } + else if (path == "settings"sv) + define_direct_property("settings", realm.create(realm), JS::default_attributes); } return {}; diff --git a/Libraries/LibWeb/Internals/Settings.cpp b/Libraries/LibWeb/Internals/Settings.cpp new file mode 100644 index 00000000000..d3237f55d13 --- /dev/null +++ b/Libraries/LibWeb/Internals/Settings.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +namespace Web::Internals { + +GC_DEFINE_ALLOCATOR(Settings); + +Settings::Settings(JS::Realm& realm) + : InternalsBase(realm) +{ +} + +Settings::~Settings() = default; + +void Settings::initialize(JS::Realm& realm) +{ + Base::initialize(realm); + WEB_SET_PROTOTYPE_FOR_INTERFACE(Settings); +} + +void Settings::load_current_settings() +{ + page().client().request_current_settings(); +} + +void Settings::restore_default_settings() +{ + page().client().restore_default_settings(); +} + +void Settings::set_new_tab_page_url(String const& new_tab_page_url) +{ + if (auto parsed_new_tab_page_url = URL::Parser::basic_parse(new_tab_page_url); parsed_new_tab_page_url.has_value()) + page().client().set_new_tab_page_url(*parsed_new_tab_page_url); +} + +void Settings::load_available_search_engines() +{ + page().client().request_available_search_engines(); +} + +void Settings::set_search_engine(Optional const& search_engine) +{ + page().client().set_search_engine(search_engine); +} + +} diff --git a/Libraries/LibWeb/Internals/Settings.h b/Libraries/LibWeb/Internals/Settings.h new file mode 100644 index 00000000000..169d940dccf --- /dev/null +++ b/Libraries/LibWeb/Internals/Settings.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Web::Internals { + +class Settings final : public InternalsBase { + WEB_PLATFORM_OBJECT(Settings, InternalsBase); + GC_DECLARE_ALLOCATOR(Settings); + +public: + virtual ~Settings() override; + + void load_current_settings(); + void restore_default_settings(); + + void set_new_tab_page_url(String const& new_tab_page_url); + + void load_available_search_engines(); + void set_search_engine(Optional const& search_engine); + +private: + explicit Settings(JS::Realm&); + + virtual void initialize(JS::Realm&) override; +}; + +} diff --git a/Libraries/LibWeb/Internals/Settings.idl b/Libraries/LibWeb/Internals/Settings.idl new file mode 100644 index 00000000000..da584c72fd1 --- /dev/null +++ b/Libraries/LibWeb/Internals/Settings.idl @@ -0,0 +1,10 @@ +[Exposed=Nobody] +interface Settings { + undefined loadCurrentSettings(); + undefined restoreDefaultSettings(); + + undefined setNewTabPageURL(USVString newTabPageURL); + + undefined loadAvailableSearchEngines(); + undefined setSearchEngine(DOMString? search_engine); +}; diff --git a/Libraries/LibWeb/Page/Page.h b/Libraries/LibWeb/Page/Page.h index 5abb1043cf5..e845ff43cc7 100644 --- a/Libraries/LibWeb/Page/Page.h +++ b/Libraries/LibWeb/Page/Page.h @@ -402,6 +402,12 @@ public: virtual void update_process_statistics() { } + virtual void request_current_settings() { } + virtual void restore_default_settings() { } + virtual void set_new_tab_page_url(URL::URL const&) { } + virtual void request_available_search_engines() { } + virtual void set_search_engine(Optional const&) { } + virtual bool is_ready_to_paint() const = 0; virtual DisplayListPlayerType display_list_player_type() const = 0; diff --git a/Libraries/LibWeb/idl_files.cmake b/Libraries/LibWeb/idl_files.cmake index b51b98cd335..6962eb5a474 100644 --- a/Libraries/LibWeb/idl_files.cmake +++ b/Libraries/LibWeb/idl_files.cmake @@ -267,6 +267,7 @@ libweb_js_bindings(IndexedDB/IDBVersionChangeEvent) libweb_js_bindings(Internals/InternalAnimationTimeline) libweb_js_bindings(Internals/Internals) libweb_js_bindings(Internals/Processes) +libweb_js_bindings(Internals/Settings) libweb_js_bindings(IntersectionObserver/IntersectionObserver) libweb_js_bindings(IntersectionObserver/IntersectionObserverEntry) libweb_js_bindings(MathML/MathMLElement) diff --git a/Libraries/LibWebView/Application.cpp b/Libraries/LibWebView/Application.cpp index 5f3e7ab6825..3634ec2e910 100644 --- a/Libraries/LibWebView/Application.cpp +++ b/Libraries/LibWebView/Application.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -336,6 +337,35 @@ void Application::send_updated_process_statistics_to_view(ViewImplementation& vi view.run_javascript(MUST(builder.to_string())); } +void Application::send_current_settings_to_view(ViewImplementation& view) +{ + auto settings = m_settings.serialize_json(); + + StringBuilder builder; + builder.append("settings.loadSettings(\""sv); + builder.append_escaped_for_json(settings); + builder.append("\");"sv); + + view.run_javascript(MUST(builder.to_string())); +} + +void Application::send_available_search_engines_to_view(ViewImplementation& view) +{ + StringBuilder engines; + + auto serializer = MUST(JsonArraySerializer<>::try_create(engines)); + for (auto const& engine : search_engines()) + MUST(serializer.add(engine.name)); + MUST(serializer.finish()); + + StringBuilder builder; + builder.append("settings.loadSearchEngines(\""sv); + builder.append_escaped_for_json(engines.string_view()); + builder.append("\");"sv); + + view.run_javascript(MUST(builder.to_string())); +} + void Application::process_did_exit(Process&& process) { if (m_in_shutdown) diff --git a/Libraries/LibWebView/Application.h b/Libraries/LibWebView/Application.h index db73ecb4bd0..04522eb2572 100644 --- a/Libraries/LibWebView/Application.h +++ b/Libraries/LibWebView/Application.h @@ -61,6 +61,9 @@ public: void send_updated_process_statistics_to_view(ViewImplementation&); + void send_current_settings_to_view(ViewImplementation&); + void send_available_search_engines_to_view(ViewImplementation&); + ErrorOr path_for_downloaded_file(StringView file) const; enum class DevtoolsState { diff --git a/Libraries/LibWebView/WebContentClient.cpp b/Libraries/LibWebView/WebContentClient.cpp index feef3aee2f3..cd7d0e7bacb 100644 --- a/Libraries/LibWebView/WebContentClient.cpp +++ b/Libraries/LibWebView/WebContentClient.cpp @@ -670,6 +670,39 @@ void WebContentClient::update_process_statistics(u64 page_id) WebView::Application::the().send_updated_process_statistics_to_view(*view); } +void WebContentClient::request_current_settings(u64 page_id) +{ + if (auto view = view_for_page_id(page_id); view.has_value()) + WebView::Application::the().send_current_settings_to_view(*view); +} + +void WebContentClient::restore_default_settings(u64 page_id) +{ + WebView::Application::settings().restore_defaults(); + request_current_settings(page_id); +} + +void WebContentClient::set_new_tab_page_url(u64 page_id, URL::URL new_tab_page_url) +{ + WebView::Application::settings().set_new_tab_page_url(move(new_tab_page_url)); + request_current_settings(page_id); +} + +void WebContentClient::request_available_search_engines(u64 page_id) +{ + if (auto view = view_for_page_id(page_id); view.has_value()) + WebView::Application::the().send_available_search_engines_to_view(*view); +} + +void WebContentClient::set_search_engine(u64 page_id, Optional search_engine) +{ + WebView::Application::settings().set_search_engine(search_engine.map([](auto const& search_engine) { + return search_engine.bytes_as_string_view(); + })); + + request_current_settings(page_id); +} + Optional WebContentClient::view_for_page_id(u64 page_id, SourceLocation location) { // Don't bother logging anything for the spare WebContent process. It will only receive a load notification for about:blank. diff --git a/Libraries/LibWebView/WebContentClient.h b/Libraries/LibWebView/WebContentClient.h index 205f433c2ea..fcb738385c1 100644 --- a/Libraries/LibWebView/WebContentClient.h +++ b/Libraries/LibWebView/WebContentClient.h @@ -130,6 +130,11 @@ private: virtual void did_allocate_backing_stores(u64 page_id, i32 front_bitmap_id, Gfx::ShareableBitmap, i32 back_bitmap_id, Gfx::ShareableBitmap) override; virtual Messages::WebContentClient::RequestWorkerAgentResponse request_worker_agent(u64 page_id) override; virtual void update_process_statistics(u64 page_id) override; + virtual void request_current_settings(u64 page_id) override; + virtual void restore_default_settings(u64 page_id) override; + virtual void set_new_tab_page_url(u64 page_id, URL::URL new_tab_page_url) override; + virtual void request_available_search_engines(u64 page_id) override; + virtual void set_search_engine(u64 page_id, Optional search_engine) override; Optional view_for_page_id(u64, SourceLocation = SourceLocation::current()); diff --git a/Services/WebContent/PageClient.cpp b/Services/WebContent/PageClient.cpp index eed200cdd51..044c979a9ee 100644 --- a/Services/WebContent/PageClient.cpp +++ b/Services/WebContent/PageClient.cpp @@ -704,6 +704,31 @@ void PageClient::update_process_statistics() client().async_update_process_statistics(m_id); } +void PageClient::request_current_settings() +{ + client().async_request_current_settings(m_id); +} + +void PageClient::restore_default_settings() +{ + client().async_restore_default_settings(m_id); +} + +void PageClient::set_new_tab_page_url(URL::URL const& new_tab_page_url) +{ + client().async_set_new_tab_page_url(m_id, new_tab_page_url); +} + +void PageClient::request_available_search_engines() +{ + client().async_request_available_search_engines(m_id); +} + +void PageClient::set_search_engine(Optional const& search_engine) +{ + client().async_set_search_engine(m_id, search_engine); +} + ErrorOr PageClient::connect_to_webdriver(ByteString const& webdriver_ipc_path) { VERIFY(!m_webdriver); diff --git a/Services/WebContent/PageClient.h b/Services/WebContent/PageClient.h index b04811f4d27..decad41550f 100644 --- a/Services/WebContent/PageClient.h +++ b/Services/WebContent/PageClient.h @@ -175,6 +175,11 @@ private: virtual IPC::File request_worker_agent() override; virtual void page_did_mutate_dom(FlyString const& type, Web::DOM::Node const& target, Web::DOM::NodeList& added_nodes, Web::DOM::NodeList& removed_nodes, GC::Ptr previous_sibling, GC::Ptr next_sibling, Optional const& attribute_name) override; virtual void update_process_statistics() override; + virtual void request_current_settings() override; + virtual void restore_default_settings() override; + virtual void set_new_tab_page_url(URL::URL const&) override; + virtual void request_available_search_engines() override; + virtual void set_search_engine(Optional const&) override; Web::Layout::Viewport* layout_root(); void setup_palette(); diff --git a/Services/WebContent/WebContentClient.ipc b/Services/WebContent/WebContentClient.ipc index adfc6bbf2fb..09963a08c65 100644 --- a/Services/WebContent/WebContentClient.ipc +++ b/Services/WebContent/WebContentClient.ipc @@ -111,4 +111,10 @@ endpoint WebContentClient request_worker_agent(u64 page_id) => (IPC::File socket) // FIXME: Add required attributes to select a SharedWorker Agent update_process_statistics(u64 page_id) =| + + request_current_settings(u64 page_id) =| + restore_default_settings(u64 page_id) =| + set_new_tab_page_url(u64 page_id, URL::URL new_tab_page_url) =| + request_available_search_engines(u64 page_id) =| + set_search_engine(u64 page_id, Optional search_engine) =| } diff --git a/UI/cmake/ResourceFiles.cmake b/UI/cmake/ResourceFiles.cmake index 64a64e67279..2ea86e801d2 100644 --- a/UI/cmake/ResourceFiles.cmake +++ b/UI/cmake/ResourceFiles.cmake @@ -70,6 +70,7 @@ set(ABOUT_PAGES about.html newtab.html processes.html + settings.html ) list(TRANSFORM ABOUT_PAGES PREPEND "${LADYBIRD_SOURCE_DIR}/Base/res/ladybird/about-pages/")