tor-browser

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

commit a43d3e45ae2b10d183fcfb2e307e339a7b7aed8d
parent e3b998d8544e9cd743c5b8b74b164029070a935e
Author: Emilio Cobos Álvarez <emilio@crisal.io>
Date:   Wed, 10 Dec 2025 10:14:56 +0000

Bug 2004905 - Move manifest -> lwtData conversion to LightweightThemeManager. r=Gijs,desktop-theme-reviewers,extension-reviewers,dao,robwu

No behavior change. This is in preparation to be able to pull theme
data from a built-in manifest without involving instantiating an
extension, see bug 2003921 for context.

While at it, reduce the amount of startup data we store. lwtData already
includes the light / dark styles, and experiment too, so no need to store
them separately.

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

Diffstat:
Mtoolkit/components/extensions/parent/ext-theme.js | 299+++----------------------------------------------------------------------------
Mtoolkit/mozapps/extensions/LightweightThemeManager.sys.mjs | 259+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtoolkit/mozapps/extensions/internal/XPIProvider.sys.mjs | 6++++--
Mtoolkit/mozapps/extensions/test/xpcshell/test_cleanup_theme_processedColors.js | 9++-------
4 files changed, 275 insertions(+), 298 deletions(-)

diff --git a/toolkit/components/extensions/parent/ext-theme.js b/toolkit/components/extensions/parent/ext-theme.js @@ -66,34 +66,13 @@ class Theme { // In this case we would still be using the old interpretation instead of // the new one, until the user disables and re-enables/installs the theme. this.lwtData = startupData.lwtData; - this.lwtStyles = startupData.lwtStyles; - this.lwtDarkStyles = startupData.lwtDarkStyles; - this.experiment = startupData.experiment; + this.experiment = startupData.lwtData.experiment; } else { // lwtData will be populated by load(). this.lwtData = null; - // TODO(ntim): clean this in bug 1550090 - this.lwtStyles = {}; - this.lwtDarkStyles = darkDetails ? {} : null; this.experiment = null; if (experiment) { if (extension.canUseThemeExperiment()) { - this.lwtStyles.experimental = { - colors: {}, - images: {}, - properties: {}, - }; - if (this.lwtDarkStyles) { - this.lwtDarkStyles.experimental = { - colors: {}, - images: {}, - properties: {}, - }; - } - const { baseURI } = this.extension; - if (experiment.stylesheet) { - experiment.stylesheet = baseURI.resolve(experiment.stylesheet); - } this.experiment = experiment; } else { const { logger } = this.extension; @@ -112,20 +91,15 @@ class Theme { load() { // this.lwtData is usually null, unless populated from startupData. if (!this.lwtData) { - this.loadDetails(this.details, this.lwtStyles); - if (this.darkDetails) { - this.loadDetails(this.darkDetails, this.lwtDarkStyles); - } - - this.lwtData = { - theme: this.lwtStyles, - darkTheme: this.lwtDarkStyles, - }; - - if (this.experiment) { - this.lwtData.experiment = this.experiment; - } - + this.lwtData = LightweightThemeManager.themeDataFrom( + this.details, + this.darkDetails, + this.experiment, + this.extension.baseURI, + this.extension.id, + this.extension.version, + this.extension.logger + ); if (this.extension.type === "theme") { // Store the parsed theme in startupData, so it is available early at // browser startup, to use as LightweightThemeManager.fallbackThemeData, @@ -133,9 +107,6 @@ class Theme { // this ext-theme.js file to be loaded. this.extension.startupData = { lwtData: this.lwtData, - lwtStyles: this.lwtStyles, - lwtDarkStyles: this.lwtDarkStyles, - experiment: this.experiment, }; this.extension.saveStartupData(); } @@ -158,256 +129,6 @@ class Theme { ); } - /** - * @param {object} details Details - * @param {object} styles Styles object in which to store the colors. - */ - loadDetails(details, styles) { - if (details.colors) { - this.loadColors(details.colors, styles); - } - - if (details.images) { - this.loadImages(details.images, styles); - } - - if (details.properties) { - this.loadProperties(details.properties, styles); - } - - this.loadMetadata(this.extension, styles); - } - - /** - * Helper method for loading colors found in the extension's manifest. - * - * @param {object} colors Dictionary mapping color properties to values. - * @param {object} styles Styles object in which to store the colors. - */ - loadColors(colors, styles) { - for (let color of Object.keys(colors)) { - let val = colors[color]; - - if (!val) { - continue; - } - - let cssColor = val; - if (Array.isArray(val)) { - cssColor = - "rgb" + (val.length > 3 ? "a" : "") + "(" + val.join(",") + ")"; - } - - switch (color) { - case "frame": - styles.accentcolor = cssColor; - break; - case "frame_inactive": - styles.accentcolorInactive = cssColor; - break; - case "tab_background_text": - styles.textcolor = cssColor; - break; - case "toolbar": - styles.toolbarColor = cssColor; - break; - case "toolbar_text": - case "bookmark_text": - styles.toolbar_text = cssColor; - break; - case "icons": - styles.icon_color = cssColor; - break; - case "icons_attention": - styles.icon_attention_color = cssColor; - break; - case "tab_background_separator": - case "tab_loading": - case "tab_text": - case "tab_line": - case "tab_selected": - case "toolbar_field": - case "toolbar_field_text": - case "toolbar_field_border": - case "toolbar_field_focus": - case "toolbar_field_text_focus": - case "toolbar_field_border_focus": - case "toolbar_top_separator": - case "toolbar_bottom_separator": - case "toolbar_vertical_separator": - case "button_background_hover": - case "button_background_active": - case "popup": - case "popup_text": - case "popup_border": - case "popup_highlight": - case "popup_highlight_text": - case "ntp_background": - case "ntp_card_background": - case "ntp_text": - case "sidebar": - case "sidebar_border": - case "sidebar_text": - case "sidebar_highlight": - case "sidebar_highlight_text": - case "toolbar_field_highlight": - case "toolbar_field_highlight_text": - styles[color] = cssColor; - break; - default: - if ( - this.experiment && - this.experiment.colors && - color in this.experiment.colors - ) { - styles.experimental.colors[color] = cssColor; - } else { - const { logger } = this.extension; - logger.warn(`Unrecognized theme property found: colors.${color}`); - } - break; - } - } - } - - /** - * Helper method for loading images found in the extension's manifest. - * - * @param {object} images Dictionary mapping image properties to values. - * @param {object} styles Styles object in which to store the colors. - */ - loadImages(images, styles) { - const { baseURI, logger } = this.extension; - - for (let image of Object.keys(images)) { - let val = images[image]; - - if (!val) { - continue; - } - - switch (image) { - case "additional_backgrounds": { - let backgroundImages = val.map(img => baseURI.resolve(img)); - styles.additionalBackgrounds = backgroundImages; - break; - } - case "theme_frame": { - let resolvedURL = baseURI.resolve(val); - styles.headerURL = resolvedURL; - break; - } - default: { - if ( - this.experiment && - this.experiment.images && - image in this.experiment.images - ) { - styles.experimental.images[image] = baseURI.resolve(val); - } else { - logger.warn(`Unrecognized theme property found: images.${image}`); - } - break; - } - } - } - } - - /** - * Helper method for preparing properties found in the extension's manifest. - * Properties are commonly used to specify more advanced behavior of colors, - * images or icons. - * - * @param {object} properties Dictionary mapping properties to values. - * @param {object} styles Styles object in which to store the colors. - */ - loadProperties(properties, styles) { - let additionalBackgroundsCount = - (styles.additionalBackgrounds && styles.additionalBackgrounds.length) || - 0; - const assertValidAdditionalBackgrounds = (property, valueCount) => { - const { logger } = this.extension; - if (!additionalBackgroundsCount) { - logger.warn( - `The '${property}' property takes effect only when one ` + - `or more additional background images are specified using the 'additional_backgrounds' property.` - ); - return false; - } - if (additionalBackgroundsCount !== valueCount) { - logger.warn( - `The amount of values specified for '${property}' ` + - `(${valueCount}) is not equal to the amount of additional background ` + - `images (${additionalBackgroundsCount}), which may lead to unexpected results.` - ); - } - return true; - }; - - for (let property of Object.getOwnPropertyNames(properties)) { - let val = properties[property]; - - if (!val) { - continue; - } - - switch (property) { - case "additional_backgrounds_alignment": { - if (!assertValidAdditionalBackgrounds(property, val.length)) { - break; - } - - styles.backgroundsAlignment = val.join(","); - break; - } - case "additional_backgrounds_tiling": { - if (!assertValidAdditionalBackgrounds(property, val.length)) { - break; - } - - let tiling = []; - for (let i = 0, l = styles.additionalBackgrounds.length; i < l; ++i) { - tiling.push(val[i] || "no-repeat"); - } - styles.backgroundsTiling = tiling.join(","); - break; - } - case "color_scheme": - case "content_color_scheme": { - styles[property] = val; - break; - } - default: { - if ( - this.experiment && - this.experiment.properties && - property in this.experiment.properties - ) { - styles.experimental.properties[property] = val; - } else { - const { logger } = this.extension; - logger.warn( - `Unrecognized theme property found: properties.${property}` - ); - } - break; - } - } - } - } - - /** - * Helper method for loading extension metadata required by downstream - * consumers. - * - * @param {object} extension Extension object. - * @param {object} styles Styles object in which to store the colors. - */ - loadMetadata(extension, styles) { - styles.id = extension.id; - styles.version = extension.version; - } - static unload(browserWindow) { let lwtData = { theme: null, diff --git a/toolkit/mozapps/extensions/LightweightThemeManager.sys.mjs b/toolkit/mozapps/extensions/LightweightThemeManager.sys.mjs @@ -6,7 +6,266 @@ // active theme can be found. This the case for WebExtension Themes, for example. var _fallbackThemeData = null; +// Parses the `images` property of a theme manifest and stores them in `styles`. +function loadImages(images, styles, experiment, baseURI, logger) { + for (let image of Object.keys(images)) { + let val = images[image]; + + if (!val) { + continue; + } + + switch (image) { + case "additional_backgrounds": { + let backgroundImages = val.map(img => baseURI.resolve(img)); + styles.additionalBackgrounds = backgroundImages; + break; + } + case "theme_frame": { + let resolvedURL = baseURI.resolve(val); + styles.headerURL = resolvedURL; + break; + } + default: { + if (experiment?.images && image in experiment.images) { + styles.experimental.images[image] = baseURI.resolve(val); + } else { + logger?.warn(`Unrecognized theme property found: images.${image}`); + } + break; + } + } + } +} + +// Parses the `colors` property of a theme manifest, and stores them in `styles`. +function loadColors(colors, styles, experiment, logger) { + for (let color of Object.keys(colors)) { + let val = colors[color]; + if (!val) { + continue; + } + + let cssColor = val; + if (Array.isArray(val)) { + cssColor = + "rgb" + (val.length > 3 ? "a" : "") + "(" + val.join(",") + ")"; + } + + switch (color) { + case "frame": + styles.accentcolor = cssColor; + break; + case "frame_inactive": + styles.accentcolorInactive = cssColor; + break; + case "tab_background_text": + styles.textcolor = cssColor; + break; + case "toolbar": + styles.toolbarColor = cssColor; + break; + case "toolbar_text": + case "bookmark_text": + styles.toolbar_text = cssColor; + break; + case "icons": + styles.icon_color = cssColor; + break; + case "icons_attention": + styles.icon_attention_color = cssColor; + break; + case "tab_background_separator": + case "tab_loading": + case "tab_text": + case "tab_line": + case "tab_selected": + case "toolbar_field": + case "toolbar_field_text": + case "toolbar_field_border": + case "toolbar_field_focus": + case "toolbar_field_text_focus": + case "toolbar_field_border_focus": + case "toolbar_top_separator": + case "toolbar_bottom_separator": + case "toolbar_vertical_separator": + case "button_background_hover": + case "button_background_active": + case "popup": + case "popup_text": + case "popup_border": + case "popup_highlight": + case "popup_highlight_text": + case "ntp_background": + case "ntp_card_background": + case "ntp_text": + case "sidebar": + case "sidebar_border": + case "sidebar_text": + case "sidebar_highlight": + case "sidebar_highlight_text": + case "toolbar_field_highlight": + case "toolbar_field_highlight_text": + styles[color] = cssColor; + break; + default: + if (experiment?.colors && color in experiment.colors) { + styles.experimental.colors[color] = cssColor; + } else { + logger?.warn(`Unrecognized theme property found: colors.${color}`); + } + break; + } + } +} + +// Parses the `properties` property of the theme manifest. +function loadProperties(properties, styles, experiment, logger) { + let additionalBackgroundsCount = styles.additionalBackgrounds?.length || 0; + const assertValidAdditionalBackgrounds = (property, valueCount) => { + if (!additionalBackgroundsCount) { + logger?.warn( + `The '${property}' property takes effect only when one ` + + `or more additional background images are specified using the 'additional_backgrounds' property.` + ); + return false; + } + if (additionalBackgroundsCount !== valueCount) { + logger?.warn( + `The amount of values specified for '${property}' ` + + `(${valueCount}) is not equal to the amount of additional background ` + + `images (${additionalBackgroundsCount}), which may lead to unexpected results.` + ); + } + return true; + }; + + for (let property of Object.getOwnPropertyNames(properties)) { + let val = properties[property]; + + if (!val) { + continue; + } + + switch (property) { + case "additional_backgrounds_alignment": { + if (!assertValidAdditionalBackgrounds(property, val.length)) { + break; + } + + styles.backgroundsAlignment = val.join(","); + break; + } + case "additional_backgrounds_tiling": { + if (!assertValidAdditionalBackgrounds(property, val.length)) { + break; + } + + let tiling = []; + for (let i = 0, l = styles.additionalBackgrounds.length; i < l; ++i) { + tiling.push(val[i] || "no-repeat"); + } + styles.backgroundsTiling = tiling.join(","); + break; + } + case "color_scheme": + case "content_color_scheme": { + styles[property] = val; + break; + } + default: { + if (experiment?.properties && property in experiment.properties) { + styles.experimental.properties[property] = val; + } else { + logger?.warn( + `Unrecognized theme property found: properties.${property}` + ); + } + break; + } + } + } +} + +function loadDetails(details, experiment, baseURI, id, version, logger) { + let styles = {}; + if (experiment) { + styles.experimental = { + colors: {}, + images: {}, + properties: {}, + }; + } + + if (details.colors) { + loadColors(details.colors, styles, experiment, logger); + } + + if (details.images) { + loadImages(details.images, styles, experiment, baseURI, logger); + } + + if (details.properties) { + loadProperties(details.properties, styles, experiment, logger); + } + + styles.id = id; + styles.version = version; + return styles; +} + export var LightweightThemeManager = { + // Reads theme data from either an extension manifest or a dynamic theme, + // and converts it to an internal format used by our theming code. + // + // NOTE: This format must be backwards compatible, since it's stored in + // the extension's startup data, or it needs to be discarded when it changes. + // + // @param {object} details the `theme` entry in the manifest. + // @param {object} darkDetails the `dark_theme` entry in the manifest. + // @param {object?} experiment the `experiment` entry in the manifest. + // @param {nsIURI} baseURI the base URL to resolve images and so against. + // @param {string} id the extension id. + // @param {string} version the extension version. + // @param {object?} logger the extension logger if needed. + // + // @return {object} the internal representation of the theme. + themeDataFrom( + details, + darkDetails, + experiment, + baseURI, + id, + version, + logger + ) { + if (experiment?.stylesheet) { + experiment.stylesheet = baseURI.resolve(experiment.stylesheet); + } + let lwtData = { + experiment, + }; + lwtData.theme = loadDetails( + details, + experiment, + baseURI, + id, + version, + logger + ); + if (darkDetails) { + lwtData.darkTheme = loadDetails( + darkDetails, + experiment, + baseURI, + id, + version, + logger + ); + } + return lwtData; + }, + set fallbackThemeData(data) { if (data && Object.getOwnPropertyNames(data).length) { _fallbackThemeData = Object.assign({}, data); diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.sys.mjs b/toolkit/mozapps/extensions/internal/XPIProvider.sys.mjs @@ -1474,8 +1474,10 @@ var XPIStates = { // here. See bug 1830136. delete data.startupData.lwtData?.darkTheme?._processedColors; delete data.startupData.lwtData?.theme?._processedColors; - delete data.startupData.lwtDarkStyles?._processedColors; - delete data.startupData.lwtStyles?._processedColors; + // These properties are obsolete since bug 2004905, let's clean them up + // while at it. + delete data.startupData.lwtDarkStyles; + delete data.startupData.lwtStyles; } } } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_cleanup_theme_processedColors.js b/toolkit/mozapps/extensions/test/xpcshell/test_cleanup_theme_processedColors.js @@ -81,13 +81,8 @@ add_task(async function test_cleanup_theme_processedColors() { !("_processedColors" in themeFromFile.startupData.lwtData.theme), "No _processedColor property" ); - Assert.equal( - themeFromFile.startupData.lwtStyles.foo, - "bar", - "The sentinel value is found" - ); Assert.ok( - !("_processedColors" in themeFromFile.startupData.lwtStyles), - "No _processedColor property" + !("lwtStyles" in themeFromFile.startupData), + "No lwtStyles property" ); });