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