1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/themes/theme_helper_win.h"
6
7 #include "base/bind.h"
8 #include "base/callback.h"
9 #include "base/win/windows_version.h"
10 #include "chrome/browser/themes/theme_properties.h"
11 #include "chrome/browser/win/titlebar_config.h"
12 #include "chrome/grit/theme_resources.h"
13 #include "skia/ext/skia_utils_win.h"
14 #include "ui/base/win/shell.h"
15 #include "ui/gfx/color_utils.h"
16 #include "ui/native_theme/native_theme.h"
17 #include "ui/views/views_features.h"
18
19 namespace {
20
GetDefaultInactiveFrameColor()21 SkColor GetDefaultInactiveFrameColor() {
22 return base::win::GetVersion() < base::win::Version::WIN10
23 ? SkColorSetRGB(0xEB, 0xEB, 0xEB)
24 : SK_ColorWHITE;
25 }
26
27 } // namespace
28
ThemeHelperWin()29 ThemeHelperWin::ThemeHelperWin() {
30 // This just checks for Windows 8+ instead of calling DwmColorsAllowed()
31 // because we want to monitor the frame color even when a custom frame is in
32 // use, so that it will be correct if at any time the user switches to the
33 // native frame.
34 if (base::win::GetVersion() >= base::win::Version::WIN8) {
35 dwm_key_.reset(new base::win::RegKey(
36 HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\DWM", KEY_READ));
37 if (dwm_key_->Valid())
38 OnDwmKeyUpdated();
39 else
40 dwm_key_.reset();
41 }
42 }
43
44 ThemeHelperWin::~ThemeHelperWin() = default;
45
ShouldUseNativeFrame(const CustomThemeSupplier * theme_supplier) const46 bool ThemeHelperWin::ShouldUseNativeFrame(
47 const CustomThemeSupplier* theme_supplier) const {
48 const bool use_native_frame_if_enabled =
49 ShouldCustomDrawSystemTitlebar() ||
50 !HasCustomImage(IDR_THEME_FRAME, theme_supplier);
51 return use_native_frame_if_enabled && ui::win::IsAeroGlassEnabled();
52 }
53
ShouldUseIncreasedContrastThemeSupplier(ui::NativeTheme * native_theme) const54 bool ThemeHelperWin::ShouldUseIncreasedContrastThemeSupplier(
55 ui::NativeTheme* native_theme) const {
56 // On Windows the platform provides the high contrast colors, so don't use the
57 // IncreasedContrastThemeSupplier.
58 return false;
59 }
60
GetDefaultColor(int id,bool incognito,const CustomThemeSupplier * theme_supplier) const61 SkColor ThemeHelperWin::GetDefaultColor(
62 int id,
63 bool incognito,
64 const CustomThemeSupplier* theme_supplier) const {
65 // In high contrast mode on Windows the platform provides the color. Try to
66 // get that color first.
67 SkColor color;
68 if (ui::NativeTheme::GetInstanceForNativeUi()->UsesHighContrastColors() &&
69 GetPlatformHighContrastColor(id, &color)) {
70 return color;
71 }
72
73 if (DwmColorsAllowed(theme_supplier)) {
74 // In Windows 10, native inactive borders are #555555 with 50% alpha.
75 // Prior to version 1809, native active borders use the accent color.
76 // In version 1809 and following, the active border is #262626 with 66%
77 // alpha unless the accent color is also used for the frame.
78 if (id == ThemeProperties::COLOR_ACCENT_BORDER_ACTIVE) {
79 return (base::win::GetVersion() >= base::win::Version::WIN10_RS5 &&
80 !dwm_frame_color_)
81 ? SkColorSetARGB(0xa8, 0x26, 0x26, 0x26)
82 : dwm_accent_border_color_;
83 }
84 if (id == ThemeProperties::COLOR_ACCENT_BORDER_INACTIVE)
85 return SkColorSetARGB(0x80, 0x55, 0x55, 0x55);
86
87 // When we're custom-drawing the titlebar we want to use either the colors
88 // we calculated in OnDwmKeyUpdated() or the default colors. When we're not
89 // custom-drawing the titlebar we want to match the color Windows actually
90 // uses because some things (like the incognito icon) use this color to
91 // decide whether they should draw in light or dark mode. Incognito colors
92 // should be the same as non-incognito in all cases here.
93 if (id == ThemeProperties::COLOR_FRAME_ACTIVE) {
94 if (dwm_frame_color_)
95 return dwm_frame_color_.value();
96 if (!ShouldCustomDrawSystemTitlebar())
97 return SK_ColorWHITE;
98 // Fall through and use default.
99 }
100 if (id == ThemeProperties::COLOR_FRAME_INACTIVE) {
101 if (!ShouldCustomDrawSystemTitlebar()) {
102 return inactive_frame_color_from_registry_
103 ? dwm_inactive_frame_color_.value()
104 : GetDefaultInactiveFrameColor();
105 }
106 if (dwm_frame_color_ && !inactive_frame_color_from_registry_) {
107 // Tint to create inactive color. Always use the non-incognito version
108 // of the tint, since the frame should look the same in both modes.
109 return color_utils::HSLShift(
110 dwm_frame_color_.value(),
111 GetTint(ThemeProperties::TINT_FRAME_INACTIVE, false,
112 theme_supplier));
113 }
114 if (dwm_inactive_frame_color_)
115 return dwm_inactive_frame_color_.value();
116 // Fall through and use default.
117 }
118 }
119
120 return ThemeHelper::GetDefaultColor(id, incognito, theme_supplier);
121 }
122
GetPlatformHighContrastColor(int id,SkColor * color) const123 bool ThemeHelperWin::GetPlatformHighContrastColor(int id,
124 SkColor* color) const {
125 ui::NativeTheme::SystemThemeColor system_theme_color =
126 ui::NativeTheme::SystemThemeColor::kNotSupported;
127
128 switch (id) {
129 // Window Background
130 case ThemeProperties::COLOR_FRAME_ACTIVE:
131 case ThemeProperties::COLOR_FRAME_ACTIVE_INCOGNITO:
132 case ThemeProperties::COLOR_FRAME_INACTIVE:
133 case ThemeProperties::COLOR_FRAME_INACTIVE_INCOGNITO:
134 case ThemeProperties::COLOR_TAB_BACKGROUND_ACTIVE_FRAME_ACTIVE:
135 case ThemeProperties::COLOR_TAB_BACKGROUND_ACTIVE_FRAME_ACTIVE_INCOGNITO:
136 case ThemeProperties::COLOR_TAB_BACKGROUND_ACTIVE_FRAME_INACTIVE:
137 case ThemeProperties::COLOR_TAB_BACKGROUND_ACTIVE_FRAME_INACTIVE_INCOGNITO:
138 case ThemeProperties::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_ACTIVE:
139 case ThemeProperties::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_ACTIVE_INCOGNITO:
140 case ThemeProperties::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_INACTIVE:
141 case ThemeProperties::
142 COLOR_TAB_BACKGROUND_INACTIVE_FRAME_INACTIVE_INCOGNITO:
143 case ThemeProperties::COLOR_DOWNLOAD_SHELF:
144 case ThemeProperties::COLOR_INFOBAR:
145 case ThemeProperties::COLOR_TOOLBAR:
146 case ThemeProperties::COLOR_STATUS_BUBBLE:
147 system_theme_color = ui::NativeTheme::SystemThemeColor::kWindow;
148 break;
149
150 // Window Text
151 case ThemeProperties::COLOR_TOOLBAR_VERTICAL_SEPARATOR:
152 case ThemeProperties::COLOR_TOOLBAR_TOP_SEPARATOR:
153 case ThemeProperties::COLOR_TOOLBAR_TOP_SEPARATOR_INACTIVE:
154 case ThemeProperties::COLOR_LOCATION_BAR_BORDER:
155 system_theme_color = ui::NativeTheme::SystemThemeColor::kWindowText;
156 break;
157
158 // Button Background
159 case ThemeProperties::COLOR_OMNIBOX_BACKGROUND:
160 case ThemeProperties::COLOR_OMNIBOX_BACKGROUND_HOVERED:
161 case ThemeProperties::COLOR_OMNIBOX_RESULTS_BG:
162 system_theme_color = ui::NativeTheme::SystemThemeColor::kButtonFace;
163 break;
164
165 // Button Text Foreground
166 case ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON:
167 case ThemeProperties::COLOR_BOOKMARK_TEXT:
168 case ThemeProperties::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_ACTIVE:
169 case ThemeProperties::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_ACTIVE_INCOGNITO:
170 case ThemeProperties::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_INACTIVE:
171 case ThemeProperties::
172 COLOR_TAB_FOREGROUND_INACTIVE_FRAME_INACTIVE_INCOGNITO:
173 case ThemeProperties::COLOR_OMNIBOX_TEXT:
174 case ThemeProperties::COLOR_OMNIBOX_SELECTED_KEYWORD:
175 case ThemeProperties::COLOR_OMNIBOX_BUBBLE_OUTLINE:
176 case ThemeProperties::COLOR_OMNIBOX_TEXT_DIMMED:
177 case ThemeProperties::COLOR_OMNIBOX_RESULTS_ICON:
178 case ThemeProperties::COLOR_OMNIBOX_RESULTS_URL:
179 case ThemeProperties::COLOR_OMNIBOX_RESULTS_TEXT_DIMMED:
180 case ThemeProperties::COLOR_OMNIBOX_SECURITY_CHIP_DEFAULT:
181 case ThemeProperties::COLOR_OMNIBOX_SECURITY_CHIP_SECURE:
182 case ThemeProperties::COLOR_OMNIBOX_SECURITY_CHIP_DANGEROUS:
183 system_theme_color = ui::NativeTheme::SystemThemeColor::kButtonText;
184 break;
185
186 // Highlight/Selected Background
187 case ThemeProperties::COLOR_TOOLBAR_INK_DROP:
188 if (!base::FeatureList::IsEnabled(
189 views::features::kEnablePlatformHighContrastInkDrop))
190 return false;
191 FALLTHROUGH;
192 case ThemeProperties::COLOR_OMNIBOX_RESULTS_BG_SELECTED:
193 case ThemeProperties::COLOR_OMNIBOX_RESULTS_BG_HOVERED:
194 system_theme_color = ui::NativeTheme::SystemThemeColor::kHighlight;
195 break;
196
197 // Highlight/Selected Text Foreground
198 case ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON_HOVERED:
199 case ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON_PRESSED:
200 if (!base::FeatureList::IsEnabled(
201 views::features::kEnablePlatformHighContrastInkDrop)) {
202 return GetPlatformHighContrastColor(
203 ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON, color);
204 }
205 FALLTHROUGH;
206 case ThemeProperties::COLOR_TAB_FOREGROUND_ACTIVE_FRAME_ACTIVE:
207 case ThemeProperties::COLOR_TAB_FOREGROUND_ACTIVE_FRAME_INACTIVE:
208 case ThemeProperties::COLOR_OMNIBOX_RESULTS_TEXT_SELECTED:
209 case ThemeProperties::COLOR_OMNIBOX_RESULTS_TEXT_DIMMED_SELECTED:
210 case ThemeProperties::COLOR_OMNIBOX_RESULTS_ICON_SELECTED:
211 case ThemeProperties::COLOR_OMNIBOX_RESULTS_URL_SELECTED:
212 case ThemeProperties::COLOR_OMNIBOX_RESULTS_FOCUS_BAR:
213 system_theme_color = ui::NativeTheme::SystemThemeColor::kHighlightText;
214 break;
215
216 // Gray/Disabled Text
217 case ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON_INACTIVE:
218 system_theme_color = ui::NativeTheme::SystemThemeColor::kGrayText;
219 break;
220
221 default:
222 return false;
223 }
224
225 *color = ui::NativeTheme::GetInstanceForNativeUi()
226 ->GetSystemThemeColor(system_theme_color)
227 .value();
228 return true;
229 }
230
DwmColorsAllowed(const CustomThemeSupplier * theme_supplier) const231 bool ThemeHelperWin::DwmColorsAllowed(
232 const CustomThemeSupplier* theme_supplier) const {
233 return ShouldUseNativeFrame(theme_supplier) &&
234 (base::win::GetVersion() >= base::win::Version::WIN8);
235 }
236
OnDwmKeyUpdated()237 void ThemeHelperWin::OnDwmKeyUpdated() {
238 dwm_accent_border_color_ = GetDefaultInactiveFrameColor();
239 DWORD colorization_color, colorization_color_balance;
240 if ((dwm_key_->ReadValueDW(L"ColorizationColor", &colorization_color) ==
241 ERROR_SUCCESS) &&
242 (dwm_key_->ReadValueDW(L"ColorizationColorBalance",
243 &colorization_color_balance) == ERROR_SUCCESS)) {
244 // The accent border color is a linear blend between the colorization
245 // color and the neutral #d9d9d9. colorization_color_balance is the
246 // percentage of the colorization color in that blend.
247 //
248 // On Windows version 1611 colorization_color_balance can be 0xfffffff3 if
249 // the accent color is taken from the background and either the background
250 // is a solid color or was just changed to a slideshow. It's unclear what
251 // that value's supposed to mean, so change it to 80 to match Edge's
252 // behavior.
253 if (colorization_color_balance > 100)
254 colorization_color_balance = 80;
255
256 // colorization_color's high byte is not an alpha value, so replace it
257 // with 0xff to make an opaque ARGB color.
258 SkColor input_color = SkColorSetA(colorization_color, 0xff);
259
260 dwm_accent_border_color_ =
261 color_utils::AlphaBlend(input_color, SkColorSetRGB(0xd9, 0xd9, 0xd9),
262 colorization_color_balance / 100.0f);
263 }
264
265 inactive_frame_color_from_registry_ = false;
266 if (base::win::GetVersion() < base::win::Version::WIN10) {
267 dwm_frame_color_ = dwm_accent_border_color_;
268 } else {
269 DWORD accent_color, color_prevalence;
270 bool use_dwm_frame_color =
271 dwm_key_->ReadValueDW(L"AccentColor", &accent_color) == ERROR_SUCCESS &&
272 dwm_key_->ReadValueDW(L"ColorPrevalence", &color_prevalence) ==
273 ERROR_SUCCESS &&
274 color_prevalence == 1;
275 if (use_dwm_frame_color) {
276 dwm_frame_color_ = skia::COLORREFToSkColor(accent_color);
277 DWORD accent_color_inactive;
278 if (dwm_key_->ReadValueDW(L"AccentColorInactive",
279 &accent_color_inactive) == ERROR_SUCCESS) {
280 dwm_inactive_frame_color_ =
281 skia::COLORREFToSkColor(accent_color_inactive);
282 inactive_frame_color_from_registry_ = true;
283 }
284 } else {
285 dwm_frame_color_.reset();
286 dwm_inactive_frame_color_.reset();
287 }
288 }
289
290 // Notify native theme observers that the native theme has changed.
291 ui::NativeTheme::GetInstanceForNativeUi()->NotifyObservers();
292
293 // Watch for future changes.
294 if (!dwm_key_->StartWatching(base::BindOnce(&ThemeHelperWin::OnDwmKeyUpdated,
295 base::Unretained(this)))) {
296 dwm_key_.reset();
297 }
298 }
299