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