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