Changeset 243264 in webkit


Ignore:
Timestamp:
Mar 20, 2019 5:22:06 PM (5 years ago)
Author:
Devin Rousso
Message:

Web Inspector: Styles Redesign: Editing selector should not hide the rule
https://bugs.webkit.org/show_bug.cgi?id=178489
<rdar://problem/35062434>

Reviewed by Timothy Hatcher.

Source/WebInspectorUI:

Extracts the selector payload parsing logic inside WI.DOMNodeStyles into static functions
so that when the user changes the selector of a WI.CSSRule, it's able to process and
update itself with the new selector. This is mainly useful in the case where the WI.CSSRule
no longer applies to the selected node (meaning it won't be part of that node's
WI.DOMNodeStyles) in that it allows the WI.SpreadsheetCSSStyleDeclarationSection to
display the new selector text and the owner WI.SpreadsheetRulesStyleDetailsPanel to keep
that section visible even though it isn't applicable to the current node anymore.

  • UserInterface/Models/DOMNodeStyles.js:

(WI.DOMNodeStyles):
(WI.DOMNodeStyles.parseSelectorListPayload): Added.
(WI.DOMNodeStyles.createSourceCodeLocation): Added.
(WI.DOMNodeStyles.prototype.refresh):
(WI.DOMNodeStyles.prototype.refresh.fetchedMatchedStyles):
(WI.DOMNodeStyles.prototype.refresh.fetchedInlineStyles):
(WI.DOMNodeStyles.prototype.refresh.fetchedComputedStyle):
(WI.DOMNodeStyles.prototype._parseStyleDeclarationPayload):
(WI.DOMNodeStyles.prototype._parseRulePayload):
(WI.DOMNodeStyles.prototype._styleSheetContentDidChange):
(WI.DOMNodeStyles.prototype.refresh.parseRuleMatchArrayPayload): Deleted.
(WI.DOMNodeStyles.prototype._createSourceCodeLocation): Deleted.
(WI.DOMNodeStyles.prototype._parseSelectorListPayload): Deleted.
Keep track of all WI.CSSRule and WI.CSSStyleDeclaration that have ever been associated
with this object, so that if a rule's selector is changed to no longer match, and then is
changed back to match again, we are able to update that rule instead of creating a new one.

  • UserInterface/Views/SpreadsheetRulesStyleDetailsPanel.js:

(WI.SpreadsheetRulesStyleDetailsPanel.prototype.layout):
(WI.SpreadsheetRulesStyleDetailsPanel.prototype._handleSectionFilterApplied):
(WI.SpreadsheetRulesStyleDetailsPanel.prototype._handleSectionSelectorWillChange): Added.
Attempt to preserve the position of any sections that are changed and no longer apply to the
current node.

  • UserInterface/Views/SpreadsheetCSSStyleDeclarationSection.js:

(WI.SpreadsheetCSSStyleDeclarationSection.prototype.spreadsheetSelectorFieldDidChange):
(WI.SpreadsheetCSSStyleDeclarationSection.prototype._renderSelector):
Drive-by: remove unused CSS classes.

  • UserInterface/Models/CSSRule.js:

(WI.CSSRule.prototype.update):
(WI.CSSRule.prototype._selectorResolved):
Drive-by: remove unused event.

  • UserInterface/Base/Multimap.js:

(Multimap.prototype.has): Added.
(Multimap.prototype.sets): Added.
(Multimap.prototype.copy): Added.

LayoutTests:

  • inspector/unit-tests/multimap.html: Added.
  • inspector/unit-tests/multimap-expected.txt: Added.
Location:
trunk
Files:
2 added
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r243262 r243264  
     12019-03-20  Devin Rousso  <drousso@apple.com>
     2
     3        Web Inspector: Styles Redesign: Editing selector should not hide the rule
     4        https://bugs.webkit.org/show_bug.cgi?id=178489
     5        <rdar://problem/35062434>
     6
     7        Reviewed by Timothy Hatcher.
     8
     9        * inspector/unit-tests/multimap.html: Added.
     10        * inspector/unit-tests/multimap-expected.txt: Added.
     11
    1122019-03-20  Ryan Haddad  <ryanhaddad@apple.com>
    213
  • trunk/Source/WebInspectorUI/ChangeLog

    r243260 r243264  
     12019-03-20  Devin Rousso  <drousso@apple.com>
     2
     3        Web Inspector: Styles Redesign: Editing selector should not hide the rule
     4        https://bugs.webkit.org/show_bug.cgi?id=178489
     5        <rdar://problem/35062434>
     6
     7        Reviewed by Timothy Hatcher.
     8
     9        Extracts the selector payload parsing logic inside `WI.DOMNodeStyles` into static functions
     10        so that when the user changes the selector of a `WI.CSSRule`, it's able to process and
     11        update itself with the new selector. This is mainly useful in the case where the `WI.CSSRule`
     12        no longer applies to the selected node (meaning it won't be part of that node's
     13        `WI.DOMNodeStyles`) in that it allows the `WI.SpreadsheetCSSStyleDeclarationSection` to
     14        display the new selector text and the owner `WI.SpreadsheetRulesStyleDetailsPanel` to keep
     15        that section visible even though it isn't applicable to the current node anymore.
     16
     17        * UserInterface/Models/DOMNodeStyles.js:
     18        (WI.DOMNodeStyles):
     19        (WI.DOMNodeStyles.parseSelectorListPayload): Added.
     20        (WI.DOMNodeStyles.createSourceCodeLocation): Added.
     21        (WI.DOMNodeStyles.prototype.refresh):
     22        (WI.DOMNodeStyles.prototype.refresh.fetchedMatchedStyles):
     23        (WI.DOMNodeStyles.prototype.refresh.fetchedInlineStyles):
     24        (WI.DOMNodeStyles.prototype.refresh.fetchedComputedStyle):
     25        (WI.DOMNodeStyles.prototype._parseStyleDeclarationPayload):
     26        (WI.DOMNodeStyles.prototype._parseRulePayload):
     27        (WI.DOMNodeStyles.prototype._styleSheetContentDidChange):
     28        (WI.DOMNodeStyles.prototype.refresh.parseRuleMatchArrayPayload): Deleted.
     29        (WI.DOMNodeStyles.prototype._createSourceCodeLocation): Deleted.
     30        (WI.DOMNodeStyles.prototype._parseSelectorListPayload): Deleted.
     31        Keep track of all `WI.CSSRule` and `WI.CSSStyleDeclaration` that have ever been associated
     32        with this object, so that if a rule's selector is changed to no longer match, and then is
     33        changed back to match again, we are able to update that rule instead of creating a new one.
     34
     35        * UserInterface/Views/SpreadsheetRulesStyleDetailsPanel.js:
     36        (WI.SpreadsheetRulesStyleDetailsPanel.prototype.layout):
     37        (WI.SpreadsheetRulesStyleDetailsPanel.prototype._handleSectionFilterApplied):
     38        (WI.SpreadsheetRulesStyleDetailsPanel.prototype._handleSectionSelectorWillChange): Added.
     39        Attempt to preserve the position of any sections that are changed and no longer apply to the
     40        current node.
     41
     42        * UserInterface/Views/SpreadsheetCSSStyleDeclarationSection.js:
     43        (WI.SpreadsheetCSSStyleDeclarationSection.prototype.spreadsheetSelectorFieldDidChange):
     44        (WI.SpreadsheetCSSStyleDeclarationSection.prototype._renderSelector):
     45        Drive-by: remove unused CSS classes.
     46
     47        * UserInterface/Models/CSSRule.js:
     48        (WI.CSSRule.prototype.update):
     49        (WI.CSSRule.prototype._selectorResolved):
     50        Drive-by: remove unused event.
     51
     52        * UserInterface/Base/Multimap.js:
     53        (Multimap.prototype.has): Added.
     54        (Multimap.prototype.sets): Added.
     55        (Multimap.prototype.copy): Added.
     56
    1572019-03-20  Devin Rousso  <drousso@apple.com>
    258
  • trunk/Source/WebInspectorUI/UserInterface/Base/Multimap.js

    r242743 r243264  
    3535
    3636    // Public
     37
     38    has(key, value)
     39    {
     40        let valueSet = this._map.get(key);
     41        if (!valueSet)
     42            return false;
     43        return value === undefined || valueSet.has(value);
     44    }
    3745
    3846    get(key)
     
    8997    }
    9098
     99    sets()
     100    {
     101        return this._map.entries();
     102    }
     103
    91104    *[Symbol.iterator]()
    92105    {
     
    97110    }
    98111
     112    copy()
     113    {
     114        return new Multimap(this.toJSON());
     115    }
     116
    99117    toJSON()
    100118    {
  • trunk/Source/WebInspectorUI/UserInterface/Models/CSSRule.js

    r243038 r243264  
    5656    }
    5757
    58     update(sourceCodeLocation, selectorText, selectors, matchedSelectorIndices, style, mediaList, dontFireEvents)
     58    update(sourceCodeLocation, selectorText, selectors, matchedSelectorIndices, style, mediaList)
    5959    {
    6060        sourceCodeLocation = sourceCodeLocation || null;
     
    6464        style = style || null;
    6565        mediaList = mediaList || [];
    66 
    67         var changed = false;
    68         if (!dontFireEvents) {
    69             changed = this._selectorText !== selectorText || !Array.shallowEqual(this._selectors, selectors) ||
    70                 !Array.shallowEqual(this._matchedSelectorIndices, matchedSelectorIndices) || this._style !== style ||
    71                 !!this._sourceCodeLocation !== !!sourceCodeLocation || this._mediaList.length !== mediaList.length;
    72             // FIXME: Look for differences in the media list arrays.
    73         }
    7466
    7567        if (this._style)
     
    8577        if (this._style)
    8678            this._style.ownerRule = this;
    87 
    88         if (changed)
    89             this.dispatchEventToListeners(WI.CSSRule.Event.Changed);
    9079    }
    9180
     
    163152    _selectorResolved(rulePayload)
    164153    {
     154        if (rulePayload) {
     155            let selectorText = rulePayload.selectorList.text;
     156            if (selectorText !== this._selectorText) {
     157                let selectors = WI.DOMNodeStyles.parseSelectorListPayload(rulePayload.selectorList);
     158
     159                let sourceCodeLocation = null;
     160                let sourceRange = rulePayload.selectorList.range;
     161                if (sourceRange) {
     162                    sourceCodeLocation = WI.DOMNodeStyles.createSourceCodeLocation(rulePayload.sourceURL, {
     163                        line: sourceRange.startLine,
     164                        column: sourceRange.startColumn,
     165                        documentNode: this._nodeStyles.node.ownerDocument,
     166                    });
     167                }
     168
     169                if (this._ownerStyleSheet) {
     170                    if (!sourceCodeLocation && this._ownerStyleSheet.isInspectorStyleSheet())
     171                        sourceCodeLocation = this._ownerStyleSheet.createSourceCodeLocation(sourceRange.startLine, sourceRange.startColumn);
     172
     173                    sourceCodeLocation = this._ownerStyleSheet.offsetSourceCodeLocation(sourceCodeLocation);
     174                }
     175
     176                this.update(sourceCodeLocation, selectorText, selectors, [], this._style, this._mediaList);
     177            }
     178        }
     179
    165180        this.dispatchEventToListeners(WI.CSSRule.Event.SelectorChanged, {valid: !!rulePayload});
    166181    }
     
    168183
    169184WI.CSSRule.Event = {
    170     Changed: "css-rule-changed",
    171185    SelectorChanged: "css-rule-invalid-selector"
    172186};
  • trunk/Source/WebInspectorUI/UserInterface/Models/DOMNodeStyles.js

    r242939 r243264  
    3333        this._node = node || null;
    3434
    35         this._rulesMap = {};
    36         this._styleDeclarationsMap = {};
     35        this._rulesMap = new Map;
     36        this._stylesMap = new Multimap;
    3737
    3838        this._matchedRules = [];
     
    5252    // Static
    5353
     54    static parseSelectorListPayload(selectorList)
     55    {
     56        let selectors = selectorList.selectors;
     57        if (!selectors.length)
     58            return [];
     59
     60        // COMPATIBILITY (iOS 8): The selectorList payload was an array of selector text strings.
     61        // Now they are CSSSelector objects with multiple properties.
     62        if (typeof selectors[0] === "string") {
     63            return selectors.map(function(selectorText) {
     64                return new WI.CSSSelector(selectorText);
     65            });
     66        }
     67
     68        return selectors.map(function(selectorPayload) {
     69            return new WI.CSSSelector(selectorPayload.text, selectorPayload.specificity, selectorPayload.dynamic);
     70        });
     71    }
     72
     73    static createSourceCodeLocation(sourceURL, {line, column, documentNode} = {})
     74    {
     75        if (!sourceURL)
     76            return null;
     77
     78        let sourceCode = null;
     79
     80        // Try to use the node to find the frame which has the correct resource first.
     81        if (documentNode) {
     82            let mainResource = WI.networkManager.resourceForURL(documentNode.documentURL);
     83            if (mainResource) {
     84                let parentFrame = mainResource.parentFrame;
     85                sourceCode = parentFrame.resourceForURL(sourceURL);
     86            }
     87        }
     88
     89        // If that didn't find the resource, then search all frames.
     90        if (!sourceCode)
     91            sourceCode = WI.networkManager.resourceForURL(sourceURL);
     92
     93        if (!sourceCode)
     94            return null;
     95
     96        return sourceCode.createSourceCodeLocation(line || 0, column || 0);
     97    }
     98
    5499    static uniqueOrderedStyles(orderedStyles)
    55100    {
     
    104149
    105150        this._needsRefresh = false;
     151
     152        let previousStylesMap = this._stylesMap.copy();
    106153
    107154        let fetchedMatchedStylesPromise = new WI.WrappedPromise;
     
    121168        }
    122169
    123         function parseRuleMatchArrayPayload(matchArray, node, inherited)
    124         {
     170        let parseRuleMatchArrayPayload = (matchArray, node, inherited, pseudoId) => {
    125171            var result = [];
    126172
     
    128174            var ruleOccurrences = {};
    129175            for (var i = matchArray.length - 1; i >= 0; --i) {
    130                 var rule = this._parseRulePayload(matchArray[i].rule, matchArray[i].matchingSelectors, node, inherited, ruleOccurrences);
     176                var rule = this._parseRulePayload(matchArray[i].rule, matchArray[i].matchingSelectors, node, inherited, pseudoId, ruleOccurrences);
    131177                if (!rule)
    132178                    continue;
     
    135181
    136182            return result;
    137         }
     183        };
    138184
    139185        function fetchedMatchedStyles(error, matchedRulesPayload, pseudoElementRulesPayload, inheritedRulesPayload)
     
    143189            inheritedRulesPayload = inheritedRulesPayload || [];
    144190
    145             // Move the current maps to previous.
    146             this._previousRulesMap = this._rulesMap;
    147             this._previousStyleDeclarationsMap = this._styleDeclarationsMap;
    148 
    149             // Clear the current maps.
    150             this._rulesMap = {};
    151             this._styleDeclarationsMap = {};
    152 
    153             this._matchedRules = parseRuleMatchArrayPayload.call(this, matchedRulesPayload, this._node);
     191            this._matchedRules = parseRuleMatchArrayPayload(matchedRulesPayload, this._node);
    154192
    155193            this._pseudoElements.clear();
    156             for (var pseudoElementRulePayload of pseudoElementRulesPayload) {
    157                 var pseudoElementRules = parseRuleMatchArrayPayload.call(this, pseudoElementRulePayload.matches, this._node);
    158                 this._pseudoElements.set(pseudoElementRulePayload.pseudoId, {matchedRules: pseudoElementRules});
     194            for (let {pseudoId, matches} of pseudoElementRulesPayload) {
     195                let pseudoElementRules = parseRuleMatchArrayPayload(matches, this._node, false, pseudoId);
     196                this._pseudoElements.set(pseudoId, {matchedRules: pseudoElementRules});
    159197            }
    160198
     
    167205
    168206                var inheritedRuleInfo = {node: currentNode};
    169                 inheritedRuleInfo.inlineStyle = inheritedRulePayload.inlineStyle ? this._parseStyleDeclarationPayload(inheritedRulePayload.inlineStyle, currentNode, true, WI.CSSStyleDeclaration.Type.Inline) : null;
    170                 inheritedRuleInfo.matchedRules = inheritedRulePayload.matchedCSSRules ? parseRuleMatchArrayPayload.call(this, inheritedRulePayload.matchedCSSRules, currentNode, true) : [];
     207                inheritedRuleInfo.inlineStyle = inheritedRulePayload.inlineStyle ? this._parseStyleDeclarationPayload(inheritedRulePayload.inlineStyle, currentNode, true, null, WI.CSSStyleDeclaration.Type.Inline) : null;
     208                inheritedRuleInfo.matchedRules = inheritedRulePayload.matchedCSSRules ? parseRuleMatchArrayPayload(inheritedRulePayload.matchedCSSRules, currentNode, true) : [];
    171209
    172210                if (inheritedRuleInfo.inlineStyle || inheritedRuleInfo.matchedRules.length)
     
    182220        function fetchedInlineStyles(error, inlineStylePayload, attributesStylePayload)
    183221        {
    184             this._inlineStyle = inlineStylePayload ? this._parseStyleDeclarationPayload(inlineStylePayload, this._node, false, WI.CSSStyleDeclaration.Type.Inline) : null;
    185             this._attributesStyle = attributesStylePayload ? this._parseStyleDeclarationPayload(attributesStylePayload, this._node, false, WI.CSSStyleDeclaration.Type.Attribute) : null;
     222            this._inlineStyle = inlineStylePayload ? this._parseStyleDeclarationPayload(inlineStylePayload, this._node, false, null, WI.CSSStyleDeclaration.Type.Inline) : null;
     223            this._attributesStyle = attributesStylePayload ? this._parseStyleDeclarationPayload(attributesStylePayload, this._node, false, null, WI.CSSStyleDeclaration.Type.Attribute) : null;
    186224
    187225            this._updateStyleCascade();
     
    211249
    212250            let significantChange = false;
    213             for (let key in this._styleDeclarationsMap) {
    214                 // Check if the same key exists in the previous map and has the same style objects.
    215                 if (key in this._previousStyleDeclarationsMap) {
    216                     if (Array.shallowEqual(this._styleDeclarationsMap[key], this._previousStyleDeclarationsMap[key]))
    217                         continue;
    218 
     251            for (let [key, styles] of this._stylesMap.sets()) {
     252                let previousStyles = previousStylesMap.get(key);
     253                if (previousStyles) {
    219254                    // Some styles have selectors such that they will match with the DOM node twice (for example "::before, ::after").
    220255                    // In this case a second style for a second matching may be generated and added which will cause the shallowEqual
    221256                    // to not return true, so in this case we just want to ensure that all the current styles existed previously.
    222257                    let styleFound = false;
    223                     for (let style of this._styleDeclarationsMap[key]) {
    224                         if (this._previousStyleDeclarationsMap[key].includes(style)) {
     258                    for (let style of styles) {
     259                        if (previousStyles.has(style)) {
    225260                            styleFound = true;
    226261                            break;
     
    234269                if (!this._includeUserAgentRulesOnNextRefresh) {
    235270                    // We can assume all the styles with the same key are from the same stylesheet and rule, so we only check the first.
    236                     let firstStyle = this._styleDeclarationsMap[key][0];
     271                    let firstStyle = styles.firstValue;
    237272                    if (firstStyle && firstStyle.ownerRule && firstStyle.ownerRule.type === WI.CSSStyleSheet.Type.UserAgent) {
    238273                        // User Agent styles get different identifiers after some edits. This would cause us to fire a significant refreshed
     
    249284
    250285            if (!significantChange) {
    251                 for (var key in this._previousStyleDeclarationsMap) {
     286                for (let [key, previousStyles] of previousStylesMap.sets()) {
    252287                    // Check if the same key exists in current map. If it does exist it was already checked for equality above.
    253                     if (key in this._styleDeclarationsMap)
     288                    if (this._stylesMap.has(key))
    254289                        continue;
    255290
    256291                    if (!this._includeUserAgentRulesOnNextRefresh) {
    257292                        // See above for why we skip user agent style rules.
    258                         var firstStyle = this._previousStyleDeclarationsMap[key][0];
     293                        let firstStyle = previousStyles.firstValue;
    259294                        if (firstStyle && firstStyle.ownerRule && firstStyle.ownerRule.type === WI.CSSStyleSheet.Type.UserAgent)
    260295                            continue;
     
    267302            }
    268303
    269             delete this._includeUserAgentRulesOnNextRefresh;
    270 
    271             // Delete the previous maps now that any reused rules and style have been moved over.
    272             delete this._previousRulesMap;
    273             delete this._previousStyleDeclarationsMap;
     304            this._includeUserAgentRulesOnNextRefresh = false
    274305
    275306            this.dispatchEventToListeners(WI.DOMNodeStyles.Event.Refreshed, {significantChange});
     
    480511
    481512    // Private
    482 
    483     _createSourceCodeLocation(sourceURL, sourceLine, sourceColumn)
    484     {
    485         if (!sourceURL)
    486             return null;
    487 
    488         var sourceCode;
    489 
    490         // Try to use the node to find the frame which has the correct resource first.
    491         if (this._node.ownerDocument) {
    492             var mainResource = WI.networkManager.resourceForURL(this._node.ownerDocument.documentURL);
    493             if (mainResource) {
    494                 var parentFrame = mainResource.parentFrame;
    495                 sourceCode = parentFrame.resourceForURL(sourceURL);
    496             }
    497         }
    498 
    499         // If that didn't find the resource, then search all frames.
    500         if (!sourceCode)
    501             sourceCode = WI.networkManager.resourceForURL(sourceURL);
    502 
    503         if (!sourceCode)
    504             return null;
    505 
    506         return sourceCode.createSourceCodeLocation(sourceLine || 0, sourceColumn || 0);
    507     }
    508513
    509514    _parseSourceRangePayload(payload)
     
    591596    }
    592597
    593     _parseStyleDeclarationPayload(payload, node, inherited, type, rule, updateAllStyles)
     598    _parseStyleDeclarationPayload(payload, node, inherited, pseudoId, type, rule)
    594599    {
    595600        if (!payload)
     
    601606        var id = payload.styleId;
    602607        var mapKey = id ? id.styleSheetId + ":" + id.ordinal : null;
    603 
     608        if (pseudoId)
     609            mapKey += ":" + pseudoId;
    604610        if (type === WI.CSSStyleDeclaration.Type.Attribute)
    605             mapKey = node.id + ":attribute";
    606 
    607         var styleDeclaration = rule ? rule.style : null;
    608         var styleDeclarations = [];
    609 
    610         // Look for existing styles in the previous map if there is one, otherwise use the current map.
    611         var previousStyleDeclarationsMap = this._previousStyleDeclarationsMap || this._styleDeclarationsMap;
    612         if (mapKey && mapKey in previousStyleDeclarationsMap) {
    613             styleDeclarations = previousStyleDeclarationsMap[mapKey];
    614 
    615             // If we need to update all styles, then stop here and call _parseStyleDeclarationPayload for each style.
    616             // We need to parse multiple times so we reuse the right properties from each style.
    617             if (updateAllStyles && styleDeclarations.length) {
    618                 for (var i = 0; i < styleDeclarations.length; ++i) {
    619                     var styleDeclaration = styleDeclarations[i];
    620                     this._parseStyleDeclarationPayload(payload, styleDeclaration.node, styleDeclaration.inherited, styleDeclaration.type, styleDeclaration.ownerRule);
    621                 }
    622 
    623                 return null;
    624             }
    625 
    626             if (!styleDeclaration) {
    627                 var filteredStyleDeclarations = styleDeclarations.filter(function(styleDeclaration) {
    628                     // This case only applies for styles that are not part of a rule.
    629                     if (styleDeclaration.ownerRule) {
    630                         console.assert(!rule);
    631                         return false;
    632                     }
    633 
    634                     if (styleDeclaration.node !== node)
    635                         return false;
    636 
    637                     if (styleDeclaration.inherited !== inherited)
    638                         return false;
    639 
    640                     return true;
    641                 });
    642 
    643                 console.assert(filteredStyleDeclarations.length <= 1);
    644                 styleDeclaration = filteredStyleDeclarations[0] || null;
    645             }
    646         }
    647 
    648         if (previousStyleDeclarationsMap !== this._styleDeclarationsMap) {
    649             // If the previous and current maps differ then make sure the found styleDeclaration is added to the current map.
    650             styleDeclarations = mapKey && mapKey in this._styleDeclarationsMap ? this._styleDeclarationsMap[mapKey] : [];
    651 
    652             if (styleDeclaration && !styleDeclarations.includes(styleDeclaration)) {
    653                 styleDeclarations.push(styleDeclaration);
    654                 this._styleDeclarationsMap[mapKey] = styleDeclarations;
    655             }
    656         }
     611            mapKey += ":" + node.id + ":attribute";
     612
     613        let style = rule ? rule.style : null;
     614
     615        let existingStyles = this._stylesMap.get(mapKey);
     616        if (existingStyles && !style) {
     617            for (let existingStyle of existingStyles) {
     618                if (existingStyle.node === node && existingStyle.inherited === inherited) {
     619                    style = existingStyle;
     620                    break;
     621                }
     622            }
     623        }
     624
     625        if (style)
     626            this._stylesMap.add(mapKey, style);
    657627
    658628        var shorthands = {};
     
    671641                ++inheritedPropertyCount;
    672642
    673             let property = this._parseStylePropertyPayload(propertyPayload, i, styleDeclaration);
     643            let property = this._parseStylePropertyPayload(propertyPayload, i, style);
    674644            properties.push(property);
    675645        }
     
    678648        var styleSheetTextRange = this._parseSourceRangePayload(payload.range);
    679649
    680         if (styleDeclaration) {
    681             styleDeclaration.update(text, properties, styleSheetTextRange);
    682             return styleDeclaration;
     650        if (style) {
     651            style.update(text, properties, styleSheetTextRange);
     652            return style;
    683653        }
    684654
     
    693663            return null;
    694664
    695         styleDeclaration = new WI.CSSStyleDeclaration(this, styleSheet, id, type, node, inherited, text, properties, styleSheetTextRange);
    696 
    697         if (mapKey) {
    698             styleDeclarations.push(styleDeclaration);
    699             this._styleDeclarationsMap[mapKey] = styleDeclarations;
    700         }
    701 
    702         return styleDeclaration;
    703     }
    704 
    705     _parseSelectorListPayload(selectorList)
    706     {
    707         var selectors = selectorList.selectors;
    708         if (!selectors.length)
    709             return [];
    710 
    711         // COMPATIBILITY (iOS 8): The selectorList payload was an array of selector text strings.
    712         // Now they are CSSSelector objects with multiple properties.
    713         if (typeof selectors[0] === "string") {
    714             return selectors.map(function(selectorText) {
    715                 return new WI.CSSSelector(selectorText);
    716             });
    717         }
    718 
    719         return selectors.map(function(selectorPayload) {
    720             return new WI.CSSSelector(selectorPayload.text, selectorPayload.specificity, selectorPayload.dynamic);
    721         });
    722     }
    723 
    724     _parseRulePayload(payload, matchedSelectorIndices, node, inherited, ruleOccurrences)
     665        style = new WI.CSSStyleDeclaration(this, styleSheet, id, type, node, inherited, text, properties, styleSheetTextRange);
     666
     667        if (mapKey)
     668            this._stylesMap.add(mapKey, style);
     669
     670        return style;
     671    }
     672
     673    _parseRulePayload(payload, matchedSelectorIndices, node, inherited, pseudoId, ruleOccurrences)
    725674    {
    726675        if (!payload)
     
    733682        var id = payload.ruleId || payload.style.styleId;
    734683
    735         var mapKey = id ? id.styleSheetId + ":" + id.ordinal + ":" + (inherited ? "I" : "N") + ":" + node.id : null;
     684        var mapKey = id ? id.styleSheetId + ":" + id.ordinal + ":" + (inherited ? "I" : "N") + ":" + (pseudoId ? pseudoId + ":" : "") + node.id : null;
    736685
    737686        // Rules can match multiple times if they have multiple selectors or because of inheritance. We keep a count
     
    748697        }
    749698
    750         var rule = null;
    751 
    752         // Look for existing rules in the previous map if there is one, otherwise use the current map.
    753         var previousRulesMap = this._previousRulesMap || this._rulesMap;
    754         if (mapKey && mapKey in previousRulesMap) {
    755             rule = previousRulesMap[mapKey];
    756 
    757             if (previousRulesMap !== this._rulesMap) {
    758                 // If the previous and current maps differ then make sure the found rule is added to the current map.
    759                 this._rulesMap[mapKey] = rule;
    760             }
    761         }
    762 
    763         var style = this._parseStyleDeclarationPayload(payload.style, node, inherited, WI.CSSStyleDeclaration.Type.Rule, rule);
     699        let rule = this._rulesMap.get(mapKey);
     700
     701        var style = this._parseStyleDeclarationPayload(payload.style, node, inherited, pseudoId, WI.CSSStyleDeclaration.Type.Rule, rule);
    764702        if (!style)
    765703            return null;
     
    768706
    769707        var selectorText = payload.selectorList.text;
    770         var selectors = this._parseSelectorListPayload(payload.selectorList);
     708        let selectors = DOMNodeStyles.parseSelectorListPayload(payload.selectorList);
    771709        var type = WI.CSSManager.protocolStyleSheetOriginToEnum(payload.origin);
    772710
    773711        var sourceCodeLocation = null;
    774712        var sourceRange = payload.selectorList.range;
    775         if (sourceRange)
    776             sourceCodeLocation = this._createSourceCodeLocation(payload.sourceURL, sourceRange.startLine, sourceRange.startColumn);
    777         else {
     713        if (sourceRange) {
     714            sourceCodeLocation = DOMNodeStyles.createSourceCodeLocation(payload.sourceURL, {
     715                line: sourceRange.startLine,
     716                column: sourceRange.startColumn,
     717                documentNode: this._node.ownerDocument,
     718            });
     719        } else {
    778720            // FIXME: Is it possible for a CSSRule to have a sourceLine without its selectorList having a sourceRange? Fall back just in case.
    779             sourceCodeLocation = this._createSourceCodeLocation(payload.sourceURL, payload.sourceLine);
     721            sourceCodeLocation = DOMNodeStyles.createSourceCodeLocation(payload.sourceURL, {
     722                line: payload.sourceLine,
     723                documentNode: this._node.ownerDocument,
     724            });
    780725        }
    781726
     
    792737            var mediaType = WI.CSSManager.protocolMediaSourceToEnum(mediaItem.source);
    793738            var mediaText = mediaItem.text;
    794             var mediaSourceCodeLocation = this._createSourceCodeLocation(mediaItem.sourceURL, mediaItem.sourceLine);
     739            let mediaSourceCodeLocation = DOMNodeStyles.createSourceCodeLocation(mediaItem.sourceURL, {line: mediaItem.sourceLine});
    795740            if (styleSheet)
    796741                mediaSourceCodeLocation = styleSheet.offsetSourceCodeLocation(mediaSourceCodeLocation);
     
    810755
    811756        if (mapKey)
    812             this._rulesMap[mapKey] = rule;
     757            this._rulesMap.set(mapKey, rule);
    813758
    814759        return rule;
     
    830775        // Ignore the stylesheet we know we just changed and handled above.
    831776        if (styleSheet === this._ignoreNextContentDidChangeForStyleSheet) {
    832             delete this._ignoreNextContentDidChangeForStyleSheet;
     777            this._ignoreNextContentDidChangeForStyleSheet = null;
    833778            return;
    834779        }
  • trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetCSSStyleDeclarationSection.js

    r242622 r243264  
    175175            this._discardSelectorChange();
    176176        else {
     177            this.dispatchEventToListeners(WI.SpreadsheetCSSStyleDeclarationSection.Event.SelectorWillChange);
    177178            this._style.ownerRule.singleFireEventListener(WI.CSSRule.Event.SelectorChanged, this._renderSelector, this);
    178179            this._style.ownerRule.selectorText = selectorText;
     
    303304            var selectors = this._style.ownerRule.selectors;
    304305            var matchedSelectorIndices = this._style.ownerRule.matchedSelectorIndices;
    305             var alwaysMatch = !matchedSelectorIndices.length;
    306306            if (selectors.length) {
    307                 let hasMatchingPseudoElementSelector = false;
    308307                for (let i = 0; i < selectors.length; ++i) {
    309                     appendSelector(selectors[i], alwaysMatch || matchedSelectorIndices.includes(i));
     308                    appendSelector(selectors[i], matchedSelectorIndices.includes(i));
    310309                    if (i < selectors.length - 1)
    311310                        this._selectorElement.append(", ");
    312 
    313                     if (matchedSelectorIndices.includes(i) && selectors[i].isPseudoElementSelector())
    314                         hasMatchingPseudoElementSelector = true;
    315311                }
    316                 this._element.classList.toggle("pseudo-element-selector", hasMatchingPseudoElementSelector);
    317312            } else
    318313                appendSelectorTextKnownToMatch(this._style.ownerRule.selectorText);
     
    545540WI.SpreadsheetCSSStyleDeclarationSection.Event = {
    546541    FilterApplied: "spreadsheet-css-style-declaration-section-filter-applied",
     542    SelectorWillChange: "spreadsheet-css-style-declaration-section-selector-will-change",
    547543};
    548544
  • trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetRulesStyleDetailsPanel.js

    r242939 r243264  
    215215        this._shouldRefreshSubviews = false;
    216216
    217         this.removeAllSubviews();
     217        let oldSections = this._sections.slice();
     218        let preservedSections = oldSections.filter((section) => {
     219            if (section[SpreadsheetRulesStyleDetailsPanel.SectionShowingForNodeSymbol] !== this.nodeStyles.node) {
     220                section[SpreadsheetRulesStyleDetailsPanel.SectionShowingForNodeSymbol] = null;
     221                section[SpreadsheetRulesStyleDetailsPanel.SectionIndexSymbol] = -1;
     222            }
     223            return section[SpreadsheetRulesStyleDetailsPanel.SectionShowingForNodeSymbol];
     224        });
     225
     226        if (preservedSections.length) {
     227            for (let section of oldSections) {
     228                if (!preservedSections.includes(section))
     229                    this.removeSubview(section);
     230            }
     231            for (let header of this._headerMap.values())
     232                header.remove();
     233        } else
     234            this.removeAllSubviews();
    218235
    219236        let previousStyle = null;
     237        let currentHeader = null;
    220238        this._headerMap.clear();
    221239        this._sections = [];
    222240
    223         let createHeader = (text, nodeOrPseudoId) => {
    224             let header = this.element.appendChild(document.createElement("h2"));
    225             header.classList.add("section-header");
    226             header.append(text);
     241        let addHeader = (text, nodeOrPseudoId) => {
     242            currentHeader = this.element.appendChild(document.createElement("h2"));
     243            currentHeader.classList.add("section-header");
     244            currentHeader.append(text);
    227245
    228246            if (nodeOrPseudoId) {
    229247                if (nodeOrPseudoId instanceof WI.DOMNode) {
    230                     header.append(" ", WI.linkifyNodeReference(nodeOrPseudoId, {
     248                    currentHeader.append(" ", WI.linkifyNodeReference(nodeOrPseudoId, {
    231249                        maxLength: 100,
    232250                        excludeRevealElement: true,
    233251                    }));
    234252                } else
    235                     header.append(" ", WI.CSSManager.displayNameForPseudoId(nodeOrPseudoId));
    236 
    237                 this._headerMap.set(nodeOrPseudoId, header);
     253                    currentHeader.append(" ", WI.CSSManager.displayNameForPseudoId(nodeOrPseudoId));
    238254            }
    239255        };
    240256
     257        let addSection = (section) => {
     258            if (section.style.inherited && (!previousStyle || previousStyle.node !== section.style.node))
     259                addHeader(WI.UIString("Inherited From"), section.style.node);
     260
     261            if (!section.isDescendantOf(this)) {
     262                let referenceView = this.subviews[this._sections.length];
     263                if (!referenceView || referenceView[SpreadsheetRulesStyleDetailsPanel.SectionIndexSymbol] === this._sections.length)
     264                    this.addSubview(section);
     265                else
     266                    this.insertSubviewBefore(section, referenceView);
     267            }
     268
     269            this._sections.push(section);
     270            section.needsLayout();
     271
     272            if (currentHeader)
     273                this._headerMap.set(section.style, currentHeader);
     274
     275            previousStyle = section.style;
     276        };
     277
    241278        let createSection = (style) => {
    242             let section = style[WI.SpreadsheetRulesStyleDetailsPanel.RuleSection];
     279            let section = style[SpreadsheetRulesStyleDetailsPanel.StyleSectionSymbol];
    243280            if (!section) {
    244281                section = new WI.SpreadsheetCSSStyleDeclarationSection(this, style);
    245                 style[WI.SpreadsheetRulesStyleDetailsPanel.RuleSection] = section;
    246             }
    247 
    248             section.addEventListener(WI.SpreadsheetCSSStyleDeclarationSection.Event.FilterApplied, this._handleSectionFilterApplied, this);
     282                section.addEventListener(WI.SpreadsheetCSSStyleDeclarationSection.Event.FilterApplied, this._handleSectionFilterApplied, this);
     283                section.addEventListener(WI.SpreadsheetCSSStyleDeclarationSection.Event.SelectorWillChange, this._handleSectionSelectorWillChange, this);
     284                style[SpreadsheetRulesStyleDetailsPanel.StyleSectionSymbol] = section;
     285            }
    249286
    250287            if (this._newRuleSelector === style.selectorText && style.enabledProperties.length === 0)
    251288                section.startEditingRuleSelector();
    252289
    253             this.addSubview(section);
    254             section.needsLayout();
    255             this._sections.push(section);
    256 
    257             previousStyle = style;
    258 
    259             return section;
     290            addSection(section);
     291
     292            let preservedSection = preservedSections.find((sectionToPreserve) => sectionToPreserve[SpreadsheetRulesStyleDetailsPanel.SectionIndexSymbol] === this._sections.length - 1);
     293            if (preservedSection)
     294                addSection(preservedSection);
    260295        };
    261296
    262         for (let style of this.nodeStyles.uniqueOrderedStyles) {
    263             if (style.inherited && (!previousStyle || previousStyle.node !== style.node))
    264                 createHeader(WI.UIString("Inherited From"), style.node);
    265 
     297        for (let style of this.nodeStyles.uniqueOrderedStyles)
    266298            createSection(style);
    267         }
    268299
    269300        let beforePseudoId = null;
     
    278309        }
    279310
     311
    280312        for (let [pseudoId, pseudoElementInfo] of this.nodeStyles.pseudoElements) {
    281             let nodeOrPseudoId = null;
     313            let pseudoElement = null;
    282314            if (pseudoId === beforePseudoId)
    283                 nodeOrPseudoId = this.nodeStyles.node.beforePseudoElement();
     315                pseudoElement = this.nodeStyles.node.beforePseudoElement();
    284316            else if (pseudoId === afterPseudoId)
    285                 nodeOrPseudoId = this.nodeStyles.node.afterPseudoElement();
    286             else
    287                 nodeOrPseudoId = pseudoId;
    288 
    289             createHeader(WI.UIString("Pseudo-Element"), nodeOrPseudoId);
    290 
    291             for (let style of WI.DOMNodeStyles.uniqueOrderedStyles(pseudoElementInfo.orderedStyles)) {
    292                 let section = createSection(style);
    293 
    294                 if (nodeOrPseudoId === pseudoId)
    295                     section.__pseudoId = pseudoId;
    296             }
     317                pseudoElement = this.nodeStyles.node.afterPseudoElement();
     318            addHeader(WI.UIString("Pseudo-Element"), pseudoElement || pseudoId);
     319
     320            for (let style of WI.DOMNodeStyles.uniqueOrderedStyles(pseudoElementInfo.orderedStyles))
     321                createSection(style);
    297322        }
    298323
     
    321346        this.element.classList.remove("filter-non-matching");
    322347
    323         let header = this._headerMap.get(event.target.__pseudoId || event.target.style.node);
     348        let header = this._headerMap.get(event.target.style);
    324349        if (header)
    325350            header.classList.remove(WI.GeneralStyleDetailsSidebarPanel.NoFilterMatchInSectionClassName);
     
    336361        this.nodeStyles.addRule(this._newRuleSelector, text, stylesheetId);
    337362    }
     363
     364    _handleSectionSelectorWillChange(event)
     365    {
     366        let section = event.target;
     367        section[SpreadsheetRulesStyleDetailsPanel.SectionShowingForNodeSymbol] = this.nodeStyles.node;
     368        section[SpreadsheetRulesStyleDetailsPanel.SectionIndexSymbol] = this._sections.indexOf(section);
     369        console.assert(section[SpreadsheetRulesStyleDetailsPanel.SectionIndexSymbol] >= 0);
     370    }
    338371};
    339372
    340 WI.SpreadsheetRulesStyleDetailsPanel.RuleSection = Symbol("rule-section");
     373WI.SpreadsheetRulesStyleDetailsPanel.StyleSectionSymbol = Symbol("style-section");
     374WI.SpreadsheetRulesStyleDetailsPanel.SectionShowingForNodeSymbol = Symbol("style-showing-for-node");
     375WI.SpreadsheetRulesStyleDetailsPanel.SectionIndexSymbol = Symbol("style-index");
Note: See TracChangeset for help on using the changeset viewer.