Web Inspector: Styles: variable swatch not shown for var() with a fallback
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / SpreadsheetStyleProperty.js
index af6c069..ed1d7b5 100644 (file)
@@ -39,6 +39,7 @@ WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
         this._contentElement = null;
         this._nameElement = null;
         this._valueElement = null;
+        this._jumpToEffectivePropertyButton = null;
 
         this._nameTextField = null;
         this._valueTextField = null;
@@ -52,6 +53,7 @@ WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
 
         if (!this._readOnly) {
             this._element.tabIndex = -1;
+            property.addEventListener(WI.CSSProperty.Event.ModifiedChanged, this.updateStatus, this);
 
             this._element.addEventListener("blur", (event) => {
                 // Keep selection after tabbing out of Web Inspector window and back.
@@ -67,7 +69,10 @@ WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
                     this._delegate.spreadsheetStylePropertyMouseEnter(event, this);
             });
 
-            this._element.copyHandler = this;
+            new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, WI.KeyboardShortcut.Key.Slash, () => {
+                this._toggle();
+                this._select();
+            }, this._element);
         }
     }
 
@@ -131,6 +136,9 @@ WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
 
     remove(replacement = null)
     {
+        if (this._delegate && typeof this._delegate.spreadsheetStylePropertyWillRemove === "function")
+            this._delegate.spreadsheetStylePropertyWillRemove(this);
+
         this.element.remove();
 
         if (replacement)
@@ -139,9 +147,6 @@ WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
             this._property.remove();
 
         this.detached();
-
-        if (this._delegate && typeof this._delegate.spreadsheetStylePropertyRemoved === "function")
-            this._delegate.spreadsheetStylePropertyRemoved(this);
     }
 
     update()
@@ -156,9 +161,8 @@ WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
             this._checkboxElement.tabIndex = -1;
             this._checkboxElement.addEventListener("click", (event) => {
                 event.stopPropagation();
-                let disabled = !this._checkboxElement.checked;
-                this._property.commentOut(disabled);
-                this.update();
+                this._toggle();
+                console.assert(this._checkboxElement.checked === this._property.enabled);
             });
         }
 
@@ -257,6 +261,22 @@ WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
         let elementTitle = "";
 
         if (this._property.overridden) {
+            if (!this._jumpToEffectivePropertyButton && this._delegate && this._delegate.spreadsheetStylePropertySelectByProperty && WI.settings.experimentalEnableStylesJumpToEffective.value) {
+                console.assert(this._property.overridingProperty, `Overridden property is missing overridingProperty: ${this._property.formattedText}`);
+                if (this._property.overridingProperty) {
+                    this._jumpToEffectivePropertyButton = WI.createGoToArrowButton();
+                    this._jumpToEffectivePropertyButton.classList.add("select-effective-property");
+                    this._jumpToEffectivePropertyButton.dataset.value = this._property.overridingProperty.rawValue;
+                    this._element.append(this._jumpToEffectivePropertyButton);
+
+                    this._jumpToEffectivePropertyButton.addEventListener("click", (event) => {
+                        console.assert(this._property.overridingProperty);
+                        event.stop();
+                        this._delegate.spreadsheetStylePropertySelectByProperty(this._property.overridingProperty);
+                    });
+                }
+            }
+
             classNames.push("overridden");
             if (duplicatePropertyExistsBelow(this._property)) {
                 classNames.push("has-warning");
@@ -291,7 +311,7 @@ WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
         if (!this._property.enabled)
             classNames.push("disabled");
 
-        if (this._property.modified)
+        if (this._property.modified && this._property.name && this._property.rawValue)
             classNames.push("modified");
 
         if (this._selected)
@@ -314,11 +334,6 @@ WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
         return matches;
     }
 
-    handleCopyEvent(event)
-    {
-        this._delegate.spreadsheetStylePropertyCopy(event, this);
-    }
-
     // SpreadsheetTextField delegate
 
     spreadsheetTextFieldWillStartEditing(textField)
@@ -337,16 +352,14 @@ WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
 
     spreadsheetTextFieldDidCommit(textField, {direction})
     {
-        let propertyName = this._nameTextField.value.trim();
-        let propertyValue = this._valueTextField.value.trim();
         let willRemoveProperty = false;
         let isEditingName = textField === this._nameTextField;
 
-        if (!propertyName || (!propertyValue && !isEditingName && direction === "forward"))
+        if (!this._property.name || (!this._property.rawValue && !isEditingName && direction === "forward"))
             willRemoveProperty = true;
 
         if (!isEditingName && !willRemoveProperty)
-            this._renderValue(propertyValue);
+            this._renderValue(this._property.rawValue);
 
         if (direction === "forward") {
             if (isEditingName && !willRemoveProperty) {
@@ -380,7 +393,7 @@ WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
         }
 
         if (textField === this._valueTextField)
-            this._renderValue(this._valueElement.textContent);
+            this._renderValue(this._property.rawValue);
 
         if (typeof this._delegate.spreadsheetStylePropertyFocusMoved === "function")
             this._delegate.spreadsheetStylePropertyFocusMoved(this, {direction: null});
@@ -408,6 +421,20 @@ WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
 
     // Private
 
+    _toggle()
+    {
+        this._property.commentOut(this.property.enabled);
+        this.update();
+    }
+
+    _select()
+    {
+        if (this._delegate && this._delegate.spreadsheetStylePropertySelect) {
+            let index = parseInt(this._element.dataset.propertyIndex);
+            this._delegate.spreadsheetStylePropertySelect(index);
+        }
+    }
+
     _isEditable()
     {
         return !this._readOnly && this._property.editable;
@@ -420,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)
@@ -466,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);
@@ -482,33 +506,53 @@ WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
 
             innerElement.textContent = value;
             this._handleValueChange();
+
+            if (type === WI.InlineSwatch.Type.Variable)
+                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._swatchActive = true;
                 this._delegate.stylePropertyInlineSwatchActivated();
             });
         }
 
         if (this._delegate && typeof this._delegate.stylePropertyInlineSwatchDeactivated === "function") {
             swatch.addEventListener(WI.InlineSwatch.Event.Deactivated, () => {
-                this._swatchActive = false;
                 this._delegate.stylePropertyInlineSwatchDeactivated();
             });
         }
 
         tokenElement.append(swatch.element, innerElement);
 
-        // Prevent the value from editing when clicking on the swatch.
-        swatch.element.addEventListener("click", (event) => {
-            if (this._swatchActive || event.shiftKey)
-                event.stop();
-        });
-
         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;
@@ -534,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);
 
@@ -553,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);
         };
@@ -620,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);
 
@@ -641,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)) {
@@ -651,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);
@@ -709,9 +763,14 @@ WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
         }
     }
 
-    _nameCompletionDataProvider(prefix)
+    _nameCompletionDataProvider(prefix, options = {})
     {
-        return WI.CSSCompletions.cssNameCompletions.startsWith(prefix);
+        let completions;
+        if (!prefix && options.allowEmptyPrefix)
+            completions = WI.CSSCompletions.cssNameCompletions.values;
+        else
+            completions = WI.CSSCompletions.cssNameCompletions.startsWith(prefix);
+        return {prefix, completions};
     }
 
     _handleValueBeforeInput(event)
@@ -724,20 +783,8 @@ WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
         if (!selection.rangeCount || selection.getRangeAt(0).endOffset !== text.length)
             return;
 
-        // Find the first and last index (if any) of a quote character to ensure that the string
-        // doesn't contain unbalanced quotes. If so, then there's no way that the semicolon could be
-        // part of a string within the value, so we can assume that it's the property "terminator".
-        const quoteRegex = /["']/g;
-        let start = -1;
-        let end = text.length;
-        let match = null;
-        while (match = quoteRegex.exec(text)) {
-            if (start < 0)
-                start = match.index;
-            end = match.index + 1;
-        }
-
-        if (start !== -1 && !text.substring(start, end).hasMatchingEscapedQuotes())
+        let unbalancedCharacters = WI.CSSCompletions.completeUnbalancedValue(text);
+        if (unbalancedCharacters)
             return;
 
         event.preventDefault();
@@ -747,8 +794,19 @@ WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
 
     _valueCompletionDataProvider(prefix)
     {
+        // For "border: 1px so|", we want to suggest "solid" based on "so" prefix.
+        let match = prefix.match(/[a-z0-9()-]+$/i);
+
+        // Clicking on the value of `height: 100%` shouldn't show any completions.
+        if (!match && prefix)
+            return {completions: [], prefix: ""};
+
+        prefix = match ? match[0] : "";
         let propertyName = this._nameElement.textContent.trim();
-        return WI.CSSKeywordCompletions.forProperty(propertyName).startsWith(prefix);
+        return {
+            prefix,
+            completions: WI.CSSKeywordCompletions.forProperty(propertyName).startsWith(prefix)
+        };
     }
 
     _setupJumpToSymbol(element)