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