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 * @param {WebInspector.ComputedStyleSidebarPane} computedStylePane
34 * @param {function(DOMAgent.NodeId, string, boolean)} setPseudoClassCallback
35 */
36WebInspector.StylesSidebarPane = function(computedStylePane, setPseudoClassCallback)
37{
38    WebInspector.SidebarPane.call(this, WebInspector.UIString("Styles"));
39
40    this.settingsSelectElement = document.createElement("select");
41    this.settingsSelectElement.className = "select-settings";
42
43    var option = document.createElement("option");
44    option.value = WebInspector.Color.Format.Original;
45    option.label = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "As authored" : "As Authored");
46    this.settingsSelectElement.appendChild(option);
47
48    option = document.createElement("option");
49    option.value = WebInspector.Color.Format.HEX;
50    option.label = WebInspector.UIString("Hex Colors");
51    this.settingsSelectElement.appendChild(option);
52
53    option = document.createElement("option");
54    option.value = WebInspector.Color.Format.RGB;
55    option.label = WebInspector.UIString("RGB Colors");
56    this.settingsSelectElement.appendChild(option);
57
58    option = document.createElement("option");
59    option.value = WebInspector.Color.Format.HSL;
60    option.label = WebInspector.UIString("HSL Colors");
61    this.settingsSelectElement.appendChild(option);
62
63    // Prevent section from collapsing.
64    var muteEventListener = function(event) { event.consume(true); };
65
66    this.settingsSelectElement.addEventListener("click", muteEventListener, true);
67    this.settingsSelectElement.addEventListener("change", this._changeSetting.bind(this), false);
68    this._updateColorFormatFilter();
69
70    this.titleElement.appendChild(this.settingsSelectElement);
71
72    this._elementStateButton = document.createElement("button");
73    this._elementStateButton.className = "pane-title-button element-state";
74    this._elementStateButton.title = WebInspector.UIString("Toggle Element State");
75    this._elementStateButton.addEventListener("click", this._toggleElementStatePane.bind(this), false);
76    this.titleElement.appendChild(this._elementStateButton);
77
78    var addButton = document.createElement("button");
79    addButton.className = "pane-title-button add";
80    addButton.id = "add-style-button-test-id";
81    addButton.title = WebInspector.UIString("New Style Rule");
82    addButton.addEventListener("click", this._createNewRule.bind(this), false);
83    this.titleElement.appendChild(addButton);
84
85    this._computedStylePane = computedStylePane;
86    computedStylePane._stylesSidebarPane = this;
87    this._setPseudoClassCallback = setPseudoClassCallback;
88    this.element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true);
89    WebInspector.settings.colorFormat.addChangeListener(this._colorFormatSettingChanged.bind(this));
90
91    this._createElementStatePane();
92    this.bodyElement.appendChild(this._elementStatePane);
93    this._sectionsContainer = document.createElement("div");
94    this.bodyElement.appendChild(this._sectionsContainer);
95
96    this._spectrumHelper = new WebInspector.SpectrumPopupHelper();
97    this._linkifier = new WebInspector.Linkifier(new WebInspector.Linkifier.DefaultCSSFormatter());
98
99    WebInspector.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetOrMediaQueryResultChanged, this);
100    WebInspector.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.MediaQueryResultChanged, this._styleSheetOrMediaQueryResultChanged, this);
101    WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.AttrModified, this._attributeChanged, this);
102    WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.AttrRemoved, this._attributeChanged, this);
103    WebInspector.settings.showUserAgentStyles.addChangeListener(this._showUserAgentStylesSettingChanged.bind(this));
104}
105
106// Keep in sync with RenderStyleConstants.h PseudoId enum. Array below contains pseudo id names for corresponding enum indexes.
107// First item is empty due to its artificial NOPSEUDO nature in the enum.
108// FIXME: find a way of generating this mapping or getting it from combination of RenderStyleConstants and CSSSelector.cpp at
109// runtime.
110WebInspector.StylesSidebarPane.PseudoIdNames = [
111    "", "first-line", "first-letter", "before", "after", "selection", "", "-webkit-scrollbar", "-webkit-file-upload-button",
112    "-webkit-input-placeholder", "-webkit-slider-thumb", "-webkit-search-cancel-button", "-webkit-search-decoration",
113    "-webkit-search-results-decoration", "-webkit-search-results-button", "-webkit-media-controls-panel",
114    "-webkit-media-controls-play-button", "-webkit-media-controls-mute-button", "-webkit-media-controls-timeline",
115    "-webkit-media-controls-timeline-container", "-webkit-media-controls-volume-slider",
116    "-webkit-media-controls-volume-slider-container", "-webkit-media-controls-current-time-display",
117    "-webkit-media-controls-time-remaining-display", "-webkit-media-controls-seek-back-button", "-webkit-media-controls-seek-forward-button",
118    "-webkit-media-controls-fullscreen-button", "-webkit-media-controls-rewind-button", "-webkit-media-controls-return-to-realtime-button",
119    "-webkit-media-controls-toggle-closed-captions-button", "-webkit-media-controls-status-display", "-webkit-scrollbar-thumb",
120    "-webkit-scrollbar-button", "-webkit-scrollbar-track", "-webkit-scrollbar-track-piece", "-webkit-scrollbar-corner",
121    "-webkit-resizer", "-webkit-inner-spin-button", "-webkit-outer-spin-button"
122];
123
124WebInspector.StylesSidebarPane.canonicalPropertyName = function(name)
125{
126    if (!name || name.length < 9 || name.charAt(0) !== "-")
127        return name;
128    var match = name.match(/(?:-webkit-|-khtml-|-apple-)(.+)/);
129    if (!match)
130        return name;
131    return match[1];
132}
133
134WebInspector.StylesSidebarPane.createExclamationMark = function(propertyName)
135{
136    var exclamationElement = document.createElement("img");
137    exclamationElement.className = "exclamation-mark";
138    exclamationElement.title = WebInspector.CSSMetadata.cssPropertiesMetainfo.keySet()[propertyName.toLowerCase()] ? WebInspector.UIString("Invalid property value.") : WebInspector.UIString("Unknown property name.");
139    return exclamationElement;
140}
141
142WebInspector.StylesSidebarPane.prototype = {
143    /**
144     * @param {Event} event
145     */
146    _contextMenuEventFired: function(event)
147    {
148        // We start editing upon click -> default navigation to resources panel is not available
149        // Hence we add a soft context menu for hrefs.
150        var contextMenu = new WebInspector.ContextMenu(event);
151        contextMenu.appendApplicableItems(event.target);
152        contextMenu.show();
153    },
154
155    get _forcedPseudoClasses()
156    {
157        return this.node ? (this.node.getUserProperty("pseudoState") || undefined) : undefined;
158    },
159
160    _updateForcedPseudoStateInputs: function()
161    {
162        if (!this.node)
163            return;
164
165        var nodePseudoState = this._forcedPseudoClasses;
166        if (!nodePseudoState)
167            nodePseudoState = [];
168
169        var inputs = this._elementStatePane.inputs;
170        for (var i = 0; i < inputs.length; ++i)
171            inputs[i].checked = nodePseudoState.indexOf(inputs[i].state) >= 0;
172    },
173
174    /**
175     * @param {WebInspector.DOMNode=} node
176     * @param {boolean=} forceUpdate
177     */
178    update: function(node, forceUpdate)
179    {
180        this._spectrumHelper.hide();
181
182        var refresh = false;
183
184        if (forceUpdate)
185            delete this.node;
186
187        if (!forceUpdate && (node === this.node))
188            refresh = true;
189
190        if (node && node.nodeType() === Node.TEXT_NODE && node.parentNode)
191            node = node.parentNode;
192
193        if (node && node.nodeType() !== Node.ELEMENT_NODE)
194            node = null;
195
196        if (node)
197            this.node = node;
198        else
199            node = this.node;
200
201        this._updateForcedPseudoStateInputs();
202
203        if (refresh)
204            this._refreshUpdate();
205        else
206            this._rebuildUpdate();
207    },
208
209    /**
210     * @param {WebInspector.StylePropertiesSection=} editedSection
211     * @param {boolean=} forceFetchComputedStyle
212     * @param {function()=} userCallback
213     */
214    _refreshUpdate: function(editedSection, forceFetchComputedStyle, userCallback)
215    {
216        if (this._refreshUpdateInProgress) {
217            this._lastNodeForInnerRefresh = this.node;
218            return;
219        }
220
221        var node = this._validateNode(userCallback);
222        if (!node)
223            return;
224
225        function computedStyleCallback(computedStyle)
226        {
227            delete this._refreshUpdateInProgress;
228
229            if (this._lastNodeForInnerRefresh) {
230                delete this._lastNodeForInnerRefresh;
231                this._refreshUpdate(editedSection, forceFetchComputedStyle, userCallback);
232                return;
233            }
234
235            if (this.node === node && computedStyle)
236                this._innerRefreshUpdate(node, computedStyle, editedSection);
237
238            if (userCallback)
239                userCallback();
240        }
241
242        if (this._computedStylePane.isShowing() || forceFetchComputedStyle) {
243            this._refreshUpdateInProgress = true;
244            WebInspector.cssModel.getComputedStyleAsync(node.id, computedStyleCallback.bind(this));
245        } else {
246            this._innerRefreshUpdate(node, null, editedSection);
247            if (userCallback)
248                userCallback();
249        }
250    },
251
252    _rebuildUpdate: function()
253    {
254        if (this._rebuildUpdateInProgress) {
255            this._lastNodeForInnerRebuild = this.node;
256            return;
257        }
258
259        var node = this._validateNode();
260        if (!node)
261            return;
262
263        this._rebuildUpdateInProgress = true;
264
265        var resultStyles = {};
266
267        function stylesCallback(matchedResult)
268        {
269            delete this._rebuildUpdateInProgress;
270
271            var lastNodeForRebuild = this._lastNodeForInnerRebuild;
272            if (lastNodeForRebuild) {
273                delete this._lastNodeForInnerRebuild;
274                if (lastNodeForRebuild !== this.node) {
275                    this._rebuildUpdate();
276                    return;
277                }
278            }
279
280            if (matchedResult && this.node === node) {
281                resultStyles.matchedCSSRules = matchedResult.matchedCSSRules;
282                resultStyles.pseudoElements = matchedResult.pseudoElements;
283                resultStyles.inherited = matchedResult.inherited;
284                this._innerRebuildUpdate(node, resultStyles);
285            }
286
287            if (lastNodeForRebuild) {
288                // lastNodeForRebuild is the same as this.node - another rebuild has been requested.
289                this._rebuildUpdate();
290                return;
291            }
292        }
293
294        function inlineCallback(inlineStyle, attributesStyle)
295        {
296            resultStyles.inlineStyle = inlineStyle;
297            resultStyles.attributesStyle = attributesStyle;
298        }
299
300        function computedCallback(computedStyle)
301        {
302            resultStyles.computedStyle = computedStyle;
303        }
304
305        if (this._computedStylePane.isShowing())
306            WebInspector.cssModel.getComputedStyleAsync(node.id, computedCallback.bind(this));
307        WebInspector.cssModel.getInlineStylesAsync(node.id, inlineCallback.bind(this));
308        WebInspector.cssModel.getMatchedStylesAsync(node.id, true, true, stylesCallback.bind(this));
309    },
310
311    /**
312     * @param {function()=} userCallback
313     */
314    _validateNode: function(userCallback)
315    {
316        if (!this.node) {
317            this._sectionsContainer.removeChildren();
318            this._computedStylePane.bodyElement.removeChildren();
319            this.sections = {};
320            if (userCallback)
321                userCallback();
322            return null;
323        }
324        return this.node;
325    },
326
327    _styleSheetOrMediaQueryResultChanged: function()
328    {
329        if (this._userOperation || this._isEditingStyle)
330            return;
331
332        this._rebuildUpdate();
333    },
334
335    _attributeChanged: function(event)
336    {
337        // Any attribute removal or modification can affect the styles of "related" nodes.
338        // Do not touch the styles if they are being edited.
339        if (this._isEditingStyle || this._userOperation)
340            return;
341
342        if (!this._canAffectCurrentStyles(event.data.node))
343            return;
344
345        this._rebuildUpdate();
346    },
347
348    _canAffectCurrentStyles: function(node)
349    {
350        return this.node && (this.node === node || node.parentNode === this.node.parentNode || node.isAncestor(this.node));
351    },
352
353    _innerRefreshUpdate: function(node, computedStyle, editedSection)
354    {
355        for (var pseudoId in this.sections) {
356            var styleRules = this._refreshStyleRules(this.sections[pseudoId], computedStyle);
357            var usedProperties = {};
358            this._markUsedProperties(styleRules, usedProperties);
359            this._refreshSectionsForStyleRules(styleRules, usedProperties, editedSection);
360        }
361        if (computedStyle)
362            this.sections[0][0].rebuildComputedTrace(this.sections[0]);
363
364        this._nodeStylesUpdatedForTest(node, false);
365    },
366
367    _innerRebuildUpdate: function(node, styles)
368    {
369        this._sectionsContainer.removeChildren();
370        this._computedStylePane.bodyElement.removeChildren();
371        this._linkifier.reset();
372
373        var styleRules = this._rebuildStyleRules(node, styles);
374        var usedProperties = {};
375        this._markUsedProperties(styleRules, usedProperties);
376        this.sections[0] = this._rebuildSectionsForStyleRules(styleRules, usedProperties, 0, null);
377        var anchorElement = this.sections[0].inheritedPropertiesSeparatorElement;
378
379        if (styles.computedStyle)
380            this.sections[0][0].rebuildComputedTrace(this.sections[0]);
381
382        for (var i = 0; i < styles.pseudoElements.length; ++i) {
383            var pseudoElementCSSRules = styles.pseudoElements[i];
384
385            styleRules = [];
386            var pseudoId = pseudoElementCSSRules.pseudoId;
387
388            var entry = { isStyleSeparator: true, pseudoId: pseudoId };
389            styleRules.push(entry);
390
391            // Add rules in reverse order to match the cascade order.
392            for (var j = pseudoElementCSSRules.rules.length - 1; j >= 0; --j) {
393                var rule = pseudoElementCSSRules.rules[j];
394                styleRules.push({ style: rule.style, selectorText: rule.selectorText, media: rule.media, sourceURL: rule.sourceURL, rule: rule, editable: !!(rule.style && rule.style.id) });
395            }
396            usedProperties = {};
397            this._markUsedProperties(styleRules, usedProperties);
398            this.sections[pseudoId] = this._rebuildSectionsForStyleRules(styleRules, usedProperties, pseudoId, anchorElement);
399        }
400
401        this._nodeStylesUpdatedForTest(node, true);
402    },
403
404    _nodeStylesUpdatedForTest: function(node, rebuild)
405    {
406        // Tests override this method.
407    },
408
409    _refreshStyleRules: function(sections, computedStyle)
410    {
411        var nodeComputedStyle = computedStyle;
412        var styleRules = [];
413        for (var i = 0; sections && i < sections.length; ++i) {
414            var section = sections[i];
415            if (section.isBlank)
416                continue;
417            if (section.computedStyle)
418                section.styleRule.style = nodeComputedStyle;
419            var styleRule = { section: section, style: section.styleRule.style, computedStyle: section.computedStyle, rule: section.rule, editable: !!(section.styleRule.style && section.styleRule.style.id), isAttribute: section.styleRule.isAttribute, isInherited: section.styleRule.isInherited };
420            styleRules.push(styleRule);
421        }
422        return styleRules;
423    },
424
425    _rebuildStyleRules: function(node, styles)
426    {
427        var nodeComputedStyle = styles.computedStyle;
428        this.sections = {};
429
430        var styleRules = [];
431
432        function addAttributesStyle()
433        {
434            if (!styles.attributesStyle)
435                return;
436            var attrStyle = { style: styles.attributesStyle, editable: false };
437            attrStyle.selectorText = node.nodeNameInCorrectCase() + "[" + WebInspector.UIString("Attributes Style") + "]";
438            styleRules.push(attrStyle);
439        }
440
441        styleRules.push({ computedStyle: true, selectorText: "", style: nodeComputedStyle, editable: false });
442
443        // Inline style has the greatest specificity.
444        if (styles.inlineStyle && node.nodeType() === Node.ELEMENT_NODE) {
445            var inlineStyle = { selectorText: "element.style", style: styles.inlineStyle, isAttribute: true };
446            styleRules.push(inlineStyle);
447        }
448
449        // Add rules in reverse order to match the cascade order.
450        if (styles.matchedCSSRules.length)
451            styleRules.push({ isStyleSeparator: true, text: WebInspector.UIString("Matched CSS Rules") });
452        var addedAttributesStyle;
453        for (var i = styles.matchedCSSRules.length - 1; i >= 0; --i) {
454            var rule = styles.matchedCSSRules[i];
455            if (!WebInspector.settings.showUserAgentStyles.get() && (rule.isUser || rule.isUserAgent))
456                continue;
457            if ((rule.isUser || rule.isUserAgent) && !addedAttributesStyle) {
458                // Show element's Style Attributes after all author rules.
459                addedAttributesStyle = true;
460                addAttributesStyle();
461            }
462            styleRules.push({ style: rule.style, selectorText: rule.selectorText, media: rule.media, sourceURL: rule.sourceURL, rule: rule, editable: !!(rule.style && rule.style.id) });
463        }
464
465        if (!addedAttributesStyle)
466            addAttributesStyle();
467
468        // Walk the node structure and identify styles with inherited properties.
469        var parentNode = node.parentNode;
470        function insertInheritedNodeSeparator(node)
471        {
472            var entry = {};
473            entry.isStyleSeparator = true;
474            entry.node = node;
475            styleRules.push(entry);
476        }
477
478        for (var parentOrdinal = 0; parentOrdinal < styles.inherited.length; ++parentOrdinal) {
479            var parentStyles = styles.inherited[parentOrdinal];
480            var separatorInserted = false;
481            if (parentStyles.inlineStyle) {
482                if (this._containsInherited(parentStyles.inlineStyle)) {
483                    var inlineStyle = { selectorText: WebInspector.UIString("Style Attribute"), style: parentStyles.inlineStyle, isAttribute: true, isInherited: true, parentNode: parentNode };
484                    if (!separatorInserted) {
485                        insertInheritedNodeSeparator(parentNode);
486                        separatorInserted = true;
487                    }
488                    styleRules.push(inlineStyle);
489                }
490            }
491
492            for (var i = parentStyles.matchedCSSRules.length - 1; i >= 0; --i) {
493                var rulePayload = parentStyles.matchedCSSRules[i];
494                if (!this._containsInherited(rulePayload.style))
495                    continue;
496                var rule = rulePayload;
497                if (!WebInspector.settings.showUserAgentStyles.get() && (rule.isUser || rule.isUserAgent))
498                    continue;
499
500                if (!separatorInserted) {
501                    insertInheritedNodeSeparator(parentNode);
502                    separatorInserted = true;
503                }
504                styleRules.push({ style: rule.style, selectorText: rule.selectorText, media: rule.media, sourceURL: rule.sourceURL, rule: rule, isInherited: true, parentNode: parentNode, editable: !!(rule.style && rule.style.id) });
505            }
506            parentNode = parentNode.parentNode;
507        }
508        return styleRules;
509    },
510
511    _markUsedProperties: function(styleRules, usedProperties)
512    {
513        var foundImportantProperties = {};
514        var propertyToEffectiveRule = {};
515        for (var i = 0; i < styleRules.length; ++i) {
516            var styleRule = styleRules[i];
517            if (styleRule.computedStyle || styleRule.isStyleSeparator)
518                continue;
519            if (styleRule.section && styleRule.section.noAffect)
520                continue;
521
522            styleRule.usedProperties = {};
523
524            var style = styleRule.style;
525            var allProperties = style.allProperties;
526            for (var j = 0; j < allProperties.length; ++j) {
527                var property = allProperties[j];
528                if (!property.isLive || !property.parsedOk)
529                    continue;
530
531                var canonicalName = WebInspector.StylesSidebarPane.canonicalPropertyName(property.name);
532                // Do not pick non-inherited properties from inherited styles.
533                if (styleRule.isInherited && !WebInspector.CSSMetadata.InheritedProperties[canonicalName])
534                    continue;
535
536                if (foundImportantProperties.hasOwnProperty(canonicalName))
537                    continue;
538
539                var isImportant = property.priority.length;
540                if (!isImportant && usedProperties.hasOwnProperty(canonicalName))
541                    continue;
542
543                if (isImportant) {
544                    foundImportantProperties[canonicalName] = true;
545                    if (propertyToEffectiveRule.hasOwnProperty(canonicalName))
546                        delete propertyToEffectiveRule[canonicalName].usedProperties[canonicalName];
547                }
548
549                styleRule.usedProperties[canonicalName] = true;
550                usedProperties[canonicalName] = true;
551                propertyToEffectiveRule[canonicalName] = styleRule;
552            }
553        }
554    },
555
556    _refreshSectionsForStyleRules: function(styleRules, usedProperties, editedSection)
557    {
558        // Walk the style rules and update the sections with new overloaded and used properties.
559        for (var i = 0; i < styleRules.length; ++i) {
560            var styleRule = styleRules[i];
561            var section = styleRule.section;
562            if (styleRule.computedStyle) {
563                section._usedProperties = usedProperties;
564                section.update();
565            } else {
566                section._usedProperties = styleRule.usedProperties;
567                section.update(section === editedSection);
568            }
569        }
570    },
571
572    _rebuildSectionsForStyleRules: function(styleRules, usedProperties, pseudoId, anchorElement)
573    {
574        // Make a property section for each style rule.
575        var sections = [];
576        var lastWasSeparator = true;
577        for (var i = 0; i < styleRules.length; ++i) {
578            var styleRule = styleRules[i];
579            if (styleRule.isStyleSeparator) {
580                var separatorElement = document.createElement("div");
581                separatorElement.className = "sidebar-separator";
582                if (styleRule.node) {
583                    var link = WebInspector.DOMPresentationUtils.linkifyNodeReference(styleRule.node);
584                    separatorElement.appendChild(document.createTextNode(WebInspector.UIString("Inherited from") + " "));
585                    separatorElement.appendChild(link);
586                    if (!sections.inheritedPropertiesSeparatorElement)
587                        sections.inheritedPropertiesSeparatorElement = separatorElement;
588                } else if ("pseudoId" in styleRule) {
589                    var pseudoName = WebInspector.StylesSidebarPane.PseudoIdNames[styleRule.pseudoId];
590                    if (pseudoName)
591                        separatorElement.textContent = WebInspector.UIString("Pseudo ::%s element", pseudoName);
592                    else
593                        separatorElement.textContent = WebInspector.UIString("Pseudo element");
594                } else
595                    separatorElement.textContent = styleRule.text;
596                this._sectionsContainer.insertBefore(separatorElement, anchorElement);
597                lastWasSeparator = true;
598                continue;
599            }
600            var computedStyle = styleRule.computedStyle;
601
602            // Default editable to true if it was omitted.
603            var editable = styleRule.editable;
604            if (typeof editable === "undefined")
605                editable = true;
606
607            if (computedStyle)
608                var section = new WebInspector.ComputedStylePropertiesSection(this, styleRule, usedProperties);
609            else {
610                var section = new WebInspector.StylePropertiesSection(this, styleRule, editable, styleRule.isInherited, lastWasSeparator);
611                section._markSelectorMatches();
612            }
613            section.expanded = true;
614
615            if (computedStyle) {
616                this._computedStylePane.bodyElement.appendChild(section.element);
617                lastWasSeparator = true;
618            } else {
619                this._sectionsContainer.insertBefore(section.element, anchorElement);
620                lastWasSeparator = false;
621            }
622            sections.push(section);
623        }
624        return sections;
625    },
626
627    _containsInherited: function(style)
628    {
629        var properties = style.allProperties;
630        for (var i = 0; i < properties.length; ++i) {
631            var property = properties[i];
632            // Does this style contain non-overridden inherited property?
633            if (property.isLive && property.name in WebInspector.CSSMetadata.InheritedProperties)
634                return true;
635        }
636        return false;
637    },
638
639    _colorFormatSettingChanged: function(event)
640    {
641        this._updateColorFormatFilter();
642        for (var pseudoId in this.sections) {
643            var sections = this.sections[pseudoId];
644            for (var i = 0; i < sections.length; ++i)
645                sections[i].update(true);
646        }
647    },
648
649    _updateColorFormatFilter: function()
650    {
651        // Select the correct color format setting again, since it needs to be selected.
652        var selectedIndex = 0;
653        var value = WebInspector.settings.colorFormat.get();
654        var options = this.settingsSelectElement.options;
655        for (var i = 0; i < options.length; ++i) {
656            if (options[i].value === value) {
657                selectedIndex = i;
658                break;
659            }
660        }
661        this.settingsSelectElement.selectedIndex = selectedIndex;
662    },
663
664    _changeSetting: function(event)
665    {
666        var options = this.settingsSelectElement.options;
667        var selectedOption = options[this.settingsSelectElement.selectedIndex];
668        WebInspector.settings.colorFormat.set(selectedOption.value);
669    },
670
671    _createNewRule: function(event)
672    {
673        event.consume();
674        this.expand();
675        this.addBlankSection().startEditingSelector();
676    },
677
678    addBlankSection: function()
679    {
680        var blankSection = new WebInspector.BlankStylePropertiesSection(this, this.node ? this.node.appropriateSelectorFor(true) : "");
681
682        var elementStyleSection = this.sections[0][1];
683        this._sectionsContainer.insertBefore(blankSection.element, elementStyleSection.element.nextSibling);
684
685        this.sections[0].splice(2, 0, blankSection);
686
687        return blankSection;
688    },
689
690    removeSection: function(section)
691    {
692        for (var pseudoId in this.sections) {
693            var sections = this.sections[pseudoId];
694            var index = sections.indexOf(section);
695            if (index === -1)
696                continue;
697            sections.splice(index, 1);
698            if (section.element.parentNode)
699                section.element.parentNode.removeChild(section.element);
700        }
701    },
702
703    _toggleElementStatePane: function(event)
704    {
705        event.consume();
706        if (!this._elementStateButton.hasStyleClass("toggled")) {
707            this.expand();
708            this._elementStateButton.addStyleClass("toggled");
709            this._elementStatePane.addStyleClass("expanded");
710        } else {
711            this._elementStateButton.removeStyleClass("toggled");
712            this._elementStatePane.removeStyleClass("expanded");
713        }
714    },
715
716    _createElementStatePane: function()
717    {
718        this._elementStatePane = document.createElement("div");
719        this._elementStatePane.className = "styles-element-state-pane source-code";
720        var table = document.createElement("table");
721
722        var inputs = [];
723        this._elementStatePane.inputs = inputs;
724
725        function clickListener(event)
726        {
727            var node = this._validateNode();
728            if (!node)
729                return;
730            this._setPseudoClassCallback(node.id, event.target.state, event.target.checked);
731        }
732
733        function createCheckbox(state)
734        {
735            var td = document.createElement("td");
736            var label = document.createElement("label");
737            var input = document.createElement("input");
738            input.type = "checkbox";
739            input.state = state;
740            input.addEventListener("click", clickListener.bind(this), false);
741            inputs.push(input);
742            label.appendChild(input);
743            label.appendChild(document.createTextNode(":" + state));
744            td.appendChild(label);
745            return td;
746        }
747
748        var tr = document.createElement("tr");
749        tr.appendChild(createCheckbox.call(this, "active"));
750        tr.appendChild(createCheckbox.call(this, "hover"));
751        table.appendChild(tr);
752
753        tr = document.createElement("tr");
754        tr.appendChild(createCheckbox.call(this, "focus"));
755        tr.appendChild(createCheckbox.call(this, "visited"));
756        table.appendChild(tr);
757
758        this._elementStatePane.appendChild(table);
759    },
760
761    _showUserAgentStylesSettingChanged: function()
762    {
763        this._rebuildUpdate();
764    },
765
766    willHide: function()
767    {
768        this._spectrumHelper.hide();
769    },
770
771    __proto__: WebInspector.SidebarPane.prototype
772}
773
774/**
775 * @constructor
776 * @extends {WebInspector.SidebarPane}
777 */
778WebInspector.ComputedStyleSidebarPane = function()
779{
780    WebInspector.SidebarPane.call(this, WebInspector.UIString("Computed Style"));
781    var showInheritedCheckbox = new WebInspector.Checkbox(WebInspector.UIString("Show inherited"), "sidebar-pane-subtitle");
782    this.titleElement.appendChild(showInheritedCheckbox.element);
783
784    if (WebInspector.settings.showInheritedComputedStyleProperties.get()) {
785        this.bodyElement.addStyleClass("show-inherited");
786        showInheritedCheckbox.checked = true;
787    }
788
789    function showInheritedToggleFunction(event)
790    {
791        WebInspector.settings.showInheritedComputedStyleProperties.set(showInheritedCheckbox.checked);
792        if (WebInspector.settings.showInheritedComputedStyleProperties.get())
793            this.bodyElement.addStyleClass("show-inherited");
794        else
795            this.bodyElement.removeStyleClass("show-inherited");
796    }
797
798    showInheritedCheckbox.addEventListener(showInheritedToggleFunction.bind(this));
799}
800
801WebInspector.ComputedStyleSidebarPane.prototype = {
802    wasShown: function()
803    {
804        WebInspector.SidebarPane.prototype.wasShown.call(this);
805        if (!this._hasFreshContent)
806            this.prepareContent();
807    },
808
809    /**
810     * @param {function()=} callback
811     */
812    prepareContent: function(callback)
813    {
814        function wrappedCallback() {
815            this._hasFreshContent = true;
816            if (callback)
817                callback();
818            delete this._hasFreshContent;
819        }
820        this._stylesSidebarPane._refreshUpdate(null, true, wrappedCallback.bind(this));
821    },
822
823    __proto__: WebInspector.SidebarPane.prototype
824}
825
826/**
827 * @constructor
828 * @extends {WebInspector.PropertiesSection}
829 */
830WebInspector.StylePropertiesSection = function(parentPane, styleRule, editable, isInherited, isFirstSection)
831{
832    WebInspector.PropertiesSection.call(this, "");
833    this.element.className = "styles-section matched-styles monospace" + (isFirstSection ? " first-styles-section" : "");
834
835    if (styleRule.media) {
836        for (var i = styleRule.media.length - 1; i >= 0; --i) {
837            var media = styleRule.media[i];
838            var mediaDataElement = this.titleElement.createChild("div", "media");
839            var mediaText;
840            switch (media.source) {
841            case WebInspector.CSSMedia.Source.LINKED_SHEET:
842            case WebInspector.CSSMedia.Source.INLINE_SHEET:
843                mediaText = "media=\"" + media.text + "\"";
844                break;
845            case WebInspector.CSSMedia.Source.MEDIA_RULE:
846                mediaText = "@media " + media.text;
847                break;
848            case WebInspector.CSSMedia.Source.IMPORT_RULE:
849                mediaText = "@import " + media.text;
850                break;
851            }
852
853            if (media.sourceURL) {
854                var refElement = mediaDataElement.createChild("div", "subtitle");
855                var lineNumber = media.sourceLine < 0 ? undefined : media.sourceLine;
856                var anchor = WebInspector.linkifyResourceAsNode(media.sourceURL, lineNumber, "subtitle", media.sourceURL + (isNaN(lineNumber) ? "" : (":" + (lineNumber + 1))));
857                anchor.preferredPanel = "scripts";
858                anchor.style.float = "right";
859                refElement.appendChild(anchor);
860            }
861
862            var mediaTextElement = mediaDataElement.createChild("span");
863            mediaTextElement.textContent = mediaText;
864            mediaTextElement.title = media.text;
865        }
866    }
867
868    var selectorContainer = document.createElement("div");
869    this._selectorElement = document.createElement("span");
870    this._selectorElement.textContent = styleRule.selectorText;
871    selectorContainer.appendChild(this._selectorElement);
872
873    var openBrace = document.createElement("span");
874    openBrace.textContent = " {";
875    selectorContainer.appendChild(openBrace);
876    selectorContainer.addEventListener("mousedown", this._handleEmptySpaceMouseDown.bind(this), false);
877    selectorContainer.addEventListener("click", this._handleSelectorContainerClick.bind(this), false);
878
879    var closeBrace = document.createElement("div");
880    closeBrace.textContent = "}";
881    this.element.appendChild(closeBrace);
882
883    this._selectorElement.addEventListener("click", this._handleSelectorClick.bind(this), false);
884    this.element.addEventListener("mousedown", this._handleEmptySpaceMouseDown.bind(this), false);
885    this.element.addEventListener("click", this._handleEmptySpaceClick.bind(this), false);
886
887    this._parentPane = parentPane;
888    this.styleRule = styleRule;
889    this.rule = this.styleRule.rule;
890    this.editable = editable;
891    this.isInherited = isInherited;
892
893    if (this.rule) {
894        // Prevent editing the user agent and user rules.
895        if (this.rule.isUserAgent || this.rule.isUser)
896            this.editable = false;
897        else {
898            // Check this is a real CSSRule, not a bogus object coming from WebInspector.BlankStylePropertiesSection.
899            if (this.rule.id)
900                this.navigable = this.rule.isSourceNavigable();
901        }
902        this.titleElement.addStyleClass("styles-selector");
903    }
904
905    this._usedProperties = styleRule.usedProperties;
906
907    this._selectorRefElement = document.createElement("div");
908    this._selectorRefElement.className = "subtitle";
909    this._selectorRefElement.appendChild(this._createRuleOriginNode());
910    selectorContainer.insertBefore(this._selectorRefElement, selectorContainer.firstChild);
911    this.titleElement.appendChild(selectorContainer);
912    this._selectorContainer = selectorContainer;
913
914    if (isInherited)
915        this.element.addStyleClass("show-inherited"); // This one is related to inherited rules, not computed style.
916
917    if (this.navigable)
918        this.element.addStyleClass("navigable");
919
920    if (!this.editable)
921        this.element.addStyleClass("read-only");
922}
923
924WebInspector.StylePropertiesSection.prototype = {
925    get pane()
926    {
927        return this._parentPane;
928    },
929
930    collapse: function(dontRememberState)
931    {
932        // Overriding with empty body.
933    },
934
935    isPropertyInherited: function(propertyName)
936    {
937        if (this.isInherited) {
938            // While rendering inherited stylesheet, reverse meaning of this property.
939            // Render truly inherited properties with black, i.e. return them as non-inherited.
940            return !(propertyName in WebInspector.CSSMetadata.InheritedProperties);
941        }
942        return false;
943    },
944
945    /**
946     * @param {string} propertyName
947     * @param {boolean=} isShorthand
948     */
949    isPropertyOverloaded: function(propertyName, isShorthand)
950    {
951        if (!this._usedProperties || this.noAffect)
952            return false;
953
954        if (this.isInherited && !(propertyName in WebInspector.CSSMetadata.InheritedProperties)) {
955            // In the inherited sections, only show overrides for the potentially inherited properties.
956            return false;
957        }
958
959        var canonicalName = WebInspector.StylesSidebarPane.canonicalPropertyName(propertyName);
960        var used = (canonicalName in this._usedProperties);
961        if (used || !isShorthand)
962            return !used;
963
964        // Find out if any of the individual longhand properties of the shorthand
965        // are used, if none are then the shorthand is overloaded too.
966        var longhandProperties = this.styleRule.style.longhandProperties(propertyName);
967        for (var j = 0; j < longhandProperties.length; ++j) {
968            var individualProperty = longhandProperties[j];
969            if (WebInspector.StylesSidebarPane.canonicalPropertyName(individualProperty.name) in this._usedProperties)
970                return false;
971        }
972
973        return true;
974    },
975
976    nextEditableSibling: function()
977    {
978        var curSection = this;
979        do {
980            curSection = curSection.nextSibling;
981        } while (curSection && !curSection.editable);
982
983        if (!curSection) {
984            curSection = this.firstSibling;
985            while (curSection && !curSection.editable)
986                curSection = curSection.nextSibling;
987        }
988
989        return (curSection && curSection.editable) ? curSection : null;
990    },
991
992    previousEditableSibling: function()
993    {
994        var curSection = this;
995        do {
996            curSection = curSection.previousSibling;
997        } while (curSection && !curSection.editable);
998
999        if (!curSection) {
1000            curSection = this.lastSibling;
1001            while (curSection && !curSection.editable)
1002                curSection = curSection.previousSibling;
1003        }
1004
1005        return (curSection && curSection.editable) ? curSection : null;
1006    },
1007
1008    update: function(full)
1009    {
1010        if (this.styleRule.selectorText)
1011            this._selectorElement.textContent = this.styleRule.selectorText;
1012        this._markSelectorMatches();
1013        if (full) {
1014            this.propertiesTreeOutline.removeChildren();
1015            this.populated = false;
1016        } else {
1017            var child = this.propertiesTreeOutline.children[0];
1018            while (child) {
1019                child.overloaded = this.isPropertyOverloaded(child.name, child.isShorthand);
1020                child = child.traverseNextTreeElement(false, null, true);
1021            }
1022        }
1023        this.afterUpdate();
1024    },
1025
1026    afterUpdate: function()
1027    {
1028        if (this._afterUpdate) {
1029            this._afterUpdate(this);
1030            delete this._afterUpdate;
1031        }
1032    },
1033
1034    onpopulate: function()
1035    {
1036        var style = this.styleRule.style;
1037        var allProperties = style.allProperties;
1038        this.uniqueProperties = [];
1039
1040        var styleHasEditableSource = this.editable && !!style.range;
1041        if (styleHasEditableSource) {
1042            for (var i = 0; i < allProperties.length; ++i) {
1043                var property = allProperties[i];
1044                this.uniqueProperties.push(property);
1045                if (property.styleBased)
1046                    continue;
1047
1048                var isShorthand = !!WebInspector.CSSMetadata.cssPropertiesMetainfo.longhands(property.name);
1049                var inherited = this.isPropertyInherited(property.name);
1050                var overloaded = property.inactive || this.isPropertyOverloaded(property.name);
1051                var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this.styleRule, style, property, isShorthand, inherited, overloaded);
1052                this.propertiesTreeOutline.appendChild(item);
1053            }
1054            return;
1055        }
1056
1057        var generatedShorthands = {};
1058        // For style-based properties, generate shorthands with values when possible.
1059        for (var i = 0; i < allProperties.length; ++i) {
1060            var property = allProperties[i];
1061            this.uniqueProperties.push(property);
1062            var isShorthand = !!WebInspector.CSSMetadata.cssPropertiesMetainfo.longhands(property.name);
1063
1064            // For style-based properties, try generating shorthands.
1065            var shorthands = isShorthand ? null : WebInspector.CSSMetadata.cssPropertiesMetainfo.shorthands(property.name);
1066            var shorthandPropertyAvailable = false;
1067            for (var j = 0; shorthands && !shorthandPropertyAvailable && j < shorthands.length; ++j) {
1068                var shorthand = shorthands[j];
1069                if (shorthand in generatedShorthands) {
1070                    shorthandPropertyAvailable = true;
1071                    continue;  // There already is a shorthand this longhands falls under.
1072                }
1073                if (style.getLiveProperty(shorthand)) {
1074                    shorthandPropertyAvailable = true;
1075                    continue;  // There is an explict shorthand property this longhands falls under.
1076                }
1077                if (!style.shorthandValue(shorthand)) {
1078                    shorthandPropertyAvailable = false;
1079                    continue;  // Never generate synthetic shorthands when no value is available.
1080                }
1081
1082                // Generate synthetic shorthand we have a value for.
1083                var shorthandProperty = new WebInspector.CSSProperty(style, style.allProperties.length, shorthand, style.shorthandValue(shorthand), "", "style", true, true, undefined);
1084                var overloaded = property.inactive || this.isPropertyOverloaded(property.name, true);
1085                var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this.styleRule, style, shorthandProperty,  /* isShorthand */ true, /* inherited */ false, overloaded);
1086                this.propertiesTreeOutline.appendChild(item);
1087                generatedShorthands[shorthand] = shorthandProperty;
1088                shorthandPropertyAvailable = true;
1089            }
1090            if (shorthandPropertyAvailable)
1091                continue;  // Shorthand for the property found.
1092
1093            var inherited = this.isPropertyInherited(property.name);
1094            var overloaded = property.inactive || this.isPropertyOverloaded(property.name, isShorthand);
1095            var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this.styleRule, style, property, isShorthand, inherited, overloaded);
1096            this.propertiesTreeOutline.appendChild(item);
1097        }
1098    },
1099
1100    findTreeElementWithName: function(name)
1101    {
1102        var treeElement = this.propertiesTreeOutline.children[0];
1103        while (treeElement) {
1104            if (treeElement.name === name)
1105                return treeElement;
1106            treeElement = treeElement.traverseNextTreeElement(true, null, true);
1107        }
1108        return null;
1109    },
1110
1111    _markSelectorMatches: function()
1112    {
1113        var rule = this.styleRule.rule;
1114        if (!rule)
1115            return;
1116
1117        var matchingSelectors = rule.matchingSelectors;
1118        // .selector is rendered as non-affecting selector by default.
1119        if (this.noAffect || matchingSelectors)
1120            this._selectorElement.className = "selector";
1121        if (!matchingSelectors)
1122            return;
1123
1124        var selectors = rule.selectors;
1125        var fragment = document.createDocumentFragment();
1126        var currentMatch = 0;
1127        for (var i = 0, lastSelectorIndex = selectors.length - 1; i <= lastSelectorIndex ; ++i) {
1128            var selectorNode;
1129            var textNode = document.createTextNode(selectors[i]);
1130            if (matchingSelectors[currentMatch] === i) {
1131                ++currentMatch;
1132                selectorNode = document.createElement("span");
1133                selectorNode.className = "selector-matches";
1134                selectorNode.appendChild(textNode);
1135            } else
1136                selectorNode = textNode;
1137
1138            fragment.appendChild(selectorNode);
1139            if (i !== lastSelectorIndex)
1140                fragment.appendChild(document.createTextNode(", "));
1141        }
1142
1143        this._selectorElement.removeChildren();
1144        this._selectorElement.appendChild(fragment);
1145    },
1146
1147    _checkWillCancelEditing: function()
1148    {
1149        var willCauseCancelEditing = this._willCauseCancelEditing;
1150        delete this._willCauseCancelEditing;
1151        return willCauseCancelEditing;
1152    },
1153
1154    _handleSelectorContainerClick: function(event)
1155    {
1156        if (this._checkWillCancelEditing() || !this.editable)
1157            return;
1158        if (event.target === this._selectorContainer)
1159            this.addNewBlankProperty(0).startEditing();
1160    },
1161
1162    /**
1163     * @param {number=} index
1164     */
1165    addNewBlankProperty: function(index)
1166    {
1167        var style = this.styleRule.style;
1168        var property = style.newBlankProperty(index);
1169        var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this.styleRule, style, property, false, false, false);
1170        index = property.index;
1171        this.propertiesTreeOutline.insertChild(item, index);
1172        item.listItemElement.textContent = "";
1173        item._newProperty = true;
1174        item.updateTitle();
1175        return item;
1176    },
1177
1178    _createRuleOriginNode: function()
1179    {
1180        /**
1181         * @param {string} url
1182         * @param {number} line
1183         */
1184        function linkifyUncopyable(url, line)
1185        {
1186            var link = WebInspector.linkifyResourceAsNode(url, line, "", url + ":" + (line + 1));
1187            link.preferredPanel = "scripts";
1188            link.classList.add("webkit-html-resource-link");
1189            link.setAttribute("data-uncopyable", link.textContent);
1190            link.textContent = "";
1191            return link;
1192        }
1193
1194        if (this.styleRule.sourceURL)
1195            return this._parentPane._linkifier.linkifyCSSRuleLocation(this.rule) || linkifyUncopyable(this.styleRule.sourceURL, this.rule.sourceLine);
1196
1197        if (!this.rule)
1198            return document.createTextNode("");
1199
1200        var origin = "";
1201        if (this.rule.isUserAgent)
1202            return document.createTextNode(WebInspector.UIString("user agent stylesheet"));
1203        if (this.rule.isUser)
1204            return document.createTextNode(WebInspector.UIString("user stylesheet"));
1205        if (this.rule.isViaInspector) {
1206            var element = document.createElement("span");
1207            /**
1208             * @param {?WebInspector.Resource} resource
1209             */
1210            function callback(resource)
1211            {
1212                if (resource)
1213                    element.appendChild(linkifyUncopyable(resource.url, this.rule.sourceLine));
1214                else
1215                    element.textContent = WebInspector.UIString("via inspector");
1216            }
1217            WebInspector.cssModel.getViaInspectorResourceForRule(this.rule, callback.bind(this));
1218            return element;
1219        }
1220    },
1221
1222    _handleEmptySpaceMouseDown: function(event)
1223    {
1224        this._willCauseCancelEditing = this._parentPane._isEditingStyle;
1225    },
1226
1227    _handleEmptySpaceClick: function(event)
1228    {
1229        if (!this.editable)
1230            return;
1231
1232        if (!window.getSelection().isCollapsed)
1233            return;
1234
1235        if (this._checkWillCancelEditing())
1236            return;
1237
1238        if (event.target.hasStyleClass("header") || this.element.hasStyleClass("read-only") || event.target.enclosingNodeOrSelfWithClass("media")) {
1239            event.consume();
1240            return;
1241        }
1242        this.expand();
1243        this.addNewBlankProperty().startEditing();
1244    },
1245
1246    _handleSelectorClick: function(event)
1247    {
1248        this._startEditingOnMouseEvent();
1249        event.consume(true);
1250    },
1251
1252    _startEditingOnMouseEvent: function()
1253    {
1254        if (!this.editable)
1255            return;
1256
1257        if (!this.rule && this.propertiesTreeOutline.children.length === 0) {
1258            this.expand();
1259            this.addNewBlankProperty().startEditing();
1260            return;
1261        }
1262
1263        if (!this.rule)
1264            return;
1265
1266        this.startEditingSelector();
1267    },
1268
1269    startEditingSelector: function()
1270    {
1271        var element = this._selectorElement;
1272        if (WebInspector.isBeingEdited(element))
1273            return;
1274
1275        element.scrollIntoViewIfNeeded(false);
1276        element.textContent = element.textContent; // Reset selector marks in group.
1277
1278        var config = new WebInspector.EditingConfig(this.editingSelectorCommitted.bind(this), this.editingSelectorCancelled.bind(this));
1279        WebInspector.startEditing(this._selectorElement, config);
1280
1281        window.getSelection().setBaseAndExtent(element, 0, element, 1);
1282    },
1283
1284    _moveEditorFromSelector: function(moveDirection)
1285    {
1286        this._markSelectorMatches();
1287
1288        if (!moveDirection)
1289            return;
1290
1291        if (moveDirection === "forward") {
1292            this.expand();
1293            var firstChild = this.propertiesTreeOutline.children[0];
1294            while (firstChild && firstChild.inherited)
1295                firstChild = firstChild.nextSibling;
1296            if (!firstChild)
1297                this.addNewBlankProperty().startEditing();
1298            else
1299                firstChild.startEditing(firstChild.nameElement);
1300        } else {
1301            var previousSection = this.previousEditableSibling();
1302            if (!previousSection)
1303                return;
1304
1305            previousSection.expand();
1306            previousSection.addNewBlankProperty().startEditing();
1307        }
1308    },
1309
1310    editingSelectorCommitted: function(element, newContent, oldContent, context, moveDirection)
1311    {
1312        if (newContent)
1313            newContent = newContent.trim();
1314        if (newContent === oldContent) {
1315            // Revert to a trimmed version of the selector if need be.
1316            this._selectorElement.textContent = newContent;
1317            return this._moveEditorFromSelector(moveDirection);
1318        }
1319
1320        var selectedNode = this._parentPane.node;
1321
1322        function successCallback(newRule, doesAffectSelectedNode)
1323        {
1324            if (!doesAffectSelectedNode) {
1325                this.noAffect = true;
1326                this.element.addStyleClass("no-affect");
1327            } else {
1328                delete this.noAffect;
1329                this.element.removeStyleClass("no-affect");
1330            }
1331
1332            this.rule = newRule;
1333            this.styleRule = { section: this, style: newRule.style, selectorText: newRule.selectorText, media: newRule.media, sourceURL: newRule.sourceURL, rule: newRule };
1334
1335            this._parentPane.update(selectedNode);
1336
1337            finishOperationAndMoveEditor.call(this, moveDirection);
1338        }
1339
1340        function finishOperationAndMoveEditor(direction)
1341        {
1342            delete this._parentPane._userOperation;
1343            this._moveEditorFromSelector(direction);
1344        }
1345
1346        // This gets deleted in finishOperationAndMoveEditor(), which is called both on success and failure.
1347        this._parentPane._userOperation = true;
1348        WebInspector.cssModel.setRuleSelector(this.rule.id, selectedNode ? selectedNode.id : 0, newContent, successCallback.bind(this), finishOperationAndMoveEditor.bind(this, moveDirection));
1349    },
1350
1351    editingSelectorCancelled: function()
1352    {
1353        // Do nothing but mark the selectors in group if necessary.
1354        // This is overridden by BlankStylePropertiesSection.
1355        this._markSelectorMatches();
1356    },
1357
1358    __proto__: WebInspector.PropertiesSection.prototype
1359}
1360
1361/**
1362 * @constructor
1363 * @extends {WebInspector.PropertiesSection}
1364 * @param {!WebInspector.StylesSidebarPane} stylesPane
1365 * @param {!Object} styleRule
1366 * @param {!Object.<string, boolean>} usedProperties
1367 */
1368WebInspector.ComputedStylePropertiesSection = function(stylesPane, styleRule, usedProperties)
1369{
1370    WebInspector.PropertiesSection.call(this, "");
1371    this.headerElement.addStyleClass("hidden");
1372    this.element.className = "styles-section monospace first-styles-section read-only computed-style";
1373    this._stylesPane = stylesPane;
1374    this.styleRule = styleRule;
1375    this._usedProperties = usedProperties;
1376    this._alwaysShowComputedProperties = { "display": true, "height": true, "width": true };
1377    this.computedStyle = true;
1378    this._propertyTreeElements = {};
1379    this._expandedPropertyNames = {};
1380}
1381
1382WebInspector.ComputedStylePropertiesSection.prototype = {
1383    collapse: function(dontRememberState)
1384    {
1385        // Overriding with empty body.
1386    },
1387
1388    _isPropertyInherited: function(propertyName)
1389    {
1390        var canonicalName = WebInspector.StylesSidebarPane.canonicalPropertyName(propertyName);
1391        return !(canonicalName in this._usedProperties) && !(canonicalName in this._alwaysShowComputedProperties);
1392    },
1393
1394    update: function()
1395    {
1396        this._expandedPropertyNames = {};
1397        for (var name in this._propertyTreeElements) {
1398            if (this._propertyTreeElements[name].expanded)
1399                this._expandedPropertyNames[name] = true;
1400        }
1401        this._propertyTreeElements = {};
1402        this.propertiesTreeOutline.removeChildren();
1403        this.populated = false;
1404    },
1405
1406    onpopulate: function()
1407    {
1408        function sorter(a, b)
1409        {
1410            return a.name.compareTo(b.name);
1411        }
1412
1413        var style = this.styleRule.style;
1414        if (!style)
1415            return;
1416
1417        var uniqueProperties = [];
1418        var allProperties = style.allProperties;
1419        for (var i = 0; i < allProperties.length; ++i)
1420            uniqueProperties.push(allProperties[i]);
1421        uniqueProperties.sort(sorter);
1422
1423        this._propertyTreeElements = {};
1424        for (var i = 0; i < uniqueProperties.length; ++i) {
1425            var property = uniqueProperties[i];
1426            var inherited = this._isPropertyInherited(property.name);
1427            var item = new WebInspector.ComputedStylePropertyTreeElement(this._stylesPane, this.styleRule, style, property, inherited);
1428            this.propertiesTreeOutline.appendChild(item);
1429            this._propertyTreeElements[property.name] = item;
1430        }
1431    },
1432
1433    rebuildComputedTrace: function(sections)
1434    {
1435        for (var i = 0; i < sections.length; ++i) {
1436            var section = sections[i];
1437            if (section.computedStyle || section.isBlank)
1438                continue;
1439
1440            for (var j = 0; j < section.uniqueProperties.length; ++j) {
1441                var property = section.uniqueProperties[j];
1442                if (property.disabled)
1443                    continue;
1444                if (section.isInherited && !(property.name in WebInspector.CSSMetadata.InheritedProperties))
1445                    continue;
1446
1447                var treeElement = this._propertyTreeElements[property.name];
1448                if (treeElement) {
1449                    var fragment = document.createDocumentFragment();
1450                    var selector = fragment.createChild("span");
1451                    selector.style.color = "gray";
1452                    selector.textContent = section.styleRule.selectorText;
1453                    fragment.appendChild(document.createTextNode(" - " + property.value + " "));
1454                    var subtitle = fragment.createChild("span");
1455                    subtitle.style.float = "right";
1456                    subtitle.appendChild(section._createRuleOriginNode());
1457                    var childElement = new TreeElement(fragment, null, false);
1458                    treeElement.appendChild(childElement);
1459                    if (property.inactive || section.isPropertyOverloaded(property.name))
1460                        childElement.listItemElement.addStyleClass("overloaded");
1461                    if (!property.parsedOk) {
1462                        childElement.listItemElement.addStyleClass("not-parsed-ok");
1463                        childElement.listItemElement.insertBefore(WebInspector.StylesSidebarPane.createExclamationMark(property.name), childElement.listItemElement.firstChild);
1464                    }
1465                }
1466            }
1467        }
1468
1469        // Restore expanded state after update.
1470        for (var name in this._expandedPropertyNames) {
1471            if (name in this._propertyTreeElements)
1472                this._propertyTreeElements[name].expand();
1473        }
1474    },
1475
1476    __proto__: WebInspector.PropertiesSection.prototype
1477}
1478
1479/**
1480 * @constructor
1481 * @extends {WebInspector.StylePropertiesSection}
1482 * @param {WebInspector.StylesSidebarPane} stylesPane
1483 * @param {string} defaultSelectorText
1484 */
1485WebInspector.BlankStylePropertiesSection = function(stylesPane, defaultSelectorText)
1486{
1487    WebInspector.StylePropertiesSection.call(this, stylesPane, {selectorText: defaultSelectorText, rule: {isViaInspector: true}}, true, false, false);
1488    this.element.addStyleClass("blank-section");
1489}
1490
1491WebInspector.BlankStylePropertiesSection.prototype = {
1492    get isBlank()
1493    {
1494        return !this._normal;
1495    },
1496
1497    expand: function()
1498    {
1499        if (!this.isBlank)
1500            WebInspector.StylePropertiesSection.prototype.expand.call(this);
1501    },
1502
1503    editingSelectorCommitted: function(element, newContent, oldContent, context, moveDirection)
1504    {
1505        if (!this.isBlank) {
1506            WebInspector.StylePropertiesSection.prototype.editingSelectorCommitted.call(this, element, newContent, oldContent, context, moveDirection);
1507            return;
1508        }
1509
1510        function successCallback(newRule, doesSelectorAffectSelectedNode)
1511        {
1512            var styleRule = { section: this, style: newRule.style, selectorText: newRule.selectorText, sourceURL: newRule.sourceURL, rule: newRule };
1513            this.makeNormal(styleRule);
1514
1515            if (!doesSelectorAffectSelectedNode) {
1516                this.noAffect = true;
1517                this.element.addStyleClass("no-affect");
1518            }
1519
1520            this._selectorRefElement.removeChildren();
1521            this._selectorRefElement.appendChild(this._createRuleOriginNode());
1522            this.expand();
1523            if (this.element.parentElement) // Might have been detached already.
1524                this._moveEditorFromSelector(moveDirection);
1525
1526            this._markSelectorMatches();
1527            delete this._parentPane._userOperation;
1528        }
1529
1530        if (newContent)
1531            newContent = newContent.trim();
1532        this._parentPane._userOperation = true;
1533        WebInspector.cssModel.addRule(this.pane.node.id, newContent, successCallback.bind(this), this.editingSelectorCancelled.bind(this));
1534    },
1535
1536    editingSelectorCancelled: function()
1537    {
1538        delete this._parentPane._userOperation;
1539        if (!this.isBlank) {
1540            WebInspector.StylePropertiesSection.prototype.editingSelectorCancelled.call(this);
1541            return;
1542        }
1543
1544        this.pane.removeSection(this);
1545    },
1546
1547    makeNormal: function(styleRule)
1548    {
1549        this.element.removeStyleClass("blank-section");
1550        this.styleRule = styleRule;
1551        this.rule = styleRule.rule;
1552
1553        // FIXME: replace this instance by a normal WebInspector.StylePropertiesSection.
1554        this._normal = true;
1555    },
1556
1557    __proto__: WebInspector.StylePropertiesSection.prototype
1558}
1559
1560/**
1561 * @constructor
1562 * @extends {TreeElement}
1563 * @param {Object} styleRule
1564 * @param {WebInspector.CSSStyleDeclaration} style
1565 * @param {WebInspector.CSSProperty} property
1566 * @param {boolean} inherited
1567 * @param {boolean} overloaded
1568 * @param {boolean} hasChildren
1569 */
1570WebInspector.StylePropertyTreeElementBase = function(styleRule, style, property, inherited, overloaded, hasChildren)
1571{
1572    this._styleRule = styleRule;
1573    this.style = style;
1574    this.property = property;
1575    this._inherited = inherited;
1576    this._overloaded = overloaded;
1577
1578    // Pass an empty title, the title gets made later in onattach.
1579    TreeElement.call(this, "", null, hasChildren);
1580
1581    this.selectable = false;
1582}
1583
1584WebInspector.StylePropertyTreeElementBase.prototype = {
1585    /**
1586     * @return {?WebInspector.DOMNode}
1587     */
1588    node: function()
1589    {
1590        return null;  // Overridden by ancestors.
1591    },
1592
1593    /**
1594     * @return {?WebInspector.StylesSidebarPane}
1595     */
1596    editablePane: function()
1597    {
1598        return null;  // Overridden by ancestors.
1599    },
1600
1601    get inherited()
1602    {
1603        return this._inherited;
1604    },
1605
1606    set inherited(x)
1607    {
1608        if (x === this._inherited)
1609            return;
1610        this._inherited = x;
1611        this.updateState();
1612    },
1613
1614    get overloaded()
1615    {
1616        return this._overloaded;
1617    },
1618
1619    set overloaded(x)
1620    {
1621        if (x === this._overloaded)
1622            return;
1623        this._overloaded = x;
1624        this.updateState();
1625    },
1626
1627    get disabled()
1628    {
1629        return this.property.disabled;
1630    },
1631
1632    get name()
1633    {
1634        if (!this.disabled || !this.property.text)
1635            return this.property.name;
1636
1637        var text = this.property.text;
1638        var index = text.indexOf(":");
1639        if (index < 1)
1640            return this.property.name;
1641
1642        return text.substring(0, index).trim();
1643    },
1644
1645    get priority()
1646    {
1647        if (this.disabled)
1648            return ""; // rely upon raw text to render it in the value field
1649        return this.property.priority;
1650    },
1651
1652    get value()
1653    {
1654        if (!this.disabled || !this.property.text)
1655            return this.property.value;
1656
1657        var match = this.property.text.match(/(.*);\s*/);
1658        if (!match || !match[1])
1659            return this.property.value;
1660
1661        var text = match[1];
1662        var index = text.indexOf(":");
1663        if (index < 1)
1664            return this.property.value;
1665
1666        return text.substring(index + 1).trim();
1667    },
1668
1669    get parsedOk()
1670    {
1671        return this.property.parsedOk;
1672    },
1673
1674    onattach: function()
1675    {
1676        this.updateTitle();
1677    },
1678
1679    updateTitle: function()
1680    {
1681        var value = this.value;
1682
1683        this.updateState();
1684
1685        var nameElement = document.createElement("span");
1686        nameElement.className = "webkit-css-property";
1687        nameElement.textContent = this.name;
1688        nameElement.title = this.property.propertyText;
1689        this.nameElement = nameElement;
1690
1691        this._expandElement = document.createElement("span");
1692        this._expandElement.className = "expand-element";
1693
1694        var valueElement = document.createElement("span");
1695        valueElement.className = "value";
1696        this.valueElement = valueElement;
1697
1698        var cf = WebInspector.Color.Format;
1699
1700        if (value) {
1701            var self = this;
1702
1703            function processValue(regex, processor, nextProcessor, valueText)
1704            {
1705                var container = document.createDocumentFragment();
1706
1707                var items = valueText.replace(regex, "\0$1\0").split("\0");
1708                for (var i = 0; i < items.length; ++i) {
1709                    if ((i % 2) === 0) {
1710                        if (nextProcessor)
1711                            container.appendChild(nextProcessor(items[i]));
1712                        else
1713                            container.appendChild(document.createTextNode(items[i]));
1714                    } else {
1715                        var processedNode = processor(items[i]);
1716                        if (processedNode)
1717                            container.appendChild(processedNode);
1718                    }
1719                }
1720
1721                return container;
1722            }
1723
1724            function linkifyURL(url)
1725            {
1726                var hrefUrl = url;
1727                var match = hrefUrl.match(/['"]?([^'"]+)/);
1728                if (match)
1729                    hrefUrl = match[1];
1730                var container = document.createDocumentFragment();
1731                container.appendChild(document.createTextNode("url("));
1732                if (self._styleRule.sourceURL)
1733                    hrefUrl = WebInspector.ParsedURL.completeURL(self._styleRule.sourceURL, hrefUrl);
1734                else if (self.node())
1735                    hrefUrl = self.node().resolveURL(hrefUrl);
1736                var hasResource = !!WebInspector.resourceForURL(hrefUrl);
1737                // FIXME: WebInspector.linkifyURLAsNode() should really use baseURI.
1738                container.appendChild(WebInspector.linkifyURLAsNode(hrefUrl, url, undefined, !hasResource));
1739                container.appendChild(document.createTextNode(")"));
1740                return container;
1741            }
1742
1743            function processColor(text)
1744            {
1745                var color = WebInspector.Color.parse(text);
1746
1747                // We can be called with valid non-color values of |text| (like 'none' from border style)
1748                if (!color)
1749                    return document.createTextNode(text);
1750
1751                var format = getFormat();
1752                var spectrumHelper = self.editablePane() && self.editablePane()._spectrumHelper;
1753                var spectrum = spectrumHelper ? spectrumHelper.spectrum() : null;
1754
1755                var colorSwatch = new WebInspector.ColorSwatch();
1756                colorSwatch.setColorString(text);
1757                colorSwatch.element.addEventListener("click", swatchClick, false);
1758
1759                var scrollerElement;
1760
1761                function spectrumChanged(e)
1762                {
1763                    color = e.data;
1764                    var colorString = color.toString();
1765                    spectrum.displayText = colorString;
1766                    colorValueElement.textContent = colorString;
1767                    colorSwatch.setColorString(colorString);
1768                    self.applyStyleText(nameElement.textContent + ": " + valueElement.textContent, false, false, false);
1769                }
1770
1771                function spectrumHidden(event)
1772                {
1773                    if (scrollerElement)
1774                        scrollerElement.removeEventListener("scroll", repositionSpectrum, false);
1775                    var commitEdit = event.data;
1776                    var propertyText = !commitEdit && self.originalPropertyText ? self.originalPropertyText : (nameElement.textContent + ": " + valueElement.textContent);
1777                    self.applyStyleText(propertyText, true, true, false);
1778                    spectrum.removeEventListener(WebInspector.Spectrum.Events.ColorChanged, spectrumChanged);
1779                    spectrumHelper.removeEventListener(WebInspector.SpectrumPopupHelper.Events.Hidden, spectrumHidden);
1780
1781                    delete self.editablePane()._isEditingStyle;
1782                    delete self.originalPropertyText;
1783                }
1784
1785                function repositionSpectrum()
1786                {
1787                    spectrumHelper.reposition(colorSwatch.element);
1788                }
1789
1790                function swatchClick(e)
1791                {
1792                    // Shift + click toggles color formats.
1793                    // Click opens colorpicker, only if the element is not in computed styles section.
1794                    if (!spectrumHelper || e.shiftKey)
1795                        changeColorDisplay(e);
1796                    else {
1797                        var visible = spectrumHelper.toggle(colorSwatch.element, color, format);
1798
1799                        if (visible) {
1800                            spectrum.displayText = color.toString(format);
1801                            self.originalPropertyText = self.property.propertyText;
1802                            self.editablePane()._isEditingStyle = true;
1803                            spectrum.addEventListener(WebInspector.Spectrum.Events.ColorChanged, spectrumChanged);
1804                            spectrumHelper.addEventListener(WebInspector.SpectrumPopupHelper.Events.Hidden, spectrumHidden);
1805
1806                            scrollerElement = colorSwatch.element.enclosingNodeOrSelfWithClass("scroll-target");
1807                            if (scrollerElement)
1808                                scrollerElement.addEventListener("scroll", repositionSpectrum, false);
1809                            else
1810                                console.error("Unable to handle color picker scrolling");
1811                        }
1812                    }
1813                    e.consume(true);
1814                }
1815
1816                function getFormat()
1817                {
1818                    var format;
1819                    var formatSetting = WebInspector.settings.colorFormat.get();
1820                    if (formatSetting === cf.Original)
1821                        format = cf.Original;
1822                    else if (formatSetting === cf.RGB)
1823                        format = (color.simple ? cf.RGB : cf.RGBA);
1824                    else if (formatSetting === cf.HSL)
1825                        format = (color.simple ? cf.HSL : cf.HSLA);
1826                    else if (color.simple)
1827                        format = (color.hasShortHex() ? cf.ShortHEX : cf.HEX);
1828                    else
1829                        format = cf.RGBA;
1830
1831                    return format;
1832                }
1833
1834                var colorValueElement = document.createElement("span");
1835                colorValueElement.textContent = color.toString(format);
1836
1837                function nextFormat(curFormat)
1838                {
1839                    // The format loop is as follows:
1840                    // * original
1841                    // * rgb(a)
1842                    // * hsl(a)
1843                    // * nickname (if the color has a nickname)
1844                    // * if the color is simple:
1845                    //   - shorthex (if has short hex)
1846                    //   - hex
1847                    switch (curFormat) {
1848                        case cf.Original:
1849                            return color.simple ? cf.RGB : cf.RGBA;
1850
1851                        case cf.RGB:
1852                        case cf.RGBA:
1853                            return color.simple ? cf.HSL : cf.HSLA;
1854
1855                        case cf.HSL:
1856                        case cf.HSLA:
1857                            if (color.nickname)
1858                                return cf.Nickname;
1859                            if (color.simple)
1860                                return color.hasShortHex() ? cf.ShortHEX : cf.HEX;
1861                            else
1862                                return cf.Original;
1863
1864                        case cf.ShortHEX:
1865                            return cf.HEX;
1866
1867                        case cf.HEX:
1868                            return cf.Original;
1869
1870                        case cf.Nickname:
1871                            if (color.simple)
1872                                return color.hasShortHex() ? cf.ShortHEX : cf.HEX;
1873                            else
1874                                return cf.Original;
1875
1876                        default:
1877                            return null;
1878                    }
1879                }
1880
1881                function changeColorDisplay(event)
1882                {
1883                    do {
1884                        format = nextFormat(format);
1885                        var currentValue = color.toString(format || "");
1886                    } while (format && currentValue === color.value && format !== cf.Original);
1887
1888                    if (format)
1889                        colorValueElement.textContent = currentValue;
1890                }
1891
1892                var container = document.createElement("nobr");
1893                container.appendChild(colorSwatch.element);
1894                container.appendChild(colorValueElement);
1895                return container;
1896            }
1897
1898            var colorRegex = /((?:rgb|hsl)a?\([^)]+\)|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}|\b\w+\b(?!-))/g;
1899            var colorProcessor = processValue.bind(window, colorRegex, processColor, null);
1900
1901            valueElement.appendChild(processValue(/url\(\s*([^)]+)\s*\)/g, linkifyURL.bind(this), WebInspector.CSSMetadata.isColorAwareProperty(self.name) ? colorProcessor : null, value));
1902        }
1903
1904        this.listItemElement.removeChildren();
1905        nameElement.normalize();
1906        valueElement.normalize();
1907
1908        if (!this.treeOutline)
1909            return;
1910
1911        this.listItemElement.appendChild(nameElement);
1912        this.listItemElement.appendChild(document.createTextNode(": "));
1913        this.listItemElement.appendChild(this._expandElement);
1914        this.listItemElement.appendChild(valueElement);
1915        this.listItemElement.appendChild(document.createTextNode(";"));
1916
1917        if (!this.parsedOk) {
1918            // Avoid having longhands under an invalid shorthand.
1919            this.hasChildren = false;
1920            this.listItemElement.addStyleClass("not-parsed-ok");
1921
1922            // Add a separate exclamation mark IMG element with a tooltip.
1923            this.listItemElement.insertBefore(WebInspector.StylesSidebarPane.createExclamationMark(this.property.name), this.listItemElement.firstChild);
1924        }
1925        if (this.property.inactive)
1926            this.listItemElement.addStyleClass("inactive");
1927    },
1928
1929    updateState: function()
1930    {
1931        if (!this.listItemElement)
1932            return;
1933
1934        if (this.style.isPropertyImplicit(this.name) || this.value === "initial")
1935            this.listItemElement.addStyleClass("implicit");
1936        else
1937            this.listItemElement.removeStyleClass("implicit");
1938
1939        if (this.inherited)
1940            this.listItemElement.addStyleClass("inherited");
1941        else
1942            this.listItemElement.removeStyleClass("inherited");
1943
1944        if (this.overloaded)
1945            this.listItemElement.addStyleClass("overloaded");
1946        else
1947            this.listItemElement.removeStyleClass("overloaded");
1948
1949        if (this.disabled)
1950            this.listItemElement.addStyleClass("disabled");
1951        else
1952            this.listItemElement.removeStyleClass("disabled");
1953    },
1954
1955    __proto__: TreeElement.prototype
1956}
1957
1958/**
1959 * @constructor
1960 * @extends {WebInspector.StylePropertyTreeElementBase}
1961 * @param {WebInspector.StylesSidebarPane} stylesPane
1962 * @param {Object} styleRule
1963 * @param {WebInspector.CSSStyleDeclaration} style
1964 * @param {WebInspector.CSSProperty} property
1965 * @param {boolean} inherited
1966 */
1967WebInspector.ComputedStylePropertyTreeElement = function(stylesPane, styleRule, style, property, inherited)
1968{
1969    WebInspector.StylePropertyTreeElementBase.call(this, styleRule, style, property, inherited, false, false);
1970    this._stylesPane = stylesPane;
1971}
1972
1973WebInspector.ComputedStylePropertyTreeElement.prototype = {
1974    /**
1975     * @return {?WebInspector.DOMNode}
1976     */
1977    node: function()
1978    {
1979        return this._stylesPane.node;
1980    },
1981
1982    /**
1983     * @return {?WebInspector.StylesSidebarPane}
1984     */
1985    editablePane: function()
1986    {
1987        return null;
1988    },
1989
1990    __proto__: WebInspector.StylePropertyTreeElementBase.prototype
1991}
1992
1993/**
1994 * @constructor
1995 * @extends {WebInspector.StylePropertyTreeElementBase}
1996 * @param {?WebInspector.StylesSidebarPane} stylesPane
1997 * @param {Object} styleRule
1998 * @param {WebInspector.CSSStyleDeclaration} style
1999 * @param {WebInspector.CSSProperty} property
2000 * @param {boolean} isShorthand
2001 * @param {boolean} inherited
2002 * @param {boolean} overloaded
2003 */
2004WebInspector.StylePropertyTreeElement = function(stylesPane, styleRule, style, property, isShorthand, inherited, overloaded)
2005{
2006    WebInspector.StylePropertyTreeElementBase.call(this, styleRule, style, property, inherited, overloaded, isShorthand);
2007    this._parentPane = stylesPane;
2008    this.isShorthand = isShorthand;
2009}
2010
2011WebInspector.StylePropertyTreeElement.prototype = {
2012    /**
2013     * @return {?WebInspector.DOMNode}
2014     */
2015    node: function()
2016    {
2017        return this._parentPane.node;
2018    },
2019
2020    /**
2021     * @return {?WebInspector.StylesSidebarPane}
2022     */
2023    editablePane: function()
2024    {
2025        return this._parentPane;
2026    },
2027
2028    /**
2029     * @return {WebInspector.StylePropertiesSection}
2030     */
2031    section: function()
2032    {
2033        return this.treeOutline && this.treeOutline.section;
2034    },
2035
2036    _updatePane: function(userCallback)
2037    {
2038        var section = this.section();
2039        if (section && section.pane)
2040            section.pane._refreshUpdate(section, false, userCallback);
2041        else  {
2042            if (userCallback)
2043                userCallback();
2044        }
2045    },
2046
2047    toggleEnabled: function(event)
2048    {
2049        var disabled = !event.target.checked;
2050
2051        function callback(newStyle)
2052        {
2053            if (!newStyle)
2054                return;
2055
2056            newStyle.parentRule = this.style.parentRule;
2057            this.style = newStyle;
2058            this._styleRule.style = newStyle;
2059
2060            var section = this.section();
2061            if (section && section.pane)
2062                section.pane.dispatchEventToListeners("style property toggled");
2063
2064            this._updatePane();
2065
2066            delete this._parentPane._userOperation;
2067        }
2068
2069        this._parentPane._userOperation = true;
2070        this.property.setDisabled(disabled, callback.bind(this));
2071        event.consume();
2072    },
2073
2074    onpopulate: function()
2075    {
2076        // Only populate once and if this property is a shorthand.
2077        if (this.children.length || !this.isShorthand)
2078            return;
2079
2080        var longhandProperties = this.style.longhandProperties(this.name);
2081        for (var i = 0; i < longhandProperties.length; ++i) {
2082            var name = longhandProperties[i].name;
2083
2084            var section = this.section();
2085            if (section) {
2086                var inherited = section.isPropertyInherited(name);
2087                var overloaded = section.isPropertyOverloaded(name);
2088            }
2089
2090            var liveProperty = this.style.getLiveProperty(name);
2091            if (!liveProperty)
2092                continue;
2093
2094            var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this._styleRule, this.style, liveProperty, false, inherited, overloaded);
2095            this.appendChild(item);
2096        }
2097    },
2098
2099    restoreNameElement: function()
2100    {
2101        // Restore <span class="webkit-css-property"> if it doesn't yet exist or was accidentally deleted.
2102        if (this.nameElement === this.listItemElement.querySelector(".webkit-css-property"))
2103            return;
2104
2105        this.nameElement = document.createElement("span");
2106        this.nameElement.className = "webkit-css-property";
2107        this.nameElement.textContent = "";
2108        this.listItemElement.insertBefore(this.nameElement, this.listItemElement.firstChild);
2109    },
2110
2111    onattach: function()
2112    {
2113        WebInspector.StylePropertyTreeElementBase.prototype.onattach.call(this);
2114
2115        this.listItemElement.addEventListener("mousedown", this._mouseDown.bind(this));
2116        this.listItemElement.addEventListener("mouseup", this._resetMouseDownElement.bind(this));
2117        this.listItemElement.addEventListener("click", this._mouseClick.bind(this));
2118    },
2119
2120    _mouseDown: function(event)
2121    {
2122        if (this._parentPane) {
2123            this._parentPane._mouseDownTreeElement = this;
2124            this._parentPane._mouseDownTreeElementIsName = this._isNameElement(event.target);
2125            this._parentPane._mouseDownTreeElementIsValue = this._isValueElement(event.target);
2126        }
2127    },
2128
2129    _resetMouseDownElement: function()
2130    {
2131        if (this._parentPane) {
2132            delete this._parentPane._mouseDownTreeElement;
2133            delete this._parentPane._mouseDownTreeElementIsName;
2134            delete this._parentPane._mouseDownTreeElementIsValue;
2135        }
2136    },
2137
2138    updateTitle: function()
2139    {
2140        WebInspector.StylePropertyTreeElementBase.prototype.updateTitle.call(this);
2141
2142        if (this.parsedOk && this.section() && this.parent.root) {
2143            var enabledCheckboxElement = document.createElement("input");
2144            enabledCheckboxElement.className = "enabled-button";
2145            enabledCheckboxElement.type = "checkbox";
2146            enabledCheckboxElement.checked = !this.disabled;
2147            enabledCheckboxElement.addEventListener("click", this.toggleEnabled.bind(this), false);
2148            this.listItemElement.insertBefore(enabledCheckboxElement, this.listItemElement.firstChild);
2149        }
2150    },
2151
2152    _mouseClick: function(event)
2153    {
2154        if (!window.getSelection().isCollapsed)
2155            return;
2156
2157        event.consume(true);
2158
2159        if (event.target === this.listItemElement) {
2160            var section = this.section();
2161            if (!section || !section.editable)
2162                return;
2163
2164            if (section._checkWillCancelEditing())
2165                return;
2166            section.addNewBlankProperty(this.property.index + 1).startEditing();
2167            return;
2168        }
2169
2170        if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) && this.section().navigable) {
2171            this._navigateToSource(event.target);
2172            return;
2173        }
2174
2175        this.startEditing(event.target);
2176    },
2177
2178    /**
2179     * @param {Element} element
2180     */
2181    _navigateToSource: function(element)
2182    {
2183        console.assert(this.section().navigable);
2184        var propertyNameClicked = element === this.nameElement;
2185        var uiLocation = this.property.uiLocation(propertyNameClicked);
2186        if (!uiLocation)
2187            return;
2188        WebInspector.showPanel("scripts").showUISourceCode(uiLocation.uiSourceCode, uiLocation.lineNumber);
2189    },
2190
2191    _isNameElement: function(element)
2192    {
2193        return element.enclosingNodeOrSelfWithClass("webkit-css-property") === this.nameElement;
2194    },
2195
2196    _isValueElement: function(element)
2197    {
2198        return !!element.enclosingNodeOrSelfWithClass("value");
2199    },
2200
2201    startEditing: function(selectElement)
2202    {
2203        // FIXME: we don't allow editing of longhand properties under a shorthand right now.
2204        if (this.parent.isShorthand)
2205            return;
2206
2207        if (selectElement === this._expandElement)
2208            return;
2209
2210        var section = this.section();
2211        if (section && !section.editable)
2212            return;
2213
2214        if (!selectElement)
2215            selectElement = this.nameElement; // No arguments passed in - edit the name element by default.
2216        else
2217            selectElement = selectElement.enclosingNodeOrSelfWithClass("webkit-css-property") || selectElement.enclosingNodeOrSelfWithClass("value");
2218
2219        var isEditingName = selectElement === this.nameElement;
2220        if (!isEditingName) {
2221            if (selectElement !== this.valueElement) {
2222                // Click in the LI - start editing value.
2223                selectElement = this.valueElement;
2224            }
2225
2226            this.valueElement.textContent = this.value;
2227        }
2228
2229        if (WebInspector.isBeingEdited(selectElement))
2230            return;
2231
2232        var context = {
2233            expanded: this.expanded,
2234            hasChildren: this.hasChildren,
2235            isEditingName: isEditingName,
2236            previousContent: selectElement.textContent
2237        };
2238
2239        // Lie about our children to prevent expanding on double click and to collapse shorthands.
2240        this.hasChildren = false;
2241
2242        if (selectElement.parentElement)
2243            selectElement.parentElement.addStyleClass("child-editing");
2244        selectElement.textContent = selectElement.textContent; // remove color swatch and the like
2245
2246        function pasteHandler(context, event)
2247        {
2248            var data = event.clipboardData.getData("Text");
2249            if (!data)
2250                return;
2251            var colonIdx = data.indexOf(":");
2252            if (colonIdx < 0)
2253                return;
2254            var name = data.substring(0, colonIdx).trim();
2255            var value = data.substring(colonIdx + 1).trim();
2256
2257            event.preventDefault();
2258
2259            if (!("originalName" in context)) {
2260                context.originalName = this.nameElement.textContent;
2261                context.originalValue = this.valueElement.textContent;
2262            }
2263            this.property.name = name;
2264            this.property.value = value;
2265            this.nameElement.textContent = name;
2266            this.valueElement.textContent = value;
2267            this.nameElement.normalize();
2268            this.valueElement.normalize();
2269
2270            this.editingCommitted(null, event.target.textContent, context.previousContent, context, "forward");
2271        }
2272
2273        function blurListener(context, event)
2274        {
2275            var treeElement = this._parentPane._mouseDownTreeElement;
2276            var moveDirection = "";
2277            if (treeElement === this) {
2278                if (isEditingName && this._parentPane._mouseDownTreeElementIsValue)
2279                    moveDirection = "forward";
2280                if (!isEditingName && this._parentPane._mouseDownTreeElementIsName)
2281                    moveDirection = "backward";
2282            }
2283            this.editingCommitted(null, event.target.textContent, context.previousContent, context, moveDirection);
2284        }
2285
2286        delete this.originalPropertyText;
2287
2288        this._parentPane._isEditingStyle = true;
2289        if (selectElement.parentElement)
2290            selectElement.parentElement.scrollIntoViewIfNeeded(false);
2291
2292        var applyItemCallback = !isEditingName ? this._applyFreeFlowStyleTextEdit.bind(this, true) : undefined;
2293        this._prompt = new WebInspector.StylesSidebarPane.CSSPropertyPrompt(isEditingName ? WebInspector.CSSMetadata.cssPropertiesMetainfo : WebInspector.CSSMetadata.keywordsForProperty(this.nameElement.textContent), this, isEditingName);
2294        if (applyItemCallback) {
2295            this._prompt.addEventListener(WebInspector.TextPrompt.Events.ItemApplied, applyItemCallback, this);
2296            this._prompt.addEventListener(WebInspector.TextPrompt.Events.ItemAccepted, applyItemCallback, this);
2297        }
2298        var proxyElement = this._prompt.attachAndStartEditing(selectElement, blurListener.bind(this, context));
2299
2300        proxyElement.addEventListener("keydown", this.editingNameValueKeyDown.bind(this, context), false);
2301        if (isEditingName)
2302            proxyElement.addEventListener("paste", pasteHandler.bind(this, context));
2303
2304        window.getSelection().setBaseAndExtent(selectElement, 0, selectElement, 1);
2305    },
2306
2307    editingNameValueKeyDown: function(context, event)
2308    {
2309        if (event.handled)
2310            return;
2311
2312        var isEditingName = context.isEditingName;
2313        var result;
2314
2315        function shouldCommitValueSemicolon(text, cursorPosition)
2316        {
2317            // FIXME: should this account for semicolons inside comments?
2318            var openQuote = "";
2319            for (var i = 0; i < cursorPosition; ++i) {
2320                var ch = text[i];
2321                if (ch === "\\" && openQuote !== "")
2322                    ++i; // skip next character inside string
2323                else if (!openQuote && (ch === "\"" || ch === "'"))
2324                    openQuote = ch;
2325                else if (openQuote === ch)
2326                    openQuote = "";
2327            }
2328            return !openQuote;
2329        }
2330
2331        // FIXME: the ":"/";" detection does not work for non-US layouts due to the event being keydown rather than keypress.
2332        var isFieldInputTerminated = (event.keyCode === WebInspector.KeyboardShortcut.Keys.Semicolon.code) &&
2333            (isEditingName ? event.shiftKey : (!event.shiftKey && shouldCommitValueSemicolon(event.target.textContent, event.target.selectionLeftOffset())));
2334        if (isEnterKey(event) || isFieldInputTerminated) {
2335            // Enter or colon (for name)/semicolon outside of string (for value).
2336            event.preventDefault();
2337            result = "forward";
2338        } else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code || event.keyIdentifier === "U+001B")
2339            result = "cancel";
2340        else if (!isEditingName && this._newProperty && event.keyCode === WebInspector.KeyboardShortcut.Keys.Backspace.code) {
2341            // For a new property, when Backspace is pressed at the beginning of new property value, move back to the property name.
2342            var selection = window.getSelection();
2343            if (selection.isCollapsed && !selection.focusOffset) {
2344                event.preventDefault();
2345                result = "backward";
2346            }
2347        } else if (event.keyIdentifier === "U+0009") { // Tab key.
2348            result = event.shiftKey ? "backward" : "forward";
2349            event.preventDefault();
2350        }
2351
2352        if (result) {
2353            switch (result) {
2354            case "cancel":
2355                this.editingCancelled(null, context);
2356                break;
2357            case "forward":
2358            case "backward":
2359                this.editingCommitted(null, event.target.textContent, context.previousContent, context, result);
2360                break;
2361            }
2362
2363            event.consume();
2364            return;
2365        }
2366
2367        if (!isEditingName)
2368            this._applyFreeFlowStyleTextEdit(false);
2369    },
2370
2371    _applyFreeFlowStyleTextEdit: function(now)
2372    {
2373        if (this._applyFreeFlowStyleTextEditTimer)
2374            clearTimeout(this._applyFreeFlowStyleTextEditTimer);
2375
2376        function apply()
2377        {
2378            var valueText = this.valueElement.textContent;
2379            if (valueText.indexOf(";") === -1)
2380                this.applyStyleText(this.nameElement.textContent + ": " + valueText, false, false, false);
2381        }
2382        if (now)
2383            apply.call(this);
2384        else
2385            this._applyFreeFlowStyleTextEditTimer = setTimeout(apply.bind(this), 100);
2386    },
2387
2388    kickFreeFlowStyleEditForTest: function()
2389    {
2390        this._applyFreeFlowStyleTextEdit(true);
2391    },
2392
2393    editingEnded: function(context)
2394    {
2395        this._resetMouseDownElement();
2396        if (this._applyFreeFlowStyleTextEditTimer)
2397            clearTimeout(this._applyFreeFlowStyleTextEditTimer);
2398
2399        this.hasChildren = context.hasChildren;
2400        if (context.expanded)
2401            this.expand();
2402        var editedElement = context.isEditingName ? this.nameElement : this.valueElement;
2403        // The proxyElement has been deleted, no need to remove listener.
2404        if (editedElement.parentElement)
2405            editedElement.parentElement.removeStyleClass("child-editing");
2406
2407        delete this._parentPane._isEditingStyle;
2408    },
2409
2410    editingCancelled: function(element, context)
2411    {
2412        this._removePrompt();
2413        this._revertStyleUponEditingCanceled(this.originalPropertyText);
2414        // This should happen last, as it clears the info necessary to restore the property value after [Page]Up/Down changes.
2415        this.editingEnded(context);
2416    },
2417
2418    _revertStyleUponEditingCanceled: function(originalPropertyText)
2419    {
2420        if (typeof originalPropertyText === "string") {
2421            delete this.originalPropertyText;
2422            this.applyStyleText(originalPropertyText, true, false, true);
2423        } else {
2424            if (this._newProperty)
2425                this.treeOutline.removeChild(this);
2426            else
2427                this.updateTitle();
2428        }
2429    },
2430
2431    _findSibling: function(moveDirection)
2432    {
2433        var target = this;
2434        do {
2435            target = (moveDirection === "forward" ? target.nextSibling : target.previousSibling);
2436        } while(target && target.inherited);
2437
2438        return target;
2439    },
2440
2441    editingCommitted: function(element, userInput, previousContent, context, moveDirection)
2442    {
2443        this._removePrompt();
2444        this.editingEnded(context);
2445        var isEditingName = context.isEditingName;
2446
2447        // Determine where to move to before making changes
2448        var createNewProperty, moveToPropertyName, moveToSelector;
2449        var isDataPasted = "originalName" in context;
2450        var isDirtyViaPaste = isDataPasted && (this.nameElement.textContent !== context.originalName || this.valueElement.textContent !== context.originalValue);
2451        var isPropertySplitPaste = isDataPasted && isEditingName && this.valueElement.textContent !== context.originalValue;
2452        var moveTo = this;
2453        var moveToOther = (isEditingName ^ (moveDirection === "forward"));
2454        var abandonNewProperty = this._newProperty && !userInput && (moveToOther || isEditingName);
2455        if (moveDirection === "forward" && (!isEditingName || isPropertySplitPaste) || moveDirection === "backward" && isEditingName) {
2456            moveTo = moveTo._findSibling(moveDirection);
2457            if (moveTo)
2458                moveToPropertyName = moveTo.name;
2459            else if (moveDirection === "forward" && (!this._newProperty || userInput))
2460                createNewProperty = true;
2461            else if (moveDirection === "backward")
2462                moveToSelector = true;
2463        }
2464
2465        // Make the Changes and trigger the moveToNextCallback after updating.
2466        var moveToIndex = moveTo && this.treeOutline ? this.treeOutline.children.indexOf(moveTo) : -1;
2467        var blankInput = /^\s*$/.test(userInput);
2468        var shouldCommitNewProperty = this._newProperty && (isPropertySplitPaste || moveToOther || (!moveDirection && !isEditingName) || (isEditingName && blankInput));
2469        var section = this.section();
2470        if (((userInput !== previousContent || isDirtyViaPaste) && !this._newProperty) || shouldCommitNewProperty) {
2471            section._afterUpdate = moveToNextCallback.bind(this, this._newProperty, !blankInput, section);
2472            var propertyText;
2473            if (blankInput || (this._newProperty && /^\s*$/.test(this.valueElement.textContent)))
2474                propertyText = "";
2475            else {
2476                if (isEditingName)
2477                    propertyText = userInput + ": " + this.valueElement.textContent;
2478                else
2479                    propertyText = this.nameElement.textContent + ": " + userInput;
2480            }
2481            this.applyStyleText(propertyText, true, true, false);
2482        } else {
2483            if (!isDataPasted && !this._newProperty)
2484                this.updateTitle();
2485            moveToNextCallback.call(this, this._newProperty, false, section);
2486        }
2487
2488        // The Callback to start editing the next/previous property/selector.
2489        function moveToNextCallback(alreadyNew, valueChanged, section)
2490        {
2491            if (!moveDirection)
2492                return;
2493
2494            // User just tabbed through without changes.
2495            if (moveTo && moveTo.parent) {
2496                moveTo.startEditing(!isEditingName ? moveTo.nameElement : moveTo.valueElement);
2497                return;
2498            }
2499
2500            // User has made a change then tabbed, wiping all the original treeElements.
2501            // Recalculate the new treeElement for the same property we were going to edit next.
2502            if (moveTo && !moveTo.parent) {
2503                var propertyElements = section.propertiesTreeOutline.children;
2504                if (moveDirection === "forward" && blankInput && !isEditingName)
2505                    --moveToIndex;
2506                if (moveToIndex >= propertyElements.length && !this._newProperty)
2507                    createNewProperty = true;
2508                else {
2509                    var treeElement = moveToIndex >= 0 ? propertyElements[moveToIndex] : null;
2510                    if (treeElement) {
2511                        var elementToEdit = !isEditingName || isPropertySplitPaste ? treeElement.nameElement : treeElement.valueElement;
2512                        if (alreadyNew && blankInput)
2513                            elementToEdit = moveDirection === "forward" ? treeElement.nameElement : treeElement.valueElement;
2514                        treeElement.startEditing(elementToEdit);
2515                        return;
2516                    } else if (!alreadyNew)
2517                        moveToSelector = true;
2518                }
2519            }
2520
2521            // Create a new attribute in this section (or move to next editable selector if possible).
2522            if (createNewProperty) {
2523                if (alreadyNew && !valueChanged && (isEditingName ^ (moveDirection === "backward")))
2524                    return;
2525
2526                section.addNewBlankProperty().startEditing();
2527                return;
2528            }
2529
2530            if (abandonNewProperty) {
2531                moveTo = this._findSibling(moveDirection);
2532                var sectionToEdit = (moveTo || moveDirection === "backward") ? section : section.nextEditableSibling();
2533                if (sectionToEdit) {
2534                    if (sectionToEdit.rule)
2535                        sectionToEdit.startEditingSelector();
2536                    else
2537                        sectionToEdit._moveEditorFromSelector(moveDirection);
2538                }
2539                return;
2540            }
2541
2542            if (moveToSelector) {
2543                if (section.rule)
2544                    section.startEditingSelector();
2545                else
2546                    section._moveEditorFromSelector(moveDirection);
2547            }
2548        }
2549    },
2550
2551    _removePrompt: function()
2552    {
2553        // BUG 53242. This cannot go into editingEnded(), as it should always happen first for any editing outcome.
2554        if (this._prompt) {
2555            this._prompt.detach();
2556            delete this._prompt;
2557        }
2558    },
2559
2560    _hasBeenModifiedIncrementally: function()
2561    {
2562        // New properties applied via up/down or live editing have an originalPropertyText and will be deleted later
2563        // on, if cancelled, when the empty string gets applied as their style text.
2564        return typeof this.originalPropertyText === "string" || (!!this.property.propertyText && this._newProperty);
2565    },
2566
2567    applyStyleText: function(styleText, updateInterface, majorChange, isRevert)
2568    {
2569        function userOperationFinishedCallback(parentPane, updateInterface)
2570        {
2571            if (updateInterface)
2572                delete parentPane._userOperation;
2573        }
2574
2575        // Leave a way to cancel editing after incremental changes.
2576        if (!isRevert && !updateInterface && !this._hasBeenModifiedIncrementally()) {
2577            // Remember the rule's original CSS text on [Page](Up|Down), so it can be restored
2578            // if the editing is canceled.
2579            this.originalPropertyText = this.property.propertyText;
2580        }
2581
2582        if (!this.treeOutline)
2583            return;
2584
2585        var section = this.section();
2586        styleText = styleText.replace(/\s/g, " ").trim(); // Replace &nbsp; with whitespace.
2587        var styleTextLength = styleText.length;
2588        if (!styleTextLength && updateInterface && !isRevert && this._newProperty && !this._hasBeenModifiedIncrementally()) {
2589            // The user deleted everything and never applied a new property value via Up/Down scrolling/live editing, so remove the tree element and update.
2590            this.parent.removeChild(this);
2591            section.afterUpdate();
2592            return;
2593        }
2594
2595        var currentNode = this._parentPane.node;
2596        if (updateInterface)
2597            this._parentPane._userOperation = true;
2598
2599        function callback(userCallback, originalPropertyText, newStyle)
2600        {
2601            if (!newStyle) {
2602                if (updateInterface) {
2603                    // It did not apply, cancel editing.
2604                    this._revertStyleUponEditingCanceled(originalPropertyText);
2605                }
2606                userCallback();
2607                return;
2608            }
2609
2610            if (this._newProperty)
2611                this._newPropertyInStyle = true;
2612            newStyle.parentRule = this.style.parentRule;
2613            this.style = newStyle;
2614            this.property = newStyle.propertyAt(this.property.index);
2615            this._styleRule.style = this.style;
2616
2617            if (section && section.pane)
2618                section.pane.dispatchEventToListeners("style edited");
2619
2620            if (updateInterface && currentNode === this.node()) {
2621                this._updatePane(userCallback);
2622                return;
2623            }
2624
2625            userCallback();
2626        }
2627
2628        // Append a ";" if the new text does not end in ";".
2629        // FIXME: this does not handle trailing comments.
2630        if (styleText.length && !/;\s*$/.test(styleText))
2631            styleText += ";";
2632        var overwriteProperty = !!(!this._newProperty || this._newPropertyInStyle);
2633        this.property.setText(styleText, majorChange, overwriteProperty, callback.bind(this, userOperationFinishedCallback.bind(null, this._parentPane, updateInterface), this.originalPropertyText));
2634    },
2635
2636    ondblclick: function()
2637    {
2638        return true; // handled
2639    },
2640
2641    isEventWithinDisclosureTriangle: function(event)
2642    {
2643        return event.target === this._expandElement;
2644    },
2645
2646    __proto__: WebInspector.StylePropertyTreeElementBase.prototype
2647}
2648
2649/**
2650 * @constructor
2651 * @extends {WebInspector.TextPrompt}
2652 * @param {!WebInspector.CSSMetadata} cssCompletions
2653 * @param {!WebInspector.StylePropertyTreeElement} sidebarPane
2654 * @param {boolean} isEditingName
2655 */
2656WebInspector.StylesSidebarPane.CSSPropertyPrompt = function(cssCompletions, sidebarPane, isEditingName)
2657{
2658    // Use the same callback both for applyItemCallback and acceptItemCallback.
2659    WebInspector.TextPrompt.call(this, this._buildPropertyCompletions.bind(this), WebInspector.StyleValueDelimiters);
2660    this.setSuggestBoxEnabled("generic-suggest");
2661    this._cssCompletions = cssCompletions;
2662    this._sidebarPane = sidebarPane;
2663    this._isEditingName = isEditingName;
2664}
2665
2666WebInspector.StylesSidebarPane.CSSPropertyPrompt.prototype = {
2667    onKeyDown: function(event)
2668    {
2669        switch (event.keyIdentifier) {
2670        case "Up":
2671        case "Down":
2672        case "PageUp":
2673        case "PageDown":
2674            if (this._handleNameOrValueUpDown(event)) {
2675                event.preventDefault();
2676                return;
2677            }
2678            break;
2679        }
2680
2681        WebInspector.TextPrompt.prototype.onKeyDown.call(this, event);
2682    },
2683
2684    onMouseWheel: function(event)
2685    {
2686        if (this._handleNameOrValueUpDown(event)) {
2687            event.consume(true);
2688            return;
2689        }
2690        WebInspector.TextPrompt.prototype.onMouseWheel.call(this, event);
2691    },
2692
2693    tabKeyPressed: function()
2694    {
2695        this.acceptAutoComplete();
2696
2697        // Always tab to the next field.
2698        return false;
2699    },
2700
2701    _handleNameOrValueUpDown: function(event)
2702    {
2703        function finishHandler(originalValue, replacementString)
2704        {
2705            // Synthesize property text disregarding any comments, custom whitespace etc.
2706            this._sidebarPane.applyStyleText(this._sidebarPane.nameElement.textContent + ": " + this._sidebarPane.valueElement.textContent, false, false, false);
2707        }
2708
2709        // Handle numeric value increment/decrement only at this point.
2710        if (!this._isEditingName && WebInspector.handleElementValueModifications(event, this._sidebarPane.valueElement, finishHandler.bind(this), this._isValueSuggestion.bind(this)))
2711            return true;
2712
2713        return false;
2714    },
2715
2716    _isValueSuggestion: function(word)
2717    {
2718        if (!word)
2719            return false;
2720        word = word.toLowerCase();
2721        return this._cssCompletions.keySet().hasOwnProperty(word);
2722    },
2723
2724    /**
2725     * @param {Element} proxyElement
2726     * @param {Range} wordRange
2727     * @param {boolean} force
2728     * @param {function(!Array.<string>, number=)} completionsReadyCallback
2729     */
2730    _buildPropertyCompletions: function(proxyElement, wordRange, force, completionsReadyCallback)
2731    {
2732        var prefix = wordRange.toString().toLowerCase();
2733        if (!prefix && !force)
2734            return;
2735
2736        var results = this._cssCompletions.startsWith(prefix);
2737        var selectedIndex = this._cssCompletions.mostUsedOf(results);
2738        completionsReadyCallback(results, selectedIndex);
2739    },
2740
2741    __proto__: WebInspector.TextPrompt.prototype
2742}
2743