Web Inspector: SourceCode.requestContent should return a promise
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Models / DOMNodeStyles.js
1 /*
2  * Copyright (C) 2013 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WebInspector.DOMNodeStyles = function(node)
27 {
28     WebInspector.Object.call(this);
29
30     console.assert(node);
31     this._node = node || null;
32
33     this._rulesMap = {};
34     this._styleDeclarationsMap = {};
35
36     this._matchedRules = [];
37     this._inheritedRules = [];
38     this._pseudoElements = {};
39     this._inlineStyle = null;
40     this._attributesStyle = null;
41     this._computedStyle = null;
42     this._orderedStyles = [];
43     this._stylesNeedingTextCommited = [];
44
45     this._propertyNameToEffectivePropertyMap = {};
46
47     this.refresh();
48 };
49
50 WebInspector.Object.addConstructorFunctions(WebInspector.DOMNodeStyles);
51
52 WebInspector.DOMNodeStyles.Event = {
53     NeedsRefresh: "dom-node-styles-needs-refresh",
54     Refreshed: "dom-node-styles-refreshed"
55 };
56
57 WebInspector.DOMNodeStyles.prototype = {
58     constructor: WebInspector.DOMNodeStyles,
59
60     // Public
61
62     get node()
63     {
64         return this._node;
65     },
66
67     get needsRefresh()
68     {
69         return this._refreshPending || this._needsRefresh;
70     },
71
72     refreshIfNeeded: function()
73     {
74         if (!this._needsRefresh)
75             return;
76         this.refresh();
77     },
78
79     refresh: function()
80     {
81         if (this._refreshPending)
82             return;
83
84         this._needsRefresh = false;
85         this._refreshPending = true;
86
87         function parseRuleMatchArrayPayload(matchArray, node, inherited)
88         {
89             var result = [];
90
91             var ruleOccurrences = {};
92
93             // Iterate in reverse order to match the cascade order.
94             for (var i = matchArray.length - 1; i >= 0; --i) {
95                 // COMPATIBILITY (iOS 6): This was just an array of rules, now it is an array of matches that have
96                 // a 'rule' property. Support both here. And 'matchingSelectors' does not exist on iOS 6.
97                 var matchedSelectorIndices = matchArray[i].matchingSelectors || [];
98                 var rule = this._parseRulePayload(matchArray[i].rule || matchArray[i], matchedSelectorIndices, node, inherited, ruleOccurrences);
99                 if (!rule)
100                     continue;
101                 result.push(rule);
102             }
103
104             return result;
105         }
106
107         function fetchedMatchedStyles(error, matchedRulesPayload, pseudoElementRulesPayload, inheritedRulesPayload)
108         {
109             matchedRulesPayload = matchedRulesPayload || [];
110             pseudoElementRulesPayload = pseudoElementRulesPayload || [];
111             inheritedRulesPayload = inheritedRulesPayload || [];
112
113             // Move the current maps to previous.
114             this._previousRulesMap = this._rulesMap;
115             this._previousStyleDeclarationsMap = this._styleDeclarationsMap;
116
117             // Clear the current maps.
118             this._rulesMap = {};
119             this._styleDeclarationsMap = {};
120
121             this._matchedRules = parseRuleMatchArrayPayload.call(this, matchedRulesPayload, this._node);
122
123             this._pseudoElements = {};
124             for (var i = 0; i < pseudoElementRulesPayload.length; ++i) {
125                 var pseudoElementRulePayload = pseudoElementRulesPayload[i];
126
127                 // COMPATIBILITY (iOS 6): The entry payload had a 'rules' property, now it has a 'matches' property. Support both here.
128                 var pseudoElementRules = parseRuleMatchArrayPayload.call(this, pseudoElementRulePayload.matches || pseudoElementRulePayload.rules, this._node);
129                 this._pseudoElements[pseudoElementRulePayload.pseudoId] = {matchedRules: pseudoElementRules};
130             }
131
132             this._inheritedRules = [];
133
134             var i = 0;
135             var currentNode = this._node.parentNode;
136             while (currentNode && i < inheritedRulesPayload.length) {
137                 var inheritedRulePayload = inheritedRulesPayload[i];
138
139                 var inheritedRuleInfo = {node: currentNode};
140                 inheritedRuleInfo.inlineStyle = inheritedRulePayload.inlineStyle ? this._parseStyleDeclarationPayload(inheritedRulePayload.inlineStyle, currentNode, true, WebInspector.CSSStyleDeclaration.Type.Inline) : null;
141                 inheritedRuleInfo.matchedRules = inheritedRulePayload.matchedCSSRules ? parseRuleMatchArrayPayload.call(this, inheritedRulePayload.matchedCSSRules, currentNode, true) : [];
142
143                 if (inheritedRuleInfo.inlineStyle || inheritedRuleInfo.matchedRules.length)
144                     this._inheritedRules.push(inheritedRuleInfo);
145
146                 currentNode = currentNode.parentNode;
147                 ++i;
148             }
149         }
150
151         function fetchedInlineStyles(error, inlineStylePayload, attributesStylePayload)
152         {
153             this._inlineStyle = inlineStylePayload ? this._parseStyleDeclarationPayload(inlineStylePayload, this._node, false, WebInspector.CSSStyleDeclaration.Type.Inline) : null;
154             this._attributesStyle = attributesStylePayload ? this._parseStyleDeclarationPayload(attributesStylePayload, this._node, false, WebInspector.CSSStyleDeclaration.Type.Attribute) : null;
155
156             this._updateStyleCascade();
157         }
158
159         function fetchedComputedStyle(error, computedPropertiesPayload)
160         {
161             var properties = [];
162             for (var i = 0; computedPropertiesPayload && i < computedPropertiesPayload.length; ++i) {
163                 var propertyPayload = computedPropertiesPayload[i];
164
165                 var canonicalName = WebInspector.cssStyleManager.canonicalNameForPropertyName(propertyPayload.name);
166                 propertyPayload.implicit = !this._propertyNameToEffectivePropertyMap[canonicalName];
167
168                 var property = this._parseStylePropertyPayload(propertyPayload, NaN, this._computedStyle);
169                 properties.push(property);
170             }
171
172             if (this._computedStyle)
173                 this._computedStyle.update(null, properties);
174             else
175                 this._computedStyle = new WebInspector.CSSStyleDeclaration(this, null, null, WebInspector.CSSStyleDeclaration.Type.Computed, this._node, false, null, properties);
176
177             this._refreshPending = false;
178
179             var significantChange = this._previousSignificantChange || false;
180             if (!significantChange) {
181                 for (var key in this._styleDeclarationsMap) {
182                     // Check if the same key exists in the previous map and has the same style objects.
183                     if (key in this._previousStyleDeclarationsMap && Object.shallowEqual(this._styleDeclarationsMap[key], this._previousStyleDeclarationsMap[key]))
184                         continue;
185
186                     if (!this._includeUserAgentRulesOnNextRefresh) {
187                         // We can assume all the styles with the same key are from the same stylesheet and rule, so we only check the first.
188                         var firstStyle = this._styleDeclarationsMap[key][0];
189                         if (firstStyle && firstStyle.ownerRule && firstStyle.ownerRule.type === WebInspector.CSSRule.Type.UserAgent) {
190                             // User Agent styles get different identifiers after some edits. This would cause us to fire a significant refreshed
191                             // event more than it is helpful. And since the user agent stylesheet is static it shouldn't match differently
192                             // between refreshes for the same node. This issue is tracked by: https://webkit.org/b/110055
193                             continue;
194                         }
195                     }
196
197                     // This key is new or has different style objects than before. This is a significant change.
198                     significantChange = true;
199                     break;
200                 }
201             }
202
203             if (!significantChange) {
204                 for (var key in this._previousStyleDeclarationsMap) {
205                     // Check if the same key exists in current map. If it does exist it was already checked for equality above.
206                     if (key in this._styleDeclarationsMap)
207                         continue;
208
209                     if (!this._includeUserAgentRulesOnNextRefresh) {
210                         // See above for why we skip user agent style rules.
211                         var firstStyle = this._previousStyleDeclarationsMap[key][0];
212                         if (firstStyle && firstStyle.ownerRule && firstStyle.ownerRule.type === WebInspector.CSSRule.Type.UserAgent)
213                             continue;
214                     }
215
216                     // This key no longer exists. This is a significant change.
217                     significantChange = true;
218                     break;
219                 }
220             }
221
222             delete this._includeUserAgentRulesOnNextRefresh;
223
224             // Delete the previous maps now that any reused rules and style have been moved over.
225             delete this._previousRulesMap;
226             delete this._previousStyleDeclarationsMap;
227
228             var styleToCommit = this._stylesNeedingTextCommited.shift();
229             if (styleToCommit) {
230                 // Remember the significant change flag so we can pass it along when the pending style
231                 // changes trigger a refresh. If we wait to scan later we might not find a significant change
232                 // and fail to tell listeners about it.
233                 this._previousSignificantChange = significantChange;
234
235                 this.changeStyleText(styleToCommit, styleToCommit.__pendingText);
236
237                 return;
238             }
239
240             // Delete the previous saved significant change flag so we rescan for a significant change next time.
241             delete this._previousSignificantChange;
242
243             this.dispatchEventToListeners(WebInspector.DOMNodeStyles.Event.Refreshed, {significantChange: significantChange});
244         }
245
246         CSSAgent.getMatchedStylesForNode.invoke({nodeId: this._node.id, includePseudo: true, includeInherited: true}, fetchedMatchedStyles.bind(this));
247         CSSAgent.getInlineStylesForNode.invoke({nodeId: this._node.id}, fetchedInlineStyles.bind(this));
248         CSSAgent.getComputedStyleForNode.invoke({nodeId: this._node.id}, fetchedComputedStyle.bind(this));
249     },
250
251     addRule: function(selector)
252     {
253         function addedRule(error, rulePayload)
254         {
255             if (error)
256                 return;
257
258             DOMAgent.markUndoableState();
259
260             this.refresh();
261         }
262
263         selector = selector || this._node.appropriateSelectorFor(true);
264
265         CSSAgent.addRule.invoke({contextNodeId: this._node.id, selector: selector}, addedRule.bind(this));
266     },
267
268     get matchedRules()
269     {
270         return this._matchedRules;
271     },
272
273     get inheritedRules()
274     {
275         return this._inheritedRules;
276     },
277
278     get inlineStyle()
279     {
280         return this._inlineStyle;
281     },
282
283     get attributesStyle()
284     {
285         return this._attributesStyle;
286     },
287
288     get pseudoElements()
289     {
290         return this._pseudoElements;
291     },
292
293     get computedStyle()
294     {
295         return this._computedStyle;
296     },
297
298     get orderedStyles()
299     {
300         return this._orderedStyles;
301     },
302
303     effectivePropertyForName: function(name)
304     {
305         var canonicalName = WebInspector.cssStyleManager.canonicalNameForPropertyName(name);
306         return this._propertyNameToEffectivePropertyMap[canonicalName] || null;
307     },
308
309     // Protected
310
311     mediaQueryResultDidChange: function()
312     {
313         this._markAsNeedsRefresh();
314     },
315
316     pseudoClassesDidChange: function(node)
317     {
318         this._includeUserAgentRulesOnNextRefresh = true;
319         this._markAsNeedsRefresh();
320     },
321
322     attributeDidChange: function(node, attributeName)
323     {
324         // Ignore the attribute we know we just changed and handled above.
325         if (this._ignoreNextStyleAttributeDidChangeEvent && node === this._node && attributeName === "style") {
326             delete this._ignoreNextStyleAttributeDidChangeEvent;
327             return;
328         }
329
330         this._markAsNeedsRefresh();
331     },
332
333     changeRuleSelector: function(rule, selector)
334     {
335         selector = selector || "";
336
337         function ruleSelectorChanged(error, rulePayload)
338         {
339             DOMAgent.markUndoableState();
340
341             // Do a full refresh incase the rule no longer matches the node or the
342             // matched selector indices changed.
343             this.refresh();
344         }
345
346         this._needsRefresh = true;
347         this._ignoreNextContentDidChangeForStyleSheet = rule.ownerStyleSheet;
348
349         CSSAgent.setRuleSelector(rule.id, selector, ruleSelectorChanged.bind(this));
350     },
351
352     changeStyleText: function(style, text)
353     {
354         if (!style.ownerStyleSheet || !style.styleSheetTextRange)
355             return;
356
357         text = text || "";
358
359         if (CSSAgent.setStyleText) {
360             function styleChanged(error, stylePayload)
361             {
362                 if (error)
363                     return;
364                 this.refresh();
365             }
366
367             CSSAgent.setStyleText(style.id, text, styleChanged.bind(this));
368             return;
369         }
370
371         // COMPATIBILITY (iOS 6): CSSAgent.setStyleText was not available in iOS 6.
372
373         // Setting the text on CSSStyleSheet for inline styles causes a crash. https://webkit.org/b/110359
374         // So we just set the style attribute to get the same affect. This also avoids SourceCodeRevisions.
375         if (style.type === WebInspector.CSSStyleDeclaration.Type.Inline) {
376             text = text.trim();
377
378             function attributeChanged(error)
379             {
380                 if (error)
381                     return;
382                 this.refresh();
383             }
384
385             this._ignoreNextStyleAttributeDidChangeEvent = true;
386
387             if (text)
388                 style.node.setAttributeValue("style", text, attributeChanged.bind(this));
389             else
390                 style.node.removeAttribute("style", attributeChanged.bind(this));
391
392             return;
393         }
394
395         if (this._needsRefresh || this._refreshPending) {
396             // If we need refreshed then it is not safe to use the styleSheetTextRange since the range likely has
397             // changed and we need updated ranges. Store the text and remember the style so we can commit it after
398             // the next refresh.
399
400             style.__pendingText = text;
401
402             if (!this._stylesNeedingTextCommited.contains(style))
403                 this._stylesNeedingTextCommited.push(style);
404
405             return;
406         }
407
408         function fetchedStyleSheetContent(parameters)
409         {
410             var content = parameters.content;
411
412             console.assert(style.styleSheetTextRange);
413             if (!style.styleSheetTextRange)
414                 return;
415
416             var startOffset = style.styleSheetTextRange.startOffset;
417             var endOffset = style.styleSheetTextRange.endOffset;
418
419             if (isNaN(startOffset) || isNaN(endOffset)) {
420                 style.styleSheetTextRange.resolveOffsets(content);
421
422                 startOffset = style.styleSheetTextRange.startOffset;
423                 endOffset = style.styleSheetTextRange.endOffset;
424             }
425
426             console.assert(!isNaN(startOffset));
427             console.assert(!isNaN(endOffset));
428             if (isNaN(startOffset) || isNaN(endOffset))
429                 return;
430
431             function contentDidChange()
432             {
433                 style.ownerStyleSheet.removeEventListener(WebInspector.CSSStyleSheet.Event.ContentDidChange, contentDidChange, this);
434
435                 this.refresh();
436             }
437
438             style.ownerStyleSheet.addEventListener(WebInspector.CSSStyleSheet.Event.ContentDidChange, contentDidChange, this);
439
440             var newContent = content.substring(0, startOffset) + text + content.substring(endOffset);
441
442             WebInspector.branchManager.currentBranch.revisionForRepresentedObject(style.ownerStyleSheet).content = newContent;
443         }
444
445         this._stylesNeedingTextCommited.remove(style);
446         delete style.__pendingText;
447
448         this._needsRefresh = true;
449         this._ignoreNextContentDidChangeForStyleSheet = style.ownerStyleSheet;
450
451         style.ownerStyleSheet.requestContent().then(fetchedStyleSheetContent.bind(this));
452     },
453
454     changeProperty: function(property, name, value, priority)
455     {
456         var text = name ? name + ": " + value + (priority ? " !" + priority : "") + ";" : "";
457         this.changePropertyText(property, text);
458     },
459
460     changePropertyText: function(property, text)
461     {
462         text = text || "";
463
464         var index = property.index;
465         var newProperty = isNaN(index);
466         var overwrite = true;
467
468         // If this is a new property, then give it an index at the end of the current properties.
469         // Also don't overwrite, which will cause the property to be added at that index.
470         if (newProperty) {
471             index = property.ownerStyle.properties.length;
472             overwrite = false;
473         }
474
475         if (text && text.charAt(text.length - 1) !== ";")
476             text += ";";
477
478         this._needsRefresh = true;
479         this._ignoreNextContentDidChangeForStyleSheet = property.ownerStyle.ownerStyleSheet;
480
481         CSSAgent.setPropertyText(property.ownerStyle.id, index, text, overwrite, this._handlePropertyChange.bind(this, property));
482     },
483
484     changePropertyEnabledState: function(property, enabled)
485     {
486         enabled = !!enabled;
487
488         // Can't change a pending property with a NaN index.
489         if (isNaN(property.index))
490             return;
491
492         this._ignoreNextContentDidChangeForStyleSheet = property.ownerStyle.ownerStyleSheet;
493
494         CSSAgent.toggleProperty(property.ownerStyle.id, property.index, !enabled, this._handlePropertyChange.bind(this, property));
495     },
496
497     addProperty: function(property)
498     {
499         // Can't add a property unless it has a NaN index.
500         if (!isNaN(property.index))
501             return;
502
503         // Adding is done by setting the text.
504         this.changePropertyText(property, property.text);
505     },
506
507     removeProperty: function(property)
508     {
509         // Can't remove a pending property with a NaN index.
510         if (isNaN(property.index))
511             return;
512
513         // Removing is done by setting text to an empty string.
514         this.changePropertyText(property, "");
515     },
516
517     // Private
518
519     _handlePropertyChange: function(property, error, stylePayload)
520     {
521         if (error)
522             return;
523
524         DOMAgent.markUndoableState();
525
526         // Do a refresh instead of handling stylePayload so computed style is updated and we get valid
527         // styleSheetTextRange values for all the rules after this change.
528         this.refresh();
529     },
530
531     _createSourceCodeLocation: function(sourceURL, sourceLine, sourceColumn)
532     {
533         if (!sourceURL)
534             return null;
535
536         var sourceCode;
537
538         // Try to use the node to find the frame which has the correct resource first.
539         if (this._node.ownerDocument) {
540             var mainResource = WebInspector.frameResourceManager.resourceForURL(this._node.ownerDocument.documentURL);
541             if (mainResource) {
542                 var parentFrame = mainResource.parentFrame;
543                 sourceCode = parentFrame.resourceForURL(sourceURL);
544             }
545         }
546
547         // If that didn't find the resource, then search all frames.
548         if (!sourceCode)
549             sourceCode = WebInspector.frameResourceManager.resourceForURL(sourceURL);
550
551         if (!sourceCode)
552             return null;
553
554         return sourceCode.createSourceCodeLocation(sourceLine || 0, sourceColumn || 0);
555     },
556
557     _parseSourceRangePayload: function(payload, text)
558     {
559         if (!payload)
560             return null;
561
562         // COMPATIBILITY (iOS 6): The range use to only contain start and end offsets. Now it
563         // has line and column for the start and end position. Support both here.
564         if ("start" in payload && "end" in payload) {
565             var textRange = new WebInspector.TextRange(payload.start, payload.end);
566             if (typeof text === "string")
567                 textRange.resolveLinesAndColumns(text);
568             return textRange;
569         }
570
571         return new WebInspector.TextRange(payload.startLine, payload.startColumn, payload.endLine, payload.endColumn);
572     },
573
574     _parseStylePropertyPayload: function(payload, index, styleDeclaration, styleText)
575     {
576         var text = payload.text || "";
577         var name = payload.name;
578         var value = (payload.value || "").replace(/\s*!important\s*$/, "");
579         var priority = payload.priority || "";
580
581         var enabled = true;
582         var overridden = false;
583         var implicit = payload.implicit || false;
584         var anonymous = false;
585         var valid = "parsedOk" in payload ? payload.parsedOk : true;
586
587         switch (payload.status || "style") {
588         case "active":
589             enabled = true;
590             break;
591         case "inactive":
592             overridden = true;
593             enabled = true;
594             break;
595         case "disabled":
596             enabled = false;
597             break;
598         case "style":
599             anonymous = true;
600             break;
601         }
602
603         var styleSheetTextRange = null;
604         var styleDeclarationTextRange = null;
605
606         // COMPATIBILITY (iOS 6): The range is in the style text, not the whole stylesheet.
607         // Later the range was changed to be in the whole stylesheet.
608         if (payload.range && "start" in payload.range && "end" in payload.range)
609             styleDeclarationTextRange = this._parseSourceRangePayload(payload.range, styleText);
610         else
611             styleSheetTextRange = this._parseSourceRangePayload(payload.range);
612
613         if (styleDeclaration) {
614             // Use propertyForName when the index is NaN since propertyForName is fast in that case.
615             var property = isNaN(index) ? styleDeclaration.propertyForName(name, true) : styleDeclaration.properties[index];
616
617             // Reuse a property if the index and name matches. Otherwise it is a different property
618             // and should be created from scratch. This works in the simple cases where only existing
619             // properties change in place and no properties are inserted or deleted at the beginning.
620             // FIXME: This could be smarter by ignoring index and just go by name. However, that gets
621             // tricky for rules that have more than one property with the same name.
622             if (property && property.name === name && (property.index === index || (isNaN(property.index) && isNaN(index)))) {
623                 property.update(text, name, value, priority, enabled, overridden, implicit, anonymous, valid, styleSheetTextRange, styleDeclarationTextRange);
624                 return property;
625             }
626
627             // Reuse a pending property with the same name. These properties are pending being committed,
628             // so if we find a match that likely means it got committed and we should use it.
629             var pendingProperties = styleDeclaration.pendingProperties;
630             for (var i = 0; i < pendingProperties.length; ++i) {
631                 var pendingProperty = pendingProperties[i];
632                 if (pendingProperty.name === name && isNaN(pendingProperty.index)) {
633                     pendingProperty.index = index;
634                     pendingProperty.update(text, name, value, priority, enabled, overridden, implicit, anonymous, valid, styleSheetTextRange, styleDeclarationTextRange);
635                     return pendingProperty;
636                 }
637             }
638         }
639
640         return new WebInspector.CSSProperty(index, text, name, value, priority, enabled, overridden, implicit, anonymous, valid, styleSheetTextRange, styleDeclarationTextRange);
641     },
642
643     _parseStyleDeclarationPayload: function(payload, node, inherited, type, rule, updateAllStyles)
644     {
645         if (!payload)
646             return null;
647
648         rule = rule || null;
649         inherited = inherited || false;
650
651         var id = payload.styleId;
652         var mapKey = id ? id.styleSheetId + ":" + id.ordinal : null;
653
654         var styleDeclaration = rule ? rule.style : null;
655         var styleDeclarations = [];
656
657         // Look for existing styles in the previous map if there is one, otherwise use the current map.
658         var previousStyleDeclarationsMap = this._previousStyleDeclarationsMap || this._styleDeclarationsMap;
659         if (mapKey && mapKey in previousStyleDeclarationsMap) {
660             styleDeclarations = previousStyleDeclarationsMap[mapKey];
661
662             // If we need to update all styles, then stop here and call _parseStyleDeclarationPayload for each style.
663             // We need to parse multiple times so we reuse the right properties from each style.
664             if (updateAllStyles && styleDeclarations.length) {
665                 for (var i = 0; i < styleDeclarations.length; ++i) {
666                     var styleDeclaration = styleDeclarations[i];
667                     this._parseStyleDeclarationPayload(payload, styleDeclaration.node, styleDeclaration.inherited, styleDeclaration.type, styleDeclaration.ownerRule);
668                 }
669
670                 return null;
671             }
672
673             if (!styleDeclaration) {
674                 var filteredStyleDeclarations = styleDeclarations.filter(function(styleDeclaration) {
675                     // This case only applies for styles that are not part of a rule.
676                     if (styleDeclaration.ownerRule) {
677                         console.assert(!rule);
678                         return false;
679                     }
680
681                     if (styleDeclaration.node !== node)
682                         return false;
683
684                     if (styleDeclaration.inherited !== inherited)
685                         return false;
686
687                     return true;
688                 });
689
690                 console.assert(filteredStyleDeclarations.length <= 1);
691                 styleDeclaration = filteredStyleDeclarations[0] || null;
692             }
693         }
694
695         if (previousStyleDeclarationsMap !== this._styleDeclarationsMap) {
696             // If the previous and current maps differ then make sure the found styleDeclaration is added to the current map.
697             styleDeclarations = mapKey && mapKey in this._styleDeclarationsMap ? this._styleDeclarationsMap[mapKey] : [];
698
699             if (styleDeclaration && !styleDeclarations.contains(styleDeclaration)) {
700                 styleDeclarations.push(styleDeclaration);
701                 this._styleDeclarationsMap[mapKey] = styleDeclarations;
702             }
703         }
704
705         var shorthands = {};
706         for (var i = 0; payload.shorthandEntries && i < payload.shorthandEntries.length; ++i) {
707             var shorthand = payload.shorthandEntries[i];
708             shorthands[shorthand.name] = shorthand.value;
709         }
710
711         var text = payload.cssText;
712
713         var inheritedPropertyCount = 0;
714
715         var properties = [];
716         for (var i = 0; payload.cssProperties && i < payload.cssProperties.length; ++i) {
717             var propertyPayload = payload.cssProperties[i];
718
719             if (inherited && propertyPayload.name in WebInspector.CSSKeywordCompletions.InheritedProperties)
720                 ++inheritedPropertyCount;
721
722             var property = this._parseStylePropertyPayload(propertyPayload, i, styleDeclaration, text);
723             properties.push(property);
724         }
725
726         if (inherited && !inheritedPropertyCount)
727             return null;
728
729         var styleSheetTextRange = this._parseSourceRangePayload(payload.range);
730
731         if (styleDeclaration) {
732             styleDeclaration.update(text, properties, styleSheetTextRange);
733             return styleDeclaration;
734         }
735
736         var styleSheet = id ? WebInspector.cssStyleManager.styleSheetForIdentifier(id.styleSheetId) : null;
737         if (styleSheet)
738             styleSheet.addEventListener(WebInspector.CSSStyleSheet.Event.ContentDidChange, this._styleSheetContentDidChange, this);
739
740         styleDeclaration = new WebInspector.CSSStyleDeclaration(this, styleSheet, id, type, node, inherited, text, properties, styleSheetTextRange);
741
742         if (mapKey) {
743             styleDeclarations.push(styleDeclaration);
744             this._styleDeclarationsMap[mapKey] = styleDeclarations;
745         }
746
747         return styleDeclaration;
748     },
749
750     _parseSelectorListPayload: function(selectorList)
751     {
752         // COMPATIBILITY (iOS 6): The payload did not have 'selectorList'.
753         if (!selectorList)
754             return [];
755
756         var selectors = selectorList.selectors;
757         if (!selectors.length)
758             return [];
759
760         // COMPATIBILITY (iOS 8): The selectorList payload was an array of selector text strings.
761         // Now they are CSSSelector objects with multiple properties.
762         if (typeof selectors[0] === "string") {
763             return selectors.map(function(selectorText) {
764                 return new WebInspector.CSSSelector(selectorText);
765             });
766         }
767
768         return selectors.map(function(selectorPayload) {
769             return new WebInspector.CSSSelector(selectorPayload.text, selectorPayload.specificity);
770         });
771     },
772
773     _parseRulePayload: function(payload, matchedSelectorIndices, node, inherited, ruleOccurrences)
774     {
775         if (!payload)
776             return null;
777
778         // User and User Agent rules don't have 'ruleId' in the payload. However, their style's have 'styleId' and
779         // 'styleId' is the same identifier the backend uses for Author rule identifiers, so do the same here.
780         // They are excluded by the backend because they are not editable, however our front-end does not determine
781         // editability solely based on the existence of the id like the open source front-end does.
782         var id = payload.ruleId || payload.style.styleId;
783
784         var mapKey = id ? id.styleSheetId + ":" + id.ordinal + ":" + (inherited ? "I" : "N") + ":" + node.id : null;
785
786         // Rules can match multiple times if they have multiple selectors or because of inheritance. We keep a count
787         // of occurrences so we have unique rules per occurrence, that way properties will be correctly marked as overridden.
788         var occurrence = 0;
789         if (mapKey) {
790             if (mapKey in ruleOccurrences)
791                 occurrence = ++ruleOccurrences[mapKey];
792             else
793                 ruleOccurrences[mapKey] = occurrence;
794         }
795
796         // Append the occurrence number to the map key for lookup in the rules map.
797         mapKey += ":" + occurrence;
798
799         var rule = null;
800
801         // Look for existing rules in the previous map if there is one, otherwise use the current map.
802         var previousRulesMap = this._previousRulesMap || this._rulesMap;
803         if (mapKey && mapKey in previousRulesMap) {
804             rule = previousRulesMap[mapKey];
805
806             if (previousRulesMap !== this._rulesMap) {
807                 // If the previous and current maps differ then make sure the found rule is added to the current map.
808                 this._rulesMap[mapKey] = rule;
809             }
810         }
811
812         var style = this._parseStyleDeclarationPayload(payload.style, node, inherited, WebInspector.CSSStyleDeclaration.Type.Rule, rule);
813         if (!style)
814             return null;
815
816         // COMPATIBILITY (iOS 6): The payload had 'selectorText' as a property,
817         // now it has 'selectorList' with a 'text' property. Support both here.
818         var selectorText = payload.selectorList ? payload.selectorList.text : payload.selectorText;
819         var selectors = this._parseSelectorListPayload(payload.selectorList);
820
821         // COMPATIBILITY (iOS 6): The payload did not have 'selectorList'.
822         // Fallback to using 'sourceLine' without column information.
823         if (payload.selectorList && payload.selectorList.range) {
824             var sourceRange = payload.selectorList.range;
825             var sourceCodeLocation = this._createSourceCodeLocation(payload.sourceURL, sourceRange.startLine, sourceRange.startColumn);
826         } else
827             var sourceCodeLocation = this._createSourceCodeLocation(payload.sourceURL, payload.sourceLine);
828
829         var type;
830         switch (payload.origin) {
831         case "regular":
832             type = WebInspector.CSSRule.Type.Author;
833             break;
834         case "user":
835             type = WebInspector.CSSRule.Type.User;
836             break;
837         case "user-agent":
838             type = WebInspector.CSSRule.Type.UserAgent;
839             break;
840         case "inspector":
841             type = WebInspector.CSSRule.Type.Inspector;
842             break;
843         }
844
845         var mediaList = [];
846         for (var i = 0; payload.media && i < payload.media.length; ++i) {
847             var mediaItem = payload.media[i];
848
849             var mediaType;
850             switch (mediaItem.source) {
851             case "mediaRule":
852                 mediaType = WebInspector.CSSMedia.Type.MediaRule;
853                 break;
854             case "importRule":
855                 mediaType = WebInspector.CSSMedia.Type.ImportRule;
856                 break;
857             case "linkedSheet":
858                 mediaType = WebInspector.CSSMedia.Type.LinkedStyleSheet;
859                 break;
860             case "inlineSheet":
861                 mediaType = WebInspector.CSSMedia.Type.InlineStyleSheet;
862                 break;
863             }
864
865             var mediaText = mediaItem.text;
866             var mediaSourceCodeLocation = this._createSourceCodeLocation(mediaItem.sourceURL, mediaItem.sourceLine);
867
868             mediaList.push(new WebInspector.CSSMedia(mediaType, mediaText, mediaSourceCodeLocation));
869         }
870
871         if (rule) {
872             rule.update(sourceCodeLocation, selectorText, selectors, matchedSelectorIndices, style, mediaList);
873             return rule;
874         }
875
876         var styleSheet = id ? WebInspector.cssStyleManager.styleSheetForIdentifier(id.styleSheetId) : null;
877         if (styleSheet)
878             styleSheet.addEventListener(WebInspector.CSSStyleSheet.Event.ContentDidChange, this._styleSheetContentDidChange, this);
879
880         rule = new WebInspector.CSSRule(this, styleSheet, id, type, sourceCodeLocation, selectorText, selectors, matchedSelectorIndices, style, mediaList);
881
882         if (mapKey)
883             this._rulesMap[mapKey] = rule;
884
885         return rule;
886     },
887
888     _markAsNeedsRefresh: function()
889     {
890         this._needsRefresh = true;
891         this.dispatchEventToListeners(WebInspector.DOMNodeStyles.Event.NeedsRefresh);
892     },
893
894     _styleSheetContentDidChange: function(event)
895     {
896         var styleSheet = event.target;
897         console.assert(styleSheet);
898         if (!styleSheet)
899             return;
900
901         // Ignore the stylesheet we know we just changed and handled above.
902         if (styleSheet === this._ignoreNextContentDidChangeForStyleSheet) {
903             delete this._ignoreNextContentDidChangeForStyleSheet;
904             return;
905         }
906
907         this._markAsNeedsRefresh();
908     },
909
910     _updateStyleCascade: function()
911     {
912         var cascadeOrderedStyleDeclarations = this._collectStylesInCascadeOrder(this._matchedRules, this._inlineStyle, this._attributesStyle);
913
914         for (var i = 0; i < this._inheritedRules.length; ++i) {
915             var inheritedStyleInfo = this._inheritedRules[i];
916             var inheritedCascadeOrder = this._collectStylesInCascadeOrder(inheritedStyleInfo.matchedRules, inheritedStyleInfo.inlineStyle, null);
917             cascadeOrderedStyleDeclarations = cascadeOrderedStyleDeclarations.concat(inheritedCascadeOrder);
918         }
919
920         this._orderedStyles = cascadeOrderedStyleDeclarations;
921
922         this._propertyNameToEffectivePropertyMap = {};
923
924         this._markOverriddenProperties(cascadeOrderedStyleDeclarations, this._propertyNameToEffectivePropertyMap);
925         this._associateRelatedProperties(cascadeOrderedStyleDeclarations, this._propertyNameToEffectivePropertyMap);
926
927         for (var pseudoIdentifier in this._pseudoElements) {
928             var pseudoElementInfo = this._pseudoElements[pseudoIdentifier];
929             pseudoElementInfo.orderedStyles = this._collectStylesInCascadeOrder(pseudoElementInfo.matchedRules, null, null);
930             this._markOverriddenProperties(pseudoElementInfo.orderedStyles);
931             this._associateRelatedProperties(pseudoElementInfo.orderedStyles);
932         }
933     },
934
935     _collectStylesInCascadeOrder: function(matchedRules, inlineStyle, attributesStyle)
936     {
937         var result = [];
938
939         // Inline style has the greatest specificity. So it goes first in the cascade order.
940         if (inlineStyle)
941             result.push(inlineStyle);
942
943         var userAndUserAgentStyles = [];
944
945         for (var i = 0; i < matchedRules.length; ++i) {
946             var rule = matchedRules[i];
947
948             // Only append to the result array here for author and inspector rules since attribute
949             // styles come between author rules and user/user agent rules.
950             switch (rule.type) {
951             case WebInspector.CSSRule.Type.Inspector:
952             case WebInspector.CSSRule.Type.Author:
953                 result.push(rule.style);
954                 break;
955
956             case WebInspector.CSSRule.Type.User:
957             case WebInspector.CSSRule.Type.UserAgent:
958                 userAndUserAgentStyles.push(rule.style);
959                 break;
960             }
961         }
962
963         // Style properties from HTML attributes are next.
964         if (attributesStyle)
965             result.push(attributesStyle);
966
967         // Finally add the user and user stylesheet's matched style rules we collected earlier.
968         result = result.concat(userAndUserAgentStyles);
969
970         return result;
971     },
972
973     _markOverriddenProperties: function(styles, propertyNameToEffectiveProperty)
974     {
975         propertyNameToEffectiveProperty = propertyNameToEffectiveProperty || {};
976
977         for (var i = 0; i < styles.length; ++i) {
978             var style = styles[i];
979             var properties = style.properties;
980
981             for (var j = 0; j < properties.length; ++j) {
982                 var property = properties[j];
983                 if (!property.enabled || property.anonymous || !property.valid) {
984                     property.overridden = false;
985                     continue;
986                 }
987
988                 if (style.inherited && !property.inherited) {
989                     property.overridden = false;
990                     continue;
991                 }
992
993                 var canonicalName = property.canonicalName;
994                 if (canonicalName in propertyNameToEffectiveProperty) {
995                     var effectiveProperty = propertyNameToEffectiveProperty[canonicalName];
996
997                     if (effectiveProperty.ownerStyle === property.ownerStyle) {
998                         if (effectiveProperty.important && !property.important) {
999                             property.overridden = true;
1000                             continue;
1001                         }
1002                     } else if (effectiveProperty.important || !property.important || effectiveProperty.ownerStyle.node !== property.ownerStyle.node) {
1003                         property.overridden = true;
1004                         continue;
1005                     }
1006
1007                     effectiveProperty.overridden = true;
1008                 }
1009
1010                 property.overridden = false;
1011
1012                 propertyNameToEffectiveProperty[canonicalName] = property;
1013             }
1014         }
1015     },
1016
1017     _associateRelatedProperties: function(styles, propertyNameToEffectiveProperty)
1018     {
1019         for (var i = 0; i < styles.length; ++i) {
1020             var properties = styles[i].properties;
1021
1022             var knownShorthands = {};
1023
1024             for (var j = 0; j < properties.length; ++j) {
1025                 var property = properties[j];
1026
1027                 if (!property.valid)
1028                     continue;
1029
1030                 if (!WebInspector.CSSCompletions.cssNameCompletions.isShorthandPropertyName(property.name))
1031                     continue;
1032
1033                 if (knownShorthands[property.canonicalName] && !knownShorthands[property.canonicalName].overridden) {
1034                     console.assert(property.overridden);
1035                     continue;
1036                 }
1037
1038                 knownShorthands[property.canonicalName] = property;
1039             }
1040
1041             for (var j = 0; j < properties.length; ++j) {
1042                 var property = properties[j];
1043
1044                 if (!property.valid)
1045                     continue;
1046
1047                 var shorthandProperty = null;
1048
1049                 if (!isEmptyObject(knownShorthands)) {
1050                     var possibleShorthands = WebInspector.CSSCompletions.cssNameCompletions.shorthandsForLonghand(property.canonicalName);
1051                     for (var k = 0; k < possibleShorthands.length; ++k) {
1052                         if (possibleShorthands[k] in knownShorthands) {
1053                             shorthandProperty = knownShorthands[possibleShorthands[k]];
1054                             break;
1055                         }
1056                     }
1057                 }
1058
1059                 if (!shorthandProperty || shorthandProperty.overridden !== property.overridden) {
1060                     property.relatedShorthandProperty = null;
1061                     property.clearRelatedLonghandProperties();
1062                     continue;
1063                 }
1064
1065                 shorthandProperty.addRelatedLonghandProperty(property);
1066                 property.relatedShorthandProperty = shorthandProperty;
1067
1068                 if (propertyNameToEffectiveProperty && propertyNameToEffectiveProperty[shorthandProperty.canonicalName] === shorthandProperty)
1069                     propertyNameToEffectiveProperty[property.canonicalName] = property;
1070             }
1071         }
1072     }
1073 };
1074
1075 WebInspector.DOMNodeStyles.prototype.__proto__ = WebInspector.Object.prototype;