Web Inspector: Styles: variable swatch not shown for var() with a fallback
authordrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 5 Aug 2019 23:40:40 +0000 (23:40 +0000)
committerdrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 5 Aug 2019 23:40:40 +0000 (23:40 +0000)
https://bugs.webkit.org/show_bug.cgi?id=200237

Reviewed by Joseph Pecoraro.

Source/WebInspectorUI:

* UserInterface/Views/SpreadsheetStyleProperty.js:
(WI.SpreadsheetStyleProperty.prototype._createInlineSwatch):
(WI.SpreadsheetStyleProperty.prototype._replaceSpecialTokens): Added.
(WI.SpreadsheetStyleProperty.prototype._addGradientTokens):
(WI.SpreadsheetStyleProperty.prototype._addColorTokens):
(WI.SpreadsheetStyleProperty.prototype._addTimingFunctionTokens):
(WI.SpreadsheetStyleProperty.prototype._addVariableTokens):
Check to see if there's a fallback value in the `var()` and tokenize it if there is. Mark
the property as invalid if the `var()` doesn't end up resolving to anything.

* UserInterface/Views/InlineSwatch.js:
(WI.InlineSwatch):
(WI.InlineSwatch.prototype.get value):
(WI.InlineSwatch.prototype._updateSwatch):
(WI.InlineSwatch.prototype._handleContextMenuEvent):
(WI.InlineSwatch.prototype._getNextValidHEXFormat.hexMatchesCurrentColor):
(WI.InlineSwatch.prototype._getNextValidHEXFormat):
Allow the `value` to be a function. In that case, use the getter `this.value` instead of the
value `this._value` directly so that the function is invoked.
This is needed for variable swatches because the fallback value could change after the
swatch has been created (e.g. another swatch in a CSS property value that just modifies the
text, rather than re-renders the entire CSS property value).

* UserInterface/Models/CSSStyleDeclaration.js:
(WI.CSSStyleDeclaration.prototype.resolveVariableValue): Added.
Follow the variable chain until an ultimate value is reached.

* UserInterface/Models/CSSKeywordCompletions.js:
(WI.CSSKeywordCompletions.isColorAwareProperty):
(WI.CSSKeywordCompletions.isTimingFunctionAwareProperty): Added.
Limit `cubic-bezier` and `spring` tokens to only be shown for timing function properties.

LayoutTests:

* inspector/css/resolve-variable-value.html: Added.
* inspector/css/resolve-variable-value-expected.txt: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@248279 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/inspector/css/resolve-variable-value-expected.txt [new file with mode: 0644]
LayoutTests/inspector/css/resolve-variable-value.html [new file with mode: 0644]
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/UserInterface/Models/CSSKeywordCompletions.js
Source/WebInspectorUI/UserInterface/Models/CSSStyleDeclaration.js
Source/WebInspectorUI/UserInterface/Views/InlineSwatch.js
Source/WebInspectorUI/UserInterface/Views/SpreadsheetStyleProperty.js

index 5841308..6f11117 100644 (file)
@@ -1,3 +1,13 @@
+2019-08-05  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: Styles: variable swatch not shown for var() with a fallback
+        https://bugs.webkit.org/show_bug.cgi?id=200237
+
+        Reviewed by Joseph Pecoraro.
+
+        * inspector/css/resolve-variable-value.html: Added.
+        * inspector/css/resolve-variable-value-expected.txt: Added.
+
 2019-08-05  Chris Dumez  <cdumez@apple.com>
 
         navigator.geolocation wrapper should not become GC-collectable once its frame is detached
diff --git a/LayoutTests/inspector/css/resolve-variable-value-expected.txt b/LayoutTests/inspector/css/resolve-variable-value-expected.txt
new file mode 100644 (file)
index 0000000..cc9be36
--- /dev/null
@@ -0,0 +1,43 @@
+Test that WI.CSSStyleDeclaration.prototype.resolveVariableValue works as expected.
+
+
+== Running test suite: WI.CSSStyleDeclaration.prototype.resolveVariableValue
+-- Running test case: NotVariable
+PASS: "red" should resolve to null.
+
+-- Running test case: EmptyVariable
+PASS: "var()" should resolve to null.
+
+-- Running test case: InvalidVariable
+PASS: "var(invalid)" should resolve to null.
+
+-- Running test case: NonExistentVariable
+PASS: "var(--DNE)" should resolve to null.
+
+-- Running test case: ValidVariable
+PASS: "var(--a)" should resolve to "blue".
+
+-- Running test case: InvalidFallbackValue
+PASS: "var(--DNE, )" should resolve to null.
+
+-- Running test case: ValidFallbackValue
+PASS: "var(--DNE, red)" should resolve to "red".
+
+-- Running test case: InvalidFallbackVariable
+PASS: "var(--DNE, var(--DNE))" should resolve to null.
+
+-- Running test case: ValidFallbackVariable
+PASS: "var(--DNE, var(--b, red))" should resolve to "green".
+
+-- Running test case: ValidVariableWithInvalidFallbackValue
+PASS: "var(--a, )" should resolve to "blue".
+
+-- Running test case: ValidVariableWithValidFallbackValue
+PASS: "var(--a, red)" should resolve to "blue".
+
+-- Running test case: ValidVariableWithInvalidFallbackVariable
+PASS: "var(--a, var(--DNE))" should resolve to "blue".
+
+-- Running test case: ValidVariableWithValidFallbackVariable
+PASS: "var(--a, var(--b))" should resolve to "blue".
+
diff --git a/LayoutTests/inspector/css/resolve-variable-value.html b/LayoutTests/inspector/css/resolve-variable-value.html
new file mode 100644 (file)
index 0000000..9c571f6
--- /dev/null
@@ -0,0 +1,122 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<style>
+#foo {
+    --a: blue;
+    --b: green;
+}
+</style>
+<script>
+function test() {
+    let suite = InspectorTest.createSyncSuite("WI.CSSStyleDeclaration.prototype.resolveVariableValue");
+
+    let computedStyle = null;
+
+    function addTest({name, expression, expected}) {
+        suite.addTestCase({
+            name,
+            test() {
+                InspectorTest.expectEqual(computedStyle.resolveVariableValue(expression), expected, `"${expression}" should resolve to ${JSON.stringify(expected)}.`);
+            },
+        })
+    }
+    addTest({
+        name: "NotVariable",
+        expression: "red",
+        expected: null,
+    });
+    addTest({
+        name: "EmptyVariable",
+        expression: "var()",
+        expected: null,
+    });
+    addTest({
+        name: "InvalidVariable",
+        expression: "var(invalid)",
+        expected: null,
+    });
+    addTest({
+        name: "NonExistentVariable",
+        expression: "var(--DNE)",
+        expected: null,
+    });
+    addTest({
+        name: "ValidVariable",
+        expression: "var(--a)",
+        expected: "blue",
+    });
+    addTest({
+        name: "InvalidFallbackValue",
+        expression: "var(--DNE, )",
+        expected: null,
+    });
+    addTest({
+        name: "ValidFallbackValue",
+        expression: "var(--DNE, red)",
+        expected: "red",
+    });
+    addTest({
+        name: "InvalidFallbackVariable",
+        expression: "var(--DNE, var(--DNE))",
+        expected: null,
+    });
+    addTest({
+        name: "ValidFallbackVariable",
+        expression: "var(--DNE, var(--b, red))",
+        expected: "green",
+    });
+    addTest({
+        name: "ValidVariableWithInvalidFallbackValue",
+        expression: "var(--a, )",
+        expected: "blue",
+    });
+    addTest({
+        name: "ValidVariableWithValidFallbackValue",
+        expression: "var(--a, red)",
+        expected: "blue",
+    });
+    addTest({
+        name: "ValidVariableWithInvalidFallbackVariable",
+        expression: "var(--a, var(--DNE))",
+        expected: "blue",
+    });
+    addTest({
+        name: "ValidVariableWithValidFallbackVariable",
+        expression: "var(--a, var(--b))",
+        expected: "blue",
+    });
+    
+
+    WI.domManager.requestDocument((documentNode) => {
+        WI.domManager.querySelector(documentNode.id, "#foo", (contentNodeId) => {
+            if (!contentNodeId) {
+                InspectorTest.fail("DOM node not found.");
+                InspectorTest.completeTest();
+                return;
+            }
+
+            let domNode = WI.domManager.nodeForId(contentNodeId);
+            nodeStyles = WI.cssManager.stylesForNode(domNode);
+
+            if (nodeStyles.needsRefresh) {
+                nodeStyles.singleFireEventListener(WI.DOMNodeStyles.Event.Refreshed, (event) => {
+                    computedStyle = nodeStyles.computedStyle;
+                    suite.runTestCasesAndFinish();
+                });
+                return;
+            }
+
+            computedStyle = nodeStyles.computedStyle;
+            suite.runTestCasesAndFinish();
+        });
+    });
+}
+</script>
+</head>
+<body onload="runTest()">
+<p>Test that WI.CSSStyleDeclaration.prototype.resolveVariableValue works as expected.</p>
+<div id="foo"></div>
+</body>
+</html>
index e823fe6..17c5b29 100644 (file)
@@ -1,5 +1,44 @@
 2019-08-05  Devin Rousso  <drousso@apple.com>
 
+        Web Inspector: Styles: variable swatch not shown for var() with a fallback
+        https://bugs.webkit.org/show_bug.cgi?id=200237
+
+        Reviewed by Joseph Pecoraro.
+
+        * UserInterface/Views/SpreadsheetStyleProperty.js:
+        (WI.SpreadsheetStyleProperty.prototype._createInlineSwatch):
+        (WI.SpreadsheetStyleProperty.prototype._replaceSpecialTokens): Added.
+        (WI.SpreadsheetStyleProperty.prototype._addGradientTokens):
+        (WI.SpreadsheetStyleProperty.prototype._addColorTokens):
+        (WI.SpreadsheetStyleProperty.prototype._addTimingFunctionTokens):
+        (WI.SpreadsheetStyleProperty.prototype._addVariableTokens):
+        Check to see if there's a fallback value in the `var()` and tokenize it if there is. Mark
+        the property as invalid if the `var()` doesn't end up resolving to anything.
+
+        * UserInterface/Views/InlineSwatch.js:
+        (WI.InlineSwatch):
+        (WI.InlineSwatch.prototype.get value):
+        (WI.InlineSwatch.prototype._updateSwatch):
+        (WI.InlineSwatch.prototype._handleContextMenuEvent):
+        (WI.InlineSwatch.prototype._getNextValidHEXFormat.hexMatchesCurrentColor):
+        (WI.InlineSwatch.prototype._getNextValidHEXFormat):
+        Allow the `value` to be a function. In that case, use the getter `this.value` instead of the
+        value `this._value` directly so that the function is invoked.
+        This is needed for variable swatches because the fallback value could change after the
+        swatch has been created (e.g. another swatch in a CSS property value that just modifies the
+        text, rather than re-renders the entire CSS property value).
+
+        * UserInterface/Models/CSSStyleDeclaration.js:
+        (WI.CSSStyleDeclaration.prototype.resolveVariableValue): Added.
+        Follow the variable chain until an ultimate value is reached.
+
+        * UserInterface/Models/CSSKeywordCompletions.js:
+        (WI.CSSKeywordCompletions.isColorAwareProperty):
+        (WI.CSSKeywordCompletions.isTimingFunctionAwareProperty): Added.
+        Limit `cubic-bezier` and `spring` tokens to only be shown for timing function properties.
+
+2019-08-05  Devin Rousso  <drousso@apple.com>
+
         Web Inspector: rename "Stylesheet" to "Style Sheet" to match spec text
         https://bugs.webkit.org/show_bug.cgi?id=200422
 
index 2a4d62a..a536a5a 100644 (file)
@@ -75,11 +75,11 @@ WI.CSSKeywordCompletions.forProperty = function(propertyName)
 
 WI.CSSKeywordCompletions.isColorAwareProperty = function(name)
 {
-    if (name in WI.CSSKeywordCompletions._colorAwareProperties)
+    if (WI.CSSKeywordCompletions._colorAwareProperties.has(name))
         return true;
 
     let isNotPrefixed = name.charAt(0) !== "-";
-    if (isNotPrefixed && ("-webkit-" + name) in WI.CSSKeywordCompletions._colorAwareProperties)
+    if (isNotPrefixed && WI.CSSKeywordCompletions._colorAwareProperties.has("-webkit-" + name))
         return true;
 
     if (name.endsWith("color"))
@@ -88,6 +88,18 @@ WI.CSSKeywordCompletions.isColorAwareProperty = function(name)
     return false;
 };
 
+WI.CSSKeywordCompletions.isTimingFunctionAwareProperty = function(name)
+{
+    if (WI.CSSKeywordCompletions._timingFunctionAwareProperties.has(name))
+        return true;
+
+    let isNotPrefixed = name.charAt(0) !== "-";
+    if (isNotPrefixed && WI.CSSKeywordCompletions._timingFunctionAwareProperties.has("-webkit-" + name))
+        return true;
+
+    return false;
+};
+
 WI.CSSKeywordCompletions.forFunction = function(functionName)
 {
     let suggestions = ["var()"];
@@ -302,17 +314,52 @@ WI.CSSKeywordCompletions._colors = [
     "wheat", "whitesmoke", "yellowgreen", "rgb()", "rgba()", "hsl()", "hsla()"
 ];
 
-WI.CSSKeywordCompletions._colorAwareProperties = [
-    "background", "background-color", "background-image", "border", "border-color", "border-top", "border-right", "border-bottom",
-    "border-left", "border-top-color", "border-right-color", "border-bottom-color", "border-left-color", "box-shadow", "color",
-    "fill", "outline", "outline-color", "stroke", "text-line-through", "text-line-through-color", "text-overline", "text-overline-color",
-    "text-shadow", "text-underline", "text-underline-color", "-webkit-box-shadow", "-webkit-column-rule", "-webkit-column-rule-color",
-    "-webkit-text-emphasis", "-webkit-text-emphasis-color", "-webkit-text-fill-color", "-webkit-text-stroke", "-webkit-text-stroke-color",
-    "-webkit-text-decoration-color",
+WI.CSSKeywordCompletions._colorAwareProperties = new Set([
+    "background",
+    "background-color",
+    "background-image",
+    "border",
+    "border-color",
+    "border-bottom",
+    "border-bottom-color",
+    "border-left",
+    "border-left-color",
+    "border-right",
+    "border-right-color",
+    "border-top",
+    "border-top-color",
+    "box-shadow", "-webkit-box-shadow",
+    "color",
+    "column-rule", "-webkit-column-rule",
+    "column-rule-color", "-webkit-column-rule-color",
+    "fill",
+    "outline",
+    "outline-color",
+    "stroke",
+    "text-decoration-color", "-webkit-text-decoration-color",
+    "text-emphasis", "-webkit-text-emphasis",
+    "text-emphasis-color", "-webkit-text-emphasis-color",
+    "text-line-through",
+    "text-line-through-color",
+    "text-overline",
+    "text-overline-color",
+    "text-shadow",
+    "text-underline",
+    "text-underline-color",
+    "-webkit-text-fill-color",
+    "-webkit-text-stroke",
+    "-webkit-text-stroke-color",
 
     // iOS Properties
-    "-webkit-tap-highlight-color"
-].keySet();
+    "-webkit-tap-highlight-color",
+]);
+
+WI.CSSKeywordCompletions._timingFunctionAwareProperties = new Set([
+    "animation", "-webkit-animation",
+    "animation-timing-function", "-webkit-animation-timing-function",
+    "transition", "-webkit-transition",
+    "transition-timing-function", "-webkit-transition-timing-function",
+]);
 
 WI.CSSKeywordCompletions._propertyKeywordMap = {
     "content": [
index 901b6cb..b79ac8e 100644 (file)
@@ -348,6 +348,62 @@ WI.CSSStyleDeclaration = class CSSStyleDeclaration extends WI.Object
         return newProperty;
     }
 
+    resolveVariableValue(text)
+    {
+        const invalid = Symbol("invalid");
+
+        let checkTokens = (tokens) => {
+            let startIndex = NaN;
+            let openParenthesis = 0;
+            for (let i = 0; i < tokens.length; i++) {
+                let token = tokens[i];
+                if (token.value === "var" && token.type && token.type.includes("atom")) {
+                    if (isNaN(startIndex)) {
+                        startIndex = i;
+                        openParenthesis = 0;
+                    }
+                    continue;
+                }
+
+                if (isNaN(startIndex))
+                    continue;
+
+                if (token.value === "(") {
+                    ++openParenthesis;
+                    continue;
+                }
+
+                if (token.value === ")") {
+                    --openParenthesis;
+                    if (openParenthesis > 0)
+                        continue;
+
+                    let variableTokens = tokens.slice(startIndex, i + 1);
+                    startIndex = NaN;
+
+                    let variableNameIndex = variableTokens.findIndex((token) => token.value.startsWith("--") && /\bvariable-2\b/.test(token.type));
+                    if (variableNameIndex === -1)
+                        continue;
+
+                    let variableProperty = this.propertyForName(variableTokens[variableNameIndex].value, true);
+                    if (variableProperty)
+                        return variableProperty.value.trim();
+
+                    let fallbackStartIndex = variableTokens.findIndex((value, j) => j > variableNameIndex + 1 && /\bm-css\b/.test(value.type));
+                    if (fallbackStartIndex === -1)
+                        return invalid;
+
+                    let fallbackTokens = variableTokens.slice(fallbackStartIndex, i);
+                    return checkTokens(fallbackTokens) || fallbackTokens.reduce((accumulator, token) => accumulator + token.value, "").trim();
+                }
+            }
+            return null;
+        };
+
+        let resolved = checkTokens(WI.tokenizeCSSValue(text));
+        return resolved === invalid ? null : resolved;
+    }
+
     newBlankProperty(propertyIndex)
     {
         let text, name, value, priority, overridden, implicit, anonymous;
index 4de1fd4..c20a692 100644 (file)
@@ -90,6 +90,8 @@ WI.InlineSwatch = class InlineSwatch extends WI.Object
 
     get value()
     {
+        if (typeof this._value === "function")
+            return this._value();
         return this._value;
     }
 
@@ -137,25 +139,29 @@ WI.InlineSwatch = class InlineSwatch extends WI.Object
 
     _updateSwatch(dontFireEvents)
     {
+        let value = this.value;
+
         if (this._type === WI.InlineSwatch.Type.Color || this._type === WI.InlineSwatch.Type.Gradient)
-            this._swatchInnerElement.style.background = this._value ? this._value.toString() : null;
+            this._swatchInnerElement.style.background = value ? value.toString() : null;
         else if (this._type === WI.InlineSwatch.Type.Image)
-            this._swatchInnerElement.style.setProperty("background-image", `url(${this._value.src})`);
+            this._swatchInnerElement.style.setProperty("background-image", `url(${value.src})`);
 
         if (!dontFireEvents)
-            this.dispatchEventToListeners(WI.InlineSwatch.Event.ValueChanged, {value: this._value});
+            this.dispatchEventToListeners(WI.InlineSwatch.Event.ValueChanged, {value});
     }
 
     _swatchElementClicked(event)
     {
         event.stop();
 
-        if (event.shiftKey && this._value) {
+        let value = this.value;
+
+        if (event.shiftKey && value) {
             if (this._type === WI.InlineSwatch.Type.Color) {
-                let nextFormat = this._value.nextFormat();
+                let nextFormat = value.nextFormat();
                 console.assert(nextFormat);
                 if (nextFormat) {
-                    this._value.format = nextFormat;
+                    value.format = nextFormat;
                     this._updateSwatch();
                 }
                 return;
@@ -172,6 +178,9 @@ WI.InlineSwatch = class InlineSwatch extends WI.Object
         if (this._valueEditor)
             return;
 
+        if (!value)
+            value = this._fallbackValue();
+
         let bounds = WI.Rect.rectFromClientRect(this._swatchElement.getBoundingClientRect());
         let popover = new WI.Popover(this);
 
@@ -220,12 +229,14 @@ WI.InlineSwatch = class InlineSwatch extends WI.Object
             break;
 
         case WI.InlineSwatch.Type.Image:
-            this._valueEditor = {};
-            this._valueEditor.element = document.createElement("img");
-            this._valueEditor.element.src = this._value.src;
-            this._valueEditor.element.classList.add("show-grid");
-            this._valueEditor.element.style.setProperty("max-width", "50vw");
-            this._valueEditor.element.style.setProperty("max-height", "50vh");
+            if (value.src) {
+                this._valueEditor = {};
+                this._valueEditor.element = document.createElement("img");
+                this._valueEditor.element.src = value.src;
+                this._valueEditor.element.classList.add("show-grid");
+                this._valueEditor.element.style.setProperty("max-width", "50vw");
+                this._valueEditor.element.style.setProperty("max-height", "50vh");
+            }
             break;
         }
 
@@ -237,7 +248,6 @@ WI.InlineSwatch = class InlineSwatch extends WI.Object
 
         this.dispatchEventToListeners(WI.InlineSwatch.Event.Activated);
 
-        let value = this._value || this._fallbackValue();
         switch (this._type) {
         case WI.InlineSwatch.Type.Color:
             this._valueEditor.color = value;
@@ -294,14 +304,15 @@ WI.InlineSwatch = class InlineSwatch extends WI.Object
 
     _handleContextMenuEvent(event)
     {
-        if (!this._value)
+        let value = this.value;
+        if (!value)
             return;
 
         let contextMenu = WI.ContextMenu.createFromEvent(event);
 
-        if (this._value.isKeyword() && this._value.format !== WI.Color.Format.Keyword) {
+        if (value.isKeyword() && value.format !== WI.Color.Format.Keyword) {
             contextMenu.appendItem(WI.UIString("Format: Keyword"), () => {
-                this._value.format = WI.Color.Format.Keyword;
+                value.format = WI.Color.Format.Keyword;
                 this._updateSwatch();
             });
         }
@@ -309,31 +320,31 @@ WI.InlineSwatch = class InlineSwatch extends WI.Object
         let hexInfo = this._getNextValidHEXFormat();
         if (hexInfo) {
             contextMenu.appendItem(hexInfo.title, () => {
-                this._value.format = hexInfo.format;
+                value.format = hexInfo.format;
                 this._updateSwatch();
             });
         }
 
-        if (this._value.simple && this._value.format !== WI.Color.Format.HSL) {
+        if (value.simple && value.format !== WI.Color.Format.HSL) {
             contextMenu.appendItem(WI.UIString("Format: HSL"), () => {
-                this._value.format = WI.Color.Format.HSL;
+                value.format = WI.Color.Format.HSL;
                 this._updateSwatch();
             });
-        } else if (this._value.format !== WI.Color.Format.HSLA) {
+        } else if (value.format !== WI.Color.Format.HSLA) {
             contextMenu.appendItem(WI.UIString("Format: HSLA"), () => {
-                this._value.format = WI.Color.Format.HSLA;
+                value.format = WI.Color.Format.HSLA;
                 this._updateSwatch();
             });
         }
 
-        if (this._value.simple && this._value.format !== WI.Color.Format.RGB) {
+        if (value.simple && value.format !== WI.Color.Format.RGB) {
             contextMenu.appendItem(WI.UIString("Format: RGB"), () => {
-                this._value.format = WI.Color.Format.RGB;
+                value.format = WI.Color.Format.RGB;
                 this._updateSwatch();
             });
-        } else if (this._value.format !== WI.Color.Format.RGBA) {
+        } else if (value.format !== WI.Color.Format.RGBA) {
             contextMenu.appendItem(WI.UIString("Format: RGBA"), () => {
-                this._value.format = WI.Color.Format.RGBA;
+                value.format = WI.Color.Format.RGBA;
                 this._updateSwatch();
             });
         }
@@ -344,13 +355,15 @@ WI.InlineSwatch = class InlineSwatch extends WI.Object
         if (this._type !== WI.InlineSwatch.Type.Color)
             return false;
 
+        let value = this.value;
+
         function hexMatchesCurrentColor(hexInfo) {
             let nextIsSimple = hexInfo.format === WI.Color.Format.ShortHEX || hexInfo.format === WI.Color.Format.HEX;
-            if (nextIsSimple && !this._value.simple)
+            if (nextIsSimple && !value.simple)
                 return false;
 
             let nextIsShort = hexInfo.format === WI.Color.Format.ShortHEX || hexInfo.format === WI.Color.Format.ShortHEXAlpha;
-            if (nextIsShort && !this._value.canBeSerializedAsShortHEX())
+            if (nextIsShort && !value.canBeSerializedAsShortHEX())
                 return false;
 
             return true;
@@ -375,15 +388,15 @@ WI.InlineSwatch = class InlineSwatch extends WI.Object
             }
         ];
 
-        let currentColorIsHEX = hexFormats.some((info) => info.format === this._value.format);
+        let currentColorIsHEX = hexFormats.some((info) => info.format === value.format);
 
         for (let i = 0; i < hexFormats.length; ++i) {
-            if (currentColorIsHEX && this._value.format !== hexFormats[i].format)
+            if (currentColorIsHEX && value.format !== hexFormats[i].format)
                 continue;
 
             for (let j = ~~currentColorIsHEX; j < hexFormats.length; ++j) {
                 let nextIndex = (i + j) % hexFormats.length;
-                if (hexMatchesCurrentColor.call(this, hexFormats[nextIndex]))
+                if (hexMatchesCurrentColor(hexFormats[nextIndex]))
                     return hexFormats[nextIndex];
             }
             return null;
index a3f2fc5..ed1d7b5 100644 (file)
@@ -447,18 +447,8 @@ WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
         const maxValueLength = 150;
         let tokens = WI.tokenizeCSSValue(value);
 
-        if (this._property.enabled) {
-            // FIXME: <https://webkit.org/b/178636> Web Inspector: Styles: Make inline widgets work with CSS functions (var(), calc(), etc.)
-
-            // CSS variables may contain color - display color picker for them.
-            if (this._property.variable || WI.CSSKeywordCompletions.isColorAwareProperty(this._property.name)) {
-                tokens = this._addGradientTokens(tokens);
-                tokens = this._addColorTokens(tokens);
-            }
-            tokens = this._addTimingFunctionTokens(tokens, "cubic-bezier");
-            tokens = this._addTimingFunctionTokens(tokens, "spring");
-            tokens = this._addVariableTokens(tokens);
-        }
+        if (this._property.enabled)
+            tokens = this._replaceSpecialTokens(tokens);
 
         tokens = tokens.map((token) => {
             if (token instanceof Element)
@@ -493,11 +483,18 @@ WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
         this._valueElement.append(...tokens);
     }
 
-    _createInlineSwatch(type, text, valueObject)
+    _createInlineSwatch(type, contents, valueObject)
     {
         let tokenElement = document.createElement("span");
         let innerElement = document.createElement("span");
-        innerElement.textContent = text;
+        for (let item of contents) {
+            if (item instanceof Node)
+                innerElement.appendChild(item);
+            else if (typeof item === "object")
+                innerElement.append(item.value);
+            else
+                innerElement.append(item);
+        }
 
         let readOnly = !this._isEditable();
         let swatch = new WI.InlineSwatch(type, valueObject, readOnly);
@@ -514,6 +511,12 @@ WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
                 this._renderValue(this._property.rawValue);
         }, this);
 
+        if (type === WI.InlineSwatch.Type.Variable) {
+            swatch.value = () => {
+                return this._property.ownerStyle.nodeStyles.computedStyle.resolveVariableValue(innerElement.textContent);
+            };
+        }
+
         if (this._delegate && typeof this._delegate.stylePropertyInlineSwatchActivated === "function") {
             swatch.addEventListener(WI.InlineSwatch.Event.Activated, () => {
                 this._delegate.stylePropertyInlineSwatchActivated();
@@ -531,6 +534,25 @@ WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
         return tokenElement;
     }
 
+    _replaceSpecialTokens(tokens)
+    {
+        // FIXME: <https://webkit.org/b/178636> Web Inspector: Styles: Make inline widgets work with CSS functions (var(), calc(), etc.)
+
+        tokens = this._addVariableTokens(tokens);
+
+        if (this._property.variable || WI.CSSKeywordCompletions.isColorAwareProperty(this._property.name)) {
+            tokens = this._addGradientTokens(tokens);
+            tokens = this._addColorTokens(tokens);
+        }
+
+        if (this._property.variable || WI.CSSKeywordCompletions.isTimingFunctionAwareProperty(this._property.name)) {
+            tokens = this._addTimingFunctionTokens(tokens, "cubic-bezier");
+            tokens = this._addTimingFunctionTokens(tokens, "spring");
+        }
+
+        return tokens;
+    }
+
     _addGradientTokens(tokens)
     {
         let gradientRegex = /^(repeating-)?(linear|radial)-gradient$/i;
@@ -556,7 +578,7 @@ WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
                 let text = rawTokens.map((token) => token.value).join("");
                 let gradient = WI.Gradient.fromString(text);
                 if (gradient)
-                    newTokens.push(this._createInlineSwatch(WI.InlineSwatch.Type.Gradient, text, gradient));
+                    newTokens.push(this._createInlineSwatch(WI.InlineSwatch.Type.Gradient, rawTokens, gradient));
                 else
                     newTokens.push(...rawTokens);
 
@@ -575,7 +597,7 @@ WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
         let pushPossibleColorToken = (text, ...rawTokens) => {
             let color = WI.Color.fromString(text);
             if (color)
-                newTokens.push(this._createInlineSwatch(WI.InlineSwatch.Type.Color, text, color));
+                newTokens.push(this._createInlineSwatch(WI.InlineSwatch.Type.Color, rawTokens, color));
             else
                 newTokens.push(...rawTokens);
         };
@@ -642,7 +664,7 @@ WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
                 }
 
                 if (valueObject)
-                    newTokens.push(this._createInlineSwatch(inlineSwatchType, text, valueObject));
+                    newTokens.push(this._createInlineSwatch(inlineSwatchType, rawTokens, valueObject));
                 else
                     newTokens.push(...rawTokens);
 
@@ -663,8 +685,10 @@ WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
         for (let i = 0; i < tokens.length; i++) {
             let token = tokens[i];
             if (token.value === "var" && token.type && token.type.includes("atom")) {
-                startIndex = i;
-                openParenthesis = 0;
+                if (isNaN(startIndex)) {
+                    startIndex = i;
+                    openParenthesis = 0;
+                }
             } else if (token.value === "(" && !isNaN(startIndex))
                 ++openParenthesis;
             else if (token.value === ")" && !isNaN(startIndex)) {
@@ -673,14 +697,22 @@ WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
                     continue;
 
                 let rawTokens = tokens.slice(startIndex, i + 1);
-                let tokenValues = rawTokens.map((token) => token.value);
-                let variableName = tokenValues.find((value, i) => value.startsWith("--") && /\bvariable-2\b/.test(rawTokens[i].type));
-
-                const dontCreateIfMissing = true;
-                let variableProperty = this._property.ownerStyle.nodeStyles.computedStyle.propertyForName(variableName, dontCreateIfMissing);
-                if (variableProperty) {
-                    let valueObject = variableProperty.value.trim();
-                    newTokens.push(this._createInlineSwatch(WI.InlineSwatch.Type.Variable, tokenValues.join(""), valueObject));
+                let variableNameIndex = rawTokens.findIndex((token) => token.value.startsWith("--") && /\bvariable-2\b/.test(token.type));
+                if (variableNameIndex !== -1) {
+                    let contents = [];
+                    let fallbackStartIndex = rawTokens.findIndex((value, i) => i > variableNameIndex + 1 && /\bm-css\b/.test(value.type));
+                    if (fallbackStartIndex !== -1) {
+                        contents = contents.concat(rawTokens.slice(0, fallbackStartIndex));
+                        contents = contents.concat(this._replaceSpecialTokens(rawTokens.slice(fallbackStartIndex, i)));
+                    } else
+                        contents = contents.concat(rawTokens.slice(0, i));
+                    contents.push(token);
+
+                    let text = rawTokens.reduce((accumulator, token) => accumulator + token.value, "");
+                    if (this._property.ownerStyle.nodeStyles.computedStyle.resolveVariableValue(text))
+                        newTokens.push(this._createInlineSwatch(WI.InlineSwatch.Type.Variable, contents));
+                    else
+                        newTokens = newTokens.concat(contents);
                 } else {
                     this._hasInvalidVariableValue = true;
                     newTokens.push(...rawTokens);