--- a/devtools/client/shared/output-parser.js
+++ b/devtools/client/shared/output-parser.js
@@ -2,16 +2,17 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const Services = require("Services");
const {angleUtils} = require("devtools/client/shared/css-angle");
const {colorUtils} = require("devtools/shared/css/color");
+const cssPropertiesSpec = require("devtools/shared/fronts/css-properties");
const {getCSSLexer} = require("devtools/shared/css/lexer");
const EventEmitter = require("devtools/shared/old-event-emitter");
const {appendText} = require("devtools/client/inspector/shared/utils");
loader.lazyRequireGetter(this, "ANGLE_TAKING_FUNCTIONS",
"devtools/shared/css/properties-db", true);
loader.lazyRequireGetter(this, "BASIC_SHAPE_FUNCTIONS",
"devtools/shared/css/properties-db", true);
@@ -59,16 +60,18 @@ const CSS_SHAPE_OUTSIDE_ENABLED_PREF = "
function OutputParser(document,
{supportsType, isValidOnClient, supportsCssColor4ColorFunction}) {
this.parsed = [];
this.doc = document;
this.supportsType = supportsType;
this.isValidOnClient = isValidOnClient;
this.colorSwatches = new WeakMap();
this.angleSwatches = new WeakMap();
+ // Keep track of last variable value computed while parsing
+ this.computedVariable = null;
this._onColorSwatchMouseDown = this._onColorSwatchMouseDown.bind(this);
this._onAngleSwatchMouseDown = this._onAngleSwatchMouseDown.bind(this);
this.cssColor4 = supportsCssColor4ColorFunction();
}
OutputParser.prototype = {
/**
@@ -199,17 +202,17 @@ OutputParser.prototype = {
* 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);
+ let variableNode = this._createNode("span", {});
// 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().
@@ -221,50 +224,59 @@ OutputParser.prototype = {
// 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);
+ // Reset swatch variable, may be set in future call to _doParse().
+ this.computedVariable = null;
+
+ // If we saw a ",", get the remainder with the correct highlighting.
+ let rest;
+ if (sawComma) {
+ // 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 = [];
+ subOptions.expectVar = true;
+ rest = this._doParse(text, subOptions, tokenStream, true);
+ this.parsed = saveParsed;
+ }
+
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["data-variable"] =
STYLE_INSPECTOR_L10N.getFormatStr("rule.variableValue", varName, varValue);
+ this.computedVariable = varValue;
firstOpts.class = options.matchedVariableClass;
secondOpts.class = options.unmatchedVariableClass;
} else {
// The variable name is not valid, mark it unmatched.
firstOpts.class = options.unmatchedVariableClass;
firstOpts["data-variable"] = STYLE_INSPECTOR_L10N.getFormatStr("rule.variableUnset",
varName);
}
+ this._appendVariable(this.computedVariable, variableNode);
+ appendText(variableNode, varText);
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,
@@ -359,68 +371,99 @@ OutputParser.prototype = {
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);
}
+
+ if (options.expectVar) {
+ this.computedVariable = text.substring(token.startOffset, token.endOffset);
+ }
}
}
break;
}
case "ident":
+ let tokenSubstring = text.substring(token.startOffset, token.endOffset)
+ let isCssVariable = cssPropertiesSpec.isCssVariable(tokenSubstring);
+
+ if (options.expectVar) {
+ this.computedVariable = text.substring(token.startOffset, token.endOffset);
+ }
+
if (options.expectCubicBezier &&
BEZIER_KEYWORDS.indexOf(token.text) >= 0) {
this._appendCubicBezier(token.text, options);
} else if (this._isDisplayFlex(text, token, options) &&
Services.prefs.getBoolPref(FLEXBOX_HIGHLIGHTER_ENABLED_PREF)) {
this._appendHighlighterToggle(token.text, options.flexClass);
} else if (this._isDisplayGrid(text, token, options)) {
this._appendHighlighterToggle(token.text, options.gridClass);
} else if (colorOK() &&
colorUtils.isValidCSSColor(token.text, this.cssColor4)) {
this._appendColor(token.text, options);
} else if (angleOK(token.text)) {
this._appendAngle(token.text, options);
+ } else if (options.expectVar && isCssVariable) {
+ let variableNode = this._createNode("span", {});
+ this.computedVariable = options.isVariableInUse(tokenSubstring) || null;
+ this._appendVariable(this.computedVariable, variableNode);
+ this.parsed.push(variableNode);
+ this._appendTextNode(tokenSubstring);
} else {
- this._appendTextNode(text.substring(token.startOffset,
- token.endOffset));
+ this._appendTextNode(tokenSubstring);
}
break;
case "id":
case "hash": {
let original = text.substring(token.startOffset, token.endOffset);
if (colorOK() && colorUtils.isValidCSSColor(original, this.cssColor4)) {
if (spaceNeeded) {
// Insert a space to prevent token pasting when a #xxx
// color is changed to something like rgb(...).
this._appendTextNode(" ");
}
this._appendColor(original, options);
} else {
this._appendTextNode(original);
}
+
+ if (options.expectVar) {
+ this.computedVariable = text.substring(token.startOffset, token.endOffset);
+ }
+
break;
}
case "dimension":
let value = text.substring(token.startOffset, token.endOffset);
if (angleOK(value)) {
this._appendAngle(value, options);
} else {
this._appendTextNode(value);
}
+
+ if (options.expectVar) {
+ this.computedVariable = text.substring(token.startOffset, token.endOffset);
+ }
+
break;
case "url":
case "bad_url":
this._appendURL(text.substring(token.startOffset, token.endOffset),
token.text, options);
+
+ if (options.expectVar) {
+ this.computedVariable = text.substring(token.startOffset, token.endOffset);
+ }
+
break;
case "symbol":
if (token.text === "(") {
++parenDepth;
} else if (token.text === ")") {
--parenDepth;
@@ -432,16 +475,21 @@ OutputParser.prototype = {
if (parenDepth === 0) {
outerMostFunctionTakesColor = false;
}
}
// falls through
default:
this._appendTextNode(
text.substring(token.startOffset, token.endOffset));
+
+ if (options.expectVar) {
+ this.computedVariable = text.substring(token.startOffset, token.endOffset);
+ }
+
break;
}
// If this token might possibly introduce token pasting when
// color-cycling, require a space.
spaceNeeded = (token.tokenType === "ident" || token.tokenType === "at" ||
token.tokenType === "id" || token.tokenType === "hash" ||
token.tokenType === "number" || token.tokenType === "dimension" ||
@@ -1197,16 +1245,43 @@ OutputParser.prototype = {
container.appendChild(value);
this.parsed.push(container);
} else {
this._appendTextNode(color);
}
},
/**
+ * Append a swatch for a css variable to a given node.
+ *
+ * @param {String} variable
+ * Value of the variable to append.
+ * null if the variable has no value.
+ * @param {Node} node
+ * Node to append swatch to.
+ */
+ _appendVariable: function (variable, node) {
+ let swatch;
+
+ if (variable === null) {
+ swatch = this._createNode("span", {
+ class: "ruleview-swatch ruleview-variableswatch-unmatched",
+ title: "Cannot be resolved"
+ });
+ } else {
+ swatch = this._createNode("span", {
+ class: "ruleview-swatch ruleview-variableswatch-matched",
+ title: variable
+ });
+ }
+
+ node.appendChild(swatch);
+ },
+
+ /**
* Wrap some existing nodes in a filter editor.
*
* @param {String} filters
* The full text of the "filter" property.
* @param {object} options
* The options object passed to parseCssProperty().
* @param {object} nodes
* Nodes created by _toDOM().