Bug 1145527 - Display the inherited and used CSS Variable in the rule view. r=tromey

Displays which CSS variable is used in the rule view.
Displays the actual variable value on the variable name's title property.
Displays all the inherited CSS variables on the selected element in the rule view.



MozReview-Commit-ID: 52uLJA123AP
This commit is contained in:
Rahul Chaudhary 2017-07-02 22:39:45 +02:00
Родитель 94f37c30f0
Коммит 087b46a0b9
12 изменённых файлов: 615 добавлений и 49 удалений

Просмотреть файл

@ -10,7 +10,7 @@ const promise = require("promise");
const Rule = require("devtools/client/inspector/rules/models/rule");
const {promiseWarn} = require("devtools/client/inspector/shared/utils");
const {ELEMENT_STYLE} = require("devtools/shared/specs/styles");
const {getCssProperties} = require("devtools/shared/fronts/css-properties");
const {getCssProperties, isCssVariable} = require("devtools/shared/fronts/css-properties");
/**
* ElementStyle is responsible for the following:
@ -40,6 +40,7 @@ function ElementStyle(element, ruleView, store, pageStyle,
this.showUserAgentStyles = showUserAgentStyles;
this.rules = [];
this.cssProperties = getCssProperties(this.ruleView.inspector.toolbox);
this.variables = new Map();
// We don't want to overwrite this.store.userProperties so we only create it
// if it doesn't already exist.
@ -199,7 +200,9 @@ ElementStyle.prototype = {
* Calls markOverridden with all supported pseudo elements
*/
markOverriddenAll: function () {
this.variables.clear();
this.markOverridden();
for (let pseudo of this.cssProperties.pseudoElements) {
this.markOverridden(pseudo);
}
@ -288,6 +291,10 @@ ElementStyle.prototype = {
computedProp.overridden = overridden;
if (!computedProp.overridden && computedProp.textProp.enabled) {
taken[computedProp.name] = computedProp;
if (isCssVariable(computedProp.name)) {
this.variables.set(computedProp.name, computedProp.value);
}
}
}
@ -328,7 +335,20 @@ ElementStyle.prototype = {
dirty = (!!prop.overridden !== overridden) || dirty;
prop.overridden = overridden;
return dirty;
}
},
/**
* Returns the current value of a CSS variable; or null if the
* variable is not defined.
*
* @param {String} name
* The name of the variable.
* @return {String} the variable's value or null if the variable is
* not defined.
*/
getVariable: function (name) {
return this.variables.get(name);
},
};
/**

Просмотреть файл

@ -36,6 +36,8 @@ support-files =
doc_test_image.png
doc_urls_clickable.css
doc_urls_clickable.html
doc_variables_1.html
doc_variables_2.html
head.js
!/devtools/client/commandline/test/helpers.js
!/devtools/client/framework/test/shared-head.js
@ -96,6 +98,8 @@ skip-if = stylo # Bug 1387445
[browser_rules_completion-popup-hidden-after-navigation.js]
[browser_rules_content_01.js]
[browser_rules_content_02.js]
[browser_rules_variables_01.js]
[browser_rules_variables_02.js]
skip-if = e10s && debug # Bug 1250058 - Docshell leak on debug e10s
[browser_rules_context-menu-show-mdn-docs-01.js]
[browser_rules_context-menu-show-mdn-docs-02.js]

Просмотреть файл

@ -0,0 +1,35 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test for variables in rule view.
const TEST_URI = URL_ROOT + "doc_variables_1.html";
add_task(function* () {
yield addTab(TEST_URI);
let {inspector, view} = yield openRuleView();
yield selectNode("#target", inspector);
info("Tests basic support for CSS Variables for both single variable " +
"and double variable. Formats tested: var(x, constant), var(x, var(y))");
let unsetColor = getRuleViewProperty(view, "div", "color").valueSpan
.querySelector(".ruleview-variable-unmatched");
let setColor = unsetColor.previousElementSibling;
is(unsetColor.textContent, " red", "red is unmatched in color");
is(setColor.textContent, "--color", "--color is not set correctly");
is(setColor.title, "--color = chartreuse", "--color's title is not set correctly");
let unsetVar = getRuleViewProperty(view, "div", "background-color").valueSpan
.querySelector(".ruleview-variable-unmatched");
let setVar = unsetVar.nextElementSibling;
let setVarName = setVar.firstElementChild.firstElementChild;
is(unsetVar.textContent, "--not-set",
"--not-set is unmatched in background-color");
is(setVar.textContent, " var(--bg)", "var(--bg) parsed incorrectly");
is(setVarName.textContent, "--bg", "--bg is not set correctly");
is(setVarName.title, "--bg = seagreen", "--bg's title is not set correctly");
});

Просмотреть файл

@ -0,0 +1,190 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test for variables in rule view.
const TEST_URI = URL_ROOT + "doc_variables_2.html";
add_task(function* () {
yield addTab(TEST_URI);
let {inspector, view} = yield openRuleView();
yield testBasic(inspector, view);
yield testNestedCssFunctions(inspector, view);
yield testBorderShorthandAndInheritance(inspector, view);
yield testSingleLevelVariable(inspector, view);
yield testDoubleLevelVariable(inspector, view);
yield testTripleLevelVariable(inspector, view);
});
function* testBasic(inspector, view) {
info("Test support for basic variable functionality for var() with 2 variables." +
"Format: var(--var1, var(--var2))");
yield selectNode("#a", inspector);
let unsetVar = getRuleViewProperty(view, "#a", "font-size").valueSpan
.querySelector(".ruleview-variable-unmatched");
let setVarParent = unsetVar.nextElementSibling;
let setVar = getVarFromParent(setVarParent);
is(unsetVar.textContent, "--var-not-defined",
"--var-not-defined is not set correctly");
is(unsetVar.title, "--var-not-defined is not set",
"--var-not-defined's title is not set correctly");
is(setVarParent.textContent, " var(--var-defined-font-size)",
"var(--var-defined-font-size) parsed incorrectly");
is(setVar.textContent, "--var-defined-font-size",
"--var-defined-font-size is not set correctly");
is(setVar.title, "--var-defined-font-size = 60px",
"--bg's title is not set correctly");
}
function* testNestedCssFunctions(inspector, view) {
info("Test support for variable functionality for a var() nested inside " +
"another CSS function. Format: rgb(0, 0, var(--var1, var(--var2)))");
yield selectNode("#b", inspector);
let unsetVarParent = getRuleViewProperty(view, "#b", "color").valueSpan
.querySelector(".ruleview-variable-unmatched");
let unsetVar = getVarFromParent(unsetVarParent);
let setVar = unsetVarParent.previousElementSibling;
is(unsetVarParent.textContent, " var(--var-defined-r-2)",
"var(--var-defined-r-2) not parsed correctly");
is(unsetVar.textContent, "--var-defined-r-2",
"--var-defined-r-2 is not set correctly");
is(unsetVar.title, "--var-defined-r-2 = 0",
"--var-defined-r-2's title is not set correctly");
is(setVar.textContent, "--var-defined-r-1",
"--var-defined-r-1 is not set correctly");
is(setVar.title, "--var-defined-r-1 = 255",
"--var-defined-r-1's title is not set correctly");
}
function* testBorderShorthandAndInheritance(inspector, view) {
info("Test support for variable functionality for shorthands/CSS styles with spaces " +
"like \"margin: w x y z\". Also tests functionality for inherticance of CSS" +
" variables. Format: var(l, var(m)) var(x) rgb(var(r) var(g) var(b))");
yield selectNode("#c", inspector);
let unsetVarL = getRuleViewProperty(view, "#c", "border").valueSpan
.querySelector(".ruleview-variable-unmatched");
let setVarMParent = unsetVarL.nextElementSibling;
// var(x) is the next sibling of the parent of M
let setVarXParent = setVarMParent.parentNode.nextElementSibling;
// var(r) is the next sibling of var(x), and var(g) is the next sibling of var(r), etc.
let setVarRParent = setVarXParent.nextElementSibling;
let setVarGParent = setVarRParent.nextElementSibling;
let setVarBParent = setVarGParent.nextElementSibling;
let setVarM = getVarFromParent(setVarMParent);
let setVarX = setVarXParent.firstElementChild;
let setVarR = setVarRParent.firstElementChild;
let setVarG = setVarGParent.firstElementChild;
let setVarB = setVarBParent.firstElementChild;
is(unsetVarL.textContent, "--var-undefined",
"--var-undefined is not set correctly");
is(unsetVarL.title, "--var-undefined is not set",
"--var-undefined's title is not set correctly");
is(setVarM.textContent, "--var-border-px",
"--var-border-px is not set correctly");
is(setVarM.title, "--var-border-px = 10px",
"--var-border-px's title is not set correctly");
is(setVarX.textContent, "--var-border-style",
"--var-border-style is not set correctly");
is(setVarX.title, "--var-border-style = solid",
"var-border-style's title is not set correctly");
is(setVarR.textContent, "--var-border-r",
"--var-defined-r is not set correctly");
is(setVarR.title, "--var-border-r = 255",
"--var-defined-r's title is not set correctly");
is(setVarG.textContent, "--var-border-g",
"--var-defined-g is not set correctly");
is(setVarG.title, "--var-border-g = 0",
"--var-defined-g's title is not set correctly");
is(setVarB.textContent, "--var-border-b",
"--var-defined-b is not set correctly");
is(setVarB.title, "--var-border-b = 0",
"--var-defined-b's title is not set correctly");
}
function* testSingleLevelVariable(inspector, view) {
info("Test support for variable functionality of a single level of " +
"undefined variables. Format: var(x, constant)");
yield selectNode("#d", inspector);
let unsetVar = getRuleViewProperty(view, "#d", "font-size").valueSpan
.querySelector(".ruleview-variable-unmatched");
is(unsetVar.textContent, "--var-undefined",
"--var-undefined is not set correctly");
is(unsetVar.title, "--var-undefined is not set",
"--var-undefined's title is not set correctly");
}
function* testDoubleLevelVariable(inspector, view) {
info("Test support for variable functionality of double level of " +
"undefined variables. Format: var(x, var(y, constant))");
yield selectNode("#e", inspector);
let allUnsetVars = getRuleViewProperty(view, "#e", "color").valueSpan
.querySelectorAll(".ruleview-variable-unmatched");
is(allUnsetVars.length, 2, "The number of unset variables is mismatched.");
let unsetVar1 = allUnsetVars[0];
let unsetVar2 = allUnsetVars[1];
is(unsetVar1.textContent, "--var-undefined",
"--var-undefined is not set correctly");
is(unsetVar1.title, "--var-undefined is not set",
"--var-undefined's title is not set correctly");
is(unsetVar2.textContent, "--var-undefined-2",
"--var-undefined is not set correctly");
is(unsetVar2.title, "--var-undefined-2 is not set",
"--var-undefined-2's title is not set correctly");
}
function* testTripleLevelVariable(inspector, view) {
info("Test support for variable functionality of triple level of " +
"undefined variables. Format: var(x, var(y, var(z, constant)))");
yield selectNode("#f", inspector);
let allUnsetVars = getRuleViewProperty(view, "#f", "border-style").valueSpan
.querySelectorAll(".ruleview-variable-unmatched");
is(allUnsetVars.length, 3, "The number of unset variables is mismatched.");
let unsetVar1 = allUnsetVars[0];
let unsetVar2 = allUnsetVars[1];
let unsetVar3 = allUnsetVars[2];
is(unsetVar1.textContent, "--var-undefined",
"--var-undefined is not set correctly");
is(unsetVar1.title, "--var-undefined is not set",
"--var-undefined's title is not set correctly");
is(unsetVar2.textContent, "--var-undefined-2",
"--var-undefined-2 is not set correctly");
is(unsetVar2.title, "--var-undefined-2 is not set",
"--var-defined-r-2's title is not set correctly");
is(unsetVar3.textContent, "--var-undefined-3",
"--var-undefined-3 is not set correctly");
is(unsetVar3.title, "--var-undefined-3 is not set",
"--var-defined-r-3's title is not set correctly");
}
function getVarFromParent(varParent) {
return varParent.firstElementChild.firstElementChild;
}

Просмотреть файл

@ -0,0 +1,23 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<html>
<head>
<title>variables test</title>
<style>
* {
--color: tomato;
--bg: violet;
}
div {
--color: chartreuse;
color: var(--color, red);
background-color: var(--not-set, var(--bg));
}
</style>
</head>
<body>
<div id="target" style="--bg: seagreen;"> the ocean </div>
</body>
</html>

Просмотреть файл

@ -0,0 +1,45 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<html>
<head>
<title>variables test</title>
<style>
:root {
--var-border-px: 10px;
--var-border-style: solid;
--var-border-r: 255;
--var-border-g: 0;
--var-border-b: 0;
}
#a {
--var-defined-font-size: 60px;
font-size: var(--var-not-defined, var(--var-defined-font-size));
}
#b {
--var-defined-r-1: 255;
--var-defined-r-2: 0;
color: rgb(var(--var-defined-r-1, var(--var-defined-r-2)), 0, 0);
}
#c {
border: var(--var-undefined, var(--var-border-px)) var(--var-border-style) rgb(var(--var-border-r), var(--var-border-g), var(--var-border-b))
}
#d {
font-size: var(--var-undefined, 30px);
}
#e {
color: var(--var-undefined, var(--var-undefined-2, blue));
}
#f {
border-style: var(--var-undefined, var(--var-undefined-2, var(--var-undefined-3, solid)));
}
</style>
</head>
<body>
<div id="a">A</div><br>
<div id="b">B</div><br>
<div id="c">C</div><br>
<div id="d">D</div><br>
<div id="e">E</div><br>
<div id="f">F</div>
</body>
</html>

Просмотреть файл

@ -363,7 +363,9 @@ TextPropertyEditor.prototype = {
shapeClass: "ruleview-shape",
defaultColorType: !propDirty,
urlClass: "theme-link",
baseURI: this.sheetHref
baseURI: this.sheetHref,
unmatchedVariableClass: "ruleview-variable-unmatched",
isVariableInUse: varName => this.rule.elementStyle.getVariable(varName),
};
let frag = outputParser.parseCssProperty(name, val, parserOptions);
this.valueSpan.innerHTML = "";

Просмотреть файл

@ -18,6 +18,10 @@ const {
const {appendText} = require("devtools/client/inspector/shared/utils");
const Services = require("Services");
const STYLE_INSPECTOR_PROPERTIES = "devtools/shared/locales/styleinspector.properties";
const {LocalizationHelper} = require("devtools/shared/l10n");
const STYLE_INSPECTOR_L10N = new LocalizationHelper(STYLE_INSPECTOR_PROPERTIES);
const HTML_NS = "http://www.w3.org/1999/xhtml";
const CSS_GRID_ENABLED_PREF = "layout.css.grid.enabled";
const CSS_SHAPES_ENABLED_PREF = "devtools.inspector.shapesHighlighter.enabled";
@ -96,24 +100,37 @@ OutputParser.prototype = {
},
/**
* Given an initial FUNCTION token, read tokens from |tokenStream|
* and collect all the (non-comment) text. Return the collected
* text. The function token and the close paren are included in the
* result.
* Read tokens from |tokenStream| and collect all the (non-comment)
* text. Return the collected texts and variable data (if any).
* Stop when an unmatched closing paren is seen.
* If |stopAtComma| is true, then also stop when a top-level
* (unparenthesized) comma is seen.
*
* @param {CSSToken} initialToken
* The FUNCTION token.
* @param {String} text
* The original CSS text.
* The original source text.
* @param {CSSLexer} tokenStream
* The token stream from which to read.
* @return {String}
* The text of body of the function call.
* @param {Object} options
* The options object in use; @see _mergeOptions.
* @param {Boolean} stopAtComma
* If true, stop at a comma.
* @return {Object}
* An object of the form {tokens, functionData, sawComma, sawVariable}.
* |tokens| is a list of the non-comment, non-whitespace tokens
* that were seen. The stopping token (paren or comma) will not
* be included.
* |functionData| is a list of parsed strings and nodes that contain the
* data between the matching parenthesis. The stopping token's text will
* not be included.
* |sawComma| is true if the stop was due to a comma, or false otherwise.
* |sawVariable| is true if a variable was seen while parsing the text.
*/
_collectFunctionText: function (initialToken, text, tokenStream) {
let result = text.substring(initialToken.startOffset,
initialToken.endOffset);
_parseMatchingParens: function (text, tokenStream, options, stopAtComma) {
let depth = 1;
let functionData = [];
let tokens = [];
let sawVariable = false;
while (depth > 0) {
let token = tokenStream.nextToken();
if (!token) {
@ -122,37 +139,142 @@ OutputParser.prototype = {
if (token.tokenType === "comment") {
continue;
}
result += text.substring(token.startOffset, token.endOffset);
if (token.tokenType === "symbol") {
if (token.text === "(") {
if (stopAtComma && depth === 1 && token.text === ",") {
return { tokens, functionData, sawComma: true, sawVariable };
} else if (token.text === "(") {
++depth;
} else if (token.text === ")") {
--depth;
if (depth === 0) {
break;
}
}
} else if (token.tokenType === "function" && token.text === "var" &&
options.isVariableInUse) {
sawVariable = true;
let variableNode = this._parseVariable(token, text, tokenStream, options);
functionData.push(variableNode);
} else if (token.tokenType === "function") {
++depth;
}
if (token.tokenType !== "function" || token.text !== "var" ||
!options.isVariableInUse) {
functionData.push(text.substring(token.startOffset, token.endOffset));
}
if (token.tokenType !== "whitespace") {
tokens.push(token);
}
}
return result;
return { tokens, functionData, sawComma: false, sawVariable };
},
/**
* Parse a string.
* Parse var() use and return a variable node to be added to the output state.
* This will read tokens up to and including the ")" that closes the "var("
* invocation.
*
* @param {CSSToken} initialToken
* The "var(" token that was already seen.
* @param {String} text
* The original input text.
* @param {CSSLexer} tokenStream
* The token stream from which to read.
* @param {Object} options
* The options object in use; @see _mergeOptions.
* @return {Object}
* A node for the variable, with the appropriate text and
* title. Eg. a span with "var(--var1)" as the textContent
* and a title for --var1 like "--var1 = 10" or
* "--var1 is not set".
*/
_parseVariable: function (initialToken, text, tokenStream, options) {
// Handle the "var(".
let varText = text.substring(initialToken.startOffset,
initialToken.endOffset);
let variableNode = this._createNode("span", {}, varText);
// Parse the first variable name within the parens of var().
let {tokens, functionData, sawComma, sawVariable} =
this._parseMatchingParens(text, tokenStream, options, true);
let result = sawVariable ? "" : functionData.join("");
// Display options for the first and second argument in the var().
let firstOpts = {};
let secondOpts = {};
let varValue;
// Get the variable value if it is in use.
if (tokens && tokens.length === 1) {
varValue = options.isVariableInUse(tokens[0].text);
}
// Get the variable name.
let varName = text.substring(tokens[0].startOffset, tokens[0].endOffset);
if (typeof varValue === "string") {
// The variable value is valid, set the variable name's title of the first argument
// in var() to display the variable name and value.
firstOpts.title =
STYLE_INSPECTOR_L10N.getFormatStr("rule.variableValue", varName, varValue);
secondOpts.class = options.unmatchedVariableClass;
} else {
// The variable name is not valid, mark it unmatched.
firstOpts.class = options.unmatchedVariableClass;
firstOpts.title = STYLE_INSPECTOR_L10N.getFormatStr("rule.variableUnset",
varName);
}
variableNode.appendChild(this._createNode("span", firstOpts, result));
// If we saw a ",", then append it and show the remainder using
// the correct highlighting.
if (sawComma) {
variableNode.appendChild(this.doc.createTextNode(","));
// Parse the text up until the close paren, being sure to
// disable the special case for filter.
let subOptions = Object.assign({}, options);
subOptions.expectFilter = false;
let saveParsed = this.parsed;
this.parsed = [];
let rest = this._doParse(text, subOptions, tokenStream, true);
this.parsed = saveParsed;
let span = this._createNode("span", secondOpts);
span.appendChild(rest);
variableNode.appendChild(span);
}
variableNode.appendChild(this.doc.createTextNode(")"));
return variableNode;
},
/* eslint-disable complexity */
/**
* The workhorse for @see _parse. This parses some CSS text,
* stopping at EOF; or optionally when an umatched close paren is
* seen.
*
* @param {String} text
* Text to parse.
* @param {Object} [options]
* Options object. For valid options and default values see
* _mergeOptions().
* The original input text.
* @param {Object} options
* The options object in use; @see _mergeOptions.
* @param {CSSLexer} tokenStream
* The token stream from which to read
* @param {Boolean} stopAtCloseParen
* If true, stop at an umatched close paren.
* @return {DocumentFragment}
* A document fragment.
*/
_parse: function (text, options = {}) {
text = text.trim();
this.parsed.length = 0;
let tokenStream = getCSSLexer(text);
let parenDepth = 0;
_doParse: function (text, options, tokenStream, stopAtCloseParen) {
let parenDepth = stopAtCloseParen ? 1 : 0;
let outerMostFunctionTakesColor = false;
let colorOK = function () {
@ -166,12 +288,17 @@ OutputParser.prototype = {
};
let spaceNeeded = false;
let token = tokenStream.nextToken();
while (token) {
let done = false;
while (!done) {
let token = tokenStream.nextToken();
if (!token) {
break;
}
if (token.tokenType === "comment") {
// This doesn't change spaceNeeded, because we didn't emit
// anything to the output.
token = tokenStream.nextToken();
continue;
}
@ -190,21 +317,44 @@ OutputParser.prototype = {
token.text);
}
++parenDepth;
} else if (token.text === "var" && options.isVariableInUse) {
let variableNode = this._parseVariable(token, text, tokenStream, options);
this.parsed.push(variableNode);
} else {
let functionText = this._collectFunctionText(token, text,
tokenStream);
let {functionData, sawVariable} = this._parseMatchingParens(text, tokenStream,
options);
if (options.expectCubicBezier && token.text === "cubic-bezier") {
this._appendCubicBezier(functionText, options);
} else if (colorOK() &&
colorUtils.isValidCSSColor(functionText, this.cssColor4)) {
this._appendColor(functionText, options);
} else if (options.expectShape &&
Services.prefs.getBoolPref(CSS_SHAPES_ENABLED_PREF) &&
BASIC_SHAPE_FUNCTIONS.includes(token.text)) {
this._appendShape(functionText, options);
let functionName = text.substring(token.startOffset, token.endOffset);
if (sawVariable) {
// If function contains variable, we need to add both strings
// and nodes.
this._appendTextNode(functionName);
for (let data of functionData) {
if (typeof data === "string") {
this._appendTextNode(data);
} else if (data) {
this.parsed.push(data);
}
}
this._appendTextNode(")");
} else {
this._appendTextNode(functionText);
// If no variable in function, join the text together and add
// to DOM accordingly.
let functionText = functionName + functionData.join("") + ")";
if (options.expectCubicBezier && token.text === "cubic-bezier") {
this._appendCubicBezier(functionText, options);
} else if (colorOK() &&
colorUtils.isValidCSSColor(functionText, this.cssColor4)) {
this._appendColor(functionText, options);
} else if (options.expectShape &&
Services.prefs.getBoolPref(CSS_SHAPES_ENABLED_PREF) &&
BASIC_SHAPE_FUNCTIONS.includes(token.text)) {
this._appendShape(functionText, options);
} else {
this._appendTextNode(functionText);
}
}
}
break;
@ -262,6 +412,12 @@ OutputParser.prototype = {
++parenDepth;
} else if (token.text === ")") {
--parenDepth;
if (stopAtCloseParen && parenDepth === 0) {
done = true;
break;
}
if (parenDepth === 0) {
outerMostFunctionTakesColor = false;
}
@ -279,8 +435,6 @@ OutputParser.prototype = {
token.tokenType === "id" || token.tokenType === "hash" ||
token.tokenType === "number" || token.tokenType === "dimension" ||
token.tokenType === "percentage" || token.tokenType === "dimension");
token = tokenStream.nextToken();
}
let result = this._toDOM();
@ -291,6 +445,26 @@ OutputParser.prototype = {
return result;
},
/* eslint-enable complexity */
/**
* Parse a string.
*
* @param {String} text
* Text to parse.
* @param {Object} [options]
* Options object. For valid options and default values see
* _mergeOptions().
* @return {DocumentFragment}
* A document fragment.
*/
_parse: function (text, options = {}) {
text = text.trim();
this.parsed.length = 0;
let tokenStream = getCSSLexer(text);
return this._doParse(text, options, tokenStream, false);
},
/**
* Return true if it's a display:[inline-]grid token.
@ -1242,6 +1416,14 @@ OutputParser.prototype = {
* - urlClass: "" // The class to be used for url() links.
* - baseURI: undefined // A string used to resolve
* // relative links.
* - isVariableInUse // A function taking a single
* // argument, the name of a variable.
* // This should return the variable's
* // value, if it is in use; or null.
* - unmatchedVariableClass: ""
* // The class to use for a component
* // of a "var(...)" that is not in
* // use.
* @return {Object}
* Overridden options object
*/
@ -1260,6 +1442,8 @@ OutputParser.prototype = {
supportsColor: false,
urlClass: "",
baseURI: undefined,
isVariableInUse: null,
unmatchedVariableClass: null,
};
for (let item in overrides) {

Просмотреть файл

@ -29,6 +29,7 @@ function* performTest() {
testParseFilter(doc, parser);
testParseAngle(doc, parser);
testParseShape(doc, parser);
testParseVariable(doc, parser);
host.destroy();
}
@ -413,3 +414,50 @@ function testParseShape(doc, parser) {
is(frag.textContent, definition, desc + " text content");
}
}
function testParseVariable(doc, parser) {
let TESTS = [
{
text: "var(--seen)",
variables: {"--seen": "chartreuse" },
expected: "<span>var(<span title=\"--seen = chartreuse\">--seen</span>)</span>"
},
{
text: "var(--not-seen)",
variables: {},
expected: "<span>var(<span class=\"unmatched-class\" " +
"title=\"--not-seen is not set\">--not-seen</span>)</span>"
},
{
text: "var(--seen, seagreen)",
variables: {"--seen": "chartreuse" },
expected: "<span>var(<span title=\"--seen = chartreuse\">--seen</span>," +
"<span class=\"unmatched-class\"> <span data-color=\"seagreen\"><span>seagreen" +
"</span></span></span>)</span>"
},
{
text: "var(--not-seen, var(--seen))",
variables: {"--seen": "chartreuse" },
expected: "<span>var(<span class=\"unmatched-class\" " +
"title=\"--not-seen is not set\">--not-seen</span>,<span> <span>var(<span " +
"title=\"--seen = chartreuse\">--seen</span>)</span></span>)</span>"
},
];
for (let test of TESTS) {
let getValue = function (varName) {
return test.variables[varName];
};
let frag = parser.parseCssProperty("color", test.text, {
isVariableInUse: getValue,
unmatchedVariableClass: "unmatched-class"
});
let target = doc.querySelector("div");
target.appendChild(frag);
is(target.innerHTML, test.expected, test.text);
target.innerHTML = "";
}
}

Просмотреть файл

@ -566,7 +566,8 @@
}
.ruleview-selector-separator,
.ruleview-selector-unmatched {
.ruleview-selector-unmatched,
.ruleview-variable-unmatched {
color: #888;
}

Просмотреть файл

@ -149,7 +149,8 @@ CssProperties.prototype = {
* @return {Boolean}
*/
isInherited(property) {
return this.properties[property] && this.properties[property].isInherited;
return (this.properties[property] && this.properties[property].isInherited) ||
isCssVariable(property);
},
/**
@ -350,5 +351,6 @@ module.exports = {
CssProperties,
getCssProperties,
getClientCssProperties,
initCssProperties
initCssProperties,
isCssVariable,
};

Просмотреть файл

@ -67,6 +67,18 @@ rule.filterProperty.title=Filter rules containing this property
# first opened and there's no node selected in the rule view.
rule.empty=No element selected.
# LOCALIZATION NOTE (rule.variableValue): Text displayed in a tooltip
# when the mouse is over a variable use (like "var(--something)") in
# the rule view. The first argument is the variable name and the
# second argument is the value.
rule.variableValue=%S = %S
# LOCALIZATION NOTE (rule.variableUnset): Text displayed in a tooltip
# when the mouse is over a variable use (like "var(--something)"),
# where the variable is not set. the rule view. The argument is the
# variable name.
rule.variableUnset=%S is not set
# LOCALIZATION NOTE (ruleView.selectorHighlighter.tooltip): Text displayed in a
# tooltip when the mouse is over a selector highlighter icon in the rule view.
rule.selectorHighlighter.tooltip=Highlight all elements matching this selector