tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 87357ece4212f6acb03865700f98ed99d9817e50
parent 82d9b36d9399466f1c33f6a0834d8addd890049a
Author: Emilio Cobos Álvarez <emilio@crisal.io>
Date:   Wed,  8 Oct 2025 19:13:32 +0000

Bug 1993056 - Don't maintain two versions of brand colors. r=dao,desktop-theme-reviewers,webidl,smaug

Instead special-case the built-in light and dark themes to reuse
browser-colors.

Use a built-in media query (-moz-native-theme), since otherwise checking
for it becomes quite annoying. If we didn't have the native-theme pref,
we could use an attribute selector perhaps, but this seems simpler.

Differential Revision: https://phabricator.services.mozilla.com/D267850

Diffstat:
Mbrowser/app/profile/firefox.js | 6------
Mbrowser/components/extensions/test/browser/browser_toolbar_prefers_color_scheme.js | 2+-
Mbrowser/themes/BuiltInThemeConfig.sys.mjs | 4++--
Mbrowser/themes/addons/firefox-light-dark/manifest.json | 117++-----------------------------------------------------------------------------
Mbrowser/themes/addons/gen_light_dark_manifest.py | 3---
Mbrowser/themes/osx/browser.css | 3+--
Mbrowser/themes/shared/browser-colors.css | 3+--
Mbrowser/themes/shared/browser-shared.css | 3+--
Mdom/base/Document.cpp | 13+++++++++++++
Mdom/base/Document.h | 6+++++-
Mdom/webidl/Document.webidl | 6+++++-
Mlayout/style/test/chrome/chrome-only-media-queries.js | 1+
Mmodules/libpref/init/StaticPrefList.yaml | 11+++++++++++
Mservo/components/style/gecko/media_features.rs | 15++++++++++++++-
Mtoolkit/components/extensions/test/browser/browser_ext_themes_pbm.js | 66+++++++++++++++++++++++++++++++++---------------------------------
Mtoolkit/modules/LightweightThemeConsumer.sys.mjs | 92++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mwidget/nsXPLookAndFeel.cpp | 1+
Mxpcom/ds/StaticAtoms.py | 1+
18 files changed, 141 insertions(+), 212 deletions(-)

diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js @@ -375,12 +375,6 @@ pref("browser.overlink-delay", 80); pref("browser.taskbarTabs.enabled", false); #endif -#if defined(MOZ_WIDGET_GTK) - pref("browser.theme.native-theme", true); -#else - pref("browser.theme.native-theme", false); -#endif - // Whether using `ctrl` when hitting return/enter in the URL bar // (or clicking 'go') should prefix 'www.' and suffix // browser.fixup.alternate.suffix to the URL bar value prior to diff --git a/browser/components/extensions/test/browser/browser_toolbar_prefers_color_scheme.js b/browser/components/extensions/test/browser/browser_toolbar_prefers_color_scheme.js @@ -107,7 +107,7 @@ add_task(async function dark_theme_presence_overrides_heuristics() { await testTheme( "darkTheme presence overrides heuristics", systemScheme, - kSystem, + systemScheme, { theme: { colors: { diff --git a/browser/themes/BuiltInThemeConfig.sys.mjs b/browser/themes/BuiltInThemeConfig.sys.mjs @@ -22,14 +22,14 @@ export const BuiltInThemeConfig = new Map([ [ "firefox-compact-light@mozilla.org", { - version: "1.3.3", + version: "1.3.4", path: "resource://builtin-themes/light/", }, ], [ "firefox-compact-dark@mozilla.org", { - version: "1.3.3", + version: "1.3.4", path: "resource://builtin-themes/dark/", }, ], diff --git a/browser/themes/addons/firefox-light-dark/manifest.json b/browser/themes/addons/firefox-light-dark/manifest.json @@ -10,121 +10,8 @@ "name": "Firefox Theme", "description": "A modern light and dark theme.", "author": "Mozilla", - "version": "1.3.3", + "version": "1.3.4", "icons": { "32": "icon.svg" }, - - "theme": { - "colors": { - "tab_background_text": "rgb(21,20,26)", - "tab_selected": "#fff", - "tab_text": "rgb(21,20,26)", - "icons": "rgb(91,91,102)", - "frame": "rgb(234, 234, 237)", - "frame_inactive": "rgb(240, 240, 244)", - "popup": "#fff", - "popup_text": "rgb(21,20,26)", - "popup_border": "rgb(240,240,244)", - "popup_highlight": "#e0e0e6", - "popup_highlight_text": "#15141a", - "sidebar": "rgb(255, 255, 255)", - "sidebar_text": "rgb(21, 20, 26)", - "sidebar_border": "rgba(21, 20, 26, 0.1)", - "tab_line": "transparent", - "toolbar": "#f9f9fb", - "toolbar_top_separator": "transparent", - "toolbar_bottom_separator": "rgba(21, 20, 26, 0.1)", - "toolbar_field": "rgba(0, 0, 0, .05)", - "toolbar_field_text": "rgb(21, 20, 26)", - "toolbar_field_border": "transparent", - "toolbar_field_focus": "white", - "toolbar_text": "rgb(21,20,26)", - "ntp_background": "#F9F9FB", - "ntp_text": "rgb(21, 20, 26)", - "button": "rgba(207,207,216,.33)", - "button_hover": "rgba(207,207,216,.66)", - "button_active": "rgb(207,207,216)", - "button_primary": "rgb(0, 97, 224)", - "button_primary_hover": "rgb(2, 80, 187)", - "button_primary_active": "rgb(5, 62, 148)", - "button_primary_color": "rgb(251, 251, 254)", - "input_color": "rgb(21,20,26)", - "input_background": "rgb(255,255,255)", - "urlbar_box": "white", - "urlbar_popup_hover": "rgb(240,240,244)", - "urlbar_popup_separator": "rgb(240,240,244)" - }, - "properties": { - "color_scheme": "light", - "toolbar_field_icon_opacity": "0.72", - "zap_gradient": "linear-gradient(90deg, #9059FF 0%, #FF4AA2 52.08%, #FFBD4F 100%)" - } - }, - - "dark_theme": { - "colors": { - "tab_background_text": "#fbfbfe", - "tab_selected": "rgba(106,106,120,0.7)", - "tab_text": "rgb(255,255,255)", - "icons": "rgb(251,251,254)", - "frame": "rgb(28, 27, 34)", - "frame_inactive": "rgb(31, 30, 37)", - "popup": "rgb(66,65,77)", - "popup_text": "rgb(251,251,254)", - "popup_border": "rgb(82,82,94)", - "popup_highlight": "rgb(43,42,51)", - "tab_line": "transparent", - "toolbar": "rgb(43,42,51)", - "toolbar_top_separator": "transparent", - "toolbar_bottom_separator": "rgba(251, 251, 254, 0.10)", - "toolbar_field": "rgba(0, 0, 0, .3)", - "toolbar_field_border": "transparent", - "toolbar_field_text": "rgb(251,251,254)", - "toolbar_field_focus": "rgb(66,65,77)", - "toolbar_text": "rgb(251, 251, 254)", - "ntp_background": "rgb(43, 42, 51)", - "ntp_card_background": "rgb(66,65,77)", - "ntp_text": "rgb(251, 251, 254)", - "sidebar": "rgb(28, 27, 34)", - "sidebar_text": "rgb(249, 249, 250)", - "sidebar_border": "rgba(251, 251, 254, 0.10)", - "button": "rgba(0, 0, 0, .33)", - "button_hover": "rgba(207, 207, 216, .20)", - "button_active": "rgba(207, 207, 216, .40)", - "button_primary": "rgb(0, 221, 255)", - "button_primary_hover": "rgb(128, 235, 255)", - "button_primary_active": "rgb(170, 242, 255)", - "button_primary_color": "rgb(43, 42, 51)", - "input_background": "#42414D", - "urlbar_box": "rgb(66,65,77)", - "input_color": "rgb(251,251,254)", - "urlbar_popup_separator": "rgb(82,82,94)" - }, - "properties": { - "color_scheme": "dark", - "toolbar_field_icon_opacity": "1", - "zap_gradient": "linear-gradient(90deg, #9059FF 0%, #FF4AA2 52.08%, #FFBD4F 100%)" - } - }, - - "theme_experiment": { - "colors": { - "button": "--button-background-color", - "button_hover": "--button-background-color-hover", - "button_active": "--button-background-color-active", - "button_primary": "--color-accent-primary", - "button_primary_hover": "--color-accent-primary-hover", - "button_primary_active": "--color-accent-primary-active", - "button_primary_color": "--button-text-color-primary", - "input_background": "--input-bgcolor", - "input_color": "--input-color", - "urlbar_box": "--urlbar-box-bgcolor", - "urlbar_popup_hover": "--urlbarView-hover-background", - "urlbar_popup_separator": "--urlbarView-separator-color" - }, - "properties": { - "toolbar_field_icon_opacity": "--urlbar-icon-fill-opacity", - "zap_gradient": "--panel-separator-zap-gradient" - } - } + "theme": {} } diff --git a/browser/themes/addons/gen_light_dark_manifest.py b/browser/themes/addons/gen_light_dark_manifest.py @@ -27,13 +27,10 @@ OVERRIDES = { def gen_light_dark_manifest(output_manifest, input_manifest, variant): assert variant == "light" or variant == "dark" - theme_key = "theme" if variant == "light" else "dark_theme" input = json.loads(open(input_manifest).read()) output = copy.deepcopy(input) - del output["dark_theme"] - output["theme"] = input[theme_key] output |= OVERRIDES[variant] output_manifest.write(json.dumps(output, indent=2).encode("utf-8")) diff --git a/browser/themes/osx/browser.css b/browser/themes/osx/browser.css @@ -8,8 +8,7 @@ --arrowpanel-field-background: light-dark(rgba(249, 249, 250, 0.3), rgba(12, 12, 13, 0.3)); } -/* stylelint-disable-next-line media-query-no-invalid */ -@media -moz-pref("browser.theme.native-theme") { +@media (-moz-native-theme) { @media not (prefers-contrast) { :root:not([lwtheme]) { @media (prefers-color-scheme: light) { diff --git a/browser/themes/shared/browser-colors.css b/browser/themes/shared/browser-colors.css @@ -79,8 +79,7 @@ * theme enabled, instead choosing to fall back to system colors and * transparencies in order to look more native. */ -/* stylelint-disable-next-line media-query-no-invalid */ -@media not ((prefers-contrast) or -moz-pref("browser.theme.native-theme")) { +@media not ((prefers-contrast) or (-moz-native-theme)) { :root:not([lwtheme]) { --color-accent-primary: light-dark(rgb(0, 97, 224), rgb(0, 221, 255)); --button-text-color-primary: light-dark(rgb(251, 251, 254), rgb(43, 42, 51)); diff --git a/browser/themes/shared/browser-shared.css b/browser/themes/shared/browser-shared.css @@ -215,8 +215,7 @@ body { border-bottom-style: none; } - /* stylelint-disable-next-line media-query-no-invalid */ - @media (-moz-platform: macos) and -moz-pref("browser.theme.native-theme") { + @media (-moz-platform: macos) and (-moz-native-theme) { /* This is conceptually a background, but putting this on a pseudo-element * avoids it from suppressing the chrome-content separator border, etc. * diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp @@ -1415,6 +1415,7 @@ Document::Document(const char* aContentType) mStyleSheetChangeEventsEnabled(false), mDevToolsAnonymousAndShadowEventsEnabled(false), mPausedByDevTools(false), + mForceNonNativeTheme(false), mIsSrcdocDocument(false), mHasDisplayDocument(false), mFontFaceSetDirty(true), @@ -14858,6 +14859,18 @@ NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(UnblockParsingPromiseHandler) NS_IMPL_CYCLE_COLLECTING_RELEASE(UnblockParsingPromiseHandler) +void Document::SetForceNonNativeTheme(bool aForce) { + if (mForceNonNativeTheme == aForce) { + return; + } + mForceNonNativeTheme = aForce; + if (auto* pc = GetPresContext()) { + pc->MediaFeatureValuesChanged( + {MediaFeatureChangeReason::PreferenceChange}, + MediaFeatureChangePropagation::JustThisDocument); + } +} + already_AddRefed<Promise> Document::BlockParsing( Promise& aPromise, const BlockParsingOptions& aOptions, ErrorResult& aRv) { RefPtr<Promise> resultPromise = diff --git a/dom/base/Document.h b/dom/base/Document.h @@ -3619,6 +3619,9 @@ class Document : public nsINode, void SetPausedByDevTools(bool aValue) { mPausedByDevTools = aValue; } bool PausedByDevTools() const { return mPausedByDevTools; } + void SetForceNonNativeTheme(bool); + bool ForceNonNativeTheme() const { return mForceNonNativeTheme; } + already_AddRefed<Promise> BlockParsing(Promise& aPromise, const BlockParsingOptions& aOptions, ErrorResult& aRv); @@ -4910,7 +4913,8 @@ class Document : public nsINode, // Whether DevTools is pausing the page (in which case we don't really want to // stop rendering). bool mPausedByDevTools : 1; - + // If true, (-moz-native-theme) media query always evaluates to false. + bool mForceNonNativeTheme : 1; // Whether the document was created by a srcdoc iframe. bool mIsSrcdocDocument : 1; diff --git a/dom/webidl/Document.webidl b/dom/webidl/Document.webidl @@ -248,13 +248,17 @@ partial interface Document { [ChromeOnly] readonly attribute ReferrerPolicy referrerPolicy; - /** + /** * Current referrer info, which holds all referrer related information * including referrer policy and raw referrer of document. */ [ChromeOnly] readonly attribute nsIReferrerInfo referrerInfo; + // If true, forces the (-moz-native-theme) media query to evaluate to false + // on this document. Note this doesn't propagate to subdocuments. + [ChromeOnly] + attribute boolean forceNonNativeTheme; }; // https://html.spec.whatwg.org/multipage/obsolete.html#other-elements%2C-attributes-and-apis diff --git a/layout/style/test/chrome/chrome-only-media-queries.js b/layout/style/test/chrome/chrome-only-media-queries.js @@ -15,6 +15,7 @@ const CHROME_ONLY_TOGGLES = [ "-moz-gtk-csd-close-button", "-moz-gtk-csd-reversed-placement", "-moz-panel-animations", + "-moz-native-theme", ]; // Non-parseable queries can be tested directly in diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml @@ -1808,6 +1808,17 @@ value: 2 mirror: always +# Whether (-moz-native-theme) evaluates to true by default. +- name: browser.theme.native-theme + type: RelaxedAtomicBool +#ifdef MOZ_WIDGET_GTK + value: true +#else + value: false +#endif + mirror: always + rust: true + # Communicates the preferred content theme color to platform (for e.g., # prefers-color-scheme). # diff --git a/servo/components/style/gecko/media_features.rs b/servo/components/style/gecko/media_features.rs @@ -614,6 +614,13 @@ fn eval_moz_mac_rtl(context: &Context) -> bool { unsafe { bindings::Gecko_MediaFeatures_MacRTL(context.device().document()) } } +fn eval_moz_native_theme(context: &Context) -> bool { + if context.device().document().mForceNonNativeTheme() { + return false; + } + static_prefs::pref!("browser.theme.native-theme") +} + fn get_lnf_int(int_id: i32) -> i32 { unsafe { bindings::Gecko_GetLookAndFeelInt(int_id) } } @@ -645,7 +652,7 @@ macro_rules! lnf_int_feature { /// to support new types in these entries and (2) ensuring that either /// nsPresContext::MediaFeatureValuesChanged is called when the value that /// would be returned by the evaluator function could change. -pub static MEDIA_FEATURES: [QueryFeatureDescription; 59] = [ +pub static MEDIA_FEATURES: [QueryFeatureDescription; 60] = [ feature!( atom!("width"), AllowsRanges::Yes, @@ -925,6 +932,12 @@ pub static MEDIA_FEATURES: [QueryFeatureDescription; 59] = [ Evaluator::BoolInteger(eval_moz_mac_rtl), FeatureFlags::CHROME_AND_UA_ONLY, ), + feature!( + atom!("-moz-native-theme"), + AllowsRanges::No, + Evaluator::BoolInteger(eval_moz_native_theme), + FeatureFlags::CHROME_AND_UA_ONLY, + ), lnf_int_feature!( atom!("-moz-windows-accent-color-in-titlebar"), WindowsAccentColorInTitlebar diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_pbm.js b/toolkit/components/extensions/test/browser/browser_ext_themes_pbm.js @@ -40,28 +40,28 @@ async function testIsDark(win, expectDark) { * @param {Window} options.win - Window object to test. * @param {boolean} options.colorScheme - Whether expected chrome color scheme * is dark (true) or light (false). - * @param {boolean} options.expectLWTAttributes - Whether the window should - * have the LWT attributes set matching the color scheme. + * @param {boolean} options.expectNonNativeTheme - Whether the window should not + * be using the system appearance. */ -async function testWindowColorScheme({ win, expectDark, expectLWTAttributes }) { +async function testWindowColorScheme({ + win, + expectDark, + expectNonNativeTheme, +}) { let docEl = win.document.documentElement; await testIsDark(win, expectDark); - if (expectLWTAttributes) { - ok(docEl.hasAttribute("lwtheme"), "Window should have LWT attribute."); - is( - docEl.hasAttribute("lwtheme-brighttext"), - expectDark, - "LWT text color attribute should be set." - ); - } else { - ok(!docEl.hasAttribute("lwtheme"), "Window should not have LWT attribute."); - ok( - !docEl.hasAttribute("lwtheme-brighttext"), - "LWT text color attribute should not be set." - ); - } + ok(!docEl.hasAttribute("lwtheme"), "Window should not have LWT attribute."); + ok( + !docEl.hasAttribute("lwtheme-brighttext"), + "LWT text color attribute should not be set." + ); + is( + win.document.forceNonNativeTheme, + expectNonNativeTheme, + "Window should not have LWT attribute." + ); } /** @@ -128,7 +128,7 @@ add_task(async function test_default_theme_light() { await testWindowColorScheme({ win: window, expectDark: false, - expectLWTAttributes: false, + expectNonNativeTheme: false, }); let windowB = await BrowserTestUtils.openNewBrowserWindow(); @@ -137,7 +137,7 @@ add_task(async function test_default_theme_light() { await testWindowColorScheme({ win: windowB, expectDark: false, - expectLWTAttributes: false, + expectNonNativeTheme: false, }); let pbmWindowA = await BrowserTestUtils.openNewBrowserWindow({ @@ -148,7 +148,7 @@ add_task(async function test_default_theme_light() { await testWindowColorScheme({ win: pbmWindowA, expectDark: true, - expectLWTAttributes: false, + expectNonNativeTheme: false, }); let prefersColorScheme = await getPrefersColorSchemeInfo({ win: pbmWindowA }); @@ -164,7 +164,7 @@ add_task(async function test_default_theme_light() { await testWindowColorScheme({ win: pbmWindowB, expectDark: true, - expectLWTAttributes: false, + expectNonNativeTheme: false, }); await BrowserTestUtils.closeWindow(windowB); @@ -183,7 +183,7 @@ add_task(async function test_default_theme_dark() { await testWindowColorScheme({ win: window, expectDark: true, - expectLWTAttributes: false, + expectNonNativeTheme: false, }); let pbmWindow = await BrowserTestUtils.openNewBrowserWindow({ @@ -194,7 +194,7 @@ add_task(async function test_default_theme_dark() { await testWindowColorScheme({ win: pbmWindow, expectDark: true, - expectLWTAttributes: false, + expectNonNativeTheme: false, }); await BrowserTestUtils.closeWindow(pbmWindow); @@ -212,7 +212,7 @@ add_task(async function test_light_theme_builtin() { await testWindowColorScheme({ win: window, expectDark: false, - expectLWTAttributes: true, + expectNonNativeTheme: true, }); let pbmWindow = await BrowserTestUtils.openNewBrowserWindow({ @@ -222,7 +222,7 @@ add_task(async function test_light_theme_builtin() { await testWindowColorScheme({ win: pbmWindow, expectDark: false, - expectLWTAttributes: true, + expectNonNativeTheme: true, }); await BrowserTestUtils.closeWindow(pbmWindow); @@ -239,7 +239,7 @@ add_task(async function test_dark_theme_builtin() { await testWindowColorScheme({ win: window, expectDark: true, - expectLWTAttributes: true, + expectNonNativeTheme: true, }); let pbmWindow = await BrowserTestUtils.openNewBrowserWindow({ @@ -250,7 +250,7 @@ add_task(async function test_dark_theme_builtin() { await testWindowColorScheme({ win: pbmWindow, expectDark: true, - expectLWTAttributes: true, + expectNonNativeTheme: true, }); await BrowserTestUtils.closeWindow(pbmWindow); @@ -269,14 +269,14 @@ add_task(async function test_theme_switch_updates_existing_pbm_win() { await testWindowColorScheme({ win: window, expectDark: false, - expectLWTAttributes: false, + expectNonNativeTheme: false, }); info("Private browsing window should be in dark mode."); await testWindowColorScheme({ win: pbmWindow, expectDark: true, - expectLWTAttributes: false, + expectNonNativeTheme: false, }); info("Enabling light theme."); @@ -287,14 +287,14 @@ add_task(async function test_theme_switch_updates_existing_pbm_win() { await testWindowColorScheme({ win: window, expectDark: false, - expectLWTAttributes: true, + expectNonNativeTheme: true, }); info("Private browsing window should not be in dark mode."); await testWindowColorScheme({ win: pbmWindow, expectDark: false, - expectLWTAttributes: true, + expectNonNativeTheme: true, }); await lightTheme.disable(); @@ -307,14 +307,14 @@ add_task(async function test_theme_switch_updates_existing_pbm_win() { await testWindowColorScheme({ win: window, expectDark: true, - expectLWTAttributes: true, + expectNonNativeTheme: true, }); info("Private browsing window should be in dark mode."); await testWindowColorScheme({ win: pbmWindow, expectDark: true, - expectLWTAttributes: true, + expectNonNativeTheme: true, }); await darkTheme.disable(); diff --git a/toolkit/modules/LightweightThemeConsumer.sys.mjs b/toolkit/modules/LightweightThemeConsumer.sys.mjs @@ -23,6 +23,17 @@ XPCOMUtils.defineLazyPreferenceGetter( ); const DEFAULT_THEME_ID = "default-theme@mozilla.org"; +const kDefaultThemes = { + [DEFAULT_THEME_ID]: {}, + "firefox-compact-light@mozilla.org": { + colorScheme: "light", + forceNonNative: true, + }, + "firefox-compact-dark@mozilla.org": { + colorScheme: "dark", + forceNonNative: true, + }, +}; const toolkitVariableMap = [ [ @@ -298,39 +309,38 @@ LightweightThemeConsumer.prototype = { // _determineToolbarAndContentTheme, because it applies the color scheme // globally for all windows. Skipping this method also means we don't // switch the content theme to dark. - // - // TODO: On Linux we most likely need to apply the dark theme, but on - // Windows and macOS we should be able to render light and dark windows - // with the default theme at the same time. updateGlobalThemeData = false; return true; })(); - // If this is a per-window dark theme, set the color scheme override so - // child BrowsingContexts, such as embedded prompts, get themed - // appropriately. - // If not, reset the color scheme override field. This is required to reset - // the color scheme on theme switch. - if (this._win.browsingContext == this._win.browsingContext.top) { - if (useDarkTheme && !updateGlobalThemeData) { - this._win.browsingContext.prefersColorSchemeOverride = "dark"; - } else { - this._win.browsingContext.prefersColorSchemeOverride = "none"; - } - } - let theme = useDarkTheme ? themeData.darkTheme : themeData.theme; if (!theme) { theme = { id: DEFAULT_THEME_ID }; } - let hasTheme = theme.id != DEFAULT_THEME_ID; - let root = this._doc.documentElement; - if (hasTheme && theme.headerURL) { - root.setAttribute("lwtheme-image", "true"); - } else { - root.removeAttribute("lwtheme-image"); - } + let builtinThemeConfig = kDefaultThemes[theme.id]; + let hasTheme = !builtinThemeConfig; + let colorSchemeOverride = (() => { + if (useDarkTheme || themeData.darkTheme) { + return useDarkTheme ? "dark" : "light"; + } + if (builtinThemeConfig?.colorScheme) { + return builtinThemeConfig.colorScheme; + } + // If not, reset the color scheme override field. This is required to reset + // the color scheme on theme switch. + return "none"; + })(); + // NOTE(emilio): The !parent check shouldn't be needed ideally, but + // apparently Thunderbird uses this code on child frames? + // See bug 1752815. + if (!this._win.browsingContext.parent) { + this._win.browsingContext.prefersColorSchemeOverride = + colorSchemeOverride; + } + this._doc.forceNonNativeTheme = !!builtinThemeConfig?.forceNonNative; + let root = this._doc.documentElement; + root.toggleAttribute("lwtheme-image", !!(hasTheme && theme.headerURL)); this._setExperiment(hasTheme, themeData.experiment, theme.experimental); _setImage(this._win, root, hasTheme, "--lwt-header-image", theme.headerURL); _setImage( @@ -344,21 +354,15 @@ LightweightThemeConsumer.prototype = { _setDarkModeAttributes(this._doc, root, theme, _processedColors, hasTheme); - if (hasTheme) { - if (updateGlobalThemeData) { - _determineToolbarAndContentTheme( - this._doc, - theme, - _processedColors, - supportsDarkTheme, - useDarkTheme - ); - } - root.setAttribute("lwtheme", "true"); - } else { - _determineToolbarAndContentTheme(this._doc, null, null); - root.removeAttribute("lwtheme"); + if (updateGlobalThemeData) { + _determineToolbarAndContentTheme( + this._doc, + hasTheme ? theme : null, + _processedColors, + colorSchemeOverride + ); } + root.toggleAttribute("lwtheme", hasTheme); let contentThemeData = _getContentProperties(this._doc, hasTheme, theme); Services.ppmm.sharedData.set(`theme/${this._winId}`, contentThemeData); @@ -509,8 +513,7 @@ function _determineToolbarAndContentTheme( aDoc, aTheme, colors, - aHasDarkTheme = false, - aIsDarkTheme = false + aColorSchemeOverride ) { const kDark = 0; const kLight = 1; @@ -535,6 +538,9 @@ function _determineToolbarAndContentTheme( } let toolbarTheme = (function () { + if (aColorSchemeOverride != "none") { + return colorSchemeValue(aColorSchemeOverride); + } if (!aTheme) { return kSystem; } @@ -542,9 +548,6 @@ function _determineToolbarAndContentTheme( if (themeValue !== null) { return themeValue; } - if (aHasDarkTheme) { - return aIsDarkTheme ? kDark : kLight; - } return _isToolbarDark(aDoc, aTheme, colors, true) ? kDark : kLight; })(); @@ -552,6 +555,9 @@ function _determineToolbarAndContentTheme( if (lazy.BROWSER_THEME_UNIFIED_COLOR_SCHEME) { return toolbarTheme; } + if (aColorSchemeOverride != "none") { + return colorSchemeValue(aColorSchemeOverride); + } if (!aTheme) { return kSystem; } diff --git a/widget/nsXPLookAndFeel.cpp b/widget/nsXPLookAndFeel.cpp @@ -530,6 +530,7 @@ static constexpr struct { // need to re-layout. {"browser.theme.toolbar-theme"_ns, widget::ThemeChangeKind::AllBits}, {"browser.theme.content-theme"_ns}, + {"browser.theme.native-theme"_ns}, // Affects PreferenceSheet, and thus styling. {"browser.anchor_color"_ns, widget::ThemeChangeKind::Style}, {"browser.anchor_color.dark"_ns, widget::ThemeChangeKind::Style}, diff --git a/xpcom/ds/StaticAtoms.py b/xpcom/ds/StaticAtoms.py @@ -2309,6 +2309,7 @@ STATIC_ATOMS = [ Atom("_moz_content_prefers_color_scheme", "-moz-content-prefers-color-scheme"), Atom("_moz_content_preferred_color_scheme", "-moz-content-preferred-color-scheme"), Atom("_moz_system_dark_theme", "-moz-system-dark-theme"), + Atom("_moz_native_theme", "-moz-native-theme"), Atom("_moz_panel_animations", "-moz-panel-animations"), # application commands Atom("Back", "Back"),