Bug 1897931 - [devtools] Include starting-style value in var() tooltip. r=devtools-reviewers,bomsy.

This patch adds a new Map that holds CSS variable value for `@starting-style` rules,
so we can display the proper value when hovering a variable in a `@starting-style` rule.

As regular properties, CSS variables can be overridden by declaration in regular
rule, so we ensure this is reflected in the content of the tooltip.

We also add a new `@starting-style` section in the tooltip, that we show when
it's displayed for a regular rule.

Many test cases are added to cover the different situations we can have.
The `checkVariableTooltipForProperty` was moved out of browser_rules_registered-custom-properties.js,
and refactored to accomodate for the new starting-style section.

Differential Revision: https://phabricator.services.mozilla.com/D215100
This commit is contained in:
Nicolas Chevobbe
2024-06-29 09:47:31 +00:00
parent f2af75e097
commit 10e32d6c65
10 changed files with 439 additions and 157 deletions

View File

@@ -62,6 +62,7 @@ class ElementStyle {
this.rules = []; this.rules = [];
this.cssProperties = this.ruleView.cssProperties; this.cssProperties = this.ruleView.cssProperties;
this.variablesMap = new Map(); this.variablesMap = new Map();
this.startingStyleVariablesMap = new Map();
// We don't want to overwrite this.store.userProperties so we only create it // We don't want to overwrite this.store.userProperties so we only create it
// if it doesn't already exist. // if it doesn't already exist.
@@ -306,6 +307,9 @@ class ElementStyle {
// CSS Variables inherits from the normal element in case of pseudo element. // CSS Variables inherits from the normal element in case of pseudo element.
const variables = new Map(pseudo ? this.variablesMap.get("") : null); const variables = new Map(pseudo ? this.variablesMap.get("") : null);
const startingStyleVariables = new Map(
pseudo ? this.startingStyleVariablesMap.get("") : null
);
// Walk over the computed properties. As we see a property name // Walk over the computed properties. As we see a property name
// for the first time, mark that property's name as taken by this // for the first time, mark that property's name as taken by this
@@ -377,6 +381,10 @@ class ElementStyle {
earlierInStartingStyle._overriddenDirty = earlierInStartingStyle._overriddenDirty =
!earlierInStartingStyle._overriddenDirty; !earlierInStartingStyle._overriddenDirty;
earlierInStartingStyle.overridden = true; earlierInStartingStyle.overridden = true;
// which means we also need to remove the variable from startingStyleVariables
if (isCssVariable(computedProp.name)) {
startingStyleVariables.delete(computedProp.name);
}
} }
// This computed property is overridden if: // This computed property is overridden if:
@@ -407,12 +415,13 @@ class ElementStyle {
// get the initial value from the registered property definition. // get the initial value from the registered property definition.
if ( if (
isCssVariable(computedProp.name) && isCssVariable(computedProp.name) &&
!computedProp.textProp.invisible && !computedProp.textProp.invisible
// variables set in starting-style rules should only impact the starting style
// value, not the "final" one.
!isPropInStartingStyle
) { ) {
variables.set(computedProp.name, computedProp.value); if (!isPropInStartingStyle) {
variables.set(computedProp.name, computedProp.value);
} else {
startingStyleVariables.set(computedProp.name, computedProp.value);
}
} }
} }
} }
@@ -425,8 +434,17 @@ class ElementStyle {
k => variables.get(k) !== previousVariablesMap.get(k) k => variables.get(k) !== previousVariablesMap.get(k)
) )
); );
const previousStartingStyleVariablesMap = new Map(
this.startingStyleVariablesMap.get(pseudo)
);
const changedStartingStyleVariableNamesSet = new Set(
[...variables.keys(), ...previousStartingStyleVariablesMap.keys()].filter(
k => variables.get(k) !== previousStartingStyleVariablesMap.get(k)
)
);
this.variablesMap.set(pseudo, variables); this.variablesMap.set(pseudo, variables);
this.startingStyleVariablesMap.set(pseudo, startingStyleVariables);
// For each TextProperty, mark it overridden if all of its computed // For each TextProperty, mark it overridden if all of its computed
// properties are marked overridden. Update the text property's associated // properties are marked overridden. Update the text property's associated
@@ -440,7 +458,11 @@ class ElementStyle {
// of the updated CSS variable names. // of the updated CSS variable names.
if ( if (
this._updatePropertyOverridden(textProp) || this._updatePropertyOverridden(textProp) ||
this._hasUpdatedCSSVariable(textProp, changedVariableNamesSet) this._hasUpdatedCSSVariable(textProp, changedVariableNamesSet) ||
this._hasUpdatedCSSVariable(
textProp,
changedStartingStyleVariableNamesSet
)
) { ) {
textProp.updateEditor(); textProp.updateEditor();
} }
@@ -912,6 +934,7 @@ class ElementStyle {
*/ */
getVariableData(name, pseudo = "") { getVariableData(name, pseudo = "") {
const variables = this.variablesMap.get(pseudo); const variables = this.variablesMap.get(pseudo);
const startingStyleVariables = this.startingStyleVariablesMap.get(pseudo);
const registeredPropertiesMap = const registeredPropertiesMap =
this.ruleView.getRegisteredPropertiesForSelectedNodeTarget(); this.ruleView.getRegisteredPropertiesForSelectedNodeTarget();
@@ -921,6 +944,9 @@ class ElementStyle {
// Will be handled in Bug 1866712 // Will be handled in Bug 1866712
data.value = variables.get(name); data.value = variables.get(name);
} }
if (startingStyleVariables?.has(name)) {
data.startingStyle = startingStyleVariables.get(name);
}
if (registeredPropertiesMap?.has(name)) { if (registeredPropertiesMap?.has(name)) {
data.registeredProperty = registeredPropertiesMap.get(name); data.registeredProperty = registeredPropertiesMap.get(name);
} }
@@ -939,26 +965,51 @@ class ElementStyle {
*/ */
getAllCustomProperties(pseudo = "") { getAllCustomProperties(pseudo = "") {
let customProperties = this.variablesMap.get(pseudo); let customProperties = this.variablesMap.get(pseudo);
const startingStyleCustomProperties =
this.startingStyleVariablesMap.get(pseudo);
const registeredPropertiesMap = const registeredPropertiesMap =
this.ruleView.getRegisteredPropertiesForSelectedNodeTarget(); this.ruleView.getRegisteredPropertiesForSelectedNodeTarget();
// If there's no registered properties, we can return the Map as is // If there's no registered properties nor starting style ones, we can return the Map as is
if (!registeredPropertiesMap || registeredPropertiesMap.size === 0) { if (
(!registeredPropertiesMap || registeredPropertiesMap.size === 0) &&
(!startingStyleCustomProperties ||
startingStyleCustomProperties.size === 0)
) {
return customProperties; return customProperties;
} }
let newMapCreated = false; let newMapCreated = false;
for (const [name, propertyDefinition] of registeredPropertiesMap) {
// Only set the registered property if it's not defined (i.e. not in this.variablesMap) if (startingStyleCustomProperties) {
if (!customProperties.has(name)) { for (const [name, value] of startingStyleCustomProperties) {
// Since we want to return registered property, we need to create a new Map // Only set the starting style property if it's not defined (i.e. not in the "main"
// to not modify the one in this.variablesMap. // variable map)
if (!newMapCreated) { if (!customProperties.has(name)) {
customProperties = new Map(customProperties); // Since we want to return starting style variables, we need to create a new Map
newMapCreated = true; // to not modify the one in the main map.
if (!newMapCreated) {
customProperties = new Map(customProperties);
newMapCreated = true;
}
customProperties.set(name, value);
}
}
}
if (registeredPropertiesMap) {
for (const [name, propertyDefinition] of registeredPropertiesMap) {
// Only set the registered property if it's not defined (i.e. not in the variable map)
if (!customProperties.has(name)) {
// Since we want to return registered property, we need to create a new Map
// to not modify the one in the variable map.
if (!newMapCreated) {
customProperties = new Map(customProperties);
newMapCreated = true;
}
customProperties.set(name, propertyDefinition.initialValue);
} }
customProperties.set(name, propertyDefinition.initialValue);
} }
} }

View File

@@ -7,6 +7,18 @@
const TEST_URI = ` const TEST_URI = `
<style> <style>
@property --my-registered-color {
syntax: "<color>";
inherits: true;
initial-value: blue;
}
@property --my-unset-registered-color {
syntax: "<color>";
inherits: true;
initial-value: lavender;
}
h1, [data-test="top-level"] { h1, [data-test="top-level"] {
color: tomato; color: tomato;
transition: all 1s; transition: all 1s;
@@ -33,6 +45,12 @@ const TEST_URI = `
main, [data-test="in-starting-style"] { main, [data-test="in-starting-style"] {
--my-color: black !important; --my-color: black !important;
--my-overridden-color: black;
--my-registered-color: black !important;
--check-my-color: var(--my-color);
--check-my-overridden-color: var(--my-overridden-color);
--check-my-registered-color: var(--my-registered-color);
--check-my-unset-registered-color: var(--my-unset-registered-color);
background-color: dodgerblue; background-color: dodgerblue;
padding-top: 1px; padding-top: 1px;
margin-top: 1px !important; margin-top: 1px !important;
@@ -60,6 +78,10 @@ const TEST_URI = `
main, [data-test="top-level"] { main, [data-test="top-level"] {
--my-color: white; --my-color: white;
--my-overridden-color: white !important;
--my-registered-color: white;
--check-my-overridden-color: var(--my-overridden-color);
--check-my-registered-color: var(--my-registered-color);
color: var(--my-color); color: var(--my-color);
background-color: firebrick; background-color: firebrick;
padding-top: 2px !important; padding-top: 2px !important;
@@ -244,21 +266,126 @@ add_task(async function () {
!isPropertyOverridden(view, 2, { "--my-color": "white" }), !isPropertyOverridden(view, 2, { "--my-color": "white" }),
"--my-color value in top level rule is not overridden" "--my-color value in top level rule is not overridden"
); );
const variableEl = getRuleViewProperty(
info(
"Check var() in regular rule for a variable set in both regular and starting-style rule"
);
await assertVariableTooltipForProperty(
view, view,
`main, [data-test="top-level"]`, `main, [data-test="top-level"]`,
"color" "color",
).valueSpan.querySelector(".ruleview-variable"); {
is( header: "--my-color = white",
variableEl.dataset.variable, // The starting-style value is displayed in the tooltip
"--my-color = white", startingStyle: "--my-color = black",
"variable popup for --my-color has the expected value" }
);
info(
"Check var() in starting-style rule for a variable set in both regular and starting-style rule"
);
await assertVariableTooltipForProperty(
view,
`main, [data-test="in-starting-style"]`,
"--check-my-color",
{
// The displayed value is the one set in the starting-style rule
header: "--my-color = black",
// The starting-style section is not displayed when hovering starting-style rule
startingStyle: null,
}
);
info(
"Check var() in both regular and starting-style rule for a variable overridden in regular rule"
);
ok(
isPropertyOverridden(view, 3, { "--my-overridden-color": "black" }),
"--my-overridden-color in top-level starting style rule is overridden"
);
await assertVariableTooltipForProperty(
view,
`main, [data-test="top-level"]`,
"--check-my-overridden-color",
{
header: "--my-overridden-color = white",
// The starting-style rule is overridden, so we don't show a starting-style section in the tooltip
startingStyle: null,
}
);
await assertVariableTooltipForProperty(
view,
`main, [data-test="in-starting-style"]`,
"--check-my-overridden-color",
{
// the value is the one from the regular rule, not the one from the starting-style rule
header: "--my-overridden-color = white",
startingStyle: null,
}
);
info(
"Check var() for a registered property in both regular and starting-style rule"
);
await assertVariableTooltipForProperty(
view,
`main, [data-test="top-level"]`,
"--check-my-registered-color",
{
header: "--my-registered-color = white",
// The starting-style value is displayed in the tooltip
startingStyle: "--my-registered-color = black",
// registered property data is displayed
registeredProperty: [
`syntax:"<color>"`,
`inherits:true`,
`initial-value:blue`,
],
}
);
await assertVariableTooltipForProperty(
view,
`main, [data-test="in-starting-style"]`,
"--check-my-registered-color",
{
// The displayed value is the one set in the starting-style rule
header: "--my-registered-color = black",
// The starting-style section is not displayed when hovering starting-style rule
startingStyle: null,
// registered property data is displayed
registeredProperty: [
`syntax:"<color>"`,
`inherits:true`,
`initial-value:blue`,
],
}
);
info("Check var() for a unset registered property in starting-style rule");
await assertVariableTooltipForProperty(
view,
`main, [data-test="in-starting-style"]`,
"--check-my-unset-registered-color",
{
// The displayed value is the registered property initial value
header: "--my-unset-registered-color = lavender",
// The starting-style section is not displayed when hovering starting-style rule
startingStyle: null,
// registered property data is displayed
registeredProperty: [
`syntax:"<color>"`,
`inherits:true`,
`initial-value:lavender`,
],
}
); );
async function assertRules(nodeSelector, expectedRules) { async function assertRules(nodeSelector, expectedRules) {
await selectNode(nodeSelector, inspector); await selectNode(nodeSelector, inspector);
const rulesInView = Array.from( const rulesInView = Array.from(
view.element.querySelectorAll(".ruleview-rule") // don't retrieve @property rules
view.element.querySelectorAll(".ruleview-rule:not([data-name])")
); );
is( is(
rulesInView.length, rulesInView.length,

View File

@@ -148,61 +148,61 @@ add_task(async function () {
checkRegisteredProperties(view, expectedProperties); checkRegisteredProperties(view, expectedProperties);
info("Check that var() tooltips handle registered properties"); info("Check that var() tooltips handle registered properties");
await checkVariableTooltipForProperty( await assertVariableTooltipForProperty(view, "h1", "background-color", {
view,
"h1",
"background-color",
// The variable value is the initial value since the variable does not inherit // The variable value is the initial value since the variable does not inherit
`--css-no-inherit = ${CSS_NO_INHERIT_INITIAL_VALUE}`, header: `--css-no-inherit = ${CSS_NO_INHERIT_INITIAL_VALUE}`,
[ registeredProperty: [
`syntax:"<color>"`, `syntax:"<color>"`,
`inherits:false`, `inherits:false`,
`initial-value:${CSS_NO_INHERIT_INITIAL_VALUE}`, `initial-value:${CSS_NO_INHERIT_INITIAL_VALUE}`,
] ],
); });
await checkVariableTooltipForProperty( await assertVariableTooltipForProperty(view, "h1", "color", {
view,
"h1",
"color",
// The variable value is the value set in the main selector, since the variable does inherit // The variable value is the value set in the main selector, since the variable does inherit
`--css-inherit = ${CSS_INHERIT_MAIN_VALUE}`, header: `--css-inherit = ${CSS_INHERIT_MAIN_VALUE}`,
[ registeredProperty: [
`syntax:"<color>"`, `syntax:"<color>"`,
`inherits:true`, `inherits:true`,
`initial-value:${CSS_INHERIT_INITIAL_VALUE}`, `initial-value:${CSS_INHERIT_INITIAL_VALUE}`,
] ],
); });
await checkVariableTooltipForProperty( await assertVariableTooltipForProperty(
view, view,
"h1", "h1",
"border-color", "border-color",
// The variable value is the initial value since the variable is not set // The variable value is the initial value since the variable is not set
`--css-not-defined = ${CSS_NOT_DEFINED_INITIAL_VALUE}`, {
[ header: `--css-not-defined = ${CSS_NOT_DEFINED_INITIAL_VALUE}`,
`syntax:"<color>"`, registeredProperty: [
`inherits:true`, `syntax:"<color>"`,
`initial-value:${CSS_NOT_DEFINED_INITIAL_VALUE}`, `inherits:true`,
] `initial-value:${CSS_NOT_DEFINED_INITIAL_VALUE}`,
],
}
); );
await checkVariableTooltipForProperty( await assertVariableTooltipForProperty(
view, view,
"h1", "h1",
"height", "height",
// The variable value is the initial value since the variable does not inherit // The variable value is the initial value since the variable does not inherit
`--js-no-inherit = ${JS_NO_INHERIT_INITIAL_VALUE}`, {
[ header: `--js-no-inherit = ${JS_NO_INHERIT_INITIAL_VALUE}`,
`syntax:"<length>"`, registeredProperty: [
`inherits:false`, `syntax:"<length>"`,
`initial-value:${JS_NO_INHERIT_INITIAL_VALUE}`, `inherits:false`,
] `initial-value:${JS_NO_INHERIT_INITIAL_VALUE}`,
],
}
); );
await checkVariableTooltipForProperty( await assertVariableTooltipForProperty(
view, view,
"h1", "h1",
"width", "width",
// The variable value is the value set in the main selector, since the variable does inherit // The variable value is the value set in the main selector, since the variable does inherit
`--js-inherit = ${JS_INHERIT_MAIN_VALUE}`, {
[`syntax:"*"`, `inherits:true`] header: `--js-inherit = ${JS_INHERIT_MAIN_VALUE}`,
registeredProperty: [`syntax:"*"`, `inherits:true`],
}
); );
info( info(
@@ -241,13 +241,14 @@ add_task(async function () {
); );
// The var() tooltip should show the initial value of the new property // The var() tooltip should show the initial value of the new property
await checkVariableTooltipForProperty( await assertVariableTooltipForProperty(view, "h1", "caret-color", {
view, header: `--css-dynamic-registered = orchid`,
"h1", registeredProperty: [
"caret-color", `syntax:"<color>"`,
`--css-dynamic-registered = orchid`, `inherits:false`,
[`syntax:"<color>"`, `inherits:false`, `initial-value:orchid`] `initial-value:orchid`,
); ],
});
info("Check that updating property does update rules view"); info("Check that updating property does update rules view");
onRuleViewRefreshed = view.once("ruleview-refreshed"); onRuleViewRefreshed = view.once("ruleview-refreshed");
@@ -281,13 +282,14 @@ add_task(async function () {
); );
// The var() tooltip should show the new initial value of the updated property // The var() tooltip should show the new initial value of the updated property
await checkVariableTooltipForProperty( await assertVariableTooltipForProperty(view, "h1", "caret-color", {
view, header: `--css-dynamic-registered = purple`,
"h1", registeredProperty: [
"caret-color", `syntax:"<color>"`,
`--css-dynamic-registered = purple`, `inherits:true`,
[`syntax:"<color>"`, `inherits:true`, `initial-value:purple`] `initial-value:purple`,
); ],
});
info("Check that removing property does update rules view"); info("Check that removing property does update rules view");
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
@@ -304,12 +306,9 @@ add_task(async function () {
checkRegisteredProperties(view, expectedProperties); checkRegisteredProperties(view, expectedProperties);
// The var() tooltip should indicate that the property isn't set anymore // The var() tooltip should indicate that the property isn't set anymore
await checkVariableTooltipForProperty( await assertVariableTooltipForProperty(view, "h1", "caret-color", {
view, header: `--css-dynamic-registered is not set`,
"h1", });
"caret-color",
`--css-dynamic-registered is not set`
);
info( info(
"Check that registered properties from new constructed stylesheets are displayed" "Check that registered properties from new constructed stylesheets are displayed"
@@ -353,13 +352,14 @@ add_task(async function () {
); );
// The `var()` tooltip should show the initial-value of the new property // The `var()` tooltip should show the initial-value of the new property
await checkVariableTooltipForProperty( await assertVariableTooltipForProperty(view, "h1", "outline", {
view, header: `--constructed = aqua`,
"h1", registeredProperty: [
"outline", `syntax:"<color>"`,
`--constructed = aqua`, `inherits:true`,
[`syntax:"<color>"`, `inherits:true`, `initial-value:aqua`] `initial-value:aqua`,
); ],
});
info( info(
"Check that selecting a node in another document with no registered property hides the container" "Check that selecting a node in another document with no registered property hides the container"
@@ -552,53 +552,3 @@ function checkRegisteredProperties(view, expectedProperties) {
} }
} }
} }
/**
* Check the content of a `var()` tooltip on a given rule and property name.
*
* @param {CssRuleView} view
* @param {String} ruleSelector
* @param {String} propertyName
* @param {String} expectedTooltipValueContent
* @param {Array<String>} expectedTooltipRegisteredPropertyContent
*/
async function checkVariableTooltipForProperty(
view,
ruleSelector,
propertyName,
expectedTooltipValueContent,
expectedTooltipRegisteredPropertyContent
) {
// retrieve tooltip target
const variableEl = await waitFor(() =>
getRuleViewProperty(
view,
ruleSelector,
propertyName
).valueSpan.querySelector(".ruleview-variable,.ruleview-unmatched-variable")
);
const previewTooltip = await assertShowPreviewTooltip(view, variableEl);
const [valueEl, registeredPropertyEl] = previewTooltip.panel.querySelectorAll(
".variable-value,.registered-property"
);
is(
valueEl.textContent,
expectedTooltipValueContent,
`CSS variable preview tooltip shows the expected value for ${propertyName} in ${ruleSelector}`
);
if (!expectedTooltipRegisteredPropertyContent) {
is(
registeredPropertyEl,
undefined,
`CSS variable preview tooltip doesn't have registered property section for ${propertyName} in ${ruleSelector}`
);
} else {
is(
registeredPropertyEl.innerText,
expectedTooltipRegisteredPropertyContent.join("\n"),
`CSS variable preview tooltip has expected registered property section for ${propertyName} in ${ruleSelector}`
);
}
await assertTooltipHiddenOnMouseOut(previewTooltip, variableEl);
}

View File

@@ -166,6 +166,7 @@ function getNodeInfo(node, elementStyle) {
sheetHref: rule.domRule.href, sheetHref: rule.domRule.href,
textProperty: declaration, textProperty: declaration,
variable: node.dataset.variable, variable: node.dataset.variable,
startingStyleVariable: node.dataset.startingStyleVariable,
registeredProperty: { registeredProperty: {
initialValue: node.dataset.registeredPropertyInitialValue, initialValue: node.dataset.registeredPropertyInitialValue,
syntax: node.dataset.registeredPropertySyntax, syntax: node.dataset.registeredPropertySyntax,

View File

@@ -611,6 +611,7 @@ TextPropertyEditor.prototype = {
varName, varName,
this.rule.pseudoElement this.rule.pseudoElement
), ),
inStartingStyleRule: this.rule.isInStartingStyle(),
}; };
const frag = outputParser.parseCssProperty(name, val, parserOptions); const frag = outputParser.parseCssProperty(name, val, parserOptions);

View File

@@ -347,8 +347,13 @@ TooltipsOverlay.prototype = {
type === TOOLTIP_VARIABLE_TYPE && type === TOOLTIP_VARIABLE_TYPE &&
nodeInfo.value.value.startsWith("--") nodeInfo.value.value.startsWith("--")
) { ) {
const { variable, registeredProperty } = nodeInfo.value; const { variable, registeredProperty, startingStyleVariable } =
await this._setVariablePreviewTooltip(variable, registeredProperty); nodeInfo.value;
await this._setVariablePreviewTooltip({
topSectionText: variable,
registeredProperty,
startingStyle: startingStyleVariable,
});
this.sendOpenScalarToTelemetry(type); this.sendOpenScalarToTelemetry(type);
@@ -539,17 +544,16 @@ TooltipsOverlay.prototype = {
/** /**
* Set the content of the preview tooltip to display a variable preview. * Set the content of the preview tooltip to display a variable preview.
* *
* @param {String} text * @param {Object} tooltipParams
* The text to display for the variable tooltip * See VariableTooltipHelper#setVariableTooltip `params`.
* @return {Promise} A promise that resolves when the preview tooltip content is ready * @return {Promise} A promise that resolves when the preview tooltip content is ready
*/ */
async _setVariablePreviewTooltip(text, registeredProperty) { async _setVariablePreviewTooltip(tooltipParams) {
const doc = this.view.inspector.panelDoc; const doc = this.view.inspector.panelDoc;
await setVariableTooltip( await setVariableTooltip(
this.getTooltip("previewTooltip"), this.getTooltip("previewTooltip"),
doc, doc,
text, tooltipParams
registeredProperty
); );
}, },

View File

@@ -998,6 +998,82 @@ async function assertTooltipHiddenOnMouseOut(tooltip, target) {
ok(!tooltip.isVisible(), "The tooltip is hidden on mouseout"); ok(!tooltip.isVisible(), "The tooltip is hidden on mouseout");
} }
/**
* Check the content of a `var()` tooltip on a given rule and property name.
*
* @param {CssRuleView} view
* @param {String} ruleSelector
* @param {String} propertyName
* @param {Object} tooltipExpected
* @param {String} tooltipExpected.header: The text that is displayed in the top section
* (might be the only section when the variable is not a registered property and
* there is no starting-style).
* @param {String} tooltipExpected.startingStyle: The text that is displayed in the starting-style
* section. Pass undefined if the tooltip isn't supposed to have a `@starting-style` section.
* @param {Array<String>} tooltipExpected.registeredProperty: Array of the registered property
* fields (e.g. [`syntax:"<color>"`, `inherits:true`, `initial-value:aqua`]).
* Pass undefined if the tooltip isn't supposed to have a @property section.
*/
async function assertVariableTooltipForProperty(
view,
ruleSelector,
propertyName,
{ header, registeredProperty, startingStyle }
) {
// retrieve tooltip target
const variableEl = await waitFor(() =>
getRuleViewProperty(
view,
ruleSelector,
propertyName
).valueSpan.querySelector(".ruleview-variable,.ruleview-unmatched-variable")
);
const previewTooltip = await assertShowPreviewTooltip(view, variableEl);
const valueEl = previewTooltip.panel.querySelector(".variable-value");
const startingStyleEl = previewTooltip.panel.querySelector(
".starting-style div"
);
const registeredPropertyEl = previewTooltip.panel.querySelector(
".registered-property dl"
);
is(
valueEl.textContent,
header,
`CSS variable preview tooltip has expected header text for ${propertyName} in ${ruleSelector}`
);
if (!registeredProperty) {
is(
registeredPropertyEl,
null,
`CSS variable preview tooltip doesn't have registered property section for ${propertyName} in ${ruleSelector}`
);
} else {
is(
registeredPropertyEl.innerText,
registeredProperty.join("\n"),
`CSS variable preview tooltip has expected registered property section for ${propertyName} in ${ruleSelector}`
);
}
if (!startingStyle) {
is(
startingStyleEl,
null,
`CSS variable preview tooltip doesn't have a starting-style section for ${propertyName} in ${ruleSelector}`
);
} else {
is(
startingStyleEl.innerText,
startingStyle,
`CSS variable preview tooltip has expected starting-style section for ${propertyName} in ${ruleSelector}`
);
}
await assertTooltipHiddenOnMouseOut(previewTooltip, variableEl);
}
/** /**
* Get the text displayed for a given DOM Element's textContent within the * Get the text displayed for a given DOM Element's textContent within the
* markup view. * markup view.

View File

@@ -297,31 +297,57 @@ class OutputParser {
const secondOpts = {}; const secondOpts = {};
let varData; let varData;
let varValue;
let varFallbackValue; let varFallbackValue;
let varSubsitutedValue;
// Get the variable value if it is in use. // Get the variable value if it is in use.
if (tokens && tokens.length === 1) { if (tokens && tokens.length === 1) {
varData = options.getVariableData(tokens[0].text); varData = options.getVariableData(tokens[0].text);
varValue = const varValue =
typeof varData.value === "string" typeof varData.value === "string"
? varData.value ? varData.value
: varData.registeredProperty?.initialValue; : varData.registeredProperty?.initialValue;
const varStartingStyleValue =
typeof varData.startingStyle === "string"
? varData.startingStyle
: // If the variable is not set in starting style, then it will default to either:
// - a declaration in a "regular" rule
// - or if there's no declaration in regular rule, to the registered property initial-value.
varValue;
varSubsitutedValue = options.inStartingStyleRule
? varStartingStyleValue
: varValue;
} }
// Get the variable name. // Get the variable name.
const varName = text.substring(tokens[0].startOffset, tokens[0].endOffset); const varName = text.substring(tokens[0].startOffset, tokens[0].endOffset);
if (typeof varValue === "string") { if (typeof varSubsitutedValue === "string") {
// The variable value is valid, set the variable name's title of the first argument // The variable value is valid, set the variable name's title of the first argument
// in var() to display the variable name and value. // in var() to display the variable name and value.
firstOpts["data-variable"] = STYLE_INSPECTOR_L10N.getFormatStr( firstOpts["data-variable"] = STYLE_INSPECTOR_L10N.getFormatStr(
"rule.variableValue", "rule.variableValue",
varName, varName,
varValue varSubsitutedValue
); );
firstOpts.class = options.matchedVariableClass; firstOpts.class = options.matchedVariableClass;
secondOpts.class = options.unmatchedVariableClass; secondOpts.class = options.unmatchedVariableClass;
// Display starting-style value when not in a starting style rule
if (
!options.inStartingStyleRule &&
typeof varData.startingStyle === "string"
) {
firstOpts["data-starting-style-variable"] =
STYLE_INSPECTOR_L10N.getFormatStr(
"rule.variableValue",
varName,
varData.startingStyle
);
}
if (varData.registeredProperty) { if (varData.registeredProperty) {
const { initialValue, syntax, inherits } = varData.registeredProperty; const { initialValue, syntax, inherits } = varData.registeredProperty;
firstOpts["data-registered-property-initial-value"] = initialValue; firstOpts["data-registered-property-initial-value"] = initialValue;
@@ -363,7 +389,7 @@ class OutputParser {
return { return {
node: variableNode, node: variableNode,
value: varValue, value: varSubsitutedValue,
fallbackValue: varFallbackValue, fallbackValue: varFallbackValue,
}; };
} }
@@ -2074,6 +2100,7 @@ class OutputParser {
baseURI: undefined, baseURI: undefined,
getVariableData: null, getVariableData: null,
unmatchedVariableClass: null, unmatchedVariableClass: null,
inStartingStyleRule: false,
}; };
for (const item in overrides) { for (const item in overrides) {

View File

@@ -14,23 +14,57 @@ const XHTML_NS = "http://www.w3.org/1999/xhtml";
* The tooltip instance on which the text preview content should be set. * The tooltip instance on which the text preview content should be set.
* @param {Document} doc * @param {Document} doc
* A document element to create the HTML elements needed for the tooltip. * A document element to create the HTML elements needed for the tooltip.
* @param {String} text * @param {Object} params
* Text to display in tooltip. * @param {String} params.topSectionText
* Text to display in the top section of tooltip (e.g. "--x = blue" or "--x is not defined").
* @param {Object} params.registeredProperty
* Contains the registered property data, if the variable was registered (@property or CSS.registerProperty)
* @param {String} params.registeredProperty.syntax
* The registered property `syntax` value
* @param {Boolean} params.registeredProperty.inherits
* The registered property `inherits` value
* @param {String} params.registeredProperty.initialValue
* The registered property `initial-value`
* @param {String} params.startingStyle
* The text for @starting-style value (e.g. `--x = red`)
*/ */
function setVariableTooltip(tooltip, doc, text, registeredProperty) { function setVariableTooltip(
tooltip,
doc,
{ topSectionText, registeredProperty, startingStyle }
) {
// Create tooltip content // Create tooltip content
const div = doc.createElementNS(XHTML_NS, "div"); const div = doc.createElementNS(XHTML_NS, "div");
div.classList.add("devtools-monospace", "devtools-tooltip-css-variable"); div.classList.add("devtools-monospace", "devtools-tooltip-css-variable");
const valueEl = doc.createElementNS(XHTML_NS, "section"); const valueEl = doc.createElementNS(XHTML_NS, "section");
valueEl.classList.add("variable-value"); valueEl.classList.add("variable-value");
valueEl.append(doc.createTextNode(text)); valueEl.append(doc.createTextNode(topSectionText));
div.appendChild(valueEl); div.appendChild(valueEl);
// A registered property always have a non-falsy syntax
if (typeof startingStyle !== "undefined") {
const section = doc.createElementNS(XHTML_NS, "section");
section.classList.add("starting-style", "variable-tooltip-section");
const h2 = doc.createElementNS(XHTML_NS, "h2");
h2.append(doc.createTextNode("@starting-style"));
const startingStyleValue = doc.createElementNS(XHTML_NS, "div");
startingStyleValue.append(doc.createTextNode(startingStyle));
section.append(h2, startingStyleValue);
div.appendChild(section);
}
// A registered property always have a non-falsy syntax // A registered property always have a non-falsy syntax
if (registeredProperty?.syntax) { if (registeredProperty?.syntax) {
const section = doc.createElementNS(XHTML_NS, "section");
section.classList.add("registered-property", "variable-tooltip-section");
const h2 = doc.createElementNS(XHTML_NS, "h2");
h2.append(doc.createTextNode("@property"));
const dl = doc.createElementNS(XHTML_NS, "dl"); const dl = doc.createElementNS(XHTML_NS, "dl");
dl.classList.add("registered-property");
const addProperty = (label, value, lineBreak = true) => { const addProperty = (label, value, lineBreak = true) => {
const dt = doc.createElementNS(XHTML_NS, "dt"); const dt = doc.createElementNS(XHTML_NS, "dt");
dt.append(doc.createTextNode(label)); dt.append(doc.createTextNode(label));
@@ -50,7 +84,8 @@ function setVariableTooltip(tooltip, doc, text, registeredProperty) {
addProperty("initial-value:", registeredProperty.initialValue, false); addProperty("initial-value:", registeredProperty.initialValue, false);
} }
div.appendChild(dl); section.append(h2, dl);
div.appendChild(section);
} }
tooltip.panel.innerHTML = ""; tooltip.panel.innerHTML = "";

View File

@@ -94,15 +94,25 @@ strong {
word-break: break-word; word-break: break-word;
max-inline-size: 100vw; max-inline-size: 100vw;
.variable-value:has(+ .registered-property) { .variable-value:has(+ .variable-tooltip-section) {
padding-block-end: var(--block-padding); padding-block-end: var(--block-padding);
} }
.registered-property { .variable-tooltip-section {
border-block-start: 1px solid var(--theme-splitter-color); border-block-start: 1px solid var(--theme-splitter-color);
margin: 0; margin: 0;
padding: 0; padding-block: var(--block-padding);
padding-block-start: var(--block-padding);
h2 {
margin-block: 0;
padding-block-start: 0;
padding-block-end: var(--block-padding);
}
dl {
padding: 0;
margin: 0;
}
dt,dd { dt,dd {
display: inline; display: inline;