Browse Source

LibWebView+UI: Introduce a persistent settings object

This adds a WebView::Settings class to own persistent browser settings.
In this first pass, it now owns the new tab page URL and search engine
settings.

For simplicitly, we currently use a JSON format for these settings. They
are stored alongside the cookie database. As of this commit, the saved
JSON will have the form:

    {
        "newTabPageURL": "about:blank",
        "searchEngine": {
            "name": "Google"
        }
    }

(The search engine is an object to allow room for a future patch to
implement custom search engine URLs.)

For Qt, this replaces the management of these particular settings in the
Qt settings UI. We will have an internal browser page to control these
settings instead. In the future, we will want to port all settings to
this new class. We will also want to allow UI-specific settings (such as
whether the hamburger menu is displayed in Qt).
Timothy Flynn 3 weeks ago
parent
commit
e084a86861

+ 3 - 3
Libraries/LibWebView/Application.cpp

@@ -27,6 +27,7 @@ namespace WebView {
 Application* Application::s_the = nullptr;
 
 Application::Application()
+    : m_settings(Settings::create({}))
 {
     VERIFY(!s_the);
     s_the = this;
@@ -57,7 +58,7 @@ Application::~Application()
     s_the = nullptr;
 }
 
-void Application::initialize(Main::Arguments const& arguments, URL::URL new_tab_page_url)
+void Application::initialize(Main::Arguments const& arguments)
 {
 #ifndef AK_OS_WINDOWS
     // Increase the open file limit, as the default limits on Linux cause us to run out of file descriptors with around 15 tabs open.
@@ -152,9 +153,8 @@ void Application::initialize(Main::Arguments const& arguments, URL::URL new_tab_
         disable_site_isolation = true;
 
     m_browser_options = {
-        .urls = sanitize_urls(raw_urls, new_tab_page_url),
+        .urls = sanitize_urls(raw_urls, m_settings.new_tab_page_url()),
         .raw_urls = move(raw_urls),
-        .new_tab_page_url = move(new_tab_page_url),
         .certificates = move(certificates),
         .new_window = new_window ? NewWindow::Yes : NewWindow::No,
         .force_new_process = force_new_process ? ForceNewProcess::Yes : ForceNewProcess::No,

+ 15 - 10
Libraries/LibWebView/Application.h

@@ -22,6 +22,7 @@
 #include <LibWebView/Options.h>
 #include <LibWebView/Process.h>
 #include <LibWebView/ProcessManager.h>
+#include <LibWebView/Settings.h>
 
 namespace WebView {
 
@@ -35,6 +36,8 @@ public:
 
     static Application& the() { return *s_the; }
 
+    static Settings& settings() { return the().m_settings; }
+
     static BrowserOptions const& browser_options() { return the().m_browser_options; }
     static WebContentOptions& web_content_options() { return the().m_web_content_options; }
 
@@ -69,10 +72,10 @@ public:
 
 protected:
     template<DerivedFrom<Application> ApplicationType>
-    static NonnullOwnPtr<ApplicationType> create(Main::Arguments& arguments, URL::URL new_tab_page_url)
+    static NonnullOwnPtr<ApplicationType> create(Main::Arguments& arguments)
     {
         auto app = adopt_own(*new ApplicationType { {}, arguments });
-        app->initialize(arguments, move(new_tab_page_url));
+        app->initialize(arguments);
 
         return app;
     }
@@ -87,7 +90,7 @@ protected:
     virtual Optional<ByteString> ask_user_for_download_folder() const { return {}; }
 
 private:
-    void initialize(Main::Arguments const& arguments, URL::URL new_tab_page_url);
+    void initialize(Main::Arguments const& arguments);
 
     void launch_spare_web_content_process();
     ErrorOr<void> launch_request_server();
@@ -127,6 +130,8 @@ private:
 
     static Application* s_the;
 
+    Settings m_settings;
+
     BrowserOptions m_browser_options;
     WebContentOptions m_web_content_options;
 
@@ -150,11 +155,11 @@ private:
 
 }
 
-#define WEB_VIEW_APPLICATION(ApplicationType)                                                           \
-public:                                                                                                 \
-    static NonnullOwnPtr<ApplicationType> create(Main::Arguments& arguments, URL::URL new_tab_page_url) \
-    {                                                                                                   \
-        return WebView::Application::create<ApplicationType>(arguments, move(new_tab_page_url));        \
-    }                                                                                                   \
-                                                                                                        \
+#define WEB_VIEW_APPLICATION(ApplicationType)                                \
+public:                                                                      \
+    static NonnullOwnPtr<ApplicationType> create(Main::Arguments& arguments) \
+    {                                                                        \
+        return WebView::Application::create<ApplicationType>(arguments);     \
+    }                                                                        \
+                                                                             \
     ApplicationType(Badge<WebView::Application>, Main::Arguments&);

+ 2 - 2
Libraries/LibWebView/BrowserProcess.cpp

@@ -117,13 +117,13 @@ void UIProcessConnectionFromClient::die()
 void UIProcessConnectionFromClient::create_new_tab(Vector<ByteString> urls)
 {
     if (on_new_tab)
-        on_new_tab(sanitize_urls(urls, Application::browser_options().new_tab_page_url));
+        on_new_tab(sanitize_urls(urls, Application::settings().new_tab_page_url()));
 }
 
 void UIProcessConnectionFromClient::create_new_window(Vector<ByteString> urls)
 {
     if (on_new_window)
-        on_new_window(sanitize_urls(urls, Application::browser_options().new_tab_page_url));
+        on_new_window(sanitize_urls(urls, Application::settings().new_tab_page_url()));
 }
 
 }

+ 1 - 0
Libraries/LibWebView/CMakeLists.txt

@@ -16,6 +16,7 @@ set(SOURCES
     ProcessHandle.cpp
     ProcessManager.cpp
     SearchEngine.cpp
+    Settings.cpp
     SiteIsolation.cpp
     SourceHighlighter.cpp
     URL.cpp

+ 1 - 0
Libraries/LibWebView/Forward.h

@@ -15,6 +15,7 @@ class CookieJar;
 class Database;
 class OutOfProcessWebView;
 class ProcessManager;
+class Settings;
 class ViewImplementation;
 class WebContentClient;
 

+ 0 - 1
Libraries/LibWebView/Options.h

@@ -62,7 +62,6 @@ constexpr inline u16 default_devtools_port = 6000;
 struct BrowserOptions {
     Vector<URL::URL> urls;
     Vector<ByteString> raw_urls;
-    URL::URL new_tab_page_url;
     Vector<ByteString> certificates {};
     NewWindow new_window { NewWindow::No };
     ForceNewProcess force_new_process { ForceNewProcess::No };

+ 0 - 8
Libraries/LibWebView/SearchEngine.cpp

@@ -28,14 +28,6 @@ ReadonlySpan<SearchEngine> search_engines()
     return builtin_search_engines;
 }
 
-SearchEngine const& default_search_engine()
-{
-    static auto default_engine = find_search_engine_by_name("Google"sv);
-    VERIFY(default_engine.has_value());
-
-    return *default_engine;
-}
-
 Optional<SearchEngine const&> find_search_engine_by_name(StringView name)
 {
     auto it = AK::find_if(builtin_search_engines.begin(), builtin_search_engines.end(),

+ 0 - 1
Libraries/LibWebView/SearchEngine.h

@@ -17,7 +17,6 @@ struct SearchEngine {
 };
 
 ReadonlySpan<SearchEngine> search_engines();
-SearchEngine const& default_search_engine();
 Optional<SearchEngine const&> find_search_engine_by_name(StringView name);
 Optional<SearchEngine const&> find_search_engine_by_query_url(StringView query_url);
 String format_search_query_for_display(StringView query_url, StringView query);

+ 170 - 0
Libraries/LibWebView/Settings.cpp

@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/ByteString.h>
+#include <AK/JsonObject.h>
+#include <AK/JsonObjectSerializer.h>
+#include <AK/JsonValue.h>
+#include <AK/LexicalPath.h>
+#include <AK/StringBuilder.h>
+#include <LibCore/Directory.h>
+#include <LibCore/File.h>
+#include <LibCore/StandardPaths.h>
+#include <LibURL/Parser.h>
+#include <LibWebView/Application.h>
+#include <LibWebView/Settings.h>
+
+namespace WebView {
+
+static constexpr auto new_tab_page_url_key = "newTabPageURL"sv;
+static constexpr auto search_engine_key = "searchEngine"sv;
+static constexpr auto search_engine_name_key = "name"sv;
+
+static ErrorOr<JsonObject> read_settings_file(StringView settings_path)
+{
+    // FIXME: Move this to a generic "Ladybird data directory" helper.
+    auto settings_directory = ByteString::formatted("{}/Ladybird", Core::StandardPaths::user_data_directory());
+
+    auto settings_file = Core::File::open(settings_path, Core::File::OpenMode::Read);
+    if (settings_file.is_error()) {
+        if (settings_file.error().is_errno() && settings_file.error().code() == ENOENT)
+            return JsonObject {};
+        return settings_file.release_error();
+    }
+
+    auto settings_contents = TRY(settings_file.value()->read_until_eof());
+    auto settings_json = TRY(JsonValue::from_string(settings_contents));
+
+    if (!settings_json.is_object())
+        return Error::from_string_literal("Expected Ladybird settings to be a JSON object");
+    return move(settings_json.as_object());
+}
+
+static ErrorOr<void> write_settings_file(StringView settings_path, StringView contents)
+{
+    auto settings_directory = LexicalPath { settings_path }.parent();
+    TRY(Core::Directory::create(settings_directory, Core::Directory::CreateDirectories::Yes));
+
+    auto settings_file = TRY(Core::File::open(settings_path, Core::File::OpenMode::Write));
+    TRY(settings_file->write_until_depleted(contents));
+
+    return {};
+}
+
+Settings Settings::create(Badge<Application>)
+{
+    auto settings_directory = ByteString::formatted("{}/Ladybird", Core::StandardPaths::user_data_directory());
+    auto settings_path = ByteString::formatted("{}/Settings.json", settings_directory);
+
+    Settings settings { move(settings_path) };
+
+    auto settings_json = read_settings_file(settings.m_settings_path);
+    if (settings_json.is_error()) {
+        warnln("Unable to read Ladybird settings: {}", settings_json.error());
+        return settings;
+    }
+
+    if (auto new_tab_page_url = settings_json.value().get_string(new_tab_page_url_key); new_tab_page_url.has_value()) {
+        if (auto parsed_new_tab_page_url = URL::Parser::basic_parse(*new_tab_page_url); parsed_new_tab_page_url.has_value())
+            settings.m_new_tab_page_url = parsed_new_tab_page_url.release_value();
+    }
+
+    if (auto search_engine = settings_json.value().get_object(search_engine_key); search_engine.has_value()) {
+        if (auto search_engine_name = search_engine->get_string(search_engine_name_key); search_engine_name.has_value())
+            settings.m_search_engine = find_search_engine_by_name(*search_engine_name);
+    }
+
+    return settings;
+}
+
+Settings::Settings(ByteString settings_path)
+    : m_settings_path(move(settings_path))
+    , m_new_tab_page_url(URL::about_newtab())
+{
+}
+
+String Settings::serialize_json() const
+{
+    StringBuilder builder;
+    auto serializer = MUST(JsonObjectSerializer<>::try_create(builder));
+
+    MUST(serializer.add(new_tab_page_url_key, m_new_tab_page_url.serialize()));
+
+    if (m_search_engine.has_value()) {
+        auto search_engine = MUST(serializer.add_object(search_engine_key));
+        MUST(search_engine.add(search_engine_name_key, m_search_engine->name));
+        MUST(search_engine.finish());
+    }
+
+    MUST(serializer.finish());
+    return MUST(builder.to_string());
+}
+
+void Settings::restore_defaults()
+{
+    m_new_tab_page_url = URL::about_newtab();
+    m_search_engine.clear();
+
+    persist_settings();
+
+    for (auto& observer : m_observers)
+        observer.new_tab_page_url_changed();
+}
+
+void Settings::set_new_tab_page_url(URL::URL new_tab_page_url)
+{
+    m_new_tab_page_url = move(new_tab_page_url);
+    persist_settings();
+
+    for (auto& observer : m_observers)
+        observer.new_tab_page_url_changed();
+}
+
+void Settings::set_search_engine(Optional<StringView> search_engine_name)
+{
+    if (search_engine_name.has_value())
+        m_search_engine = find_search_engine_by_name(*search_engine_name);
+    else
+        m_search_engine.clear();
+
+    persist_settings();
+
+    for (auto& observer : m_observers)
+        observer.search_engine_changed();
+}
+
+void Settings::persist_settings()
+{
+    auto settings = serialize_json();
+
+    if (auto result = write_settings_file(m_settings_path, settings); result.is_error())
+        warnln("Unable to persist Ladybird settings: {}", result.error());
+}
+
+void Settings::add_observer(Badge<SettingsObserver>, SettingsObserver& observer)
+{
+    Application::settings().m_observers.append(observer);
+}
+
+void Settings::remove_observer(Badge<SettingsObserver>, SettingsObserver& observer)
+{
+    auto was_removed = Application::settings().m_observers.remove_first_matching([&](auto const& candidate) {
+        return &candidate == &observer;
+    });
+    VERIFY(was_removed);
+}
+
+SettingsObserver::SettingsObserver()
+{
+    Settings::add_observer({}, *this);
+}
+
+SettingsObserver::~SettingsObserver()
+{
+    Settings::remove_observer({}, *this);
+}
+
+}

+ 56 - 0
Libraries/LibWebView/Settings.h

@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/Badge.h>
+#include <AK/Optional.h>
+#include <LibURL/URL.h>
+#include <LibWebView/Forward.h>
+#include <LibWebView/SearchEngine.h>
+
+namespace WebView {
+
+class SettingsObserver {
+public:
+    SettingsObserver();
+    virtual ~SettingsObserver();
+
+    virtual void new_tab_page_url_changed() { }
+    virtual void search_engine_changed() { }
+};
+
+class Settings {
+public:
+    static Settings create(Badge<Application>);
+
+    String serialize_json() const;
+
+    void restore_defaults();
+
+    URL::URL const& new_tab_page_url() const { return m_new_tab_page_url; }
+    void set_new_tab_page_url(URL::URL);
+
+    Optional<SearchEngine> const& search_engine() const { return m_search_engine; }
+    void set_search_engine(Optional<StringView> search_engine_name);
+
+    static void add_observer(Badge<SettingsObserver>, SettingsObserver&);
+    static void remove_observer(Badge<SettingsObserver>, SettingsObserver&);
+
+private:
+    explicit Settings(ByteString settings_path);
+
+    void persist_settings();
+
+    ByteString m_settings_path;
+
+    URL::URL m_new_tab_page_url;
+    Optional<SearchEngine> m_search_engine;
+
+    Vector<SettingsObserver&> m_observers;
+};
+
+}

+ 2 - 5
UI/AppKit/Application/Application.h

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
+ * Copyright (c) 2023-2025, Tim Flynn <trflynn89@ladybird.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
  */
@@ -9,7 +9,6 @@
 #include <AK/Error.h>
 #include <LibIPC/Forward.h>
 #include <LibMain/Main.h>
-#include <LibURL/URL.h>
 #include <LibWebView/Forward.h>
 
 #import <Cocoa/Cocoa.h>
@@ -20,9 +19,7 @@ class WebViewBridge;
 
 @interface Application : NSApplication
 
-- (void)setupWebViewApplication:(Main::Arguments&)arguments
-                  newTabPageURL:(URL::URL)new_tab_page_url;
-
+- (void)setupWebViewApplication:(Main::Arguments&)arguments;
 - (ErrorOr<void>)launchServices;
 
 @end

+ 2 - 3
UI/AppKit/Application/Application.mm

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
+ * Copyright (c) 2023-2025, Tim Flynn <trflynn89@ladybird.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
  */
@@ -58,9 +58,8 @@ ApplicationBridge::ApplicationBridge(Badge<WebView::Application>, Main::Argument
 #pragma mark - Public methods
 
 - (void)setupWebViewApplication:(Main::Arguments&)arguments
-                  newTabPageURL:(URL::URL)new_tab_page_url
 {
-    m_application_bridge = Ladybird::ApplicationBridge::create(arguments, move(new_tab_page_url));
+    m_application_bridge = Ladybird::ApplicationBridge::create(arguments);
 }
 
 - (ErrorOr<void>)launchServices

+ 1 - 3
UI/AppKit/Application/ApplicationDelegate.h

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
+ * Copyright (c) 2023-2025, Tim Flynn <trflynn89@ladybird.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
  */
@@ -13,7 +13,6 @@
 #include <LibWeb/CSS/PreferredContrast.h>
 #include <LibWeb/CSS/PreferredMotion.h>
 #include <LibWeb/HTML/ActivateTab.h>
-#include <LibWebView/Forward.h>
 
 #import <Cocoa/Cocoa.h>
 
@@ -46,6 +45,5 @@
 - (Web::CSS::PreferredColorScheme)preferredColorScheme;
 - (Web::CSS::PreferredContrast)preferredContrast;
 - (Web::CSS::PreferredMotion)preferredMotion;
-- (WebView::SearchEngine const&)searchEngine;
 
 @end

+ 0 - 38
UI/AppKit/Application/ApplicationDelegate.mm

@@ -6,7 +6,6 @@
 
 #include <LibWebView/Application.h>
 #include <LibWebView/CookieJar.h>
-#include <LibWebView/SearchEngine.h>
 
 #import <Application/ApplicationDelegate.h>
 #import <Interface/InfoBar.h>
@@ -27,8 +26,6 @@
     Web::CSS::PreferredContrast m_preferred_contrast;
     Web::CSS::PreferredMotion m_preferred_motion;
     ByteString m_navigator_compatibility_mode;
-
-    WebView::SearchEngine m_search_engine;
 }
 
 @property (nonatomic, strong) NSMutableArray<TabController*>* managed_tabs;
@@ -75,7 +72,6 @@
         m_preferred_contrast = Web::CSS::PreferredContrast::Auto;
         m_preferred_motion = Web::CSS::PreferredMotion::Auto;
         m_navigator_compatibility_mode = "chrome";
-        m_search_engine = WebView::default_search_engine();
 
         // Reduce the tooltip delay, as the default delay feels quite long.
         [[NSUserDefaults standardUserDefaults] setObject:@100 forKey:@"NSInitialToolTipDelay"];
@@ -158,11 +154,6 @@
     return m_preferred_motion;
 }
 
-- (WebView::SearchEngine const&)searchEngine
-{
-    return m_search_engine;
-}
-
 #pragma mark - Private methods
 
 - (void)openAboutVersionPage:(id)sender
@@ -383,17 +374,6 @@
     }
 }
 
-- (void)setSearchEngine:(id)sender
-{
-    auto* item = (NSMenuItem*)sender;
-    auto title = Ladybird::ns_string_to_string([item title]);
-
-    if (auto search_engine = WebView::find_search_engine_by_name(title); search_engine.has_value())
-        m_search_engine = search_engine.release_value();
-    else
-        m_search_engine = WebView::default_search_engine();
-}
-
 - (void)clearHistory:(id)sender
 {
     for (TabController* controller in self.managed_tabs) {
@@ -590,21 +570,6 @@
     auto* menu = [[NSMenuItem alloc] init];
     auto* submenu = [[NSMenu alloc] initWithTitle:@"Settings"];
 
-    auto* search_engine_menu = [[NSMenu alloc] init];
-
-    for (auto const& search_engine : WebView::search_engines()) {
-        [search_engine_menu addItem:[[NSMenuItem alloc] initWithTitle:Ladybird::string_to_ns_string(search_engine.name)
-                                                               action:@selector(setSearchEngine:)
-                                                        keyEquivalent:@""]];
-    }
-
-    auto* search_engine_menu_item = [[NSMenuItem alloc] initWithTitle:@"Search Engine"
-                                                               action:nil
-                                                        keyEquivalent:@""];
-    [search_engine_menu_item setSubmenu:search_engine_menu];
-
-    [submenu addItem:search_engine_menu_item];
-
     [submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Enable Autoplay"
                                                 action:@selector(toggleAutoplay:)
                                          keyEquivalent:@""]];
@@ -835,9 +800,6 @@
         [item setState:(m_preferred_motion == Web::CSS::PreferredMotion::NoPreference) ? NSControlStateValueOn : NSControlStateValueOff];
     } else if ([item action] == @selector(setReducePreferredMotion:)) {
         [item setState:(m_preferred_motion == Web::CSS::PreferredMotion::Reduce) ? NSControlStateValueOn : NSControlStateValueOff];
-    } else if ([item action] == @selector(setSearchEngine:)) {
-        auto title = Ladybird::ns_string_to_string([item title]);
-        [item setState:(m_search_engine.name == title) ? NSControlStateValueOn : NSControlStateValueOff];
     }
 
     return YES;

+ 9 - 7
UI/AppKit/Interface/LadybirdWebView.mm

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
+ * Copyright (c) 2023-2025, Tim Flynn <trflynn89@ladybird.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
  */
@@ -628,15 +628,15 @@ static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_
         }
         auto* search_selected_text_menu_item = [self.page_context_menu itemWithTag:CONTEXT_MENU_SEARCH_SELECTED_TEXT_TAG];
 
-        auto selected_text = self.observer
+        auto const& search_engine = WebView::Application::settings().search_engine();
+
+        auto selected_text = self.observer && search_engine.has_value()
             ? m_web_view_bridge->selected_text_with_whitespace_collapsed()
             : OptionalNone {};
         TemporaryChange change_url { m_context_menu_search_text, move(selected_text) };
 
         if (m_context_menu_search_text.has_value()) {
-            auto* delegate = (ApplicationDelegate*)[NSApp delegate];
-            auto action_text = WebView::format_search_query_for_display([delegate searchEngine].query_url, *m_context_menu_search_text);
-
+            auto action_text = WebView::format_search_query_for_display(search_engine->query_url, *m_context_menu_search_text);
             [search_selected_text_menu_item setTitle:Ladybird::string_to_ns_string(action_text)];
             [search_selected_text_menu_item setHidden:NO];
         } else {
@@ -1136,9 +1136,11 @@ static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_
 
 - (void)searchSelectedText:(id)sender
 {
-    auto* delegate = (ApplicationDelegate*)[NSApp delegate];
+    auto const& search_engine = WebView::Application::settings().search_engine();
+    if (!search_engine.has_value())
+        return;
 
-    auto url_string = MUST(String::formatted([delegate searchEngine].query_url, URL::percent_encode(*m_context_menu_search_text)));
+    auto url_string = MUST(String::formatted(search_engine->query_url, URL::percent_encode(*m_context_menu_search_text)));
     auto url = URL::Parser::basic_parse(url_string);
     VERIFY(url.has_value());
     [self.observer onCreateNewTab:url.release_value() activateTab:Web::HTML::ActivateTab::Yes];

+ 7 - 4
UI/AppKit/Interface/TabController.mm

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
+ * Copyright (c) 2023-2025, Tim Flynn <trflynn89@ladybird.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
  */
@@ -234,7 +234,7 @@ static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIde
 
     self.tab.titlebarAppearsTransparent = NO;
 
-    [delegate createNewTab:WebView::Application::browser_options().new_tab_page_url
+    [delegate createNewTab:WebView::Application::settings().new_tab_page_url()
                    fromTab:[self tab]
                activateTab:Web::HTML::ActivateTab::Yes];
 
@@ -705,9 +705,12 @@ static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIde
     }
 
     auto url_string = Ladybird::ns_string_to_string([[text_view textStorage] string]);
-    auto* delegate = (ApplicationDelegate*)[NSApp delegate];
 
-    if (auto url = WebView::sanitize_url(url_string, [delegate searchEngine].query_url); url.has_value()) {
+    Optional<StringView> search_engine_url;
+    if (auto const& search_engine = WebView::Application::settings().search_engine(); search_engine.has_value())
+        search_engine_url = search_engine->query_url;
+
+    if (auto url = WebView::sanitize_url(url_string, search_engine_url); url.has_value()) {
         [self loadURL:*url];
     }
 

+ 4 - 5
UI/AppKit/main.mm

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023-2024, Tim Flynn <trflynn89@ladybird.org>
+ * Copyright (c) 2023-2025, Tim Flynn <trflynn89@ladybird.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
  */
@@ -12,11 +12,11 @@
 #include <LibWebView/BrowserProcess.h>
 #include <LibWebView/EventLoop/EventLoopImplementationMacOS.h>
 #include <LibWebView/MachPortServer.h>
+#include <LibWebView/Settings.h>
 #include <LibWebView/URL.h>
 #include <LibWebView/Utilities.h>
 #include <LibWebView/ViewImplementation.h>
 #include <LibWebView/WebContentClient.h>
-#include <UI/DefaultSettings.h>
 
 #import <Application/Application.h>
 #import <Application/ApplicationDelegate.h>
@@ -50,9 +50,8 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
     Application* application = [Application sharedApplication];
 
     Core::EventLoopManager::install(*new WebView::EventLoopManagerMacOS);
-    auto url = URL::Parser::basic_parse(Browser::default_new_tab_url);
-    VERIFY(url.has_value());
-    [application setupWebViewApplication:arguments newTabPageURL:url.release_value()];
+
+    [application setupWebViewApplication:arguments];
 
     WebView::platform_init();
 

+ 0 - 21
UI/DefaultSettings.h

@@ -1,21 +0,0 @@
-/*
- * Copyright (c) 2023, Ben Wiederhake <BenWiederhake.GitHub@gmx.de>
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
-
-#pragma once
-
-#include <AK/StringView.h>
-
-namespace Browser {
-
-static constexpr StringView default_homepage_url = "resource://html/misc/welcome.html"sv;
-static constexpr StringView default_new_tab_url = "about:newtab"sv;
-static constexpr StringView default_color_scheme = "auto"sv;
-static constexpr bool default_enable_content_filters = true;
-static constexpr bool default_show_bookmarks_bar = true;
-static constexpr bool default_close_download_widget_on_finish = false;
-static constexpr bool default_allow_autoplay_on_all_websites = false;
-
-}

+ 1 - 1
UI/Headless/main.cpp

@@ -63,7 +63,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
 {
     WebView::platform_init();
 
-    auto app = Ladybird::Application::create(arguments, URL::about_newtab());
+    auto app = Ladybird::Application::create(arguments);
     TRY(app->launch_services());
 
     Core::ResourceImplementation::install(make<Core::ResourceImplementationFile>(MUST(String::from_byte_string(app->resources_folder))));

+ 1 - 3
UI/Qt/BrowserWindow.cpp

@@ -633,9 +633,7 @@ BrowserWindow::BrowserWindow(Vector<URL::URL> const& initial_urls, IsPopupWindow
     QObject::connect(quit_action, &QAction::triggered, this, &QMainWindow::close);
 
     QObject::connect(m_new_tab_action, &QAction::triggered, this, [this] {
-        auto url = ak_url_from_qstring(Settings::the()->new_tab_page());
-        VERIFY(url.has_value());
-        auto& tab = new_tab_from_url(url.release_value(), Web::HTML::ActivateTab::Yes);
+        auto& tab = new_tab_from_url(WebView::Application::settings().new_tab_page_url(), Web::HTML::ActivateTab::Yes);
         tab.set_url_is_hidden(true);
         tab.focus_location_editor();
     });

+ 13 - 9
UI/Qt/LocationEdit.cpp

@@ -5,6 +5,7 @@
  */
 
 #include <LibURL/URL.h>
+#include <LibWebView/Application.h>
 #include <LibWebView/URL.h>
 #include <UI/Qt/LocationEdit.h>
 #include <UI/Qt/Settings.h>
@@ -21,8 +22,6 @@ LocationEdit::LocationEdit(QWidget* parent)
     : QLineEdit(parent)
 {
     update_placeholder();
-    QObject::connect(Settings::the(), &Settings::enable_search_changed, this, &LocationEdit::update_placeholder);
-    QObject::connect(Settings::the(), &Settings::search_engine_changed, this, &LocationEdit::update_placeholder);
 
     m_autocomplete = make<AutoComplete>(this);
     this->setCompleter(m_autocomplete);
@@ -38,8 +37,8 @@ LocationEdit::LocationEdit(QWidget* parent)
         clearFocus();
 
         Optional<StringView> search_engine_url;
-        if (Settings::the()->enable_search())
-            search_engine_url = Settings::the()->search_engine().query_url;
+        if (auto const& search_engine = WebView::Application::settings().search_engine(); search_engine.has_value())
+            search_engine_url = search_engine->query_url;
 
         auto query = ak_string_from_qstring(text());
 
@@ -87,14 +86,19 @@ void LocationEdit::focusOutEvent(QFocusEvent* event)
     }
 }
 
+void LocationEdit::search_engine_changed()
+{
+    update_placeholder();
+}
+
 void LocationEdit::update_placeholder()
 {
-    if (Settings::the()->enable_search())
-        setPlaceholderText(qstring_from_ak_string(
-            MUST(String::formatted("Search with {} or enter web address",
-                Settings::the()->search_engine().name))));
-    else
+    if (auto const& search_engine = WebView::Application::settings().search_engine(); search_engine.has_value()) {
+        auto prompt = MUST(String::formatted("Search with {} or enter web address", search_engine->name));
+        setPlaceholderText(qstring_from_ak_string(prompt));
+    } else {
         setPlaceholderText("Enter web address");
+    }
 }
 
 void LocationEdit::highlight_location()

+ 7 - 1
UI/Qt/LocationEdit.h

@@ -7,14 +7,18 @@
 #pragma once
 
 #include <AK/OwnPtr.h>
+#include <LibWebView/Settings.h>
 #include <UI/Qt/AutoComplete.h>
 
 #include <QLineEdit>
 
 namespace Ladybird {
 
-class LocationEdit final : public QLineEdit {
+class LocationEdit final
+    : public QLineEdit
+    , public WebView::SettingsObserver {
     Q_OBJECT
+
 public:
     explicit LocationEdit(QWidget*);
 
@@ -28,6 +32,8 @@ private:
     virtual void focusInEvent(QFocusEvent* event) override;
     virtual void focusOutEvent(QFocusEvent* event) override;
 
+    virtual void search_engine_changed() override;
+
     void update_placeholder();
     void highlight_location();
     AK::OwnPtr<AutoComplete> m_autocomplete;

+ 0 - 42
UI/Qt/Settings.cpp

@@ -7,27 +7,14 @@
  */
 
 #include <AK/LexicalPath.h>
-#include <UI/DefaultSettings.h>
 #include <UI/Qt/Settings.h>
 #include <UI/Qt/StringUtils.h>
 
 namespace Ladybird {
 
 Settings::Settings()
-    : m_search_engine(WebView::default_search_engine())
 {
     m_qsettings = make<QSettings>(QSettings::IniFormat, QSettings::UserScope, "Ladybird", "Ladybird", this);
-
-    auto default_search_engine = WebView::default_search_engine();
-    auto default_search_engine_name = qstring_from_ak_string(default_search_engine.name);
-
-    auto search_engine_name = m_qsettings->value("search_engine_name", default_search_engine_name).toString();
-    auto search_engine = WebView::find_search_engine_by_name(ak_string_from_qstring(search_engine_name));
-
-    if (search_engine.has_value())
-        m_search_engine = search_engine.release_value();
-    else
-        set_search_engine(move(default_search_engine));
 }
 
 ByteString Settings::directory()
@@ -67,13 +54,6 @@ void Settings::set_is_maximized(bool is_maximized)
     m_qsettings->setValue("is_maximized", is_maximized);
 }
 
-void Settings::set_search_engine(WebView::SearchEngine search_engine)
-{
-    m_qsettings->setValue("search_engine_name", qstring_from_ak_string(search_engine.name));
-    m_search_engine = move(search_engine);
-    emit search_engine_changed(m_search_engine);
-}
-
 QStringList Settings::preferred_languages()
 {
     return m_qsettings->value("preferred_languages").toStringList();
@@ -99,17 +79,6 @@ void Settings::set_autocomplete_engine(EngineProvider const& engine_provider)
     m_qsettings->setValue("autocomplete_engine", engine_provider.url);
 }
 
-QString Settings::new_tab_page()
-{
-    static auto const default_new_tab_url = qstring_from_ak_string(Browser::default_new_tab_url);
-    return m_qsettings->value("new_tab_page", default_new_tab_url).toString();
-}
-
-void Settings::set_new_tab_page(QString const& page)
-{
-    m_qsettings->setValue("new_tab_page", page);
-}
-
 bool Settings::enable_autocomplete()
 {
     return m_qsettings->value("enable_autocomplete", false).toBool();
@@ -120,17 +89,6 @@ void Settings::set_enable_autocomplete(bool enable)
     m_qsettings->setValue("enable_autocomplete", enable);
 }
 
-bool Settings::enable_search()
-{
-    return m_qsettings->value("enable_search", false).toBool();
-}
-
-void Settings::set_enable_search(bool enable)
-{
-    m_qsettings->setValue("enable_search", enable);
-    emit enable_search_changed(enable);
-}
-
 bool Settings::enable_do_not_track()
 {
     return m_qsettings->value("enable_do_not_track", false).toBool();

+ 0 - 14
UI/Qt/Settings.h

@@ -10,7 +10,6 @@
 
 #include <AK/ByteString.h>
 #include <AK/OwnPtr.h>
-#include <LibWebView/SearchEngine.h>
 
 #include <QPoint>
 #include <QSettings>
@@ -42,12 +41,6 @@ public:
     bool is_maximized();
     void set_is_maximized(bool is_maximized);
 
-    QString new_tab_page();
-    void set_new_tab_page(QString const& page);
-
-    WebView::SearchEngine search_engine() const { return m_search_engine; }
-    void set_search_engine(WebView::SearchEngine engine);
-
     QStringList preferred_languages();
     void set_preferred_languages(QStringList const& languages);
 
@@ -55,16 +48,12 @@ public:
         QString name;
         QString url;
     };
-
     EngineProvider autocomplete_engine();
     void set_autocomplete_engine(EngineProvider const& engine);
 
     bool enable_autocomplete();
     void set_enable_autocomplete(bool enable);
 
-    bool enable_search();
-    void set_enable_search(bool enable);
-
     bool enable_do_not_track();
     void set_enable_do_not_track(bool enable);
 
@@ -76,8 +65,6 @@ public:
 
 signals:
     void show_menubar_changed(bool show_menubar);
-    void enable_search_changed(bool enable);
-    void search_engine_changed(WebView::SearchEngine engine);
     void preferred_languages_changed(QStringList const& languages);
     void enable_do_not_track_changed(bool enable);
     void enable_autoplay_changed(bool enable);
@@ -87,7 +74,6 @@ protected:
 
 private:
     OwnPtr<QSettings> m_qsettings;
-    WebView::SearchEngine m_search_engine;
 };
 
 }

+ 2 - 52
UI/Qt/SettingsDialog.cpp

@@ -8,7 +8,6 @@
 #include <LibURL/Parser.h>
 #include <LibURL/URL.h>
 #include <LibWebView/Application.h>
-#include <LibWebView/SearchEngine.h>
 #include <UI/Qt/Settings.h>
 #include <UI/Qt/SettingsDialog.h>
 #include <UI/Qt/StringUtils.h>
@@ -24,13 +23,6 @@ SettingsDialog::SettingsDialog(QMainWindow* window)
 {
     m_layout = new QFormLayout(this);
 
-    m_enable_search = new QCheckBox(this);
-    m_enable_search->setChecked(Settings::the()->enable_search());
-
-    m_search_engine_dropdown = new QPushButton(this);
-    m_search_engine_dropdown->setText(qstring_from_ak_string(Settings::the()->search_engine().name));
-    m_search_engine_dropdown->setMaximumWidth(200);
-
     m_preferred_languages = new QLineEdit(this);
     m_preferred_languages->setText(Settings::the()->preferred_languages().join(","));
     QObject::connect(m_preferred_languages, &QLineEdit::editingFinished, this, [this] {
@@ -47,21 +39,6 @@ SettingsDialog::SettingsDialog(QMainWindow* window)
     m_autocomplete_engine_dropdown->setText(Settings::the()->autocomplete_engine().name);
     m_autocomplete_engine_dropdown->setMaximumWidth(200);
 
-    m_new_tab_page = new QLineEdit(this);
-    m_new_tab_page->setText(Settings::the()->new_tab_page());
-    QObject::connect(m_new_tab_page, &QLineEdit::textChanged, this, [this] {
-        auto url_string = ak_string_from_qstring(m_new_tab_page->text());
-        m_new_tab_page->setStyleSheet(URL::Parser::basic_parse(url_string).has_value() ? "" : "border: 1px solid red;");
-    });
-    QObject::connect(m_new_tab_page, &QLineEdit::editingFinished, this, [this] {
-        auto url_string = ak_string_from_qstring(m_new_tab_page->text());
-        if (URL::Parser::basic_parse(url_string).has_value())
-            Settings::the()->set_new_tab_page(m_new_tab_page->text());
-    });
-    QObject::connect(m_new_tab_page, &QLineEdit::returnPressed, this, [this] {
-        close();
-    });
-
     m_enable_do_not_track = new QCheckBox(this);
     m_enable_do_not_track->setChecked(Settings::the()->enable_do_not_track());
 #if (QT_VERSION > QT_VERSION_CHECK(6, 7, 0))
@@ -87,11 +64,8 @@ SettingsDialog::SettingsDialog(QMainWindow* window)
         Settings::the()->set_enable_autoplay(state == Qt::Checked);
     });
 
-    setup_search_engines();
+    setup_autocomplete_engine();
 
-    m_layout->addRow(new QLabel("Page on New Tab", this), m_new_tab_page);
-    m_layout->addRow(new QLabel("Enable Search", this), m_enable_search);
-    m_layout->addRow(new QLabel("Search Engine", this), m_search_engine_dropdown);
     m_layout->addRow(new QLabel("Preferred Language(s)", this), m_preferred_languages);
     m_layout->addRow(new QLabel("Enable Autocomplete", this), m_enable_autocomplete);
     m_layout->addRow(new QLabel("Autocomplete Engine", this), m_autocomplete_engine_dropdown);
@@ -103,7 +77,7 @@ SettingsDialog::SettingsDialog(QMainWindow* window)
     resize(600, 250);
 }
 
-void SettingsDialog::setup_search_engines()
+void SettingsDialog::setup_autocomplete_engine()
 {
     // FIXME: These should be centralized in LibWebView.
     Vector<Settings::EngineProvider> autocomplete_engines = {
@@ -112,21 +86,6 @@ void SettingsDialog::setup_search_engines()
         { "Yahoo", "https://search.yahoo.com/sugg/gossip/gossip-us-ura/?output=sd1&command={}" },
     };
 
-    QMenu* search_engine_menu = new QMenu(this);
-    for (auto const& search_engine : WebView::search_engines()) {
-        auto search_engine_name = qstring_from_ak_string(search_engine.name);
-        QAction* action = new QAction(search_engine_name, this);
-
-        connect(action, &QAction::triggered, this, [&, search_engine_name = std::move(search_engine_name)]() {
-            Settings::the()->set_search_engine(search_engine);
-            m_search_engine_dropdown->setText(search_engine_name);
-        });
-
-        search_engine_menu->addAction(action);
-    }
-    m_search_engine_dropdown->setMenu(search_engine_menu);
-    m_search_engine_dropdown->setEnabled(Settings::the()->enable_search());
-
     QMenu* autocomplete_engine_menu = new QMenu(this);
     for (auto& autocomplete_engine : autocomplete_engines) {
         QAction* action = new QAction(autocomplete_engine.name, this);
@@ -139,15 +98,6 @@ void SettingsDialog::setup_search_engines()
     m_autocomplete_engine_dropdown->setMenu(autocomplete_engine_menu);
     m_autocomplete_engine_dropdown->setEnabled(Settings::the()->enable_autocomplete());
 
-#if (QT_VERSION > QT_VERSION_CHECK(6, 7, 0))
-    connect(m_enable_search, &QCheckBox::checkStateChanged, this, [&](int state) {
-#else
-    connect(m_enable_search, &QCheckBox::stateChanged, this, [&](int state) {
-#endif
-        Settings::the()->set_enable_search(state == Qt::Checked);
-        m_search_engine_dropdown->setEnabled(state == Qt::Checked);
-    });
-
 #if (QT_VERSION > QT_VERSION_CHECK(6, 7, 0))
     connect(m_enable_autocomplete, &QCheckBox::checkStateChanged, this, [&](int state) {
 #else

+ 3 - 6
UI/Qt/SettingsDialog.h

@@ -5,6 +5,8 @@
  * SPDX-License-Identifier: BSD-2-Clause
  */
 
+#pragma once
+
 #include <QCheckBox>
 #include <QDialog>
 #include <QFormLayout>
@@ -12,8 +14,6 @@
 #include <QMainWindow>
 #include <QPushButton>
 
-#pragma once
-
 namespace Ladybird {
 
 class SettingsDialog : public QDialog {
@@ -23,13 +23,10 @@ public:
     explicit SettingsDialog(QMainWindow* window);
 
 private:
-    void setup_search_engines();
+    void setup_autocomplete_engine();
 
     QFormLayout* m_layout;
     QMainWindow* m_window { nullptr };
-    QLineEdit* m_new_tab_page { nullptr };
-    QCheckBox* m_enable_search { nullptr };
-    QPushButton* m_search_engine_dropdown { nullptr };
     QLineEdit* m_preferred_languages { nullptr };
     QCheckBox* m_enable_autocomplete { nullptr };
     QPushButton* m_autocomplete_engine_dropdown { nullptr };

+ 12 - 4
UI/Qt/Tab.cpp

@@ -10,6 +10,7 @@
 #include <LibGfx/ImageFormats/BMPWriter.h>
 #include <LibURL/Parser.h>
 #include <LibWeb/HTML/SelectedFile.h>
+#include <LibWebView/Application.h>
 #include <LibWebView/SearchEngine.h>
 #include <LibWebView/SourceHighlighter.h>
 #include <LibWebView/URL.h>
@@ -454,9 +455,14 @@ Tab::Tab(BrowserWindow* window, RefPtr<WebView::WebContentClient> parent_client,
     auto* search_selected_text_action = new QAction("&Search for <query>", this);
     search_selected_text_action->setIcon(load_icon_from_uri("resource://icons/16x16/find.png"sv));
     QObject::connect(search_selected_text_action, &QAction::triggered, this, [this]() {
-        auto url_string = MUST(String::formatted(Settings::the()->search_engine().query_url, URL::percent_encode(*m_page_context_menu_search_text)));
+        auto const& search_engine = WebView::Application::settings().search_engine();
+        if (!search_engine.has_value())
+            return;
+
+        auto url_string = MUST(String::formatted(search_engine->query_url, URL::percent_encode(*m_page_context_menu_search_text)));
         auto url = URL::Parser::basic_parse(url_string);
         VERIFY(url.has_value());
+
         m_window->new_tab_from_url(url.release_value(), Web::HTML::ActivateTab::Yes);
     });
 
@@ -517,13 +523,15 @@ Tab::Tab(BrowserWindow* window, RefPtr<WebView::WebContentClient> parent_client,
     m_page_context_menu->addAction(&m_window->view_source_action());
 
     view().on_context_menu_request = [this, search_selected_text_action](Gfx::IntPoint content_position) {
-        auto selected_text = Settings::the()->enable_search()
+        auto const& search_engine = WebView::Application::settings().search_engine();
+
+        auto selected_text = search_engine.has_value()
             ? view().selected_text_with_whitespace_collapsed()
             : OptionalNone {};
-        TemporaryChange change_url { m_page_context_menu_search_text, std::move(selected_text) };
+        TemporaryChange change_url { m_page_context_menu_search_text, AK::move(selected_text) };
 
         if (m_page_context_menu_search_text.has_value()) {
-            auto action_text = WebView::format_search_query_for_display(Settings::the()->search_engine().query_url, *m_page_context_menu_search_text);
+            auto action_text = WebView::format_search_query_for_display(search_engine->query_url, *m_page_context_menu_search_text);
             search_selected_text_action->setText(qstring_from_ak_string(action_text));
             search_selected_text_action->setVisible(true);
         } else {

+ 1 - 3
UI/Qt/main.cpp

@@ -73,9 +73,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
 
     Core::EventLoopManager::install(*new WebView::EventLoopManagerQt);
 
-    auto url = ak_url_from_qstring(Ladybird::Settings::the()->new_tab_page());
-    VERIFY(url.has_value());
-    auto app = Ladybird::Application::create(arguments, url.release_value());
+    auto app = Ladybird::Application::create(arguments);
 
     static_cast<WebView::EventLoopImplementationQt&>(Core::EventLoop::current().impl()).set_main_loop();
     TRY(handle_attached_debugger());