LibWebView: Add settings and actions to control vertical tabs

This adds a setting to enable vertical tabs and to expand/collapse them
if enabled. This setting is hidden for UIs that do not support them
(which is every UI as of this commit).
This commit is contained in:
Timothy Flynn 2026-05-31 12:57:05 -04:00 committed by Andreas Kling
parent 758688a7a1
commit f0ac723539
10 changed files with 147 additions and 1 deletions

View file

@ -380,6 +380,16 @@
</div>
</div>
<h3 class="card-title">Tabs</h3>
<div class="card">
<div class="card-body">
<div class="card-group inline-container">
<label for="enable-vertical-tabs">Use vertical tabs</label>
<input id="enable-vertical-tabs" type="checkbox" switch />
</div>
</div>
</div>
<h3 class="card-title">Browsing Behavior</h3>
<div class="card">
<div class="card-body">
@ -707,6 +717,7 @@
<script src="resource://ladybird/about-pages/settings/permissions.js" type="module"></script>
<script src="resource://ladybird/about-pages/settings/privacy.js" type="module"></script>
<script src="resource://ladybird/about-pages/settings/search.js" type="module"></script>
<script src="resource://ladybird/about-pages/settings/tabs.js" type="module"></script>
<script type="module">
function switchTab(name) {

View file

@ -0,0 +1,33 @@
const enableVerticalTabs = document.querySelector("#enable-vertical-tabs");
let TAB_SETTINGS = {};
const loadFeatures = features => {
// When more tab settings are added, we will need to be more selective here.
const tabSettingsCard = enableVerticalTabs.closest(".card");
tabSettingsCard.previousElementSibling.classList.toggle("hidden", !features?.verticalTabs);
tabSettingsCard.classList.toggle("hidden", !features?.verticalTabs);
};
const loadSettings = settings => {
TAB_SETTINGS = settings.tabs || {};
enableVerticalTabs.checked = !!TAB_SETTINGS.verticalTabsEnabled;
};
function addChangeHandler(input, name) {
input.addEventListener("change", () => {
TAB_SETTINGS[name] = input.checked;
ladybird.sendMessage("setTabSettings", TAB_SETTINGS);
});
}
addChangeHandler(enableVerticalTabs, "verticalTabsEnabled");
document.addEventListener("WebUIMessage", event => {
if (event.detail.name === "loadFeatures") {
loadFeatures(event.detail.data);
} else if (event.detail.name === "loadSettings") {
loadSettings(event.detail.data);
}
});

View file

@ -63,6 +63,11 @@ static double sanitized_display_refresh_rate(double refresh_rate)
}
struct ApplicationSettingsObserver final : public SettingsObserver {
virtual void tab_settings_changed() override
{
Application::the().tab_settings_changed({});
}
virtual void show_bookmarks_bar_changed() override
{
Application::the().show_bookmarks_bar_changed({});
@ -1376,6 +1381,13 @@ void Application::initialize_actions()
m_motion_menu->add_action(Action::create_checkable("No Preference"sv, ActionID::PreferredMotion, set_motion(Web::CSS::PreferredMotion::NoPreference)));
m_motion_menu->items().first().get<NonnullRefPtr<Action>>()->set_checked(true);
m_toggle_vertical_tabs_expanded_action = Action::create("Toggle Vertical Tabs Expanded"sv, ActionID::ToggleVerticalTabsExpanded, [this]() {
auto tab_settings = m_settings.tab_settings();
tab_settings.vertical_tabs_expanded = !tab_settings.vertical_tabs_expanded;
m_settings.set_tab_settings(tab_settings);
});
update_vertical_tabs_action();
m_bookmarks_menu = Menu::create("Bookmarks"sv);
m_bookmarks_menu->add_action(Action::create("Manage Bookmarks"sv, ActionID::ManageBookmarks, [this]() {
open_url_in_new_tab(URL::about_bookmarks(), Web::HTML::ActivateTab::Yes);
@ -1603,6 +1615,20 @@ void Application::apply_view_options(Badge<ViewImplementation>, ViewImplementati
view.debug_request("navigator-compatibility-mode"sv, m_navigator_compatibility_mode);
}
void Application::update_vertical_tabs_action()
{
auto const& settings = m_settings.tab_settings();
m_toggle_vertical_tabs_expanded_action->set_visible(settings.vertical_tabs_enabled);
m_toggle_vertical_tabs_expanded_action->set_engaged(settings.vertical_tabs_expanded);
m_toggle_vertical_tabs_expanded_action->set_tooltip(settings.vertical_tabs_expanded ? "Minimize Tabs"sv : "Expand Tabs"sv);
}
void Application::tab_settings_changed(Badge<ApplicationSettingsObserver>)
{
update_vertical_tabs_action();
update_tabs_display();
}
void Application::update_bookmark_action_for_current_web_view()
{
auto view = active_web_view();

View file

@ -72,6 +72,9 @@ public:
static Requests::RequestClient& request_server_client() { return *the().m_request_server_client; }
static ImageDecoderClient::Client& image_decoder_client() { return *the().m_image_decoder_client; }
virtual bool supports_vertical_tabs() const { return false; }
void tab_settings_changed(Badge<ApplicationSettingsObserver>);
static BookmarkStore& bookmark_store() { return the().m_bookmark_store; }
static HistoryStore& history_store() { return *the().m_history_store; }
void update_bookmark_action_for_current_web_view();
@ -177,6 +180,8 @@ public:
Menu& contrast_menu() { return *m_contrast_menu; }
Menu& motion_menu() { return *m_motion_menu; }
Action& toggle_vertical_tabs_expanded_action() { return *m_toggle_vertical_tabs_expanded_action; }
Menu& bookmarks_menu() { return *m_bookmarks_menu; }
Menu& bookmarks_bar_context_menu() { return *m_bookmarks_bar_context_menu; }
Menu& bookmark_context_menu() { return *m_bookmark_context_menu; }
@ -209,6 +214,8 @@ protected:
virtual Optional<ByteString> ask_user_for_download_path([[maybe_unused]] StringView file) const { return {}; }
virtual void update_tabs_display() const { }
virtual void rebuild_bookmarks_menu() const { }
virtual void update_bookmarks_bar_display([[maybe_unused]] bool show_bookmarks_bar) const { }
virtual void on_recently_closed_entries_changed() const { }
@ -247,6 +254,7 @@ private:
void initialize_actions();
void update_vertical_tabs_action();
void update_bookmarks_bar_action();
struct MenuData {
@ -365,6 +373,8 @@ private:
RefPtr<Menu> m_motion_menu;
Web::CSS::PreferredMotion m_motion { Web::CSS::PreferredMotion::Auto };
RefPtr<Action> m_toggle_vertical_tabs_expanded_action;
RefPtr<Menu> m_bookmarks_menu;
RefPtr<Action> m_toggle_bookmark_action;
RefPtr<Action> m_toggle_bookmark_bar_action;

View file

@ -38,6 +38,8 @@ enum class ActionID {
TakeVisibleScreenshot,
TakeFullScreenshot,
ToggleVerticalTabsExpanded,
ManageBookmarks,
ToggleBookmark,
ToggleBookmarkViaToolbar,

View file

@ -22,6 +22,10 @@ namespace WebView {
static constexpr auto NEW_TAB_PAGE_URL_KEY = "newTabPageURL"sv;
static constexpr auto TAB_SETTINGS_KEY = "tabs"sv;
static constexpr auto VERTICAL_TABS_ENABLED_KEY = "verticalTabsEnabled"sv;
static constexpr auto VERTICAL_TABS_EXPANDED_KEY = "verticalTabsExpanded"sv;
static constexpr auto SHOW_BOOKMARKS_BAR_KEY = "showBookmarksBar"sv;
static constexpr auto DEFAULT_SHOW_BOOKMARKS_BAR = true;
@ -169,6 +173,9 @@ Settings Settings::create(Badge<Application>)
settings.m_new_tab_page_url = parsed_new_tab_page_url.release_value();
}
if (auto tab_settings = settings_json.value().get(TAB_SETTINGS_KEY); tab_settings.has_value())
settings.m_tab_settings = parse_tab_settings(*tab_settings);
if (auto show_bookmarks_bar = settings_json.value().get_bool(SHOW_BOOKMARKS_BAR_KEY); show_bookmarks_bar.has_value())
settings.m_show_bookmarks_bar = *show_bookmarks_bar;
@ -267,6 +274,12 @@ JsonValue Settings::serialize_json() const
{
JsonObject settings;
settings.set(NEW_TAB_PAGE_URL_KEY, m_new_tab_page_url.serialize());
JsonObject tab_settings;
tab_settings.set(VERTICAL_TABS_ENABLED_KEY, m_tab_settings.vertical_tabs_enabled);
tab_settings.set(VERTICAL_TABS_EXPANDED_KEY, m_tab_settings.vertical_tabs_expanded);
settings.set(TAB_SETTINGS_KEY, move(tab_settings));
settings.set(SHOW_BOOKMARKS_BAR_KEY, m_show_bookmarks_bar);
settings.set(DEFAULT_ZOOM_LEVEL_FACTOR_KEY, m_default_zoom_level_factor);
@ -383,6 +396,30 @@ void Settings::set_new_tab_page_url(URL::URL new_tab_page_url)
observer.new_tab_page_url_changed();
}
TabSettings Settings::parse_tab_settings(JsonValue const& settings)
{
if (!settings.is_object())
return {};
TabSettings tab_settings;
if (auto vertical_tabs_enabled = settings.as_object().get_bool(VERTICAL_TABS_ENABLED_KEY); vertical_tabs_enabled.has_value())
tab_settings.vertical_tabs_enabled = *vertical_tabs_enabled;
if (auto vertical_tabs_expanded = settings.as_object().get_bool(VERTICAL_TABS_EXPANDED_KEY); vertical_tabs_expanded.has_value())
tab_settings.vertical_tabs_expanded = *vertical_tabs_expanded;
return tab_settings;
}
void Settings::set_tab_settings(TabSettings tab_settings)
{
m_tab_settings = tab_settings;
persist_settings();
for (auto& observer : m_observers)
observer.tab_settings_changed();
}
void Settings::set_show_bookmarks_bar(bool show_bookmarks_bar)
{
m_show_bookmarks_bar = show_bookmarks_bar;

View file

@ -22,6 +22,11 @@
namespace WebView {
struct TabSettings {
bool vertical_tabs_enabled { false };
bool vertical_tabs_expanded { true };
};
struct BrowsingBehavior {
bool enable_autoscroll { true };
bool enable_primary_paste { true };
@ -69,6 +74,7 @@ public:
virtual ~SettingsObserver();
virtual void new_tab_page_url_changed() { }
virtual void tab_settings_changed() { }
virtual void show_bookmarks_bar_changed() { }
virtual void default_zoom_level_factor_changed() { }
virtual void zoom_per_host_changed(StringView host) { (void)host; }
@ -92,6 +98,10 @@ public:
URL::URL const& new_tab_page_url() const { return m_new_tab_page_url; }
void set_new_tab_page_url(URL::URL);
static TabSettings parse_tab_settings(JsonValue const&);
TabSettings const& tab_settings() const { return m_tab_settings; }
void set_tab_settings(TabSettings);
bool show_bookmarks_bar() const { return m_show_bookmarks_bar; }
void set_show_bookmarks_bar(bool);
@ -155,6 +165,7 @@ private:
ByteString m_settings_path;
URL::URL m_new_tab_page_url;
TabSettings m_tab_settings;
bool m_show_bookmarks_bar { true };
double m_default_zoom_level_factor { 0 };
HashMap<String, double> m_zoom_per_host;

View file

@ -44,6 +44,9 @@ void SettingsUI::register_interfaces()
register_interface("setNewTabPageURL"sv, [this](auto const& data) {
set_new_tab_page_url(data);
});
register_interface("setTabSettings"sv, [this](auto const& data) {
set_tab_settings(data);
});
register_interface("setDefaultZoomLevelFactor"sv, [this](auto const& data) {
set_default_zoom_level_factor(data);
});
@ -109,8 +112,11 @@ void SettingsUI::register_interfaces()
void SettingsUI::load_features()
{
auto& application = Application::the();
JsonObject features;
features.set("primaryPaste"_string, Application::the().supports_clipboard_type(Application::ClipboardType::Selection));
features.set("primaryPaste"_string, application.supports_clipboard_type(Application::ClipboardType::Selection));
features.set("verticalTabs"_string, application.supports_vertical_tabs());
async_send_message("loadFeatures"sv, move(features));
}
@ -150,6 +156,14 @@ void SettingsUI::set_new_tab_page_url(JsonValue const& new_tab_page_url)
WebView::Application::settings().set_new_tab_page_url(parsed_new_tab_page_url.release_value());
}
void SettingsUI::set_tab_settings(JsonValue const& tab_settings)
{
auto parsed_tab_settings = Settings::parse_tab_settings(tab_settings);
WebView::Application::settings().set_tab_settings(parsed_tab_settings);
load_current_settings();
}
void SettingsUI::set_default_zoom_level_factor(JsonValue const& default_zoom_level_factor)
{
auto const maybe_factor = default_zoom_level_factor.get_double_with_precision_loss();

View file

@ -21,6 +21,7 @@ private:
void load_current_settings();
void set_new_tab_page_url(JsonValue const&);
void set_tab_settings(JsonValue const&);
void set_default_zoom_level_factor(JsonValue const&);
void set_languages(JsonValue const&);
void set_browsing_behavior(JsonValue const&);

View file

@ -50,6 +50,7 @@ set(ABOUT_SETTINGS_RESOURCES
permissions.js
privacy.js
search.js
tabs.js
)
list(TRANSFORM ABOUT_SETTINGS_RESOURCES PREPEND "${LADYBIRD_SOURCE_DIR}/Base/res/ladybird/about-pages/settings/")