2011-06-30 Pavel Feldman <pfeldman@chromium.org>
[WebKit-https.git] / Source / WebCore / inspector / front-end / StylesSidebarPane.js
1 /*
2  * Copyright (C) 2007 Apple Inc.  All rights reserved.
3  * Copyright (C) 2009 Joseph Pecoraro
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1.  Redistributions of source code must retain the above copyright
10  *     notice, this list of conditions and the following disclaimer.
11  * 2.  Redistributions in binary form must reproduce the above copyright
12  *     notice, this list of conditions and the following disclaimer in the
13  *     documentation and/or other materials provided with the distribution.
14  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15  *     its contributors may be used to endorse or promote products derived
16  *     from this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 WebInspector.StylesSidebarPane = function(computedStylePane)
31 {
32     WebInspector.SidebarPane.call(this, WebInspector.UIString("Styles"));
33
34     this.settingsSelectElement = document.createElement("select");
35     this.settingsSelectElement.className = "select-settings";
36
37     var option = document.createElement("option");
38     option.value = "original";
39     option.action = this._changeColorFormat.bind(this);
40     option.label = WebInspector.UIString("As Authored");
41     this.settingsSelectElement.appendChild(option);
42
43     var option = document.createElement("option");
44     option.value = "hex";
45     option.action = this._changeColorFormat.bind(this);
46     option.label = WebInspector.UIString("Hex Colors");
47     this.settingsSelectElement.appendChild(option);
48
49     option = document.createElement("option");
50     option.value = "rgb";
51     option.action = this._changeColorFormat.bind(this);
52     option.label = WebInspector.UIString("RGB Colors");
53     this.settingsSelectElement.appendChild(option);
54
55     option = document.createElement("option");
56     option.value = "hsl";
57     option.action = this._changeColorFormat.bind(this);
58     option.label = WebInspector.UIString("HSL Colors");
59     this.settingsSelectElement.appendChild(option);
60
61     this.settingsSelectElement.appendChild(document.createElement("hr"));
62
63     option = document.createElement("option");
64     option.action = this._createNewRule.bind(this);
65     option.label = WebInspector.UIString("New Style Rule");
66     this.settingsSelectElement.appendChild(option);
67
68     this.settingsSelectElement.addEventListener("click", function(event) { event.stopPropagation() }, false);
69     this.settingsSelectElement.addEventListener("change", this._changeSetting.bind(this), false);
70     var format = WebInspector.settings.colorFormat;
71     if (format === "original")
72         this.settingsSelectElement[0].selected = true;
73     else if (format === "hex")
74         this.settingsSelectElement[1].selected = true;
75     else if (format === "rgb")
76         this.settingsSelectElement[2].selected = true;
77     else if (format === "hsl")
78         this.settingsSelectElement[3].selected = true;
79
80     this.titleElement.appendChild(this.settingsSelectElement);
81     this._computedStylePane = computedStylePane;
82     this.element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true);
83 }
84
85 WebInspector.StylesSidebarPane.StyleValueDelimiters = " \t\n\"':;,/()";
86
87 // Taken from http://www.w3.org/TR/CSS21/propidx.html.
88 WebInspector.StylesSidebarPane.InheritedProperties = [
89     "azimuth", "border-collapse", "border-spacing", "caption-side", "color", "cursor", "direction", "elevation",
90     "empty-cells", "font-family", "font-size", "font-style", "font-variant", "font-weight", "font", "letter-spacing",
91     "line-height", "list-style-image", "list-style-position", "list-style-type", "list-style", "orphans", "pitch-range",
92     "pitch", "quotes", "richness", "speak-header", "speak-numeral", "speak-punctuation", "speak", "speech-rate", "stress",
93     "text-align", "text-indent", "text-transform", "text-shadow", "visibility", "voice-family", "volume", "white-space", "widows", "word-spacing"
94 ].keySet();
95
96 // Keep in sync with RenderStyleConstants.h PseudoId enum. Array below contains pseudo id names for corresponding enum indexes.
97 // First item is empty due to its artificial NOPSEUDO nature in the enum.
98 // FIXME: find a way of generating this mapping or getting it from combination of RenderStyleConstants and CSSSelector.cpp at
99 // runtime.
100 WebInspector.StylesSidebarPane.PseudoIdNames = [
101     "", "first-line", "first-letter", "before", "after", "selection", "", "-webkit-scrollbar", "-webkit-file-upload-button",
102     "-webkit-input-placeholder", "-webkit-slider-thumb", "-webkit-search-cancel-button", "-webkit-search-decoration",
103     "-webkit-search-results-decoration", "-webkit-search-results-button", "-webkit-media-controls-panel",
104     "-webkit-media-controls-play-button", "-webkit-media-controls-mute-button", "-webkit-media-controls-timeline",
105     "-webkit-media-controls-timeline-container", "-webkit-media-controls-volume-slider",
106     "-webkit-media-controls-volume-slider-container", "-webkit-media-controls-current-time-display",
107     "-webkit-media-controls-time-remaining-display", "-webkit-media-controls-seek-back-button", "-webkit-media-controls-seek-forward-button",
108     "-webkit-media-controls-fullscreen-button", "-webkit-media-controls-rewind-button", "-webkit-media-controls-return-to-realtime-button",
109     "-webkit-media-controls-toggle-closed-captions-button", "-webkit-media-controls-status-display", "-webkit-scrollbar-thumb",
110     "-webkit-scrollbar-button", "-webkit-scrollbar-track", "-webkit-scrollbar-track-piece", "-webkit-scrollbar-corner",
111     "-webkit-resizer", "-webkit-input-list-button", "-webkit-inner-spin-button", "-webkit-outer-spin-button"
112 ];
113
114 WebInspector.StylesSidebarPane.CSSNumberRegex = /^(-?(?:\d+(?:\.\d+)?|\.\d+))$/;
115
116 WebInspector.StylesSidebarPane.alteredFloatNumber = function(number, event)
117 {
118     var arrowKeyPressed = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down");
119     // If the number is near zero or the number is one and the direction will take it near zero.
120     var numberNearZero = (number < 1 && number > -1);
121     if (number === 1 && event.keyIdentifier === "Down")
122         numberNearZero = true;
123     else if (number === -1 && event.keyIdentifier === "Up")
124         numberNearZero = true;
125
126     var result;
127     if (numberNearZero && event.altKey && arrowKeyPressed) {
128         if (event.keyIdentifier === "Down")
129             result = Math.ceil(number - 1);
130         else
131             result = Math.floor(number + 1);
132     } else {
133         // Jump by 10 when shift is down or jump by 0.1 when near zero or Alt/Option is down.
134         // Also jump by 10 for page up and down, or by 100 if shift is held with a page key.
135         var changeAmount = 1;
136         if (event.shiftKey && !arrowKeyPressed)
137             changeAmount = 100;
138         else if (event.shiftKey || !arrowKeyPressed)
139             changeAmount = 10;
140         else if (event.altKey || numberNearZero)
141             changeAmount = 0.1;
142
143         if (event.keyIdentifier === "Down" || event.keyIdentifier === "PageDown")
144             changeAmount *= -1;
145
146         // Make the new number and constrain it to a precision of 6, this matches numbers the engine returns.
147         // Use the Number constructor to forget the fixed precision, so 1.100000 will print as 1.1.
148         result = Number((number + changeAmount).toFixed(6));
149         if (!String(result).match(WebInspector.StylesSidebarPane.CSSNumberRegex))
150             return null;
151     }
152
153     return result;
154 }
155
156 WebInspector.StylesSidebarPane.alteredHexNumber = function(hexString, event)
157 {
158     var number = parseInt(hexString, 16);
159     if (isNaN(number) || !isFinite(number))
160         return hexString;
161
162     var maxValue = Math.pow(16, hexString.length) - 1;
163     var arrowKeyPressed = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down");
164
165     var delta;
166     if (arrowKeyPressed)
167         delta = (event.keyIdentifier === "Up") ? 1 : -1;
168     else
169         delta = (event.keyIdentifier === "PageUp") ? 16 : -16;
170
171     if (event.shiftKey)
172         delta *= 16;
173
174     var result = number + delta;
175     if (result < 0)
176         result = 0; // Color hex values are never negative, so clamp to 0.
177     else if (result > maxValue)
178         return hexString;
179
180     // Ensure the result length is the same as the original hex value.
181     var resultString = result.toString(16).toUpperCase();
182     for (var i = 0, lengthDelta = hexString.length - resultString.length; i < lengthDelta; ++i)
183         resultString = "0" + resultString;
184     return resultString;
185 },
186
187 WebInspector.StylesSidebarPane.prototype = {
188     _contextMenuEventFired: function(event)
189     {
190         var href = event.target.enclosingNodeOrSelfWithClass("webkit-html-resource-link") || event.target.enclosingNodeOrSelfWithClass("webkit-html-external-link");
191         if (href) {
192             var contextMenu = new WebInspector.ContextMenu();
193             var filled = WebInspector.panels.elements.populateHrefContextMenu(contextMenu, event, href);
194             if (filled)
195                 contextMenu.show(event);
196         }
197     },
198
199     update: function(node, editedSection, forceUpdate, callback)
200     {
201         var refresh = false;
202
203         if (forceUpdate)
204             delete this.node;
205
206         if (!forceUpdate && (!node || node === this.node))
207             refresh = true;
208
209         if (node && node.nodeType() === Node.TEXT_NODE && node.parentNode)
210             node = node.parentNode;
211
212         if (node && node.nodeType() !== Node.ELEMENT_NODE)
213             node = null;
214
215         if (node)
216             this.node = node;
217         else
218             node = this.node;
219
220         if (!node) {
221             this.bodyElement.removeChildren();
222             this._computedStylePane.bodyElement.removeChildren();
223             this.sections = {};
224             if (callback)
225                 callback();
226             return;
227         }
228
229         function stylesCallback(styles)
230         {
231             if (this.node === node && styles)
232                 this._rebuildUpdate(node, styles);
233             if (callback)
234                 callback();
235         }
236
237         function computedStyleCallback(computedStyle)
238         {
239             if (this.node === node && computedStyle)
240                 this._refreshUpdate(node, computedStyle, editedSection);
241             if (callback)
242                 callback();
243         }
244
245         if (refresh)
246             WebInspector.cssModel.getComputedStyleAsync(node.id, computedStyleCallback.bind(this));
247         else
248             WebInspector.cssModel.getStylesAsync(node.id, undefined, stylesCallback.bind(this));
249     },
250
251     _refreshUpdate: function(node, computedStyle, editedSection)
252     {
253         for (var pseudoId in this.sections) {
254             var styleRules = this._refreshStyleRules(this.sections[pseudoId], computedStyle);
255             var usedProperties = {};
256             var disabledComputedProperties = {};
257             this._markUsedProperties(styleRules, usedProperties, disabledComputedProperties);
258             this._refreshSectionsForStyleRules(styleRules, usedProperties, disabledComputedProperties, editedSection);
259         }
260         // Trace the computed style.
261         this.sections[0][0].rebuildComputedTrace(this.sections[0]);
262     },
263
264     _rebuildUpdate: function(node, styles)
265     {
266         this.bodyElement.removeChildren();
267         this._computedStylePane.bodyElement.removeChildren();
268
269         var styleRules = this._rebuildStyleRules(node, styles);
270         var usedProperties = {};
271         var disabledComputedProperties = {};
272         this._markUsedProperties(styleRules, usedProperties, disabledComputedProperties);
273         this.sections[0] = this._rebuildSectionsForStyleRules(styleRules, usedProperties, disabledComputedProperties, 0);
274         var anchorElement = this.sections[0].inheritedPropertiesSeparatorElement;
275         // Trace the computed style.
276         this.sections[0][0].rebuildComputedTrace(this.sections[0]);
277
278         for (var i = 0; i < styles.pseudoElements.length; ++i) {
279             var pseudoElementCSSRules = styles.pseudoElements[i];
280
281             styleRules = [];
282             var pseudoId = pseudoElementCSSRules.pseudoId;
283
284             var entry = { isStyleSeparator: true, pseudoId: pseudoId };
285             styleRules.push(entry);
286
287             // Add rules in reverse order to match the cascade order.
288             for (var j = pseudoElementCSSRules.rules.length - 1; j >= 0; --j) {
289                 var rule = pseudoElementCSSRules.rules[j];
290                 styleRules.push({ style: rule.style, selectorText: rule.selectorText, sourceURL: rule.sourceURL, rule: rule, editable: !!(rule.style && rule.style.id) });
291             }
292             usedProperties = {};
293             disabledComputedProperties = {};
294             this._markUsedProperties(styleRules, usedProperties, disabledComputedProperties);
295             this.sections[pseudoId] = this._rebuildSectionsForStyleRules(styleRules, usedProperties, disabledComputedProperties, pseudoId, anchorElement);
296         }
297     },
298
299     _refreshStyleRules: function(sections, computedStyle)
300     {
301         var nodeComputedStyle = computedStyle;
302         var styleRules = [];
303         for (var i = 0; sections && i < sections.length; ++i) {
304             var section = sections[i];
305             if (section instanceof WebInspector.BlankStylePropertiesSection)
306                 continue;
307             if (section.computedStyle)
308                 section.styleRule.style = nodeComputedStyle;
309             var styleRule = { section: section, style: section.styleRule.style, computedStyle: section.computedStyle, rule: section.rule, editable: !!(section.styleRule.style && section.styleRule.style.id) };
310             styleRules.push(styleRule);
311         }
312         return styleRules;
313     },
314
315     _rebuildStyleRules: function(node, styles)
316     {
317         var nodeComputedStyle = styles.computedStyle;
318         this.sections = {};
319
320         var styleRules = [];
321
322         styleRules.push({ computedStyle: true, selectorText: "", style: nodeComputedStyle, editable: false });
323
324         var styleAttributes = {};
325         for (var name in styles.styleAttributes) {
326             var attrStyle = { style: styles.styleAttributes[name], editable: false };
327             attrStyle.selectorText = WebInspector.panels.elements.treeOutline.nodeNameToCorrectCase(node.nodeName()) + "[" + name;
328             if (node.getAttribute(name))
329                 attrStyle.selectorText += "=" + node.getAttribute(name);
330             attrStyle.selectorText += "]";
331             styleRules.push(attrStyle);
332         }
333
334         // Show element's Style Attributes
335         if (styles.inlineStyle && node.nodeType() === Node.ELEMENT_NODE) {
336             var inlineStyle = { selectorText: "element.style", style: styles.inlineStyle, isAttribute: true };
337             styleRules.push(inlineStyle);
338         }
339
340         // Add rules in reverse order to match the cascade order.
341         if (styles.matchedCSSRules.length)
342             styleRules.push({ isStyleSeparator: true, text: WebInspector.UIString("Matched CSS Rules") });
343         for (var i = styles.matchedCSSRules.length - 1; i >= 0; --i) {
344             var rule = styles.matchedCSSRules[i];
345             styleRules.push({ style: rule.style, selectorText: rule.selectorText, sourceURL: rule.sourceURL, rule: rule, editable: !!(rule.style && rule.style.id) });
346         }
347
348         // Walk the node structure and identify styles with inherited properties.
349         var parentNode = node.parentNode;
350         function insertInheritedNodeSeparator(node)
351         {
352             var entry = {};
353             entry.isStyleSeparator = true;
354             entry.node = node;
355             styleRules.push(entry);
356         }
357
358         for (var parentOrdinal = 0; parentOrdinal < styles.inherited.length; ++parentOrdinal) {
359             var parentStyles = styles.inherited[parentOrdinal];
360             var separatorInserted = false;
361             if (parentStyles.inlineStyle) {
362                 if (this._containsInherited(parentStyles.inlineStyle)) {
363                     var inlineStyle = { selectorText: WebInspector.UIString("Style Attribute"), style: parentStyles.inlineStyle, isAttribute: true, isInherited: true };
364                     if (!separatorInserted) {
365                         insertInheritedNodeSeparator(parentNode);
366                         separatorInserted = true;
367                     }
368                     styleRules.push(inlineStyle);
369                 }
370             }
371
372             for (var i = parentStyles.matchedCSSRules.length - 1; i >= 0; --i) {
373                 var rulePayload = parentStyles.matchedCSSRules[i];
374                 if (!this._containsInherited(rulePayload.style))
375                     continue;
376                 var rule = rulePayload;
377                 if (!separatorInserted) {
378                     insertInheritedNodeSeparator(parentNode);
379                     separatorInserted = true;
380                 }
381                 styleRules.push({ style: rule.style, selectorText: rule.selectorText, sourceURL: rule.sourceURL, rule: rule, isInherited: true, editable: !!(rule.style && rule.style.id) });
382             }
383             parentNode = parentNode.parentNode;
384         }
385         return styleRules;
386     },
387
388     _markUsedProperties: function(styleRules, usedProperties, disabledComputedProperties)
389     {
390         var priorityUsed = false;
391
392         // Walk the style rules and make a list of all used and overloaded properties.
393         for (var i = 0; i < styleRules.length; ++i) {
394             var styleRule = styleRules[i];
395             if (styleRule.computedStyle || styleRule.isStyleSeparator)
396                 continue;
397             if (styleRule.section && styleRule.section.noAffect)
398                 continue;
399
400             styleRule.usedProperties = {};
401
402             var style = styleRule.style;
403             var allProperties = style.allProperties;
404             for (var j = 0; j < allProperties.length; ++j) {
405                 var property = allProperties[j];
406                 if (!property.isLive || !property.parsedOk)
407                     continue;
408                 var name = property.name;
409
410                 if (!priorityUsed && property.priority.length)
411                     priorityUsed = true;
412
413                 // If the property name is already used by another rule then this rule's
414                 // property is overloaded, so don't add it to the rule's usedProperties.
415                 if (!(name in usedProperties))
416                     styleRule.usedProperties[name] = true;
417
418                 if (name === "font") {
419                     // The font property is not reported as a shorthand. Report finding the individual
420                     // properties so they are visible in computed style.
421                     // FIXME: remove this when http://bugs.webkit.org/show_bug.cgi?id=15598 is fixed.
422                     styleRule.usedProperties["font-family"] = true;
423                     styleRule.usedProperties["font-size"] = true;
424                     styleRule.usedProperties["font-style"] = true;
425                     styleRule.usedProperties["font-variant"] = true;
426                     styleRule.usedProperties["font-weight"] = true;
427                     styleRule.usedProperties["line-height"] = true;
428                 }
429             }
430
431             // Add all the properties found in this style to the used properties list.
432             // Do this here so only future rules are affect by properties used in this rule.
433             for (var name in styleRules[i].usedProperties)
434                 usedProperties[name] = true;
435         }
436
437         if (priorityUsed) {
438             // Walk the properties again and account for !important.
439             var foundPriorityProperties = [];
440
441             // Walk in reverse to match the order !important overrides.
442             for (var i = (styleRules.length - 1); i >= 0; --i) {
443                 if (styleRules[i].computedStyle || styleRules[i].isStyleSeparator)
444                     continue;
445
446                 var style = styleRules[i].style;
447                 var allProperties = style.allProperties;
448                 for (var j = 0; j < allProperties.length; ++j) {
449                     var property = allProperties[j];
450                     if (!property.isLive)
451                         continue;
452                     var name = property.name;
453                     if (property.priority.length) {
454                         if (!(name in foundPriorityProperties))
455                             styleRules[i].usedProperties[name] = true;
456                         else
457                             delete styleRules[i].usedProperties[name];
458                         foundPriorityProperties[name] = true;
459                     } else if (name in foundPriorityProperties)
460                         delete styleRules[i].usedProperties[name];
461                 }
462             }
463         }
464     },
465
466     _refreshSectionsForStyleRules: function(styleRules, usedProperties, disabledComputedProperties, editedSection)
467     {
468         // Walk the style rules and update the sections with new overloaded and used properties.
469         for (var i = 0; i < styleRules.length; ++i) {
470             var styleRule = styleRules[i];
471             var section = styleRule.section;
472             if (styleRule.computedStyle) {
473                 section._disabledComputedProperties = disabledComputedProperties;
474                 section._usedProperties = usedProperties;
475                 section.update();
476             } else {
477                 section._usedProperties = styleRule.usedProperties;
478                 section.update(section === editedSection);
479             }
480         }
481     },
482
483     _rebuildSectionsForStyleRules: function(styleRules, usedProperties, disabledComputedProperties, pseudoId, anchorElement)
484     {
485         // Make a property section for each style rule.
486         var sections = [];
487         var lastWasSeparator = true;
488         for (var i = 0; i < styleRules.length; ++i) {
489             var styleRule = styleRules[i];
490             if (styleRule.isStyleSeparator) {
491                 var separatorElement = document.createElement("div");
492                 separatorElement.className = "styles-sidebar-separator";
493                 if (styleRule.node) {
494                     var link = WebInspector.panels.elements.linkifyNodeReference(styleRule.node);
495                     separatorElement.appendChild(document.createTextNode(WebInspector.UIString("Inherited from") + " "));
496                     separatorElement.appendChild(link);
497                     if (!sections.inheritedPropertiesSeparatorElement)
498                         sections.inheritedPropertiesSeparatorElement = separatorElement;
499                 } else if ("pseudoId" in styleRule) {
500                     var pseudoName = WebInspector.StylesSidebarPane.PseudoIdNames[styleRule.pseudoId];
501                     if (pseudoName)
502                         separatorElement.textContent = WebInspector.UIString("Pseudo ::%s element", pseudoName);
503                     else
504                         separatorElement.textContent = WebInspector.UIString("Pseudo element");
505                 } else
506                     separatorElement.textContent = styleRule.text;
507                 this.bodyElement.insertBefore(separatorElement, anchorElement);
508                 lastWasSeparator = true;
509                 continue;
510             }
511             var computedStyle = styleRule.computedStyle;
512
513             // Default editable to true if it was omitted.
514             var editable = styleRule.editable;
515             if (typeof editable === "undefined")
516                 editable = true;
517
518             if (computedStyle)
519                 var section = new WebInspector.ComputedStylePropertiesSection(styleRule, usedProperties, disabledComputedProperties, styleRules);
520             else
521                 var section = new WebInspector.StylePropertiesSection(this, styleRule, editable, styleRule.isInherited, lastWasSeparator);
522             section.pane = this;
523             section.expanded = true;
524
525             if (computedStyle) {
526                 this._computedStylePane.bodyElement.appendChild(section.element);
527                 lastWasSeparator = true;
528             } else {
529                 this.bodyElement.insertBefore(section.element, anchorElement);
530                 lastWasSeparator = false;
531             }
532             sections.push(section);
533         }
534         return sections;
535     },
536
537     _containsInherited: function(style)
538     {
539         var properties = style.allProperties;
540         for (var i = 0; i < properties.length; ++i) {
541             var property = properties[i];
542             // Does this style contain non-overridden inherited property?
543             if (property.isLive && property.name in WebInspector.StylesSidebarPane.InheritedProperties)
544                 return true;
545         }
546         return false;
547     },
548
549     _changeSetting: function(event)
550     {
551         var options = this.settingsSelectElement.options;
552         var selectedOption = options[this.settingsSelectElement.selectedIndex];
553         selectedOption.action(event);
554
555         // Select the correct color format setting again, since it needs to be selected.
556         var selectedIndex = 0;
557         for (var i = 0; i < options.length; ++i) {
558             if (options[i].value === WebInspector.settings.colorFormat) {
559                 selectedIndex = i;
560                 break;
561             }
562         }
563
564         this.settingsSelectElement.selectedIndex = selectedIndex;
565     },
566
567     _changeColorFormat: function(event)
568     {
569         var selectedOption = this.settingsSelectElement[this.settingsSelectElement.selectedIndex];
570         WebInspector.settings.colorFormat = selectedOption.value;
571
572         for (var pseudoId in this.sections) {
573             var sections = this.sections[pseudoId];
574             for (var i = 0; i < sections.length; ++i)
575                 sections[i].update(true);
576         }
577     },
578
579     _createNewRule: function(event)
580     {
581         this.expanded = true;
582         this.addBlankSection().startEditingSelector();
583     },
584
585     addBlankSection: function()
586     {
587         var blankSection = new WebInspector.BlankStylePropertiesSection(this, this.node ? this.node.appropriateSelectorFor(true) : "");
588         blankSection.pane = this;
589
590         var elementStyleSection = this.sections[0][1];
591         this.bodyElement.insertBefore(blankSection.element, elementStyleSection.element.nextSibling);
592
593         this.sections[0].splice(2, 0, blankSection);
594
595         return blankSection;
596     },
597
598     removeSection: function(section)
599     {
600         for (var pseudoId in this.sections) {
601             var sections = this.sections[pseudoId];
602             var index = sections.indexOf(section);
603             if (index === -1)
604                 continue;
605             sections.splice(index, 1);
606             if (section.element.parentNode)
607                 section.element.parentNode.removeChild(section.element);
608         }
609     },
610
611     registerShortcuts: function()
612     {
613         var section = WebInspector.shortcutsHelp.section(WebInspector.UIString("Styles Pane"));
614         var shortcut = WebInspector.KeyboardShortcut;
615         var keys = [
616             shortcut.shortcutToString(shortcut.Keys.Tab),
617             shortcut.shortcutToString(shortcut.Keys.Tab, shortcut.Modifiers.Shift)
618         ];
619         section.addRelatedKeys(keys, WebInspector.UIString("Next/previous property"));
620         keys = [
621             shortcut.shortcutToString(shortcut.Keys.Up),
622             shortcut.shortcutToString(shortcut.Keys.Down)
623         ];
624         section.addRelatedKeys(keys, WebInspector.UIString("Increment/decrement value"));
625         keys = [
626             shortcut.shortcutToString(shortcut.Keys.Up, shortcut.Modifiers.Shift),
627             shortcut.shortcutToString(shortcut.Keys.Down, shortcut.Modifiers.Shift)
628         ];
629         section.addRelatedKeys(keys, WebInspector.UIString("Increment/decrement by %f", 10));
630         keys = [
631             shortcut.shortcutToString(shortcut.Keys.PageUp),
632             shortcut.shortcutToString(shortcut.Keys.PageDown)
633         ];
634         section.addRelatedKeys(keys, WebInspector.UIString("Increment/decrement by %f", 10));
635         keys = [
636             shortcut.shortcutToString(shortcut.Keys.PageUp, shortcut.Modifiers.Shift),
637             shortcut.shortcutToString(shortcut.Keys.PageDown, shortcut.Modifiers.Shift)
638         ];
639         section.addRelatedKeys(keys, WebInspector.UIString("Increment/decrement by %f", 100));
640         keys = [
641             shortcut.shortcutToString(shortcut.Keys.PageUp, shortcut.Modifiers.Alt),
642             shortcut.shortcutToString(shortcut.Keys.PageDown, shortcut.Modifiers.Alt)
643         ];
644         section.addRelatedKeys(keys, WebInspector.UIString("Increment/decrement by %f", 0.1));
645     }
646 }
647
648 WebInspector.StylesSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;
649
650 WebInspector.ComputedStyleSidebarPane = function()
651 {
652     WebInspector.SidebarPane.call(this, WebInspector.UIString("Computed Style"));
653     var showInheritedCheckbox = new WebInspector.Checkbox(WebInspector.UIString("Show inherited"), "sidebar-pane-subtitle");
654     this.titleElement.appendChild(showInheritedCheckbox.element);
655
656     if (WebInspector.settings.showInheritedComputedStyleProperties) {
657         this.bodyElement.addStyleClass("show-inherited");
658         showInheritedCheckbox.checked = true;
659     }
660
661     function showInheritedToggleFunction(event)
662     {
663         WebInspector.settings.showInheritedComputedStyleProperties = showInheritedCheckbox.checked;
664         if (WebInspector.settings.showInheritedComputedStyleProperties)
665             this.bodyElement.addStyleClass("show-inherited");
666         else
667             this.bodyElement.removeStyleClass("show-inherited");
668     }
669
670     showInheritedCheckbox.addEventListener(showInheritedToggleFunction.bind(this));
671 }
672
673 WebInspector.ComputedStyleSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;
674
675 WebInspector.StylePropertiesSection = function(parentPane, styleRule, editable, isInherited, isFirstSection)
676 {
677     WebInspector.PropertiesSection.call(this, "");
678     this.element.className = "styles-section monospace" + (isFirstSection ? " first-styles-section" : "");
679
680     this._selectorElement = document.createElement("span");
681     this._selectorElement.textContent = styleRule.selectorText;
682     this.titleElement.appendChild(this._selectorElement);
683     if (Preferences.debugMode)
684         this._selectorElement.addEventListener("click", this._debugShowStyle.bind(this), false);
685
686     var openBrace = document.createElement("span");
687     openBrace.textContent = " {";
688     this.titleElement.appendChild(openBrace);
689
690     var closeBrace = document.createElement("div");
691     closeBrace.textContent = "}";
692     this.element.appendChild(closeBrace);
693
694     this._selectorElement.addEventListener("dblclick", this._handleSelectorDoubleClick.bind(this), false);
695     this.element.addEventListener("dblclick", this._handleEmptySpaceDoubleClick.bind(this), false);
696
697     this._parentPane = parentPane;
698     this.styleRule = styleRule;
699     this.rule = this.styleRule.rule;
700     this.editable = editable;
701     this.isInherited = isInherited;
702
703     // Prevent editing the user agent and user rules.
704     var isUserAgent = this.rule && this.rule.isUserAgent;
705     var isUser = this.rule && this.rule.isUser;
706     var isViaInspector = this.rule && this.rule.isViaInspector;
707
708     if (isUserAgent || isUser)
709         this.editable = false;
710
711     this._usedProperties = styleRule.usedProperties;
712
713     if (this.rule)
714         this.titleElement.addStyleClass("styles-selector");
715
716     function linkifyUncopyable(url, line)
717     {
718         var link = WebInspector.linkifyResourceAsNode(url, "resources", line + 1);
719         return link;
720     }
721
722     var subtitle = "";
723     if (this.styleRule.sourceURL)
724         this.subtitleElement.appendChild(linkifyUncopyable(this.styleRule.sourceURL, this.rule.sourceLine));
725     else if (isUserAgent)
726         subtitle = WebInspector.UIString("user agent stylesheet");
727     else if (isUser)
728         subtitle = WebInspector.UIString("user stylesheet");
729     else if (isViaInspector)
730         subtitle = WebInspector.UIString("via inspector");
731     else if (this.rule && this.rule.sourceURL)
732         this.subtitleElement.appendChild(linkifyUncopyable(this.rule.sourceURL, this.rule.sourceLine));
733
734     if (isInherited)
735         this.element.addStyleClass("show-inherited"); // This one is related to inherited rules, not compted style.
736     if (subtitle)
737         this.subtitle = subtitle;
738
739     this.identifier = styleRule.selectorText;
740     if (this.subtitle)
741         this.identifier += ":" + this.subtitle;
742
743     if (!this.editable)
744         this.element.addStyleClass("read-only");
745 }
746
747 WebInspector.StylePropertiesSection.prototype = {
748     collapse: function(dontRememberState)
749     {
750         // Overriding with empty body.
751     },
752
753     isPropertyInherited: function(propertyName)
754     {
755         if (this.isInherited) {
756             // While rendering inherited stylesheet, reverse meaning of this property.
757             // Render truly inherited properties with black, i.e. return them as non-inherited.
758             return !(propertyName in WebInspector.StylesSidebarPane.InheritedProperties);
759         }
760         return false;
761     },
762
763     isPropertyOverloaded: function(propertyName, shorthand)
764     {
765         if (!this._usedProperties || this.noAffect)
766             return false;
767
768         if (this.isInherited && !(propertyName in WebInspector.StylesSidebarPane.InheritedProperties)) {
769             // In the inherited sections, only show overrides for the potentially inherited properties.
770             return false;
771         }
772
773         var used = (propertyName in this._usedProperties);
774         if (used || !shorthand)
775             return !used;
776
777         // Find out if any of the individual longhand properties of the shorthand
778         // are used, if none are then the shorthand is overloaded too.
779         var longhandProperties = this.styleRule.style.getLonghandProperties(propertyName);
780         for (var j = 0; j < longhandProperties.length; ++j) {
781             var individualProperty = longhandProperties[j];
782             if (individualProperty.name in this._usedProperties)
783                 return false;
784         }
785
786         return true;
787     },
788
789     nextEditableSibling: function()
790     {
791         var curSection = this;
792         do {
793             curSection = curSection.nextSibling;
794         } while (curSection && !curSection.editable);
795
796         return curSection;
797     },
798
799     previousEditableSibling: function()
800     {
801         var curSection = this;
802         do {
803             curSection = curSection.previousSibling;
804         } while (curSection && !curSection.editable);
805
806         return curSection;
807     },
808
809     update: function(full)
810     {
811         if (full) {
812             this.propertiesTreeOutline.removeChildren();
813             this.populated = false;
814         } else {
815             var child = this.propertiesTreeOutline.children[0];
816             while (child) {
817                 child.overloaded = this.isPropertyOverloaded(child.name, child.shorthand);
818                 child = child.traverseNextTreeElement(false, null, true);
819             }
820         }
821         this.afterUpdate();
822     },
823
824     afterUpdate: function()
825     {
826         if (this._afterUpdate) {
827             this._afterUpdate(this);
828             delete this._afterUpdate;
829         }
830     },
831
832     onpopulate: function()
833     {
834         var style = this.styleRule.style;
835
836         var handledProperties = {};
837         var shorthandNames = {};
838
839         this.uniqueProperties = [];
840         var allProperties = style.allProperties;
841         for (var i = 0; i < allProperties.length; ++i)
842             this.uniqueProperties.push(allProperties[i]);
843
844         // Collect all shorthand names.
845         for (var i = 0; i < this.uniqueProperties.length; ++i) {
846             var property = this.uniqueProperties[i];
847             if (property.disabled)
848                 continue;
849             if (property.shorthand)
850                 shorthandNames[property.shorthand] = true;
851         }
852
853         // Collect all shorthand names.
854         for (var i = 0; i < this.uniqueProperties.length; ++i) {
855             var property = this.uniqueProperties[i];
856             var disabled = property.disabled;
857             if (!disabled && this.disabledComputedProperties && !(property.name in this.usedProperties) && property.name in this.disabledComputedProperties)
858                 disabled = true;
859
860             var shorthand = !disabled ? property.shorthand : null;
861
862             if (shorthand && shorthand in handledProperties)
863                 continue;
864
865             if (shorthand) {
866                 property = style.getLiveProperty(shorthand);
867                 if (!property)
868                     property = new WebInspector.CSSProperty(style, style.allProperties.length, shorthand, style.getShorthandValue(shorthand), style.getShorthandPriority(shorthand), "style", true, true, "");
869             }
870
871             var isShorthand = !!(property.isLive && (shorthand || shorthandNames[property.name]));
872             var inherited = this.isPropertyInherited(property.name);
873             var overloaded = this.isPropertyOverloaded(property.name, isShorthand);
874
875             var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this.styleRule, style, property, isShorthand, inherited, overloaded);
876             this.propertiesTreeOutline.appendChild(item);
877             handledProperties[property.name] = property;
878         }
879     },
880
881     findTreeElementWithName: function(name)
882     {
883         var treeElement = this.propertiesTreeOutline.children[0];
884         while (treeElement) {
885             if (treeElement.name === name)
886                 return treeElement;
887             treeElement = treeElement.traverseNextTreeElement(true, null, true);
888         }
889         return null;
890     },
891
892     addNewBlankProperty: function(optionalIndex)
893     {
894         var style = this.styleRule.style;
895         var property = style.newBlankProperty();
896         var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this.styleRule, style, property, false, false, false);
897         this.propertiesTreeOutline.appendChild(item);
898         item.listItemElement.textContent = "";
899         item._newProperty = true;
900         item.updateTitle();
901         return item;
902     },
903
904     _debugShowStyle: function(anchor)
905     {
906         var boundHandler;
907         function removeStyleBox(element, event)
908         {
909             if (event.target === element) {
910                 event.stopPropagation();
911                 return;
912             }
913             document.body.removeChild(element);
914             document.getElementById("main").removeEventListener("mousedown", boundHandler, true);
915         }
916
917         if (!event.shiftKey)
918             return;
919
920         var container = document.createElement("div");
921         var element = document.createElement("span");
922         container.appendChild(element);
923         element.style.background = "yellow";
924         element.style.display = "inline-block";
925         container.style.cssText = "z-index: 2000000; position: absolute; top: 50px; left: 50px; white-space: pre; overflow: auto; background: white; font-family: monospace; font-size: 12px; border: 1px solid black; opacity: 0.85; -webkit-user-select: text; padding: 2px;";
926         container.style.width = (document.body.offsetWidth - 100) + "px";
927         container.style.height = (document.body.offsetHeight - 100) + "px";
928         document.body.appendChild(container);
929         if (this.rule)
930             element.textContent = this.rule.selectorText + " {" + ((this.styleRule.style.cssText !== undefined) ? this.styleRule.style.cssText : "<no cssText>") + "}";
931         else
932             element.textContent = this.styleRule.style.cssText;
933         boundHandler = removeStyleBox.bind(null, container);
934         document.getElementById("main").addEventListener("mousedown", boundHandler, true);
935     },
936
937     _handleEmptySpaceDoubleClick: function(event)
938     {
939         if (event.target.hasStyleClass("header") || this.element.hasStyleClass("read-only")) {
940             event.stopPropagation();
941             return;
942         }
943         this.expand();
944         this.addNewBlankProperty().startEditing();
945     },
946
947     _handleSelectorClick: function(event)
948     {
949         event.stopPropagation();
950     },
951
952     _handleSelectorDoubleClick: function(event)
953     {
954         this._startEditingOnMouseEvent();
955         event.stopPropagation();
956     },
957
958     _startEditingOnMouseEvent: function()
959     {
960         if (!this.editable)
961             return;
962
963         if (!this.rule && this.propertiesTreeOutline.children.length === 0) {
964             this.expand();
965             this.addNewBlankProperty().startEditing();
966             return;
967         }
968
969         if (!this.rule)
970             return;
971
972         this.startEditingSelector();
973     },
974
975     startEditingSelector: function()
976     {
977         var element = this._selectorElement;
978         if (WebInspector.isBeingEdited(element))
979             return;
980
981         WebInspector.startEditing(this._selectorElement, {
982             context: null,
983             commitHandler: this.editingSelectorCommitted.bind(this),
984             cancelHandler: this.editingSelectorCancelled.bind(this)
985         });
986         window.getSelection().setBaseAndExtent(element, 0, element, 1);
987     },
988
989     editingSelectorCommitted: function(element, newContent, oldContent, context, moveDirection)
990     {
991         function moveToNextIfNeeded() {
992             if (!moveDirection)
993                 return;
994
995             if (moveDirection === "forward") {
996                 this.expand();
997                 if (this.propertiesTreeOutline.children.length === 0)
998                     this.addNewBlankProperty().startEditing();
999                 else {
1000                     var item = this.propertiesTreeOutline.children[0]
1001                     item.startEditing(item.nameElement);
1002                 }
1003             } else {
1004                 var previousSection = this.previousEditableSibling();
1005                 if (!previousSection)
1006                     return;
1007
1008                 previousSection.expand();
1009                 previousSection.addNewBlankProperty().startEditing();
1010             }
1011         }
1012
1013         if (newContent === oldContent)
1014             return moveToNextIfNeeded.call(this);
1015
1016         var self = this;
1017
1018         function successCallback(newRule, doesAffectSelectedNode)
1019         {
1020             if (!doesAffectSelectedNode) {
1021                 self.noAffect = true;
1022                 self.element.addStyleClass("no-affect");
1023             } else {
1024                 delete self.noAffect;
1025                 self.element.removeStyleClass("no-affect");
1026             }
1027
1028             self.rule = newRule;
1029             self.styleRule = { section: self, style: newRule.style, selectorText: newRule.selectorText, sourceURL: newRule.sourceURL, rule: newRule };
1030
1031             var oldIdentifier = this.identifier;
1032             self.identifier = newRule.selectorText + ":" + self.subtitleElement.textContent;
1033
1034             self.pane.update();
1035
1036             WebInspector.panels.elements.renameSelector(oldIdentifier, this.identifier, oldContent, newContent);
1037
1038             moveToNextIfNeeded.call(self);
1039         }
1040
1041         var focusedNode = WebInspector.panels.elements.focusedDOMNode;
1042         WebInspector.cssModel.setRuleSelector(this.rule.id, focusedNode ? focusedNode.id : 0, newContent, successCallback, moveToNextIfNeeded.bind(this));
1043     },
1044
1045     editingSelectorCancelled: function()
1046     {
1047         // Do nothing, this is overridden by BlankStylePropertiesSection.
1048     }
1049 }
1050
1051 WebInspector.StylePropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype;
1052
1053 WebInspector.ComputedStylePropertiesSection = function(styleRule, usedProperties, disabledComputedProperties)
1054 {
1055     WebInspector.PropertiesSection.call(this, "");
1056     this.headerElement.addStyleClass("hidden");
1057     this.element.className = "styles-section monospace first-styles-section read-only computed-style";
1058     this.styleRule = styleRule;
1059     this._usedProperties = usedProperties;
1060     this._disabledComputedProperties = disabledComputedProperties;
1061     this._alwaysShowComputedProperties = { "display": true, "height": true, "width": true };
1062     this.computedStyle = true;
1063     this._propertyTreeElements = {};
1064     this._expandedPropertyNames = {};
1065 }
1066
1067 WebInspector.ComputedStylePropertiesSection.prototype = {
1068     collapse: function(dontRememberState)
1069     {
1070         // Overriding with empty body.
1071     },
1072
1073     _isPropertyInherited: function(propertyName)
1074     {
1075         return !(propertyName in this._usedProperties) && !(propertyName in this._alwaysShowComputedProperties) && !(propertyName in this._disabledComputedProperties);
1076     },
1077
1078     update: function()
1079     {
1080         this._expandedPropertyNames = {};
1081         for (var name in this._propertyTreeElements) {
1082             if (this._propertyTreeElements[name].expanded)
1083                 this._expandedPropertyNames[name] = true;
1084         }
1085         this._propertyTreeElements = {};
1086         this.propertiesTreeOutline.removeChildren();
1087         this.populated = false;
1088     },
1089
1090     onpopulate: function()
1091     {
1092         function sorter(a, b)
1093         {
1094             return a.name.localeCompare(b.name);
1095         }
1096
1097         var style = this.styleRule.style;
1098         var uniqueProperties = [];
1099         var allProperties = style.allProperties;
1100         for (var i = 0; i < allProperties.length; ++i)
1101             uniqueProperties.push(allProperties[i]);
1102         uniqueProperties.sort(sorter);
1103
1104         this._propertyTreeElements = {};
1105         for (var i = 0; i < uniqueProperties.length; ++i) {
1106             var property = uniqueProperties[i];
1107             var inherited = this._isPropertyInherited(property.name);
1108             var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this.styleRule, style, property, false, inherited, false);
1109             this.propertiesTreeOutline.appendChild(item);
1110             this._propertyTreeElements[property.name] = item;
1111         }
1112     },
1113
1114     rebuildComputedTrace: function(sections)
1115     {
1116         for (var i = 0; i < sections.length; ++i) {
1117             var section = sections[i];
1118             if (section.computedStyle || section instanceof WebInspector.BlankStylePropertiesSection)
1119                 continue;
1120
1121             for (var j = 0; j < section.uniqueProperties.length; ++j) {
1122                 var property = section.uniqueProperties[j];
1123                 if (property.disabled)
1124                     continue;
1125                 if (section.isInherited && !(property.name in WebInspector.StylesSidebarPane.InheritedProperties))
1126                     continue;
1127
1128                 var treeElement = this._propertyTreeElements[property.name];
1129                 if (treeElement) {
1130                     var selectorText = section.styleRule.selectorText;
1131                     var value = property.value;
1132                     var title = "<span style='color: gray'>" + selectorText + "</span> - " + value;
1133                     var subtitle = " <span style='float:right'>" + section.subtitleElement.innerHTML + "</span>";
1134                     var childElement = new TreeElement(null, null, false);
1135                     childElement.titleHTML = title + subtitle;
1136                     treeElement.appendChild(childElement);
1137                     if (section.isPropertyOverloaded(property.name))
1138                         childElement.listItemElement.addStyleClass("overloaded");
1139                     if (!property.parsedOk)
1140                         childElement.listItemElement.addStyleClass("not-parsed-ok");
1141                 }
1142             }
1143         }
1144
1145         // Restore expanded state after update.
1146         for (var name in this._expandedPropertyNames) {
1147             if (name in this._propertyTreeElements)
1148                 this._propertyTreeElements[name].expand();
1149         }
1150     }
1151 }
1152
1153 WebInspector.ComputedStylePropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype;
1154
1155 WebInspector.BlankStylePropertiesSection = function(parentPane, defaultSelectorText)
1156 {
1157     WebInspector.StylePropertiesSection.call(this, parentPane, {selectorText: defaultSelectorText, rule: {isViaInspector: true}}, true, false, false);
1158     this.element.addStyleClass("blank-section");
1159 }
1160
1161 WebInspector.BlankStylePropertiesSection.prototype = {
1162     expand: function()
1163     {
1164         // Do nothing, blank sections are not expandable.
1165     },
1166
1167     editingSelectorCommitted: function(element, newContent, oldContent, context)
1168     {
1169         var self = this;
1170         function successCallback(newRule, doesSelectorAffectSelectedNode)
1171         {
1172             var styleRule = { section: self, style: newRule.style, selectorText: newRule.selectorText, sourceURL: newRule.sourceURL, rule: newRule };
1173             self.makeNormal(styleRule);
1174
1175             if (!doesSelectorAffectSelectedNode) {
1176                 self.noAffect = true;
1177                 self.element.addStyleClass("no-affect");
1178             }
1179
1180             self.subtitleElement.textContent = WebInspector.UIString("via inspector");
1181             self.expand();
1182
1183             self.addNewBlankProperty().startEditing();
1184         }
1185
1186         WebInspector.cssModel.addRule(this.pane.node.id, newContent, successCallback, this.editingSelectorCancelled.bind(this));
1187     },
1188
1189     editingSelectorCancelled: function()
1190     {
1191         this.pane.removeSection(this);
1192     },
1193
1194     makeNormal: function(styleRule)
1195     {
1196         this.element.removeStyleClass("blank-section");
1197         this.styleRule = styleRule;
1198         this.rule = styleRule.rule;
1199         this.identifier = styleRule.selectorText + ":via inspector";
1200         this.__proto__ = WebInspector.StylePropertiesSection.prototype;
1201     }
1202 }
1203
1204 WebInspector.BlankStylePropertiesSection.prototype.__proto__ = WebInspector.StylePropertiesSection.prototype;
1205
1206 WebInspector.StylePropertyTreeElement = function(parentPane, styleRule, style, property, shorthand, inherited, overloaded)
1207 {
1208     this._parentPane = parentPane;
1209     this._styleRule = styleRule;
1210     this.style = style;
1211     this.property = property;
1212     this.shorthand = shorthand;
1213     this._inherited = inherited;
1214     this._overloaded = overloaded;
1215
1216     // Pass an empty title, the title gets made later in onattach.
1217     TreeElement.call(this, "", null, shorthand);
1218 }
1219
1220 WebInspector.StylePropertyTreeElement.prototype = {
1221     get inherited()
1222     {
1223         return this._inherited;
1224     },
1225
1226     set inherited(x)
1227     {
1228         if (x === this._inherited)
1229             return;
1230         this._inherited = x;
1231         this.updateState();
1232     },
1233
1234     get overloaded()
1235     {
1236         return this._overloaded;
1237     },
1238
1239     set overloaded(x)
1240     {
1241         if (x === this._overloaded)
1242             return;
1243         this._overloaded = x;
1244         this.updateState();
1245     },
1246
1247     get disabled()
1248     {
1249         return this.property.disabled;
1250     },
1251
1252     get name()
1253     {
1254         if (!this.disabled || !this.property.text)
1255             return this.property.name;
1256
1257         var text = this.property.text;
1258         var index = text.indexOf(":");
1259         if (index < 1)
1260             return this.property.name;
1261
1262         return text.substring(0, index).trim();
1263     },
1264
1265     get priority()
1266     {
1267         if (this.disabled)
1268             return ""; // rely upon raw text to render it in the value field
1269         return this.property.priority;
1270     },
1271
1272     get value()
1273     {
1274         if (!this.disabled || !this.property.text)
1275             return this.property.value;
1276
1277         var match = this.property.text.match(/(.*);\s*/);
1278         if (!match || !match[1])
1279             return this.property.value;
1280
1281         var text = match[1];
1282         var index = text.indexOf(":");
1283         if (index < 1)
1284             return this.property.value;
1285
1286         return text.substring(index + 1).trim();
1287     },
1288
1289     get parsedOk()
1290     {
1291         return this.property.parsedOk;
1292     },
1293
1294     onattach: function()
1295     {
1296         this.updateTitle();
1297     },
1298
1299     updateTitle: function()
1300     {
1301         var value = this.value;
1302
1303         this.updateState();
1304
1305         var enabledCheckboxElement;
1306         if (this.parsedOk) {
1307             enabledCheckboxElement = document.createElement("input");
1308             enabledCheckboxElement.className = "enabled-button";
1309             enabledCheckboxElement.type = "checkbox";
1310             enabledCheckboxElement.checked = !this.disabled;
1311             enabledCheckboxElement.addEventListener("change", this.toggleEnabled.bind(this), false);
1312         }
1313
1314         var nameElement = document.createElement("span");
1315         nameElement.className = "webkit-css-property";
1316         nameElement.textContent = this.name;
1317         this.nameElement = nameElement;
1318
1319         var valueElement = document.createElement("span");
1320         valueElement.className = "value";
1321         this.valueElement = valueElement;
1322
1323         if (value) {
1324             var self = this;
1325
1326             function processValue(regex, processor, nextProcessor, valueText)
1327             {
1328                 var container = document.createDocumentFragment();
1329
1330                 var items = valueText.replace(regex, "\0$1\0").split("\0");
1331                 for (var i = 0; i < items.length; ++i) {
1332                     if ((i % 2) === 0) {
1333                         if (nextProcessor)
1334                             container.appendChild(nextProcessor(items[i]));
1335                         else
1336                             container.appendChild(document.createTextNode(items[i]));
1337                     } else {
1338                         var processedNode = processor(items[i]);
1339                         if (processedNode)
1340                             container.appendChild(processedNode);
1341                     }
1342                 }
1343
1344                 return container;
1345             }
1346
1347             function linkifyURL(url)
1348             {
1349                 var hrefUrl = url;
1350                 var match = hrefUrl.match(/['"]?([^'"]+)/);
1351                 if (match)
1352                     hrefUrl = match[1];
1353                 var container = document.createDocumentFragment();
1354                 container.appendChild(document.createTextNode("url("));
1355                 if (self._styleRule.sourceURL)
1356                     hrefUrl = WebInspector.completeURL(self._styleRule.sourceURL, hrefUrl);
1357                 else if (WebInspector.panels.elements.focusedDOMNode)
1358                     hrefUrl = WebInspector.resourceURLForRelatedNode(WebInspector.panels.elements.focusedDOMNode, hrefUrl);
1359                 var hasResource = !!WebInspector.resourceForURL(hrefUrl);
1360                 // FIXME: WebInspector.linkifyURLAsNode() should really use baseURI.
1361                 container.appendChild(WebInspector.linkifyURLAsNode(hrefUrl, url, null, hasResource));
1362                 container.appendChild(document.createTextNode(")"));
1363                 return container;
1364             }
1365
1366             function processColor(text)
1367             {
1368                 try {
1369                     var color = new WebInspector.Color(text);
1370                 } catch (e) {
1371                     return document.createTextNode(text);
1372                 }
1373
1374                 var swatchElement = document.createElement("span");
1375                 swatchElement.title = WebInspector.UIString("Click to change color format");
1376                 swatchElement.className = "swatch";
1377                 swatchElement.style.setProperty("background-color", text);
1378
1379                 swatchElement.addEventListener("click", changeColorDisplay, false);
1380                 swatchElement.addEventListener("dblclick", function(event) { event.stopPropagation() }, false);
1381
1382                 var format;
1383                 if (WebInspector.settings.colorFormat === "original")
1384                     format = "original";
1385                 else if (Preferences.showColorNicknames && color.nickname)
1386                     format = "nickname";
1387                 else if (WebInspector.settings.colorFormat === "rgb")
1388                     format = (color.simple ? "rgb" : "rgba");
1389                 else if (WebInspector.settings.colorFormat === "hsl")
1390                     format = (color.simple ? "hsl" : "hsla");
1391                 else if (color.simple)
1392                     format = (color.hasShortHex() ? "shorthex" : "hex");
1393                 else
1394                     format = "rgba";
1395
1396                 var colorValueElement = document.createElement("span");
1397                 colorValueElement.textContent = color.toString(format);
1398
1399                 function nextFormat(curFormat)
1400                 {
1401                     // The format loop is as follows:
1402                     // * original
1403                     // * rgb(a)
1404                     // * hsl(a)
1405                     // * nickname (if the color has a nickname)
1406                     // * if the color is simple:
1407                     //   - shorthex (if has short hex)
1408                     //   - hex
1409                     switch (curFormat) {
1410                         case "original":
1411                             return color.simple ? "rgb" : "rgba";
1412
1413                         case "rgb":
1414                         case "rgba":
1415                             return color.simple ? "hsl" : "hsla";
1416
1417                         case "hsl":
1418                         case "hsla":
1419                             if (color.nickname)
1420                                 return "nickname";
1421                             if (color.simple)
1422                                 return color.hasShortHex() ? "shorthex" : "hex";
1423                             else
1424                                 return "original";
1425
1426                         case "shorthex":
1427                             return "hex";
1428
1429                         case "hex":
1430                             return "original";
1431
1432                         case "nickname":
1433                             if (color.simple)
1434                                 return color.hasShortHex() ? "shorthex" : "hex";
1435                             else
1436                                 return "original";
1437
1438                         default:
1439                             return null;
1440                     }
1441                 }
1442
1443                 function changeColorDisplay(event)
1444                 {
1445                     do {
1446                         format = nextFormat(format);
1447                         var currentValue = color.toString(format || "");
1448                     } while (format && currentValue === color.value && format !== "original");
1449
1450                     if (format)
1451                         colorValueElement.textContent = currentValue;
1452                 }
1453
1454                 var container = document.createDocumentFragment();
1455                 container.appendChild(swatchElement);
1456                 container.appendChild(colorValueElement);
1457                 return container;
1458             }
1459
1460             var colorRegex = /((?:rgb|hsl)a?\([^)]+\)|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}|\b\w+\b(?!-))/g;
1461             var colorProcessor = processValue.bind(window, colorRegex, processColor, null);
1462
1463             valueElement.appendChild(processValue(/url\(\s*([^)\s]+)\s*\)/g, linkifyURL, WebInspector.CSSKeywordCompletions.isColorAwareProperty(self.name) ? colorProcessor : null, value));
1464         }
1465
1466         this.listItemElement.removeChildren();
1467         nameElement.normalize();
1468         valueElement.normalize();
1469
1470         if (!this.treeOutline)
1471             return;
1472
1473         // Append the checkbox for root elements of an editable section.
1474         if (enabledCheckboxElement && this.treeOutline.section && this.treeOutline.section.editable && this.parent.root)
1475             this.listItemElement.appendChild(enabledCheckboxElement);
1476         this.listItemElement.appendChild(nameElement);
1477         this.listItemElement.appendChild(document.createTextNode(": "));
1478         this.listItemElement.appendChild(valueElement);
1479         this.listItemElement.appendChild(document.createTextNode(";"));
1480
1481         if (!this.parsedOk) {
1482             // Avoid having longhands under an invalid shorthand.
1483             this.hasChildren = false;
1484             this.listItemElement.addStyleClass("not-parsed-ok");
1485         }
1486         if (this.property.inactive)
1487             this.listItemElement.addStyleClass("inactive");
1488
1489         this.tooltip = this.property.propertyText;
1490     },
1491
1492     updateAll: function(updateAllRules)
1493     {
1494         if (!this.treeOutline)
1495             return;
1496         if (updateAllRules && this.treeOutline.section && this.treeOutline.section.pane)
1497             this.treeOutline.section.pane.update(null, this.treeOutline.section);
1498         else if (this.treeOutline.section)
1499             this.treeOutline.section.update(true);
1500         else
1501             this.updateTitle(); // FIXME: this will not show new properties. But we don't hit this case yet.
1502     },
1503
1504     toggleEnabled: function(event)
1505     {
1506         var disabled = !event.target.checked;
1507
1508         function callback(newStyle)
1509         {
1510             if (!newStyle)
1511                 return;
1512
1513             this.style = newStyle;
1514             this._styleRule.style = newStyle;
1515
1516             if (this.treeOutline.section && this.treeOutline.section.pane)
1517                 this.treeOutline.section.pane.dispatchEventToListeners("style property toggled");
1518
1519             this.updateAll(true);
1520         }
1521
1522         this.property.setDisabled(disabled, callback.bind(this));
1523     },
1524
1525     updateState: function()
1526     {
1527         if (!this.listItemElement)
1528             return;
1529
1530         if (this.style.isPropertyImplicit(this.name) || this.value === "initial")
1531             this.listItemElement.addStyleClass("implicit");
1532         else
1533             this.listItemElement.removeStyleClass("implicit");
1534
1535         this.selectable = !this.inherited;
1536         if (this.inherited)
1537             this.listItemElement.addStyleClass("inherited");
1538         else
1539             this.listItemElement.removeStyleClass("inherited");
1540
1541         if (this.overloaded)
1542             this.listItemElement.addStyleClass("overloaded");
1543         else
1544             this.listItemElement.removeStyleClass("overloaded");
1545
1546         if (this.disabled)
1547             this.listItemElement.addStyleClass("disabled");
1548         else
1549             this.listItemElement.removeStyleClass("disabled");
1550     },
1551
1552     onpopulate: function()
1553     {
1554         // Only populate once and if this property is a shorthand.
1555         if (this.children.length || !this.shorthand)
1556             return;
1557
1558         var longhandProperties = this.style.getLonghandProperties(this.name);
1559         for (var i = 0; i < longhandProperties.length; ++i) {
1560             var name = longhandProperties[i].name;
1561
1562
1563             if (this.treeOutline.section) {
1564                 var inherited = this.treeOutline.section.isPropertyInherited(name);
1565                 var overloaded = this.treeOutline.section.isPropertyOverloaded(name);
1566             }
1567
1568             var liveProperty = this.style.getLiveProperty(name);
1569             var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this._styleRule, this.style, liveProperty, false, inherited, overloaded);
1570             this.appendChild(item);
1571         }
1572     },
1573
1574     ondblclick: function(event)
1575     {
1576         this.startEditing(event.target);
1577         event.stopPropagation();
1578     },
1579
1580     restoreNameElement: function()
1581     {
1582         // Restore <span class="webkit-css-property"> if it doesn't yet exist or was accidentally deleted.
1583         if (this.nameElement === this.listItemElement.querySelector(".webkit-css-property"))
1584             return;
1585
1586         this.nameElement = document.createElement("span");
1587         this.nameElement.className = "webkit-css-property";
1588         this.nameElement.textContent = "";
1589         this.listItemElement.insertBefore(this.nameElement, this.listItemElement.firstChild);
1590     },
1591
1592     startEditing: function(selectElement)
1593     {
1594         // FIXME: we don't allow editing of longhand properties under a shorthand right now.
1595         if (this.parent.shorthand)
1596             return;
1597
1598         if (this.treeOutline.section && !this.treeOutline.section.editable)
1599             return;
1600
1601         if (!selectElement)
1602             selectElement = this.nameElement; // No arguments passed in - edit the name element by default.
1603         else
1604             selectElement = selectElement.enclosingNodeOrSelfWithClass("webkit-css-property") || selectElement.enclosingNodeOrSelfWithClass("value");
1605
1606         var isEditingName = selectElement === this.nameElement;
1607         if (!isEditingName && selectElement !== this.valueElement) {
1608             // Double-click in the LI - start editing value.
1609             isEditingName = false;
1610             selectElement = this.valueElement;
1611         }
1612
1613         if (WebInspector.isBeingEdited(selectElement))
1614             return;
1615
1616         var context = {
1617             expanded: this.expanded,
1618             hasChildren: this.hasChildren,
1619             keyDownListener: isEditingName ? null : this.editingValueKeyDown.bind(this),
1620             isEditingName: isEditingName,
1621         };
1622
1623         // Lie about our children to prevent expanding on double click and to collapse shorthands.
1624         this.hasChildren = false;
1625
1626         if (!isEditingName)
1627             selectElement.addEventListener("keydown", context.keyDownListener, false);
1628         if (selectElement.parentElement)
1629             selectElement.parentElement.addStyleClass("child-editing");
1630         selectElement.textContent = selectElement.textContent; // remove color swatch and the like
1631
1632         function shouldCommitValueSemicolon(text, cursorPosition)
1633         {
1634             // FIXME: should this account for semicolons inside comments?
1635             var openQuote = "";
1636             for (var i = 0; i < cursorPosition; ++i) {
1637                 var ch = text[i];
1638                 if (ch === "\\" && openQuote !== "")
1639                     ++i; // skip next character inside string
1640                 else if (!openQuote && (ch === "\"" || ch === "'"))
1641                     openQuote = ch;
1642                 else if (openQuote === ch)
1643                     openQuote = "";
1644             }
1645             return !openQuote;
1646         }
1647
1648         function nameValueFinishHandler(context, isEditingName, event)
1649         {
1650             // FIXME: the ":"/";" detection does not work for non-US layouts due to the event being keydown rather than keypress.
1651             var isFieldInputTerminated = (event.keyCode === WebInspector.KeyboardShortcut.Keys.Semicolon.code) &&
1652                 (isEditingName ? event.shiftKey : (!event.shiftKey && shouldCommitValueSemicolon(event.target.textContent, event.target.selectionLeftOffset)));
1653             if (isEnterKey(event) || isFieldInputTerminated) {
1654                 // Enter or colon (for name)/semicolon outside of string (for value).
1655                 event.preventDefault();
1656                 return "move-forward";
1657             } else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code || event.keyIdentifier === "U+001B")
1658                 return "cancel";
1659             else if (!isEditingName && this._newProperty && event.keyCode === WebInspector.KeyboardShortcut.Keys.Backspace.code) {
1660                 // For a new property, when Backspace is pressed at the beginning of new property value, move back to the property name.
1661                 var selection = window.getSelection();
1662                 if (selection.isCollapsed && !selection.focusOffset) {
1663                     event.preventDefault();
1664                     return "move-backward";
1665                 }
1666             } else if (event.keyIdentifier === "U+0009") // Tab key.
1667                 return "move-" + (event.shiftKey ? "backward" : "forward");
1668         }
1669
1670         function pasteHandler(context, event)
1671         {
1672             var data = event.clipboardData.getData("Text");
1673             if (!data)
1674                 return;
1675             var colonIdx = data.indexOf(":");
1676             if (colonIdx < 0)
1677                 return;
1678             var name = data.substring(0, colonIdx).trim();
1679             var value = data.substring(colonIdx + 1).trim();
1680
1681             event.preventDefault();
1682
1683             if (!("originalName" in context)) {
1684                 context.originalName = this.nameElement.textContent;
1685                 context.originalValue = this.valueElement.textContent;
1686             }
1687             this.nameElement.textContent = name;
1688             this.valueElement.textContent = value;
1689             this.nameElement.normalize();
1690             this.valueElement.normalize();
1691
1692             return "move-forward";
1693         }
1694
1695         delete this.originalPropertyText;
1696         WebInspector.panels.elements.startEditingStyle();
1697         WebInspector.startEditing(selectElement, {
1698             context: context,
1699             commitHandler: this.editingCommitted.bind(this),
1700             cancelHandler: this.editingCancelled.bind(this),
1701             customFinishHandler: nameValueFinishHandler.bind(this, context, isEditingName),
1702             pasteHandler: isEditingName ? pasteHandler.bind(this, context) : null
1703         });
1704
1705         this._prompt = new WebInspector.StylesSidebarPane.CSSPropertyPrompt(selectElement, isEditingName ? WebInspector.cssNameCompletions : WebInspector.CSSKeywordCompletions.forProperty(this.nameElement.textContent));
1706         window.getSelection().setBaseAndExtent(selectElement, 0, selectElement, 1);
1707     },
1708
1709     editingValueKeyDown: function(event)
1710     {
1711         if (event.handled)
1712             return;
1713         if (this._handleUpOrDownKeyPressed(event))
1714             return;
1715
1716         this._applyFreeFlowStyleTextEdit();
1717     },
1718
1719     _applyFreeFlowStyleTextEdit: function(now)
1720     {
1721         if (this._applyFreeFlowStyleTextEditTimer)
1722             clearTimeout(this._applyFreeFlowStyleTextEditTimer);
1723
1724         function apply()
1725         {
1726             this.applyStyleText(this.nameElement.textContent + ": " + this.valueElement.textContent);
1727         }
1728         if (now)
1729             apply.call(this);
1730         else
1731             this._applyFreeFlowStyleTextEditTimer = setTimeout(apply.bind(this), 100);
1732     },
1733
1734     kickFreeFlowStyleEditForTest: function()
1735     {
1736         this._applyFreeFlowStyleTextEdit(true);
1737     },
1738
1739     _handleUpOrDownKeyPressed: function(event)
1740     {
1741         var arrowKeyPressed = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down");
1742         var pageKeyPressed = (event.keyIdentifier === "PageUp" || event.keyIdentifier === "PageDown");
1743         if (!arrowKeyPressed && !pageKeyPressed)
1744             return false;
1745
1746         var selection = window.getSelection();
1747         if (!selection.rangeCount)
1748             return false;
1749
1750         var selectionRange = selection.getRangeAt(0);
1751         if (selectionRange.commonAncestorContainer !== this.valueElement && !selectionRange.commonAncestorContainer.isDescendant(this.valueElement))
1752             return false;
1753
1754         var wordRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, WebInspector.StylesSidebarPane.StyleValueDelimiters, this.valueElement);
1755         var wordString = wordRange.toString();
1756         var replacementString;
1757         var prefix, suffix, number;
1758
1759         var matches;
1760         matches = /(.*#)([\da-fA-F]+)(.*)/.exec(wordString);
1761         if (matches && matches.length) {
1762             prefix = matches[1];
1763             suffix = matches[3];
1764             number = WebInspector.StylesSidebarPane.alteredHexNumber(matches[2], event);
1765
1766             replacementString = prefix + number + suffix;
1767         } else {
1768             matches = /(.*?)(-?(?:\d+(?:\.\d+)?|\.\d+))(.*)/.exec(wordString);
1769             if (matches && matches.length) {
1770                 prefix = matches[1];
1771                 suffix = matches[3];
1772                 number = WebInspector.StylesSidebarPane.alteredFloatNumber(parseFloat(matches[2]), event);
1773                 if (number === null) {
1774                     // Need to check for null explicitly.
1775                     return false;
1776                 }
1777
1778                 replacementString = prefix + number + suffix;
1779             }
1780         }
1781
1782         if (replacementString) {
1783             var replacementTextNode = document.createTextNode(replacementString);
1784
1785             wordRange.deleteContents();
1786             wordRange.insertNode(replacementTextNode);
1787
1788             var finalSelectionRange = document.createRange();
1789             finalSelectionRange.setStart(replacementTextNode, 0);
1790             finalSelectionRange.setEnd(replacementTextNode, replacementString.length);
1791
1792             selection.removeAllRanges();
1793             selection.addRange(finalSelectionRange);
1794
1795             event.handled = true;
1796             event.preventDefault();
1797
1798             // Synthesize property text disregarding any comments, custom whitespace etc.
1799             this.applyStyleText(this.nameElement.textContent + ": " + this.valueElement.textContent);
1800         }
1801         return true;
1802     },
1803
1804     editingEnded: function(context)
1805     {
1806         if (this._applyFreeFlowStyleTextEditTimer)
1807             clearTimeout(this._applyFreeFlowStyleTextEditTimer);
1808
1809         this.hasChildren = context.hasChildren;
1810         if (context.expanded)
1811             this.expand();
1812         var editedElement = context.isEditingName ? this.nameElement : this.valueElement;
1813         if (!context.isEditingName)
1814             editedElement.removeEventListener("keydown", context.keyDownListener, false);
1815         if (editedElement.parentElement)
1816             editedElement.parentElement.removeStyleClass("child-editing");
1817
1818         WebInspector.panels.elements.endEditingStyle();
1819     },
1820
1821     editingCancelled: function(element, context)
1822     {
1823         this._removePrompt();
1824         this._revertStyleUponEditingCanceled(this.originalPropertyText);
1825         // This should happen last, as it clears the info necessary to restore the property value after [Page]Up/Down changes.
1826         this.editingEnded(context);
1827     },
1828
1829     _revertStyleUponEditingCanceled: function(originalPropertyText)
1830     {
1831         if (typeof originalPropertyText === "string") {
1832             delete this.originalPropertyText;
1833             this.applyStyleText(originalPropertyText, true, false, true);
1834         } else {
1835             if (this._newProperty)
1836                 this.treeOutline.removeChild(this);
1837             else
1838                 this.updateTitle();
1839         }
1840     }, 
1841
1842     editingCommitted: function(element, userInput, previousContent, context, moveDirection)
1843     {
1844         this._removePrompt();
1845         this.editingEnded(context);
1846         var isEditingName = context.isEditingName;
1847
1848         // Determine where to move to before making changes
1849         var createNewProperty, moveToPropertyName, moveToSelector;
1850         var moveTo = this;
1851         var moveToOther = (isEditingName ^ (moveDirection === "forward"));
1852         var abandonNewProperty = this._newProperty && !userInput && (moveToOther || isEditingName);
1853         if (moveDirection === "forward" && !isEditingName || moveDirection === "backward" && isEditingName) {
1854             do {
1855                 moveTo = (moveDirection === "forward" ? moveTo.nextSibling : moveTo.previousSibling);
1856             } while(moveTo && !moveTo.selectable);
1857
1858            if (moveTo)
1859                 moveToPropertyName = moveTo.name;
1860             else if (moveDirection === "forward" && (!this._newProperty || userInput))
1861                 createNewProperty = true;
1862             else if (moveDirection === "backward" && this.treeOutline.section.rule)
1863                 moveToSelector = true;
1864         }
1865
1866         // Make the Changes and trigger the moveToNextCallback after updating.
1867         var blankInput = /^\s*$/.test(userInput);
1868         var isDataPasted = "originalName" in context;
1869         var isDirtyViaPaste = isDataPasted && (this.nameElement.textContent !== context.originalName || this.valueElement.textContent !== context.originalValue);
1870         var shouldCommitNewProperty = this._newProperty && (moveToOther || (!moveDirection && !isEditingName) || (isEditingName && blankInput));
1871         if (((userInput !== previousContent || isDirtyViaPaste) && !this._newProperty) || shouldCommitNewProperty) {
1872             WebInspector.panels.elements.startEditingStyle();
1873             this.treeOutline.section._afterUpdate = moveToNextCallback.bind(this, this._newProperty, !blankInput, this.treeOutline.section);
1874             var propertyText;
1875             if (blankInput || (this._newProperty && /^\s*$/.test(this.valueElement.textContent)))
1876                 propertyText = "";
1877             else {
1878                 if (isEditingName)
1879                     propertyText = userInput + ": " + this.valueElement.textContent;
1880                 else
1881                     propertyText = this.nameElement.textContent + ": " + userInput;
1882             }
1883             this.applyStyleText(propertyText, true, true);
1884         } else {
1885             if (!isDataPasted && !this._newProperty)
1886                 this.updateTitle();
1887             moveToNextCallback.call(this, this._newProperty, false, this.treeOutline.section);
1888         }
1889
1890         var moveToIndex = moveTo && this.treeOutline ? this.treeOutline.children.indexOf(moveTo) : -1;
1891
1892         // The Callback to start editing the next/previous property/selector.
1893         function moveToNextCallback(alreadyNew, valueChanged, section)
1894         {
1895             WebInspector.panels.elements.endEditingStyle();
1896
1897             if (!moveDirection)
1898                 return;
1899
1900             // User just tabbed through without changes.
1901             if (moveTo && moveTo.parent) {
1902                 moveTo.startEditing(!isEditingName ? moveTo.nameElement : moveTo.valueElement);
1903                 return;
1904             }
1905
1906             // User has made a change then tabbed, wiping all the original treeElements.
1907             // Recalculate the new treeElement for the same property we were going to edit next.
1908             if (moveTo && !moveTo.parent) {
1909                 var propertyElements = section.propertiesTreeOutline.children;
1910                 if (moveDirection === "forward" && blankInput && !isEditingName)
1911                     --moveToIndex;
1912                 if (moveToIndex >= propertyElements.length && !this._newProperty)
1913                     createNewProperty = true;
1914                 else {
1915                     var treeElement = moveToIndex >= 0 ? propertyElements[moveToIndex] : null;
1916                     if (treeElement) {
1917                         treeElement.startEditing(!isEditingName ? treeElement.nameElement : treeElement.valueElement);
1918                         return;
1919                     } else if (!alreadyNew)
1920                         moveToSelector = true;
1921                 }
1922             }
1923
1924             // Create a new attribute in this section (or move to next editable selector if possible).
1925             if (createNewProperty) {
1926                 if (alreadyNew && !valueChanged && (isEditingName ^ (moveDirection === "backward")))
1927                     return;
1928
1929                 section.addNewBlankProperty().startEditing();
1930                 return;
1931             }
1932
1933             if (abandonNewProperty) {
1934                 var sectionToEdit = moveDirection === "backward" ? section : section.nextEditableSibling();
1935                 if (sectionToEdit && sectionToEdit.rule)
1936                     sectionToEdit.startEditingSelector();
1937                 return;
1938             }
1939
1940             if (moveToSelector)
1941                 section.startEditingSelector();
1942         }
1943     },
1944
1945     _removePrompt: function()
1946     {
1947         // BUG 53242. This cannot go into editingEnded(), as it should always happen first for any editing outcome.
1948         if (this._prompt) {
1949             this._prompt.removeFromElement();
1950             delete this._prompt;
1951         }
1952     },
1953
1954     _hasBeenModifiedIncrementally: function()
1955     {
1956         // New properties applied via up/down have an originalPropertyText and will be deleted later
1957         // on, if cancelled, when the empty string gets applied as their style text.
1958         return typeof this.originalPropertyText === "string";
1959     },
1960
1961     applyStyleText: function(styleText, updateInterface, majorChange, isRevert)
1962     {
1963         // Leave a way to cancel editing after incremental changes.
1964         if (!isRevert && !updateInterface && !this._hasBeenModifiedIncrementally()) {
1965             // Remember the rule's original CSS text on [Page](Up|Down), so it can be restored
1966             // if the editing is canceled.
1967             this.originalPropertyText = this.property.propertyText;
1968         }
1969
1970         var section = this.treeOutline.section;
1971         var elementsPanel = WebInspector.panels.elements;
1972         styleText = styleText.replace(/\s/g, " ").trim(); // Replace &nbsp; with whitespace.
1973         var styleTextLength = styleText.length;
1974         if (!styleTextLength && updateInterface && !isRevert && this._newProperty && !this._hasBeenModifiedIncrementally()) {
1975             // The user deleted everything and never applied a new property value via Up/Down scrolling, so remove the tree element and update.
1976             this.parent.removeChild(this);
1977             section.afterUpdate();
1978             return;
1979         }
1980
1981         function callback(originalPropertyText, newStyle)
1982         {
1983             if (!newStyle) {
1984                 if (updateInterface) {
1985                     // It did not apply, cancel editing.
1986                     this._revertStyleUponEditingCanceled(originalPropertyText);
1987                 }
1988                 return;
1989             }
1990
1991             this.style = newStyle;
1992             this.property = newStyle.propertyAt(this.property.index);
1993             this._styleRule.style = this.style;
1994
1995             if (section && section.pane)
1996                 section.pane.dispatchEventToListeners("style edited");
1997
1998             if (updateInterface)
1999                 this.updateAll(true);
2000         }
2001
2002         // Append a ";" if the new text does not end in ";".
2003         // FIXME: this does not handle trailing comments.
2004         if (styleText.length && !/;\s*$/.test(styleText))
2005             styleText += ";";
2006         this.property.setText(styleText, majorChange, callback.bind(this, this.originalPropertyText));
2007     }
2008 }
2009
2010 WebInspector.StylePropertyTreeElement.prototype.__proto__ = TreeElement.prototype;
2011
2012 WebInspector.StylesSidebarPane.CSSPropertyPrompt = function(element, cssCompletions)
2013 {
2014     WebInspector.TextPrompt.call(this, element, this._buildPropertyCompletions.bind(this), WebInspector.StylesSidebarPane.StyleValueDelimiters, true);
2015     this._cssCompletions = cssCompletions;
2016 }
2017
2018 WebInspector.StylesSidebarPane.CSSPropertyPrompt.prototype = {
2019     upKeyPressed: function(event)
2020     {
2021         this._handleNameOrValueUpDown(event);
2022     },
2023
2024     downKeyPressed: function(event)
2025     {
2026         this._handleNameOrValueUpDown(event);
2027     },
2028
2029     tabKeyPressed: function(event)
2030     {
2031         this.acceptAutoComplete();
2032     },
2033
2034     _handleNameOrValueUpDown: function(event)
2035     {
2036         var reverse = event.keyIdentifier === "Up";
2037         if (this.autoCompleteElement)
2038             this.complete(false, reverse); // Accept the current suggestion, if any.
2039         else {
2040             // Select the word suffix to affect it when computing the subsequent suggestion.
2041             this._selectCurrentWordSuffix();
2042         }
2043
2044         this.complete(false, reverse); // Actually increment/decrement the suggestion.
2045         event.handled = true;
2046     },
2047
2048     _selectCurrentWordSuffix: function()
2049     {
2050         var selection = window.getSelection();
2051         if (!selection.rangeCount)
2052             return;
2053
2054         var selectionRange = selection.getRangeAt(0);
2055         if (!selectionRange.commonAncestorContainer.isDescendant(this.element))
2056             return;
2057         var wordSuffixRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, WebInspector.StylesSidebarPane.StyleValueDelimiters, this.element, "forward");
2058         if (!wordSuffixRange.toString())
2059             return;
2060         selection.removeAllRanges();
2061         selection.addRange(wordSuffixRange);
2062     },
2063
2064     _buildPropertyCompletions: function(wordRange, bestMatchOnly, completionsReadyCallback)
2065     {
2066         var prefix = wordRange.toString().toLowerCase();
2067         if (!prefix && bestMatchOnly)
2068             return;
2069
2070         var results;
2071         if (bestMatchOnly) {
2072             results = [];
2073             var firstMatch = this._cssCompletions.firstStartsWith(prefix);
2074             if (firstMatch)
2075                 results.push(firstMatch);
2076             return completionsReadyCallback(results);
2077         }
2078
2079         results = this._cssCompletions.startsWith(prefix);
2080         if (results)
2081             completionsReadyCallback(results);
2082     }
2083 }
2084
2085 WebInspector.StylesSidebarPane.CSSPropertyPrompt.prototype.__proto__ = WebInspector.TextPrompt.prototype;