Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / StylesSidebarPane.js
1 /*
2  * Copyright (C) 2007 Apple Inc.  All rights reserved.
3  * Copyright (C) 2009 Joseph Pecoraro
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1.  Redistributions of source code must retain the above copyright
10  *     notice, this list of conditions and the following disclaimer.
11  * 2.  Redistributions in binary form must reproduce the above copyright
12  *     notice, this list of conditions and the following disclaimer in the
13  *     documentation and/or other materials provided with the distribution.
14  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15  *     its contributors may be used to endorse or promote products derived
16  *     from this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 /**
31  * @constructor
32  * @extends {WebInspector.SidebarPane}
33  * @param {!WebInspector.ComputedStyleSidebarPane} computedStylePane
34  * @param {function(!DOMAgent.NodeId, string, boolean)} setPseudoClassCallback
35  */
36 WebInspector.StylesSidebarPane = function(computedStylePane, setPseudoClassCallback)
37 {
38     WebInspector.SidebarPane.call(this, WebInspector.UIString("Styles"));
39
40     this._elementStateButton = document.createElement("button");
41     this._elementStateButton.className = "pane-title-button element-state";
42     this._elementStateButton.title = WebInspector.UIString("Toggle Element State");
43     this._elementStateButton.addEventListener("click", this._toggleElementStatePane.bind(this), false);
44     this.titleElement.appendChild(this._elementStateButton);
45
46     var addButton = document.createElement("button");
47     addButton.className = "pane-title-button add";
48     addButton.id = "add-style-button-test-id";
49     addButton.title = WebInspector.UIString("New Style Rule");
50     addButton.addEventListener("click", this._createNewRule.bind(this), false);
51     this.titleElement.appendChild(addButton);
52
53     this._computedStylePane = computedStylePane;
54     computedStylePane.setHostingPane(this);
55     this._setPseudoClassCallback = setPseudoClassCallback;
56     this.element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true);
57     WebInspector.settings.colorFormat.addChangeListener(this._colorFormatSettingChanged.bind(this));
58
59     this._createElementStatePane();
60     this.bodyElement.appendChild(this._elementStatePane);
61     this._sectionsContainer = document.createElement("div");
62     this.bodyElement.appendChild(this._sectionsContainer);
63
64     this._spectrumHelper = new WebInspector.SpectrumPopupHelper();
65     this._linkifier = new WebInspector.Linkifier(new WebInspector.Linkifier.DefaultCSSFormatter());
66
67     WebInspector.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this._styleSheetOrMediaQueryResultChanged, this);
68     WebInspector.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._styleSheetOrMediaQueryResultChanged, this);
69     WebInspector.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetOrMediaQueryResultChanged, this);
70     WebInspector.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.MediaQueryResultChanged, this._styleSheetOrMediaQueryResultChanged, this);
71     WebInspector.domModel.addEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributeChanged, this);
72     WebInspector.domModel.addEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributeChanged, this);
73     WebInspector.settings.showUserAgentStyles.addChangeListener(this._showUserAgentStylesSettingChanged.bind(this));
74     WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameResized, this._frameResized, this);
75     this.element.classList.add("styles-pane");
76     this.element.classList.toggle("show-user-styles", WebInspector.settings.showUserAgentStyles.get());
77     this.element.addEventListener("mousemove", this._mouseMovedOverElement.bind(this), false);
78     document.body.addEventListener("keydown", this._keyDown.bind(this), false);
79     document.body.addEventListener("keyup", this._keyUp.bind(this), false);
80 }
81
82 // Keep in sync with RenderStyleConstants.h PseudoId enum. Array below contains pseudo id names for corresponding enum indexes.
83 // First item is empty due to its artificial NOPSEUDO nature in the enum.
84 // FIXME: find a way of generating this mapping or getting it from combination of RenderStyleConstants and CSSSelector.cpp at
85 // runtime.
86 WebInspector.StylesSidebarPane.PseudoIdNames = [
87     "", "first-line", "first-letter", "before", "after", "selection", "", "-webkit-scrollbar", "-webkit-file-upload-button",
88     "-webkit-input-placeholder", "-webkit-slider-thumb", "-webkit-search-cancel-button", "-webkit-search-decoration",
89     "-webkit-search-results-decoration", "-webkit-search-results-button", "-webkit-media-controls-panel",
90     "-webkit-media-controls-play-button", "-webkit-media-controls-mute-button", "-webkit-media-controls-timeline",
91     "-webkit-media-controls-timeline-container", "-webkit-media-controls-volume-slider",
92     "-webkit-media-controls-volume-slider-container", "-webkit-media-controls-current-time-display",
93     "-webkit-media-controls-time-remaining-display", "-webkit-media-controls-fullscreen-button",
94     "-webkit-media-controls-toggle-closed-captions-button", "-webkit-media-controls-status-display", "-webkit-scrollbar-thumb",
95     "-webkit-scrollbar-button", "-webkit-scrollbar-track", "-webkit-scrollbar-track-piece", "-webkit-scrollbar-corner",
96     "-webkit-resizer", "-webkit-inner-spin-button", "-webkit-outer-spin-button"
97 ];
98
99 WebInspector.StylesSidebarPane._colorRegex = /((?:rgb|hsl)a?\([^)]+\)|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}|\b\w+\b(?!-))/g;
100
101 /**
102  * @param {!WebInspector.CSSProperty} property
103  */
104 WebInspector.StylesSidebarPane.createExclamationMark = function(property)
105 {
106     var exclamationElement = document.createElement("div");
107     exclamationElement.className = "exclamation-mark" + (WebInspector.StylesSidebarPane._ignoreErrorsForProperty(property) ? "" : " warning-icon-small");
108     exclamationElement.title = WebInspector.CSSMetadata.cssPropertiesMetainfo.keySet()[property.name.toLowerCase()] ? WebInspector.UIString("Invalid property value.") : WebInspector.UIString("Unknown property name.");
109     return exclamationElement;
110 }
111
112 /**
113  * @param {!WebInspector.Color} color
114  */
115 WebInspector.StylesSidebarPane._colorFormat = function(color)
116 {
117     const cf = WebInspector.Color.Format;
118     var format;
119     var formatSetting = WebInspector.settings.colorFormat.get();
120     if (formatSetting === cf.Original)
121         format = cf.Original;
122     else if (formatSetting === cf.RGB)
123         format = (color.hasAlpha() ? cf.RGBA : cf.RGB);
124     else if (formatSetting === cf.HSL)
125         format = (color.hasAlpha() ? cf.HSLA : cf.HSL);
126     else if (!color.hasAlpha())
127         format = (color.canBeShortHex() ? cf.ShortHEX : cf.HEX);
128     else
129         format = cf.RGBA;
130
131     return format;
132 }
133
134 /**
135  * @param {!WebInspector.CSSProperty} property
136  */
137 WebInspector.StylesSidebarPane._ignoreErrorsForProperty = function(property) {
138     function hasUnknownVendorPrefix(string)
139     {
140         return !string.startsWith("-webkit-") && /^[-_][\w\d]+-\w/.test(string);
141     }
142
143     var name = property.name.toLowerCase();
144
145     // IE hack.
146     if (name.charAt(0) === "_")
147         return true;
148
149     // IE has a different format for this.
150     if (name === "filter")
151         return true;
152
153     // Common IE-specific property prefix.
154     if (name.startsWith("scrollbar-"))
155         return true;
156     if (hasUnknownVendorPrefix(name))
157         return true;
158
159     var value = property.value.toLowerCase();
160
161     // IE hack.
162     if (value.endsWith("\9"))
163         return true;
164     if (hasUnknownVendorPrefix(value))
165         return true;
166
167     return false;
168 }
169
170 WebInspector.StylesSidebarPane.prototype = {
171     /**
172      * @param {?Event} event
173      */
174     _contextMenuEventFired: function(event)
175     {
176         // We start editing upon click -> default navigation to resources panel is not available
177         // Hence we add a soft context menu for hrefs.
178         var contextMenu = new WebInspector.ContextMenu(event);
179         contextMenu.appendApplicableItems(/** @type {!Node} */ (event.target));
180         contextMenu.show();
181     },
182
183     /**
184      * @param {!Element} matchedStylesElement
185      * @param {!Element} computedStylesElement
186      */
187     setFilterBoxContainers: function(matchedStylesElement, computedStylesElement)
188     {
189         matchedStylesElement.appendChild(this._createCSSFilterControl());
190         this._computedStylePane.setFilterBoxContainer(computedStylesElement);
191     },
192
193     /**
194      * @return {!Element}
195      */
196     _createCSSFilterControl: function()
197     {
198         var filterInput = this._createPropertyFilterElement(false, searchHandler.bind(this));
199
200         /**
201          * @param {?RegExp} regex
202          * @this {WebInspector.StylesSidebarPane}
203          */
204         function searchHandler(regex)
205         {
206             this._filterRegex = regex;
207         }
208
209         return filterInput;
210     },
211
212     get _forcedPseudoClasses()
213     {
214         return this.node ? (this.node.getUserProperty("pseudoState") || undefined) : undefined;
215     },
216
217     _updateForcedPseudoStateInputs: function()
218     {
219         if (!this.node)
220             return;
221
222         var hasPseudoType = !!this.node.pseudoType();
223         this._elementStateButton.classList.toggle("hidden", hasPseudoType);
224         this._elementStatePane.classList.toggle("expanded", !hasPseudoType && this._elementStateButton.classList.contains("toggled"));
225
226         var nodePseudoState = this._forcedPseudoClasses;
227         if (!nodePseudoState)
228             nodePseudoState = [];
229
230         var inputs = this._elementStatePane.inputs;
231         for (var i = 0; i < inputs.length; ++i)
232             inputs[i].checked = nodePseudoState.indexOf(inputs[i].state) >= 0;
233     },
234
235     /**
236      * @param {?WebInspector.DOMNode} node
237      * @param {boolean=} forceUpdate
238      */
239     update: function(node, forceUpdate)
240     {
241         this._spectrumHelper.hide();
242         this._discardElementUnderMouse();
243
244         var refresh = false;
245
246         if (forceUpdate)
247             delete this.node;
248
249         if (!forceUpdate && (node === this.node))
250             refresh = true;
251
252         if (node && node.nodeType() === Node.TEXT_NODE && node.parentNode)
253             node = node.parentNode;
254
255         if (node && node.nodeType() !== Node.ELEMENT_NODE)
256             node = null;
257
258         if (node)
259             this.node = node;
260         else
261             node = this.node;
262
263         this._updateForcedPseudoStateInputs();
264
265         if (refresh)
266             this._refreshUpdate();
267         else
268             this._rebuildUpdate();
269     },
270
271     /**
272      * @param {!WebInspector.StylePropertiesSection=} editedSection
273      * @param {boolean=} forceFetchComputedStyle
274      * @param {function()=} userCallback
275      */
276     _refreshUpdate: function(editedSection, forceFetchComputedStyle, userCallback)
277     {
278         var callbackWrapper = function()
279         {
280             if (this._filterRegex)
281                 this._updateFilter(false);
282             if (userCallback)
283                 userCallback();
284         }.bind(this);
285
286         if (this._refreshUpdateInProgress) {
287             this._lastNodeForInnerRefresh = this.node;
288             return;
289         }
290
291         var node = this._validateNode(userCallback);
292         if (!node)
293             return;
294
295         /**
296          * @param {?WebInspector.CSSStyleDeclaration} computedStyle
297          * @this {WebInspector.StylesSidebarPane}
298          */
299         function computedStyleCallback(computedStyle)
300         {
301             delete this._refreshUpdateInProgress;
302
303             if (this._lastNodeForInnerRefresh) {
304                 delete this._lastNodeForInnerRefresh;
305                 this._refreshUpdate(editedSection, forceFetchComputedStyle, callbackWrapper);
306                 return;
307             }
308
309             if (this.node === node && computedStyle)
310                 this._innerRefreshUpdate(node, computedStyle, editedSection);
311
312             callbackWrapper();
313         }
314
315         if (this._computedStylePane.isShowing() || forceFetchComputedStyle) {
316             this._refreshUpdateInProgress = true;
317             WebInspector.cssModel.getComputedStyleAsync(node.id, computedStyleCallback.bind(this));
318         } else {
319             this._innerRefreshUpdate(node, null, editedSection);
320             callbackWrapper();
321         }
322     },
323
324     _rebuildUpdate: function()
325     {
326         if (this._rebuildUpdateInProgress) {
327             this._lastNodeForInnerRebuild = this.node;
328             return;
329         }
330
331         var node = this._validateNode();
332         if (!node)
333             return;
334
335         this._rebuildUpdateInProgress = true;
336
337         var resultStyles = {};
338
339         /**
340          * @param {?*} matchedResult
341          * @this {WebInspector.StylesSidebarPane}
342          */
343         function stylesCallback(matchedResult)
344         {
345             delete this._rebuildUpdateInProgress;
346
347             var lastNodeForRebuild = this._lastNodeForInnerRebuild;
348             if (lastNodeForRebuild) {
349                 delete this._lastNodeForInnerRebuild;
350                 if (lastNodeForRebuild !== this.node) {
351                     this._rebuildUpdate();
352                     return;
353                 }
354             }
355
356             if (matchedResult && this.node === node) {
357                 resultStyles.matchedCSSRules = matchedResult.matchedCSSRules;
358                 resultStyles.pseudoElements = matchedResult.pseudoElements;
359                 resultStyles.inherited = matchedResult.inherited;
360                 this._innerRebuildUpdate(node, resultStyles);
361             }
362
363             if (lastNodeForRebuild) {
364                 // lastNodeForRebuild is the same as this.node - another rebuild has been requested.
365                 this._rebuildUpdate();
366                 return;
367             }
368         }
369
370         /**
371          * @param {?WebInspector.CSSStyleDeclaration} inlineStyle
372          * @param {?WebInspector.CSSStyleDeclaration} attributesStyle
373          */
374         function inlineCallback(inlineStyle, attributesStyle)
375         {
376             resultStyles.inlineStyle = inlineStyle;
377             resultStyles.attributesStyle = attributesStyle;
378         }
379
380         /**
381          * @param {?WebInspector.CSSStyleDeclaration} computedStyle
382          */
383         function computedCallback(computedStyle)
384         {
385             resultStyles.computedStyle = computedStyle;
386         }
387
388         if (this._computedStylePane.isShowing())
389             WebInspector.cssModel.getComputedStyleAsync(node.id, computedCallback);
390         WebInspector.cssModel.getInlineStylesAsync(node.id, inlineCallback);
391         WebInspector.cssModel.getMatchedStylesAsync(node.id, true, true, stylesCallback.bind(this));
392     },
393
394     /**
395      * @param {function()=} userCallback
396      */
397     _validateNode: function(userCallback)
398     {
399         if (!this.node) {
400             this._sectionsContainer.removeChildren();
401             this._computedStylePane.bodyElement.removeChildren();
402             this.sections = {};
403             if (userCallback)
404                 userCallback();
405             return null;
406         }
407         return this.node;
408     },
409
410     _styleSheetOrMediaQueryResultChanged: function()
411     {
412         if (this._userOperation || this._isEditingStyle)
413             return;
414
415         this._rebuildUpdate();
416     },
417
418     _frameResized: function()
419     {
420         /**
421          * @this {WebInspector.StylesSidebarPane}
422          */
423         function refreshContents()
424         {
425             this._rebuildUpdate();
426             delete this._activeTimer;
427         }
428
429         if (this._activeTimer)
430             clearTimeout(this._activeTimer);
431
432         this._activeTimer = setTimeout(refreshContents.bind(this), 100);
433     },
434
435     _attributeChanged: function(event)
436     {
437         // Any attribute removal or modification can affect the styles of "related" nodes.
438         // Do not touch the styles if they are being edited.
439         if (this._isEditingStyle || this._userOperation)
440             return;
441
442         if (!this._canAffectCurrentStyles(event.data.node))
443             return;
444
445         this._rebuildUpdate();
446     },
447
448     _canAffectCurrentStyles: function(node)
449     {
450         return this.node && (this.node === node || node.parentNode === this.node.parentNode || node.isAncestor(this.node));
451     },
452
453     _innerRefreshUpdate: function(node, computedStyle, editedSection)
454     {
455         for (var pseudoId in this.sections) {
456             var styleRules = this._refreshStyleRules(this.sections[pseudoId], computedStyle);
457             var usedProperties = {};
458             this._markUsedProperties(styleRules, usedProperties);
459             this._refreshSectionsForStyleRules(styleRules, usedProperties, editedSection);
460         }
461         if (computedStyle)
462             this.sections[0][0].rebuildComputedTrace(this.sections[0]);
463
464         this._nodeStylesUpdatedForTest(node, false);
465     },
466
467     _innerRebuildUpdate: function(node, styles)
468     {
469         this._sectionsContainer.removeChildren();
470         this._computedStylePane.bodyElement.removeChildren();
471         this._linkifier.reset();
472
473         var styleRules = this._rebuildStyleRules(node, styles);
474         var usedProperties = {};
475         this._markUsedProperties(styleRules, usedProperties);
476         this.sections[0] = this._rebuildSectionsForStyleRules(styleRules, usedProperties, null);
477         var anchorElement = this.sections[0].inheritedPropertiesSeparatorElement;
478
479         if (styles.computedStyle)
480             this.sections[0][0].rebuildComputedTrace(this.sections[0]);
481
482         for (var i = 0; i < styles.pseudoElements.length; ++i) {
483             var pseudoElementCSSRules = styles.pseudoElements[i];
484
485             styleRules = [];
486             var pseudoId = pseudoElementCSSRules.pseudoId;
487
488             var entry = { isStyleSeparator: true, pseudoId: pseudoId };
489             styleRules.push(entry);
490
491             // Add rules in reverse order to match the cascade order.
492             for (var j = pseudoElementCSSRules.rules.length - 1; j >= 0; --j) {
493                 var rule = pseudoElementCSSRules.rules[j];
494                 styleRules.push({ style: rule.style, selectorText: rule.selectorText, media: rule.media, sourceURL: rule.resourceURL(), rule: rule, editable: !!(rule.style && rule.style.id) });
495             }
496             usedProperties = {};
497             this._markUsedProperties(styleRules, usedProperties);
498             this.sections[pseudoId] = this._rebuildSectionsForStyleRules(styleRules, usedProperties, anchorElement);
499         }
500
501         if (this._filterRegex)
502             this._updateFilter(false);
503         this._nodeStylesUpdatedForTest(node, true);
504     },
505
506     _nodeStylesUpdatedForTest: function(node, rebuild)
507     {
508         // Tests override this method.
509     },
510
511     _refreshStyleRules: function(sections, computedStyle)
512     {
513         var nodeComputedStyle = computedStyle;
514         var styleRules = [];
515         for (var i = 0; sections && i < sections.length; ++i) {
516             var section = sections[i];
517             if (section.isBlank)
518                 continue;
519             if (section.computedStyle)
520                 section.styleRule.style = nodeComputedStyle;
521             var styleRule = { section: section, style: section.styleRule.style, computedStyle: section.computedStyle, rule: section.rule, editable: !!(section.styleRule.style && section.styleRule.style.id),
522                 isAttribute: section.styleRule.isAttribute, isInherited: section.styleRule.isInherited, parentNode: section.styleRule.parentNode };
523             styleRules.push(styleRule);
524         }
525         return styleRules;
526     },
527
528     _rebuildStyleRules: function(node, styles)
529     {
530         var nodeComputedStyle = styles.computedStyle;
531         this.sections = {};
532
533         var styleRules = [];
534
535         function addAttributesStyle()
536         {
537             if (!styles.attributesStyle)
538                 return;
539             var attrStyle = { style: styles.attributesStyle, editable: false };
540             attrStyle.selectorText = node.nodeNameInCorrectCase() + "[" + WebInspector.UIString("Attributes Style") + "]";
541             styleRules.push(attrStyle);
542         }
543
544         styleRules.push({ computedStyle: true, selectorText: "", style: nodeComputedStyle, editable: false });
545
546         if (!!node.pseudoType())
547             styleRules.push({ isStyleSeparator: true, isPlaceholder: true });
548
549         // Inline style has the greatest specificity.
550         if (styles.inlineStyle && node.nodeType() === Node.ELEMENT_NODE) {
551             var inlineStyle = { selectorText: "element.style", style: styles.inlineStyle, isAttribute: true };
552             styleRules.push(inlineStyle);
553         }
554
555         // Add rules in reverse order to match the cascade order.
556         var addedAttributesStyle;
557         for (var i = styles.matchedCSSRules.length - 1; i >= 0; --i) {
558             var rule = styles.matchedCSSRules[i];
559             if ((rule.isUser || rule.isUserAgent) && !addedAttributesStyle) {
560                 // Show element's Style Attributes after all author rules.
561                 addedAttributesStyle = true;
562                 addAttributesStyle();
563             }
564             styleRules.push({ style: rule.style, selectorText: rule.selectorText, media: rule.media, sourceURL: rule.resourceURL(), rule: rule, editable: !!(rule.style && rule.style.id) });
565         }
566
567         if (!addedAttributesStyle)
568             addAttributesStyle();
569
570         // Walk the node structure and identify styles with inherited properties.
571         var parentNode = node.parentNode;
572         function insertInheritedNodeSeparator(node)
573         {
574             var entry = {};
575             entry.isStyleSeparator = true;
576             entry.node = node;
577             styleRules.push(entry);
578         }
579
580         for (var parentOrdinal = 0; parentOrdinal < styles.inherited.length; ++parentOrdinal) {
581             var parentStyles = styles.inherited[parentOrdinal];
582             var separatorInserted = false;
583             if (parentStyles.inlineStyle) {
584                 if (this._containsInherited(parentStyles.inlineStyle)) {
585                     var inlineStyle = { selectorText: WebInspector.UIString("Style Attribute"), style: parentStyles.inlineStyle, isAttribute: true, isInherited: true, parentNode: parentNode };
586                     if (!separatorInserted) {
587                         insertInheritedNodeSeparator(parentNode);
588                         separatorInserted = true;
589                     }
590                     styleRules.push(inlineStyle);
591                 }
592             }
593
594             for (var i = parentStyles.matchedCSSRules.length - 1; i >= 0; --i) {
595                 var rulePayload = parentStyles.matchedCSSRules[i];
596                 if (!this._containsInherited(rulePayload.style))
597                     continue;
598                 var rule = rulePayload;
599
600                 if (!separatorInserted) {
601                     insertInheritedNodeSeparator(parentNode);
602                     separatorInserted = true;
603                 }
604                 styleRules.push({ style: rule.style, selectorText: rule.selectorText, media: rule.media, sourceURL: rule.resourceURL(), rule: rule, isInherited: true, parentNode: parentNode, editable: !!(rule.style && rule.style.id) });
605             }
606             parentNode = parentNode.parentNode;
607         }
608         return styleRules;
609     },
610
611     _markUsedProperties: function(styleRules, usedProperties)
612     {
613         var foundImportantProperties = {};
614         var propertyToEffectiveRule = {};
615         var inheritedPropertyToNode = {};
616         for (var i = 0; i < styleRules.length; ++i) {
617             var styleRule = styleRules[i];
618             if (styleRule.computedStyle || styleRule.isStyleSeparator)
619                 continue;
620             if (styleRule.section && styleRule.section.noAffect)
621                 continue;
622
623             styleRule.usedProperties = {};
624
625             var style = styleRule.style;
626             var allProperties = style.allProperties;
627             for (var j = 0; j < allProperties.length; ++j) {
628                 var property = allProperties[j];
629                 if (!property.isLive || !property.parsedOk)
630                     continue;
631
632                 // Do not pick non-inherited properties from inherited styles.
633                 if (styleRule.isInherited && !WebInspector.CSSMetadata.isPropertyInherited(property.name))
634                     continue;
635
636                 var canonicalName = WebInspector.CSSMetadata.canonicalPropertyName(property.name);
637                 if (foundImportantProperties.hasOwnProperty(canonicalName))
638                     continue;
639
640                 if (!property.important && usedProperties.hasOwnProperty(canonicalName))
641                     continue;
642
643                 var isKnownProperty = propertyToEffectiveRule.hasOwnProperty(canonicalName);
644                 if (!isKnownProperty && styleRule.isInherited && !inheritedPropertyToNode[canonicalName])
645                     inheritedPropertyToNode[canonicalName] = styleRule.parentNode;
646
647                 if (property.important) {
648                     if (styleRule.isInherited && isKnownProperty && styleRule.parentNode !== inheritedPropertyToNode[canonicalName])
649                         continue;
650
651                     foundImportantProperties[canonicalName] = true;
652                     if (isKnownProperty)
653                         delete propertyToEffectiveRule[canonicalName].usedProperties[canonicalName];
654                 }
655
656                 styleRule.usedProperties[canonicalName] = true;
657                 usedProperties[canonicalName] = true;
658                 propertyToEffectiveRule[canonicalName] = styleRule;
659             }
660         }
661     },
662
663     _refreshSectionsForStyleRules: function(styleRules, usedProperties, editedSection)
664     {
665         // Walk the style rules and update the sections with new overloaded and used properties.
666         for (var i = 0; i < styleRules.length; ++i) {
667             var styleRule = styleRules[i];
668             var section = styleRule.section;
669             if (styleRule.computedStyle) {
670                 section._usedProperties = usedProperties;
671                 section.update();
672             } else {
673                 section._usedProperties = styleRule.usedProperties;
674                 section.update(section === editedSection);
675             }
676         }
677     },
678
679     /**
680      * @param {!Array.<!Object>} styleRules
681      * @param {!Object.<string, boolean>} usedProperties
682      * @param {?Element} anchorElement
683      */
684     _rebuildSectionsForStyleRules: function(styleRules, usedProperties, anchorElement)
685     {
686         // Make a property section for each style rule.
687         var sections = [];
688         for (var i = 0; i < styleRules.length; ++i) {
689             var styleRule = styleRules[i];
690             if (styleRule.isStyleSeparator) {
691                 var separatorElement = document.createElement("div");
692                 if (styleRule.isPlaceholder) {
693                     separatorElement.className = "styles-sidebar-placeholder";
694                     this._sectionsContainer.insertBefore(separatorElement, anchorElement);
695                     continue;
696                 }
697                 separatorElement.className = "sidebar-separator";
698                 if (styleRule.node) {
699                     var link = WebInspector.DOMPresentationUtils.linkifyNodeReference(styleRule.node);
700                     separatorElement.appendChild(document.createTextNode(WebInspector.UIString("Inherited from") + " "));
701                     separatorElement.appendChild(link);
702                     if (!sections.inheritedPropertiesSeparatorElement)
703                         sections.inheritedPropertiesSeparatorElement = separatorElement;
704                 } else if ("pseudoId" in styleRule) {
705                     var pseudoName = WebInspector.StylesSidebarPane.PseudoIdNames[styleRule.pseudoId];
706                     if (pseudoName)
707                         separatorElement.textContent = WebInspector.UIString("Pseudo ::%s element", pseudoName);
708                     else
709                         separatorElement.textContent = WebInspector.UIString("Pseudo element");
710                 } else
711                     separatorElement.textContent = styleRule.text;
712                 this._sectionsContainer.insertBefore(separatorElement, anchorElement);
713                 continue;
714             }
715             var computedStyle = styleRule.computedStyle;
716
717             // Default editable to true if it was omitted.
718             var editable = styleRule.editable;
719             if (typeof editable === "undefined")
720                 editable = true;
721
722             if (computedStyle)
723                 var section = new WebInspector.ComputedStylePropertiesSection(this, styleRule, usedProperties);
724             else {
725                 var section = new WebInspector.StylePropertiesSection(this, styleRule, editable, styleRule.isInherited);
726                 section._markSelectorMatches();
727             }
728             section.expanded = true;
729
730             if (computedStyle)
731                 this._computedStylePane.bodyElement.appendChild(section.element);
732             else
733                 this._sectionsContainer.insertBefore(section.element, anchorElement);
734             sections.push(section);
735         }
736         return sections;
737     },
738
739     _containsInherited: function(style)
740     {
741         var properties = style.allProperties;
742         for (var i = 0; i < properties.length; ++i) {
743             var property = properties[i];
744             // Does this style contain non-overridden inherited property?
745             if (property.isLive && WebInspector.CSSMetadata.isPropertyInherited(property.name))
746                 return true;
747         }
748         return false;
749     },
750
751     _colorFormatSettingChanged: function(event)
752     {
753         for (var pseudoId in this.sections) {
754             var sections = this.sections[pseudoId];
755             for (var i = 0; i < sections.length; ++i)
756                 sections[i].update(true);
757         }
758     },
759
760     _createNewRule: function(event)
761     {
762         event.consume();
763         this.expand();
764         this.addBlankSection().startEditingSelector();
765     },
766
767     /**
768      * @return {!WebInspector.BlankStylePropertiesSection}
769      */
770     addBlankSection: function()
771     {
772         var blankSection = new WebInspector.BlankStylePropertiesSection(this, this.node ? WebInspector.DOMPresentationUtils.simpleSelector(this.node) : "");
773
774         var elementStyleSection = this.sections[0][1];
775         this._sectionsContainer.insertBefore(blankSection.element, elementStyleSection.element.nextSibling);
776
777         this.sections[0].splice(2, 0, blankSection);
778
779         return blankSection;
780     },
781
782     removeSection: function(section)
783     {
784         for (var pseudoId in this.sections) {
785             var sections = this.sections[pseudoId];
786             var index = sections.indexOf(section);
787             if (index === -1)
788                 continue;
789             sections.splice(index, 1);
790             section.element.remove();
791         }
792     },
793
794     _toggleElementStatePane: function(event)
795     {
796         event.consume();
797
798         var buttonToggled = !this._elementStateButton.classList.contains("toggled");
799         if (buttonToggled)
800             this.expand();
801         this._elementStateButton.classList.toggle("toggled", buttonToggled);
802         this._elementStatePane.classList.toggle("expanded", buttonToggled);
803     },
804
805     _createElementStatePane: function()
806     {
807         this._elementStatePane = document.createElement("div");
808         this._elementStatePane.className = "styles-element-state-pane source-code";
809         var table = document.createElement("table");
810
811         var inputs = [];
812         this._elementStatePane.inputs = inputs;
813
814         /**
815          * @param {?Event} event
816          * @this {WebInspector.StylesSidebarPane}
817          */
818         function clickListener(event)
819         {
820             var node = this._validateNode();
821             if (!node)
822                 return;
823             this._setPseudoClassCallback(node.id, event.target.state, event.target.checked);
824         }
825
826         /**
827          * @param {string} state
828          * @return {!Element}
829          * @this {WebInspector.StylesSidebarPane}
830          */
831         function createCheckbox(state)
832         {
833             var td = document.createElement("td");
834             var label = document.createElement("label");
835             var input = document.createElement("input");
836             input.type = "checkbox";
837             input.state = state;
838             input.addEventListener("click", clickListener.bind(this), false);
839             inputs.push(input);
840             label.appendChild(input);
841             label.appendChild(document.createTextNode(":" + state));
842             td.appendChild(label);
843             return td;
844         }
845
846         var tr = table.createChild("tr");
847         tr.appendChild(createCheckbox.call(this, "active"));
848         tr.appendChild(createCheckbox.call(this, "hover"));
849
850         tr = table.createChild("tr");
851         tr.appendChild(createCheckbox.call(this, "focus"));
852         tr.appendChild(createCheckbox.call(this, "visited"));
853
854         this._elementStatePane.appendChild(table);
855     },
856
857     /**
858      * @return {?RegExp}
859      */
860     filterRegex: function()
861     {
862         return this._filterRegex;
863     },
864
865     /**
866      * @param {boolean} isComputedStyleFilter
867      * @return {!Element}
868      * @param {function(?RegExp)} filterCallback
869      */
870     _createPropertyFilterElement: function(isComputedStyleFilter, filterCallback)
871     {
872         var input = document.createElement("input");
873         input.type = "text";
874         input.placeholder = isComputedStyleFilter ? WebInspector.UIString("Filter") : WebInspector.UIString("Find in Styles");
875         var boundSearchHandler = searchHandler.bind(this);
876
877         /**
878          * @this {WebInspector.StylesSidebarPane}
879          */
880         function searchHandler()
881         {
882             var regex = input.value ? new RegExp(input.value.escapeForRegExp(), "i") : null;
883             filterCallback(regex);
884             input.parentNode.classList.toggle("styles-filter-engaged", !!input.value);
885             this._updateFilter(isComputedStyleFilter);
886         }
887         input.addEventListener("input", boundSearchHandler, false);
888
889         /**
890          * @param {?Event} event
891          */
892         function keydownHandler(event)
893         {
894             var Esc = "U+001B";
895             if (event.keyIdentifier !== Esc || !input.value)
896                 return;
897             event.consume(true);
898             input.value = "";
899             boundSearchHandler();
900         }
901         input.addEventListener("keydown", keydownHandler, false);
902
903         return input;
904     },
905
906     /**
907      * @param {boolean} isComputedStyleFilter
908      */
909     _updateFilter: function(isComputedStyleFilter)
910     {
911         for (var pseudoId in this.sections) {
912             var sections = this.sections[pseudoId];
913             for (var i = 0; i < sections.length; ++i) {
914                 var section = sections[i];
915                 if (isComputedStyleFilter !== !!section.computedStyle)
916                     continue;
917                 section._updateFilter();
918             }
919         }
920     },
921
922     /**
923      * @param {!WebInspector.Event} event
924      */
925     _showUserAgentStylesSettingChanged: function(event)
926     {
927         var showStyles = /** @type {boolean} */ (event.data);
928         this.element.classList.toggle("show-user-styles", showStyles);
929     },
930
931     willHide: function()
932     {
933         this._spectrumHelper.hide();
934         this._discardElementUnderMouse();
935     },
936
937     _discardElementUnderMouse: function()
938     {
939         if (this._elementUnderMouse)
940             this._elementUnderMouse.classList.remove("styles-panel-hovered");
941         delete this._elementUnderMouse;
942     },
943
944     _mouseMovedOverElement: function(e)
945     {
946         if (this._elementUnderMouse && e.target !== this._elementUnderMouse)
947             this._discardElementUnderMouse();
948         this._elementUnderMouse = e.target;
949         if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(e))
950             this._elementUnderMouse.classList.add("styles-panel-hovered");
951     },
952
953     _keyDown: function(e)
954     {
955         if ((!WebInspector.isMac() && e.keyCode === WebInspector.KeyboardShortcut.Keys.Ctrl.code) ||
956             (WebInspector.isMac() && e.keyCode === WebInspector.KeyboardShortcut.Keys.Meta.code)) {
957             if (this._elementUnderMouse)
958                 this._elementUnderMouse.classList.add("styles-panel-hovered");
959         }
960     },
961
962     _keyUp: function(e)
963     {
964         if ((!WebInspector.isMac() && e.keyCode === WebInspector.KeyboardShortcut.Keys.Ctrl.code) ||
965             (WebInspector.isMac() && e.keyCode === WebInspector.KeyboardShortcut.Keys.Meta.code)) {
966             this._discardElementUnderMouse();
967         }
968     },
969
970     __proto__: WebInspector.SidebarPane.prototype
971 }
972
973 /**
974  * @constructor
975  * @extends {WebInspector.SidebarPane}
976  */
977 WebInspector.ComputedStyleSidebarPane = function()
978 {
979     WebInspector.SidebarPane.call(this, WebInspector.UIString("Computed Style"));
980 }
981
982 WebInspector.ComputedStyleSidebarPane.prototype = {
983     /**
984      * @param {!WebInspector.StylesSidebarPane} pane
985      */
986     setHostingPane: function(pane)
987     {
988         this._stylesSidebarPane = pane;
989     },
990
991     setFilterBoxContainer: function(element)
992     {
993         element.appendChild(this._stylesSidebarPane._createPropertyFilterElement(true, filterCallback.bind(this)));
994
995         /**
996          * @param {?RegExp} regex
997          * @this {WebInspector.ComputedStyleSidebarPane}
998          */
999         function filterCallback(regex)
1000         {
1001             this._filterRegex = regex;
1002         }
1003     },
1004
1005     wasShown: function()
1006     {
1007         WebInspector.SidebarPane.prototype.wasShown.call(this);
1008         if (!this._hasFreshContent)
1009             this.prepareContent();
1010     },
1011
1012     /**
1013      * @param {function()=} callback
1014      */
1015     prepareContent: function(callback)
1016     {
1017         /**
1018          * @this {WebInspector.ComputedStyleSidebarPane}
1019          */
1020         function wrappedCallback() {
1021             this._hasFreshContent = true;
1022             if (callback)
1023                 callback();
1024             delete this._hasFreshContent;
1025         }
1026         this._stylesSidebarPane._refreshUpdate(null, true, wrappedCallback.bind(this));
1027     },
1028
1029     /**
1030      * @return {?RegExp}
1031      */
1032     filterRegex: function()
1033     {
1034         return this._filterRegex;
1035     },
1036
1037     __proto__: WebInspector.SidebarPane.prototype
1038 }
1039
1040 /**
1041  * @constructor
1042  * @extends {WebInspector.PropertiesSection}
1043  * @param {!WebInspector.StylesSidebarPane} parentPane
1044  * @param {!Object} styleRule
1045  * @param {boolean} editable
1046  * @param {boolean} isInherited
1047  */
1048 WebInspector.StylePropertiesSection = function(parentPane, styleRule, editable, isInherited)
1049 {
1050     WebInspector.PropertiesSection.call(this, "");
1051
1052     this._parentPane = parentPane;
1053     this.styleRule = styleRule;
1054     this.rule = this.styleRule.rule;
1055     this.editable = editable;
1056     this.isInherited = isInherited;
1057
1058     var extraClasses = (this.rule && (this.rule.isUser || this.rule.isUserAgent) ? " user-rule" : "");
1059     this.element.className = "styles-section matched-styles monospace" + extraClasses;
1060     // We don't really use properties' disclosure.
1061     this.propertiesElement.classList.remove("properties-tree");
1062
1063     if (styleRule.media) {
1064         for (var i = styleRule.media.length - 1; i >= 0; --i) {
1065             var media = styleRule.media[i];
1066             var mediaDataElement = this.titleElement.createChild("div", "media");
1067             var mediaText;
1068             switch (media.source) {
1069             case WebInspector.CSSMedia.Source.LINKED_SHEET:
1070             case WebInspector.CSSMedia.Source.INLINE_SHEET:
1071                 mediaText = "media=\"" + media.text + "\"";
1072                 break;
1073             case WebInspector.CSSMedia.Source.MEDIA_RULE:
1074                 mediaText = "@media " + media.text;
1075                 break;
1076             case WebInspector.CSSMedia.Source.IMPORT_RULE:
1077                 mediaText = "@import " + media.text;
1078                 break;
1079             }
1080
1081             if (media.sourceURL) {
1082                 var refElement = mediaDataElement.createChild("div", "subtitle");
1083                 var rawLocation;
1084                 var mediaHeader;
1085                 if (media.range) {
1086                     mediaHeader = media.header();
1087                     if (mediaHeader) {
1088                         var lineNumber = media.lineNumberInSource();
1089                         var columnNumber = media.columnNumberInSource();
1090                         console.assert(typeof lineNumber !== "undefined" && typeof columnNumber !== "undefined");
1091                         rawLocation = new WebInspector.CSSLocation(media.sourceURL, lineNumber, columnNumber);
1092                     }
1093                 }
1094
1095                 var anchor;
1096                 if (rawLocation)
1097                     anchor = this._parentPane._linkifier.linkifyCSSLocation(mediaHeader.id, rawLocation);
1098                 else {
1099                     // The "linkedStylesheet" case.
1100                     anchor = WebInspector.linkifyResourceAsNode(media.sourceURL, undefined, "subtitle", media.sourceURL);
1101                 }
1102                 anchor.style.float = "right";
1103                 refElement.appendChild(anchor);
1104             }
1105
1106             var mediaTextElement = mediaDataElement.createChild("span");
1107             mediaTextElement.textContent = mediaText;
1108             mediaTextElement.title = media.text;
1109         }
1110     }
1111
1112     var selectorContainer = document.createElement("div");
1113     this._selectorElement = document.createElement("span");
1114     this._selectorElement.textContent = styleRule.selectorText;
1115     selectorContainer.appendChild(this._selectorElement);
1116
1117     var openBrace = document.createElement("span");
1118     openBrace.textContent = " {";
1119     selectorContainer.appendChild(openBrace);
1120     selectorContainer.addEventListener("mousedown", this._handleEmptySpaceMouseDown.bind(this), false);
1121     selectorContainer.addEventListener("click", this._handleSelectorContainerClick.bind(this), false);
1122
1123     var closeBrace = document.createElement("div");
1124     closeBrace.textContent = "}";
1125     this.element.appendChild(closeBrace);
1126
1127     this._selectorElement.addEventListener("click", this._handleSelectorClick.bind(this), false);
1128     this.element.addEventListener("mousedown", this._handleEmptySpaceMouseDown.bind(this), false);
1129     this.element.addEventListener("click", this._handleEmptySpaceClick.bind(this), false);
1130
1131     if (this.rule) {
1132         // Prevent editing the user agent and user rules.
1133         if (this.rule.isUserAgent || this.rule.isUser)
1134             this.editable = false;
1135         else {
1136             // Check this is a real CSSRule, not a bogus object coming from WebInspector.BlankStylePropertiesSection.
1137             if (this.rule.id)
1138                 this.navigable = !!this.rule.resourceURL();
1139         }
1140         this.titleElement.classList.add("styles-selector");
1141     }
1142
1143     this._usedProperties = styleRule.usedProperties;
1144
1145     this._selectorRefElement = document.createElement("div");
1146     this._selectorRefElement.className = "subtitle";
1147     this._updateRuleOrigin();
1148     selectorContainer.insertBefore(this._selectorRefElement, selectorContainer.firstChild);
1149     this.titleElement.appendChild(selectorContainer);
1150     this._selectorContainer = selectorContainer;
1151
1152     if (isInherited)
1153         this.element.classList.add("styles-show-inherited"); // This one is related to inherited rules, not computed style.
1154
1155     if (this.navigable)
1156         this.element.classList.add("navigable");
1157
1158     if (!this.editable)
1159         this.element.classList.add("read-only");
1160 }
1161
1162 WebInspector.StylePropertiesSection.prototype = {
1163     get pane()
1164     {
1165         return this._parentPane;
1166     },
1167
1168     collapse: function()
1169     {
1170         // Overriding with empty body.
1171     },
1172
1173     handleClick: function()
1174     {
1175         // Avoid consuming events.
1176     },
1177
1178     /**
1179      * @param {string} propertyName
1180      * @return {boolean}
1181      */
1182     isPropertyInherited: function(propertyName)
1183     {
1184         if (this.isInherited) {
1185             // While rendering inherited stylesheet, reverse meaning of this property.
1186             // Render truly inherited properties with black, i.e. return them as non-inherited.
1187             return !WebInspector.CSSMetadata.isPropertyInherited(propertyName);
1188         }
1189         return false;
1190     },
1191
1192     /**
1193      * @param {string} propertyName
1194      * @param {boolean=} isShorthand
1195      * @return {boolean}
1196      */
1197     isPropertyOverloaded: function(propertyName, isShorthand)
1198     {
1199         if (!this._usedProperties || this.noAffect)
1200             return false;
1201
1202         if (this.isInherited && !WebInspector.CSSMetadata.isPropertyInherited(propertyName)) {
1203             // In the inherited sections, only show overrides for the potentially inherited properties.
1204             return false;
1205         }
1206
1207         var canonicalName = WebInspector.CSSMetadata.canonicalPropertyName(propertyName);
1208         var used = (canonicalName in this._usedProperties);
1209         if (used || !isShorthand)
1210             return !used;
1211
1212         // Find out if any of the individual longhand properties of the shorthand
1213         // are used, if none are then the shorthand is overloaded too.
1214         var longhandProperties = this.styleRule.style.longhandProperties(propertyName);
1215         for (var j = 0; j < longhandProperties.length; ++j) {
1216             var individualProperty = longhandProperties[j];
1217             if (WebInspector.CSSMetadata.canonicalPropertyName(individualProperty.name) in this._usedProperties)
1218                 return false;
1219         }
1220
1221         return true;
1222     },
1223
1224     /**
1225      * @return {?WebInspector.StylePropertiesSection}
1226      */
1227     nextEditableSibling: function()
1228     {
1229         var curSection = this;
1230         do {
1231             curSection = curSection.nextSibling;
1232         } while (curSection && !curSection.editable);
1233
1234         if (!curSection) {
1235             curSection = this.firstSibling;
1236             while (curSection && !curSection.editable)
1237                 curSection = curSection.nextSibling;
1238         }
1239
1240         return (curSection && curSection.editable) ? curSection : null;
1241     },
1242
1243     /**
1244      * @return {?WebInspector.StylePropertiesSection}
1245      */
1246     previousEditableSibling: function()
1247     {
1248         var curSection = this;
1249         do {
1250             curSection = curSection.previousSibling;
1251         } while (curSection && !curSection.editable);
1252
1253         if (!curSection) {
1254             curSection = this.lastSibling;
1255             while (curSection && !curSection.editable)
1256                 curSection = curSection.previousSibling;
1257         }
1258
1259         return (curSection && curSection.editable) ? curSection : null;
1260     },
1261
1262     update: function(full)
1263     {
1264         if (this.styleRule.selectorText)
1265             this._selectorElement.textContent = this.styleRule.selectorText;
1266         this._markSelectorMatches();
1267         if (full) {
1268             this.propertiesTreeOutline.removeChildren();
1269             this.populated = false;
1270         } else {
1271             var child = this.propertiesTreeOutline.children[0];
1272             while (child) {
1273                 child.overloaded = this.isPropertyOverloaded(child.name, child.isShorthand);
1274                 child = child.traverseNextTreeElement(false, null, true);
1275             }
1276         }
1277         this.afterUpdate();
1278     },
1279
1280     afterUpdate: function()
1281     {
1282         if (this._afterUpdate) {
1283             this._afterUpdate(this);
1284             delete this._afterUpdate;
1285         }
1286     },
1287
1288     onpopulate: function()
1289     {
1290         var style = this.styleRule.style;
1291         var allProperties = style.allProperties;
1292         this.uniqueProperties = [];
1293
1294         var styleHasEditableSource = this.editable && !!style.range;
1295         if (styleHasEditableSource) {
1296             for (var i = 0; i < allProperties.length; ++i) {
1297                 var property = allProperties[i];
1298                 this.uniqueProperties.push(property);
1299                 if (property.styleBased)
1300                     continue;
1301
1302                 var isShorthand = !!WebInspector.CSSMetadata.cssPropertiesMetainfo.longhands(property.name);
1303                 var inherited = this.isPropertyInherited(property.name);
1304                 var overloaded = property.inactive || this.isPropertyOverloaded(property.name);
1305                 var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this.styleRule, style, property, isShorthand, inherited, overloaded);
1306                 this.propertiesTreeOutline.appendChild(item);
1307             }
1308             return;
1309         }
1310
1311         var generatedShorthands = {};
1312         // For style-based properties, generate shorthands with values when possible.
1313         for (var i = 0; i < allProperties.length; ++i) {
1314             var property = allProperties[i];
1315             this.uniqueProperties.push(property);
1316             var isShorthand = !!WebInspector.CSSMetadata.cssPropertiesMetainfo.longhands(property.name);
1317
1318             // For style-based properties, try generating shorthands.
1319             var shorthands = isShorthand ? null : WebInspector.CSSMetadata.cssPropertiesMetainfo.shorthands(property.name);
1320             var shorthandPropertyAvailable = false;
1321             for (var j = 0; shorthands && !shorthandPropertyAvailable && j < shorthands.length; ++j) {
1322                 var shorthand = shorthands[j];
1323                 if (shorthand in generatedShorthands) {
1324                     shorthandPropertyAvailable = true;
1325                     continue;  // There already is a shorthand this longhands falls under.
1326                 }
1327                 if (style.getLiveProperty(shorthand)) {
1328                     shorthandPropertyAvailable = true;
1329                     continue;  // There is an explict shorthand property this longhands falls under.
1330                 }
1331                 if (!style.shorthandValue(shorthand)) {
1332                     shorthandPropertyAvailable = false;
1333                     continue;  // Never generate synthetic shorthands when no value is available.
1334                 }
1335
1336                 // Generate synthetic shorthand we have a value for.
1337                 var shorthandProperty = new WebInspector.CSSProperty(style, style.allProperties.length, shorthand, style.shorthandValue(shorthand), false, false, true, true);
1338                 var overloaded = property.inactive || this.isPropertyOverloaded(property.name, true);
1339                 var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this.styleRule, style, shorthandProperty,  /* isShorthand */ true, /* inherited */ false, overloaded);
1340                 this.propertiesTreeOutline.appendChild(item);
1341                 generatedShorthands[shorthand] = shorthandProperty;
1342                 shorthandPropertyAvailable = true;
1343             }
1344             if (shorthandPropertyAvailable)
1345                 continue;  // Shorthand for the property found.
1346
1347             var inherited = this.isPropertyInherited(property.name);
1348             var overloaded = property.inactive || this.isPropertyOverloaded(property.name, isShorthand);
1349             var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this.styleRule, style, property, isShorthand, inherited, overloaded);
1350             this.propertiesTreeOutline.appendChild(item);
1351         }
1352     },
1353
1354     _updateFilter: function()
1355     {
1356         if (this.styleRule.isAttribute)
1357             return;
1358         var regex = this._parentPane.filterRegex();
1359         var hideRule = regex && !regex.test(this.element.textContent);
1360         this.element.classList.toggle("hidden", hideRule);
1361         if (hideRule)
1362             return;
1363
1364         var children = this.propertiesTreeOutline.children;
1365         for (var i = 0; i < children.length; ++i)
1366             children[i]._updateFilter();
1367
1368         if (this.styleRule.rule)
1369             this._markSelectorHighlights();
1370     },
1371
1372     _markSelectorMatches: function()
1373     {
1374         var rule = this.styleRule.rule;
1375         if (!rule)
1376             return;
1377
1378         var matchingSelectors = rule.matchingSelectors;
1379         // .selector is rendered as non-affecting selector by default.
1380         if (this.noAffect || matchingSelectors)
1381             this._selectorElement.className = "selector";
1382         if (!matchingSelectors)
1383             return;
1384
1385         var selectors = rule.selectors;
1386         var fragment = document.createDocumentFragment();
1387         var currentMatch = 0;
1388         for (var i = 0; i < selectors.length ; ++i) {
1389             if (i)
1390                 fragment.appendChild(document.createTextNode(", "));
1391             var isSelectorMatching = matchingSelectors[currentMatch] === i;
1392             if (isSelectorMatching)
1393                 ++currentMatch;
1394             var rawLocation = new WebInspector.CSSLocation(rule.sourceURL, rule.lineNumberInSource(i), rule.columnNumberInSource(i));
1395             var matchingSelectorClass = isSelectorMatching ? " selector-matches" : "";
1396             var selectorElement = document.createElement("span");
1397             selectorElement.className = "simple-selector" + matchingSelectorClass;
1398             if (rule.id)
1399                 selectorElement._selectorIndex = i;
1400             selectorElement.textContent = selectors[i].value;
1401
1402             fragment.appendChild(selectorElement);
1403         }
1404
1405         this._selectorElement.removeChildren();
1406         this._selectorElement.appendChild(fragment);
1407         this._markSelectorHighlights();
1408     },
1409
1410     _markSelectorHighlights: function()
1411     {
1412         var selectors = this._selectorElement.getElementsByClassName("simple-selector");
1413         var regex = this.pane.filterRegex();
1414         for (var i = 0; i < selectors.length; ++i) {
1415             var selectorMatchesFilter = regex && regex.test(selectors[i].textContent);
1416             selectors[i].classList.toggle("filter-match", selectorMatchesFilter);
1417         }
1418     },
1419
1420     _checkWillCancelEditing: function()
1421     {
1422         var willCauseCancelEditing = this._willCauseCancelEditing;
1423         delete this._willCauseCancelEditing;
1424         return willCauseCancelEditing;
1425     },
1426
1427     _handleSelectorContainerClick: function(event)
1428     {
1429         if (this._checkWillCancelEditing() || !this.editable)
1430             return;
1431         if (event.target === this._selectorContainer)
1432             this.addNewBlankProperty(0).startEditing();
1433     },
1434
1435     /**
1436      * @param {number=} index
1437      * @return {!WebInspector.StylePropertyTreeElement}
1438      */
1439     addNewBlankProperty: function(index)
1440     {
1441         var style = this.styleRule.style;
1442         var property = style.newBlankProperty(index);
1443         var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this.styleRule, style, property, false, false, false);
1444         index = property.index;
1445         this.propertiesTreeOutline.insertChild(item, index);
1446         item.listItemElement.textContent = "";
1447         item._newProperty = true;
1448         item.updateTitle();
1449         return item;
1450     },
1451
1452     _createRuleOriginNode: function()
1453     {
1454         /**
1455          * @param {string} url
1456          * @param {number} line
1457          */
1458         function linkifyUncopyable(url, line)
1459         {
1460             var link = WebInspector.linkifyResourceAsNode(url, line, "", url + ":" + (line + 1));
1461             link.classList.add("webkit-html-resource-link");
1462             link.setAttribute("data-uncopyable", link.textContent);
1463             link.textContent = "";
1464             return link;
1465         }
1466
1467         if (this.styleRule.sourceURL) {
1468             var firstMatchingIndex = this.styleRule.rule.matchingSelectors && this.rule.matchingSelectors.length ? this.rule.matchingSelectors[0] : 0;
1469             var matchingSelectorLocation = new WebInspector.CSSLocation(this.styleRule.sourceURL, this.rule.lineNumberInSource(firstMatchingIndex), this.rule.columnNumberInSource(firstMatchingIndex));
1470             return this._parentPane._linkifier.linkifyCSSLocation(this.rule.id.styleSheetId, matchingSelectorLocation) || linkifyUncopyable(this.styleRule.sourceURL, this.rule.lineNumberInSource());
1471         }
1472
1473         if (!this.rule)
1474             return document.createTextNode("");
1475
1476         if (this.rule.isUserAgent)
1477             return document.createTextNode(WebInspector.UIString("user agent stylesheet"));
1478         if (this.rule.isUser)
1479             return document.createTextNode(WebInspector.UIString("user stylesheet"));
1480         if (this.rule.isViaInspector)
1481             return document.createTextNode(WebInspector.UIString("via inspector"));
1482         return document.createTextNode("");
1483     },
1484
1485     _handleEmptySpaceMouseDown: function()
1486     {
1487         this._willCauseCancelEditing = this._parentPane._isEditingStyle;
1488     },
1489
1490     _handleEmptySpaceClick: function(event)
1491     {
1492         if (!this.editable)
1493             return;
1494
1495         if (!window.getSelection().isCollapsed)
1496             return;
1497
1498         if (this._checkWillCancelEditing())
1499             return;
1500
1501         if (event.target.classList.contains("header") || this.element.classList.contains("read-only") || event.target.enclosingNodeOrSelfWithClass("media")) {
1502             event.consume();
1503             return;
1504         }
1505         this.expand();
1506         this.addNewBlankProperty().startEditing();
1507     },
1508
1509     _handleSelectorClick: function(event)
1510     {
1511         if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) && this.navigable && event.target.classList.contains("simple-selector")) {
1512             var index = event.target._selectorIndex;
1513             var styleSheetHeader = WebInspector.cssModel.styleSheetHeaderForId(this.rule.id.styleSheetId);
1514             var uiLocation = styleSheetHeader.rawLocationToUILocation(this.rule.lineNumberInSource(index), this.rule.columnNumberInSource(index));
1515             WebInspector.Revealer.reveal(uiLocation);
1516             return;
1517         }
1518         this._startEditingOnMouseEvent();
1519         event.consume(true);
1520     },
1521
1522     _startEditingOnMouseEvent: function()
1523     {
1524         if (!this.editable)
1525             return;
1526
1527         if (!this.rule && this.propertiesTreeOutline.children.length === 0) {
1528             this.expand();
1529             this.addNewBlankProperty().startEditing();
1530             return;
1531         }
1532
1533         if (!this.rule)
1534             return;
1535
1536         this.startEditingSelector();
1537     },
1538
1539     startEditingSelector: function()
1540     {
1541         var element = this._selectorElement;
1542         if (WebInspector.isBeingEdited(element))
1543             return;
1544
1545         element.scrollIntoViewIfNeeded(false);
1546         element.textContent = element.textContent; // Reset selector marks in group.
1547
1548         var config = new WebInspector.InplaceEditor.Config(this.editingSelectorCommitted.bind(this), this.editingSelectorCancelled.bind(this));
1549         WebInspector.InplaceEditor.startEditing(this._selectorElement, config);
1550
1551         window.getSelection().setBaseAndExtent(element, 0, element, 1);
1552         this._parentPane._isEditingStyle = true;
1553     },
1554
1555     _moveEditorFromSelector: function(moveDirection)
1556     {
1557         this._markSelectorMatches();
1558
1559         if (!moveDirection)
1560             return;
1561
1562         if (moveDirection === "forward") {
1563             this.expand();
1564             var firstChild = this.propertiesTreeOutline.children[0];
1565             while (firstChild && firstChild.inherited)
1566                 firstChild = firstChild.nextSibling;
1567             if (!firstChild)
1568                 this.addNewBlankProperty().startEditing();
1569             else
1570                 firstChild.startEditing(firstChild.nameElement);
1571         } else {
1572             var previousSection = this.previousEditableSibling();
1573             if (!previousSection)
1574                 return;
1575
1576             previousSection.expand();
1577             previousSection.addNewBlankProperty().startEditing();
1578         }
1579     },
1580
1581     editingSelectorCommitted: function(element, newContent, oldContent, context, moveDirection)
1582     {
1583         this._editingSelectorEnded();
1584         if (newContent)
1585             newContent = newContent.trim();
1586         if (newContent === oldContent) {
1587             // Revert to a trimmed version of the selector if need be.
1588             this._selectorElement.textContent = newContent;
1589             this._moveEditorFromSelector(moveDirection);
1590             return;
1591         }
1592
1593         var selectedNode = this._parentPane.node;
1594
1595         /**
1596          * @param {!WebInspector.CSSRule} newRule
1597          * @this {WebInspector.StylePropertiesSection}
1598          */
1599         function successCallback(newRule)
1600         {
1601             var doesAffectSelectedNode = newRule.matchingSelectors.length > 0;
1602             if (!doesAffectSelectedNode) {
1603                 this.noAffect = true;
1604                 this.element.classList.add("no-affect");
1605             } else {
1606                 delete this.noAffect;
1607                 this.element.classList.remove("no-affect");
1608             }
1609
1610             this.rule = newRule;
1611             this.styleRule = { section: this, style: newRule.style, selectorText: newRule.selectorText, media: newRule.media, sourceURL: newRule.resourceURL(), rule: newRule };
1612
1613             this._parentPane.update(selectedNode);
1614             this._updateRuleOrigin();
1615
1616             finishOperationAndMoveEditor.call(this, moveDirection);
1617         }
1618
1619         /**
1620          * @this {WebInspector.StylePropertiesSection}
1621          */
1622         function finishOperationAndMoveEditor(direction)
1623         {
1624             delete this._parentPane._userOperation;
1625             this._moveEditorFromSelector(direction);
1626         }
1627
1628         // This gets deleted in finishOperationAndMoveEditor(), which is called both on success and failure.
1629         this._parentPane._userOperation = true;
1630         WebInspector.cssModel.setRuleSelector(this.rule.id, selectedNode ? selectedNode.id : 0, newContent, successCallback.bind(this), finishOperationAndMoveEditor.bind(this, moveDirection));
1631     },
1632
1633     _updateRuleOrigin: function()
1634     {
1635         this._selectorRefElement.removeChildren();
1636         this._selectorRefElement.appendChild(this._createRuleOriginNode());
1637     },
1638
1639     _editingSelectorEnded: function()
1640     {
1641         delete this._parentPane._isEditingStyle;
1642     },
1643
1644     editingSelectorCancelled: function()
1645     {
1646         this._editingSelectorEnded();
1647
1648         // Mark the selectors in group if necessary.
1649         // This is overridden by BlankStylePropertiesSection.
1650         this._markSelectorMatches();
1651     },
1652
1653     __proto__: WebInspector.PropertiesSection.prototype
1654 }
1655
1656 /**
1657  * @constructor
1658  * @extends {WebInspector.PropertiesSection}
1659  * @param {!WebInspector.StylesSidebarPane} stylesPane
1660  * @param {!Object} styleRule
1661  * @param {!Object.<string, boolean>} usedProperties
1662  */
1663 WebInspector.ComputedStylePropertiesSection = function(stylesPane, styleRule, usedProperties)
1664 {
1665     WebInspector.PropertiesSection.call(this, "");
1666
1667     var subtitle = this.headerElement.createChild("div", "sidebar-pane-subtitle vbox");
1668     var showInheritedCheckbox = new WebInspector.Checkbox(WebInspector.UIString("Show inherited properties"), "hbox");
1669     subtitle.appendChild(showInheritedCheckbox.element);
1670
1671     this._hasFreshContent = false;
1672
1673     /**
1674      * @this {WebInspector.ComputedStylePropertiesSection}
1675      */
1676     function showInheritedToggleFunction()
1677     {
1678         var showInherited = showInheritedCheckbox.checked;
1679         WebInspector.settings.showInheritedComputedStyleProperties.set(showInherited);
1680         if (showInherited)
1681             this.element.classList.add("styles-show-inherited");
1682         else
1683             this.element.classList.remove("styles-show-inherited");
1684     }
1685
1686     showInheritedCheckbox.addEventListener(showInheritedToggleFunction.bind(this));
1687
1688     this.element.className = "styles-section monospace read-only computed-style";
1689     if (WebInspector.settings.showInheritedComputedStyleProperties.get()) {
1690         this.element.classList.add("styles-show-inherited");
1691         showInheritedCheckbox.checked = true;
1692     }
1693
1694     this._stylesPane = stylesPane;
1695     this.styleRule = styleRule;
1696     this._usedProperties = usedProperties;
1697     this._alwaysShowComputedProperties = { "display": true, "height": true, "width": true };
1698     this.computedStyle = true;
1699     this._propertyTreeElements = {};
1700     this._expandedPropertyNames = {};
1701 }
1702
1703 WebInspector.ComputedStylePropertiesSection.prototype = {
1704     collapse: function(dontRememberState)
1705     {
1706         // Overriding with empty body.
1707     },
1708
1709     _isPropertyInherited: function(propertyName)
1710     {
1711         var canonicalName = WebInspector.CSSMetadata.canonicalPropertyName(propertyName);
1712         return !(canonicalName in this._usedProperties) && !(canonicalName in this._alwaysShowComputedProperties);
1713     },
1714
1715     update: function()
1716     {
1717         this._expandedPropertyNames = {};
1718         for (var name in this._propertyTreeElements) {
1719             if (this._propertyTreeElements[name].expanded)
1720                 this._expandedPropertyNames[name] = true;
1721         }
1722         this._propertyTreeElements = {};
1723         this.propertiesTreeOutline.removeChildren();
1724         this.populated = false;
1725     },
1726
1727     _updateFilter: function()
1728     {
1729         var children = this.propertiesTreeOutline.children;
1730         for (var i = 0; i < children.length; ++i)
1731             children[i]._updateFilter();
1732     },
1733
1734     onpopulate: function()
1735     {
1736         function sorter(a, b)
1737         {
1738             return a.name.compareTo(b.name);
1739         }
1740
1741         var style = this.styleRule.style;
1742         if (!style)
1743             return;
1744
1745         var uniqueProperties = [];
1746         var allProperties = style.allProperties;
1747         for (var i = 0; i < allProperties.length; ++i)
1748             uniqueProperties.push(allProperties[i]);
1749         uniqueProperties.sort(sorter);
1750
1751         this._propertyTreeElements = {};
1752         for (var i = 0; i < uniqueProperties.length; ++i) {
1753             var property = uniqueProperties[i];
1754             var inherited = this._isPropertyInherited(property.name);
1755             var item = new WebInspector.ComputedStylePropertyTreeElement(this._stylesPane, this.styleRule, style, property, inherited);
1756             this.propertiesTreeOutline.appendChild(item);
1757             this._propertyTreeElements[property.name] = item;
1758         }
1759     },
1760
1761     rebuildComputedTrace: function(sections)
1762     {
1763         for (var i = 0; i < sections.length; ++i) {
1764             var section = sections[i];
1765             if (section.computedStyle || section.isBlank)
1766                 continue;
1767
1768             for (var j = 0; j < section.uniqueProperties.length; ++j) {
1769                 var property = section.uniqueProperties[j];
1770                 if (property.disabled)
1771                     continue;
1772                 if (section.isInherited && !WebInspector.CSSMetadata.isPropertyInherited(property.name))
1773                     continue;
1774
1775                 var treeElement = this._propertyTreeElements[property.name.toLowerCase()];
1776                 if (treeElement) {
1777                     var fragment = document.createDocumentFragment();
1778                     var selector = fragment.createChild("span");
1779                     selector.style.color = "gray";
1780                     selector.textContent = section.styleRule.selectorText;
1781                     fragment.appendChild(document.createTextNode(" - " + property.value + " "));
1782                     var subtitle = fragment.createChild("span");
1783                     subtitle.style.float = "right";
1784                     subtitle.appendChild(section._createRuleOriginNode());
1785                     var childElement = new TreeElement(fragment, null, false);
1786                     treeElement.appendChild(childElement);
1787                     if (property.inactive || section.isPropertyOverloaded(property.name))
1788                         childElement.listItemElement.classList.add("overloaded");
1789                     if (!property.parsedOk) {
1790                         childElement.listItemElement.classList.add("not-parsed-ok");
1791                         childElement.listItemElement.insertBefore(WebInspector.StylesSidebarPane.createExclamationMark(property), childElement.listItemElement.firstChild);
1792                         if (WebInspector.StylesSidebarPane._ignoreErrorsForProperty(property))
1793                             childElement.listItemElement.classList.add("has-ignorable-error");
1794                     }
1795                 }
1796             }
1797         }
1798
1799         // Restore expanded state after update.
1800         for (var name in this._expandedPropertyNames) {
1801             if (name in this._propertyTreeElements)
1802                 this._propertyTreeElements[name].expand();
1803         }
1804     },
1805
1806     __proto__: WebInspector.PropertiesSection.prototype
1807 }
1808
1809 /**
1810  * @constructor
1811  * @extends {WebInspector.StylePropertiesSection}
1812  * @param {!WebInspector.StylesSidebarPane} stylesPane
1813  * @param {string} defaultSelectorText
1814  */
1815 WebInspector.BlankStylePropertiesSection = function(stylesPane, defaultSelectorText)
1816 {
1817     WebInspector.StylePropertiesSection.call(this, stylesPane, {selectorText: defaultSelectorText, rule: {isViaInspector: true}}, true, false);
1818     this.element.classList.add("blank-section");
1819 }
1820
1821 WebInspector.BlankStylePropertiesSection.prototype = {
1822     get isBlank()
1823     {
1824         return !this._normal;
1825     },
1826
1827     expand: function()
1828     {
1829         if (!this.isBlank)
1830             WebInspector.StylePropertiesSection.prototype.expand.call(this);
1831     },
1832
1833     editingSelectorCommitted: function(element, newContent, oldContent, context, moveDirection)
1834     {
1835         if (!this.isBlank) {
1836             WebInspector.StylePropertiesSection.prototype.editingSelectorCommitted.call(this, element, newContent, oldContent, context, moveDirection);
1837             return;
1838         }
1839
1840         /**
1841          * @param {!WebInspector.CSSRule} newRule
1842          * @this {WebInspector.StylePropertiesSection}
1843          */
1844         function successCallback(newRule)
1845         {
1846             var doesSelectorAffectSelectedNode = newRule.matchingSelectors.length > 0;
1847             var styleRule = { section: this, style: newRule.style, selectorText: newRule.selectorText, sourceURL: newRule.resourceURL(), rule: newRule };
1848             this.makeNormal(styleRule);
1849
1850             if (!doesSelectorAffectSelectedNode) {
1851                 this.noAffect = true;
1852                 this.element.classList.add("no-affect");
1853             }
1854
1855             this._updateRuleOrigin();
1856             this.expand();
1857             if (this.element.parentElement) // Might have been detached already.
1858                 this._moveEditorFromSelector(moveDirection);
1859
1860             delete this._parentPane._userOperation;
1861             this._editingSelectorEnded();
1862             this._markSelectorMatches();
1863         }
1864
1865         if (newContent)
1866             newContent = newContent.trim();
1867         this._parentPane._userOperation = true;
1868
1869         WebInspector.cssModel.requestViaInspectorStylesheet(this.pane.node, viaInspectorCallback.bind(this));
1870
1871         /**
1872          * @this {WebInspector.BlankStylePropertiesSection}
1873          * @param {?WebInspector.CSSStyleSheetHeader} styleSheetHeader
1874          */
1875         function viaInspectorCallback(styleSheetHeader)
1876         {
1877             if (!styleSheetHeader) {
1878                 this.editingSelectorCancelled();
1879                 return;
1880             }
1881             WebInspector.cssModel.addRule(styleSheetHeader.id, this.pane.node, newContent, successCallback.bind(this), this.editingSelectorCancelled.bind(this));
1882         }
1883     },
1884
1885     editingSelectorCancelled: function()
1886     {
1887         delete this._parentPane._userOperation;
1888         if (!this.isBlank) {
1889             WebInspector.StylePropertiesSection.prototype.editingSelectorCancelled.call(this);
1890             return;
1891         }
1892
1893         this._editingSelectorEnded();
1894         this.pane.removeSection(this);
1895     },
1896
1897     makeNormal: function(styleRule)
1898     {
1899         this.element.classList.remove("blank-section");
1900         this.styleRule = styleRule;
1901         this.rule = styleRule.rule;
1902
1903         // FIXME: replace this instance by a normal WebInspector.StylePropertiesSection.
1904         this._normal = true;
1905     },
1906
1907     __proto__: WebInspector.StylePropertiesSection.prototype
1908 }
1909
1910 /**
1911  * @constructor
1912  * @extends {TreeElement}
1913  * @param {!Object} styleRule
1914  * @param {!WebInspector.CSSStyleDeclaration} style
1915  * @param {!WebInspector.CSSProperty} property
1916  * @param {boolean} inherited
1917  * @param {boolean} overloaded
1918  * @param {boolean} hasChildren
1919  */
1920 WebInspector.StylePropertyTreeElementBase = function(styleRule, style, property, inherited, overloaded, hasChildren)
1921 {
1922     this._styleRule = styleRule;
1923     this.style = style;
1924     this.property = property;
1925     this._inherited = inherited;
1926     this._overloaded = overloaded;
1927
1928     // Pass an empty title, the title gets made later in onattach.
1929     TreeElement.call(this, "", null, hasChildren);
1930
1931     this.selectable = false;
1932 }
1933
1934 WebInspector.StylePropertyTreeElementBase.prototype = {
1935     /**
1936      * @return {?WebInspector.DOMNode}
1937      */
1938     node: function()
1939     {
1940         return null;  // Overridden by ancestors.
1941     },
1942
1943     /**
1944      * @return {?WebInspector.StylesSidebarPane}
1945      */
1946     editablePane: function()
1947     {
1948         return null;  // Overridden by ancestors.
1949     },
1950
1951     /**
1952      * @return {!WebInspector.StylesSidebarPane|!WebInspector.ComputedStyleSidebarPane}
1953      */
1954     parentPane: function()
1955     {
1956         throw "Not implemented";
1957     },
1958
1959     get inherited()
1960     {
1961         return this._inherited;
1962     },
1963
1964     /**
1965      * @return {boolean}
1966      */
1967     hasIgnorableError: function()
1968     {
1969         return !this.parsedOk && WebInspector.StylesSidebarPane._ignoreErrorsForProperty(this.property);
1970     },
1971
1972     set inherited(x)
1973     {
1974         if (x === this._inherited)
1975             return;
1976         this._inherited = x;
1977         this.updateState();
1978     },
1979
1980     get overloaded()
1981     {
1982         return this._overloaded;
1983     },
1984
1985     set overloaded(x)
1986     {
1987         if (x === this._overloaded)
1988             return;
1989         this._overloaded = x;
1990         this.updateState();
1991     },
1992
1993     get disabled()
1994     {
1995         return this.property.disabled;
1996     },
1997
1998     get name()
1999     {
2000         if (!this.disabled || !this.property.text)
2001             return this.property.name;
2002
2003         var text = this.property.text;
2004         var index = text.indexOf(":");
2005         if (index < 1)
2006             return this.property.name;
2007
2008         text = text.substring(0, index).trim();
2009         if (text.startsWith("/*"))
2010             text = text.substring(2).trim();
2011         return text;
2012     },
2013
2014     get value()
2015     {
2016         if (!this.disabled || !this.property.text)
2017             return this.property.value;
2018
2019         var match = this.property.text.match(/(.*);\s*/);
2020         if (!match || !match[1])
2021             return this.property.value;
2022
2023         var text = match[1];
2024         var index = text.indexOf(":");
2025         if (index < 1)
2026             return this.property.value;
2027
2028         return text.substring(index + 1).trim();
2029     },
2030
2031     get parsedOk()
2032     {
2033         return this.property.parsedOk;
2034     },
2035
2036     onattach: function()
2037     {
2038         this.updateTitle();
2039     },
2040
2041     updateTitle: function()
2042     {
2043         var value = this.value;
2044
2045         this.updateState();
2046
2047         var nameElement = document.createElement("span");
2048         nameElement.className = "webkit-css-property";
2049         nameElement.textContent = this.name;
2050         nameElement.title = this.property.propertyText;
2051         this.nameElement = nameElement;
2052
2053         this._expandElement = document.createElement("span");
2054         this._expandElement.className = "expand-element";
2055
2056         var valueElement = document.createElement("span");
2057         valueElement.className = "value";
2058         this.valueElement = valueElement;
2059
2060         /**
2061          * @param {!RegExp} regex
2062          * @return {!DocumentFragment}
2063          */
2064         function processValue(regex, processor, nextProcessor, valueText)
2065         {
2066             var container = document.createDocumentFragment();
2067
2068             var items = valueText.replace(regex, "\0$1\0").split("\0");
2069             for (var i = 0; i < items.length; ++i) {
2070                 if ((i % 2) === 0) {
2071                     if (nextProcessor)
2072                         container.appendChild(nextProcessor(items[i]));
2073                     else
2074                         container.appendChild(document.createTextNode(items[i]));
2075                 } else {
2076                     var processedNode = processor(items[i]);
2077                     if (processedNode)
2078                         container.appendChild(processedNode);
2079                 }
2080             }
2081
2082             return container;
2083         }
2084
2085         /**
2086          * @param {string} url
2087          * @return {!Node}
2088          * @this {WebInspector.StylePropertyTreeElementBase}
2089          */
2090         function linkifyURL(url)
2091         {
2092             var hrefUrl = url;
2093             var match = hrefUrl.match(/['"]?([^'"]+)/);
2094             if (match)
2095                 hrefUrl = match[1];
2096             var container = document.createDocumentFragment();
2097             container.appendChild(document.createTextNode("url("));
2098             if (this._styleRule.sourceURL)
2099                 hrefUrl = WebInspector.ParsedURL.completeURL(this._styleRule.sourceURL, hrefUrl);
2100             else if (this.node())
2101                 hrefUrl = this.node().resolveURL(hrefUrl);
2102             var hasResource = hrefUrl && !!WebInspector.resourceForURL(hrefUrl);
2103             // FIXME: WebInspector.linkifyURLAsNode() should really use baseURI.
2104             container.appendChild(WebInspector.linkifyURLAsNode(hrefUrl || url, url, undefined, !hasResource));
2105             container.appendChild(document.createTextNode(")"));
2106             return container;
2107         }
2108
2109         if (value) {
2110             var colorProcessor = processValue.bind(null, WebInspector.StylesSidebarPane._colorRegex, this._processColor.bind(this, nameElement, valueElement), null);
2111             valueElement.appendChild(processValue(/url\(\s*([^)]+)\s*\)/g, linkifyURL.bind(this), WebInspector.CSSMetadata.isColorAwareProperty(this.name) && this.parsedOk ? colorProcessor : null, value));
2112         }
2113
2114         this.listItemElement.removeChildren();
2115         nameElement.normalize();
2116         valueElement.normalize();
2117
2118         if (!this.treeOutline)
2119             return;
2120
2121         if (this.disabled)
2122             this.listItemElement.createChild("span", "styles-clipboard-only").createTextChild("/* ");
2123         this.listItemElement.appendChild(nameElement);
2124         this.listItemElement.appendChild(document.createTextNode(": "));
2125         this.listItemElement.appendChild(this._expandElement);
2126         this.listItemElement.appendChild(valueElement);
2127         this.listItemElement.appendChild(document.createTextNode(";"));
2128         if (this.disabled)
2129             this.listItemElement.createChild("span", "styles-clipboard-only").createTextChild(" */");
2130
2131         if (!this.parsedOk) {
2132             // Avoid having longhands under an invalid shorthand.
2133             this.hasChildren = false;
2134             this.listItemElement.classList.add("not-parsed-ok");
2135
2136             // Add a separate exclamation mark IMG element with a tooltip.
2137             this.listItemElement.insertBefore(WebInspector.StylesSidebarPane.createExclamationMark(this.property), this.listItemElement.firstChild);
2138         }
2139         if (this.property.inactive)
2140             this.listItemElement.classList.add("inactive");
2141         this._updateFilter();
2142     },
2143
2144     _updateFilter: function()
2145     {
2146         var regEx = this.parentPane().filterRegex();
2147         this.listItemElement.classList.toggle("filter-match", !!regEx && (regEx.test(this.property.name) || regEx.test(this.property.value)));
2148     },
2149
2150     /**
2151      * @param {!Element} nameElement
2152      * @param {!Element} valueElement
2153      * @param {string} text
2154      */
2155     _processColor: function(nameElement, valueElement, text)
2156     {
2157         var color = WebInspector.Color.parse(text);
2158
2159         // We can be called with valid non-color values of |text| (like 'none' from border style)
2160         if (!color)
2161             return document.createTextNode(text);
2162
2163         var format = WebInspector.StylesSidebarPane._colorFormat(color);
2164         var spectrumHelper = this.editablePane() && this.editablePane()._spectrumHelper;
2165         var spectrum = spectrumHelper ? spectrumHelper.spectrum() : null;
2166
2167         var isEditable = !!(this._styleRule && this._styleRule.editable !== false); // |editable| is true by default.
2168         var colorSwatch = new WebInspector.ColorSwatch(!isEditable);
2169         colorSwatch.setColorString(text);
2170         colorSwatch.element.addEventListener("click", swatchClick.bind(this), false);
2171
2172         var scrollerElement;
2173         var boundSpectrumChanged = spectrumChanged.bind(this);
2174         var boundSpectrumHidden = spectrumHidden.bind(this);
2175
2176         /**
2177          * @param {!WebInspector.Event} e
2178          * @this {WebInspector.StylePropertyTreeElementBase}
2179          */
2180         function spectrumChanged(e)
2181         {
2182             var colorString = /** @type {string} */ (e.data);
2183             spectrum.displayText = colorString;
2184             colorValueElement.textContent = colorString;
2185             colorSwatch.setColorString(colorString);
2186             this.applyStyleText(nameElement.textContent + ": " + valueElement.textContent, false, false, false);
2187         }
2188
2189         /**
2190          * @param {!WebInspector.Event} event
2191          * @this {WebInspector.StylePropertyTreeElementBase}
2192          */
2193         function spectrumHidden(event)
2194         {
2195             if (scrollerElement)
2196                 scrollerElement.removeEventListener("scroll", repositionSpectrum, false);
2197             var commitEdit = event.data;
2198             var propertyText = !commitEdit && this.originalPropertyText ? this.originalPropertyText : (nameElement.textContent + ": " + valueElement.textContent);
2199             this.applyStyleText(propertyText, true, true, false);
2200             spectrum.removeEventListener(WebInspector.Spectrum.Events.ColorChanged, boundSpectrumChanged);
2201             spectrumHelper.removeEventListener(WebInspector.SpectrumPopupHelper.Events.Hidden, boundSpectrumHidden);
2202
2203             delete this.editablePane()._isEditingStyle;
2204             delete this.originalPropertyText;
2205         }
2206
2207         function repositionSpectrum()
2208         {
2209             spectrumHelper.reposition(colorSwatch.element);
2210         }
2211
2212         /**
2213          * @param {?Event} e
2214          * @this {WebInspector.StylePropertyTreeElementBase}
2215          */
2216         function swatchClick(e)
2217         {
2218             e.consume(true);
2219
2220             // Shift + click toggles color formats.
2221             // Click opens colorpicker, only if the element is not in computed styles section.
2222             if (!spectrumHelper || e.shiftKey) {
2223                 changeColorDisplay();
2224                 return;
2225             }
2226
2227             if (!isEditable)
2228                 return;
2229
2230             var visible = spectrumHelper.toggle(colorSwatch.element, color, format);
2231             if (visible) {
2232                 spectrum.displayText = color.toString(format);
2233                 this.originalPropertyText = this.property.propertyText;
2234                 this.editablePane()._isEditingStyle = true;
2235                 spectrum.addEventListener(WebInspector.Spectrum.Events.ColorChanged, boundSpectrumChanged);
2236                 spectrumHelper.addEventListener(WebInspector.SpectrumPopupHelper.Events.Hidden, boundSpectrumHidden);
2237
2238                 scrollerElement = colorSwatch.element.enclosingNodeOrSelfWithClass("scroll-target");
2239                 if (scrollerElement)
2240                     scrollerElement.addEventListener("scroll", repositionSpectrum, false);
2241                 else
2242                     console.error("Unable to handle color picker scrolling");
2243             }
2244         }
2245
2246         var colorValueElement = document.createElement("span");
2247         colorValueElement.textContent = color.toString(format);
2248
2249         /**
2250          * @param {string} curFormat
2251          */
2252         function nextFormat(curFormat)
2253         {
2254             // The format loop is as follows:
2255             // * original
2256             // * rgb(a)
2257             // * hsl(a)
2258             // * nickname (if the color has a nickname)
2259             // * if the color is simple:
2260             //   - shorthex (if has short hex)
2261             //   - hex
2262             var cf = WebInspector.Color.Format;
2263
2264             switch (curFormat) {
2265                 case cf.Original:
2266                     return !color.hasAlpha() ? cf.RGB : cf.RGBA;
2267
2268                 case cf.RGB:
2269                 case cf.RGBA:
2270                     return !color.hasAlpha() ? cf.HSL : cf.HSLA;
2271
2272                 case cf.HSL:
2273                 case cf.HSLA:
2274                     if (color.nickname())
2275                         return cf.Nickname;
2276                     if (!color.hasAlpha())
2277                         return color.canBeShortHex() ? cf.ShortHEX : cf.HEX;
2278                     else
2279                         return cf.Original;
2280
2281                 case cf.ShortHEX:
2282                     return cf.HEX;
2283
2284                 case cf.HEX:
2285                     return cf.Original;
2286
2287                 case cf.Nickname:
2288                     if (!color.hasAlpha())
2289                         return color.canBeShortHex() ? cf.ShortHEX : cf.HEX;
2290                     else
2291                         return cf.Original;
2292
2293                 default:
2294                     return cf.RGBA;
2295             }
2296         }
2297
2298         function changeColorDisplay()
2299         {
2300             do {
2301                 format = nextFormat(format);
2302                 var currentValue = color.toString(format);
2303             } while (currentValue === colorValueElement.textContent);
2304             colorValueElement.textContent = currentValue;
2305         }
2306
2307         var container = document.createElement("nobr");
2308         container.appendChild(colorSwatch.element);
2309         container.appendChild(colorValueElement);
2310         return container;
2311     },
2312
2313     updateState: function()
2314     {
2315         if (!this.listItemElement)
2316             return;
2317
2318         if (this.style.isPropertyImplicit(this.name))
2319             this.listItemElement.classList.add("implicit");
2320         else
2321             this.listItemElement.classList.remove("implicit");
2322
2323         if (this.hasIgnorableError())
2324             this.listItemElement.classList.add("has-ignorable-error");
2325         else
2326             this.listItemElement.classList.remove("has-ignorable-error");
2327
2328         if (this.inherited)
2329             this.listItemElement.classList.add("inherited");
2330         else
2331             this.listItemElement.classList.remove("inherited");
2332
2333         if (this.overloaded)
2334             this.listItemElement.classList.add("overloaded");
2335         else
2336             this.listItemElement.classList.remove("overloaded");
2337
2338         if (this.disabled)
2339             this.listItemElement.classList.add("disabled");
2340         else
2341             this.listItemElement.classList.remove("disabled");
2342     },
2343
2344     __proto__: TreeElement.prototype
2345 }
2346
2347 /**
2348  * @constructor
2349  * @extends {WebInspector.StylePropertyTreeElementBase}
2350  * @param {!WebInspector.StylesSidebarPane} stylesPane
2351  * @param {!Object} styleRule
2352  * @param {!WebInspector.CSSStyleDeclaration} style
2353  * @param {!WebInspector.CSSProperty} property
2354  * @param {boolean} inherited
2355  */
2356 WebInspector.ComputedStylePropertyTreeElement = function(stylesPane, styleRule, style, property, inherited)
2357 {
2358     WebInspector.StylePropertyTreeElementBase.call(this, styleRule, style, property, inherited, false, false);
2359     this._stylesPane = stylesPane;
2360 }
2361
2362 WebInspector.ComputedStylePropertyTreeElement.prototype = {
2363     /**
2364      * @return {?WebInspector.DOMNode}
2365      */
2366     node: function()
2367     {
2368         return this._stylesPane.node;
2369     },
2370
2371     /**
2372      * @return {?WebInspector.StylesSidebarPane}
2373      */
2374     editablePane: function()
2375     {
2376         return null;
2377     },
2378
2379     /**
2380      * @return {!WebInspector.ComputedStyleSidebarPane}
2381      */
2382     parentPane: function()
2383     {
2384         return this._stylesPane._computedStylePane;
2385     },
2386
2387     _updateFilter: function()
2388     {
2389         var regEx = this.parentPane().filterRegex();
2390         this.listItemElement.classList.toggle("hidden", !!regEx && (!regEx.test(this.property.name) && !regEx.test(this.property.value)));
2391     },
2392
2393     __proto__: WebInspector.StylePropertyTreeElementBase.prototype
2394 }
2395
2396 /**
2397  * @constructor
2398  * @extends {WebInspector.StylePropertyTreeElementBase}
2399  * @param {!WebInspector.StylesSidebarPane} stylesPane
2400  * @param {!Object} styleRule
2401  * @param {!WebInspector.CSSStyleDeclaration} style
2402  * @param {!WebInspector.CSSProperty} property
2403  * @param {boolean} isShorthand
2404  * @param {boolean} inherited
2405  * @param {boolean} overloaded
2406  */
2407 WebInspector.StylePropertyTreeElement = function(stylesPane, styleRule, style, property, isShorthand, inherited, overloaded)
2408 {
2409     WebInspector.StylePropertyTreeElementBase.call(this, styleRule, style, property, inherited, overloaded, isShorthand);
2410     this._parentPane = stylesPane;
2411     this.isShorthand = isShorthand;
2412 }
2413
2414 WebInspector.StylePropertyTreeElement.prototype = {
2415     /**
2416      * @return {?WebInspector.DOMNode}
2417      */
2418     node: function()
2419     {
2420         return this._parentPane.node;
2421     },
2422
2423     /**
2424      * @return {?WebInspector.StylesSidebarPane}
2425      */
2426     editablePane: function()
2427     {
2428         return this._parentPane;
2429     },
2430
2431     /**
2432      * @return {!WebInspector.StylesSidebarPane}
2433      */
2434     parentPane: function()
2435     {
2436         return this._parentPane;
2437     },
2438
2439     /**
2440      * @return {?WebInspector.StylePropertiesSection}
2441      */
2442     section: function()
2443     {
2444         return this.treeOutline && this.treeOutline.section;
2445     },
2446
2447     /**
2448      * @param {function()=} userCallback
2449      */
2450     _updatePane: function(userCallback)
2451     {
2452         var section = this.section();
2453         if (section && section.pane)
2454             section.pane._refreshUpdate(section, false, userCallback);
2455         else  {
2456             if (userCallback)
2457                 userCallback();
2458         }
2459     },
2460
2461     /**
2462      * @param {?Event} event
2463      */
2464     toggleEnabled: function(event)
2465     {
2466         var disabled = !event.target.checked;
2467
2468         /**
2469          * @param {?WebInspector.CSSStyleDeclaration} newStyle
2470          * @this {WebInspector.StylePropertyTreeElement}
2471          */
2472         function callback(newStyle)
2473         {
2474             delete this._parentPane._userOperation;
2475
2476             if (!newStyle)
2477                 return;
2478
2479             newStyle.parentRule = this.style.parentRule;
2480             this.style = newStyle;
2481             this._styleRule.style = newStyle;
2482
2483             var section = this.section();
2484             if (section && section.pane)
2485                 section.pane.dispatchEventToListeners("style property toggled");
2486
2487             this._updatePane();
2488         }
2489
2490         this._parentPane._userOperation = true;
2491         this.property.setDisabled(disabled, callback.bind(this));
2492         event.consume();
2493     },
2494
2495     onpopulate: function()
2496     {
2497         // Only populate once and if this property is a shorthand.
2498         if (this.children.length || !this.isShorthand)
2499             return;
2500
2501         var longhandProperties = this.style.longhandProperties(this.name);
2502         for (var i = 0; i < longhandProperties.length; ++i) {
2503             var name = longhandProperties[i].name;
2504             var inherited = false;
2505             var overloaded = false;
2506
2507             var section = this.section();
2508             if (section) {
2509                 inherited = section.isPropertyInherited(name);
2510                 overloaded = section.isPropertyOverloaded(name);
2511             }
2512
2513             var liveProperty = this.style.getLiveProperty(name);
2514             if (!liveProperty)
2515                 continue;
2516
2517             var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this._styleRule, this.style, liveProperty, false, inherited, overloaded);
2518             this.appendChild(item);
2519         }
2520     },
2521
2522     onattach: function()
2523     {
2524         WebInspector.StylePropertyTreeElementBase.prototype.onattach.call(this);
2525
2526         this.listItemElement.addEventListener("mousedown", this._mouseDown.bind(this));
2527         this.listItemElement.addEventListener("mouseup", this._resetMouseDownElement.bind(this));
2528         this.listItemElement.addEventListener("click", this._mouseClick.bind(this));
2529     },
2530
2531     _mouseDown: function(event)
2532     {
2533         if (this._parentPane) {
2534             this._parentPane._mouseDownTreeElement = this;
2535             this._parentPane._mouseDownTreeElementIsName = this._isNameElement(event.target);
2536             this._parentPane._mouseDownTreeElementIsValue = this._isValueElement(event.target);
2537         }
2538     },
2539
2540     _resetMouseDownElement: function()
2541     {
2542         if (this._parentPane) {
2543             delete this._parentPane._mouseDownTreeElement;
2544             delete this._parentPane._mouseDownTreeElementIsName;
2545             delete this._parentPane._mouseDownTreeElementIsValue;
2546         }
2547     },
2548
2549     updateTitle: function()
2550     {
2551         WebInspector.StylePropertyTreeElementBase.prototype.updateTitle.call(this);
2552
2553         if (this.parsedOk && this.section() && this.parent.root) {
2554             var enabledCheckboxElement = document.createElement("input");
2555             enabledCheckboxElement.className = "enabled-button";
2556             enabledCheckboxElement.type = "checkbox";
2557             enabledCheckboxElement.checked = !this.disabled;
2558             enabledCheckboxElement.addEventListener("click", this.toggleEnabled.bind(this), false);
2559             this.listItemElement.insertBefore(enabledCheckboxElement, this.listItemElement.firstChild);
2560         }
2561     },
2562
2563     _mouseClick: function(event)
2564     {
2565         if (!window.getSelection().isCollapsed)
2566             return;
2567
2568         event.consume(true);
2569
2570         if (event.target === this.listItemElement) {
2571             var section = this.section();
2572             if (!section || !section.editable)
2573                 return;
2574
2575             if (section._checkWillCancelEditing())
2576                 return;
2577             section.addNewBlankProperty(this.property.index + 1).startEditing();
2578             return;
2579         }
2580
2581         if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) && this.section().navigable) {
2582             this._navigateToSource(event.target);
2583             return;
2584         }
2585
2586         this.startEditing(event.target);
2587     },
2588
2589     /**
2590      * @param {!Element} element
2591      */
2592     _navigateToSource: function(element)
2593     {
2594         console.assert(this.section().navigable);
2595         var propertyNameClicked = element === this.nameElement;
2596         WebInspector.Revealer.reveal(this.property.uiLocation(propertyNameClicked));
2597     },
2598
2599     /**
2600      * @param {!Element} element
2601      */
2602     _isNameElement: function(element)
2603     {
2604         return element.enclosingNodeOrSelfWithClass("webkit-css-property") === this.nameElement;
2605     },
2606
2607     /**
2608      * @param {!Element} element
2609      */
2610     _isValueElement: function(element)
2611     {
2612         return !!element.enclosingNodeOrSelfWithClass("value");
2613     },
2614
2615     /**
2616      * @param {!Element=} selectElement
2617      */
2618     startEditing: function(selectElement)
2619     {
2620         // FIXME: we don't allow editing of longhand properties under a shorthand right now.
2621         if (this.parent.isShorthand)
2622             return;
2623
2624         if (selectElement === this._expandElement)
2625             return;
2626
2627         var section = this.section();
2628         if (section && !section.editable)
2629             return;
2630
2631         if (!selectElement)
2632             selectElement = this.nameElement; // No arguments passed in - edit the name element by default.
2633         else
2634             selectElement = selectElement.enclosingNodeOrSelfWithClass("webkit-css-property") || selectElement.enclosingNodeOrSelfWithClass("value");
2635
2636         if (WebInspector.isBeingEdited(selectElement))
2637             return;
2638
2639         var isEditingName = selectElement === this.nameElement;
2640         if (!isEditingName)
2641             this.valueElement.textContent = restoreURLs(this.valueElement.textContent, this.value);
2642
2643         /**
2644          * @param {string} fieldValue
2645          * @param {string} modelValue
2646          * @return {string}
2647          */
2648         function restoreURLs(fieldValue, modelValue)
2649         {
2650             const urlRegex = /\b(url\([^)]*\))/g;
2651             var splitFieldValue = fieldValue.split(urlRegex);
2652             if (splitFieldValue.length === 1)
2653                 return fieldValue;
2654             var modelUrlRegex = new RegExp(urlRegex);
2655             for (var i = 1; i < splitFieldValue.length; i += 2) {
2656                 var match = modelUrlRegex.exec(modelValue);
2657                 if (match)
2658                     splitFieldValue[i] = match[0];
2659             }
2660             return splitFieldValue.join("");
2661         }
2662
2663         var context = {
2664             expanded: this.expanded,
2665             hasChildren: this.hasChildren,
2666             isEditingName: isEditingName,
2667             previousContent: selectElement.textContent
2668         };
2669
2670         // Lie about our children to prevent expanding on double click and to collapse shorthands.
2671         this.hasChildren = false;
2672
2673         if (selectElement.parentElement)
2674             selectElement.parentElement.classList.add("child-editing");
2675         selectElement.textContent = selectElement.textContent; // remove color swatch and the like
2676
2677         /**
2678          * @this {WebInspector.StylePropertyTreeElement}
2679          */
2680         function pasteHandler(context, event)
2681         {
2682             var data = event.clipboardData.getData("Text");
2683             if (!data)
2684                 return;
2685             var colonIdx = data.indexOf(":");
2686             if (colonIdx < 0)
2687                 return;
2688             var name = data.substring(0, colonIdx).trim();
2689             var value = data.substring(colonIdx + 1).trim();
2690
2691             event.preventDefault();
2692
2693             if (!("originalName" in context)) {
2694                 context.originalName = this.nameElement.textContent;
2695                 context.originalValue = this.valueElement.textContent;
2696             }
2697             this.property.name = name;
2698             this.property.value = value;
2699             this.nameElement.textContent = name;
2700             this.valueElement.textContent = value;
2701             this.nameElement.normalize();
2702             this.valueElement.normalize();
2703
2704             this.editingCommitted(event.target.textContent, context, "forward");
2705         }
2706
2707         /**
2708          * @this {WebInspector.StylePropertyTreeElement}
2709          */
2710         function blurListener(context, event)
2711         {
2712             var treeElement = this._parentPane._mouseDownTreeElement;
2713             var moveDirection = "";
2714             if (treeElement === this) {
2715                 if (isEditingName && this._parentPane._mouseDownTreeElementIsValue)
2716                     moveDirection = "forward";
2717                 if (!isEditingName && this._parentPane._mouseDownTreeElementIsName)
2718                     moveDirection = "backward";
2719             }
2720             this.editingCommitted(event.target.textContent, context, moveDirection);
2721         }
2722
2723         delete this.originalPropertyText;
2724
2725         this._parentPane._isEditingStyle = true;
2726         if (selectElement.parentElement)
2727             selectElement.parentElement.scrollIntoViewIfNeeded(false);
2728
2729         var applyItemCallback = !isEditingName ? this._applyFreeFlowStyleTextEdit.bind(this, true) : undefined;
2730         this._prompt = new WebInspector.StylesSidebarPane.CSSPropertyPrompt(isEditingName ? WebInspector.CSSMetadata.cssPropertiesMetainfo : WebInspector.CSSMetadata.keywordsForProperty(this.nameElement.textContent), this, isEditingName);
2731         if (applyItemCallback) {
2732             this._prompt.addEventListener(WebInspector.TextPrompt.Events.ItemApplied, applyItemCallback, this);
2733             this._prompt.addEventListener(WebInspector.TextPrompt.Events.ItemAccepted, applyItemCallback, this);
2734         }
2735         var proxyElement = this._prompt.attachAndStartEditing(selectElement, blurListener.bind(this, context));
2736
2737         proxyElement.addEventListener("keydown", this.editingNameValueKeyDown.bind(this, context), false);
2738         proxyElement.addEventListener("keypress", this.editingNameValueKeyPress.bind(this, context), false);
2739         if (isEditingName)
2740             proxyElement.addEventListener("paste", pasteHandler.bind(this, context), false);
2741
2742         window.getSelection().setBaseAndExtent(selectElement, 0, selectElement, 1);
2743     },
2744
2745     editingNameValueKeyDown: function(context, event)
2746     {
2747         if (event.handled)
2748             return;
2749
2750         var isEditingName = context.isEditingName;
2751         var result;
2752
2753         if (isEnterKey(event)) {
2754             event.preventDefault();
2755             result = "forward";
2756         } else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code || event.keyIdentifier === "U+001B")
2757             result = "cancel";
2758         else if (!isEditingName && this._newProperty && event.keyCode === WebInspector.KeyboardShortcut.Keys.Backspace.code) {
2759             // For a new property, when Backspace is pressed at the beginning of new property value, move back to the property name.
2760             var selection = window.getSelection();
2761             if (selection.isCollapsed && !selection.focusOffset) {
2762                 event.preventDefault();
2763                 result = "backward";
2764             }
2765         } else if (event.keyIdentifier === "U+0009") { // Tab key.
2766             result = event.shiftKey ? "backward" : "forward";
2767             event.preventDefault();
2768         }
2769
2770         if (result) {
2771             switch (result) {
2772             case "cancel":
2773                 this.editingCancelled(null, context);
2774                 break;
2775             case "forward":
2776             case "backward":
2777                 this.editingCommitted(event.target.textContent, context, result);
2778                 break;
2779             }
2780
2781             event.consume();
2782             return;
2783         }
2784
2785         if (!isEditingName)
2786             this._applyFreeFlowStyleTextEdit(false);
2787     },
2788
2789     editingNameValueKeyPress: function(context, event)
2790     {
2791         function shouldCommitValueSemicolon(text, cursorPosition)
2792         {
2793             // FIXME: should this account for semicolons inside comments?
2794             var openQuote = "";
2795             for (var i = 0; i < cursorPosition; ++i) {
2796                 var ch = text[i];
2797                 if (ch === "\\" && openQuote !== "")
2798                     ++i; // skip next character inside string
2799                 else if (!openQuote && (ch === "\"" || ch === "'"))
2800                     openQuote = ch;
2801                 else if (openQuote === ch)
2802                     openQuote = "";
2803             }
2804             return !openQuote;
2805         }
2806
2807         var keyChar = String.fromCharCode(event.charCode);
2808         var isFieldInputTerminated = (context.isEditingName ? keyChar === ":" : keyChar === ";" && shouldCommitValueSemicolon(event.target.textContent, event.target.selectionLeftOffset()));
2809         if (isFieldInputTerminated) {
2810             // Enter or colon (for name)/semicolon outside of string (for value).
2811             event.consume(true);
2812             this.editingCommitted(event.target.textContent, context, "forward");
2813             return;
2814         }
2815     },
2816
2817     _applyFreeFlowStyleTextEdit: function(now)
2818     {
2819         if (this._applyFreeFlowStyleTextEditTimer)
2820             clearTimeout(this._applyFreeFlowStyleTextEditTimer);
2821
2822         /**
2823          * @this {WebInspector.StylePropertyTreeElement}
2824          */
2825         function apply()
2826         {
2827             var valueText = this.valueElement.textContent;
2828             if (valueText.indexOf(";") === -1)
2829                 this.applyStyleText(this.nameElement.textContent + ": " + valueText, false, false, false);
2830         }
2831         if (now)
2832             apply.call(this);
2833         else
2834             this._applyFreeFlowStyleTextEditTimer = setTimeout(apply.bind(this), 100);
2835     },
2836
2837     kickFreeFlowStyleEditForTest: function()
2838     {
2839         this._applyFreeFlowStyleTextEdit(true);
2840     },
2841
2842     editingEnded: function(context)
2843     {
2844         this._resetMouseDownElement();
2845         if (this._applyFreeFlowStyleTextEditTimer)
2846             clearTimeout(this._applyFreeFlowStyleTextEditTimer);
2847
2848         this.hasChildren = context.hasChildren;
2849         if (context.expanded)
2850             this.expand();
2851         var editedElement = context.isEditingName ? this.nameElement : this.valueElement;
2852         // The proxyElement has been deleted, no need to remove listener.
2853         if (editedElement.parentElement)
2854             editedElement.parentElement.classList.remove("child-editing");
2855
2856         delete this._parentPane._isEditingStyle;
2857     },
2858
2859     editingCancelled: function(element, context)
2860     {
2861         this._removePrompt();
2862         this._revertStyleUponEditingCanceled(this.originalPropertyText);
2863         // This should happen last, as it clears the info necessary to restore the property value after [Page]Up/Down changes.
2864         this.editingEnded(context);
2865     },
2866
2867     _revertStyleUponEditingCanceled: function(originalPropertyText)
2868     {
2869         if (typeof originalPropertyText === "string") {
2870             delete this.originalPropertyText;
2871             this.applyStyleText(originalPropertyText, true, false, true);
2872         } else {
2873             if (this._newProperty)
2874                 this.treeOutline.removeChild(this);
2875             else
2876                 this.updateTitle();
2877         }
2878     },
2879
2880     _findSibling: function(moveDirection)
2881     {
2882         var target = this;
2883         do {
2884             target = (moveDirection === "forward" ? target.nextSibling : target.previousSibling);
2885         } while(target && target.inherited);
2886
2887         return target;
2888     },
2889
2890     /**
2891      * @param {string} userInput
2892      * @param {!Object} context
2893      * @param {string} moveDirection
2894      */
2895     editingCommitted: function(userInput, context, moveDirection)
2896     {
2897         this._removePrompt();
2898         this.editingEnded(context);
2899         var isEditingName = context.isEditingName;
2900
2901         // Determine where to move to before making changes
2902         var createNewProperty, moveToPropertyName, moveToSelector;
2903         var isDataPasted = "originalName" in context;
2904         var isDirtyViaPaste = isDataPasted && (this.nameElement.textContent !== context.originalName || this.valueElement.textContent !== context.originalValue);
2905         var isPropertySplitPaste = isDataPasted && isEditingName && this.valueElement.textContent !== context.originalValue;
2906         var moveTo = this;
2907         var moveToOther = (isEditingName ^ (moveDirection === "forward"));
2908         var abandonNewProperty = this._newProperty && !userInput && (moveToOther || isEditingName);
2909         if (moveDirection === "forward" && (!isEditingName || isPropertySplitPaste) || moveDirection === "backward" && isEditingName) {
2910             moveTo = moveTo._findSibling(moveDirection);
2911             if (moveTo)
2912                 moveToPropertyName = moveTo.name;
2913             else if (moveDirection === "forward" && (!this._newProperty || userInput))
2914                 createNewProperty = true;
2915             else if (moveDirection === "backward")
2916                 moveToSelector = true;
2917         }
2918
2919         // Make the Changes and trigger the moveToNextCallback after updating.
2920         var moveToIndex = moveTo && this.treeOutline ? this.treeOutline.children.indexOf(moveTo) : -1;
2921         var blankInput = /^\s*$/.test(userInput);
2922         var shouldCommitNewProperty = this._newProperty && (isPropertySplitPaste || moveToOther || (!moveDirection && !isEditingName) || (isEditingName && blankInput));
2923         var section = this.section();
2924         if (((userInput !== context.previousContent || isDirtyViaPaste) && !this._newProperty) || shouldCommitNewProperty) {
2925             section._afterUpdate = moveToNextCallback.bind(this, this._newProperty, !blankInput, section);
2926             var propertyText;
2927             if (blankInput || (this._newProperty && /^\s*$/.test(this.valueElement.textContent)))
2928                 propertyText = "";
2929             else {
2930                 if (isEditingName)
2931                     propertyText = userInput + ": " + this.property.value;
2932                 else
2933                     propertyText = this.property.name + ": " + userInput;
2934             }
2935             this.applyStyleText(propertyText, true, true, false);
2936         } else {
2937             if (isEditingName)
2938                 this.property.name = userInput;
2939             else
2940                 this.property.value = userInput;
2941             if (!isDataPasted && !this._newProperty)
2942                 this.updateTitle();
2943             moveToNextCallback.call(this, this._newProperty, false, section);
2944         }
2945
2946         /**
2947          * The Callback to start editing the next/previous property/selector.
2948          * @this {WebInspector.StylePropertyTreeElement}
2949          */
2950         function moveToNextCallback(alreadyNew, valueChanged, section)
2951         {
2952             if (!moveDirection)
2953                 return;
2954
2955             // User just tabbed through without changes.
2956             if (moveTo && moveTo.parent) {
2957                 moveTo.startEditing(!isEditingName ? moveTo.nameElement : moveTo.valueElement);
2958                 return;
2959             }
2960
2961             // User has made a change then tabbed, wiping all the original treeElements.
2962             // Recalculate the new treeElement for the same property we were going to edit next.
2963             if (moveTo && !moveTo.parent) {
2964                 var propertyElements = section.propertiesTreeOutline.children;
2965                 if (moveDirection === "forward" && blankInput && !isEditingName)
2966                     --moveToIndex;
2967                 if (moveToIndex >= propertyElements.length && !this._newProperty)
2968                     createNewProperty = true;
2969                 else {
2970                     var treeElement = moveToIndex >= 0 ? propertyElements[moveToIndex] : null;
2971                     if (treeElement) {
2972                         var elementToEdit = !isEditingName || isPropertySplitPaste ? treeElement.nameElement : treeElement.valueElement;
2973                         if (alreadyNew && blankInput)
2974                             elementToEdit = moveDirection === "forward" ? treeElement.nameElement : treeElement.valueElement;
2975                         treeElement.startEditing(elementToEdit);
2976                         return;
2977                     } else if (!alreadyNew)
2978                         moveToSelector = true;
2979                 }
2980             }
2981
2982             // Create a new attribute in this section (or move to next editable selector if possible).
2983             if (createNewProperty) {
2984                 if (alreadyNew && !valueChanged && (isEditingName ^ (moveDirection === "backward")))
2985                     return;
2986
2987                 section.addNewBlankProperty().startEditing();
2988                 return;
2989             }
2990
2991             if (abandonNewProperty) {
2992                 moveTo = this._findSibling(moveDirection);
2993                 var sectionToEdit = (moveTo || moveDirection === "backward") ? section : section.nextEditableSibling();
2994                 if (sectionToEdit) {
2995                     if (sectionToEdit.rule)
2996                         sectionToEdit.startEditingSelector();
2997                     else
2998                         sectionToEdit._moveEditorFromSelector(moveDirection);
2999                 }
3000                 return;
3001             }
3002
3003             if (moveToSelector) {
3004                 if (section.rule)
3005                     section.startEditingSelector();
3006                 else
3007                     section._moveEditorFromSelector(moveDirection);
3008             }
3009         }
3010     },
3011
3012     _removePrompt: function()
3013     {
3014         // BUG 53242. This cannot go into editingEnded(), as it should always happen first for any editing outcome.
3015         if (this._prompt) {
3016             this._prompt.detach();
3017             delete this._prompt;
3018         }
3019     },
3020
3021     _hasBeenModifiedIncrementally: function()
3022     {
3023         // New properties applied via up/down or live editing have an originalPropertyText and will be deleted later
3024         // on, if cancelled, when the empty string gets applied as their style text.
3025         return typeof this.originalPropertyText === "string" || (!!this.property.propertyText && this._newProperty);
3026     },
3027
3028     applyStyleText: function(styleText, updateInterface, majorChange, isRevert)
3029     {
3030         function userOperationFinishedCallback(parentPane, updateInterface)
3031         {
3032             if (updateInterface)
3033                 delete parentPane._userOperation;
3034         }
3035
3036         // Leave a way to cancel editing after incremental changes.
3037         if (!isRevert && !updateInterface && !this._hasBeenModifiedIncrementally()) {
3038             // Remember the rule's original CSS text on [Page](Up|Down), so it can be restored
3039             // if the editing is canceled.
3040             this.originalPropertyText = this.property.propertyText;
3041         }
3042
3043         if (!this.treeOutline)
3044             return;
3045
3046         var section = this.section();
3047         styleText = styleText.replace(/\s/g, " ").trim(); // Replace &nbsp; with whitespace.
3048         var styleTextLength = styleText.length;
3049         if (!styleTextLength && updateInterface && !isRevert && this._newProperty && !this._hasBeenModifiedIncrementally()) {
3050             // The user deleted everything and never applied a new property value via Up/Down scrolling/live editing, so remove the tree element and update.
3051             this.parent.removeChild(this);
3052             section.afterUpdate();
3053             return;
3054         }
3055
3056         var currentNode = this._parentPane.node;
3057         if (updateInterface)
3058             this._parentPane._userOperation = true;
3059
3060         /**
3061          * @param {function()} userCallback
3062          * @param {string} originalPropertyText
3063          * @param {?WebInspector.CSSStyleDeclaration} newStyle
3064          * @this {WebInspector.StylePropertyTreeElement}
3065          */
3066         function callback(userCallback, originalPropertyText, newStyle)
3067         {
3068             if (!newStyle) {
3069                 if (updateInterface) {
3070                     // It did not apply, cancel editing.
3071                     this._revertStyleUponEditingCanceled(originalPropertyText);
3072                 }
3073                 userCallback();
3074                 return;
3075             }
3076
3077             if (this._newProperty)
3078                 this._newPropertyInStyle = true;
3079             newStyle.parentRule = this.style.parentRule;
3080             this.style = newStyle;
3081             this.property = newStyle.propertyAt(this.property.index);
3082             this._styleRule.style = this.style;
3083
3084             if (section && section.pane)
3085                 section.pane.dispatchEventToListeners("style edited");
3086
3087             if (updateInterface && currentNode === this.node()) {
3088                 this._updatePane(userCallback);
3089                 return;
3090             }
3091
3092             userCallback();
3093         }
3094
3095         // Append a ";" if the new text does not end in ";".
3096         // FIXME: this does not handle trailing comments.
3097         if (styleText.length && !/;\s*$/.test(styleText))
3098             styleText += ";";
3099         var overwriteProperty = !!(!this._newProperty || this._newPropertyInStyle);
3100         this.property.setText(styleText, majorChange, overwriteProperty, callback.bind(this, userOperationFinishedCallback.bind(null, this._parentPane, updateInterface), this.originalPropertyText));
3101     },
3102
3103     /**
3104      * @return {boolean}
3105      */
3106     ondblclick: function()
3107     {
3108         return true; // handled
3109     },
3110
3111     /**
3112      * @param {?Event} event
3113      * @return {boolean}
3114      */
3115     isEventWithinDisclosureTriangle: function(event)
3116     {
3117         return event.target === this._expandElement;
3118     },
3119
3120     __proto__: WebInspector.StylePropertyTreeElementBase.prototype
3121 }
3122
3123 /**
3124  * @constructor
3125  * @extends {WebInspector.TextPrompt}
3126  * @param {!WebInspector.CSSMetadata} cssCompletions
3127  * @param {!WebInspector.StylePropertyTreeElement} sidebarPane
3128  * @param {boolean} isEditingName
3129  */
3130 WebInspector.StylesSidebarPane.CSSPropertyPrompt = function(cssCompletions, sidebarPane, isEditingName)
3131 {
3132     // Use the same callback both for applyItemCallback and acceptItemCallback.
3133     WebInspector.TextPrompt.call(this, this._buildPropertyCompletions.bind(this), WebInspector.StyleValueDelimiters);
3134     this.setSuggestBoxEnabled("generic-suggest");
3135     this._cssCompletions = cssCompletions;
3136     this._sidebarPane = sidebarPane;
3137     this._isEditingName = isEditingName;
3138
3139     if (!isEditingName)
3140         this.disableDefaultSuggestionForEmptyInput();
3141 }
3142
3143 WebInspector.StylesSidebarPane.CSSPropertyPrompt.prototype = {
3144     /**
3145      * @param {?Event} event
3146      * @return {boolean}
3147      */
3148     onKeyDown: function(event)
3149     {
3150         switch (event.keyIdentifier) {
3151         case "Up":
3152         case "Down":
3153         case "PageUp":
3154         case "PageDown":
3155             if (this._handleNameOrValueUpDown(event)) {
3156                 event.preventDefault();
3157                 return true;
3158             }
3159             break;
3160         case "Enter":
3161             if (this.autoCompleteElement && !this.autoCompleteElement.textContent.length) {
3162                 this.tabKeyPressed();
3163                 return true;
3164             }
3165             break;
3166         }
3167
3168         return WebInspector.TextPrompt.prototype.onKeyDown.call(this, event);
3169     },
3170
3171     onMouseWheel: function(event)
3172     {
3173         if (this._handleNameOrValueUpDown(event)) {
3174             event.consume(true);
3175             return;
3176         }
3177         WebInspector.TextPrompt.prototype.onMouseWheel.call(this, event);
3178     },
3179
3180     /**
3181      * @override
3182      * @return {boolean}
3183      */
3184     tabKeyPressed: function()
3185     {
3186         this.acceptAutoComplete();
3187
3188         // Always tab to the next field.
3189         return false;
3190     },
3191
3192     /**
3193      * @param {?Event} event
3194      * @return {boolean}
3195      */
3196     _handleNameOrValueUpDown: function(event)
3197     {
3198         /**
3199          * @param {string} originalValue
3200          * @param {string} replacementString
3201          * @this {WebInspector.StylesSidebarPane.CSSPropertyPrompt}
3202          */
3203         function finishHandler(originalValue, replacementString)
3204         {
3205             // Synthesize property text disregarding any comments, custom whitespace etc.
3206             this._sidebarPane.applyStyleText(this._sidebarPane.nameElement.textContent + ": " + this._sidebarPane.valueElement.textContent, false, false, false);
3207         }
3208
3209         // Handle numeric value increment/decrement only at this point.
3210         if (!this._isEditingName && WebInspector.handleElementValueModifications(event, this._sidebarPane.valueElement, finishHandler.bind(this), this._isValueSuggestion.bind(this)))
3211             return true;
3212
3213         return false;
3214     },
3215
3216     /**
3217      * @param {string} word
3218      * @return {boolean}
3219      */
3220     _isValueSuggestion: function(word)
3221     {
3222         if (!word)
3223             return false;
3224         word = word.toLowerCase();
3225         return this._cssCompletions.keySet().hasOwnProperty(word);
3226     },
3227
3228     /**
3229      * @param {!Element} proxyElement
3230      * @param {!Range} wordRange
3231      * @param {boolean} force
3232      * @param {function(!Array.<string>, number=)} completionsReadyCallback
3233      */
3234     _buildPropertyCompletions: function(proxyElement, wordRange, force, completionsReadyCallback)
3235     {
3236         var prefix = wordRange.toString().toLowerCase();
3237         if (!prefix && !force && (this._isEditingName || proxyElement.textContent.length)) {
3238             completionsReadyCallback([]);
3239             return;
3240         }
3241
3242         var results = this._cssCompletions.startsWith(prefix);
3243         var selectedIndex = this._cssCompletions.mostUsedOf(results);
3244         completionsReadyCallback(results, selectedIndex);
3245     },
3246
3247     __proto__: WebInspector.TextPrompt.prototype
3248 }