Skip to content

Commit 0d16be9

Browse files
feat: add nativeTheme.themeSource to allow apps to override Chromiums theme choice (electron#19960)
* feat: add nativeTheme.shouldUseDarkColorsOverride to allow apps to override Chromiums theme choice * spec: add tests for shouldUseDarkColorsOverride * chore: add missing forward declarations * refactor: rename overrideShouldUseDarkColors to themeSource * chore: only run appLevelAppearance specs on Mojave and up * chore: update patch with more info and no define * Update spec-main/api-native-theme-spec.ts Co-Authored-By: Jeremy Apthorp <[email protected]> * Update api-native-theme-spec.ts * Update api-native-theme-spec.ts * Update api-native-theme-spec.ts
1 parent 1376229 commit 0d16be9

10 files changed

+294
-3
lines changed

Diff for: docs/api/native-theme.md

+32-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,38 @@ The `nativeTheme` module has the following properties:
2222
### `nativeTheme.shouldUseDarkColors` _Readonly_
2323

2424
A `Boolean` for if the OS / Chromium currently has a dark mode enabled or is
25-
being instructed to show a dark-style UI.
25+
being instructed to show a dark-style UI. If you want to modify this value you
26+
should use `themeSource` below.
27+
28+
### `nativeTheme.themeSource`
29+
30+
A `String` property that can be `system`, `light` or `dark`. It is used to override and supercede
31+
the value that Chromium has chosen to use internally.
32+
33+
Setting this property to `system` will remove the override and
34+
everything will be reset to the OS default. By default `themeSource` is `system`.
35+
36+
Settings this property to `dark` will have the following effects:
37+
* `nativeTheme.shouldUseDarkColors` will be `true` when accessed
38+
* Any UI Electron renders on Linux and Windows including context menus, devtools, etc. will use the dark UI.
39+
* Any UI the OS renders on macOS including menus, window frames, etc. will use the dark UI.
40+
* The [`prefers-color-scheme`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) CSS query will match `dark` mode.
41+
* The `updated` event will be emitted
42+
43+
Settings this property to `light` will have the following effects:
44+
* `nativeTheme.shouldUseDarkColors` will be `false` when accessed
45+
* Any UI Electron renders on Linux and Windows including context menus, devtools, etc. will use the light UI.
46+
* Any UI the OS renders on macOS including menus, window frames, etc. will use the light UI.
47+
* The [`prefers-color-scheme`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) CSS query will match `light` mode.
48+
* The `updated` event will be emitted
49+
50+
The usage of this property should align with a classic "dark mode" state machine in your application
51+
where the user has three options.
52+
* `Follow OS` --> `themeSource = 'system'`
53+
* `Dark Mode` --> `themeSource = 'dark'`
54+
* `Light Mode` --> `themeSource = 'light'`
55+
56+
Your application should then always use `shouldUseDarkColors` to determine what CSS to apply.
2657

2758
### `nativeTheme.shouldUseHighContrastColors` _macOS_ _Windows_ _Readonly_
2859

Diff for: filenames.gni

+1
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,7 @@ filenames = {
429429
"shell/common/api/atom_api_native_image_mac.mm",
430430
"shell/common/api/atom_api_native_theme.cc",
431431
"shell/common/api/atom_api_native_theme.h",
432+
"shell/common/api/atom_api_native_theme_mac.mm",
432433
"shell/common/api/atom_api_shell.cc",
433434
"shell/common/api/atom_api_v8_util.cc",
434435
"shell/common/api/electron_bindings.cc",

Diff for: package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"@types/fs-extra": "^5.0.5",
1717
"@types/mocha": "^5.2.6",
1818
"@types/node": "^12.0.10",
19+
"@types/semver": "^6.0.1",
1920
"@types/send": "^0.14.5",
2021
"@types/split": "^1.0.0",
2122
"@types/webpack": "^4.4.32",
@@ -130,4 +131,4 @@
130131
"git add filenames.auto.gni"
131132
]
132133
}
133-
}
134+
}

Diff for: patches/chromium/.patches

+1
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,4 @@ picture-in-picture.patch
7777
disable_compositor_recycling.patch
7878
allow_new_privileges_in_unsandboxed_child_processes.patch
7979
expose_setuseragent_on_networkcontext.patch
80+
feat_add_set_theme_source_to_allow_apps_to.patch
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
2+
From: Samuel Attard <[email protected]>
3+
Date: Mon, 26 Aug 2019 14:32:41 -0700
4+
Subject: feat: add set_theme_source to allow apps to override chromiums
5+
internal theme choice
6+
7+
This patch is required as Chromium doesn't currently let folks using
8+
//ui override the theme choice in NativeTheme. It defaults to
9+
respecting the OS theme choice and some apps don't always want to do
10+
that. With this patch we can override the theme value that Chromium
11+
uses internally for things like menus and devtools.
12+
13+
We can remove this patch once it has in some shape been upstreamed.
14+
15+
diff --git a/ui/native_theme/native_theme.cc b/ui/native_theme/native_theme.cc
16+
index 2370d15332c8c6c7dc7e3403b38891c885704d9f..171214379437f319d3feccc289a5d91e74b77f9e 100644
17+
--- a/ui/native_theme/native_theme.cc
18+
+++ b/ui/native_theme/native_theme.cc
19+
@@ -40,6 +40,8 @@ NativeTheme::NativeTheme()
20+
NativeTheme::~NativeTheme() = default;
21+
22+
bool NativeTheme::ShouldUseDarkColors() const {
23+
+ if (theme_source() == ThemeSource::kForcedLight) return false;
24+
+ if (theme_source() == ThemeSource::kForcedDark) return true;
25+
return should_use_dark_colors_;
26+
}
27+
28+
diff --git a/ui/native_theme/native_theme.h b/ui/native_theme/native_theme.h
29+
index 70389e0245993faa2c17e9deefeb000280ef2368..cef1c0d4706e7e720a4004ca54765a39fc29c5e8 100644
30+
--- a/ui/native_theme/native_theme.h
31+
+++ b/ui/native_theme/native_theme.h
32+
@@ -429,6 +429,22 @@ class NATIVE_THEME_EXPORT NativeTheme {
33+
ColorId color_id,
34+
ColorScheme color_scheme = ColorScheme::kDefault) const = 0;
35+
36+
+ enum ThemeSource {
37+
+ kSystem,
38+
+ kForcedDark,
39+
+ kForcedLight,
40+
+ };
41+
+
42+
+ ThemeSource theme_source() const {
43+
+ return theme_source_;
44+
+ }
45+
+
46+
+ void set_theme_source(ThemeSource theme_source) {
47+
+ bool original = ShouldUseDarkColors();
48+
+ theme_source_ = theme_source;
49+
+ if (ShouldUseDarkColors() != original) NotifyObservers();
50+
+ }
51+
+
52+
// Returns a shared instance of the native theme that should be used for web
53+
// rendering. Do not use it in a normal application context (i.e. browser).
54+
// The returned object should not be deleted by the caller. This function is
55+
@@ -547,6 +563,8 @@ class NATIVE_THEME_EXPORT NativeTheme {
56+
PreferredColorScheme preferred_color_scheme_ =
57+
PreferredColorScheme::kNoPreference;
58+
59+
+ ThemeSource theme_source_ = ThemeSource::kSystem;
60+
+
61+
DISALLOW_COPY_AND_ASSIGN(NativeTheme);
62+
};
63+
64+
diff --git a/ui/native_theme/native_theme_dark_aura.cc b/ui/native_theme/native_theme_dark_aura.cc
65+
index a8fbfee3b13672902aac05fd5a65fa8ee81f9f7e..1be6369acf0b7c02a6f862636c2b2de1fbf8cb5a 100644
66+
--- a/ui/native_theme/native_theme_dark_aura.cc
67+
+++ b/ui/native_theme/native_theme_dark_aura.cc
68+
@@ -20,6 +20,8 @@ SkColor NativeThemeDarkAura::GetSystemColor(ColorId color_id,
69+
}
70+
71+
bool NativeThemeDarkAura::ShouldUseDarkColors() const {
72+
+ if (theme_source() == ThemeSource::kForcedLight) return false;
73+
+ if (theme_source() == ThemeSource::kForcedDark) return true;
74+
return true;
75+
}
76+
77+
diff --git a/ui/native_theme/native_theme_win.cc b/ui/native_theme/native_theme_win.cc
78+
index 3003643bfb78cec2f5e84fc9e1471e1ef54aae41..06f2cbc84401958d49445f4ce6acb1b2fef0aa04 100644
79+
--- a/ui/native_theme/native_theme_win.cc
80+
+++ b/ui/native_theme/native_theme_win.cc
81+
@@ -611,6 +611,8 @@ bool NativeThemeWin::ShouldUseDarkColors() const {
82+
// ...unless --force-dark-mode was specified in which case caveat emptor.
83+
if (UsesHighContrastColors() && !IsForcedDarkMode())
84+
return false;
85+
+ if (theme_source() == ThemeSource::kForcedLight) return false;
86+
+ if (theme_source() == ThemeSource::kForcedDark) return true;
87+
return NativeTheme::ShouldUseDarkColors();
88+
}
89+

Diff for: shell/common/api/atom_api_native_theme.cc

+56
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
#include "shell/common/api/atom_api_native_theme.h"
66

7+
#include <string>
8+
79
#include "native_mate/dictionary.h"
810
#include "native_mate/object_template_builder.h"
911
#include "shell/common/node_includes.h"
@@ -28,6 +30,20 @@ void NativeTheme::OnNativeThemeUpdated(ui::NativeTheme* theme) {
2830
Emit("updated");
2931
}
3032

33+
void NativeTheme::SetThemeSource(ui::NativeTheme::ThemeSource override) {
34+
theme_->set_theme_source(override);
35+
#if defined(OS_MACOSX)
36+
// Update the macOS appearance setting for this new override value
37+
UpdateMacOSAppearanceForOverrideValue(override);
38+
#endif
39+
// TODO(MarshallOfSound): Update all existing browsers windows to use GTK dark
40+
// theme
41+
}
42+
43+
ui::NativeTheme::ThemeSource NativeTheme::GetThemeSource() const {
44+
return theme_->theme_source();
45+
}
46+
3147
bool NativeTheme::ShouldUseDarkColors() {
3248
return theme_->ShouldUseDarkColors();
3349
}
@@ -68,6 +84,8 @@ void NativeTheme::BuildPrototype(v8::Isolate* isolate,
6884
prototype->SetClassName(mate::StringToV8(isolate, "NativeTheme"));
6985
mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
7086
.SetProperty("shouldUseDarkColors", &NativeTheme::ShouldUseDarkColors)
87+
.SetProperty("themeSource", &NativeTheme::GetThemeSource,
88+
&NativeTheme::SetThemeSource)
7189
.SetProperty("shouldUseHighContrastColors",
7290
&NativeTheme::ShouldUseHighContrastColors)
7391
.SetProperty("shouldUseInvertedColorScheme",
@@ -94,4 +112,42 @@ void Initialize(v8::Local<v8::Object> exports,
94112

95113
} // namespace
96114

115+
namespace mate {
116+
117+
v8::Local<v8::Value> Converter<ui::NativeTheme::ThemeSource>::ToV8(
118+
v8::Isolate* isolate,
119+
const ui::NativeTheme::ThemeSource& val) {
120+
switch (val) {
121+
case ui::NativeTheme::ThemeSource::kForcedDark:
122+
return mate::ConvertToV8(isolate, "dark");
123+
case ui::NativeTheme::ThemeSource::kForcedLight:
124+
return mate::ConvertToV8(isolate, "light");
125+
case ui::NativeTheme::ThemeSource::kSystem:
126+
default:
127+
return mate::ConvertToV8(isolate, "system");
128+
}
129+
}
130+
131+
bool Converter<ui::NativeTheme::ThemeSource>::FromV8(
132+
v8::Isolate* isolate,
133+
v8::Local<v8::Value> val,
134+
ui::NativeTheme::ThemeSource* out) {
135+
std::string theme_source;
136+
if (mate::ConvertFromV8(isolate, val, &theme_source)) {
137+
if (theme_source == "dark") {
138+
*out = ui::NativeTheme::ThemeSource::kForcedDark;
139+
} else if (theme_source == "light") {
140+
*out = ui::NativeTheme::ThemeSource::kForcedLight;
141+
} else if (theme_source == "system") {
142+
*out = ui::NativeTheme::ThemeSource::kSystem;
143+
} else {
144+
return false;
145+
}
146+
return true;
147+
}
148+
return false;
149+
}
150+
151+
} // namespace mate
152+
97153
NODE_LINKED_MODULE_CONTEXT_AWARE(atom_common_native_theme, Initialize)

Diff for: shell/common/api/atom_api_native_theme.h

+20
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include "native_mate/handle.h"
99
#include "shell/browser/api/event_emitter.h"
10+
#include "ui/native_theme/native_theme.h"
1011
#include "ui/native_theme/native_theme_observer.h"
1112

1213
namespace electron {
@@ -25,6 +26,12 @@ class NativeTheme : public mate::EventEmitter<NativeTheme>,
2526
NativeTheme(v8::Isolate* isolate, ui::NativeTheme* theme);
2627
~NativeTheme() override;
2728

29+
void SetThemeSource(ui::NativeTheme::ThemeSource override);
30+
#if defined(OS_MACOSX)
31+
void UpdateMacOSAppearanceForOverrideValue(
32+
ui::NativeTheme::ThemeSource override);
33+
#endif
34+
ui::NativeTheme::ThemeSource GetThemeSource() const;
2835
bool ShouldUseDarkColors();
2936
bool ShouldUseHighContrastColors();
3037
bool ShouldUseInvertedColorScheme();
@@ -42,4 +49,17 @@ class NativeTheme : public mate::EventEmitter<NativeTheme>,
4249

4350
} // namespace electron
4451

52+
namespace mate {
53+
54+
template <>
55+
struct Converter<ui::NativeTheme::ThemeSource> {
56+
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
57+
const ui::NativeTheme::ThemeSource& val);
58+
static bool FromV8(v8::Isolate* isolate,
59+
v8::Local<v8::Value> val,
60+
ui::NativeTheme::ThemeSource* out);
61+
};
62+
63+
} // namespace mate
64+
4565
#endif // SHELL_COMMON_API_ATOM_API_NATIVE_THEME_H_

Diff for: shell/common/api/atom_api_native_theme_mac.mm

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright (c) 2019 Slack Technologies, Inc.
2+
// Use of this source code is governed by the MIT license that can be
3+
// found in the LICENSE file.
4+
5+
#include "shell/common/api/atom_api_native_theme.h"
6+
7+
#include "base/mac/sdk_forward_declarations.h"
8+
#include "shell/browser/mac/atom_application.h"
9+
10+
namespace electron {
11+
12+
namespace api {
13+
14+
void NativeTheme::UpdateMacOSAppearanceForOverrideValue(
15+
ui::NativeTheme::ThemeSource override) {
16+
if (@available(macOS 10.14, *)) {
17+
NSAppearance* new_appearance;
18+
switch (override) {
19+
case ui::NativeTheme::ThemeSource::kForcedDark:
20+
new_appearance =
21+
[NSAppearance appearanceNamed:NSAppearanceNameDarkAqua];
22+
break;
23+
case ui::NativeTheme::ThemeSource::kForcedLight:
24+
new_appearance = [NSAppearance appearanceNamed:NSAppearanceNameAqua];
25+
break;
26+
case ui::NativeTheme::ThemeSource::kSystem:
27+
default:
28+
new_appearance = nil;
29+
break;
30+
}
31+
[[NSApplication sharedApplication] setAppearance:new_appearance];
32+
}
33+
}
34+
35+
} // namespace api
36+
37+
} // namespace electron

Diff for: spec-main/api-native-theme-spec.ts

+51-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { expect } from 'chai'
2-
import { nativeTheme } from 'electron'
2+
import { nativeTheme, systemPreferences } from 'electron'
3+
import * as os from 'os'
4+
import * as semver from 'semver'
5+
6+
import { ifdescribe } from './spec-helpers'
37

48
describe('nativeTheme module', () => {
59
describe('nativeTheme.shouldUseDarkColors', () => {
@@ -8,6 +12,52 @@ describe('nativeTheme module', () => {
812
})
913
})
1014

15+
describe('nativeTheme.themeSource', () => {
16+
afterEach(() => {
17+
nativeTheme.themeSource = 'system'
18+
})
19+
20+
it('is system by default', () => {
21+
expect(nativeTheme.themeSource).to.equal('system')
22+
})
23+
24+
it('should override the value of shouldUseDarkColors', () => {
25+
nativeTheme.themeSource = 'dark'
26+
expect(nativeTheme.shouldUseDarkColors).to.equal(true)
27+
nativeTheme.themeSource = 'light'
28+
expect(nativeTheme.shouldUseDarkColors).to.equal(false)
29+
})
30+
31+
it('should emit the "updated" event when it is set and the resulting "shouldUseDarkColors" value changes', () => {
32+
nativeTheme.themeSource = 'dark'
33+
let called = false
34+
nativeTheme.once('updated', () => {
35+
called = true
36+
})
37+
nativeTheme.themeSource = 'light'
38+
expect(called).to.equal(true)
39+
})
40+
41+
it('should not emit the "updated" event when it is set and the resulting "shouldUseDarkColors" value is the same', () => {
42+
nativeTheme.themeSource = 'dark'
43+
let called = false
44+
nativeTheme.once('updated', () => {
45+
called = true
46+
})
47+
nativeTheme.themeSource = 'dark'
48+
expect(called).to.equal(false)
49+
})
50+
51+
ifdescribe(process.platform === 'darwin' && semver.gte(os.release(), '18.0.0'))('on macOS 10.14', () => {
52+
it('should update appLevelAppearance when set', () => {
53+
nativeTheme.themeSource = 'dark'
54+
expect(systemPreferences.appLevelAppearance).to.equal('dark')
55+
nativeTheme.themeSource = 'light'
56+
expect(systemPreferences.appLevelAppearance).to.equal('light')
57+
})
58+
})
59+
})
60+
1161
describe('nativeTheme.shouldUseInvertedColorScheme', () => {
1262
it('returns a boolean', () => {
1363
expect(nativeTheme.shouldUseInvertedColorScheme).to.be.a('boolean')

Diff for: yarn.lock

+5
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,11 @@
261261
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
262262
integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==
263263

264+
"@types/semver@^6.0.1":
265+
version "6.0.1"
266+
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-6.0.1.tgz#a984b405c702fa5a7ec6abc56b37f2ba35ef5af6"
267+
integrity sha512-ffCdcrEE5h8DqVxinQjo+2d1q+FV5z7iNtPofw3JsrltSoSVlOGaW0rY8XxtO9XukdTn8TaCGWmk2VFGhI70mg==
268+
264269
"@types/send@^0.14.5":
265270
version "0.14.5"
266271
resolved "https://registry.yarnpkg.com/@types/send/-/send-0.14.5.tgz#653f7d25b93c3f7f51a8994addaf8a229de022a7"

0 commit comments

Comments
 (0)