diff --git a/Base/res/ladybird/about-pages/settings.html b/Base/res/ladybird/about-pages/settings.html index 5e981d14a4e..fb871caf27a 100644 --- a/Base/res/ladybird/about-pages/settings.html +++ b/Base/res/ladybird/about-pages/settings.html @@ -80,6 +80,18 @@ margin-bottom: 0; } + .permission-container { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + } + + .forcibly-enabled { + font-size: 14px; + opacity: 0.6; + } + label { display: block; margin-bottom: 8px; @@ -87,6 +99,7 @@ font-size: 14px; } + input[type="text"], input[type="url"], select { background-color: var(--input-background-color); @@ -95,14 +108,145 @@ border: 1px solid var(--border-color); } + input[type="text"].success, input[type="url"].success { border: 1px solid green; } + input[type="text"].error, input[type="url"].error { border: 1px solid red; } + dialog { + background-color: var(--card-background-color); + box-shadow: 0 0 20px rgba(0, 0, 0, 0.2); + + width: 90%; + max-width: 500px; + + margin: auto; + padding: 0; + + border: none; + border-radius: 8px; + outline: none; + } + + dialog::backdrop { + /* FIXME: This doesn't work in Ladybird. */ + background-color: rgba(0, 0, 0, 0.5); + } + + dialog .dialog-header { + background-color: var(--card-header-background-color); + + display: flex; + justify-content: space-between; + align-items: center; + + padding: 15px 20px; + } + + dialog .dialog-body { + height: 450px; + + border-top: 1px solid var(--border-color); + border-bottom: 1px solid var(--border-color); + + display: flex; + flex-direction: column; + + padding: 15px 20px; + } + + dialog .dialog-body label { + font-size: 16px; + } + + dialog .dialog-body hr { + height: 1px; + background-color: var(--border-color); + border: none; + + margin: 8px 4px 10px 4px; + } + + dialog .dialog-footer { + display: flex; + justify-content: flex-end; + + padding: 15px 20px; + gap: 10px; + } + + dialog .dialog-title { + font-size: 18px; + font-weight: 500; + } + + dialog .dialog-close { + background: none; + border: none; + outline: none; + + padding: 0; + + font-size: 22px; + opacity: 0.8; + + cursor: pointer; + } + + dialog .dialog-close:hover { + opacity: 1; + } + + dialog .dialog-site-list { + outline: 1px solid var(--border-color); + border-radius: 4px; + + margin: 10px 0; + + font-size: 14px; + + overflow-y: auto; + flex-grow: 1; + } + + dialog .dialog-site-item { + padding: 10px; + + display: flex; + justify-content: space-between; + align-items: center; + } + + dialog .dialog-site-item:hover { + background-color: var(--card-header-background-color); + } + + dialog .dialog-site-item-placeholder { + padding: 10px; + opacity: 0.6; + } + + dialog .dialog-site-item .filter { + flex-grow: 1; + } + + dialog .dialog-add-site { + display: flex; + justify-content: flex-end; + + margin-top: auto; + gap: 10px; + } + + dialog .dialog-add-site input { + flex-grow: 1; + } + .button-container { display: flex; justify-content: flex-end; @@ -146,7 +290,7 @@ - + + + diff --git a/Base/res/ladybird/ladybird.css b/Base/res/ladybird/ladybird.css index 8beb7f7e708..d8a4a1b96fd 100644 --- a/Base/res/ladybird/ladybird.css +++ b/Base/res/ladybird/ladybird.css @@ -43,6 +43,9 @@ :root { --text-color: #2f3640; --toggle-background-color: #cccccc; + --secondary-button-color: #dddddd; + --secondary-button-hover: #e1e1e1; + --secondary-button-active: #e9e9e9; } } @@ -50,26 +53,35 @@ :root { --text-color: #e0e0e0; --toggle-background-color: #555555; + --secondary-button-color: #555555; + --secondary-button-hover: #505050; + --secondary-button-active: #444444; } } * { color-scheme: light dark; font-family: sans-serif; + + margin: 0; + padding: 0; } body { color: var(--text-color); } +.hidden { + display: none; + opacity: 0; +} + /** * Generic form controls. */ -button.primary-button { - color: white; - background-color: var(--violet-100); - +button.primary-button, +button.secondary-button { font-size: 14px; border: none; @@ -79,14 +91,39 @@ button.primary-button { cursor: pointer; } +button.primary-button:hover, +button.secondary-button:hover { + filter: unset; +} + +button.primary-button:active, +button.secondary-button:active { + outline: none; +} + +button.primary-button { + color: white; + background-color: var(--violet-100); +} + button.primary-button:hover { background-color: var(--violet-500); - filter: unset; } button.primary-button:active { background-color: var(--violet-900); - outline: none; +} + +button.secondary-button { + background-color: var(--secondary-button-color); +} + +button.secondary-button:hover { + background-color: var(--secondary-button-hover); +} + +button.secondary-button:active { + background-color: var(--secondary-button-active); } input[type="search"], diff --git a/Libraries/LibWebView/Settings.cpp b/Libraries/LibWebView/Settings.cpp index ce43406f2a7..579a56be45b 100644 --- a/Libraries/LibWebView/Settings.cpp +++ b/Libraries/LibWebView/Settings.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -19,9 +20,15 @@ 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 constexpr auto site_setting_enabled_globally_key = "enabledGlobally"sv; +static constexpr auto site_setting_site_filters_key = "siteFilters"sv; + +static constexpr auto autoplay_key = "autoplay"sv; + static ErrorOr read_settings_file(StringView settings_path) { auto settings_file = Core::File::open(settings_path, Core::File::OpenMode::Read); @@ -74,6 +81,26 @@ Settings Settings::create(Badge) settings.m_search_engine = find_search_engine_by_name(*search_engine_name); } + auto load_site_setting = [&](SiteSetting& site_setting, StringView key) { + auto saved_settings = settings_json.value().get_object(key); + if (!saved_settings.has_value()) + return; + + if (auto enabled_globally = saved_settings->get_bool(site_setting_enabled_globally_key); enabled_globally.has_value()) + site_setting.enabled_globally = *enabled_globally; + + if (auto site_filters = saved_settings->get_array(site_setting_site_filters_key); site_filters.has_value()) { + site_setting.site_filters.clear(); + + site_filters->for_each([&](auto const& site_filter) { + if (site_filter.is_string()) + site_setting.site_filters.set(site_filter.as_string()); + }); + } + }; + + load_site_setting(settings.m_autoplay, autoplay_key); + return settings; } @@ -95,6 +122,22 @@ JsonValue Settings::serialize_json() const settings.set(search_engine_key, move(search_engine)); } + auto save_site_setting = [&](SiteSetting const& site_setting, StringView key) { + JsonArray site_filters; + site_filters.ensure_capacity(site_setting.site_filters.size()); + + for (auto const& site_filter : site_setting.site_filters) + site_filters.must_append(site_filter); + + JsonObject setting; + setting.set("enabledGlobally"sv, site_setting.enabled_globally); + setting.set("siteFilters"sv, move(site_filters)); + + settings.set(key, move(setting)); + }; + + save_site_setting(m_autoplay, autoplay_key); + return settings; } @@ -102,6 +145,7 @@ void Settings::restore_defaults() { m_new_tab_page_url = URL::about_newtab(); m_search_engine.clear(); + m_autoplay = SiteSetting {}; persist_settings(); @@ -131,6 +175,46 @@ void Settings::set_search_engine(Optional search_engine_name) observer.search_engine_changed(); } +void Settings::set_autoplay_enabled_globally(bool enabled_globally) +{ + m_autoplay.enabled_globally = enabled_globally; + persist_settings(); + + for (auto& observer : m_observers) + observer.autoplay_settings_changed(); +} + +void Settings::add_autoplay_site_filter(String const& site_filter) +{ + auto trimmed_site_filter = MUST(site_filter.trim_whitespace()); + if (trimmed_site_filter.is_empty()) + return; + + m_autoplay.site_filters.set(move(trimmed_site_filter)); + persist_settings(); + + for (auto& observer : m_observers) + observer.autoplay_settings_changed(); +} + +void Settings::remove_autoplay_site_filter(String const& site_filter) +{ + m_autoplay.site_filters.remove(site_filter); + persist_settings(); + + for (auto& observer : m_observers) + observer.autoplay_settings_changed(); +} + +void Settings::remove_all_autoplay_site_filters() +{ + m_autoplay.site_filters.clear(); + persist_settings(); + + for (auto& observer : m_observers) + observer.autoplay_settings_changed(); +} + void Settings::persist_settings() { auto settings = serialize_json(); @@ -162,4 +246,9 @@ SettingsObserver::~SettingsObserver() Settings::remove_observer({}, *this); } +SiteSetting::SiteSetting() +{ + site_filters.set("file://"_string); +} + } diff --git a/Libraries/LibWebView/Settings.h b/Libraries/LibWebView/Settings.h index c05b1dcb689..79e1ec20457 100644 --- a/Libraries/LibWebView/Settings.h +++ b/Libraries/LibWebView/Settings.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include #include #include @@ -15,6 +16,13 @@ namespace WebView { +struct SiteSetting { + SiteSetting(); + + bool enabled_globally { false }; + OrderedHashTable site_filters; +}; + class SettingsObserver { public: SettingsObserver(); @@ -22,6 +30,7 @@ public: virtual void new_tab_page_url_changed() { } virtual void search_engine_changed() { } + virtual void autoplay_settings_changed() { } }; class Settings { @@ -38,6 +47,12 @@ public: Optional const& search_engine() const { return m_search_engine; } void set_search_engine(Optional search_engine_name); + SiteSetting const& autoplay_settings() const { return m_autoplay; } + void set_autoplay_enabled_globally(bool); + void add_autoplay_site_filter(String const&); + void remove_autoplay_site_filter(String const&); + void remove_all_autoplay_site_filters(); + static void add_observer(Badge, SettingsObserver&); static void remove_observer(Badge, SettingsObserver&); @@ -50,6 +65,7 @@ private: URL::URL m_new_tab_page_url; Optional m_search_engine; + SiteSetting m_autoplay; Vector m_observers; }; diff --git a/Libraries/LibWebView/WebUI/SettingsUI.cpp b/Libraries/LibWebView/WebUI/SettingsUI.cpp index c49cf717b08..828603de458 100644 --- a/Libraries/LibWebView/WebUI/SettingsUI.cpp +++ b/Libraries/LibWebView/WebUI/SettingsUI.cpp @@ -29,6 +29,21 @@ void SettingsUI::register_interfaces() register_interface("setSearchEngine"sv, [this](auto const& data) { set_search_engine(data); }); + register_interface("loadForciblyEnabledSiteSettings"sv, [this](auto const&) { + load_forcibly_enabled_site_settings(); + }); + register_interface("setSiteSettingEnabledGlobally"sv, [this](auto const& data) { + set_site_setting_enabled_globally(data); + }); + register_interface("addSiteSettingFilter"sv, [this](auto const& data) { + add_site_setting_filter(data); + }); + register_interface("removeSiteSettingFilter"sv, [this](auto const& data) { + remove_site_setting_filter(data); + }); + register_interface("removeAllSiteSettingFilters"sv, [this](auto const& data) { + remove_all_site_setting_filters(data); + }); } void SettingsUI::load_current_settings() @@ -72,4 +87,113 @@ void SettingsUI::set_search_engine(JsonValue const& search_engine) WebView::Application::settings().set_search_engine(search_engine.as_string()); } +enum class SiteSettingType { + Autoplay, +}; + +static constexpr StringView site_setting_type_to_string(SiteSettingType setting) +{ + switch (setting) { + case SiteSettingType::Autoplay: + return "autoplay"sv; + } + VERIFY_NOT_REACHED(); +} + +static Optional site_setting_type(JsonValue const& settings) +{ + if (!settings.is_object()) + return {}; + + auto setting_type = settings.as_object().get_string("setting"sv); + if (!setting_type.has_value()) + return {}; + + if (*setting_type == "autoplay"sv) + return SiteSettingType::Autoplay; + return {}; +} + +void SettingsUI::load_forcibly_enabled_site_settings() +{ + JsonArray site_settings; + + if (Application::web_content_options().enable_autoplay == EnableAutoplay::Yes) + site_settings.must_append(site_setting_type_to_string(SiteSettingType::Autoplay)); + + async_send_message("forciblyEnableSiteSettings"sv, move(site_settings)); +} + +void SettingsUI::set_site_setting_enabled_globally(JsonValue const& site_setting) +{ + auto setting = site_setting_type(site_setting); + if (!setting.has_value()) + return; + + auto enabled = site_setting.as_object().get_bool("enabled"sv); + if (!enabled.has_value()) + return; + + switch (*setting) { + case SiteSettingType::Autoplay: + WebView::Application::settings().set_autoplay_enabled_globally(*enabled); + break; + } + + load_current_settings(); +} + +void SettingsUI::add_site_setting_filter(JsonValue const& site_setting) +{ + auto setting = site_setting_type(site_setting); + if (!setting.has_value()) + return; + + auto filter = site_setting.as_object().get_string("filter"sv); + if (!filter.has_value()) + return; + + switch (*setting) { + case SiteSettingType::Autoplay: + WebView::Application::settings().add_autoplay_site_filter(*filter); + break; + } + + load_current_settings(); +} + +void SettingsUI::remove_site_setting_filter(JsonValue const& site_setting) +{ + auto setting = site_setting_type(site_setting); + if (!setting.has_value()) + return; + + auto filter = site_setting.as_object().get_string("filter"sv); + if (!filter.has_value()) + return; + + switch (*setting) { + case SiteSettingType::Autoplay: + WebView::Application::settings().remove_autoplay_site_filter(*filter); + break; + } + + load_current_settings(); +} + +void SettingsUI::remove_all_site_setting_filters(JsonValue const& site_setting) +{ + auto setting = site_setting_type(site_setting); + if (!setting.has_value()) + return; + + switch (*setting) { + case SiteSettingType::Autoplay: + WebView::Application::settings().remove_all_autoplay_site_filters(); + break; + } + + load_current_settings(); +} + } diff --git a/Libraries/LibWebView/WebUI/SettingsUI.h b/Libraries/LibWebView/WebUI/SettingsUI.h index 262d600ce8a..d0253cc7187 100644 --- a/Libraries/LibWebView/WebUI/SettingsUI.h +++ b/Libraries/LibWebView/WebUI/SettingsUI.h @@ -21,6 +21,11 @@ private: void set_new_tab_page_url(JsonValue const&); void load_available_search_engines(); void set_search_engine(JsonValue const&); + void load_forcibly_enabled_site_settings(); + void set_site_setting_enabled_globally(JsonValue const&); + void add_site_setting_filter(JsonValue const&); + void remove_site_setting_filter(JsonValue const&); + void remove_all_site_setting_filters(JsonValue const&); }; }