Skip to content

helium/ui: add adaptive tab color from page theme-color - #2557

Open
elricfd wants to merge 2 commits into
imputnet:mainfrom
elricfd:adaptive-tab-color
Open

elricfd wants to merge 2 commits into
imputnet:mainfrom
elricfd:adaptive-tab-color

Conversation

@elricfd

@elricfd elricfd commented Sep 21, 2026

Copy link
Copy Markdown

Closes #725.

  • add kHeliumAdaptiveTabColor feature flag (on by default) and min-contrast param
  • track the active tab's theme-color in AdaptiveTabColor, falling back to the page background; updates are debounced by 100 ms
  • blend the color until text on it reaches the min-contrast ratio
  • plumb adaptive_color through ColorProviderKey; ColorProviderManager picks light or dark UI from its brightness without touching color_mode, which pages read for their own light/dark theme
  • override active/inactive frame and toolbar colors in the material chrome mixer
  • skip custom themes, high contrast, forced colors, web app and non-normal windows, and non-http(s) pages
  • clear adaptive_color in renderer color maps
  • while the flag is on, page scrollbars follow the page's color scheme instead of the toolbar's darkness

Tested on: MacOS 27.0
Screenshot 2026-09-21 at 4 30 09 PM
Screenshot 2026-09-21 at 4 20 46 PM
Screenshot 2026-09-21 at 4 33 19 PM

@greptile-apps

greptile-apps Bot commented Sep 21, 2026

Copy link
Copy Markdown

Retrigger

The PR is not yet safe to merge because some HTTP(S) navigations can retain stale site chrome indefinitely, and page-controlled colors can grow the color-provider cache without a bound.

Reviews (1) · Last reviewed commit: "helium/ui: add adaptive tab color from p..."

Comment on lines +213 to +216
+void AdaptiveTabColor::PrimaryPageChanged(content::Page& page) {
+ if (!CanAdapt()) {
+ Update();
+ }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Stale color survives navigation

When one HTTP(S) page navigates to another, PrimaryPageChanged() skips Update() because CanAdapt() remains true. The later theme-color, background-color, and first-paint callbacks are conditional, so an empty or non-painting destination can retain the previous site’s frame and toolbar color indefinitely. Update the adaptive state on every primary-page change instead of relying only on optional follow-up callbacks.

Knowledge Base Used: Helium browser interface

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updating on every primary page change wld flash the default color between any two page b/c the new page's theme-color and background aren't known until it renders
Instead I added a DidStopLoading() hook that rereads the color when a load finishes, whether or not anything painted. A destination that never paints now falls back to the default instead of keeping the previous site's color
The theme color, bg color and first-paint hooks still handle the normal case

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That addresses the normal navigation case without flashing: DidStopLoading() rereads the color even when no paint callback occurs, and pages with no theme/background color fall back to the default.

One stale-state path remains: colors_seen_ is only cleared when switching tabs, not in PrimaryPageChanged(). Once a tab has observed 64 distinct quantized colors, Update() returns before calling SetAdaptiveColor() for every subsequent new color (adaptive_tab_color.cc:99-104). Navigating that same tab to a page with a new color can therefore retain the previous page’s toolbar color, including after DidStopLoading(). Clear the per-page color budget in PrimaryPageChanged() so the load-completion fallback can always apply.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my most recent commit, I added colors_seen_.clear() to PrimaryPageChanged(). Primary page navigations now reset the budget before evaluating page adaptation, allowing subsequent color updates and the load-completion fallback to apply

--- a/ui/color/color_provider_key.h
+++ b/ui/color/color_provider_key.h
@@ -140,6 +140,7 @@ struct COMPONENT_EXPORT(COLOR_PROVIDER_KEY) ColorProvi
FrameStyle frame_style = FrameStyle::kDefault;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Page colors grow cache

adaptive_color is controlled by the page and is now part of ColorProviderKey, while providers are cached by the complete key without per-entry eviction. A page can publish a new theme color slightly more than every 100 ms, repeatedly initializing providers and growing the process-wide cache for as long as it remains active. Avoid using arbitrary page colors directly as unbounded cache-key values, or add a bound or eviction strategy.

@elricfd elricfd Sep 22, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion, made two changes in AdaptiveTabColor::Update() ^^

Color is quantized to 32 levels per channel b4 the contrast blend, so there are at most ~32.8k possible key values in total. Quantizing b4 blend keeps contrast guarantee since blend is a deterministic fn of its input
A tab can contribute at most 64 distinct colors while it is the active tab (kMaxColorsPerTab). Past that, the last color stays. The set resets on tab switch, which a page can't trigger

Separately, the 100ms timer restarts on every change, so a page that changes its theme-color faster than that never triggers an update at all

@dumbmoron dumbmoron added the bot:review Request a Helium bot review label Sep 22, 2026
@helium-gardener helium-gardener Bot removed the bot:review Request a Helium bot review label Sep 22, 2026
@dumbmoron dumbmoron added the bot:review Request a Helium bot review label Sep 22, 2026
@helium-gardener helium-gardener Bot removed the bot:review Request a Helium bot review label Sep 22, 2026
@helium-gardener

Copy link
Copy Markdown

Possibly related issues

These relationships were inferred from the effective change and may need maintainer confirmation:

  • Related: imputnet/helium#857 — The issue requests stronger window-color differentiation for incognito mode. This default-enabled change directly affects that distinction by applying page-derived colors to incognito 'TYPE_NORMAL' windows, but it does not implement the requested behavior.

@helium-gardener helium-gardener Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 1 finding(s) were already covered by other reviewers; 2 new finding(s) remain.

Review walkthrough

Changes

Files Summary
chrome/browser/ui/views/helium/adaptive_tab_color.h
chrome/browser/ui/views/helium/adaptive_tab_color.cc
chrome/browser/ui/views/frame/browser_widget.h
chrome/browser/ui/views/frame/browser_widget.cc
chrome/browser/ui/BUILD.gn
Adds an active-tab observer, debounced page-color updates, contrast adjustment, widget-owned adaptive state, and build wiring. The observer currently reaches incognito windows and its color budget is scoped to the window activation rather than the tab.
ui/color/color_provider_key.h
ui/color/color_provider_manager.cc
chrome/browser/ui/color/material_chrome_color_mixer.cc
ui/views/widget/widget.cc
Adds the adaptive color to provider cache keys, initializes chrome mixers in a light or dark mode derived from that color, overrides frame and toolbar colors, and removes the adaptive value from renderer color maps.
chrome/browser/ui/ui_features.h
chrome/browser/ui/ui_features.cc
chrome/browser/chrome_content_browser_client.cc
Defines the default-enabled feature and contrast parameter, and makes root scrollbar preference follow the page-facing color scheme while the feature is enabled.
patches/helium/ui/adaptive-tab-color.patch
patches/series
Introduces one coherent feature patch with no application warnings, but appends it after unrelated UI patches instead of placing it with the existing color-mixer stack.

Review checks

Check Result Notes
Scope ✅ Passed The PR adds one patch and one series entry; effective reviewable additions map to 12 Chromium source paths, with no rebase detected.
Project Policy ✅ Passed No upstream test file, test data, expectation, or test target is changed.
Correctness And Safety ⚠️ Finding Incognito windows receive page-derived shell colors, and the 64-color guard can both leave stale cross-navigation color and be reset by tab activation.
Chromium Idioms ⚠️ Finding The existing 'IsIncognitoBrowser()' guard is not used, and nominally per-tab state is instead stored on the window despite Chromium’s documented 'TabFeatures'/'TabInterface' ownership pattern.
Simplicity And Decomposition ⚠️ Finding 'colors_seen_' has overlapping window, activation, and claimed per-tab lifetimes, producing non-obvious cache and update behavior.
Patch Organization ⚠️ Finding The new patch is appended at the series tail despite sharing the material color-mixer seam with 'helium/ui/helium-color-mixers.patch'.

Analysis record

  • Changed files considered: 2
  • Findings: 2 new, 1 retained, 0 resolved
  • Already covered by other reviewers: 1
  • Source snapshot: d9de58346842
  • Codex: gpt-5.6-sol, reasoning high, fast mode on, web search cached
  • Patch application: clean
  • Chromium context tool calls: none
  • Maintainer conventions applied: ignore-disabled-telemetry, script-resource-lifecycle, ui-command-contexts, verify-runtime-selection-paths

Limitations

  • None reported.

non_client_view()->set_context_menu_controller(this);
+ }
+
+ if (browser->GetType() == BrowserWindowInterface::Type::TYPE_NORMAL &&

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium: Preserve the incognito window color mode

Incognito tabbed windows also have TYPE_NORMAL, so they construct AdaptiveTabColor. Because incognito has no custom_theme, GetColorProviderKey() accepts the adaptive color, and ColorProviderManager can replace Chromium’s forced dark incognito color_mode with a light mode derived from the page. A light-themed site can therefore make an incognito window resemble a normal window, weakening an intentional privacy-mode distinction. Exclude IsIncognitoBrowser() here; that existing helper also preserves Helium’s explicit enable-incognito-themes opt-in behavior.

Evidence: ThemeService::GetColorProviderKey() forces incognito profiles to ColorMode::kDark, but this new condition enables adaptation for every TYPE_NORMAL window without checking IsIncognitoBrowser().

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My recent commit added !IsIncognitoBrowser() to the setup check in BrowserWidget::InitBrowserWidget(). Incognito windows now skip AdaptiveTabColor initialization and preserve default dark framing

Comment thread patches/series Outdated
helium/ui/compact-debugger-warning.patch
helium/ui/progress-bar.patch
helium/ui/exclusive-access-bubble.patch
helium/ui/adaptive-tab-color.patch

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium: Place the adaptive-color patch with the theming stack

The patch is appended after unrelated debugger, progress-bar, and exclusive-access patches even though it directly edits the same AddMaterialChromeColorMixer() area established by helium/ui/helium-color-mixers.patch and feeds the frame-color consumers that follow it. Move helium/ui/adaptive-tab-color.patch immediately after helium/ui/helium-color-mixers.patch; this keeps the shared color-mixer rebase surface together and places the provider prerequisite before the neighboring frame consumers.

Evidence: The new entry is at the end of patches/series, while helium/ui/helium-color-mixers.patch is at line 317 and owns the toolbar-color mixer hunk adapted by this patch.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved helium/ui/adaptive-tab-color.patch directly after helium/ui/helium-color-mixers.patch in series to group the mixer modifications together in my most recent commit

@elricfd

elricfd commented Sep 23, 2026

Copy link
Copy Markdown
Author

Bump, all comments resolved (?)

@elricfd elricfd left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Responded to all comments. The changes are in line with what my goal was with this feature.

@elricfd
elricfd force-pushed the adaptive-tab-color branch 2 times, most recently from 44143de to a7bbbad Compare September 23, 2026 22:23
@elricfd

elricfd commented Sep 23, 2026

Copy link
Copy Markdown
Author

Bump, handled Chromium update merge.
Based off a commit not pushed a month ago

  • Sample 16px strip along top edge of rendered page
  • Pick most common quantized color so text and logos don't skew result
  • Keep theme-color unless rendered page has opposite lightness (Dark Reader)
  • Use rendered color on pages without theme-color, CSS background as fallback
  • Allow chrome://new-tab-page so NTP background image sets tab color
  • Re-sample 1s after load to catch NTP image fade-in
  • Retry up to 5 times while tab has no surface to copy yet
  • Keep last rendered color when copies fail, e.g. occluded window

@wukko

wukko commented Sep 24, 2026

Copy link
Copy Markdown
Member

could you put it behind a flag and attach screenshots of how it looks like in other layouts, such as vertical?

the first screenshot shows that the location bar and selected tab are the same color as the background, but that shouldn't be the case.

@elricfd

elricfd commented Sep 26, 2026

Copy link
Copy Markdown
Author

could you put it behind a flag and attach screenshots of how it looks like in other layouts, such as vertical?

the first screenshot shows that the location bar and selected tab are the same color as the background, but that shouldn't be the case.

put it behind chrome://flags/#helium-adaptive-tab-color, off by default. location bar and selected tab are now derived from the page color with a minimum contrast, so they no longer blend into the tinted frame. will attach screenshots momentarily

@elricfd

elricfd commented Sep 27, 2026

Copy link
Copy Markdown
Author

could you put it behind a flag and attach screenshots of how it looks like in other layouts, such as vertical?

the first screenshot shows that the location bar and selected tab are the same color as the background, but that shouldn't be the case.

Screenshots attached:

Screenshot 2026-09-26 at 10 10 14 PM Screenshot 2026-09-26 at 10 10 41 PM Screenshot 2026-09-26 at 10 11 11 PM

@elricfd

elricfd commented Sep 27, 2026

Copy link
Copy Markdown
Author

Forgot to attach the vertical and dynamic screenshots:
Vertical
Screenshot 2026-09-26 at 11 54 50 PM
Screenshot 2026-09-26 at 11 55 18 PM
Screenshot 2026-09-26 at 11 55 34 PM

Dynamic
Screenshot 2026-09-26 at 11 56 03 PM
Screenshot 2026-09-26 at 11 56 17 PM
Screenshot 2026-09-26 at 11 56 32 PM

@elricfd elricfd left a comment •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After deliberating with the bots, I am happy with the final code.

Merged with the main branch, made improvements to the adaptive color's responsiveness to theme-changing extensions, among other things.

- Tint frame and toolbar with active page's color
- Add chrome://flags#helium-adaptive-tab-color, off by default
- Prefer theme-color, use rendered top edge when page has none
- Use rendered color if theme-color has opposite lightness (Dark Reader)
- Cover NTP background image, re-sample 1s after load for fade-ins
- Pick light or dark mixers from color, blend it for text contrast
- Keep location bar and selected tab distinct from tinted background
- Skip incognito, app windows, custom themes and high contrast modes
- Cap distinct colors per page at 64 to bound color provider cache
- Keep adaptive color out of renderer color maps
- Derive root scrollbar scheme from page scheme, not toolbar color
With the grayscale theme on macOS, the native postprocessing mixer
converts every ui color to grayscale when the system accent is graphite.
That turned the adaptive frame color, and the compact tab strip painted
with it, grey while the toolbar kept the page color.
@elricfd
elricfd force-pushed the adaptive-tab-color branch from 7f4c727 to c309eb6 Compare October 20, 2026 06:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FR]: Support theme-color meta tag for adaptive browser UI

3 participants