Title: [245991] trunk
Revision
245991
Author
[email protected]
Date
2019-05-31 15:53:40 -0700 (Fri, 31 May 2019)

Log Message

Web Inspector: CSS Changes: modifications aren't shared for rules that match multiple elements
https://bugs.webkit.org/show_bug.cgi?id=195264
<rdar://problem/48550023>

Reviewed by Devin Rousso.

Source/WebInspectorUI:

This patch fixes several cases when the diff was incorrect.

1. Perform diff based on CSSProperty content (name, value, and enabled property) instead
   of strict equality of CSSProperty instances.

2. Copy all initial CSSProperty instances of CSSStyleDeclaration on 1st edit.
   This removes the need to update `properties` on every single edit.

3. Do full diff to display modified property markers (green background) in Rules panel.
   This fixes a few cases when the markers were inaccurate. E.g. a newly added property
   matches removed property - no need to show the green background.

* UserInterface/Base/Utilities.js:
(Array.diffArrays):
Allow repeating items in the arrays.

* UserInterface/Controllers/CSSManager.js:
(WI.CSSManager.prototype.getModifiedStyle):
(WI.CSSManager.prototype.removeModifiedStyle):
* UserInterface/Models/CSSProperty.js:
(WI.CSSProperty):
(WI.CSSProperty.prototype.get modified):
(WI.CSSProperty.prototype.set modified):
(WI.CSSProperty.prototype.equals):
(WI.CSSProperty.prototype.clone):
(WI.CSSProperty.prototype._updateOwnerStyleText):
(WI.CSSProperty.prototype._markModified):
* UserInterface/Models/CSSStyleDeclaration.js:
(WI.CSSStyleDeclaration.prototype.markModified):
(WI.CSSStyleDeclaration.prototype.updatePropertiesModifiedState):
* UserInterface/Views/ChangesDetailsSidebarPanel.js:
(WI.ChangesDetailsSidebarPanel.prototype._createRuleElement):
* UserInterface/Views/SpreadsheetCSSStyleDeclarationEditor.js:
(WI.SpreadsheetCSSStyleDeclarationEditor.prototype.layout):
* UserInterface/Views/SpreadsheetStyleProperty.js:

LayoutTests:

Test arrays with repeating items for Array.diffArrays.

* inspector/unit-tests/array-utilities-expected.txt:
* inspector/unit-tests/array-utilities.html:

Modified Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (245990 => 245991)


--- trunk/LayoutTests/ChangeLog	2019-05-31 22:30:55 UTC (rev 245990)
+++ trunk/LayoutTests/ChangeLog	2019-05-31 22:53:40 UTC (rev 245991)
@@ -1,3 +1,16 @@
+2019-05-31  Nikita Vasilyev  <[email protected]>
+
+        Web Inspector: CSS Changes: modifications aren't shared for rules that match multiple elements
+        https://bugs.webkit.org/show_bug.cgi?id=195264
+        <rdar://problem/48550023>
+
+        Reviewed by Devin Rousso.
+
+        Test arrays with repeating items for Array.diffArrays.
+
+        * inspector/unit-tests/array-utilities-expected.txt:
+        * inspector/unit-tests/array-utilities.html:
+
 2019-05-31  Ryosuke Niwa  <[email protected]>
 
         iOS: Main frame should be scrollable when pinch zoomed or software keyboard is up

Modified: trunk/LayoutTests/inspector/unit-tests/array-utilities-expected.txt (245990 => 245991)


--- trunk/LayoutTests/inspector/unit-tests/array-utilities-expected.txt	2019-05-31 22:30:55 UTC (rev 245990)
+++ trunk/LayoutTests/inspector/unit-tests/array-utilities-expected.txt	2019-05-31 22:53:40 UTC (rev 245991)
@@ -81,9 +81,19 @@
 ["b","a"], ["a"] => [["b",-1],["a",0]]
 ["b","a"], ["a","c"] => [["b",-1],["a",0],["c",1]]
 ["b","a"], ["a","c"] => [["b",-1],["a",0],["c",1]]
-["b","a"], ["a","b"] => [["a",0],["b",0]]
+["b","a"], ["a","b"] => [["a",1],["b",0],["a",-1]]
 ["a","b","c"], ["a","d","c"] => [["a",0],["b",-1],["d",1],["c",0]]
+["a","b","c"], ["c","b","a"] => [["c",1],["b",1],["a",0],["b",-1],["c",-1]]
 
+Repeating items:
+["a"], ["a","a"] => [["a",0],["a",1]]
+["a","a"], ["a"] => [["a",0],["a",-1]]
+["a","a"], ["a","a"] => [["a",0],["a",0]]
+["b","a","b"], ["a","b","a"] => [["a",1],["b",0],["a",0],["b",-1]]
+["a","b","b","c"], ["c","b","b","b","a"] => [["a",-1],["c",1],["b",0],["b",0],["c",-1],["b",1],["a",1]]
+["a","b","b","b","c"], ["c","b","b","a"] => [["a",-1],["c",1],["b",0],["b",0],["b",-1],["c",-1],["a",1]]
+["a","a","b","b","c","c"], ["b","b","c","c","a","a"] => [["a",-1],["a",-1],["b",0],["b",0],["c",0],["c",0],["a",1],["a",1]]
+
 -- Running test case: Array.prototype.lastValue
 PASS: lastValue of a nonempty array should be the last value.
 PASS: lastValue of an empty array should be undefined.

Modified: trunk/LayoutTests/inspector/unit-tests/array-utilities.html (245990 => 245991)


--- trunk/LayoutTests/inspector/unit-tests/array-utilities.html	2019-05-31 22:30:55 UTC (rev 245990)
+++ trunk/LayoutTests/inspector/unit-tests/array-utilities.html	2019-05-31 22:53:40 UTC (rev 245991)
@@ -175,7 +175,17 @@
             diff(["b", "a"], ["a", "c"]);
             diff(["b", "a"], ["a", "b"]);
             diff(["a", "b", "c"], ["a", "d", "c"]);
+            diff(["a", "b", "c"], ["c", "b", "a"]);
 
+            InspectorTest.log("\nRepeating items:");
+            diff(["a"], ["a", "a"]);
+            diff(["a", "a"], ["a"]);
+            diff(["a", "a"], ["a", "a"]);
+            diff(["b", "a", "b"], ["a", "b", "a"]);
+            diff(["a", "b", "b", "c"], ["c", "b", "b", "b", "a"]);
+            diff(["a", "b", "b", "b", "c"], ["c", "b", "b", "a"]);
+            diff(["a", "a", "b", "b", "c", "c"], ["b", "b", "c", "c", "a", "a"]);
+
             return true;
         }
     });

Modified: trunk/Source/WebInspectorUI/ChangeLog (245990 => 245991)


--- trunk/Source/WebInspectorUI/ChangeLog	2019-05-31 22:30:55 UTC (rev 245990)
+++ trunk/Source/WebInspectorUI/ChangeLog	2019-05-31 22:53:40 UTC (rev 245991)
@@ -1,3 +1,47 @@
+2019-05-31  Nikita Vasilyev  <[email protected]>
+
+        Web Inspector: CSS Changes: modifications aren't shared for rules that match multiple elements
+        https://bugs.webkit.org/show_bug.cgi?id=195264
+        <rdar://problem/48550023>
+
+        Reviewed by Devin Rousso.
+
+        This patch fixes several cases when the diff was incorrect.
+
+        1. Perform diff based on CSSProperty content (name, value, and enabled property) instead
+           of strict equality of CSSProperty instances.
+
+        2. Copy all initial CSSProperty instances of CSSStyleDeclaration on 1st edit.
+           This removes the need to update `properties` on every single edit.
+
+        3. Do full diff to display modified property markers (green background) in Rules panel.
+           This fixes a few cases when the markers were inaccurate. E.g. a newly added property
+           matches removed property - no need to show the green background.
+
+        * UserInterface/Base/Utilities.js:
+        (Array.diffArrays):
+        Allow repeating items in the arrays.
+
+        * UserInterface/Controllers/CSSManager.js:
+        (WI.CSSManager.prototype.getModifiedStyle):
+        (WI.CSSManager.prototype.removeModifiedStyle):
+        * UserInterface/Models/CSSProperty.js:
+        (WI.CSSProperty):
+        (WI.CSSProperty.prototype.get modified):
+        (WI.CSSProperty.prototype.set modified):
+        (WI.CSSProperty.prototype.equals):
+        (WI.CSSProperty.prototype.clone):
+        (WI.CSSProperty.prototype._updateOwnerStyleText):
+        (WI.CSSProperty.prototype._markModified):
+        * UserInterface/Models/CSSStyleDeclaration.js:
+        (WI.CSSStyleDeclaration.prototype.markModified):
+        (WI.CSSStyleDeclaration.prototype.updatePropertiesModifiedState):
+        * UserInterface/Views/ChangesDetailsSidebarPanel.js:
+        (WI.ChangesDetailsSidebarPanel.prototype._createRuleElement):
+        * UserInterface/Views/SpreadsheetCSSStyleDeclarationEditor.js:
+        (WI.SpreadsheetCSSStyleDeclarationEditor.prototype.layout):
+        * UserInterface/Views/SpreadsheetStyleProperty.js:
+
 2019-05-31  Devin Rousso  <[email protected]>
 
         Web Inspector: Timelines: CPU: gray (?) and (x) should be white

Modified: trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js (245990 => 245991)


--- trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js	2019-05-31 22:30:55 UTC (rev 245990)
+++ trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js	2019-05-31 22:53:40 UTC (rev 245991)
@@ -522,62 +522,86 @@
 
 Object.defineProperty(Array, "diffArrays",
 {
-    value(initialArray, currentArray, onEach)
+    value(initialArray, currentArray, onEach, comparator)
     {
-        let initialSet = new Set(initialArray);
-        let currentSet = new Set(currentArray);
-        let indexInitial = 0;
-        let indexCurrent = 0;
-        let deltaInitial = 0;
-        let deltaCurrent = 0;
+        "use strict";
 
-        let i = 0;
-        while (true) {
-            if (indexInitial >= initialArray.length || indexCurrent >= currentArray.length)
-                break;
+        function defaultComparator(initial, current) {
+            return initial === current;
+        }
+        comparator = comparator || defaultComparator;
 
-            let initial = initialArray[indexInitial];
-            let current = currentArray[indexCurrent];
+        // Find the shortest prefix of matching items in both arrays.
+        //
+        //    initialArray = ["a", "b", "b", "c"]
+        //    currentArray = ["c", "b", "b", "a"]
+        //    findShortestEdit() // [1, 1]
+        //
+        function findShortestEdit() {
+            let deletionCount = initialArray.length;
+            let additionCount = currentArray.length;
+            let editCount = deletionCount + additionCount;
+            for (let i = 0; i < initialArray.length; ++i) {
+                if (i > editCount) {
+                    // Break since any possible edits at this point are going to be longer than the one already found.
+                    break;
+                }
 
-            if (initial === current)
-                onEach(current, 0);
-            else if (currentSet.has(initial)) {
-                if (initialSet.has(current)) {
-                    // Moved.
-                    onEach(current, 0);
-                } else {
-                    // Added.
-                    onEach(current, 1);
-                    --i;
-                    ++deltaCurrent;
+                for (let j = 0; j < currentArray.length; ++j) {
+                    let newEditCount = i + j;
+                    if (newEditCount > editCount) {
+                        // Break since any possible edits at this point are going to be longer than the one already found.
+                        break;
+                    }
+
+                    if (comparator(initialArray[i], currentArray[j])) {
+                        // A candidate for the shortest edit found.
+                        if (newEditCount < editCount) {
+                            editCount = newEditCount;
+                            deletionCount = i;
+                            additionCount = j;
+                        }
+                        break;
+                    }
                 }
-            } else {
-                // Removed.
-                onEach(initial, -1);
-                if (!initialSet.has(current)) {
-                    // Added.
-                    onEach(current, 1);
-                } else {
-                    --i;
-                    ++deltaInitial;
-                }
             }
+            return [deletionCount, additionCount];
+        }
 
-            ++i;
-            indexInitial = i + deltaInitial;
-            indexCurrent = i + deltaCurrent;
+        function commonPrefixLength(listA, listB) {
+            let shorterListLength = Math.min(listA.length, listB.length);
+            let i = 0;
+            while (i < shorterListLength) {
+                if (!comparator(listA[i], listB[i]))
+                    break;
+                ++i;
+            }
+            return i;
         }
 
-        for (let i = indexInitial; i < initialArray.length; ++i) {
-            // Removed.
-            onEach(initialArray[i], -1);
+        function fireOnEach(count, diffAction, array) {
+            for (let i = 0; i < count; ++i)
+                onEach(array[i], diffAction);
         }
 
-        for (let i = indexCurrent; i < currentArray.length; ++i) {
-            // Added.
-            onEach(currentArray[i], 1);
+        while (initialArray.length || currentArray.length) {
+            // Remove common prefix.
+            let prefixLength = commonPrefixLength(initialArray, currentArray);
+            if (prefixLength) {
+                fireOnEach(prefixLength, 0, currentArray);
+                initialArray = initialArray.slice(prefixLength);
+                currentArray = currentArray.slice(prefixLength);
+            }
+
+            if (!initialArray.length && !currentArray.length)
+                break;
+
+            let [deletionCount, additionCount] = findShortestEdit();
+            fireOnEach(deletionCount, -1, initialArray);
+            fireOnEach(additionCount, 1, currentArray);
+            initialArray = initialArray.slice(deletionCount);
+            currentArray = currentArray.slice(additionCount);
         }
-
     }
 });
 

Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/CSSManager.js (245990 => 245991)


--- trunk/Source/WebInspectorUI/UserInterface/Controllers/CSSManager.js	2019-05-31 22:30:55 UTC (rev 245990)
+++ trunk/Source/WebInspectorUI/UserInterface/Controllers/CSSManager.js	2019-05-31 22:53:40 UTC (rev 245991)
@@ -431,6 +431,16 @@
         this._modifiedStyles.set(style.stringId, style);
     }
 
+    getModifiedStyle(style)
+    {
+        return this._modifiedStyles.get(style.stringId);
+    }
+
+    removeModifiedStyle(style)
+    {
+        this._modifiedStyles.delete(style.stringId);
+    }
+
     // Protected
 
     mediaQueryResultChanged()

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/CSSProperty.js (245990 => 245991)


--- trunk/Source/WebInspectorUI/UserInterface/Models/CSSProperty.js	2019-05-31 22:30:55 UTC (rev 245990)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/CSSProperty.js	2019-05-31 22:53:40 UTC (rev 245991)
@@ -33,6 +33,7 @@
         this._index = index;
         this._overridingProperty = null;
         this._initialState = null;
+        this._modified = false;
 
         this.update(text, name, value, priority, enabled, overridden, implicit, anonymous, valid, styleSheetTextRange, true);
     }
@@ -184,9 +185,18 @@
 
     get modified()
     {
-        return !!this._initialState;
+        return this._modified;
     }
 
+    set modified(value)
+    {
+        if (this._modified === value)
+            return;
+
+        this._modified = value;
+        this.dispatchEventToListeners(WI.CSSProperty.Event.ModifiedChanged);
+    }
+
     get name()
     {
         return this._name;
@@ -377,6 +387,37 @@
         return this._hasOtherVendorNameOrKeyword;
     }
 
+    equals(property)
+    {
+        if (property === this)
+            return true;
+
+        if (!property)
+            return false;
+
+        return this._name === property.name && this._rawValue === property.rawValue && this._enabled === property.enabled;
+    }
+
+    clone()
+    {
+        let cssProperty = new WI.CSSProperty(
+            this._index,
+            this._text,
+            this._name,
+            this._rawValue,
+            this._priority,
+            this._enabled,
+            this._overridden,
+            this._implicit,
+            this._anonymous,
+            this._valid,
+            this._styleSheetTextRange);
+
+        cssProperty.ownerStyle = this._ownerStyle;
+
+        return cssProperty;
+    }
+
     // Private
 
     _updateStyleText(forceRemove = false)
@@ -393,8 +434,6 @@
 
     _updateOwnerStyleText(oldText, newText, forceRemove = false)
     {
-        console.assert(this.modified, "CSSProperty was modified without saving initial state.");
-
         if (oldText === newText) {
             if (forceRemove) {
                 const lineDelta = 0;
@@ -437,6 +476,7 @@
 
         let propertyWasRemoved = !newText;
         this._ownerStyle.shiftPropertiesAfter(this, lineDelta, columnDelta, propertyWasRemoved);
+        this._ownerStyle.updatePropertiesModifiedState();
     }
 
     _prependSemicolonIfNeeded()
@@ -456,30 +496,13 @@
 
     _markModified()
     {
-        if (this.modified)
-            return;
-
-        this._initialState = new WI.CSSProperty(
-            this._index,
-            this._text,
-            this._name,
-            this._rawValue,
-            this._priority,
-            this._enabled,
-            this._overridden,
-            this._implicit,
-            this._anonymous,
-            this._valid,
-            this._styleSheetTextRange);
-
-        if (this._ownerStyle) {
+        if (this._ownerStyle)
             this._ownerStyle.markModified();
-            this._initialState.ownerStyle = this._ownerStyle.initialState;
-        }
     }
 };
 
 WI.CSSProperty.Event = {
     Changed: "css-property-changed",
+    ModifiedChanged: "css-property-modified-changed",
     OverriddenStatusChanged: "css-property-overridden-status-changed"
 };

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/CSSStyleDeclaration.js (245990 => 245991)


--- trunk/Source/WebInspectorUI/UserInterface/Models/CSSStyleDeclaration.js	2019-05-31 22:30:55 UTC (rev 245990)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/CSSStyleDeclaration.js	2019-05-31 22:53:40 UTC (rev 245991)
@@ -365,9 +365,11 @@
 
     markModified()
     {
-        let properties = this._initialState ? this._initialState.properties : this._properties;
+        if (!this._initialState) {
+            let visibleProperties = this.visibleProperties.map((property) => {
+                return property.clone();
+            });
 
-        if (!this._initialState) {
             this._initialState = new WI.CSSStyleDeclaration(
                 this._nodeStyles,
                 this._ownerStyleSheet,
@@ -376,12 +378,10 @@
                 this._node,
                 this._inherited,
                 this._text,
-                [], // Passing CSS properties here would change their ownerStyle.
+                visibleProperties,
                 this._styleSheetTextRange);
         }
 
-        this._initialState.properties = properties.map((property) => { return property.initialState || property });
-
         WI.cssManager.addModifiedStyle(this);
     }
 
@@ -417,6 +417,36 @@
         this._visibleProperties = null;
     }
 
+    updatePropertiesModifiedState()
+    {
+        if (!this._initialState)
+            return;
+
+        if (this._type === WI.CSSStyleDeclaration.Type.Computed)
+            return;
+
+        let initialCSSProperties = this._initialState.visibleProperties;
+        let cssProperties = this.visibleProperties;
+
+        let hasModified = false;
+
+        function onEach(cssProperty, action) {
+            if (action !== 0)
+                hasModified = true;
+
+            cssProperty.modified = action ="" 1;
+        }
+
+        function comparator(a, b) {
+            return a.equals(b);
+        }
+
+        Array.diffArrays(initialCSSProperties, cssProperties, onEach, comparator);
+
+        if (!hasModified)
+            WI.cssManager.removeModifiedStyle(this);
+    }
+
     // Protected
 
     get nodeStyles()

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ChangesDetailsSidebarPanel.js (245990 => 245991)


--- trunk/Source/WebInspectorUI/UserInterface/Views/ChangesDetailsSidebarPanel.js	2019-05-31 22:30:55 UTC (rev 245990)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ChangesDetailsSidebarPanel.js	2019-05-31 22:53:40 UTC (rev 245991)
@@ -149,28 +149,28 @@
 
         selectorLineElement.append(" {\n");
 
-        let appendProperty = (cssProperty, className) => {
+        function onEach(cssProperty, action) {
+            let className = "";
+            if (action ="" 1)
+                className = "added";
+            else if (action ="" -1)
+                className = "removed";
+            else
+                className = "unchanged";
+
             let propertyLineElement = ruleElement.appendChild(document.createElement("div"));
             propertyLineElement.classList.add("css-property-line", className);
-            let stylePropertyView = new WI.SpreadsheetStyleProperty(null, cssProperty, {readOnly: true});
+
+            const delegate = null;
+            let stylePropertyView = new WI.SpreadsheetStyleProperty(delegate, cssProperty, {readOnly: true});
             propertyLineElement.append(WI.indentString(), stylePropertyView.element, "\n");
-        };
+        }
 
-        let initialCSSProperties = style.initialState.visibleProperties;
-        let cssProperties = style.visibleProperties;
+        function comparator(a, b) {
+            return a.equals(b);
+        }
 
-        Array.diffArrays(initialCSSProperties, cssProperties, (cssProperty, action) => {
-            if (action ="" 0) {
-                if (cssProperty.modified) {
-                    appendProperty(cssProperty.initialState, "removed");
-                    appendProperty(cssProperty, "added");
-                } else
-                    appendProperty(cssProperty, "unchanged");
-            } else if (action ="" 1)
-                appendProperty(cssProperty, "added");
-            else if (action ="" -1)
-                appendProperty(cssProperty, "removed");
-        });
+        Array.diffArrays(style.initialState.visibleProperties, style.visibleProperties, onEach, comparator);
 
         let closeBraceElement = document.createElement("span");
         closeBraceElement.className = "close-brace";

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetCSSStyleDeclarationEditor.js (245990 => 245991)


--- trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetCSSStyleDeclarationEditor.js	2019-05-31 22:30:55 UTC (rev 245990)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetCSSStyleDeclarationEditor.js	2019-05-31 22:53:40 UTC (rev 245991)
@@ -82,6 +82,9 @@
 
         this.element.removeChildren();
 
+        if (this._style)
+            this._style.updatePropertiesModifiedState();
+
         let properties = this.propertiesToRender;
         this.element.classList.toggle("no-properties", !properties.length);
 

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetStyleProperty.js (245990 => 245991)


--- trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetStyleProperty.js	2019-05-31 22:30:55 UTC (rev 245990)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetStyleProperty.js	2019-05-31 22:53:40 UTC (rev 245991)
@@ -53,6 +53,7 @@
 
         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.
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to