Migrates theme patching to top-level ThemeSupport
This CL replaces all uses of self.UI.themeSupport with an instance
function from the top level theme_support directory. It also dupes in
some of the functionality from ui/utils to prevent a circular dependency
between ui/utils and theme_support.
Change-Id: Ib8efd06630b99ee23af04ecba0dbf3cc4b19c036
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2352711
Commit-Queue: Paul Lewis <[email protected]>
Reviewed-by: Jack Franklin <[email protected]>
diff --git a/front_end/ui/BUILD.gn b/front_end/ui/BUILD.gn
index ca841cf..910ca72 100644
--- a/front_end/ui/BUILD.gn
+++ b/front_end/ui/BUILD.gn
@@ -74,6 +74,7 @@
"../host:bundle",
"../platform:bundle",
"../text_utils:bundle",
+ "../theme_support:bundle",
"utils:bundle",
]
}
diff --git a/front_end/ui/SoftContextMenu.js b/front_end/ui/SoftContextMenu.js
index 15e5c38..f10cb24 100644
--- a/front_end/ui/SoftContextMenu.js
+++ b/front_end/ui/SoftContextMenu.js
@@ -32,6 +32,7 @@
// TODO(crbug.com/1011811): Enable TypeScript compiler checks
import * as Host from '../host/host.js';
+import * as ThemeSupport from '../theme_support/theme_support.js';
import * as ARIAUtils from './ARIAUtils.js';
import {AnchorBehavior, GlassPane, MarginBehavior, PointerEventsBehavior, SizeBehavior,} from './GlassPane.js'; // eslint-disable-line no-unused-vars
@@ -208,7 +209,7 @@
menuItemElement.createTextChild(item.label);
ARIAUtils.setExpanded(menuItemElement, false);
- if (Host.Platform.isMac() && !self.UI.themeSupport.hasTheme()) {
+ if (Host.Platform.isMac() && !ThemeSupport.ThemeSupport.instance().hasTheme()) {
const subMenuArrowElement = menuItemElement.createChild('span', 'soft-context-menu-item-submenu-arrow');
subMenuArrowElement.textContent = '\u25B6'; // BLACK RIGHT-POINTING TRIANGLE
} else {
@@ -329,7 +330,7 @@
}
this._highlightedMenuItemElement = menuItemElement;
if (this._highlightedMenuItemElement) {
- if (self.UI.themeSupport.hasTheme() || Host.Platform.isMac()) {
+ if (ThemeSupport.ThemeSupport.instance().hasTheme() || Host.Platform.isMac()) {
this._highlightedMenuItemElement.classList.add('force-white-icons');
}
this._highlightedMenuItemElement.classList.add('soft-context-menu-item-mouse-over');
diff --git a/front_end/ui/UIUtils.js b/front_end/ui/UIUtils.js
index 98e43dd..179e1b5 100644
--- a/front_end/ui/UIUtils.js
+++ b/front_end/ui/UIUtils.js
@@ -36,6 +36,7 @@
import * as Host from '../host/host.js';
import * as Platform from '../platform/platform.js';
import * as TextUtils from '../text_utils/text_utils.js';
+import * as ThemeSupport from '../theme_support/theme_support.js';
import * as ARIAUtils from './ARIAUtils.js';
import {Dialog} from './Dialog.js';
@@ -1195,10 +1196,10 @@
document.defaultView.addEventListener('blur', _windowBlurred.bind(UI, document), false);
document.addEventListener('focus', focusChanged.bind(UI), true);
- if (!self.UI.themeSupport) {
- self.UI.themeSupport = new ThemeSupport(themeSetting);
+ if (!ThemeSupport.ThemeSupport.hasInstance()) {
+ ThemeSupport.ThemeSupport.instance({forceNew: true, setting: themeSetting});
}
- self.UI.themeSupport.applyTheme(document);
+ ThemeSupport.ThemeSupport.instance().applyTheme(document);
const body = /** @type {!Element} */ (document.body);
appendStyle(body, 'ui/inspectorStyle.css');
@@ -1694,300 +1695,6 @@
}
/**
- * @unrestricted
- */
-export class ThemeSupport {
- /**
- * @param {!Common.Settings.Setting<string>} setting
- */
- constructor(setting) {
- const systemPreferredTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'default';
- this._themeName = setting.get() === 'systemPreferred' ? systemPreferredTheme : setting.get();
- this._themableProperties = new Set([
- 'color', 'box-shadow', 'text-shadow', 'outline-color', 'background-image', 'background-color',
- 'border-left-color', 'border-right-color', 'border-top-color', 'border-bottom-color', '-webkit-border-image',
- 'fill', 'stroke'
- ]);
- /** @type {!Map<string, string>} */
- this._cachedThemePatches = new Map();
- this._setting = setting;
- this._customSheets = new Set();
- this._computedRoot = Common.Lazy.lazy(() => window.getComputedStyle(document.documentElement));
- }
-
- /**
- * @param {string} variableName
- * @returns {string}
- */
- getComputedValue(variableName) {
- const computedRoot = this._computedRoot();
-
- if (typeof computedRoot === 'symbol') {
- throw new Error(`Computed value for property (${variableName}) could not be found on :root.`);
- }
-
- return computedRoot.getPropertyValue(variableName);
- }
-
- /**
- * @return {boolean}
- */
- hasTheme() {
- return this._themeName !== 'default';
- }
-
- /**
- * @return {string}
- */
- themeName() {
- return this._themeName;
- }
-
- /**
- * @param {!Element|!ShadowRoot} element
- */
- injectHighlightStyleSheets(element) {
- this._injectingStyleSheet = true;
- appendStyle(element, 'ui/inspectorSyntaxHighlight.css');
- if (this._themeName === 'dark') {
- appendStyle(element, 'ui/inspectorSyntaxHighlightDark.css');
- }
- this._injectingStyleSheet = false;
- }
-
- /**
- * @param {!Element|!ShadowRoot} element
- */
- injectCustomStyleSheets(element) {
- for (const sheet of this._customSheets) {
- const styleElement = createElement('style');
- styleElement.textContent = sheet;
- element.appendChild(styleElement);
- }
- }
-
- /**
- * @return {boolean}
- */
- isForcedColorsMode() {
- return window.matchMedia('(forced-colors: active)').matches;
- }
-
- /**
- * @param {string} sheetText
- */
- addCustomStylesheet(sheetText) {
- this._customSheets.add(sheetText);
- }
-
- /**
- * @param {!Document} document
- */
- applyTheme(document) {
- if (!this.hasTheme() || this.isForcedColorsMode()) {
- return;
- }
-
- if (this._themeName === 'dark') {
- document.documentElement.classList.add('-theme-with-dark-background');
- }
-
- const styleSheets = document.styleSheets;
- const result = [];
- for (let i = 0; i < styleSheets.length; ++i) {
- result.push(this._patchForTheme(styleSheets[i].href, styleSheets[i]));
- }
- result.push('/*# sourceURL=inspector.css.theme */');
-
- const styleElement = createElement('style');
- styleElement.textContent = result.join('\n');
- document.head.appendChild(styleElement);
- }
-
- /**
- * @param {string} id
- * @param {string} text
- * @return {string}
- * @suppressGlobalPropertiesCheck
- */
- themeStyleSheet(id, text) {
- if (!this.hasTheme() || this._injectingStyleSheet || this.isForcedColorsMode()) {
- return '';
- }
-
- let patch = this._cachedThemePatches.get(id);
- if (!patch) {
- const styleElement = createElement('style');
- styleElement.textContent = text;
- document.body.appendChild(styleElement);
- patch = this._patchForTheme(id, styleElement.sheet);
- document.body.removeChild(styleElement);
- }
- return patch;
- }
-
- /**
- * @param {string} id
- * @param {!StyleSheet} styleSheet
- * @return {string}
- */
- _patchForTheme(id, styleSheet) {
- const cached = this._cachedThemePatches.get(id);
- if (cached) {
- return cached;
- }
-
- try {
- const rules = styleSheet.cssRules;
- const result = [];
- for (let j = 0; j < rules.length; ++j) {
- if (rules[j] instanceof CSSImportRule) {
- result.push(this._patchForTheme(rules[j].styleSheet.href, rules[j].styleSheet));
- continue;
- }
- const output = [];
- const style = rules[j].style;
- const selectorText = rules[j].selectorText;
- for (let i = 0; style && i < style.length; ++i) {
- this._patchProperty(selectorText, style, style[i], output);
- }
- if (output.length) {
- result.push(rules[j].selectorText + '{' + output.join('') + '}');
- }
- }
-
- const fullText = result.join('\n');
- this._cachedThemePatches.set(id, fullText);
- return fullText;
- } catch (e) {
- this._setting.set('default');
- return '';
- }
- }
-
- /**
- * @param {string} selectorText
- * @param {!CSSStyleDeclaration} style
- * @param {string} name
- * @param {!Array<string>} output
- *
- * Theming API is primarily targeted at making dark theme look good.
- * - If rule has ".-theme-preserve" in selector, it won't be affected.
- * - One can create specializations for dark themes via body.-theme-with-dark-background selector in host context.
- */
- _patchProperty(selectorText, style, name, output) {
- if (!this._themableProperties.has(name)) {
- return;
- }
-
- const value = style.getPropertyValue(name);
- if (!value || value === 'none' || value === 'inherit' || value === 'initial' || value === 'transparent') {
- return;
- }
- if (name === 'background-image' && value.indexOf('gradient') === -1) {
- return;
- }
-
- // Don't operate on CSS variables.
- if (/^var\(.*\)$/.test(value)) {
- return;
- }
-
- if (selectorText.indexOf('-theme-') !== -1) {
- return;
- }
-
- let colorUsage = ThemeSupport.ColorUsage.Unknown;
- if (name.indexOf('background') === 0 || name.indexOf('border') === 0) {
- colorUsage |= ThemeSupport.ColorUsage.Background;
- }
- if (name.indexOf('background') === -1) {
- colorUsage |= ThemeSupport.ColorUsage.Foreground;
- }
-
- output.push(name);
- output.push(':');
- const items = value.replace(Common.Color.Regex, '\0$1\0').split('\0');
- for (let i = 0; i < items.length; ++i) {
- output.push(this.patchColorText(items[i], /** @type {!ThemeSupport.ColorUsage} */ (colorUsage)));
- }
- if (style.getPropertyPriority(name)) {
- output.push(' !important');
- }
- output.push(';');
- }
-
- /**
- * @param {string} text
- * @param {!ThemeSupport.ColorUsage} colorUsage
- * @return {string}
- */
- patchColorText(text, colorUsage) {
- const color = Common.Color.Color.parse(text);
- if (!color) {
- return text;
- }
- const outColor = this.patchColor(color, colorUsage);
- let outText = outColor.asString(null);
- if (!outText) {
- outText = outColor.asString(outColor.hasAlpha() ? Common.Color.Format.RGBA : Common.Color.Format.RGB);
- }
- return outText || text;
- }
-
- /**
- * @param {!Common.Color.Color} color
- * @param {!ThemeSupport.ColorUsage} colorUsage
- * @return {!Common.Color.Color}
- */
- patchColor(color, colorUsage) {
- const hsla = color.hsla();
- this._patchHSLA(hsla, colorUsage);
- const rgba = [];
- Common.Color.Color.hsl2rgb(hsla, rgba);
- return new Common.Color.Color(rgba, color.format());
- }
-
- /**
- * @param {!Array<number>} hsla
- * @param {!ThemeSupport.ColorUsage} colorUsage
- */
- _patchHSLA(hsla, colorUsage) {
- const hue = hsla[0];
- const sat = hsla[1];
- let lit = hsla[2];
- const alpha = hsla[3];
-
- switch (this._themeName) {
- case 'dark': {
- const minCap = colorUsage & ThemeSupport.ColorUsage.Background ? 0.14 : 0;
- const maxCap = colorUsage & ThemeSupport.ColorUsage.Foreground ? 0.9 : 1;
- lit = 1 - lit;
- if (lit < minCap * 2) {
- lit = minCap + lit / 2;
- } else if (lit > 2 * maxCap - 1) {
- lit = maxCap - 1 / 2 + lit / 2;
- }
- break;
- }
- }
- hsla[0] = Platform.NumberUtilities.clamp(hue, 0, 1);
- hsla[1] = Platform.NumberUtilities.clamp(sat, 0, 1);
- hsla[2] = Platform.NumberUtilities.clamp(lit, 0, 1);
- hsla[3] = Platform.NumberUtilities.clamp(alpha, 0, 1);
- }
-}
-
-/**
- * @enum {number}
- */
-ThemeSupport.ColorUsage = {
- Unknown: 0,
- Foreground: 1 << 0,
- Background: 1 << 1,
-};
-
-/**
* @param {string} article
* @param {string} title
* @return {!Element}
diff --git a/front_end/ui/utils/BUILD.gn b/front_end/ui/utils/BUILD.gn
index 978d2f8..e62b991 100644
--- a/front_end/ui/utils/BUILD.gn
+++ b/front_end/ui/utils/BUILD.gn
@@ -14,6 +14,8 @@
"measured-scrollbar-width.js",
"register-custom-element.js",
]
+
+ deps = [ "../../theme_support:bundle" ]
}
devtools_entrypoint("bundle") {
diff --git a/front_end/ui/utils/append-style.js b/front_end/ui/utils/append-style.js
index 11ead59..c9c5ff9 100644
--- a/front_end/ui/utils/append-style.js
+++ b/front_end/ui/utils/append-style.js
@@ -5,6 +5,8 @@
// @ts-nocheck
// TODO(crbug.com/1011811): Enable TypeScript compiler checks
+import * as ThemeSupport from '../../theme_support/theme_support.js';
+
/**
* @param {!Node} node
* @param {string} cssFile
@@ -19,7 +21,7 @@
styleElement.textContent = content;
node.appendChild(styleElement);
- const themeStyleSheet = self.UI.themeSupport.themeStyleSheet(cssFile, content);
+ const themeStyleSheet = ThemeSupport.ThemeSupport.instance().themeStyleSheet(cssFile, content);
if (themeStyleSheet) {
styleElement = createElement('style');
styleElement.textContent = themeStyleSheet + '\n' + Root.Runtime.resolveSourceURL(cssFile + '.theme');
diff --git a/front_end/ui/utils/inject-core-styles.js b/front_end/ui/utils/inject-core-styles.js
index b74483b..79f60cf 100644
--- a/front_end/ui/utils/inject-core-styles.js
+++ b/front_end/ui/utils/inject-core-styles.js
@@ -4,6 +4,7 @@
// @ts-nocheck
// TODO(crbug.com/1011811): Enable TypeScript compiler checks
+import * as ThemeSupport from '../../theme_support/theme_support.js';
import {appendStyle} from './append-style.js';
@@ -13,6 +14,6 @@
export function injectCoreStyles(root) {
appendStyle(root, 'ui/inspectorCommon.css');
appendStyle(root, 'ui/textButton.css');
- self.UI.themeSupport.injectHighlightStyleSheets(root);
- self.UI.themeSupport.injectCustomStyleSheets(root);
+ ThemeSupport.ThemeSupport.instance().injectHighlightStyleSheets(root);
+ ThemeSupport.ThemeSupport.instance().injectCustomStyleSheets(root);
}