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