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