From 88961df8067e9d4f118972db2e2da952bb60167d Mon Sep 17 00:00:00 2001 From: dusan-nikcevic <168220721+dusan-nikcevic@users.noreply.github.com> Date: Wed, 25 Feb 2026 23:53:21 +0100 Subject: [PATCH 1/6] docs: add vertical tabs hover overlay implementation plan --- plans/vertical-tabs-hover-overlay-plan.md | 110 ++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 plans/vertical-tabs-hover-overlay-plan.md diff --git a/plans/vertical-tabs-hover-overlay-plan.md b/plans/vertical-tabs-hover-overlay-plan.md new file mode 100644 index 000000000..d1a0f02c5 --- /dev/null +++ b/plans/vertical-tabs-hover-overlay-plan.md @@ -0,0 +1,110 @@ +# Vertical Tabs Hover Overlay Plan + +Date: 2026-02-25 +Target: `imputnet/helium` (fork: `dusan-nikcevic/helium`) + +## Goal + +Make collapsed vertical tabs expand on hover **as an overlay above page content** (no content resize/reflow during hover animation), while keeping manual expand/collapse behavior intact. + +## Current Problem + +- PR #964 expands hover by reusing `SetCollapsed(false/true)`. +- That path drives the normal layout flow, which updates reserved tab strip width and causes repeated content resize during animation. +- Top controls can shift under cursor between collapsed and expanded states. + +## Proposed Behavior + +- Collapsed state remains the persisted state. +- Hover adds a transient "hover-expanded" presentation state. +- During hover-expanded mode: + - Content area reservation stays at collapsed width. + - Visual tab strip width animates to uncollapsed width. + - Expanded portion draws over web contents. + - Top control button positions remain stable. + +## Implementation Plan + +1. State model split (persistent vs transient) +- Add transient hover state to `VerticalTabStripStateController`: + - `is_hovered_` + - `held_by_hover_` + - delayed expand timer (existing 400ms can remain, tune after testing) +- Keep persisted prefs unchanged: + - `helium.browser.vertical_collapsed` + - `helium.browser.vertical_uncollapsed_width` +- Add query API for layout code, e.g. `IsHoverExpanded()` or equivalent. + +2. Separate reserved width from drawn width +- In `browser_view_tabbed_layout_impl` (via Helium patch), split vertical strip width logic: + - `reserved_width`: width used for `params.InsetHorizontal(...)` (collapsed width during hover-expanded mode) + - `drawn_width`: actual bounds width used for vertical strip painting/animation +- When hover-expanded and persisted-collapsed: + - `reserved_width = kCollapsedWidth` + - `drawn_width = animated_width (collapsed -> uncollapsed)` +- Keep right-aligned behavior symmetric. + +3. Overlay stacking and hit testing +- Ensure vertical tab strip remains above contents in overlap region. +- Verify window caption hit testing still works in overlay area. +- Verify drag/drop and resize area interactions still target vertical tab strip, not web contents. + +4. Stabilize top controls +- Keep top container controls in a stable horizontal arrangement during hover-expanded mode. +- Do not switch control orientation solely because hover-expanded is active. +- Maintain existing manual expanded layout behavior. + +5. Shortcut support +- Add command ID for vertical strip collapse toggle (separate from layout mode toggle). +- Wire to `VerticalTabStripStateController::SetCollapsed(...)`. +- Add accelerators in `accelerator_table.cc` (+ mac mappings) with conflict check. +- Suggested default: only active when layout is vertical. + +6. Settings surface (optional but recommended) +- Add one setting: "Expand collapsed vertical tabs on hover". +- Default on/off to be decided before merge (recommend default `on` if behavior is stable). +- Allow disabling for users sensitive to motion. + +## Patch Stack Strategy (Helium repository) + +- Add a focused patch instead of further bloating existing `layout/vertical.patch`: + - `patches/helium/ui/layout/vertical-hover-overlay.patch` +- Add shortcut patch: + - `patches/helium/core/vertical-tabs-shortcut.patch` +- Add settings patch if enabled: + - `patches/helium/ui/layout/vertical-hover-settings.patch` +- Update `patches/series` ordering so overlay patch applies after `helium/ui/layout/vertical.patch`. + +## Validation Plan + +1. Functional checks +- Hover collapsed strip -> expands without resizing page contents. +- Mouse leave -> collapses back. +- Manual expand/collapse button still behaves as before. +- Right-aligned mode behaves identically. + +2. Interaction checks +- Tab drag and drop across groups while hover-expanded. +- Close button, mute indicator, pinned tabs, tab groups. +- Resize handle works for persistent uncollapsed width. + +3. Platform checks +- Linux (required), macOS and Windows smoke tests. +- Fullscreen, maximized, restored window states. + +4. Performance checks +- No repeated web contents relayout during hover animation. +- No obvious frame drops during rapid enter/leave. + +## Risks + +- Overlay z-order regressions with `contents_container`. +- Caption/button exclusion math on right-aligned + fullscreen combinations. +- Shortcut conflicts with existing accelerators. +- More merge conflicts if done inside current monolithic `vertical.patch`. + +## Estimated Effort + +- Overlay behavior + stability: 3-5 engineering days. +- Shortcut + settings + polish + QA: 2-3 engineering days. +- Total realistic range: 5-8 engineering days. From 2b0cd58ce707207ca2f7334c61cf5f61c547964d Mon Sep 17 00:00:00 2001 From: dusan-nikcevic <168220721+dusan-nikcevic@users.noreply.github.com> Date: Thu, 26 Feb 2026 23:58:44 +0100 Subject: [PATCH 2/6] helium/ui/layout: finalize vertical hover overlay follow-up --- .../ui/layout/vertical-collapse-command.patch | 120 +++ .../vertical-hide-tab-search-button.patch | 166 +++ .../vertical-hover-overlay-callers.patch | 981 ++++++++++++++++++ .../vertical-hover-overlay-setting.patch | 190 ++++ .../ui/layout/vertical-hover-overlay.patch | 227 ++++ patches/series | 7 + ...fill-async-context-model-predictions.patch | 11 + .../passage-embeddings-forward-decl.patch | 11 + 8 files changed, 1713 insertions(+) create mode 100644 patches/helium/ui/layout/vertical-collapse-command.patch create mode 100644 patches/helium/ui/layout/vertical-hide-tab-search-button.patch create mode 100644 patches/helium/ui/layout/vertical-hover-overlay-callers.patch create mode 100644 patches/helium/ui/layout/vertical-hover-overlay-setting.patch create mode 100644 patches/helium/ui/layout/vertical-hover-overlay.patch create mode 100644 patches/upstream-fixes/autofill-async-context-model-predictions.patch create mode 100644 patches/upstream-fixes/passage-embeddings-forward-decl.patch diff --git a/patches/helium/ui/layout/vertical-collapse-command.patch b/patches/helium/ui/layout/vertical-collapse-command.patch new file mode 100644 index 000000000..a1805273f --- /dev/null +++ b/patches/helium/ui/layout/vertical-collapse-command.patch @@ -0,0 +1,120 @@ +--- a/chrome/app/chrome_command_ids.h ++++ b/chrome/app/chrome_command_ids.h +@@ -99,6 +99,7 @@ + #define IDC_BROWSER_LAYOUT_COMPACT 34083 + #define IDC_BROWSER_LAYOUT_VERTICAL 34084 + #define IDC_BROWSER_LAYOUT_VERTICAL_RIGHT 34085 ++#define IDC_TOGGLE_VERTICAL_TAB_STRIP_COLLAPSED 34086 + + // Tab group Commands + #define IDC_ADD_NEW_TAB_TO_GROUP 34100 +--- a/chrome/browser/ui/browser_commands.h ++++ b/chrome/browser/ui/browser_commands.h +@@ -263,6 +263,7 @@ + void ToggleVerticalTabs(Browser* browser); + void SetBrowserLayout(Browser* browser, HeliumLayoutType layout); + void ToggleVerticalTabStripRightAligned(Browser* browser); ++void ToggleVerticalTabStripCollapsed(Browser* browser); + void ShowTabDeclutter(Browser* browser); + bool CanCloseFind(Browser* browser); + void CloseFind(Browser* browser); +--- a/chrome/browser/ui/browser_commands.cc ++++ b/chrome/browser/ui/browser_commands.cc +@@ -2243,6 +2243,14 @@ + } + } + ++void ToggleVerticalTabStripCollapsed(Browser* browser) { ++ auto* controller = tabs::VerticalTabStripStateController::From(browser); ++ if (!controller || !controller->ShouldDisplayVerticalTabs()) { ++ return; ++ } ++ controller->SetCollapsed(!controller->IsCollapsed()); ++} ++ + void ShowTabDeclutter(Browser* browser) { + browser->window()->CreateTabSearchBubble( + tab_search::mojom::TabSearchSection::kOrganize, +--- a/chrome/browser/ui/browser_command_controller.h ++++ b/chrome/browser/ui/browser_command_controller.h +@@ -224,6 +224,9 @@ + // Updates commands that depend on the state of the tab strip model. + void UpdateCommandsForTabStripStateChanged(); + ++ // Updates commands that depend on Helium layout mode. ++ void UpdateCommandsForHeliumLayoutMode(); ++ + // Updates commands that depend on the enabled state of glic. + void UpdateCommandsForEnableGlicChanged(); + +--- a/chrome/browser/ui/browser_command_controller.cc ++++ b/chrome/browser/ui/browser_command_controller.cc +@@ -266,6 +266,11 @@ + &BrowserCommandController::UpdateCommandsForBookmarkBar, + base::Unretained(this))); + profile_pref_registrar_.Add( ++ prefs::kHeliumLayout, ++ base::BindRepeating( ++ &BrowserCommandController::UpdateCommandsForHeliumLayoutMode, ++ base::Unretained(this))); ++ profile_pref_registrar_.Add( + policy::policy_prefs::kIncognitoModeAvailability, + base::BindRepeating( + &BrowserCommandController::UpdateCommandsForIncognitoAvailability, +@@ -600,6 +605,9 @@ + case IDC_BROWSER_LAYOUT_VERTICAL_RIGHT: + ToggleVerticalTabStripRightAligned(browser_); + break; ++ case IDC_TOGGLE_VERTICAL_TAB_STRIP_COLLAPSED: ++ ToggleVerticalTabStripCollapsed(browser_); ++ break; + + // Window management commands + case IDC_NEW_WINDOW: +@@ -1518,6 +1526,9 @@ + command_updater_.UpdateCommandEnabled(IDC_BROWSER_LAYOUT_VERTICAL, true); + command_updater_.UpdateCommandEnabled(IDC_BROWSER_LAYOUT_VERTICAL_RIGHT, + true); ++ command_updater_.UpdateCommandEnabled(IDC_TOGGLE_VERTICAL_TAB_STRIP_COLLAPSED, ++ false); ++ UpdateCommandsForHeliumLayoutMode(); + #if BUILDFLAG(IS_CHROMEOS) + command_updater_.UpdateCommandEnabled(IDC_TOGGLE_MULTITASK_MENU, true); + command_updater_.UpdateCommandEnabled(IDC_MINIMIZE_WINDOW, true); +@@ -2399,6 +2410,14 @@ + UpdateCommandsForBookmarkEditing(); + } + ++void BrowserCommandController::UpdateCommandsForHeliumLayoutMode() { ++ const bool is_vertical_layout = ++ profile()->GetPrefs()->GetInteger(prefs::kHeliumLayout) == ++ std::to_underlying(HeliumLayoutType::kVertical); ++ command_updater_.UpdateCommandEnabled(IDC_TOGGLE_VERTICAL_TAB_STRIP_COLLAPSED, ++ is_vertical_layout); ++} ++ + actions::ActionItem* BrowserCommandController::FindAction( + actions::ActionId action_id) { + actions::ActionItem* const root_action_item = +--- a/chrome/browser/ui/accelerator_table.cc ++++ b/chrome/browser/ui/accelerator_table.cc +@@ -61,6 +61,8 @@ + {ui::VKEY_F, ui::EF_PLATFORM_ACCELERATOR, IDC_FIND}, + {ui::VKEY_A, ui::EF_SHIFT_DOWN | ui::EF_PLATFORM_ACCELERATOR, + IDC_TAB_SEARCH}, ++ {ui::VKEY_V, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN, ++ IDC_TOGGLE_VERTICAL_TAB_STRIP_COLLAPSED}, + {ui::VKEY_G, ui::EF_PLATFORM_ACCELERATOR, IDC_FIND_NEXT}, + {ui::VKEY_G, ui::EF_SHIFT_DOWN | ui::EF_PLATFORM_ACCELERATOR, + IDC_FIND_PREVIOUS}, +--- a/chrome/browser/ui/cocoa/accelerators_cocoa.mm ++++ b/chrome/browser/ui/cocoa/accelerators_cocoa.mm +@@ -40,6 +40,8 @@ + {IDC_DEV_TOOLS_INSPECT, ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN, ui::VKEY_E}, + {IDC_COPY_OR_INSPECT_SHORTCUT, ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN, ui::VKEY_C}, + {IDC_NEW_SPLIT_TAB, ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN, ui::VKEY_N}, ++ {IDC_TOGGLE_VERTICAL_TAB_STRIP_COLLAPSED, ++ ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN, ui::VKEY_V}, + {IDC_FIND, ui::EF_COMMAND_DOWN, ui::VKEY_F}, + {IDC_NEW_INCOGNITO_WINDOW, ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN, + ui::VKEY_N}, diff --git a/patches/helium/ui/layout/vertical-hide-tab-search-button.patch b/patches/helium/ui/layout/vertical-hide-tab-search-button.patch new file mode 100644 index 000000000..5607e8d58 --- /dev/null +++ b/patches/helium/ui/layout/vertical-hide-tab-search-button.patch @@ -0,0 +1,166 @@ +diff --git a/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_top_container.cc b/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_top_container.cc +index c867823de7..bd45d35127 100644 +--- a/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_top_container.cc ++++ b/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_top_container.cc +@@ -18,7 +18,16 @@ + #include "ui/views/view_class_properties.h" + + namespace { +-constexpr int kTopButtonPadding = 4; ++constexpr int kTopButtonPadding = 3; ++constexpr int kCollapsedTopButtonPadding = 2; ++// Arc-like vertical sidebars keep search in the command bar/shortcut, not as a ++// persistent sidebar button. ++constexpr bool kShowTabSearchButton = false; ++ ++bool IsVisuallyCollapsed( ++ const tabs::VerticalTabStripStateController* controller) { ++ return controller->IsCollapsed() && !controller->IsHoverExpanded(); ++} + } // namespace + + VerticalTabStripTopContainer::VerticalTabStripTopContainer( +@@ -34,16 +43,14 @@ VerticalTabStripTopContainer::VerticalTabStripTopContainer( + tab_search_button_ = AddChildButtonFor(kActionTabSearch); + tab_search_button_->SetProperty(views::kElementIdentifierKey, + kTabSearchButtonElementId); ++ // Keep an invisible anchor for TabSearchBubbleHost, but never consume ++ // sidebar layout space. ++ tab_search_button_->SetProperty(views::kViewIgnoredByLayoutKey, true); ++ tab_search_button_->SetVisible(kShowTabSearchButton); + + collapse_button_ = AddChildButtonFor(kActionToggleCollapseVertical); + collapse_button_->SetProperty(views::kElementIdentifierKey, + kVerticalTabStripCollapseButtonElementId); +- +- if (tabs::IsProjectsPanelFeatureEnabled()) { +- projects_button_ = AddChildButtonFor(kActionToggleProjectsPanel); +- projects_button_->SetProperty(views::kElementIdentifierKey, +- kVerticalTabStripProjectsButtonElementId); +- } + } + + VerticalTabStripTopContainer::~VerticalTabStripTopContainer() = default; +@@ -58,18 +65,12 @@ views::ProposedLayout VerticalTabStripTopContainer::CalculateProposedLayout( + LayoutConstant::kVerticalTabStripTopButtonContainerHeight)); + std::vector container_buttons; + +- CHECK(tab_search_button_); +- container_buttons.push_back(tab_search_button_); ++ // Intentionally omit tab_search_button_ from layout. + + CHECK(collapse_button_); + container_buttons.push_back(collapse_button_); + +- if (tabs::IsProjectsPanelFeatureEnabled()) { +- CHECK(projects_button_); +- container_buttons.push_back(projects_button_); +- } +- +- if (state_controller_->IsCollapsed()) { ++ if (IsVisuallyCollapsed(state_controller_)) { + // If the vertical tab strip is collapsed, then lay out the buttons + // vertically in reverse order from top-to-bottom. + int total_height = exclusion_width_ == 0 ? 0 : toolbar_height_; +@@ -77,7 +78,7 @@ views::ProposedLayout VerticalTabStripTopContainer::CalculateProposedLayout( + total_height += container_button->GetPreferredSize().height(); + } + +- total_height += (container_buttons.size() - 1) * kTopButtonPadding; ++ total_height += (container_buttons.size() - 1) * kCollapsedTopButtonPadding; + + if (total_height > host_size.height()) { + host_size.set_height(total_height); +@@ -88,52 +89,43 @@ views::ProposedLayout VerticalTabStripTopContainer::CalculateProposedLayout( + for (views::LabelButton* container_button : + base::Reversed(container_buttons)) { + const gfx::Size pref_size = container_button->GetPreferredSize(); ++ const int button_size = pref_size.height(); + +- gfx::Rect bounds((host_size.width() - pref_size.width()) / 2, current_y, +- pref_size.width(), pref_size.height()); ++ gfx::Rect bounds((host_size.width() - button_size) / 2, current_y, ++ button_size, button_size); + + layout.child_layouts.emplace_back(container_button, + container_button->GetVisible(), bounds, + views::SizeBounds(pref_size)); + +- current_y += pref_size.height() + kTopButtonPadding; ++ current_y += button_size + kCollapsedTopButtonPadding; + } + } else { +- // If the vertical tab strip is uncollapsed, then lay out the buttons +- // horizontally from right-to-left. +- int total_width = exclusion_width_; +- for (views::LabelButton* container_button : container_buttons) { +- total_width += container_button->GetPreferredSize().width(); +- } +- +- total_width += (container_buttons.size() - 1) * kTopButtonPadding; +- +- // If there is not enough space for the buttons on a single line with +- // caption buttons, shift them below. +- if (exclusion_width_ > 0 && total_width > host_size.width()) { +- host_size.Enlarge(0, toolbar_height_); +- } +- +- int current_x = host_size.width(); ++ const bool is_right_aligned = state_controller_->IsTabStripRightAligned(); ++ int current_x = is_right_aligned ? host_size.width() - exclusion_width_ ++ : exclusion_width_; + +- // Calculate bounds to right-align the button horizontally and center it +- // vertically within the available space. +- for (views::LabelButton* container_button : container_buttons) { ++ // Calculate bounds to align buttons against the same edge as the tabstrip, ++ // and center them vertically within the available space. ++ for (views::LabelButton* container_button : ++ base::Reversed(container_buttons)) { + const gfx::Size pref_size = container_button->GetPreferredSize(); ++ const int button_size = pref_size.height(); + gfx::Rect bounds( +- current_x - pref_size.width(), ++ is_right_aligned ? current_x - button_size : current_x, + host_size.height() - + (GetLayoutConstant( + LayoutConstant::kVerticalTabStripTopButtonContainerHeight) + +- pref_size.height()) / ++ button_size) / + 2, +- pref_size.width(), pref_size.height()); ++ button_size, button_size); + + layout.child_layouts.emplace_back(container_button, + container_button->GetVisible(), bounds, + views::SizeBounds(pref_size)); + +- current_x = bounds.x() - kTopButtonPadding; ++ current_x = is_right_aligned ? bounds.x() - kTopButtonPadding ++ : bounds.right() + kTopButtonPadding; + } + } + +@@ -171,10 +163,6 @@ bool VerticalTabStripTopContainer::IsPositionInWindowCaption( + return false; + } + +- if (projects_button_ && IsHitInView(projects_button_, point)) { +- return false; +- } +- + return true; + } + +@@ -185,7 +173,11 @@ void VerticalTabStripTopContainer::SetToolbarHeightForLayout( + + void VerticalTabStripTopContainer::SetExclusionWidthForLayout( + const int exclusion_width) { ++ if (exclusion_width_ == exclusion_width) { ++ return; ++ } + exclusion_width_ = exclusion_width; ++ PreferredSizeChanged(); + } + + BEGIN_METADATA(VerticalTabStripTopContainer) diff --git a/patches/helium/ui/layout/vertical-hover-overlay-callers.patch b/patches/helium/ui/layout/vertical-hover-overlay-callers.patch new file mode 100644 index 000000000..f72cc325d --- /dev/null +++ b/patches/helium/ui/layout/vertical-hover-overlay-callers.patch @@ -0,0 +1,981 @@ +diff --git a/chrome/browser/ui/views/frame/layout/browser_view_layout_delegate.h b/chrome/browser/ui/views/frame/layout/browser_view_layout_delegate.h +index c18f56560a..ebce6c59aa 100644 +--- a/chrome/browser/ui/views/frame/layout/browser_view_layout_delegate.h ++++ b/chrome/browser/ui/views/frame/layout/browser_view_layout_delegate.h +@@ -30,7 +30,10 @@ class BrowserViewLayoutDelegate { + virtual bool ShouldDrawTabStrip() const = 0; + virtual bool ShouldUseTouchableTabstrip() const = 0; + virtual bool ShouldDrawVerticalTabStrip() const = 0; ++ virtual bool ShouldDrawToolbarTabStrip() const = 0; + virtual bool IsVerticalTabStripCollapsed() const = 0; ++ virtual bool IsVerticalTabStripHoverExpanded() const = 0; ++ virtual bool IsVerticalTabStripRightAligned() const = 0; + virtual bool ShouldDrawWebAppFrameToolbar() const = 0; + virtual bool GetBorderlessModeEnabled() const = 0; + virtual gfx::Rect GetBoundsForTabStripRegionInBrowserView() const = 0; +diff --git a/chrome/browser/ui/views/frame/layout/browser_view_layout_delegate_impl.cc b/chrome/browser/ui/views/frame/layout/browser_view_layout_delegate_impl.cc +index 010596c2e5..9d48b1b90b 100644 +--- a/chrome/browser/ui/views/frame/layout/browser_view_layout_delegate_impl.cc ++++ b/chrome/browser/ui/views/frame/layout/browser_view_layout_delegate_impl.cc +@@ -50,10 +50,26 @@ bool BrowserViewLayoutDelegateImpl::ShouldDrawVerticalTabStrip() const { + return browser_view_->ShouldDrawVerticalTabStrip(); + } + ++bool BrowserViewLayoutDelegateImpl::ShouldDrawToolbarTabStrip() const { ++ return browser_view_->ShouldDrawToolbarTabStrip(); ++} ++ + bool BrowserViewLayoutDelegateImpl::IsVerticalTabStripCollapsed() const { + return browser_view_->IsVerticalTabStripCollapsed(); + } + ++bool BrowserViewLayoutDelegateImpl::IsVerticalTabStripHoverExpanded() const { ++ auto* controller = ++ tabs::VerticalTabStripStateController::From(browser_view_->browser()); ++ return controller && controller->IsHoverExpanded(); ++} ++ ++bool BrowserViewLayoutDelegateImpl::IsVerticalTabStripRightAligned() const { ++ auto* controller = ++ tabs::VerticalTabStripStateController::From(browser_view_->browser()); ++ return controller && controller->IsTabStripRightAligned(); ++} ++ + bool BrowserViewLayoutDelegateImpl::ShouldDrawWebAppFrameToolbar() const { + return !GetBorderlessModeEnabled() && + GetFrameView()->ShouldShowWebAppFrameToolbar(); +@@ -127,12 +143,7 @@ bool BrowserViewLayoutDelegateImpl::IsInfobarVisible() const { + } + + bool BrowserViewLayoutDelegateImpl::IsContentsSeparatorEnabled() const { +- // Web app windows manage their own separator. +- // TODO(crbug.com/40102629): Make PWAs set the visibility of the ToolbarView +- // based on whether it is visible instead of setting the height to 0px. This +- // will enable BrowserViewLayout to hide the contents separator on its own +- // using the same logic used by normal BrowserElementsViews. +- return !browser_view_->browser()->app_controller(); ++ return false; + } + + bool BrowserViewLayoutDelegateImpl::IsActiveTabSplit() const { +diff --git a/chrome/browser/ui/views/frame/layout/browser_view_layout_delegate_impl.h b/chrome/browser/ui/views/frame/layout/browser_view_layout_delegate_impl.h +index e12cbe490e..8ada52de34 100644 +--- a/chrome/browser/ui/views/frame/layout/browser_view_layout_delegate_impl.h ++++ b/chrome/browser/ui/views/frame/layout/browser_view_layout_delegate_impl.h +@@ -23,7 +23,10 @@ class BrowserViewLayoutDelegateImpl : public BrowserViewLayoutDelegate { + bool ShouldDrawTabStrip() const override; + bool ShouldUseTouchableTabstrip() const override; + bool ShouldDrawVerticalTabStrip() const override; ++ bool ShouldDrawToolbarTabStrip() const override; + bool IsVerticalTabStripCollapsed() const override; ++ bool IsVerticalTabStripHoverExpanded() const override; ++ bool IsVerticalTabStripRightAligned() const override; + bool ShouldDrawWebAppFrameToolbar() const override; + bool GetBorderlessModeEnabled() const override; + gfx::Rect GetBoundsForTabStripRegionInBrowserView() const override; +diff --git a/chrome/browser/ui/views/frame/layout/browser_view_tabbed_layout_impl.cc b/chrome/browser/ui/views/frame/layout/browser_view_tabbed_layout_impl.cc +index 450e172919..490e00f40f 100644 +--- a/chrome/browser/ui/views/frame/layout/browser_view_tabbed_layout_impl.cc ++++ b/chrome/browser/ui/views/frame/layout/browser_view_tabbed_layout_impl.cc +@@ -103,6 +103,9 @@ BrowserViewTabbedLayoutImpl::GetMinimumTabStripSize() const { + views().vertical_tab_strip_region_view->GetMinimumSize(); + return std::make_pair(result, gfx::Size()); + } ++ case TabStripType::kToolbar: ++ // In compact layout, tab strip is in the toolbar ++ return std::make_pair(gfx::Size(), gfx::Size()); + case TabStripType::kWebUi: + // WebUI tabstrip is lazily-created. + return std::make_pair(gfx::Size(), +@@ -148,6 +151,9 @@ BrowserViewTabbedLayoutImpl::GetTabStripType() const { + if (delegate().ShouldDrawVerticalTabStrip()) { + return TabStripType::kVertical; + } ++ if (delegate().ShouldDrawToolbarTabStrip()) { ++ return TabStripType::kToolbar; ++ } + return delegate().ShouldDrawTabStrip() ? TabStripType::kHorizontal + : TabStripType::kNone; + } +@@ -167,13 +173,17 @@ int BrowserViewTabbedLayoutImpl::GetCollapsedVerticalTabStripRelativeTop( + return 0; + } + +- // If there is no leading exclusion, the tabstrip goes all the way to the top. +- if (params.leading_exclusion.IsEmpty()) { ++ const auto& exclusion_area = delegate().IsVerticalTabStripRightAligned() ++ ? params.trailing_exclusion ++ : params.leading_exclusion; ++ ++ // If there is no side exclusion, the tabstrip goes all the way to the top. ++ if (exclusion_area.IsEmpty()) { + return 0; + } + + const int exclusion_height = +- base::ClampCeil(params.leading_exclusion.ContentWithPadding().height()); ++ base::ClampCeil(exclusion_area.ContentWithPadding().height()); + + // Try to align with toolbar. But if it's not visible, then don't. + if (!delegate().IsToolbarVisible()) { +@@ -242,6 +252,10 @@ BrowserViewTabbedLayoutImpl::CalculateProposedLayout( + BrowserLayoutParams params = browser_params; + bool needs_exclusion = true; + const TabStripType tab_strip_type = GetTabStripType(); ++ const bool vertical_hover_expanded = ++ delegate().IsVerticalTabStripHoverExpanded(); ++ const bool vertical_right_aligned = ++ delegate().IsVerticalTabStripRightAligned(); + + if (tab_strip_type == TabStripType::kWebUi) { + // When the WebUI tab strip is present, it does not paint over the caption +@@ -292,29 +306,46 @@ BrowserViewTabbedLayoutImpl::CalculateProposedLayout( + views().browser_view)) { + gfx::Rect vertical_tab_strip_bounds; + if (tab_strip_type == TabStripType::kVertical) { ++ const BrowserLayoutExclusionArea& side_exclusion = ++ vertical_right_aligned ? params.trailing_exclusion ++ : params.leading_exclusion; + int vertical_tab_strip_relative_top = 0; + int vertical_tab_strip_width = + views().vertical_tab_strip_region_view->GetPreferredSize().width(); ++ int vertical_tab_strip_reserved_width = vertical_tab_strip_width; + if (delegate().IsVerticalTabStripCollapsed()) { + // Collapsed tabstrip sits underneath caption buttons when present. ++ vertical_tab_strip_reserved_width = ++ VerticalTabStripRegionView::kCollapsedWidth; ++ if (!vertical_hover_expanded) { ++ vertical_tab_strip_width = vertical_tab_strip_reserved_width; ++ } + vertical_tab_strip_relative_top = + GetCollapsedVerticalTabStripRelativeTop(params); + collapsed_vertical_tab_strip_adjustment = +- vertical_tab_strip_relative_top > 0 ? vertical_tab_strip_width : 0; ++ vertical_tab_strip_relative_top > 0 ++ ? vertical_tab_strip_reserved_width ++ : 0; + } else { + // Un-collapsed tabstrip must be at least as wide as the caption + // buttons, if present. +- const int leading_exclusion_width = base::ClampCeil( +- params.leading_exclusion.ContentWithPadding().width()); ++ const int side_exclusion_width = base::ClampCeil( ++ side_exclusion.ContentWithPadding().width()); + vertical_tab_strip_width = +- std::max(vertical_tab_strip_width, leading_exclusion_width); ++ std::max(vertical_tab_strip_width, side_exclusion_width); + } ++ const int vertical_tab_strip_x = ++ vertical_right_aligned ++ ? params.visual_client_area.right() - vertical_tab_strip_width ++ : params.visual_client_area.x(); + vertical_tab_strip_bounds = gfx::Rect( +- params.visual_client_area.x(), +- params.visual_client_area.y() + vertical_tab_strip_relative_top, +- vertical_tab_strip_width, +- params.visual_client_area.height() - vertical_tab_strip_relative_top); +- params.InsetHorizontal(vertical_tab_strip_width, /*leading=*/true); ++ vertical_tab_strip_x, params.visual_client_area.y() + ++ vertical_tab_strip_relative_top, ++ vertical_tab_strip_width, params.visual_client_area.height() - ++ vertical_tab_strip_relative_top); ++ params.InsetHorizontal( ++ vertical_tab_strip_reserved_width, ++ /*leading=*/!vertical_right_aligned); + } + layout.AddChild(views().vertical_tab_strip_region_view, + vertical_tab_strip_bounds, +@@ -356,16 +387,21 @@ BrowserViewTabbedLayoutImpl::CalculateProposedLayout( + GetTopContainerBoundsInParent(top_container_local_bounds, params); + params.SetTop(top_container_layout.bounds.bottom()); + +- // Possibly bump the leading margin of the top container out to cover the ++ // Possibly bump the side margin of the top container out to cover the + // caption buttons, leaving all of the child views in the same absolute + // position. + if (collapsed_vertical_tab_strip_adjustment > 0) { +- top_container_layout.bounds.Outset( +- gfx::Outsets::TLBR(0, collapsed_vertical_tab_strip_adjustment, 0, 0)); +- for (auto& [child, child_layout] : top_container_layout.children) { +- if (!child_layout.bounds.IsEmpty()) { +- child_layout.bounds.Offset(collapsed_vertical_tab_strip_adjustment, +- 0); ++ if (vertical_right_aligned) { ++ top_container_layout.bounds.Outset(gfx::Outsets::TLBR( ++ 0, 0, 0, collapsed_vertical_tab_strip_adjustment)); ++ } else { ++ top_container_layout.bounds.Outset(gfx::Outsets::TLBR( ++ 0, collapsed_vertical_tab_strip_adjustment, 0, 0)); ++ for (auto& [child, child_layout] : top_container_layout.children) { ++ if (!child_layout.bounds.IsEmpty()) { ++ child_layout.bounds.Offset(collapsed_vertical_tab_strip_adjustment, ++ 0); ++ } + } + } + } +@@ -641,15 +677,25 @@ BrowserViewTabbedLayoutImpl::CalculateProposedLayout( + toolbar_height); + + // If the toolbar is not in the browser, then the exclusion isn't either. +- const int exclusion_width = +- toolbar_bounds +- ? std::max(0, base::ClampCeil(browser_params.leading_exclusion +- .ContentWithPadding() +- .width()) - +- tabstrip_bounds->x()) +- : 0; ++ int exclusion_width = 0; ++ if (toolbar_bounds) { ++ const BrowserLayoutExclusionArea& side_exclusion = ++ vertical_right_aligned ? browser_params.trailing_exclusion ++ : browser_params.leading_exclusion; ++ const int side_exclusion_width = ++ base::ClampCeil(side_exclusion.ContentWithPadding().width()); ++ const int side_distance_from_client_edge = ++ vertical_right_aligned ++ ? browser_params.visual_client_area.right() - ++ tabstrip_bounds->right() ++ : tabstrip_bounds->x(); ++ exclusion_width = std::max( ++ 0, side_exclusion_width - side_distance_from_client_edge - 10); ++ } + views().vertical_tab_strip_region_view->SetExclusionWidthForLayout( + exclusion_width); ++ views().vertical_tab_strip_region_view->SetHasLeadingExclusionForLayout( ++ exclusion_width > 0); + } + + return layout; +@@ -705,9 +751,12 @@ gfx::Rect BrowserViewTabbedLayoutImpl::CalculateTopContainerLayout( + if (IsParentedTo(views().toolbar, views().top_container)) { + gfx::Rect toolbar_bounds; + if (toolbar_visible) { ++ // Visually clamp two conflicting margins: window controls & toolbar ++ const int exclusion = tab_strip_type == TabStripType::kToolbar || ++ tab_strip_type == TabStripType::kVertical ? 10 : 0; + toolbar_bounds = + needs_exclusion +- ? GetBoundsWithExclusion(params, views().toolbar) ++ ? GetBoundsWithExclusion(params, views().toolbar, exclusion) + : gfx::Rect(params.visual_client_area.x(), + params.visual_client_area.y(), + params.visual_client_area.width(), +diff --git a/chrome/browser/ui/views/frame/vertical_tab_strip_region_view.cc b/chrome/browser/ui/views/frame/vertical_tab_strip_region_view.cc +index 334c2e83ba..e8f4056497 100644 +--- a/chrome/browser/ui/views/frame/vertical_tab_strip_region_view.cc ++++ b/chrome/browser/ui/views/frame/vertical_tab_strip_region_view.cc +@@ -8,6 +8,7 @@ + #include + + #include "base/callback_list.h" ++#include "base/containers/adapters.h" + #include "base/functional/bind.h" + #include "base/notimplemented.h" + #include "chrome/browser/ui/browser_actions.h" +@@ -19,11 +20,13 @@ + #include "chrome/browser/ui/tabs/tab_group_model.h" + #include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service.h" + #include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_feature.h" ++#include "chrome/browser/ui/tabs/vertical_tab_strip_state.h" + #include "chrome/browser/ui/tabs/vertical_tab_strip_state_controller.h" + #include "chrome/browser/ui/views/frame/browser_view.h" + #include "chrome/browser/ui/views/frame/top_container_background.h" + #include "chrome/browser/ui/views/tabs/vertical/root_tab_collection_node.h" + #include "chrome/browser/ui/views/tabs/vertical/tab_collection_node.h" ++#include "chrome/browser/ui/views/tabs/vertical/vertical_pinned_tab_container_view.h" + #include "chrome/browser/ui/views/tabs/vertical/vertical_tab_drag_handler.h" + #include "chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_bottom_container.h" + #include "chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller.h" +@@ -36,6 +39,7 @@ + #include "components/tabs/public/tab_interface.h" + #include "ui/base/metadata/metadata_impl_macros.h" + #include "ui/color/color_id.h" ++#include "ui/compositor/layer.h" + #include "ui/gfx/animation/animation.h" + #include "ui/views/background.h" + #include "ui/views/controls/resize_area.h" +@@ -49,7 +53,9 @@ + #include "ui/views/view_utils.h" + + namespace { +-constexpr int kRegionVerticalPadding = 5; ++constexpr int kRegionVerticalPadding = 3; ++constexpr int kBottomContainerGap = 2; ++constexpr int kDefaultWidthSnapDistance = 6; + } // namespace + + VerticalTabStripRegionView::VerticalTabStripRegionView( +@@ -57,9 +63,17 @@ VerticalTabStripRegionView::VerticalTabStripRegionView( + actions::ActionItem* root_action_item, + BrowserWindowInterface* browser, + BrowserView* browser_view) +- : tab_strip_model_(browser->GetTabStripModel()), ++ : browser_view_(browser_view), ++ tab_strip_model_(browser_view->browser()->GetTabStripModel()), + state_controller_(state_controller), + resize_animation_(this) { ++ // For z-ordering purposes this needs to be on a layer. ++ SetPaintToLayer(); ++ // Because corners may be transparent, this must be set to false. ++ layer()->SetFillsBoundsOpaquely(false); ++ // Hover expansion should react when entering/leaving any descendant view. ++ SetNotifyEnterExitOnChild(true); ++ + flex_layout_ = SetLayoutManager(std::make_unique()); + flex_layout_->SetOrientation(views::LayoutOrientation::kVertical) + .SetCollapseMargins(true) +@@ -73,24 +87,27 @@ VerticalTabStripRegionView::VerticalTabStripRegionView( + AddChildView(std::make_unique( + state_controller_, root_action_item)); + +- top_button_separator_ = AddChildView(std::make_unique()); +- + bottom_button_container_ = + AddChildView(std::make_unique( + state_controller_, root_action_item, browser)); + ++ bottom_button_container_->SetProperty( ++ views::kFlexBehaviorKey, ++ views::FlexSpecification(views::MinimumFlexSizeRule::kPreferred, ++ views::MaximumFlexSizeRule::kUnbounded)); ++ + gemini_button_ = AddChildView(std::make_unique()); + + resize_area_ = AddChildView(std::make_unique(this)); + resize_area_->SetProperty(views::kViewIgnoredByLayoutKey, true); + + resize_animation_.SetSlideDuration( +- gfx::Animation::RichAnimationDuration(base::Milliseconds(450))); ++ gfx::Animation::RichAnimationDuration(base::Milliseconds(250))); + resize_animation_.SetTweenType(gfx::Tween::Type::EASE_IN_OUT_EMPHASIZED); +- resize_animation_.Reset(!state_controller_->IsCollapsed()); +- + target_collapse_state_ = state_controller_->GetState(); +- SetPreferredSize(gfx::Size(target_collapse_state_.collapsed ++ target_visually_collapsed_ = IsVisuallyCollapsed(); ++ resize_animation_.Reset(!target_visually_collapsed_); ++ SetPreferredSize(gfx::Size(target_visually_collapsed_ + ? kCollapsedWidth + : target_collapse_state_.uncollapsed_width, + 0)); +@@ -104,21 +121,22 @@ VerticalTabStripRegionView::VerticalTabStripRegionView( + + GetViewAccessibility().SetRole(ax::mojom::Role::kTabList); + +- root_node_ = std::make_unique( +- browser->GetTabStripModel(), +- base::BindRepeating(&VerticalTabStripRegionView::SetTabStripView, +- base::Unretained(this))); +- + SetBackground(std::make_unique( + browser_view, TopContainerBackground::ColorChoice::kFrameColor)); + UpdateBackgroundColors(); + } + + VerticalTabStripRegionView::~VerticalTabStripRegionView() { +- root_node_->SetController(nullptr); ++ if (root_node_) { ++ root_node_->SetController(nullptr); ++ } ++ + tab_strip_controller_.reset(); +- auto handler = RemoveChildViewT(drag_handler_); +- drag_handler_ = nullptr; ++ ++ if (drag_handler_) { ++ auto handler = RemoveChildViewT(drag_handler_->GetDragContext()); ++ drag_handler_ = nullptr; ++ } + } + + void VerticalTabStripRegionView::AddedToWidget() { +@@ -133,44 +151,97 @@ void VerticalTabStripRegionView::Layout(PassKey) { + + // Manually position the resize area as it overlaps views handled by the flex + // layout. +- resize_area_->SetBoundsRect(gfx::Rect(bounds().right() - kResizeAreaWidth, 0, +- kResizeAreaWidth, bounds().height())); ++ const int resize_area_x = state_controller_->IsTabStripRightAligned() ++ ? 0 ++ : bounds().right() - kResizeAreaWidth; ++ resize_area_->SetBoundsRect(gfx::Rect(resize_area_x, 0, kResizeAreaWidth, ++ bounds().height())); ++} ++ ++void VerticalTabStripRegionView::OnMouseEntered(const ui::MouseEvent& event) { ++ TabStripRegionView::OnMouseEntered(event); ++ state_controller_->SetHovered(true); ++} ++ ++void VerticalTabStripRegionView::OnMouseExited(const ui::MouseEvent& event) { ++ TabStripRegionView::OnMouseExited(event); ++ state_controller_->SetHovered(false); + } + + views::View* VerticalTabStripRegionView::GetDefaultFocusableChild() { + return top_button_container_; + } + +-bool VerticalTabStripRegionView::IsTabStripEditable() const { +- // TODO(crbug.com/467710547): This needs to consider the drag context. Wait +- // until that is implemented before updating this function. +- NOTIMPLEMENTED(); +- return tab_strip_editable_for_testing_; ++void VerticalTabStripRegionView::InitializeTabStrip() { ++ if (root_node_) { ++ return; ++ } ++ ++ root_node_ = std::make_unique( ++ tab_strip_model_, ++ base::BindRepeating(&VerticalTabStripRegionView::SetTabStripView, ++ base::Unretained(this)), ++ base::BindRepeating(&VerticalTabStripRegionView::ClearTabStripView, ++ base::Unretained(this))); ++ ++ std::unique_ptr tab_menu_model_factory; ++ if (browser_view_ && browser_view_->browser()->app_controller()) { ++ tab_menu_model_factory = ++ browser_view_->browser()->app_controller()->GetTabMenuModelFactory(); ++ } ++ ++ TabStripModel* tab_strip_model = browser_view_->browser()->GetTabStripModel(); ++ CHECK(tab_strip_model); ++ auto drag_handler = std::make_unique( ++ *tab_strip_model, *root_node_.get()); ++ drag_handler_ = drag_handler.get(); ++ ++ CHECK(!tab_strip_controller_); ++ tab_strip_controller_ = std::make_unique( ++ tab_strip_model, browser_view_, *AddChildView(std::move(drag_handler)), ++ std::move(tab_menu_model_factory)); ++ ++ root_node_->SetController(tab_strip_controller_.get()); ++ ++ root_node_->Init(); + } + +-void VerticalTabStripRegionView::DisableTabStripEditingForTesting() const { +- // TODO(crbug.com/467710617): Implement this in VerticalTabStripView. +- NOTIMPLEMENTED(); ++void VerticalTabStripRegionView::ResetTabStrip() { ++ if (!root_node_) { ++ return; ++ } ++ ++ root_node_->Reset(); ++ ++ root_node_->SetController(nullptr); ++ tab_strip_controller_.reset(); ++ ++ CHECK(drag_handler_); ++ auto* drag_handler = drag_handler_.get(); ++ drag_handler_ = nullptr; ++ RemoveChildViewT(drag_handler->GetDragContext()); ++ ++ root_node_.reset(); + } + +-bool VerticalTabStripRegionView::IsTabStripCloseable() const { +- // TODO(crbug.com/467710547): Return TabDragContext::IsTabStripCloseable once +- // it exists. +- NOTIMPLEMENTED(); +- return true; ++bool VerticalTabStripRegionView::IsTabStripEditable() const { ++ return tab_strip_editable_for_testing_ && ++ (!drag_handler_ || ++ !drag_handler_->GetDragContext()->GetDragController()); + } + +-bool VerticalTabStripRegionView::IsAnimating() const { +- // TODO(crbug.com/467710547): Return if the view or drag context is animating +- // something. +- NOTIMPLEMENTED(); +- return true; ++void VerticalTabStripRegionView::DisableTabStripEditingForTesting() const { ++ const_cast(this) ++ ->tab_strip_editable_for_testing_ = false; + } + +-void VerticalTabStripRegionView::StopAnimating() { +- // TODO(crbug.com/467710547): Stop any ongoing animation in the +- // VerticalTabStripView. +- NOTIMPLEMENTED(); ++bool VerticalTabStripRegionView::IsTabStripCloseable() const { ++ if (!drag_handler_) { ++ return true; ++ } ++ TabDragController* drag_controller = ++ drag_handler_->GetDragContext()->GetDragController(); ++ return !drag_controller || drag_controller->IsMovingLastTab(); + } + + void VerticalTabStripRegionView::UpdateLoadingAnimations( +@@ -250,7 +321,7 @@ views::View* VerticalTabStripRegionView::GetTabGroupAnchorView( + } + + TabDragContext* VerticalTabStripRegionView::GetDragContext() { +- return drag_handler_.get(); ++ return drag_handler_->GetDragContext(); + } + + std::optional +@@ -277,7 +348,10 @@ void VerticalTabStripRegionView::OnResize(int resize_amount, + if (!starting_width_on_resize_.has_value()) { + starting_width_on_resize_ = width(); + } +- const int proposed_width = starting_width_on_resize_.value() + resize_amount; ++ const int resize_delta = state_controller_->IsTabStripRightAligned() ++ ? -resize_amount ++ : resize_amount; ++ const int proposed_width = starting_width_on_resize_.value() + resize_delta; + if (done_resizing) { + starting_width_on_resize_ = std::nullopt; + } +@@ -287,6 +361,19 @@ void VerticalTabStripRegionView::OnResize(int resize_amount, + new_state.collapsed = false; + new_state.uncollapsed_width = + std::clamp(proposed_width, kUncollapsedMinWidth, kUncollapsedMaxWidth); ++ ++ // Snap to default uncollapsed width if within the snap distance. ++ const int default_uncollapsed_width = ++ tabs::kVerticalTabStripDefaultUncollapsedWidth; ++ const int min_snap_width = ++ default_uncollapsed_width - kDefaultWidthSnapDistance; ++ const int max_snap_width = ++ default_uncollapsed_width + kDefaultWidthSnapDistance; ++ if (new_state.uncollapsed_width >= min_snap_width && ++ new_state.uncollapsed_width <= max_snap_width) { ++ new_state.uncollapsed_width = default_uncollapsed_width; ++ } ++ + if (done_resizing) { + // We only want to save the uncollapsed width to the state controller if + // the user has lifted their mouse, otherwise dragging the resize area to +@@ -315,36 +402,33 @@ void VerticalTabStripRegionView::AnimationProgressed( + + bool VerticalTabStripRegionView::IsPositionInWindowCaption( + const gfx::Point& point) { +- gfx::Point point_in_target = point; +- views::View::ConvertPointToTarget(this, top_button_container_, +- &point_in_target); +- if (top_button_container_->HitTestPoint(point_in_target)) { +- return top_button_container_->IsPositionInWindowCaption(point_in_target); +- } +- return false; +-} +- +-void VerticalTabStripRegionView::CreateTabStripController( +- BrowserView* browser_view) { +- std::unique_ptr tab_menu_model_factory; +- if (browser_view && browser_view->browser()->app_controller()) { +- tab_menu_model_factory = +- browser_view->browser()->app_controller()->GetTabMenuModelFactory(); ++ // Check the resize area first, it should always take precedence over other ++ // children regardless of order. ++ if (IsHitInView(resize_area_, point)) { ++ return false; + } + +- TabStripModel* tab_strip_model = browser_view->browser()->GetTabStripModel(); +- CHECK(tab_strip_model); +- auto drag_handler = std::make_unique( +- *tab_strip_model, *root_node_.get()); +- drag_handler_ = drag_handler.get(); +- +- tab_strip_controller_ = std::make_unique( +- tab_strip_model, browser_view, *AddChildView(std::move(drag_handler)), +- std::move(tab_menu_model_factory)); +- +- if (root_node_) { +- root_node_->SetController(tab_strip_controller_.get()); ++ for (views::View* child : children()) { ++ if (!child->GetVisible()) { ++ continue; ++ } ++ gfx::Point point_in_child = point; ++ views::View::ConvertPointToTarget(this, child, &point_in_child); ++ if (child->HitTestPoint(point_in_child)) { ++ if (child == top_button_container_) { ++ return top_button_container_->IsPositionInWindowCaption(point_in_child); ++ } ++ if (child == tab_strip_view_) { ++ return tab_strip_view_->IsPositionInWindowCaption(point_in_child); ++ } ++ if (child == bottom_button_container_) { ++ return bottom_button_container_->IsPositionInWindowCaption( ++ point_in_child); ++ } ++ return false; ++ } + } ++ return true; + } + + void VerticalTabStripRegionView::SetToolbarHeightForLayout( +@@ -354,10 +438,22 @@ void VerticalTabStripRegionView::SetToolbarHeightForLayout( + + void VerticalTabStripRegionView::SetExclusionWidthForLayout( + const int exclusion_width) { ++ if (exclusion_width_ == exclusion_width) { ++ return; ++ } + exclusion_width_ = exclusion_width; + top_button_container_->SetExclusionWidthForLayout(exclusion_width); + } + ++void VerticalTabStripRegionView::SetHasLeadingExclusionForLayout( ++ bool has_leading_exclusion) { ++ if (has_leading_exclusion_ == has_leading_exclusion) { ++ return; ++ } ++ has_leading_exclusion_ = has_leading_exclusion; ++ UpdateInteriorMargin(); ++} ++ + VerticalPinnedTabContainerView* + VerticalTabStripRegionView::GetPinnedTabsContainer() { + return tab_strip_view_->GetPinnedTabsContainer(); +@@ -377,15 +473,45 @@ views::View* VerticalTabStripRegionView::SetTabStripView( + tab_strip_view_->SetProperty( + views::kFlexBehaviorKey, + views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero, +- views::MaximumFlexSizeRule::kUnbounded)); ++ views::MaximumFlexSizeRule::kPreferred)); + tab_strip_view_->SetProperty(views::kMarginsKey, +- gfx::Insets::VH(kRegionVerticalPadding, 0)); +- std::optional separator_index = GetIndexOf(top_button_separator_); +- CHECK(separator_index.has_value()); +- ReorderChildView(tab_strip_view_, separator_index.value() + 1); ++ gfx::Insets::TLBR(kRegionVerticalPadding, 0, ++ kBottomContainerGap, 0)); ++ tab_strip_view_->InitializeTabStrip(*tab_strip_model_); ++ std::optional top_container_index = GetIndexOf(top_button_container_); ++ CHECK(top_container_index.has_value()); ++ ReorderChildView(tab_strip_view_, top_container_index.value() + 1); + return tab_strip_view_; + } + ++void VerticalTabStripRegionView::ClearTabStripView(views::View* view) { ++ CHECK(tab_strip_view_); ++ CHECK(tab_strip_view_ == view); ++ RemoveChildViewT(std::exchange(tab_strip_view_, nullptr)); ++} ++ ++void VerticalTabStripRegionView::UpdateInteriorMargin() { ++ const int padding = GetLayoutConstant( ++ IsVisuallyCollapsed() ++ ? LayoutConstant::kVerticalTabStripCollapsedPadding ++ : LayoutConstant::kVerticalTabStripUncollapsedPadding); ++ ++ // When collapsed and under the toolbar, the top padding has to be 0 ++ // in order to align with webview. ++ int top_padding = ++ IsVisuallyCollapsed() && has_leading_exclusion_ ++ ? 0 ++ : kRegionVerticalPadding; ++ ++ flex_layout_->SetInteriorMargin( ++ gfx::Insets::TLBR(top_padding, 0, padding, 0)); ++} ++ ++bool VerticalTabStripRegionView::IsVisuallyCollapsed() const { ++ return state_controller_->IsCollapsed() && ++ !state_controller_->IsHoverExpanded(); ++} ++ + void VerticalTabStripRegionView::OnCollapsedStateChanged( + tabs::VerticalTabStripStateController* state_controller) { + if (target_collapse_state_.collapsed != state_controller->IsCollapsed()) { +@@ -397,23 +523,37 @@ void VerticalTabStripRegionView::OnCollapsedStateChanged( + UpdateCollapseState(state_controller_->GetState()); + } + ++ const bool is_visually_collapsed = IsVisuallyCollapsed(); + const int padding = GetLayoutConstant( +- state_controller_->IsCollapsed() ++ is_visually_collapsed + ? LayoutConstant::kVerticalTabStripCollapsedPadding + : LayoutConstant::kVerticalTabStripUncollapsedPadding); +- top_button_separator_->SetProperty( +- views::kMarginsKey, gfx::Insets::VH(kRegionVerticalPadding, padding)); ++ + top_button_container_->SetProperty( + views::kMarginsKey, + gfx::Insets::TLBR(0, padding, kRegionVerticalPadding, padding)); + bottom_button_container_->SetProperty( + views::kMarginsKey, +- gfx::Insets::TLBR(kRegionVerticalPadding, padding, 0, padding)); ++ gfx::Insets::TLBR(kBottomContainerGap, padding, 0, padding)); + +- flex_layout_->SetInteriorMargin(gfx::Insets::VH(padding, 0)); ++ UpdateInteriorMargin(); + + if (tab_strip_view_) { +- tab_strip_view_->SetCollapsedState(state_controller->IsCollapsed()); ++ tab_strip_view_->SetCollapsedState(is_visually_collapsed); ++ } ++ ++ if (target_visually_collapsed_ != is_visually_collapsed) { ++ target_visually_collapsed_ = is_visually_collapsed; ++ if (target_visually_collapsed_) { ++ resize_animation_.Hide(); ++ } else { ++ resize_animation_.Show(); ++ } ++ } else if (state_controller_->IsCollapsed() && !resize_animation_.is_animating()) { ++ const int expanded_width = std::clamp(state_controller_->GetUncollapsedWidth(), ++ kUncollapsedMinWidth, ++ kUncollapsedMaxWidth); ++ ResizeToWidth(is_visually_collapsed ? kCollapsedWidth : expanded_width); + } + } + +@@ -422,11 +562,6 @@ void VerticalTabStripRegionView::UpdateCollapseState( + bool previously_collapsed = target_collapse_state_.collapsed; + target_collapse_state_ = new_state; + if (previously_collapsed != target_collapse_state_.collapsed) { +- if (target_collapse_state_.collapsed) { +- resize_animation_.Hide(); +- } else { +- resize_animation_.Show(); +- } + state_controller_->SetCollapsed(target_collapse_state_.collapsed); + } else if (!target_collapse_state_.collapsed && + !resize_animation_.is_animating()) { +@@ -451,9 +586,7 @@ void VerticalTabStripRegionView::ResizeToWidth(int width) { + } + + void VerticalTabStripRegionView::UpdateBackgroundColors() { +- top_button_separator_->SetColorId(IsFrameActive() +- ? kColorTabDividerFrameActive +- : kColorTabDividerFrameInactive); ++ SchedulePaint(); + } + + bool VerticalTabStripRegionView::IsFrameActive() const { +@@ -462,12 +595,20 @@ bool VerticalTabStripRegionView::IsFrameActive() const { + + TabDragTarget* VerticalTabStripRegionView::GetTabDragTarget( + const gfx::Point& point_in_screen) { +- VerticalUnpinnedTabContainerView* container = GetUnpinnedTabsContainer(); +- CHECK(container); +- if (container->GetBoundsInScreen().Contains(point_in_screen)) { +- return &container->GetTabDragTarget(point_in_screen); ++ if (!drag_handler_) { ++ return nullptr; + } +- return nullptr; ++ if (!tab_strip_view_->GetBoundsInScreen().Contains(point_in_screen)) { ++ return nullptr; ++ } ++ ++ // Note: if the drag has not attached to this tab strip yet, it doesn't matter ++ // which container is used because the first drag loop iteration just attaches ++ // it. ++ if (drag_handler_->IsDraggingPinnedTabs()) { ++ return &GetPinnedTabsContainer()->GetTabDragTarget(point_in_screen); ++ } ++ return &GetUnpinnedTabsContainer()->GetTabDragTarget(point_in_screen); + } + + BEGIN_METADATA(VerticalTabStripRegionView) +diff --git a/chrome/browser/ui/views/frame/vertical_tab_strip_region_view.h b/chrome/browser/ui/views/frame/vertical_tab_strip_region_view.h +index 0de5186bde..6588bca74c 100644 +--- a/chrome/browser/ui/views/frame/vertical_tab_strip_region_view.h ++++ b/chrome/browser/ui/views/frame/vertical_tab_strip_region_view.h +@@ -30,6 +30,10 @@ namespace tabs { + class VerticalTabStripStateController; + } // namespace tabs + ++namespace ui { ++class MouseEvent; ++} // namespace ui ++ + namespace views { + class ResizeArea; + class Separator; +@@ -48,11 +52,11 @@ class VerticalTabStripRegionView final : public TabStripRegionView, + static constexpr int kResizeAreaWidth = 6; + // TODO(crbug.com/465833741): Replace constant with derived value based on + // caption buttons. +- static constexpr int kUncollapsedMinWidth = 126; ++ static constexpr int kUncollapsedMinWidth = 160; + // TODO(crbug.com/465832180): Replace constant based width final max width for + // view. + static constexpr int kUncollapsedMaxWidth = 400; +- static constexpr int kCollapsedWidth = 48; ++ static constexpr int kCollapsedWidth = 38; + // TODO(crbug.com/465833741): Determine snapping behavior. + static constexpr int kCollapseSnapWidth = + (kUncollapsedMinWidth + kCollapsedWidth) / 2; +@@ -97,14 +101,16 @@ class VerticalTabStripRegionView final : public TabStripRegionView, + // views::View: + void AddedToWidget() override; + void Layout(PassKey) override; ++ void OnMouseEntered(const ui::MouseEvent& event) override; ++ void OnMouseExited(const ui::MouseEvent& event) override; + views::View* GetDefaultFocusableChild() override; + + // TabStripRegionView ++ void InitializeTabStrip() override; ++ void ResetTabStrip() override; + bool IsTabStripEditable() const override; + void DisableTabStripEditingForTesting() const override; + bool IsTabStripCloseable() const override; +- bool IsAnimating() const override; +- void StopAnimating() override; + void UpdateLoadingAnimations(const base::TimeDelta& elapsed_time) override; + std::optional GetFocusedTabIndex() const override; + const TabRendererData& GetTabRendererData(int tab_index) override; +@@ -127,8 +133,6 @@ class VerticalTabStripRegionView final : public TabStripRegionView, + + bool IsPositionInWindowCaption(const gfx::Point& point); + +- void CreateTabStripController(BrowserView* browser_view); +- + // These methods provide the toolbar height and exclusion width, before the + // layout of this view, for use in calculating positioning of child views. If + // an exclusion width is provided, nothing can be rendered within the +@@ -136,11 +140,16 @@ class VerticalTabStripRegionView final : public TabStripRegionView, + // the leading, top corner. + void SetToolbarHeightForLayout(const int toolbar_height); + void SetExclusionWidthForLayout(const int exclusion_width); ++ void SetHasLeadingExclusionForLayout(bool has_leading_exclusion); + + TabDragTarget* GetTabDragTarget(const gfx::Point& point_in_screen); + + private: + views::View* SetTabStripView(std::unique_ptr view); ++ void ClearTabStripView(views::View* view); ++ ++ void UpdateInteriorMargin(); ++ bool IsVisuallyCollapsed() const; + + void OnCollapsedStateChanged( + tabs::VerticalTabStripStateController* state_controller); +@@ -151,6 +160,8 @@ class VerticalTabStripRegionView final : public TabStripRegionView, + + bool IsFrameActive() const; + ++ raw_ptr browser_view_; ++ + // When false simulates a non-editable tabstrip. For testing only. + bool tab_strip_editable_for_testing_ = true; + +@@ -164,7 +175,7 @@ class VerticalTabStripRegionView final : public TabStripRegionView, + + // The drag handler is a view (required for capturing mouse inputs during + // a drag loop) owned by the tab strip's View. +- raw_ptr drag_handler_ = nullptr; ++ raw_ptr drag_handler_ = nullptr; + + std::unique_ptr tab_strip_controller_; + std::unique_ptr root_node_; +@@ -185,6 +196,7 @@ class VerticalTabStripRegionView final : public TabStripRegionView, + // Additionally, the collapsed value may differ from the state controller, in + // which case this is the source of truth only if we are in a drag operation. + tabs::VerticalTabStripState target_collapse_state_; ++ bool target_visually_collapsed_ = false; + + // Animation for collapsing (GetCurrentValue() -> 0) and expanding + // (GetCurrentValue() -> 1). +@@ -193,6 +205,9 @@ class VerticalTabStripRegionView final : public TabStripRegionView, + // The width of the exclusion zone. This is used to determine when to toggle + // the collapse state of the state controller. + std::optional exclusion_width_ = std::nullopt; ++ ++ // Whether a leading exclusion exists due to window controls. ++ bool has_leading_exclusion_ = false; + }; + + #endif // CHROME_BROWSER_UI_VIEWS_FRAME_VERTICAL_TAB_STRIP_REGION_VIEW_H_ +diff --git a/chrome/browser/ui/views/frame/vertical_tab_strip_region_view_browsertest.cc b/chrome/browser/ui/views/frame/vertical_tab_strip_region_view_browsertest.cc +index 0fb1ac6a41..3ac5369480 100644 +--- a/chrome/browser/ui/views/frame/vertical_tab_strip_region_view_browsertest.cc ++++ b/chrome/browser/ui/views/frame/vertical_tab_strip_region_view_browsertest.cc +@@ -393,6 +393,42 @@ IN_PROC_BROWSER_TEST_F(VerticalTabStripRegionViewTest, + WaitForBoundsToMatchPreferredWidth(); + } + ++IN_PROC_BROWSER_TEST_F(VerticalTabStripRegionViewTest, ++ HoverExpandedDoesNotResizeContents) { ++ state_controller()->SetCollapsed(true); ++ ASSERT_TRUE(base::test::RunUntil( ++ [&]() { return !region_view()->is_animating_for_testing(); })); ++ WaitForBoundsToMatchPreferredWidth(); ++ ++ const auto initial_contents_bounds = ++ browser()->GetBrowserView().GetContentsContainerForTest()->bounds(); ++ ++ state_controller()->SetHovered(true); ++ ASSERT_TRUE(base::test::RunUntil( ++ [&]() { return state_controller()->IsHoverExpanded(); })); ++ ASSERT_TRUE(base::test::RunUntil([&]() { ++ return region_view()->GetPreferredSize().width() > ++ VerticalTabStripRegionView::kCollapsedWidth; ++ })); ++ WaitForBoundsToMatchPreferredWidth(); ++ ++ EXPECT_TRUE(state_controller()->IsCollapsed()); ++ EXPECT_EQ(initial_contents_bounds, ++ browser()->GetBrowserView().GetContentsContainerForTest()->bounds()); ++ ++ state_controller()->SetHovered(false); ++ ASSERT_TRUE(base::test::RunUntil( ++ [&]() { return !state_controller()->IsHoverExpanded(); })); ++ ASSERT_TRUE(base::test::RunUntil([&]() { ++ return region_view()->GetPreferredSize().width() == ++ VerticalTabStripRegionView::kCollapsedWidth; ++ })); ++ WaitForBoundsToMatchPreferredWidth(); ++ ++ EXPECT_EQ(initial_contents_bounds, ++ browser()->GetBrowserView().GetContentsContainerForTest()->bounds()); ++} ++ + // Verify that the pinned tabs container will never be larger than the unpinned + // tabs area. + IN_PROC_BROWSER_TEST_F(VerticalTabStripRegionViewTest, +diff --git a/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller.cc b/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller.cc +index 5596508a3d..1669a250e8 100644 +--- a/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller.cc ++++ b/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller.cc +@@ -20,6 +20,7 @@ + #include "chrome/browser/ui/views/tabs/tab_group_editor_bubble_view.h" + #include "chrome/browser/ui/views/tabs/vertical/tab_collection_node.h" + #include "chrome/browser/ui/views/tabs/vertical/vertical_tab_drag_handler.h" ++#include "chrome/browser/ui/views/tabs/vertical/vertical_tab_group_view.h" + #include "components/tabs/public/tab_collection_types.h" + #include "components/tabs/public/tab_group.h" + #include "components/tabs/public/tab_interface.h" +@@ -198,6 +199,14 @@ void VerticalTabStripController::ToggleTabGroupCollapsedState( + } + } + ++void VerticalTabStripController::ShowGroupEditorBubble( ++ const TabCollectionNode* group_node) { ++ auto* group_header_view = ++ static_cast(group_node->view())->group_header(); ++ group_header_view->ShowContextMenuForViewImpl( ++ group_header_view, gfx::Point(), ui::mojom::MenuSourceType::kNone); ++} ++ + views::Widget* VerticalTabStripController::ShowGroupEditorBubble( + const tab_groups::TabGroupId& group_id, + views::View* anchor_view, +@@ -208,10 +217,11 @@ views::Widget* VerticalTabStripController::ShowGroupEditorBubble( + /*stop_context_menu_propagation=*/stop_context_menu_propagation); + } + +-bool VerticalTabStripController::IsCollapsed() { ++bool VerticalTabStripController::IsCollapsed() const { + tabs::VerticalTabStripStateController* state_controller = + tabs::VerticalTabStripStateController::From(browser_view_->browser()); +- return state_controller && state_controller->IsCollapsed(); ++ return state_controller && state_controller->IsCollapsed() && ++ !state_controller->IsHoverExpanded(); + } + + bool VerticalTabStripController::IsContextMenuCommandChecked( diff --git a/patches/helium/ui/layout/vertical-hover-overlay-setting.patch b/patches/helium/ui/layout/vertical-hover-overlay-setting.patch new file mode 100644 index 000000000..260f80e4a --- /dev/null +++ b/patches/helium/ui/layout/vertical-hover-overlay-setting.patch @@ -0,0 +1,190 @@ +--- a/chrome/common/pref_names.h ++++ b/chrome/common/pref_names.h +@@ -1328,6 +1328,10 @@ + inline constexpr char kHeliumVerticalUncollapsedWidth[] = + "helium.browser.vertical_uncollapsed_width"; + ++// A boolean pref set to true if collapsed vertical tabs expand on hover. ++inline constexpr char kHeliumVerticalHoverExpandEnabled[] = ++ "helium.browser.vertical_hover_expand_enabled"; ++ + // A boolean pref set to true if a Home button to open the Home pages should be + // visible on the toolbar. + inline constexpr char kShowHomeButton[] = "browser.show_home_button"; +--- a/chrome/browser/ui/tabs/tab_strip_prefs.cc ++++ b/chrome/browser/ui/tabs/tab_strip_prefs.cc +@@ -44,6 +44,8 @@ + registry->RegisterIntegerPref( + prefs::kHeliumVerticalUncollapsedWidth, + kVerticalTabStripDefaultUncollapsedWidth); ++ registry->RegisterBooleanPref(prefs::kHeliumVerticalHoverExpandEnabled, ++ true); + } + + TabSearchPosition GetTabSearchPosition(const Profile* profile) { +--- a/chrome/browser/extensions/api/settings_private/prefs_util.cc ++++ b/chrome/browser/extensions/api/settings_private/prefs_util.cc +@@ -185,6 +185,8 @@ + settings_api::PrefType::kNumber; + (*s_allowlist)[::prefs::kHeliumVerticalRightAligned] = + settings_api::PrefType::kBoolean; ++ (*s_allowlist)[::prefs::kHeliumVerticalHoverExpandEnabled] = ++ settings_api::PrefType::kBoolean; + + // Miscellaneous + (*s_allowlist)[::embedder_support::kAlternateErrorPagesEnabled] = +--- a/chrome/browser/ui/tabs/vertical_tab_strip_state_controller.h ++++ b/chrome/browser/ui/tabs/vertical_tab_strip_state_controller.h +@@ -74,6 +74,8 @@ + // Update the Collapse Button's Action Item (kActionToggleCollapseVertical) + // based on the Vertical Tab Strip's Collapse State. + void UpdateCollapseActionItem(); ++ bool IsHoverExpandEnabled() const; ++ void OnHoverExpandEnabledPrefChanged(); + void StartHoverExpandTimerIfNeeded(); + void OnHoverExpandDelayElapsed(); + +--- a/chrome/browser/ui/tabs/vertical_tab_strip_state_controller.cc ++++ b/chrome/browser/ui/tabs/vertical_tab_strip_state_controller.cc +@@ -48,6 +48,11 @@ + {prefs::kHeliumLayout, prefs::kHeliumVerticalRightAligned}, + base::BindRepeating(&VerticalTabStripStateController::NotifyStateChanged, + base::Unretained(this))); ++ pref_change_registrar_.Add( ++ prefs::kHeliumVerticalHoverExpandEnabled, ++ base::BindRepeating( ++ &VerticalTabStripStateController::OnHoverExpandEnabledPrefChanged, ++ base::Unretained(this))); + + state_.collapsed = + pref_service_->GetBoolean(prefs::kHeliumVerticalCollapsed); +@@ -106,7 +111,7 @@ + } + + bool VerticalTabStripStateController::IsHoverExpanded() const { +- return state_.collapsed && held_by_hover_; ++ return state_.collapsed && held_by_hover_ && IsHoverExpandEnabled(); + } + + void VerticalTabStripStateController::SetHovered(bool hovered) { +@@ -116,6 +121,9 @@ + is_hovered_ = hovered; + + if (is_hovered_) { ++ if (!IsHoverExpandEnabled()) { ++ return; ++ } + StartHoverExpandTimerIfNeeded(); + return; + } +@@ -194,8 +202,25 @@ + } + } + ++bool VerticalTabStripStateController::IsHoverExpandEnabled() const { ++ return pref_service_->GetBoolean(prefs::kHeliumVerticalHoverExpandEnabled); ++} ++ ++void VerticalTabStripStateController::OnHoverExpandEnabledPrefChanged() { ++ if (!IsHoverExpandEnabled()) { ++ hover_expand_timer_.Stop(); ++ if (held_by_hover_) { ++ held_by_hover_ = false; ++ NotifyStateChanged(); ++ } ++ return; ++ } ++ StartHoverExpandTimerIfNeeded(); ++} ++ + void VerticalTabStripStateController::StartHoverExpandTimerIfNeeded() { +- if (!state_.collapsed || !is_hovered_ || held_by_hover_ || ++ if (!IsHoverExpandEnabled() || !state_.collapsed || !is_hovered_ || ++ held_by_hover_ || + hover_expand_timer_.IsRunning()) { + return; + } +--- a/chrome/browser/ui/tabs/vertical_tab_strip_state_controller_unittest.cc ++++ b/chrome/browser/ui/tabs/vertical_tab_strip_state_controller_unittest.cc +@@ -48,6 +48,9 @@ + prefs::kHeliumVerticalUncollapsedWidth, + kVerticalTabStripDefaultUncollapsedWidth, + user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); ++ pref_service_.registry()->RegisterBooleanPref( ++ prefs::kHeliumVerticalHoverExpandEnabled, true, ++ user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); + SessionID test_session_id = SessionID::FromSerializedValue(kSessionIDValue); + + EXPECT_CALL(mock_browser_window_interface_, GetUnownedUserDataHost) +@@ -188,4 +191,21 @@ + EXPECT_EQ(3, call_count); + } + ++TEST_F(VerticalTabStripStateControllerTest, HoverExpandedDisabledByPref) { ++ controller()->SetCollapsed(true); ++ controller()->SetHovered(true); ++ task_environment_.FastForwardBy(base::Milliseconds(500)); ++ EXPECT_FALSE(controller()->IsHoverExpanded()); ++} ++ ++TEST_F(VerticalTabStripStateControllerTest, DisablingPrefClearsHoverExpanded) { ++ controller()->SetCollapsed(true); ++ controller()->SetHovered(true); ++ task_environment_.FastForwardBy(base::Milliseconds(500)); ++ EXPECT_TRUE(controller()->IsHoverExpanded()); ++ ++ pref_service()->SetBoolean(prefs::kHeliumVerticalHoverExpandEnabled, false); ++ EXPECT_FALSE(controller()->IsHoverExpanded()); ++} ++ + } // namespace tabs +--- a/chrome/app/settings_strings.grdp ++++ b/chrome/app/settings_strings.grdp +@@ -291,6 +291,9 @@ + + Show vertical tabs on right side + ++ ++ Expand collapsed vertical tabs on hover ++ + + Show home button + +--- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc ++++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc +@@ -530,6 +530,7 @@ + {"browserLayoutCompact", IDS_SETTINGS_BROWSER_LAYOUT_COMPACT}, + {"browserLayoutVertical", IDS_SETTINGS_BROWSER_LAYOUT_VERTICAL}, + {"tabStripRightAlign", IDS_SETTINGS_TAB_STRIP_RIGHT_ALIGN}, ++ {"tabStripHoverExpand", IDS_SETTINGS_TAB_STRIP_HOVER_EXPAND}, + {"showHomeButton", IDS_SETTINGS_SHOW_HOME_BUTTON}, + {"showBookmarksBar", IDS_SETTINGS_SHOW_BOOKMARKS_BAR}, + {"tabStripPosition", IDS_SETTINGS_TAB_STRIP_POSITION}, +--- a/chrome/browser/resources/settings/appearance_page/appearance_page.ts ++++ b/chrome/browser/resources/settings/appearance_page/appearance_page.ts +@@ -474,6 +474,10 @@ + return this.showVerticalTabsEnabled_ && layout === BrowserLayout.VERTICAL; + } + ++ private showVerticalHoverExpansionSetting_(layout: number|undefined): boolean { ++ return this.showVerticalTabsEnabled_ && layout === BrowserLayout.VERTICAL; ++ } ++ + private themeChanged_(themeId: string) { + if (this.prefs === undefined || this.systemTheme_ === undefined) { + return; +--- a/chrome/browser/resources/settings/appearance_page/appearance_page.html ++++ b/chrome/browser/resources/settings/appearance_page/appearance_page.html +@@ -114,6 +114,12 @@ + pref="{{prefs.helium.browser.vertical_right_aligned}}" + label="$i18n{tabStripRightAlign}"> + ++ + + +
pref_service_; + PrefChangeRegistrar pref_change_registrar_; + raw_ptr root_action_item_; + + VerticalTabStripState state_; ++ bool is_hovered_ = false; ++ bool held_by_hover_ = false; ++ base::OneShotTimer hover_expand_timer_; + + base::RepeatingCallbackList + on_state_changed_callback_list_; +--- a/chrome/browser/ui/tabs/vertical_tab_strip_state_controller.cc ++++ b/chrome/browser/ui/tabs/vertical_tab_strip_state_controller.cc +@@ -6,6 +6,8 @@ + + #include + ++#include "base/functional/bind.h" ++#include "base/time/time.h" + #include "chrome/browser/profiles/profile.h" + #include "chrome/browser/ui/actions/chrome_action_id.h" + #include "chrome/browser/ui/browser_actions.h" +@@ -24,6 +26,10 @@ + + DEFINE_USER_DATA(VerticalTabStripStateController); + ++namespace { ++constexpr base::TimeDelta kHoverExpandDelay = base::Milliseconds(400); ++} // namespace ++ + VerticalTabStripStateController::VerticalTabStripStateController( + BrowserWindowInterface* browser_window, + PrefService* pref_service, +@@ -88,6 +94,35 @@ bool VerticalTabStripStateController::IsCollapsed() const { + void VerticalTabStripStateController::SetCollapsed(bool collapsed) { + if (state_.collapsed != collapsed) { + state_.collapsed = collapsed; ++ if (!state_.collapsed) { ++ held_by_hover_ = false; ++ hover_expand_timer_.Stop(); ++ } else { ++ held_by_hover_ = false; ++ StartHoverExpandTimerIfNeeded(); ++ } ++ NotifyStateChanged(); ++ } ++} ++ ++bool VerticalTabStripStateController::IsHoverExpanded() const { ++ return state_.collapsed && held_by_hover_; ++} ++ ++void VerticalTabStripStateController::SetHovered(bool hovered) { ++ if (is_hovered_ == hovered) { ++ return; ++ } ++ is_hovered_ = hovered; ++ ++ if (is_hovered_) { ++ StartHoverExpandTimerIfNeeded(); ++ return; ++ } ++ ++ hover_expand_timer_.Stop(); ++ if (held_by_hover_) { ++ held_by_hover_ = false; + NotifyStateChanged(); + } + } +@@ -108,6 +143,13 @@ void VerticalTabStripStateController::SetState( + if (state_.collapsed != state.collapsed || + state_.uncollapsed_width != state.uncollapsed_width) { + state_ = state; ++ if (!state_.collapsed) { ++ held_by_hover_ = false; ++ hover_expand_timer_.Stop(); ++ } else { ++ held_by_hover_ = false; ++ StartHoverExpandTimerIfNeeded(); ++ } + NotifyStateChanged(); + } + } +@@ -152,4 +194,24 @@ void VerticalTabStripStateController::UpdateCollapseActionItem() { + } + } + ++void VerticalTabStripStateController::StartHoverExpandTimerIfNeeded() { ++ if (!state_.collapsed || !is_hovered_ || held_by_hover_ || ++ hover_expand_timer_.IsRunning()) { ++ return; ++ } ++ hover_expand_timer_.Start( ++ FROM_HERE, kHoverExpandDelay, ++ base::BindOnce( ++ &VerticalTabStripStateController::OnHoverExpandDelayElapsed, ++ base::Unretained(this))); ++} ++ ++void VerticalTabStripStateController::OnHoverExpandDelayElapsed() { ++ if (!state_.collapsed || !is_hovered_ || held_by_hover_) { ++ return; ++ } ++ held_by_hover_ = true; ++ NotifyStateChanged(); ++} ++ + } // namespace tabs +--- a/chrome/browser/ui/tabs/vertical_tab_strip_state_controller_unittest.cc ++++ b/chrome/browser/ui/tabs/vertical_tab_strip_state_controller_unittest.cc +@@ -5,7 +5,11 @@ + #include "chrome/browser/ui/tabs/vertical_tab_strip_state_controller.h" + + #include ++#include + ++#include "base/test/task_environment.h" ++#include "base/time/time.h" ++#include "chrome/browser/ui/helium/helium_layout_state_controller.h" + #include "chrome/browser/ui/browser_window/test/mock_browser_window_interface.h" + #include "chrome/browser/ui/tabs/vertical_tab_strip_state.h" + #include "chrome/common/pref_names.h" +@@ -30,8 +34,19 @@ class VerticalTabStripStateControllerTest : public testing::Test { + + void SetUp() override { + testing::Test::SetUp(); ++ pref_service_.registry()->RegisterIntegerPref( ++ prefs::kHeliumLayout, ++ std::to_underlying(HeliumLayoutType::kHorizontal), ++ user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); ++ pref_service_.registry()->RegisterBooleanPref( ++ prefs::kHeliumVerticalRightAligned, false, ++ user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); + pref_service_.registry()->RegisterBooleanPref( +- prefs::kVerticalTabsEnabled, false, ++ prefs::kHeliumVerticalCollapsed, false, ++ user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); ++ pref_service_.registry()->RegisterIntegerPref( ++ prefs::kHeliumVerticalUncollapsedWidth, ++ kVerticalTabStripDefaultUncollapsedWidth, + user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); + SessionID test_session_id = SessionID::FromSerializedValue(kSessionIDValue); + +@@ -58,6 +73,8 @@ class VerticalTabStripStateControllerTest : public testing::Test { + } + + private: ++ base::test::TaskEnvironment task_environment_{ ++ base::test::TaskEnvironment::TimeSource::MOCK_TIME}; + std::unique_ptr controller_; + sync_preferences::TestingPrefServiceSyncable pref_service_; + ui::UnownedUserDataHost unowned_user_data_host_; +@@ -74,11 +91,13 @@ TEST_F(VerticalTabStripStateControllerTest, Initial) { + TEST_F(VerticalTabStripStateControllerTest, VerticalTabsEnabled) { + controller()->SetVerticalTabsEnabled(true); + EXPECT_TRUE(controller()->ShouldDisplayVerticalTabs()); +- EXPECT_TRUE(pref_service()->GetBoolean(prefs::kVerticalTabsEnabled)); ++ EXPECT_EQ(std::to_underlying(HeliumLayoutType::kVertical), ++ pref_service()->GetInteger(prefs::kHeliumLayout)); + + controller()->SetVerticalTabsEnabled(false); + EXPECT_FALSE(controller()->ShouldDisplayVerticalTabs()); +- EXPECT_FALSE(pref_service()->GetBoolean(prefs::kVerticalTabsEnabled)); ++ EXPECT_EQ(std::to_underlying(HeliumLayoutType::kHorizontal), ++ pref_service()->GetInteger(prefs::kHeliumLayout)); + } + + TEST_F(VerticalTabStripStateControllerTest, Collapsed) { +@@ -141,4 +160,32 @@ TEST_F(VerticalTabStripStateControllerTest, State) { + EXPECT_EQ(1, call_count); + } + ++TEST_F(VerticalTabStripStateControllerTest, HoverExpanded) { ++ int call_count = 0; ++ auto subscription = controller()->RegisterOnStateChanged(base::BindRepeating( ++ [](int* call_count, VerticalTabStripStateController* controller) { ++ (*call_count)++; ++ if (*call_count == 2) { ++ EXPECT_TRUE(controller->IsHoverExpanded()); ++ } ++ }, ++ &call_count)); ++ ++ controller()->SetCollapsed(true); ++ EXPECT_FALSE(controller()->IsHoverExpanded()); ++ ++ controller()->SetHovered(true); ++ task_environment_.FastForwardBy(base::Milliseconds(399)); ++ EXPECT_FALSE(controller()->IsHoverExpanded()); ++ EXPECT_EQ(1, call_count); ++ ++ task_environment_.FastForwardBy(base::Milliseconds(1)); ++ EXPECT_TRUE(controller()->IsHoverExpanded()); ++ EXPECT_EQ(2, call_count); ++ ++ controller()->SetHovered(false); ++ EXPECT_FALSE(controller()->IsHoverExpanded()); ++ EXPECT_EQ(3, call_count); ++} ++ + } // namespace tabs diff --git a/patches/series b/patches/series index 446d0ed50..139bdaf43 100644 --- a/patches/series +++ b/patches/series @@ -69,6 +69,8 @@ ungoogled-chromium/disable-webstore-urls.patch ungoogled-chromium/fix-learn-doubleclick-hsts.patch ungoogled-chromium/disable-webrtc-log-uploader.patch ungoogled-chromium/fix-building-with-prunned-binaries.patch +upstream-fixes/autofill-async-context-model-predictions.patch +upstream-fixes/passage-embeddings-forward-decl.patch ungoogled-chromium/disable-network-time-tracker.patch ungoogled-chromium/disable-mei-preload.patch ungoogled-chromium/fix-building-without-safebrowsing.patch @@ -320,6 +322,11 @@ helium/ui/layout/settings.patch helium/ui/layout/context-menu.patch helium/ui/layout/compact.patch helium/ui/layout/vertical.patch +helium/ui/layout/vertical-hover-overlay.patch +helium/ui/layout/vertical-hover-overlay-callers.patch +helium/ui/layout/vertical-collapse-command.patch +helium/ui/layout/vertical-hover-overlay-setting.patch +helium/ui/layout/vertical-hide-tab-search-button.patch helium/ui/pdf-viewer.patch helium/ui/hide-pip-live-caption-button.patch diff --git a/patches/upstream-fixes/autofill-async-context-model-predictions.patch b/patches/upstream-fixes/autofill-async-context-model-predictions.patch new file mode 100644 index 000000000..7560d71f8 --- /dev/null +++ b/patches/upstream-fixes/autofill-async-context-model-predictions.patch @@ -0,0 +1,11 @@ +--- a/components/autofill/core/browser/foundations/autofill_manager.cc ++++ b/components/autofill/core/browser/foundations/autofill_manager.cc +@@ -136,6 +136,8 @@ struct AutofillManager::AsyncContext { + + std::vector forms; + std::vector regex_predictions; ++ std::vector autofill_predictions; ++ std::vector password_manager_predictions; + GeoIpCountryCode country_code; + LanguageCode current_page_language; + std::unique_ptr log_manager; diff --git a/patches/upstream-fixes/passage-embeddings-forward-decl.patch b/patches/upstream-fixes/passage-embeddings-forward-decl.patch new file mode 100644 index 000000000..ba90a35b3 --- /dev/null +++ b/patches/upstream-fixes/passage-embeddings-forward-decl.patch @@ -0,0 +1,11 @@ +--- a/services/passage_embeddings/passage_embeddings_service.h ++++ b/services/passage_embeddings/passage_embeddings_service.h +@@ -11,6 +11,8 @@ + + namespace passage_embeddings { + ++class PassageEmbedder; ++ + + // Class implementation of the passage embeddings service mojo interface. + class PassageEmbeddingsService : public mojom::PassageEmbeddingsService { From ea737bcadc453ef91dabcb1bda262983fa70d502 Mon Sep 17 00:00:00 2001 From: dusan-nikcevic <168220721+dusan-nikcevic@users.noreply.github.com> Date: Fri, 27 Feb 2026 01:19:00 +0100 Subject: [PATCH 3/6] Add vertical command launcher patch --- .../ui/layout/vertical-command-launcher.patch | 878 ++++++++++++++++++ patches/series | 1 + 2 files changed, 879 insertions(+) create mode 100644 patches/helium/ui/layout/vertical-command-launcher.patch diff --git a/patches/helium/ui/layout/vertical-command-launcher.patch b/patches/helium/ui/layout/vertical-command-launcher.patch new file mode 100644 index 000000000..6589970e7 --- /dev/null +++ b/patches/helium/ui/layout/vertical-command-launcher.patch @@ -0,0 +1,878 @@ +--- a/chrome/app/chrome_command_ids.h ++++ b/chrome/app/chrome_command_ids.h +@@ -100,6 +100,7 @@ + #define IDC_BROWSER_LAYOUT_VERTICAL 34084 + #define IDC_BROWSER_LAYOUT_VERTICAL_RIGHT 34085 + #define IDC_TOGGLE_VERTICAL_TAB_STRIP_COLLAPSED 34086 ++#define IDC_HELIUM_COMMAND_LAUNCHER 34087 + + // Tab group Commands + #define IDC_ADD_NEW_TAB_TO_GROUP 34100 +--- a/chrome/browser/ui/browser_commands.h ++++ b/chrome/browser/ui/browser_commands.h +@@ -258,6 +258,7 @@ + void FindPrevious(Browser* browser); + void FindInPage(Browser* browser, bool find_next, bool forward_direction); + void ShowTabSearch(BrowserWindowInterface* bwi); ++void ShowCommandLauncher(BrowserWindowInterface* bwi); + void CloseTabSearch(Browser* browser); + void ToggleContextualTasksSidePanel(BrowserWindowInterface* browser); + void ToggleVerticalTabs(Browser* browser); +--- a/chrome/browser/ui/browser_commands.cc ++++ b/chrome/browser/ui/browser_commands.cc +@@ -2199,6 +2199,12 @@ + tab_search::mojom::TabOrganizationFeature::kNone); + } + ++void ShowCommandLauncher(BrowserWindowInterface* bwi) { ++ bwi->GetBrowserForMigrationOnly()->window()->CreateTabSearchBubble( ++ tab_search::mojom::TabSearchSection::kCommand, ++ tab_search::mojom::TabOrganizationFeature::kNone); ++} ++ + void CloseTabSearch(Browser* browser) { + browser->window()->CloseTabSearchBubble(); + } +--- a/chrome/browser/ui/browser_command_controller.cc ++++ b/chrome/browser/ui/browser_command_controller.cc +@@ -415,7 +415,8 @@ + command_id == IDC_SELECT_NEXT_TAB || + command_id == IDC_SELECT_PREVIOUS_TAB || command_id == IDC_EXIT || + command_id == IDC_COPY_OR_INSPECT_SHORTCUT || command_id == IDC_DEV_TOOLS || +- command_id == IDC_DEV_TOOLS_TOGGLE || command_id == IDC_TAB_SEARCH; ++ command_id == IDC_DEV_TOOLS_TOGGLE || command_id == IDC_TAB_SEARCH || ++ command_id == IDC_HELIUM_COMMAND_LAUNCHER; + } + + void BrowserCommandController::TabStateChanged() { +@@ -587,6 +588,9 @@ + case IDC_TAB_SEARCH: + ShowTabSearch(browser_); + break; ++ case IDC_HELIUM_COMMAND_LAUNCHER: ++ ShowCommandLauncher(browser_); ++ break; + case IDC_TAB_SEARCH_CLOSE: + CloseTabSearch(browser_); + break; +@@ -1747,6 +1751,8 @@ + const bool enable_tab_search_commands = browser_->is_type_normal(); + command_updater_.UpdateCommandEnabled(IDC_TAB_SEARCH, + enable_tab_search_commands); ++ command_updater_.UpdateCommandEnabled(IDC_HELIUM_COMMAND_LAUNCHER, ++ enable_tab_search_commands); + command_updater_.UpdateCommandEnabled(IDC_TAB_SEARCH_CLOSE, + enable_tab_search_commands); + +--- a/chrome/browser/ui/accelerator_table.cc ++++ b/chrome/browser/ui/accelerator_table.cc +@@ -61,6 +61,8 @@ + {ui::VKEY_F, ui::EF_PLATFORM_ACCELERATOR, IDC_FIND}, + {ui::VKEY_A, ui::EF_SHIFT_DOWN | ui::EF_PLATFORM_ACCELERATOR, + IDC_TAB_SEARCH}, ++ {ui::VKEY_K, ui::EF_SHIFT_DOWN | ui::EF_PLATFORM_ACCELERATOR, ++ IDC_HELIUM_COMMAND_LAUNCHER}, + {ui::VKEY_V, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN, + IDC_TOGGLE_VERTICAL_TAB_STRIP_COLLAPSED}, + {ui::VKEY_G, ui::EF_PLATFORM_ACCELERATOR, IDC_FIND_NEXT}, +--- a/chrome/browser/ui/cocoa/accelerators_cocoa.mm ++++ b/chrome/browser/ui/cocoa/accelerators_cocoa.mm +@@ -40,6 +40,8 @@ + {IDC_DEV_TOOLS_INSPECT, ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN, ui::VKEY_E}, + {IDC_COPY_OR_INSPECT_SHORTCUT, ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN, ui::VKEY_C}, + {IDC_NEW_SPLIT_TAB, ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN, ui::VKEY_N}, ++ {IDC_HELIUM_COMMAND_LAUNCHER, ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN, ++ ui::VKEY_K}, + {IDC_TOGGLE_VERTICAL_TAB_STRIP_COLLAPSED, + ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN, ui::VKEY_V}, + {IDC_FIND, ui::EF_COMMAND_DOWN, ui::VKEY_F}, +--- a/chrome/browser/ui/webui/tab_search/tab_search.mojom ++++ b/chrome/browser/ui/webui/tab_search/tab_search.mojom +@@ -46,6 +46,7 @@ + [Default] kNone = 0, + kSearch = 1, + kOrganize = 2, ++ kCommand = 3, + }; + + //TODO(b/311697865) move this to a common location. +@@ -197,6 +198,12 @@ + int32 tab_id; + }; + ++struct LauncherCommand { ++ int32 command_id; ++ string title; ++ bool enabled; ++}; ++ + struct TabOrganization { + int32 organization_id; + array tabs; +@@ -281,6 +288,12 @@ + // Get window and tab data for the current profile. + GetProfileData() => (ProfileData profile_data); + ++ // Get command launcher actions available in the current browser context. ++ GetLauncherCommands() => (array commands); ++ ++ // Execute a command launcher action by command id. ++ ExecuteLauncherCommand(int32 command_id) => (bool executed); ++ + // Get all tabs that are considered stale based on their last active time, + // and tabs that are duplicates of other tabs based on partial URL match + // (excludes fragments). +--- a/chrome/browser/ui/webui/tab_search/tab_search_page_handler.h ++++ b/chrome/browser/ui/webui/tab_search/tab_search_page_handler.h +@@ -108,6 +108,9 @@ + void ExcludeFromStaleTabs(int32_t tab_id) override; + void ExcludeFromDuplicateTabs(const GURL& url) override; + void GetProfileData(GetProfileDataCallback callback) override; ++ void GetLauncherCommands(GetLauncherCommandsCallback callback) override; ++ void ExecuteLauncherCommand(int32_t command_id, ++ ExecuteLauncherCommandCallback callback) override; + void GetUnusedTabs(GetUnusedTabsCallback callback) override; + void GetTabSearchSection(GetTabSearchSectionCallback callback) override; + void GetTabOrganizationFeature( +--- a/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc ++++ b/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc +@@ -15,6 +15,7 @@ + #include + + #include "base/base64.h" ++#include "base/containers/contains.h" + #include "base/feature_list.h" + #include "base/functional/bind.h" + #include "base/metrics/histogram_functions.h" +@@ -23,6 +24,7 @@ + #include "base/timer/timer.h" + #include "base/trace_event/trace_event.h" + #include "base/values.h" ++#include "chrome/app/chrome_command_ids.h" + #include "chrome/browser/favicon/favicon_utils.h" + #include "chrome/browser/feedback/show_feedback_page.h" + #include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h" +@@ -33,6 +35,7 @@ + #include "chrome/browser/signin/signin_error_controller_factory.h" + #include "chrome/browser/signin/signin_ui_util.h" + #include "chrome/browser/ui/browser.h" ++#include "chrome/browser/ui/browser_commands.h" + #include "chrome/browser/ui/browser_live_tab_context.h" + #include "chrome/browser/ui/browser_navigator.h" + #include "chrome/browser/ui/browser_window.h" +@@ -77,6 +80,49 @@ + + namespace { + constexpr base::TimeDelta kTabsChangeDelay = base::Milliseconds(50); ++constexpr int kLauncherCommandIds[] = { ++ IDC_NEW_TAB, ++ IDC_NEW_WINDOW, ++ IDC_NEW_INCOGNITO_WINDOW, ++ IDC_RESTORE_TAB, ++ IDC_DUPLICATE_TAB, ++ IDC_BOOKMARK_THIS_TAB, ++ IDC_SHOW_HISTORY, ++ IDC_SHOW_DOWNLOADS, ++ IDC_OPTIONS, ++ IDC_TOGGLE_VERTICAL_TAB_STRIP_COLLAPSED, ++}; ++ ++bool IsSupportedLauncherCommand(int command_id) { ++ return base::Contains(kLauncherCommandIds, command_id); ++} ++ ++std::string GetLauncherCommandTitle(int command_id) { ++ switch (command_id) { ++ case IDC_NEW_TAB: ++ return l10n_util::GetStringUTF8(IDS_NEW_TAB); ++ case IDC_NEW_WINDOW: ++ return l10n_util::GetStringUTF8(IDS_NEW_WINDOW); ++ case IDC_NEW_INCOGNITO_WINDOW: ++ return l10n_util::GetStringUTF8(IDS_NEW_INCOGNITO_WINDOW); ++ case IDC_RESTORE_TAB: ++ return l10n_util::GetStringUTF8(IDS_RESTORE_TAB); ++ case IDC_DUPLICATE_TAB: ++ return l10n_util::GetStringUTF8(IDS_TAB_CXMENU_DUPLICATE); ++ case IDC_BOOKMARK_THIS_TAB: ++ return l10n_util::GetStringUTF8(IDS_BOOKMARK_THIS_TAB); ++ case IDC_SHOW_HISTORY: ++ return l10n_util::GetStringUTF8(IDS_HISTORY_SHOW_HISTORY); ++ case IDC_SHOW_DOWNLOADS: ++ return l10n_util::GetStringUTF8(IDS_SHOW_DOWNLOADS); ++ case IDC_OPTIONS: ++ return l10n_util::GetStringUTF8(IDS_SETTINGS); ++ case IDC_TOGGLE_VERTICAL_TAB_STRIP_COLLAPSED: ++ return "Toggle Sidebar"; ++ default: ++ return ""; ++ } ++} + + std::string GetLastActiveElapsedText( + const base::TimeTicks& last_active_time_ticks) { +@@ -687,6 +733,37 @@ + std::move(callback).Run(std::move(profile_tabs)); + } + ++void TabSearchPageHandler::GetLauncherCommands( ++ GetLauncherCommandsCallback callback) { ++ std::vector commands; ++ if (!browser_) { ++ std::move(callback).Run(std::move(commands)); ++ return; ++ } ++ ++ for (const int command_id : kLauncherCommandIds) { ++ if (!chrome::SupportsCommand(browser_, command_id)) { ++ continue; ++ } ++ auto command = tab_search::mojom::LauncherCommand::New(); ++ command->command_id = command_id; ++ command->title = GetLauncherCommandTitle(command_id); ++ command->enabled = chrome::IsCommandEnabled(browser_, command_id); ++ commands.push_back(std::move(command)); ++ } ++ ++ std::move(callback).Run(std::move(commands)); ++} ++ ++void TabSearchPageHandler::ExecuteLauncherCommand( ++ int32_t command_id, ++ ExecuteLauncherCommandCallback callback) { ++ const bool executed = browser_ && IsSupportedLauncherCommand(command_id) && ++ chrome::SupportsCommand(browser_, command_id) && ++ chrome::ExecuteCommand(browser_, command_id); ++ std::move(callback).Run(executed); ++} ++ + void TabSearchPageHandler::GetUnusedTabs(GetUnusedTabsCallback callback) { + UpdateUnusedTabs(); + std::move(callback).Run(GetMojoUnusedTabs()); +--- a/chrome/browser/ui/webui/tab_search/tab_search_ui.cc ++++ b/chrome/browser/ui/webui/tab_search/tab_search_ui.cc +@@ -227,6 +227,15 @@ + ui::Accelerator accelerator(ui::VKEY_A, + ui::EF_SHIFT_DOWN | ui::EF_PLATFORM_ACCELERATOR); + source->AddString("shortcutText", accelerator.GetShortcutText()); ++ ui::Accelerator command_launcher_accelerator( ++ ui::VKEY_K, ui::EF_SHIFT_DOWN | ui::EF_PLATFORM_ACCELERATOR); ++ source->AddString("commandLauncherShortcutText", ++ command_launcher_accelerator.GetShortcutText()); ++ source->AddString("commandLauncherSearchPlaceholder", ++ "Search commands and tabs"); ++ source->AddString("commandLauncherCommandsTitle", "Commands"); ++ source->AddString("commandLauncherTabsTitle", "Tabs"); ++ source->AddString("commandLauncherActionSubtitle", "Action"); + // TODO(b/362269642): Once the stale threshold duration is Finch- + // configurable, replace the hardcoded 7 below with the value of that + // parameter. +--- a/chrome/browser/resources/tab_search/BUILD.gn ++++ b/chrome/browser/resources/tab_search/BUILD.gn +@@ -45,6 +45,8 @@ + "auto_tab_groups/auto_tab_groups_results.ts", + "auto_tab_groups/auto_tab_groups_results_actions.html.ts", + "auto_tab_groups/auto_tab_groups_results_actions.ts", ++ "command_launcher_page.html.ts", ++ "command_launcher_page.ts", + "declutter/declutter_page.html.ts", + "declutter/declutter_page.ts", + "lazy_list.ts", +@@ -84,6 +86,7 @@ + "auto_tab_groups/auto_tab_groups_results.css", + "auto_tab_groups/auto_tab_groups_results_actions.css", + "auto_tab_groups/auto_tab_groups_shared_style.css", ++ "command_launcher_page.css", + "declutter/declutter_page.css", + "lazy_list.css", + "selectable_lazy_list.css", +--- a/chrome/browser/resources/tab_search/app.ts ++++ b/chrome/browser/resources/tab_search/app.ts +@@ -3,6 +3,7 @@ + // found in the LICENSE file. + + import './auto_tab_groups/auto_tab_groups_page.js'; ++import './command_launcher_page.js'; + import './declutter/declutter_page.js'; + import './tab_organization_selector.js'; + import './tab_search_page.js'; +@@ -113,9 +114,14 @@ + this.tabOrganizationEnabled_ = enabled; + } + ++ protected isCommandLauncherMode_(): boolean { ++ return this.selectedTabSection_ === TabSearchSection.kCommand; ++ } ++ + protected sectionToIndex_(section: TabSearchSection): number { + switch (section) { + case TabSearchSection.kNone: ++ case TabSearchSection.kCommand: + return -1; + case TabSearchSection.kSearch: + return 0; +--- a/chrome/browser/resources/tab_search/app.html.ts ++++ b/chrome/browser/resources/tab_search/app.html.ts +@@ -9,7 +9,10 @@ + export function getHtml(this: TabSearchAppElement) { + // clang-format off + return html` +-${(this.tabOrganizationEnabled_ || this.declutterEnabled_) ? html` ++${this.isCommandLauncherMode_() ? html` ++ ++ ++` : (this.tabOrganizationEnabled_ || this.declutterEnabled_) ? html` + ; + ++ getLauncherCommands(): Promise<{commands: LauncherCommand[]}>; ++ ++ executeLauncherCommand(commandId: number): Promise<{executed: boolean}>; ++ + getUnusedTabs(): Promise<{tabs: UnusedTabInfo}>; + + getTabSearchSection(): Promise<{section: TabSearchSection}>; +@@ -140,6 +144,14 @@ + return this.handler.getProfileData(); + } + ++ getLauncherCommands() { ++ return this.handler.getLauncherCommands(); ++ } ++ ++ executeLauncherCommand(commandId: number) { ++ return this.handler.executeLauncherCommand(commandId); ++ } ++ + getUnusedTabs() { + return this.handler.getUnusedTabs(); + } +--- a/chrome/browser/resources/tab_search/command_launcher_page.ts ++++ b/chrome/browser/resources/tab_search/command_launcher_page.ts +@@ -0,0 +1,342 @@ ++// Copyright 2026 The Chromium Authors ++// Use of this source code is governed by a BSD-style license that can be ++// found in the LICENSE file. ++ ++import '/strings.m.js'; ++import 'chrome://resources/cr_elements/cr_icon/cr_icon.js'; ++ ++import {loadTimeData} from 'chrome://resources/js/load_time_data.js'; ++import {CrLitElement} from 'chrome://resources/lit/v3_0/lit.rollup.js'; ++import type {PropertyValues} from 'chrome://resources/lit/v3_0/lit.rollup.js'; ++ ++import type {LauncherCommand, ProfileData, Tab} from './tab_search.mojom-webui.js'; ++import type {TabSearchApiProxy} from './tab_search_api_proxy.js'; ++import {TabSearchApiProxyImpl} from './tab_search_api_proxy.js'; ++import {getCss} from './command_launcher_page.css.js'; ++import {getHtml} from './command_launcher_page.html.js'; ++ ++type HeadingItem = { ++ kind: 'heading', ++ title: string, ++}; ++ ++type CommandItem = { ++ kind: 'command', ++ command: LauncherCommand, ++ score: number, ++}; ++ ++type TabItem = { ++ kind: 'tab', ++ tab: Tab, ++ inActiveWindow: boolean, ++ score: number, ++}; ++ ++type DisplayItem = HeadingItem|CommandItem|TabItem; ++ ++type OpenTab = { ++ tab: Tab, ++ inActiveWindow: boolean, ++}; ++ ++function isSelectable(item: DisplayItem): item is CommandItem|TabItem { ++ return item.kind === 'command' || item.kind === 'tab'; ++} ++ ++function safeHost(url: string): string { ++ try { ++ return new URL(url).hostname; ++ } catch { ++ return ''; ++ } ++} ++ ++function scoreText(query: string, value: string): number { ++ if (!query) { ++ return 1; ++ } ++ ++ const text = value.toLowerCase(); ++ const idx = text.indexOf(query); ++ if (idx === -1) { ++ return 0; ++ } ++ ++ if (idx === 0) { ++ return 300 - query.length; ++ } ++ ++ const charBefore = text[idx - 1] || ''; ++ const isBoundary = /[\s/_.:-]/.test(charBefore); ++ return (isBoundary ? 220 : 120) - idx; ++} ++ ++function scoreCommand(query: string, command: LauncherCommand): number { ++ return scoreText(query, command.title); ++} ++ ++function scoreTab(query: string, tab: Tab): number { ++ if (!query) { ++ return 1; ++ } ++ ++ const titleScore = scoreText(query, tab.title) * 2; ++ const hostScore = scoreText(query, safeHost(tab.url.url)); ++ return Math.max(titleScore, hostScore); ++} ++ ++function compareTabsByActivity(a: Tab, b: Tab): number { ++ const aValue = Number(a.lastActiveTimeTicks.internalValue); ++ const bValue = Number(b.lastActiveTimeTicks.internalValue); ++ return bValue - aValue; ++} ++ ++export class CommandLauncherPageElement extends CrLitElement { ++ static get is() { ++ return 'command-launcher-page'; ++ } ++ ++ static override get properties() { ++ return { ++ availableHeight: {type: Number}, ++ query_: {type: String}, ++ displayItems_: {type: Array}, ++ selectedDisplayIndex_: {type: Number}, ++ shortcut_: {type: String}, ++ }; ++ } ++ ++ accessor availableHeight: number|undefined; ++ protected accessor query_: string = ''; ++ protected accessor displayItems_: DisplayItem[] = []; ++ protected accessor selectedDisplayIndex_: number = -1; ++ protected accessor shortcut_: string = ++ loadTimeData.getString('commandLauncherShortcutText'); ++ ++ private apiProxy_: TabSearchApiProxy = TabSearchApiProxyImpl.getInstance(); ++ private commands_: LauncherCommand[] = []; ++ private openTabs_: OpenTab[] = []; ++ ++ static override get styles() { ++ return getCss(); ++ } ++ ++ override render() { ++ return getHtml.bind(this)(); ++ } ++ ++ override connectedCallback() { ++ super.connectedCallback(); ++ this.refreshData_(); ++ } ++ ++ override updated(changedProperties: PropertyValues) { ++ super.updated(changedProperties); ++ ++ if (changedProperties.has('availableHeight')) { ++ this.requestUpdate(); ++ } ++ } ++ ++ protected getListMaxHeight_(): string { ++ const height = this.availableHeight ?? 440; ++ return `${Math.max(180, height - 78)}px`; ++ } ++ ++ protected onQueryInput_(e: Event) { ++ const target = e.target as HTMLInputElement; ++ this.query_ = target.value; ++ this.rebuildDisplayItems_(); ++ } ++ ++ protected async onSearchKeyDown_(e: KeyboardEvent) { ++ if (e.key === 'ArrowDown') { ++ this.moveSelection_(1); ++ e.preventDefault(); ++ return; ++ } ++ ++ if (e.key === 'ArrowUp') { ++ this.moveSelection_(-1); ++ e.preventDefault(); ++ return; ++ } ++ ++ if (e.key === 'Enter') { ++ e.preventDefault(); ++ await this.executeSelected_(); ++ return; ++ } ++ ++ if (e.key === 'Escape') { ++ e.preventDefault(); ++ window.close(); ++ } ++ } ++ ++ protected isSelected_(index: number): boolean { ++ return this.selectedDisplayIndex_ === index; ++ } ++ ++ protected onRowMouseEnter_(e: Event) { ++ const target = e.currentTarget as HTMLElement; ++ this.selectedDisplayIndex_ = Number(target.dataset['index']); ++ } ++ ++ protected async onRowClick_(e: Event) { ++ const target = e.currentTarget as HTMLElement; ++ const index = Number(target.dataset['index']); ++ this.selectedDisplayIndex_ = index; ++ await this.executeSelected_(); ++ } ++ ++ protected rowTitle_(item: CommandItem|TabItem): string { ++ return item.kind === 'command' ? item.command.title : item.tab.title; ++ } ++ ++ protected rowSubtitle_(item: CommandItem|TabItem): string { ++ if (item.kind === 'command') { ++ return loadTimeData.getString('commandLauncherActionSubtitle'); ++ } ++ const url = item.tab.url.url; ++ return safeHost(url) || url; ++ } ++ ++ private async refreshData_() { ++ const [{commands}, {profileData}] = await Promise.all([ ++ this.apiProxy_.getLauncherCommands(), ++ this.apiProxy_.getProfileData(), ++ ]); ++ this.commands_ = commands.filter(command => command.enabled); ++ this.openTabs_ = this.getOpenTabs_(profileData); ++ this.rebuildDisplayItems_(); ++ } ++ ++ private getOpenTabs_(profileData: ProfileData): OpenTab[] { ++ return profileData.windows.reduce((acc, window) => { ++ acc.push(...window.tabs.map(tab => ({tab, inActiveWindow: window.active}))); ++ return acc; ++ }, [] as OpenTab[]); ++ } ++ ++ private rebuildDisplayItems_() { ++ const query = this.query_.trim().toLowerCase(); ++ ++ const commandItems = this.commands_ ++ .map(command => ({ ++ kind: 'command' as const, ++ command, ++ score: scoreCommand(query, command), ++ })) ++ .filter(item => query ? item.score > 0 : true) ++ .sort((a, b) => { ++ if (b.score !== a.score) { ++ return b.score - a.score; ++ } ++ return a.command.title.localeCompare(b.command.title); ++ }) ++ .slice(0, 8); ++ ++ const tabItems = this.openTabs_ ++ .map(openTab => ({ ++ kind: 'tab' as const, ++ tab: openTab.tab, ++ inActiveWindow: openTab.inActiveWindow, ++ score: scoreTab(query, openTab.tab), ++ })) ++ .filter(item => query ? item.score > 0 : true) ++ .sort((a, b) => { ++ if (b.score !== a.score) { ++ return b.score - a.score; ++ } ++ return compareTabsByActivity(a.tab, b.tab); ++ }) ++ .slice(0, 8); ++ ++ const nextItems: DisplayItem[] = []; ++ if (commandItems.length > 0) { ++ nextItems.push({ ++ kind: 'heading', ++ title: loadTimeData.getString('commandLauncherCommandsTitle'), ++ }); ++ nextItems.push(...commandItems); ++ } ++ if (tabItems.length > 0) { ++ nextItems.push({ ++ kind: 'heading', ++ title: loadTimeData.getString('commandLauncherTabsTitle'), ++ }); ++ nextItems.push(...tabItems); ++ } ++ ++ this.displayItems_ = nextItems; ++ ++ if (!this.isSelectableIndex_(this.selectedDisplayIndex_)) { ++ this.selectedDisplayIndex_ = this.firstSelectableIndex_(); ++ } ++ } ++ ++ private isSelectableIndex_(index: number): boolean { ++ if (index < 0 || index >= this.displayItems_.length) { ++ return false; ++ } ++ return isSelectable(this.displayItems_[index]!); ++ } ++ ++ private firstSelectableIndex_(): number { ++ return this.displayItems_.findIndex(item => isSelectable(item)); ++ } ++ ++ private moveSelection_(delta: number) { ++ const selectableIndices = this.displayItems_ ++ .map((item, index) => ({item, index})) ++ .filter(({item}) => isSelectable(item)) ++ .map(({index}) => index); ++ if (selectableIndices.length === 0) { ++ this.selectedDisplayIndex_ = -1; ++ return; ++ } ++ ++ const currentPosition = selectableIndices.indexOf(this.selectedDisplayIndex_); ++ if (currentPosition === -1) { ++ this.selectedDisplayIndex_ = selectableIndices[0]!; ++ return; ++ } ++ ++ const nextPosition = ++ (currentPosition + delta + selectableIndices.length) % selectableIndices.length; ++ this.selectedDisplayIndex_ = selectableIndices[nextPosition]!; ++ } ++ ++ private async executeSelected_() { ++ if (!this.isSelectableIndex_(this.selectedDisplayIndex_)) { ++ return; ++ } ++ ++ const selected = this.displayItems_[this.selectedDisplayIndex_]!; ++ if (!isSelectable(selected)) { ++ return; ++ } ++ ++ if (selected.kind === 'command') { ++ const {executed} = ++ await this.apiProxy_.executeLauncherCommand(selected.command.commandId); ++ if (!executed) { ++ return; ++ } ++ } else { ++ this.apiProxy_.switchToTab({tabId: selected.tab.tabId}); ++ } ++ ++ window.close(); ++ } ++} ++ ++declare global { ++ interface HTMLElementTagNameMap { ++ 'command-launcher-page': CommandLauncherPageElement; ++ } ++} ++ ++customElements.define(CommandLauncherPageElement.is, CommandLauncherPageElement); +--- a/chrome/browser/resources/tab_search/command_launcher_page.html.ts ++++ b/chrome/browser/resources/tab_search/command_launcher_page.html.ts +@@ -0,0 +1,47 @@ ++// Copyright 2026 The Chromium Authors ++// Use of this source code is governed by a BSD-style license that can be ++// found in the LICENSE file. ++ ++import {html} from '//resources/lit/v3_0/lit.rollup.js'; ++ ++import type {CommandLauncherPageElement} from './command_launcher_page.js'; ++ ++export function getHtml(this: CommandLauncherPageElement) { ++ // clang-format off ++ return html` ++
++
++ ++
++ ++ ++
++
++
++
++ ${this.displayItems_.length === 0 ? html` ++
$i18n{noResultsFound}
++ ` : this.displayItems_.map((item, index) => { ++ if (item.kind === 'heading') { ++ return html`
${item.title}
`; ++ } ++ return html` ++ `; ++ })} ++
++
++`; ++ // clang-format on ++} +--- a/chrome/browser/resources/tab_search/command_launcher_page.css ++++ b/chrome/browser/resources/tab_search/command_launcher_page.css +@@ -0,0 +1,112 @@ ++/* Copyright 2026 The Chromium Authors ++ * Use of this source code is governed by a BSD-style license that can be ++ * found in the LICENSE file. */ ++ ++/* #css_wrapper_metadata_start ++ * #type=style-lit ++ * #scheme=relative ++ * #css_wrapper_metadata_end */ ++ ++:host { ++ color: var(--cr-primary-text-color); ++ display: block; ++ width: 100%; ++} ++ ++#commandLauncherPage { ++ box-sizing: border-box; ++ display: flex; ++ flex-direction: column; ++ width: 100%; ++} ++ ++#searchField { ++ align-items: center; ++ display: flex; ++ min-height: 56px; ++ padding: 8px 16px; ++} ++ ++#searchIcon { ++ --cr-icon-size: 18px; ++ color: var(--cr-secondary-text-color); ++ margin-inline-end: 10px; ++} ++ ++#searchWrapper { ++ display: flex; ++ flex: 1; ++ flex-direction: column; ++ min-width: 0; ++} ++ ++#searchLabel { ++ align-items: center; ++ color: var(--cr-secondary-text-color); ++ display: flex; ++ font-size: 12px; ++ justify-content: space-between; ++ line-height: 16px; ++} ++ ++#searchInput { ++ background: transparent; ++ border: 0; ++ color: var(--cr-primary-text-color); ++ font-size: 15px; ++ line-height: 20px; ++ outline: none; ++ padding: 2px 0 0; ++ width: 100%; ++} ++ ++#divider { ++ border-top: 1px solid var(--cr-separator-color); ++} ++ ++#results { ++ overflow-y: auto; ++ padding: 8px; ++} ++ ++#noResults { ++ color: var(--cr-secondary-text-color); ++ font-size: 12px; ++ padding: 20px 8px 12px; ++} ++ ++.section { ++ color: var(--cr-secondary-text-color); ++ font-size: 11px; ++ font-weight: 600; ++ letter-spacing: 0.08em; ++ margin: 10px 8px 6px; ++ text-transform: uppercase; ++} ++ ++.row { ++ align-items: flex-start; ++ background: transparent; ++ border: 0; ++ border-radius: 8px; ++ color: inherit; ++ cursor: pointer; ++ display: flex; ++ flex-direction: column; ++ margin: 0; ++ padding: 9px 10px; ++ text-align: left; ++ width: 100%; ++} ++ ++.row:hover, ++.row.selected { ++ background: var(--cr-hover-background-color); ++} ++ ++.title { ++ font-size: 13px; ++ line-height: 18px; ++} ++ ++.subtitle { ++ color: var(--cr-secondary-text-color); ++ font-size: 11px; ++ line-height: 14px; ++ margin-top: 2px; ++} diff --git a/patches/series b/patches/series index 139bdaf43..4dde5209e 100644 --- a/patches/series +++ b/patches/series @@ -327,6 +327,7 @@ helium/ui/layout/vertical-hover-overlay-callers.patch helium/ui/layout/vertical-collapse-command.patch helium/ui/layout/vertical-hover-overlay-setting.patch helium/ui/layout/vertical-hide-tab-search-button.patch +helium/ui/layout/vertical-command-launcher.patch helium/ui/pdf-viewer.patch helium/ui/hide-pip-live-caption-button.patch From c457e22aeb740b31041c8e10596a3339a62c6e0e Mon Sep 17 00:00:00 2001 From: dusan-nikcevic <168220721+dusan-nikcevic@users.noreply.github.com> Date: Fri, 27 Feb 2026 01:25:22 +0100 Subject: [PATCH 4/6] Stop action-binding hidden vertical tab search anchor --- ...vertical-tab-search-anchor-nonaction.patch | 32 +++++++++++++++++++ patches/series | 1 + 2 files changed, 33 insertions(+) create mode 100644 patches/helium/ui/layout/vertical-tab-search-anchor-nonaction.patch diff --git a/patches/helium/ui/layout/vertical-tab-search-anchor-nonaction.patch b/patches/helium/ui/layout/vertical-tab-search-anchor-nonaction.patch new file mode 100644 index 000000000..fb578664a --- /dev/null +++ b/patches/helium/ui/layout/vertical-tab-search-anchor-nonaction.patch @@ -0,0 +1,32 @@ +diff --git a/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_top_container.cc b/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_top_container.cc +--- a/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_top_container.cc ++++ b/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_top_container.cc +@@ -20,9 +20,6 @@ + namespace { + constexpr int kTopButtonPadding = 3; + constexpr int kCollapsedTopButtonPadding = 2; +-// Arc-like vertical sidebars keep search in the command bar/shortcut, not as a +-// persistent sidebar button. +-constexpr bool kShowTabSearchButton = false; + + bool IsVisuallyCollapsed( + const tabs::VerticalTabStripStateController* controller) { +@@ -40,13 +37,14 @@ VerticalTabStripTopContainer::VerticalTabStripTopContainer( + kVerticalTabStripTopContainerElementId); + SetLayoutManager(std::make_unique(this)); + +- tab_search_button_ = AddChildButtonFor(kActionTabSearch); ++ // Keep an invisible button as the bubble anchor for TabSearchBubbleHost. ++ // Do not bind it to actions, otherwise action sync can surface it in the UI. ++ tab_search_button_ = AddChildView(std::make_unique()); + tab_search_button_->SetProperty(views::kElementIdentifierKey, + kTabSearchButtonElementId); +- // Keep an invisible anchor for TabSearchBubbleHost, but never consume +- // sidebar layout space. + tab_search_button_->SetProperty(views::kViewIgnoredByLayoutKey, true); +- tab_search_button_->SetVisible(kShowTabSearchButton); ++ tab_search_button_->SetFocusBehavior(views::View::FocusBehavior::NEVER); ++ tab_search_button_->SetVisible(false); + + collapse_button_ = AddChildButtonFor(kActionToggleCollapseVertical); + collapse_button_->SetProperty(views::kElementIdentifierKey, diff --git a/patches/series b/patches/series index 4dde5209e..070af4103 100644 --- a/patches/series +++ b/patches/series @@ -327,6 +327,7 @@ helium/ui/layout/vertical-hover-overlay-callers.patch helium/ui/layout/vertical-collapse-command.patch helium/ui/layout/vertical-hover-overlay-setting.patch helium/ui/layout/vertical-hide-tab-search-button.patch +helium/ui/layout/vertical-tab-search-anchor-nonaction.patch helium/ui/layout/vertical-command-launcher.patch helium/ui/pdf-viewer.patch From 2b917a8bb0fb3d10fdc9bb59a0a12501665b7e3a Mon Sep 17 00:00:00 2001 From: dusan-nikcevic <168220721+dusan-nikcevic@users.noreply.github.com> Date: Fri, 27 Feb 2026 01:26:44 +0100 Subject: [PATCH 5/6] Polish command launcher ranking behavior --- .../ui/layout/vertical-command-launcher.patch | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/patches/helium/ui/layout/vertical-command-launcher.patch b/patches/helium/ui/layout/vertical-command-launcher.patch index 6589970e7..e80fb1258 100644 --- a/patches/helium/ui/layout/vertical-command-launcher.patch +++ b/patches/helium/ui/layout/vertical-command-launcher.patch @@ -389,6 +389,7 @@ +type CommandItem = { + kind: 'command', + command: LauncherCommand, ++ rank: number, + score: number, +}; + @@ -438,18 +439,23 @@ + return (isBoundary ? 220 : 120) - idx; +} + -+function scoreCommand(query: string, command: LauncherCommand): number { ++function scoreCommand( ++ query: string, command: LauncherCommand, rank: number): number { ++ if (!query) { ++ return 100 - rank; ++ } + return scoreText(query, command.title); +} + -+function scoreTab(query: string, tab: Tab): number { ++function scoreTab(query: string, tab: Tab, inActiveWindow: boolean): number { + if (!query) { -+ return 1; ++ return inActiveWindow ? 2 : 1; + } + + const titleScore = scoreText(query, tab.title) * 2; + const hostScore = scoreText(query, safeHost(tab.url.url)); -+ return Math.max(titleScore, hostScore); ++ const activeWindowBoost = inActiveWindow ? 15 : 0; ++ return Math.max(titleScore, hostScore) + activeWindowBoost; +} + +function compareTabsByActivity(a: Tab, b: Tab): number { @@ -590,17 +596,22 @@ + const query = this.query_.trim().toLowerCase(); + + const commandItems = this.commands_ -+ .map(command => ({ ++ .map((command, rank) => ({ + kind: 'command' as const, + command, -+ score: scoreCommand(query, command), ++ rank, ++ score: scoreCommand(query, command, rank), + })) + .filter(item => query ? item.score > 0 : true) + .sort((a, b) => { + if (b.score !== a.score) { + return b.score - a.score; + } -+ return a.command.title.localeCompare(b.command.title); ++ if (a.rank !== b.rank) { ++ return a.rank - b.rank; ++ } ++ return a.command.title.localeCompare( ++ b.command.title); + }) + .slice(0, 8); + @@ -609,13 +620,17 @@ + kind: 'tab' as const, + tab: openTab.tab, + inActiveWindow: openTab.inActiveWindow, -+ score: scoreTab(query, openTab.tab), ++ score: scoreTab( ++ query, openTab.tab, openTab.inActiveWindow), + })) + .filter(item => query ? item.score > 0 : true) + .sort((a, b) => { + if (b.score !== a.score) { + return b.score - a.score; + } ++ if (a.inActiveWindow !== b.inActiveWindow) { ++ return a.inActiveWindow ? -1 : 1; ++ } + return compareTabsByActivity(a.tab, b.tab); + }) + .slice(0, 8); From 41889433ad40d8c94e42cd8668483f65097b9fef Mon Sep 17 00:00:00 2001 From: dusan-nikcevic <168220721+dusan-nikcevic@users.noreply.github.com> Date: Sat, 28 Feb 2026 18:32:53 +0100 Subject: [PATCH 6/6] brand: sweep user-facing Helium naming to Zephyr --- .github/ISSUE_TEMPLATE/bug-report.yml | 8 ++-- .github/ISSUE_TEMPLATE/feature-request.yml | 2 +- .github/actions/bump-platform/action.yml | 6 +-- .../helium/core/add-disable-ech-flag.patch | 4 +- .../core/add-low-power-framerate-flag.patch | 2 +- .../add-middle-click-autoscroll-flag.patch | 4 +- patches/helium/core/add-native-bangs.patch | 8 ++-- .../helium/core/add-update-channel-flag.patch | 4 +- .../helium/core/add-updater-preference.patch | 10 ++--- .../core/change-chromium-branding.patch | 18 ++++---- patches/helium/core/component-updates.patch | 2 +- .../core/exclude-irrelevant-flags.patch | 10 ++--- .../core/fixups-chrome-webstore-script.patch | 2 +- patches/helium/core/keyboard-shortcuts.patch | 2 +- patches/helium/core/noise/audio.patch | 8 ++-- patches/helium/core/noise/canvas.patch | 10 ++--- patches/helium/core/noise/core.patch | 10 ++--- .../core/noise/hardware-concurrency.patch | 8 ++-- patches/helium/core/onboarding-page.patch | 6 +-- .../core/proxy-extension-downloads.patch | 12 ++--- .../core/reenable-spellcheck-downloads.patch | 10 ++--- patches/helium/core/services-prefs.patch | 44 +++++++++---------- patches/helium/core/services-schema-nag.patch | 14 +++--- .../helium/core/ublock-helium-services.patch | 8 ++-- .../helium/core/ublock-setup-sources.patch | 6 +-- patches/helium/core/update-credits.patch | 4 +- patches/helium/hop/setup.patch | 6 +-- .../settings/fix-text-on-cookies-page.patch | 2 +- .../settings/update-search-suggest-text.patch | 2 +- ...ror-for-disabled-extension-downloads.patch | 6 +-- patches/helium/ui/app-menu-model.patch | 2 +- .../helium/ui/clean-incognito-guest-ntp.patch | 2 +- patches/helium/ui/layout-constants.patch | 2 +- patches/helium/ui/layout/compact.patch | 4 +- patches/helium/ui/layout/context-menu.patch | 8 ++-- patches/helium/ui/layout/settings.patch | 2 +- .../ui/layout/vertical-collapse-command.patch | 2 +- patches/helium/ui/licenses-in-credits.patch | 8 ++-- patches/helium/ui/toolbar-button-prefs.patch | 2 +- .../helium/ui/ublock-show-in-settings.patch | 4 +- utils/name_substitution.py | 14 +++--- 41 files changed, 144 insertions(+), 144 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 8b948acd6..273aa00c3 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -1,5 +1,5 @@ name: Bug Report -description: Report a bug building or running Helium +description: Report a bug building or running Zephyr labels: ["bug"] title: "[Bug]: " body: @@ -11,12 +11,12 @@ body: interacting with the entire organization. If you suspect your bug might be specific to a certain platform (e.g. macOS), - please submit it to the relevant repository instead of the root "helium" repo. + please submit it to the relevant repository instead of the root "zephyr" repo. - type: dropdown id: os attributes: label: Operating system - description: The OS you are running Helium on + description: The OS you are running Zephyr on options: - macOS - Linux @@ -36,7 +36,7 @@ body: options: - label: I have tried reproducing this issue in Chrome and it could not be reproduced there - label: I have tried reproducing this issue in ungoogled-chromium and it could not be reproduced there - - label: I have tried reproducing this issue in Helium with a new and empty profile using `--user-data-dir` command line argument and it could not be reproduced there + - label: I have tried reproducing this issue in Zephyr with a new and empty profile using `--user-data-dir` command line argument and it could not be reproduced there - type: input id: description attributes: diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml index 05580df31..59018497f 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -14,7 +14,7 @@ body: to read that and thus your request will be closed. If your request is for a platform-specific feature (e.g. for macOS), please - submit it to the relevant platform repo instead of the generic "helium" repo. + submit it to the relevant platform repo instead of the generic "zephyr" repo. - type: input id: description attributes: diff --git a/.github/actions/bump-platform/action.yml b/.github/actions/bump-platform/action.yml index 72def126a..23b561eca 100644 --- a/.github/actions/bump-platform/action.yml +++ b/.github/actions/bump-platform/action.yml @@ -91,10 +91,10 @@ runs: popd # commit, push, make pr - TITLE="update: helium $version_after" + TITLE="update: zephyr $version_after" - git config user.name "helium-bot" - git config user.email "helium-bot@imput.net" + git config user.name "zephyr-bot" + git config user.email "zephyr-bot@users.noreply.github.com" git add -u patches helium-chromium revision.txt PLATFORM_HOOK="$PLATFORM_DIR/.github/bump-hook.sh" diff --git a/patches/helium/core/add-disable-ech-flag.patch b/patches/helium/core/add-disable-ech-flag.patch index 26898baf3..b01c02974 100644 --- a/patches/helium/core/add-disable-ech-flag.patch +++ b/patches/helium/core/add-disable-ech-flag.patch @@ -13,12 +13,12 @@ +++ b/chrome/browser/helium_flag_entries.h @@ -11,4 +11,9 @@ "Maximum frame rate for Energy Saver", - "Configures the frame rate the browser is throttled to when Energy Saver is enabled. Helium flag.", + "Configures the frame rate the browser is throttled to when Energy Saver is enabled. Zephyr flag.", kOsDesktop, MULTI_VALUE_TYPE(helium::kEnergySaverFrameRateChoices)}, + {helium::kDisableEchCommandLine, + "Disable ECH (Encrypted Client Hello)", + "Disables TLS Encrypted Client Hello. Not recommended unless you live in an area with heavy Internet" -+ " censorship and ECH prevents websites from loading. Helium flag.", ++ " censorship and ECH prevents websites from loading. Zephyr flag.", + kOsAll, SINGLE_VALUE_TYPE(helium::kDisableEchCommandLine)}, #endif /* CHROME_BROWSER_HELIUM_FLAG_ENTRIES_H_ */ --- a/chrome/browser/ssl/ssl_config_service_manager.cc diff --git a/patches/helium/core/add-low-power-framerate-flag.patch b/patches/helium/core/add-low-power-framerate-flag.patch index f061cc65b..4ffe8e5ad 100644 --- a/patches/helium/core/add-low-power-framerate-flag.patch +++ b/patches/helium/core/add-low-power-framerate-flag.patch @@ -24,7 +24,7 @@ #define CHROME_BROWSER_HELIUM_FLAG_ENTRIES_H_ + {helium::kEnergySaverFrameRateCommandLine, + "Maximum frame rate for Energy Saver", -+ "Configures the frame rate the browser is throttled to when Energy Saver is enabled. Helium flag.", ++ "Configures the frame rate the browser is throttled to when Energy Saver is enabled. Zephyr flag.", + kOsDesktop, MULTI_VALUE_TYPE(helium::kEnergySaverFrameRateChoices)}, #endif /* CHROME_BROWSER_HELIUM_FLAG_ENTRIES_H_ */ --- a/chrome/browser/performance_manager/user_tuning/battery_saver_mode_manager.cc diff --git a/patches/helium/core/add-middle-click-autoscroll-flag.patch b/patches/helium/core/add-middle-click-autoscroll-flag.patch index 6153641a5..916066da2 100644 --- a/patches/helium/core/add-middle-click-autoscroll-flag.patch +++ b/patches/helium/core/add-middle-click-autoscroll-flag.patch @@ -13,11 +13,11 @@ +++ b/chrome/browser/helium_flag_entries.h @@ -33,4 +33,8 @@ "Randomizes the number of cores returned by " - "`navigator.hardwareConcurrency` in a reasonable range. Helium flag.", + "`navigator.hardwareConcurrency` in a reasonable range. Zephyr flag.", kOsAll, FEATURE_VALUE_TYPE(blink::features::kHeliumNoiseCpuCores)}, + {helium::kMiddleClickAutoscrollCommandLine, + "Middle Click Autoscroll", -+ "Enables autoscroll on middle click. Helium flag, Chromium feature.", ++ "Enables autoscroll on middle click. Zephyr flag, Chromium feature.", + kOsDesktop, FEATURE_VALUE_TYPE(blink::features::kHeliumMiddleClickAutoscroll)}, #endif /* CHROME_BROWSER_HELIUM_FLAG_ENTRIES_H_ */ --- a/third_party/blink/public/common/features.h diff --git a/patches/helium/core/add-native-bangs.patch b/patches/helium/core/add-native-bangs.patch index d63b1cfee..2e89b989f 100644 --- a/patches/helium/core/add-native-bangs.patch +++ b/patches/helium/core/add-native-bangs.patch @@ -738,16 +738,16 @@ +++ b/chrome/app/settings_strings.grdp @@ -2029,6 +2029,12 @@ - When enabled, Helium will proxy extension downloads and updates to protect your privacy. When disabled, downloading and updating extensions will not work. + When enabled, Zephyr will proxy extension downloads and updates to protect your privacy. When disabled, downloading and updating extensions will not work. + + Allow downloading the !bangs list + + -+ Helium will fetch a list of bangs that help you browse the Internet faster, such as !w or !gh. When disabled, bangs will not work. ++ Zephyr will fetch a list of bangs that help you browse the Internet faster, such as !w or !gh. When disabled, bangs will not work. + - - Use your own instance of Helium services + + Use your own instance of Zephyr services --- a/chrome/browser/resources/settings/privacy_page/services_page.html +++ b/chrome/browser/resources/settings/privacy_page/services_page.html diff --git a/patches/helium/core/add-update-channel-flag.patch b/patches/helium/core/add-update-channel-flag.patch index 11b752f68..e8528caf5 100644 --- a/patches/helium/core/add-update-channel-flag.patch +++ b/patches/helium/core/add-update-channel-flag.patch @@ -83,11 +83,11 @@ #ifndef CHROME_BROWSER_HELIUM_FLAG_ENTRIES_H_ #define CHROME_BROWSER_HELIUM_FLAG_ENTRIES_H_ + {helium::kChannelCommandLine, -+ "Update channel", "Selects which update channel to use for update checking. Helium flag.", ++ "Update channel", "Selects which update channel to use for update checking. Zephyr flag.", + kOsAll, MULTI_VALUE_TYPE(helium::kChannelChoices)}, {helium::kEnergySaverFrameRateCommandLine, "Maximum frame rate for Energy Saver", - "Configures the frame rate the browser is throttled to when Energy Saver is enabled. Helium flag.", + "Configures the frame rate the browser is throttled to when Energy Saver is enabled. Zephyr flag.", --- a/chrome/browser/BUILD.gn +++ b/chrome/browser/BUILD.gn @@ -263,6 +263,8 @@ static_library("browser") { diff --git a/patches/helium/core/add-updater-preference.patch b/patches/helium/core/add-updater-preference.patch index 935d08f0c..4ffe5f854 100644 --- a/patches/helium/core/add-updater-preference.patch +++ b/patches/helium/core/add-updater-preference.patch @@ -126,21 +126,21 @@ +++ b/chrome/app/settings_strings.grdp @@ -2044,6 +2044,19 @@ - Helium will fetch dictionary files used for spell checking when requested. When disabled, spell checking will not work. + Zephyr will fetch dictionary files used for spell checking when requested. When disabled, spell checking will not work. + + Allow automatic browser and component updates + + + -+ Helium will automatically download and install browser and component updates as they become available. We recommend keeping this setting enabled to ensure you get the latest features and security updates. ++ Zephyr will automatically download and install browser and component updates as they become available. We recommend keeping this setting enabled to ensure you get the latest features and security updates. + + + + -+ Helium will automatically download and install browser and component updates as they become available. We recommend keeping this setting enabled to ensure you get the latest features and security updates. Automatic core browser updates are not available on this platform yet, but component updates are. Please use external software to keep Helium up to date. ++ Zephyr will automatically download and install browser and component updates as they become available. We recommend keeping this setting enabled to ensure you get the latest features and security updates. Automatic core browser updates are not available on this platform yet, but component updates are. Please use external software to keep Zephyr up to date. + + - - Use your own instance of Helium services + + Use your own instance of Zephyr services diff --git a/patches/helium/core/change-chromium-branding.patch b/patches/helium/core/change-chromium-branding.patch index aebe80f7e..f500c8a46 100644 --- a/patches/helium/core/change-chromium-branding.patch +++ b/patches/helium/core/change-chromium-branding.patch @@ -9,14 +9,14 @@ -PRODUCT_INSTALLER_SHORTNAME=Chromium Installer -COPYRIGHT=Copyright @LASTCHANGE_YEAR@ The Chromium Authors. All rights reserved. -MAC_BUNDLE_ID=org.chromium.Chromium -+COMPANY_FULLNAME=The Helium Authors -+COMPANY_SHORTNAME=The Helium Authors -+PRODUCT_FULLNAME=Helium -+PRODUCT_SHORTNAME=Helium -+PRODUCT_INSTALLER_FULLNAME=Helium Installer -+PRODUCT_INSTALLER_SHORTNAME=Helium Installer -+COPYRIGHT=Copyright @LASTCHANGE_YEAR@ The Helium Authors. All rights reserved. -+MAC_BUNDLE_ID=net.imput.helium ++COMPANY_FULLNAME=The Zephyr Authors ++COMPANY_SHORTNAME=The Zephyr Authors ++PRODUCT_FULLNAME=Zephyr ++PRODUCT_SHORTNAME=Zephyr ++PRODUCT_INSTALLER_FULLNAME=Zephyr Installer ++PRODUCT_INSTALLER_SHORTNAME=Zephyr Installer ++COPYRIGHT=Copyright @LASTCHANGE_YEAR@ The Zephyr Authors. All rights reserved. ++MAC_BUNDLE_ID=io.github.quanticstudios.zephyr MAC_CREATOR_CODE=Cr24 -MAC_TEAM_ID= -+MAC_TEAM_ID=S4Q33XPHB4 ++MAC_TEAM_ID= diff --git a/patches/helium/core/component-updates.patch b/patches/helium/core/component-updates.patch index 95691da5a..b9b9e037a 100644 --- a/patches/helium/core/component-updates.patch +++ b/patches/helium/core/component-updates.patch @@ -167,7 +167,7 @@ Update error + -+ Component updates are disabled. See Helium services in settings. ++ Component updates are disabled. See Zephyr services in settings. + Unknown diff --git a/patches/helium/core/exclude-irrelevant-flags.patch b/patches/helium/core/exclude-irrelevant-flags.patch index 036899d3e..eaaabd6eb 100644 --- a/patches/helium/core/exclude-irrelevant-flags.patch +++ b/patches/helium/core/exclude-irrelevant-flags.patch @@ -4,7 +4,7 @@ // AboutFlagsHistogramTest unit test to verify this process). }; -+// Flags that either break Helium functionality or are not relevant to Helium ++// Flags that either break Zephyr functionality or are not relevant to Zephyr +constexpr auto kExcludedFlags = base::MakeFixedFlatSet({ + // Misc UI features + "enable-immersive-fullscreen-toolbar", @@ -15,20 +15,20 @@ + "top-chrome-touch-ui", + + // Google's broken canvas noising flag, -+ // replaced by Helium's fingerprinting-canvas-noise ++ // replaced by Zephyr's fingerprinting-canvas-noise + "enable-canvas-noise", + + // Google's fingerprinting protection blocklist -+ // Helium blocks fingerprinting by default, and these don't even work ++ // Zephyr blocks fingerprinting by default, and these don't even work + "enable-fingerprinting-protection-blocklist", + "enable-fingerprinting-protection-blocklist-incognito", + -+ // Google's experimental APIs that don't work in Helium ++ // Google's experimental APIs that don't work in Zephyr + // due to missing model binaries + "translation-api", + "translation-api-streaming-by-sentence", + -+ // Misc features not relevant to Helium ++ // Misc features not relevant to Zephyr + "extensions-menu-access-control", + "extensions-collapse-main-menu", + diff --git a/patches/helium/core/fixups-chrome-webstore-script.patch b/patches/helium/core/fixups-chrome-webstore-script.patch index 4a09cfed1..a265000f4 100644 --- a/patches/helium/core/fixups-chrome-webstore-script.patch +++ b/patches/helium/core/fixups-chrome-webstore-script.patch @@ -50,7 +50,7 @@ + return false; + } + -+ el.nodeValue = el.nodeValue.replace(/\b(Google\s)?Chrome\b/, 'Helium'); ++ el.nodeValue = el.nodeValue.replace(/\b(Google\s)?Chrome\b/, 'Zephyr'); + return true; + } else if (el.childNodes) { + for (const node of el.childNodes) { diff --git a/patches/helium/core/keyboard-shortcuts.patch b/patches/helium/core/keyboard-shortcuts.patch index 8bc007921..d5aa5c3d6 100644 --- a/patches/helium/core/keyboard-shortcuts.patch +++ b/patches/helium/core/keyboard-shortcuts.patch @@ -4,7 +4,7 @@ #define IDC_WEB_APP_MENU_APP_INFO 34063 #define IDC_WEB_APP_UPGRADE_DIALOG 34064 -+// Helium commands ++// Zephyr commands +#define IDC_COPY_OR_INSPECT_SHORTCUT 34080 + #if BUILDFLAG(IS_CHROMEOS) diff --git a/patches/helium/core/noise/audio.patch b/patches/helium/core/noise/audio.patch index 25e353b71..929d5bda0 100644 --- a/patches/helium/core/noise/audio.patch +++ b/patches/helium/core/noise/audio.patch @@ -11,12 +11,12 @@ --- a/chrome/browser/helium_flag_entries.h +++ b/chrome/browser/helium_flag_entries.h @@ -24,4 +24,8 @@ - "[Helium Noise] Canvas pixel noising", - "Adds insignificant noise to canvas pixels during readback to deceive canvas-based fingerprinting. Helium flag.", + "[Zephyr Noise] Canvas pixel noising", + "Adds insignificant noise to canvas pixels during readback to deceive canvas-based fingerprinting. Zephyr flag.", kOsAll, FEATURE_VALUE_TYPE(blink::features::kHeliumNoiseCanvas)}, + {helium::kHeliumNoiseAudioCommandLine, -+ "[Helium Noise] Jitter audio context data", -+ "Adds insignificant jitter to audio data to deceive AudioContext-based fingerprinting. Helium flag.", ++ "[Zephyr Noise] Jitter audio context data", ++ "Adds insignificant jitter to audio data to deceive AudioContext-based fingerprinting. Zephyr flag.", + kOsAll, FEATURE_VALUE_TYPE(blink::features::kHeliumNoiseAudio)}, #endif /* CHROME_BROWSER_HELIUM_FLAG_ENTRIES_H_ */ --- a/third_party/blink/common/features.cc diff --git a/patches/helium/core/noise/canvas.patch b/patches/helium/core/noise/canvas.patch index f8a830116..7cbec6a7f 100644 --- a/patches/helium/core/noise/canvas.patch +++ b/patches/helium/core/noise/canvas.patch @@ -1,5 +1,5 @@ # Some files in this patch are sourced from Chromium. They were -# heavily modified to fit Helium's needs. +# heavily modified to fit Zephyr's needs. # # Chromium's license header is maintained wherever applicable to # respect the original authorship of code. @@ -47,12 +47,12 @@ --- a/chrome/browser/helium_flag_entries.h +++ b/chrome/browser/helium_flag_entries.h @@ -20,4 +20,8 @@ - "Helium Noise", - "Helium's anti-fingerprinting functionality. Adds insignificant noise to various features to deceive fingerprinting scripts. Enabled by default, must be enabled for other anti-fingerprinting features to work. Helium flag.", + "Zephyr Noise", + "Zephyr's anti-fingerprinting functionality. Adds insignificant noise to various features to deceive fingerprinting scripts. Enabled by default, must be enabled for other anti-fingerprinting features to work. Zephyr flag.", kOsAll, FEATURE_VALUE_TYPE(blink::features::kHeliumNoise)}, + {helium::kHeliumNoiseCanvasCommandLine, -+ "[Helium Noise] Canvas pixel noising", -+ "Adds insignificant noise to canvas pixels during readback to deceive canvas-based fingerprinting. Helium flag.", ++ "[Zephyr Noise] Canvas pixel noising", ++ "Adds insignificant noise to canvas pixels during readback to deceive canvas-based fingerprinting. Zephyr flag.", + kOsAll, FEATURE_VALUE_TYPE(blink::features::kHeliumNoiseCanvas)}, #endif /* CHROME_BROWSER_HELIUM_FLAG_ENTRIES_H_ */ --- a/third_party/blink/common/features.cc diff --git a/patches/helium/core/noise/core.patch b/patches/helium/core/noise/core.patch index 4ccff4e82..6ac9d130e 100644 --- a/patches/helium/core/noise/core.patch +++ b/patches/helium/core/noise/core.patch @@ -1,5 +1,5 @@ # Some files in this patch are sourced from Chromium. They were -# heavily modified to fit Helium's needs. +# heavily modified to fit Zephyr's needs. # # Chromium's license header is maintained wherever applicable to # respect the original authorship of code. @@ -49,11 +49,11 @@ +++ b/chrome/browser/helium_flag_entries.h @@ -16,4 +16,8 @@ "Disables TLS Encrypted Client Hello. Not recommended unless you live in an area with heavy Internet" - " censorship and ECH prevents websites from loading. Helium flag.", + " censorship and ECH prevents websites from loading. Zephyr flag.", kOsAll, SINGLE_VALUE_TYPE(helium::kDisableEchCommandLine)}, + {helium::kHeliumNoiseCommandLine, -+ "Helium Noise", -+ "Helium's anti-fingerprinting functionality. Adds insignificant noise to various features to deceive fingerprinting scripts. Enabled by default, must be enabled for other anti-fingerprinting features to work. Helium flag.", ++ "Zephyr Noise", ++ "Zephyr's anti-fingerprinting functionality. Adds insignificant noise to various features to deceive fingerprinting scripts. Enabled by default, must be enabled for other anti-fingerprinting features to work. Zephyr flag.", + kOsAll, FEATURE_VALUE_TYPE(blink::features::kHeliumNoise)}, #endif /* CHROME_BROWSER_HELIUM_FLAG_ENTRIES_H_ */ --- a/third_party/blink/common/features.cc @@ -62,7 +62,7 @@ // `RuntimeEnabledFeatures)`, they should still be ordered in this section based // on the identifier name of the generated feature. -+// Helium Noise; required for fingerprinting protection features. ++// Zephyr Noise; required for fingerprinting protection features. +BASE_FEATURE(kHeliumNoise, + base::FEATURE_ENABLED_BY_DEFAULT); + diff --git a/patches/helium/core/noise/hardware-concurrency.patch b/patches/helium/core/noise/hardware-concurrency.patch index 0ed3c85ac..12c2f678b 100644 --- a/patches/helium/core/noise/hardware-concurrency.patch +++ b/patches/helium/core/noise/hardware-concurrency.patch @@ -12,13 +12,13 @@ --- a/chrome/browser/helium_flag_entries.h +++ b/chrome/browser/helium_flag_entries.h @@ -28,4 +28,9 @@ - "[Helium Noise] Jitter audio context data", - "Adds insignificant jitter to audio data to deceive AudioContext-based fingerprinting. Helium flag.", + "[Zephyr Noise] Jitter audio context data", + "Adds insignificant jitter to audio data to deceive AudioContext-based fingerprinting. Zephyr flag.", kOsAll, FEATURE_VALUE_TYPE(blink::features::kHeliumNoiseAudio)}, + {helium::kHeliumNoiseCpuCoresCommandLine, -+ "[Helium Noise] Randomize number of CPU cores", ++ "[Zephyr Noise] Randomize number of CPU cores", + "Randomizes the number of cores returned by " -+ "`navigator.hardwareConcurrency` in a reasonable range. Helium flag.", ++ "`navigator.hardwareConcurrency` in a reasonable range. Zephyr flag.", + kOsAll, FEATURE_VALUE_TYPE(blink::features::kHeliumNoiseCpuCores)}, #endif /* CHROME_BROWSER_HELIUM_FLAG_ENTRIES_H_ */ --- a/content/browser/helium_noise/noise_token_data.cc diff --git a/patches/helium/core/onboarding-page.patch b/patches/helium/core/onboarding-page.patch index 5f076d7e4..b33bde456 100644 --- a/patches/helium/core/onboarding-page.patch +++ b/patches/helium/core/onboarding-page.patch @@ -655,13 +655,13 @@ Don't notify me again + -+ Helium services are not available until setup is complete to ensure your privacy and consent. ++ Zephyr services are not available until setup is complete to ensure your privacy and consent. + + + Complete setup + - - Allow connecting to Helium services + + Allow connecting to Zephyr services --- a/chrome/browser/extensions/api/settings_private/prefs_util.cc +++ b/chrome/browser/extensions/api/settings_private/prefs_util.cc diff --git a/patches/helium/core/proxy-extension-downloads.patch b/patches/helium/core/proxy-extension-downloads.patch index db2e2d8ed..97d54e14b 100644 --- a/patches/helium/core/proxy-extension-downloads.patch +++ b/patches/helium/core/proxy-extension-downloads.patch @@ -167,7 +167,7 @@ --- a/chrome/browser/extensions/api/settings_private/prefs_util.cc +++ b/chrome/browser/extensions/api/settings_private/prefs_util.cc @@ -363,6 +363,8 @@ const PrefsUtil::TypedPrefMap& PrefsUtil - // Helium services page + // Zephyr services page (*s_allowlist)[::prefs::kHeliumServicesEnabled] = settings_api::PrefType::kBoolean; + (*s_allowlist)[::prefs::kHeliumExtProxyEnabled] = @@ -215,17 +215,17 @@ --- a/chrome/app/settings_strings.grdp +++ b/chrome/app/settings_strings.grdp @@ -2023,6 +2023,12 @@ - - When enabled, Helium will be able to connect to anonymous web services to provide additional functionality. When disabled, additional features will not work. + + When enabled, Zephyr will be able to connect to anonymous web services to provide additional functionality. When disabled, additional features will not work. + + Proxy extension downloads and updates + + -+ When enabled, Helium will proxy extension downloads and updates to protect your privacy. When disabled, downloading and updating extensions will not work. ++ When enabled, Zephyr will proxy extension downloads and updates to protect your privacy. When disabled, downloading and updating extensions will not work. + - - Use your own instance of Helium services + + Use your own instance of Zephyr services --- a/components/helium_services/pref_names.h +++ b/components/helium_services/pref_names.h diff --git a/patches/helium/core/reenable-spellcheck-downloads.patch b/patches/helium/core/reenable-spellcheck-downloads.patch index 175db9c67..de2049470 100644 --- a/patches/helium/core/reenable-spellcheck-downloads.patch +++ b/patches/helium/core/reenable-spellcheck-downloads.patch @@ -149,23 +149,23 @@ Please check with your network administrator to make sure that the firewall is not blocking downloads from Google servers. + -+ Please check that Helium services are enabled (including spell check downloads) and make sure that the firewall is not blocking downloads from Helium servers. ++ Please check that Zephyr services are enabled (including spell check downloads) and make sure that the firewall is not blocking downloads from Zephyr servers. + @@ -2035,6 +2038,12 @@ - Helium will fetch a list of bangs that help you browse the Internet faster, such as !w or !gh. When disabled, bangs will not work. + Zephyr will fetch a list of bangs that help you browse the Internet faster, such as !w or !gh. When disabled, bangs will not work. + + Allow downloading dictionary files for spell checking + + -+ Helium will fetch dictionary files used for spell checking when requested. When disabled, spell checking will not work. ++ Zephyr will fetch dictionary files used for spell checking when requested. When disabled, spell checking will not work. + - - Use your own instance of Helium services + + Use your own instance of Zephyr services --- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc +++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc diff --git a/patches/helium/core/services-prefs.patch b/patches/helium/core/services-prefs.patch index 4660c70ea..333f38e0e 100644 --- a/patches/helium/core/services-prefs.patch +++ b/patches/helium/core/services-prefs.patch @@ -4,38 +4,38 @@ Safe Browsing (protection from dangerous sites) and other security settings -+ -+ Helium services ++ ++ Zephyr services + -+ -+ Manage what Helium services are allowed in your browser ++ ++ Manage what Zephyr services are allowed in your browser + -+ -+ Updates to Helium services: ++ ++ Updates to Zephyr services: + -+ ++ + These changes are not active until you dismiss this notice. + + + Don't notify me again + -+ -+ Allow connecting to Helium services ++ ++ Allow connecting to Zephyr services + -+ -+ When enabled, Helium will be able to connect to anonymous web services to provide additional functionality. When disabled, additional features will not work. ++ ++ When enabled, Zephyr will be able to connect to anonymous web services to provide additional functionality. When disabled, additional features will not work. + -+ -+ Use your own instance of Helium services ++ ++ Use your own instance of Zephyr services + -+ -+ You can host your own instance of Helium services and use it in your browser instead of the default one. HTTPS only. ++ ++ You can host your own instance of Zephyr services and use it in your browser instead of the default one. HTTPS only. + -+ ++ + Do not use this field unless you know exactly what you are doing. Never paste URLs from other people here, they are trying to steal your data. We are not responsible for any damages caused by using a custom server and will not be able to help you. + -+ -+ Custom origin URL for Helium services ++ ++ Custom origin URL for Zephyr services + + ; + acknowledgeChanges(ignoreAllChangelogs: boolean): void; @@ -710,7 +710,7 @@ + { + 1, + "Automatic component updates are now available. They're managed by the same toggle that enables automatic browser updates.\n" -+ "From now on, you'll be notified about major changes to Helium services. Even though these notifications are extremely rare, you can choose to ignore them and accept all future changes automatically." ++ "From now on, you'll be notified about major changes to Zephyr services. Even though these notifications are extremely rare, you can choose to ignore them and accept all future changes automatically." + } + }); + diff --git a/patches/helium/core/services-schema-nag.patch b/patches/helium/core/services-schema-nag.patch index 3c07df3e7..0baf4b7e6 100644 --- a/patches/helium/core/services-schema-nag.patch +++ b/patches/helium/core/services-schema-nag.patch @@ -4,8 +4,8 @@ Customize and control Chromium. Update is available. -+ -+ Helium services have been updated. Please review the changes. ++ ++ Zephyr services have been updated. Please review the changes. + + @@ -15,9 +15,9 @@ Chromium couldn't update to the latest version, so you're missing out on new features and security fixes. -+ -+ -+ Review Helium services updates ++ ++ ++ Review Zephyr services updates + + @@ -29,7 +29,7 @@ Update -+ ++ + Services updated + @@ -95,7 +95,7 @@ // boolean indicating whether any menu items were added. bool AddDefaultBrowserMenuItems(); -+ // Adds a nag to review Helium services permission changes ++ // Adds a nag to review Zephyr services permission changes + bool AddHeliumSchemaItem(); + // Adds the Safety Hub menu notifications to the menu. Returns a boolean diff --git a/patches/helium/core/ublock-helium-services.patch b/patches/helium/core/ublock-helium-services.patch index ae57cf277..d7719bdb2 100644 --- a/patches/helium/core/ublock-helium-services.patch +++ b/patches/helium/core/ublock-helium-services.patch @@ -218,17 +218,17 @@ --- a/chrome/app/settings_strings.grdp +++ b/chrome/app/settings_strings.grdp @@ -2057,6 +2057,12 @@ - Helium will automatically download and install browser and component updates as they become available. We recommend keeping this setting enabled to ensure you get the latest features and security updates. Automatic core browser updates are not available on this platform yet, but component updates are. Please use external software to keep Helium up to date. + Zephyr will automatically download and install browser and component updates as they become available. We recommend keeping this setting enabled to ensure you get the latest features and security updates. Automatic core browser updates are not available on this platform yet, but component updates are. Please use external software to keep Zephyr up to date. + + Allow downloading filter lists for uBlock Origin + + -+ Helium will fetch fresh filter lists for uBlock Origin. All requests to lists are proxied to protect your privacy. When disabled, default filter lists will be loaded from local storage, which are only updated along with Helium. Optional filter lists will be requested without proxying. ++ Zephyr will fetch fresh filter lists for uBlock Origin. All requests to lists are proxied to protect your privacy. When disabled, default filter lists will be loaded from local storage, which are only updated along with Zephyr. Optional filter lists will be requested without proxying. + - - Use your own instance of Helium services + + Use your own instance of Zephyr services --- a/chrome/browser/resources/settings/privacy_page/services_page.html +++ b/chrome/browser/resources/settings/privacy_page/services_page.html diff --git a/patches/helium/core/ublock-setup-sources.patch b/patches/helium/core/ublock-setup-sources.patch index 75c3f52ae..bf19315f2 100644 --- a/patches/helium/core/ublock-setup-sources.patch +++ b/patches/helium/core/ublock-setup-sources.patch @@ -189,7 +189,7 @@ "https://ublockorigin.pages.dev/filters/badlists.txt", "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/badlists.txt", @@ -36,7 +33,6 @@ - "title": "Helium filters – Annoyances", + "title": "Zephyr filters – Annoyances", "tags": "annoyances", "contentURL": [ - "https://raw.githubusercontent.com/imputnet/helium-services/main/filters/helium-annoyances.txt", @@ -197,8 +197,8 @@ ] }, @@ -46,7 +42,6 @@ - "parent": "Helium filters", - "title": "Helium filters – Unbreak", + "parent": "Zephyr filters", + "title": "Zephyr filters – Unbreak", "contentURL": [ - "https://raw.githubusercontent.com/imputnet/helium-services/main/filters/helium-unbreak.txt", "assets/helium/unbreak.txt" diff --git a/patches/helium/core/update-credits.patch b/patches/helium/core/update-credits.patch index c702031c6..1236490f8 100644 --- a/patches/helium/core/update-credits.patch +++ b/patches/helium/core/update-credits.patch @@ -6,8 +6,8 @@ reciprocal_contents = EvaluateTemplate(reciprocal_template, { - 'opensource_project': 'ungoogled-chromium', - 'opensource_link': 'https://github.com/ungoogled-software/ungoogled-chromium' -+ 'opensource_project': 'Helium', -+ 'opensource_link': 'https://github.com/imputnet/helium' ++ 'opensource_project': 'Zephyr', ++ 'opensource_link': 'https://github.com/quanticstudios/zephyr' }, escape=False) diff --git a/patches/helium/hop/setup.patch b/patches/helium/hop/setup.patch index cddc3d8f3..97ef60f71 100644 --- a/patches/helium/hop/setup.patch +++ b/patches/helium/hop/setup.patch @@ -147,7 +147,7 @@ // policy. This source should be kept as highest priority source. POLICY_SOURCE_RESTRICTED_MANAGED_GUEST_SESSION_OVERRIDE, -+ // Helium opinionated policies ++ // Zephyr opinionated policies + POLICY_SOURCE_HOP, + // Number of source types. Has to be the last element. @@ -211,8 +211,8 @@ Restricted managed guest session override -+ -+ Helium defaults ++ ++ Zephyr defaults + Local Server diff --git a/patches/helium/settings/fix-text-on-cookies-page.patch b/patches/helium/settings/fix-text-on-cookies-page.patch index fa7c9bd0e..3e97d627d 100644 --- a/patches/helium/settings/fix-text-on-cookies-page.patch +++ b/patches/helium/settings/fix-text-on-cookies-page.patch @@ -5,7 +5,7 @@ Enabling "Do Not Track" means that a request will be included with your browsing traffic. Any effect depends on whether a website responds to the request, and how the request is interpreted. For example, some websites may respond to this request by showing you ads that aren't based on other websites you've visited. Many websites will still collect and use your browsing data - for example to improve security, to provide content, services, ads and recommendations on their websites, and to generate reporting statistics. + -+ Enabling "Do Not Track" means that Helium will include a request not to be tracked with your browsing traffic. Whether this request is honored depends on whether a website responds to it and on how it is interpreted. For example, some websites may respond by showing you ads that aren't personalized using your previous browsing data. Many websites may still track you regardless of the request. ++ Enabling "Do Not Track" means that Zephyr will include a request not to be tracked with your browsing traffic. Whether this request is honored depends on whether a website responds to it and on how it is interpreted. For example, some websites may respond by showing you ads that aren't personalized using your previous browsing data. Many websites may still track you regardless of the request. + Learn more about Do Not Track diff --git a/patches/helium/settings/update-search-suggest-text.patch b/patches/helium/settings/update-search-suggest-text.patch index 4c6c201c2..9f95e3413 100644 --- a/patches/helium/settings/update-search-suggest-text.patch +++ b/patches/helium/settings/update-search-suggest-text.patch @@ -5,7 +5,7 @@ When you type in the address bar or search box, Chromium sends what you type to your default search engine to get better suggestions. This is off in Incognito. + -+ When you type in the address bar, Helium sends what you type to your default search engine to get suggestions. This is off in Incognito. ++ When you type in the address bar, Zephyr sends what you type to your default search engine to get suggestions. This is off in Incognito. + When you type in the address bar or search box, Chromium sends what you type to your organization's search, AI, and agent tools to get suggestions. diff --git a/patches/helium/ui/add-specific-error-for-disabled-extension-downloads.patch b/patches/helium/ui/add-specific-error-for-disabled-extension-downloads.patch index 54e47e827..7a1f272fe 100644 --- a/patches/helium/ui/add-specific-error-for-disabled-extension-downloads.patch +++ b/patches/helium/ui/add-specific-error-for-disabled-extension-downloads.patch @@ -95,8 +95,8 @@ File name or location is too long + -+ Extension downloads are disabled. Enable this feature in Helium services settings and try again. ++ desc="Subpage summary text for a extension download item that was interrupted because Zephyr services are disabled."> ++ Extension downloads are disabled. Enable this feature in Zephyr services settings and try again. + @@ -106,7 +106,7 @@ Something went wrong + ++ desc="Status text for when Zephyr services extension downloading is disabled."> + Extension downloads are disabled + + -+ Helium will not save anything about your browsing in Incognito, but downloads and bookmarks will stick around. Your browsing won't leave any traces on this device, and other users won't be able to see your activity. ++ Zephyr will not save anything about your browsing in Incognito, but downloads and bookmarks will stick around. Your browsing won't leave any traces on this device, and other users won't be able to see your activity. + Others who use this device won’t see your activity, so you can browse more privately. This won't change how data is collected by websites you visit and the services they use, including Google. Downloads, bookmarks and reading list items will be saved. diff --git a/patches/helium/ui/layout-constants.patch b/patches/helium/ui/layout-constants.patch index f491eca17..56a281212 100644 --- a/patches/helium/ui/layout-constants.patch +++ b/patches/helium/ui/layout-constants.patch @@ -162,7 +162,7 @@ #include "ui/gfx/geometry/size.h" enum class LayoutConstant { -+ // The base padding used in Helium UI. ++ // The base padding used in Zephyr UI. + kHeliumBasePadding, + // The size of the avatar icon in the profile row of the app menu. diff --git a/patches/helium/ui/layout/compact.patch b/patches/helium/ui/layout/compact.patch index e7a1d28f6..4db944516 100644 --- a/patches/helium/ui/layout/compact.patch +++ b/patches/helium/ui/layout/compact.patch @@ -13,11 +13,11 @@ +++ b/chrome/browser/helium_flag_entries.h @@ -37,4 +37,8 @@ "Middle Click Autoscroll", - "Enables autoscroll on middle click. Helium flag, Chromium feature.", + "Enables autoscroll on middle click. Zephyr flag, Chromium feature.", kOsDesktop, FEATURE_VALUE_TYPE(blink::features::kHeliumMiddleClickAutoscroll)}, + {helium::kHeliumCompactLocationWidthCommandLine, + "Automatic address bar width in compact layout", -+ "Allows the location bar to automatically reduce its width in the compact browser layout. The omnibox may be uncomfortable to use. Helium flag.", ++ "Allows the location bar to automatically reduce its width in the compact browser layout. The omnibox may be uncomfortable to use. Zephyr flag.", + kOsDesktop, FEATURE_VALUE_TYPE(features::kHeliumCompactLocationWidth)}, #endif /* CHROME_BROWSER_HELIUM_FLAG_ENTRIES_H_ */ --- a/chrome/browser/ui/ui_features.cc diff --git a/patches/helium/ui/layout/context-menu.patch b/patches/helium/ui/layout/context-menu.patch index 44225a71f..cf75456bf 100644 --- a/patches/helium/ui/layout/context-menu.patch +++ b/patches/helium/ui/layout/context-menu.patch @@ -2,7 +2,7 @@ +++ b/chrome/app/chrome_command_ids.h @@ -94,15 +94,11 @@ - // Helium commands + // Zephyr commands #define IDC_COPY_OR_INSPECT_SHORTCUT 34080 - -#if BUILDFLAG(IS_CHROMEOS) @@ -27,7 +27,7 @@ Create split view -+ ++ + + + @@ -64,7 +64,7 @@ + + + -+ ++ + @@ -148,7 +148,7 @@ command_updater_.UpdateCommandEnabled(IDC_DECLUTTER_TABS, true); command_updater_.UpdateCommandEnabled(IDC_TOGGLE_VERTICAL_TABS, true); + -+ // Helium layout menu ++ // Zephyr layout menu + command_updater_.UpdateCommandEnabled(IDC_BROWSER_LAYOUT_MENU, true); + command_updater_.UpdateCommandEnabled(IDC_BROWSER_LAYOUT_HORIZONTAL, true); + command_updater_.UpdateCommandEnabled(IDC_BROWSER_LAYOUT_COMPACT, true); diff --git a/patches/helium/ui/layout/settings.patch b/patches/helium/ui/layout/settings.patch index cb87ce7e1..bc4083120 100644 --- a/patches/helium/ui/layout/settings.patch +++ b/patches/helium/ui/layout/settings.patch @@ -4,7 +4,7 @@ } s_allowlist = new PrefsUtil::TypedPrefMap(); -+ // Helium layout ++ // Zephyr layout + (*s_allowlist)[::prefs::kHeliumLayout] = + settings_api::PrefType::kNumber; + (*s_allowlist)[::prefs::kHeliumVerticalRightAligned] = diff --git a/patches/helium/ui/layout/vertical-collapse-command.patch b/patches/helium/ui/layout/vertical-collapse-command.patch index a1805273f..ed8123aff 100644 --- a/patches/helium/ui/layout/vertical-collapse-command.patch +++ b/patches/helium/ui/layout/vertical-collapse-command.patch @@ -41,7 +41,7 @@ // Updates commands that depend on the state of the tab strip model. void UpdateCommandsForTabStripStateChanged(); -+ // Updates commands that depend on Helium layout mode. ++ // Updates commands that depend on Zephyr layout mode. + void UpdateCommandsForHeliumLayoutMode(); + // Updates commands that depend on the enabled state of glic. diff --git a/patches/helium/ui/licenses-in-credits.patch b/patches/helium/ui/licenses-in-credits.patch index 7b0c94df4..deb835c03 100644 --- a/patches/helium/ui/licenses-in-credits.patch +++ b/patches/helium/ui/licenses-in-credits.patch @@ -1,9 +1,9 @@ --- /dev/null +++ b/third_party/helium/README.chromium @@ -0,0 +1,14 @@ -+Name: Helium -+Short Name: helium -+URL: https://github.com/imputnet/helium ++Name: Zephyr ++Short Name: zephyr ++URL: https://github.com/quanticstudios/zephyr +License: GPL-3.0-only +License File: ../../components/helium_onboarding/LICENSE +Shipped: yes @@ -11,7 +11,7 @@ + +Description: +The browser. We assume that it has the same license as helium-onboarding, -+which is released under the same license and is a direct component of Helium. ++which is released under the same license and is a direct component of Zephyr. + +Local Modifications: +None diff --git a/patches/helium/ui/toolbar-button-prefs.patch b/patches/helium/ui/toolbar-button-prefs.patch index 83e247e20..d3abc5e53 100644 --- a/patches/helium/ui/toolbar-button-prefs.patch +++ b/patches/helium/ui/toolbar-button-prefs.patch @@ -48,7 +48,7 @@ (*s_allowlist)[::prefs::kSplitViewDragAndDropEnabled] = settings_api::PrefType::kBoolean; -+ // Helium toolbar settings. ++ // Zephyr toolbar settings. + (*s_allowlist)[::prefs::kShowBackButton] = + settings_api::PrefType::kBoolean; + (*s_allowlist)[::prefs::kShowReloadButton] = diff --git a/patches/helium/ui/ublock-show-in-settings.patch b/patches/helium/ui/ublock-show-in-settings.patch index cdece9116..d9a172a90 100644 --- a/patches/helium/ui/ublock-show-in-settings.patch +++ b/patches/helium/ui/ublock-show-in-settings.patch @@ -91,8 +91,8 @@ Installed because of dependent extension(s). -+ -+ Helium component ++ ++ Zephyr component + diff --git a/utils/name_substitution.py b/utils/name_substitution.py index 06e66b019..41f54a157 100755 --- a/utils/name_substitution.py +++ b/utils/name_substitution.py @@ -3,7 +3,7 @@ # Copyright 2025 The Helium Authors # You can use, redistribute, and/or modify this source code under # the terms of the GPL-3.0 license that can be found in the LICENSE file. -"""Script to replace instances of Chrome/Chromium with Helium""" +"""Script to replace instances of Chrome/Chromium with Zephyr""" from concurrent.futures import ProcessPoolExecutor from tarfile import TarInfo @@ -22,7 +22,7 @@ (r'("BEGIN_LINK_CHROMIUM")(.*?Chromium)(.*?