WebKit Bugzilla
Attachment 360915 Details for
Bug 178489
: Web Inspector: Styles Redesign: Editing selector should not hide the rule
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
Patch
bug-178489-20190201154330.patch (text/plain), 46.85 KB, created by
Devin Rousso
on 2019-02-01 15:43:30 PST
(
hide
)
Description:
Patch
Filename:
MIME Type:
Creator:
Devin Rousso
Created:
2019-02-01 15:43:30 PST
Size:
46.85 KB
patch
obsolete
>diff --git a/Source/WebInspectorUI/ChangeLog b/Source/WebInspectorUI/ChangeLog >index b57849c5028c64c56edb6907667b811a4d36f323..411593538c9a34815661c6451904568ab55a221c 100644 >--- a/Source/WebInspectorUI/ChangeLog >+++ b/Source/WebInspectorUI/ChangeLog >@@ -1,3 +1,66 @@ >+2019-02-01 Devin Rousso <drousso@apple.com> >+ >+ 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 NOBODY (OOPS!). >+ >+ 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/CSSRule.js: >+ (WI.CSSRule.prototype.update): >+ (WI.CSSRule.prototype._selectorResolved): >+ Drive-by: remove unused event. >+ >+ * 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.fetchedComputedStyle): >+ (WI.DOMNodeStyles.prototype._parseStyleDeclarationPayload): >+ (WI.DOMNodeStyles.prototype._parseRulePayload): >+ (WI.DOMNodeStyles.prototype._styleSheetContentDidChange): >+ (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/Base/MultiMap.js: Added. >+ (MultiMap): >+ (MultiMap.prototype.get size): >+ (MultiMap.prototype.has): >+ (MultiMap.prototype.get): >+ (MultiMap.prototype.add): >+ (MultiMap.prototype.delete): >+ (MultiMap.prototype.clear): >+ (MultiMap.prototype.[Symbol.iterator]): >+ (MultiMap.prototype.copy): >+ (MultiMap.prototype.toJSON): >+ Data structure for holding a semi-ordered (key insertion is ordered separately from value >+ insertion) map of sets. >+ > 2019-01-31 Joseph Pecoraro <pecoraro@apple.com> > > Web Inspector: Timeline time range selection sometimes shows 0.000, should be just 0 >diff --git a/Source/WebInspectorUI/.eslintrc b/Source/WebInspectorUI/.eslintrc >index a8c04ddc0627ac05842fd2b00d83fd5631bda40a..9f5eb7b12f96fd0942e458e5f3305c2eeda28641 100644 >--- a/Source/WebInspectorUI/.eslintrc >+++ b/Source/WebInspectorUI/.eslintrc >@@ -75,6 +75,7 @@ > "InspectorTest": true, > "LinkedList": true, > "ListMultimap": true, >+ "MultiMap": true, > "ProtocolTest": true, > "ProtocolTestHarness": true, > "SyncTestSuite": true, >diff --git a/Source/WebInspectorUI/UserInterface/Base/MultiMap.js b/Source/WebInspectorUI/UserInterface/Base/MultiMap.js >new file mode 100644 >index 0000000000000000000000000000000000000000..04b64a569aee1e9f2a8073bb7a8af48991b9ed2d >--- /dev/null >+++ b/Source/WebInspectorUI/UserInterface/Base/MultiMap.js >@@ -0,0 +1,105 @@ >+/* >+ * Copyright (C) 2018 Apple Inc. All rights reserved. >+ * >+ * Redistribution and use in source and binary forms, with or without >+ * modification, are permitted provided that the following conditions >+ * are met: >+ * 1. Redistributions of source code must retain the above copyright >+ * notice, this list of conditions and the following disclaimer. >+ * 2. Redistributions in binary form must reproduce the above copyright >+ * notice, this list of conditions and the following disclaimer in the >+ * documentation and/or other materials provided with the distribution. >+ * >+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' >+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, >+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR >+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS >+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR >+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF >+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS >+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN >+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) >+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF >+ * THE POSSIBILITY OF SUCH DAMAGE. >+ */ >+ >+class MultiMap >+{ >+ constructor(items = []) >+ { >+ this._map = new Map; >+ >+ for (let [key, values] of items) { >+ for (let value of values) >+ this.add(key, value); >+ } >+ } >+ >+ // Public >+ >+ get size() >+ { >+ return this._map.size; >+ } >+ >+ has(key, value) >+ { >+ let valueSet = this._map.get(key); >+ if (!valueSet) >+ return false; >+ return value === undefined || valueSet.has(value); >+ } >+ >+ get(key) >+ { >+ return this._map.get(key) || null; >+ } >+ >+ add(key, value) >+ { >+ let valueSet = this._map.get(key); >+ if (!valueSet) { >+ valueSet = new Set; >+ this._map.set(key, valueSet); >+ } >+ >+ valueSet.add(value); >+ >+ return this; >+ } >+ >+ delete(key, value) >+ { >+ let valueSet = this._map.get(key); >+ if (!valueSet) >+ return false; >+ >+ let deleted = valueSet.delete(value); >+ >+ // Allow an entire key to be removed by not passing a value. >+ if (arguments.length === 1 || !valueSet.size) >+ deleted = this._map.delete(key); >+ >+ return deleted; >+ } >+ >+ clear() >+ { >+ this._map.clear(); >+ } >+ >+ [Symbol.iterator]() >+ { >+ return this._map[Symbol.iterator](); >+ } >+ >+ copy() >+ { >+ return new MultiMap(this.toJSON()); >+ } >+ >+ toJSON() >+ { >+ return Array.from(this).map(([key, set]) => [key, Array.from(set)]); >+ } >+} >diff --git a/Source/WebInspectorUI/UserInterface/Main.html b/Source/WebInspectorUI/UserInterface/Main.html >index c97d00d2bbf2b3ef1894dac3e5c7a64f837922d3..ec8b5cbfbe51bfb9f35ae2985b762a37ce169f35 100644 >--- a/Source/WebInspectorUI/UserInterface/Main.html >+++ b/Source/WebInspectorUI/UserInterface/Main.html >@@ -278,6 +278,7 @@ > <script src="Base/IndexSet.js"></script> > <script src="Base/LinkedList.js"></script> > <script src="Base/ListMultimap.js"></script> >+ <script src="Base/MultiMap.js"></script> > <script src="Base/Object.js"></script> > > <script src="Base/DOMUtilities.js"></script> >diff --git a/Source/WebInspectorUI/UserInterface/Models/CSSRule.js b/Source/WebInspectorUI/UserInterface/Models/CSSRule.js >index 375845b8df6a6ccc029b1d086788d25fcba22ba7..733f7ebeab28dab9deb6b1c03ce0447188929f77 100644 >--- a/Source/WebInspectorUI/UserInterface/Models/CSSRule.js >+++ b/Source/WebInspectorUI/UserInterface/Models/CSSRule.js >@@ -61,7 +61,7 @@ WI.CSSRule = class CSSRule extends WI.Object > return !!this._id && (this._type === WI.CSSStyleSheet.Type.Author || this._type === WI.CSSStyleSheet.Type.Inspector); > } > >- update(sourceCodeLocation, selectorText, selectors, matchedSelectorIndices, style, mediaList, dontFireEvents) >+ update(sourceCodeLocation, selectorText, selectors, matchedSelectorIndices, style, mediaList) > { > sourceCodeLocation = sourceCodeLocation || null; > selectorText = selectorText || ""; >@@ -70,14 +70,6 @@ WI.CSSRule = class CSSRule extends WI.Object > style = style || null; > mediaList = mediaList || []; > >- var changed = false; >- if (!dontFireEvents) { >- changed = this._selectorText !== selectorText || !Array.shallowEqual(this._selectors, selectors) || >- !Array.shallowEqual(this._matchedSelectorIndices, matchedSelectorIndices) || this._style !== style || >- !!this._sourceCodeLocation !== !!sourceCodeLocation || this._mediaList.length !== mediaList.length; >- // FIXME: Look for differences in the media list arrays. >- } >- > if (this._style) > this._style.ownerRule = null; > >@@ -90,9 +82,6 @@ WI.CSSRule = class CSSRule extends WI.Object > > if (this._style) > this._style.ownerRule = this; >- >- if (changed) >- this.dispatchEventToListeners(WI.CSSRule.Event.Changed); > } > > get type() >@@ -189,11 +178,36 @@ WI.CSSRule = class CSSRule extends WI.Object > > _selectorResolved(rulePayload) > { >+ if (rulePayload) { >+ let selectorText = rulePayload.selectorList.text; >+ if (selectorText !== this._selectorText) { >+ let selectors = WI.DOMNodeStyles.parseSelectorListPayload(rulePayload.selectorList); >+ >+ let sourceCodeLocation = null; >+ let sourceRange = rulePayload.selectorList.range; >+ if (sourceRange) { >+ sourceCodeLocation = WI.DOMNodeStyles.createSourceCodeLocation(rulePayload.sourceURL, { >+ line: sourceRange.startLine, >+ column: sourceRange.startColumn, >+ documentNode: this._nodeStyles.node.ownerDocument, >+ }); >+ } >+ >+ if (this._ownerStyleSheet) { >+ if (!sourceCodeLocation && this._ownerStyleSheet.isInspectorStyleSheet()) >+ sourceCodeLocation = this._ownerStyleSheet.createSourceCodeLocation(sourceRange.startLine, sourceRange.startColumn); >+ >+ sourceCodeLocation = this._ownerStyleSheet.offsetSourceCodeLocation(sourceCodeLocation); >+ } >+ >+ this.update(sourceCodeLocation, selectorText, selectors, [], this._style, this._mediaList); >+ } >+ } >+ > this.dispatchEventToListeners(WI.CSSRule.Event.SelectorChanged, {valid: !!rulePayload}); > } > }; > > WI.CSSRule.Event = { >- Changed: "css-rule-changed", > SelectorChanged: "css-rule-invalid-selector" > }; >diff --git a/Source/WebInspectorUI/UserInterface/Models/DOMNodeStyles.js b/Source/WebInspectorUI/UserInterface/Models/DOMNodeStyles.js >index 16cf2ff3f2878130a8237399dab55d5a034a3b56..c6c0dc96e4868759c694cc281e4594b861b5468e 100644 >--- a/Source/WebInspectorUI/UserInterface/Models/DOMNodeStyles.js >+++ b/Source/WebInspectorUI/UserInterface/Models/DOMNodeStyles.js >@@ -32,8 +32,8 @@ WI.DOMNodeStyles = class DOMNodeStyles extends WI.Object > console.assert(node); > this._node = node || null; > >- this._rulesMap = {}; >- this._styleDeclarationsMap = {}; >+ this._rulesMap = new Map; >+ this._stylesMap = new MultiMap; > > this._matchedRules = []; > this._inheritedRules = []; >@@ -49,6 +49,53 @@ WI.DOMNodeStyles = class DOMNodeStyles extends WI.Object > this.refresh(); > } > >+ // Static >+ >+ static parseSelectorListPayload(selectorList) >+ { >+ let selectors = selectorList.selectors; >+ if (!selectors.length) >+ return []; >+ >+ // COMPATIBILITY (iOS 8): The selectorList payload was an array of selector text strings. >+ // Now they are CSSSelector objects with multiple properties. >+ if (typeof selectors[0] === "string") { >+ return selectors.map(function(selectorText) { >+ return new WI.CSSSelector(selectorText); >+ }); >+ } >+ >+ return selectors.map(function(selectorPayload) { >+ return new WI.CSSSelector(selectorPayload.text, selectorPayload.specificity, selectorPayload.dynamic); >+ }); >+ } >+ >+ static createSourceCodeLocation(sourceURL, {line, column, documentNode} = {}) >+ { >+ if (!sourceURL) >+ return null; >+ >+ var sourceCode; >+ >+ // Try to use the node to find the frame which has the correct resource first. >+ if (documentNode) { >+ var mainResource = WI.networkManager.resourceForURL(documentNode.documentURL); >+ if (mainResource) { >+ var parentFrame = mainResource.parentFrame; >+ sourceCode = parentFrame.resourceForURL(sourceURL); >+ } >+ } >+ >+ // If that didn't find the resource, then search all frames. >+ if (!sourceCode) >+ sourceCode = WI.networkManager.resourceForURL(sourceURL); >+ >+ if (!sourceCode) >+ return null; >+ >+ return sourceCode.createSourceCodeLocation(line || 0, column || 0); >+ } >+ > // Public > > get node() >@@ -77,6 +124,8 @@ WI.DOMNodeStyles = class DOMNodeStyles extends WI.Object > > this._needsRefresh = false; > >+ let previousStylesMap = this._stylesMap.copy(); >+ > let fetchedMatchedStylesPromise = new WI.WrappedPromise; > let fetchedInlineStylesPromise = new WI.WrappedPromise; > let fetchedComputedStylesPromise = new WI.WrappedPromise; >@@ -115,14 +164,6 @@ WI.DOMNodeStyles = class DOMNodeStyles extends WI.Object > pseudoElementRulesPayload = pseudoElementRulesPayload || []; > inheritedRulesPayload = inheritedRulesPayload || []; > >- // Move the current maps to previous. >- this._previousRulesMap = this._rulesMap; >- this._previousStyleDeclarationsMap = this._styleDeclarationsMap; >- >- // Clear the current maps. >- this._rulesMap = {}; >- this._styleDeclarationsMap = {}; >- > this._matchedRules = parseRuleMatchArrayPayload.call(this, matchedRulesPayload, this._node); > > this._pseudoElements = {}; >@@ -183,18 +224,15 @@ WI.DOMNodeStyles = class DOMNodeStyles extends WI.Object > this._computedStyle = new WI.CSSStyleDeclaration(this, null, null, WI.CSSStyleDeclaration.Type.Computed, this._node, false, null, properties); > > let significantChange = false; >- for (let key in this._styleDeclarationsMap) { >- // Check if the same key exists in the previous map and has the same style objects. >- if (key in this._previousStyleDeclarationsMap) { >- if (Array.shallowEqual(this._styleDeclarationsMap[key], this._previousStyleDeclarationsMap[key])) >- continue; >- >+ for (let [key, styles] of this._stylesMap) { >+ let previousStyles = previousStylesMap.get(key); >+ if (previousStyles) { > // Some styles have selectors such that they will match with the DOM node twice (for example "::before, ::after"). > // In this case a second style for a second matching may be generated and added which will cause the shallowEqual > // to not return true, so in this case we just want to ensure that all the current styles existed previously. > let styleFound = false; >- for (let style of this._styleDeclarationsMap[key]) { >- if (this._previousStyleDeclarationsMap[key].includes(style)) { >+ for (let style of styles) { >+ if (previousStyles.has(style)) { > styleFound = true; > break; > } >@@ -206,8 +244,7 @@ WI.DOMNodeStyles = class DOMNodeStyles extends WI.Object > > if (!this._includeUserAgentRulesOnNextRefresh) { > // We can assume all the styles with the same key are from the same stylesheet and rule, so we only check the first. >- let firstStyle = this._styleDeclarationsMap[key][0]; >- if (firstStyle && firstStyle.ownerRule && firstStyle.ownerRule.type === WI.CSSStyleSheet.Type.UserAgent) { >+ if (styles[0] && styles[0].ownerRule && styles[0].ownerRule.type === WI.CSSStyleSheet.Type.UserAgent) { > // User Agent styles get different identifiers after some edits. This would cause us to fire a significant refreshed > // event more than it is helpful. And since the user agent stylesheet is static it shouldn't match differently > // between refreshes for the same node. This issue is tracked by: https://webkit.org/b/110055 >@@ -221,15 +258,14 @@ WI.DOMNodeStyles = class DOMNodeStyles extends WI.Object > } > > if (!significantChange) { >- for (var key in this._previousStyleDeclarationsMap) { >+ for (let [key, previousStyles] of previousStylesMap) { > // Check if the same key exists in current map. If it does exist it was already checked for equality above. >- if (key in this._styleDeclarationsMap) >+ if (this._stylesMap.has(key)) > continue; > > if (!this._includeUserAgentRulesOnNextRefresh) { > // See above for why we skip user agent style rules. >- var firstStyle = this._previousStyleDeclarationsMap[key][0]; >- if (firstStyle && firstStyle.ownerRule && firstStyle.ownerRule.type === WI.CSSStyleSheet.Type.UserAgent) >+ if (previousStyles[0] && previousStyles[0].ownerRule && previousStyles[0].ownerRule.type === WI.CSSStyleSheet.Type.UserAgent) > continue; > } > >@@ -239,11 +275,7 @@ WI.DOMNodeStyles = class DOMNodeStyles extends WI.Object > } > } > >- delete this._includeUserAgentRulesOnNextRefresh; >- >- // Delete the previous maps now that any reused rules and style have been moved over. >- delete this._previousRulesMap; >- delete this._previousStyleDeclarationsMap; >+ this._includeUserAgentRulesOnNextRefresh = false; > > this.dispatchEventToListeners(WI.DOMNodeStyles.Event.Refreshed, {significantChange}); > >@@ -468,32 +500,6 @@ WI.DOMNodeStyles = class DOMNodeStyles extends WI.Object > > // Private > >- _createSourceCodeLocation(sourceURL, sourceLine, sourceColumn) >- { >- if (!sourceURL) >- return null; >- >- var sourceCode; >- >- // Try to use the node to find the frame which has the correct resource first. >- if (this._node.ownerDocument) { >- var mainResource = WI.networkManager.resourceForURL(this._node.ownerDocument.documentURL); >- if (mainResource) { >- var parentFrame = mainResource.parentFrame; >- sourceCode = parentFrame.resourceForURL(sourceURL); >- } >- } >- >- // If that didn't find the resource, then search all frames. >- if (!sourceCode) >- sourceCode = WI.networkManager.resourceForURL(sourceURL); >- >- if (!sourceCode) >- return null; >- >- return sourceCode.createSourceCodeLocation(sourceLine || 0, sourceColumn || 0); >- } >- > _parseSourceRangePayload(payload) > { > if (!payload) >@@ -564,7 +570,7 @@ WI.DOMNodeStyles = class DOMNodeStyles extends WI.Object > return new WI.CSSProperty(index, text, name, value, priority, enabled, overridden, implicit, anonymous, valid, styleSheetTextRange); > } > >- _parseStyleDeclarationPayload(payload, node, inherited, type, rule, updateAllStyles) >+ _parseStyleDeclarationPayload(payload, node, inherited, type, rule) > { > if (!payload) > return null; >@@ -578,56 +584,20 @@ WI.DOMNodeStyles = class DOMNodeStyles extends WI.Object > if (type === WI.CSSStyleDeclaration.Type.Attribute) > mapKey = node.id + ":attribute"; > >- var styleDeclaration = rule ? rule.style : null; >- var styleDeclarations = []; >+ let style = rule ? rule.style : null; > >- // Look for existing styles in the previous map if there is one, otherwise use the current map. >- var previousStyleDeclarationsMap = this._previousStyleDeclarationsMap || this._styleDeclarationsMap; >- if (mapKey && mapKey in previousStyleDeclarationsMap) { >- styleDeclarations = previousStyleDeclarationsMap[mapKey]; >- >- // If we need to update all styles, then stop here and call _parseStyleDeclarationPayload for each style. >- // We need to parse multiple times so we reuse the right properties from each style. >- if (updateAllStyles && styleDeclarations.length) { >- for (var i = 0; i < styleDeclarations.length; ++i) { >- var styleDeclaration = styleDeclarations[i]; >- this._parseStyleDeclarationPayload(payload, styleDeclaration.node, styleDeclaration.inherited, styleDeclaration.type, styleDeclaration.ownerRule); >+ let existingStyles = this._stylesMap.get(mapKey); >+ if (existingStyles && !style) { >+ for (let existingStyle of existingStyles) { >+ if (existingStyle.node === node && existingStyle.inherited === inherited) { >+ style = existingStyle; >+ break; > } >- >- return null; >- } >- >- if (!styleDeclaration) { >- var filteredStyleDeclarations = styleDeclarations.filter(function(styleDeclaration) { >- // This case only applies for styles that are not part of a rule. >- if (styleDeclaration.ownerRule) { >- console.assert(!rule); >- return false; >- } >- >- if (styleDeclaration.node !== node) >- return false; >- >- if (styleDeclaration.inherited !== inherited) >- return false; >- >- return true; >- }); >- >- console.assert(filteredStyleDeclarations.length <= 1); >- styleDeclaration = filteredStyleDeclarations[0] || null; > } > } > >- if (previousStyleDeclarationsMap !== this._styleDeclarationsMap) { >- // If the previous and current maps differ then make sure the found styleDeclaration is added to the current map. >- styleDeclarations = mapKey && mapKey in this._styleDeclarationsMap ? this._styleDeclarationsMap[mapKey] : []; >- >- if (styleDeclaration && !styleDeclarations.includes(styleDeclaration)) { >- styleDeclarations.push(styleDeclaration); >- this._styleDeclarationsMap[mapKey] = styleDeclarations; >- } >- } >+ if (style) >+ this._stylesMap.add(mapKey, style); > > var shorthands = {}; > for (var i = 0; payload.shorthandEntries && i < payload.shorthandEntries.length; ++i) { >@@ -646,15 +616,15 @@ WI.DOMNodeStyles = class DOMNodeStyles extends WI.Object > if (inherited && WI.CSSProperty.isInheritedPropertyName(propertyPayload.name)) > ++inheritedPropertyCount; > >- var property = this._parseStylePropertyPayload(propertyPayload, i, styleDeclaration, text); >+ let property = this._parseStylePropertyPayload(propertyPayload, i, style, text); > properties.push(property); > } > > var styleSheetTextRange = this._parseSourceRangePayload(payload.range); > >- if (styleDeclaration) { >- styleDeclaration.update(text, properties, styleSheetTextRange); >- return styleDeclaration; >+ if (style) { >+ style.update(text, properties, styleSheetTextRange); >+ return style; > } > > var styleSheet = id ? WI.cssManager.styleSheetForIdentifier(id.styleSheetId) : null; >@@ -667,33 +637,12 @@ WI.DOMNodeStyles = class DOMNodeStyles extends WI.Object > if (inherited && !inheritedPropertyCount) > return null; > >- styleDeclaration = new WI.CSSStyleDeclaration(this, styleSheet, id, type, node, inherited, text, properties, styleSheetTextRange); >+ style = new WI.CSSStyleDeclaration(this, styleSheet, id, type, node, inherited, text, properties, styleSheetTextRange); > >- if (mapKey) { >- styleDeclarations.push(styleDeclaration); >- this._styleDeclarationsMap[mapKey] = styleDeclarations; >- } >+ if (mapKey) >+ this._stylesMap.add(mapKey, style); > >- return styleDeclaration; >- } >- >- _parseSelectorListPayload(selectorList) >- { >- var selectors = selectorList.selectors; >- if (!selectors.length) >- return []; >- >- // COMPATIBILITY (iOS 8): The selectorList payload was an array of selector text strings. >- // Now they are CSSSelector objects with multiple properties. >- if (typeof selectors[0] === "string") { >- return selectors.map(function(selectorText) { >- return new WI.CSSSelector(selectorText); >- }); >- } >- >- return selectors.map(function(selectorPayload) { >- return new WI.CSSSelector(selectorPayload.text, selectorPayload.specificity, selectorPayload.dynamic); >- }); >+ return style; > } > > _parseRulePayload(payload, matchedSelectorIndices, node, inherited, ruleOccurrences) >@@ -722,18 +671,7 @@ WI.DOMNodeStyles = class DOMNodeStyles extends WI.Object > mapKey += ":" + occurrence; > } > >- var rule = null; >- >- // Look for existing rules in the previous map if there is one, otherwise use the current map. >- var previousRulesMap = this._previousRulesMap || this._rulesMap; >- if (mapKey && mapKey in previousRulesMap) { >- rule = previousRulesMap[mapKey]; >- >- if (previousRulesMap !== this._rulesMap) { >- // If the previous and current maps differ then make sure the found rule is added to the current map. >- this._rulesMap[mapKey] = rule; >- } >- } >+ let rule = this._rulesMap.get(mapKey); > > var style = this._parseStyleDeclarationPayload(payload.style, node, inherited, WI.CSSStyleDeclaration.Type.Rule, rule); > if (!style) >@@ -742,16 +680,23 @@ WI.DOMNodeStyles = class DOMNodeStyles extends WI.Object > var styleSheet = id ? WI.cssManager.styleSheetForIdentifier(id.styleSheetId) : null; > > var selectorText = payload.selectorList.text; >- var selectors = this._parseSelectorListPayload(payload.selectorList); >+ let selectors = WI.DOMNodeStyles.parseSelectorListPayload(payload.selectorList); > var type = WI.CSSManager.protocolStyleSheetOriginToEnum(payload.origin); > > var sourceCodeLocation = null; > var sourceRange = payload.selectorList.range; >- if (sourceRange) >- sourceCodeLocation = this._createSourceCodeLocation(payload.sourceURL, sourceRange.startLine, sourceRange.startColumn); >- else { >+ if (sourceRange) { >+ sourceCodeLocation = WI.DOMNodeStyles.createSourceCodeLocation(payload.sourceURL, { >+ line: sourceRange.startLine, >+ column: sourceRange.startColumn, >+ documentNode: this._node.ownerDocument, >+ }); >+ } else { > // FIXME: Is it possible for a CSSRule to have a sourceLine without its selectorList having a sourceRange? Fall back just in case. >- sourceCodeLocation = this._createSourceCodeLocation(payload.sourceURL, payload.sourceLine); >+ sourceCodeLocation = WI.DOMNodeStyles.createSourceCodeLocation(payload.sourceURL, { >+ line: payload.sourceLine, >+ documentNode: this._node.ownerDocument, >+ }); > } > > if (styleSheet) { >@@ -766,7 +711,7 @@ WI.DOMNodeStyles = class DOMNodeStyles extends WI.Object > var mediaItem = payload.media[i]; > var mediaType = WI.CSSManager.protocolMediaSourceToEnum(mediaItem.source); > var mediaText = mediaItem.text; >- var mediaSourceCodeLocation = this._createSourceCodeLocation(mediaItem.sourceURL, mediaItem.sourceLine); >+ let mediaSourceCodeLocation = WI.DOMNodeStyles.createSourceCodeLocation(mediaItem.sourceURL, {line: mediaItem.sourceLine}); > if (styleSheet) > mediaSourceCodeLocation = styleSheet.offsetSourceCodeLocation(mediaSourceCodeLocation); > >@@ -784,7 +729,7 @@ WI.DOMNodeStyles = class DOMNodeStyles extends WI.Object > rule = new WI.CSSRule(this, styleSheet, id, type, sourceCodeLocation, selectorText, selectors, matchedSelectorIndices, style, mediaList); > > if (mapKey) >- this._rulesMap[mapKey] = rule; >+ this._rulesMap.set(mapKey, rule); > > return rule; > } >@@ -804,7 +749,7 @@ WI.DOMNodeStyles = class DOMNodeStyles extends WI.Object > > // Ignore the stylesheet we know we just changed and handled above. > if (styleSheet === this._ignoreNextContentDidChangeForStyleSheet) { >- delete this._ignoreNextContentDidChangeForStyleSheet; >+ this._ignoreNextContentDidChangeForStyleSheet = null; > return; > } > >diff --git a/Source/WebInspectorUI/UserInterface/Test.html b/Source/WebInspectorUI/UserInterface/Test.html >index 06a0d84a8a330472ae04c7529fd92babd97737f5..a80e083f0fc9a5514cfbc9f585a583f650177d08 100644 >--- a/Source/WebInspectorUI/UserInterface/Test.html >+++ b/Source/WebInspectorUI/UserInterface/Test.html >@@ -41,6 +41,7 @@ > <script src="Base/IndexSet.js"></script> > <script src="Base/LinkedList.js"></script> > <script src="Base/ListMultimap.js"></script> >+ <script src="Base/MultiMap.js"></script> > <script src="Base/Object.js"></script> > > <script src="Test/TestHarness.js"></script> >diff --git a/Source/WebInspectorUI/UserInterface/Views/SpreadsheetCSSStyleDeclarationSection.js b/Source/WebInspectorUI/UserInterface/Views/SpreadsheetCSSStyleDeclarationSection.js >index 6fa142d2a27b7995a4708a6c7958a9980b95b054..e3ec3ba372bef260c5795f05e4a03ef5529cc9d2 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/SpreadsheetCSSStyleDeclarationSection.js >+++ b/Source/WebInspectorUI/UserInterface/Views/SpreadsheetCSSStyleDeclarationSection.js >@@ -167,6 +167,7 @@ WI.SpreadsheetCSSStyleDeclarationSection = class SpreadsheetCSSStyleDeclarationS > if (!selectorText || selectorText === this._style.ownerRule.selectorText) > this._discardSelectorChange(); > else { >+ this.dispatchEventToListeners(WI.SpreadsheetCSSStyleDeclarationSection.Event.SelectorWillChange); > this._style.ownerRule.singleFireEventListener(WI.CSSRule.Event.SelectorChanged, this._renderSelector, this); > this._style.ownerRule.selectorText = selectorText; > } >@@ -289,18 +290,12 @@ WI.SpreadsheetCSSStyleDeclarationSection = class SpreadsheetCSSStyleDeclarationS > > var selectors = this._style.ownerRule.selectors; > var matchedSelectorIndices = this._style.ownerRule.matchedSelectorIndices; >- var alwaysMatch = !matchedSelectorIndices.length; > if (selectors.length) { >- let hasMatchingPseudoElementSelector = false; > for (let i = 0; i < selectors.length; ++i) { >- appendSelector(selectors[i], alwaysMatch || matchedSelectorIndices.includes(i)); >+ appendSelector(selectors[i], matchedSelectorIndices.includes(i)); > if (i < selectors.length - 1) > this._selectorElement.append(", "); >- >- if (matchedSelectorIndices.includes(i) && selectors[i].isPseudoElementSelector()) >- hasMatchingPseudoElementSelector = true; > } >- this._element.classList.toggle("pseudo-element-selector", hasMatchingPseudoElementSelector); > } else > appendSelectorTextKnownToMatch(this._style.ownerRule.selectorText); > >@@ -507,6 +502,7 @@ WI.SpreadsheetCSSStyleDeclarationSection = class SpreadsheetCSSStyleDeclarationS > > WI.SpreadsheetCSSStyleDeclarationSection.Event = { > FilterApplied: "spreadsheet-css-style-declaration-section-filter-applied", >+ SelectorWillChange: "spreadsheet-css-style-declaration-section-selector-will-change", > }; > > WI.SpreadsheetCSSStyleDeclarationSection.MatchedSelectorElementStyleClassName = "matched"; >diff --git a/Source/WebInspectorUI/UserInterface/Views/SpreadsheetRulesStyleDetailsPanel.js b/Source/WebInspectorUI/UserInterface/Views/SpreadsheetRulesStyleDetailsPanel.js >index 3f99aa09132e2eab29a116cf7a13e13452e0d2cc..cb0b1f927e6393413957e36e25561c7343285e09 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/SpreadsheetRulesStyleDetailsPanel.js >+++ b/Source/WebInspectorUI/UserInterface/Views/SpreadsheetRulesStyleDetailsPanel.js >@@ -209,22 +209,40 @@ WI.SpreadsheetRulesStyleDetailsPanel = class SpreadsheetRulesStyleDetailsPanel e > > this._shouldRefreshSubviews = false; > >- this.removeAllSubviews(); >+ let currentNode = this.nodeStyles.node; >+ let oldSections = this._sections.slice(); >+ let preservedSections = oldSections.filter((section) => { >+ if (section.__showingForNode !== currentNode) { >+ delete section.__showingForNode; >+ delete section.__index; >+ return false; >+ } >+ return section.__index >= 0; >+ }); >+ >+ if (preservedSections.length) { >+ for (let section of oldSections) { >+ if (!preservedSections.includes(section)) >+ this.removeSubview(section); >+ } >+ for (let header of this._headerMap.values()) >+ header.remove(); >+ } else >+ this.removeAllSubviews(); > > let previousStyle = null; >+ let currentHeader = null; > this._headerMap.clear(); > this._sections = []; > > let createHeader = (text, node) => { >- let header = this.element.appendChild(document.createElement("h2")); >- header.classList.add("section-header"); >- header.append(text); >- header.append(" ", WI.linkifyNodeReference(node, { >+ currentHeader = this.element.appendChild(document.createElement("h2")); >+ currentHeader.classList.add("section-header"); >+ currentHeader.append(text); >+ currentHeader.append(" ", WI.linkifyNodeReference(node, { > maxLength: 100, > excludeRevealElement: true, > })); >- >- this._headerMap.set(node, header); > }; > > let createSection = (style) => { >@@ -235,30 +253,53 @@ WI.SpreadsheetRulesStyleDetailsPanel = class SpreadsheetRulesStyleDetailsPanel e > } > > section.addEventListener(WI.SpreadsheetCSSStyleDeclarationSection.Event.FilterApplied, this._handleSectionFilterApplied, this); >+ section.addEventListener(WI.SpreadsheetCSSStyleDeclarationSection.Event.SelectorWillChange, this._handleSectionSelectorWillChange, this); > > if (this._newRuleSelector === style.selectorText && style.enabledProperties.length === 0) > section.startEditingRuleSelector(); > >- this.addSubview(section); >- section.needsLayout(); >- this._sections.push(section); >+ addSection(section); > >- previousStyle = style; >+ let preservedSection = preservedSections.find((sectionToPreserve) => sectionToPreserve.__index === this._sections.length - 1); >+ if (preservedSection) >+ addSection(preservedSection); > }; > >- for (let style of this.nodeStyles.uniqueOrderedStyles) { >- if (style.inherited && (!previousStyle || previousStyle.node !== style.node)) >- createHeader(WI.UIString("Inherited From"), style.node); >+ let addSection = (section) => { >+ if (!previousStyle || previousStyle.node !== section.style.node) { >+ if (section.style.node.isPseudoElement()) >+ createHeader(WI.UIString("Pseudo Element"), section.style.node); >+ else if (section.style.inherited) >+ createHeader(WI.UIString("Inherited From"), section.style.node); >+ } > >+ if (!section.isDescendantOf(this)) { >+ let referenceView = this.subviews[this._sections.length]; >+ if (!referenceView || referenceView.__index === this._sections.length) >+ this.addSubview(section); >+ else >+ this.insertSubviewBefore(section, referenceView); >+ } >+ >+ this._sections.push(section); >+ >+ previousStyle = section.style; >+ if (currentHeader) >+ this._headerMap.set(section.style, currentHeader); >+ >+ section.needsLayout(); >+ }; >+ >+ for (let style of this.nodeStyles.uniqueOrderedStyles) > createSection(style); >- } > >- let pseudoElements = Array.from(this.nodeStyles.node.pseudoElements().values()); >+ let pseudoElements = Array.from(currentNode.pseudoElements().values()); > Promise.all(pseudoElements.map((pseudoElement) => WI.cssManager.stylesForNode(pseudoElement).refreshIfNeeded())) > .then((pseudoNodeStyles) => { >- for (let pseudoNodeStyle of pseudoNodeStyles) { >- createHeader(WI.UIString("Pseudo Element"), pseudoNodeStyle.node); >+ if (this.nodeStyles.node !== currentNode) >+ return; > >+ for (let pseudoNodeStyle of pseudoNodeStyles) { > for (let style of pseudoNodeStyle.uniqueOrderedStyles) > createSection(style); > } >@@ -288,12 +329,18 @@ WI.SpreadsheetRulesStyleDetailsPanel = class SpreadsheetRulesStyleDetailsPanel e > > this.element.classList.remove("filter-non-matching"); > >- let header = this._headerMap.get(event.target.style.node); >+ let header = this._headerMap.get(event.target.style); > if (header) > header.classList.remove(WI.GeneralStyleDetailsSidebarPanel.NoFilterMatchInSectionClassName); > } > >- // Private >+ _handleSectionSelectorWillChange(event) >+ { >+ let section = event.target; >+ section.__showingForNode = this.nodeStyles.node; >+ section.__index = this._sections.indexOf(section); >+ console.assert(section.__index >= 0); >+ } > > _addNewRule(stylesheetId) > { >diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog >index 8495db83da9758f0ddf774402a52ffe46bb8abe5..d0d9d366be58f86eb01b8ec8be9ff45cbc5f05e8 100644 >--- a/LayoutTests/ChangeLog >+++ b/LayoutTests/ChangeLog >@@ -1,3 +1,14 @@ >+2019-02-01 Devin Rousso <drousso@apple.com> >+ >+ 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 NOBODY (OOPS!). >+ >+ * inspector/unit-tests/multiMap.html: Added. >+ * inspector/unit-tests/multiMap-expected.txt: Added. >+ > 2019-02-01 Carlos Garcia Campos <cgarcia@igalia.com> > > REGRESSION(r239915): css3/font-feature-font-face-local.html failing on WPE >diff --git a/LayoutTests/inspector/unit-tests/multiMap-expected.txt b/LayoutTests/inspector/unit-tests/multiMap-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..e79e3e123a391d8edf73d3e693c266991891387a >--- /dev/null >+++ b/LayoutTests/inspector/unit-tests/multiMap-expected.txt >@@ -0,0 +1,61 @@ >+Testing all methods of MultiMap. >+ >+ >+== Running test suite: MultiMap >+-- Running test case: Instantiating Multimap >+0 >+[] >+ >+-- Running test case: Adding unique keys and values >+2 >+[["zero",["one"]],["two",["three"]]] >+ >+-- Running test case: Adding repeating keys and unique values >+1 >+[["zero",["one","two"]]] >+ >+-- Running test case: Adding unique keys and repeating values >+3 >+[["zero",["one"]],["two",["one"]],["three",["one"]]] >+ >+-- Running test case: Adding repeating keys and values >+2 >+[["zero",["one","three"]],["two",["one"]]] >+ >+-- Running test case: Deleting existing keys and values >+2 >+[[0,[1]],[2,[3]]] >+PASS: The key and the value were successfully deleted. >+1 >+[[2,[3]]] >+PASS: The key and the value were successfully deleted. >+0 >+[] >+ >+-- Running test case: Deleting non-existing keys and non-existing values >+3 >+[[0,[1]],[2,[3]],[4,[4]]] >+PASS: Nothing was removed. >+PASS: Nothing was removed. >+PASS: Nothing was removed. >+PASS: Nothing was removed. >+PASS: Nothing was removed. >+3 >+[[0,[1]],[2,[3]],[4,[4]]] >+ >+-- Running test case: Deleting values for given key >+2 >+[["opossum",["badger","raccoon"]],["raccoon",["opossum"]]] >+PASS: Nothing was removed. >+2 >+[["opossum",["badger","raccoon"]],["raccoon",["opossum"]]] >+PASS: Values were removed. >+1 >+[["raccoon",["opossum"]]] >+ >+-- Running test case: Deleting all keys and values >+1 >+[["badger",["raccoon"]]] >+0 >+[] >+ >diff --git a/LayoutTests/inspector/unit-tests/multiMap.html b/LayoutTests/inspector/unit-tests/multiMap.html >new file mode 100644 >index 0000000000000000000000000000000000000000..46f02903e9155fe54faeab129498d56a9f652c51 >--- /dev/null >+++ b/LayoutTests/inspector/unit-tests/multiMap.html >@@ -0,0 +1,188 @@ >+<!DOCTYPE html> >+<html> >+<head> >+<script src="../../http/tests/inspector/resources/inspector-test.js"></script> >+<script> >+function test() >+{ >+ let suite = InspectorTest.createSyncSuite("MultiMap"); >+ >+ suite.addTestCase({ >+ name: "Instantiating Multimap", >+ test() { >+ let multimap = new MultiMap; >+ >+ InspectorTest.log(multimap.size); >+ InspectorTest.log(multimap); >+ >+ return true; >+ }, >+ }); >+ >+ suite.addTestCase({ >+ name: "Adding unique keys and values", >+ test() { >+ let multimap = new MultiMap; >+ >+ multimap.add("zero", "one"); >+ multimap.add("two", "three"); >+ >+ InspectorTest.log(multimap.size); >+ InspectorTest.log(multimap); >+ >+ return true; >+ }, >+ }); >+ >+ suite.addTestCase({ >+ name: "Adding repeating keys and unique values", >+ test() { >+ let multimap = new MultiMap; >+ >+ multimap.add("zero", "one"); >+ multimap.add("zero", "two"); >+ >+ InspectorTest.log(multimap.size); >+ InspectorTest.log(multimap); >+ >+ return true; >+ }, >+ }); >+ >+ suite.addTestCase({ >+ name: "Adding unique keys and repeating values", >+ test() { >+ let multimap = new MultiMap; >+ >+ multimap.add("zero", "one"); >+ multimap.add("two", "one"); >+ multimap.add("three", "one"); >+ >+ InspectorTest.log(multimap.size); >+ InspectorTest.log(multimap); >+ >+ return true; >+ }, >+ }); >+ >+ suite.addTestCase({ >+ name: "Adding repeating keys and values", >+ test() { >+ let multimap = new MultiMap; >+ >+ multimap.add("zero", "one"); >+ multimap.add("two", "one"); >+ multimap.add("zero", "one"); >+ multimap.add("zero", "three"); >+ >+ InspectorTest.log(multimap.size); >+ InspectorTest.log(multimap); >+ >+ return true; >+ }, >+ }); >+ >+ suite.addTestCase({ >+ name: "Deleting existing keys and values", >+ test() { >+ let multimap = new MultiMap; >+ >+ multimap.add(0, 1); >+ multimap.add(2, 3); >+ multimap.add(2, 3); >+ >+ InspectorTest.log(multimap.size); >+ InspectorTest.log(multimap); >+ >+ InspectorTest.expectThat(multimap.delete(0, 1), "The key and the value were successfully deleted."); >+ >+ InspectorTest.log(multimap.size); >+ InspectorTest.log(multimap); >+ >+ InspectorTest.expectThat(multimap.delete(2, 3), "The key and the value were successfully deleted."); >+ >+ InspectorTest.log(multimap.size); >+ InspectorTest.log(multimap); >+ >+ return true; >+ }, >+ }); >+ >+ suite.addTestCase({ >+ name: "Deleting non-existing keys and non-existing values", >+ test() { >+ let multimap = new MultiMap; >+ >+ multimap.add(0, 1); >+ multimap.add(2, 3); >+ multimap.add(4, 4); >+ >+ InspectorTest.log(multimap.size); >+ InspectorTest.log(multimap); >+ >+ InspectorTest.expectThat(!multimap.delete(0, 3), "Nothing was removed."); >+ InspectorTest.expectThat(!multimap.delete(2, 1), "Nothing was removed."); >+ InspectorTest.expectThat(!multimap.delete(3, 0), "Nothing was removed."); >+ InspectorTest.expectThat(!multimap.delete(4, 3), "Nothing was removed."); >+ InspectorTest.expectThat(!multimap.delete(0, 4), "Nothing was removed."); >+ >+ InspectorTest.log(multimap.size); >+ InspectorTest.log(multimap); >+ >+ return true; >+ }, >+ }); >+ >+ suite.addTestCase({ >+ name: "Deleting values for given key", >+ test() { >+ let multimap = new MultiMap; >+ >+ multimap.add("opossum", "badger"); >+ multimap.add("opossum", "raccoon"); >+ multimap.add("raccoon", "opossum"); >+ >+ InspectorTest.log(multimap.size); >+ InspectorTest.log(multimap); >+ >+ InspectorTest.expectThat(!multimap.delete("badger"), "Nothing was removed."); >+ >+ InspectorTest.log(multimap.size); >+ InspectorTest.log(multimap); >+ >+ InspectorTest.expectThat(multimap.delete("opossum"), "Values were removed."); >+ >+ InspectorTest.log(multimap.size); >+ InspectorTest.log(multimap); >+ >+ return true; >+ }, >+ }); >+ >+ suite.addTestCase({ >+ name: "Deleting all keys and values", >+ test() { >+ let multimap = new MultiMap; >+ >+ multimap.add("badger", "raccoon"); >+ >+ InspectorTest.log(multimap.size); >+ InspectorTest.log(multimap); >+ >+ multimap.clear(); >+ >+ InspectorTest.log(multimap.size); >+ InspectorTest.log(multimap); >+ >+ return true; >+ }, >+ }); >+ >+ suite.runTestCasesAndFinish(); >+} >+</script> >+</head> >+<body onload="runTest()"> >+ <p>Testing all methods of MultiMap.</p> >+</body> >+</html>
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 178489
:
329468
|
335759
|
335764
|
340634
|
340679
|
356749
|
357995
|
358002
|
358003
|
360915
|
365415
|
365434
|
365436