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(!WebInspector.DOMNode, string, boolean)=} setPseudoClassCallback
35 */
36WebInspector.StylesSidebarPane = function(computedStylePane, setPseudoClassCallback)
37{
38    WebInspector.SidebarPane.call(this, WebInspector.UIString("Styles"));
39
40    this._elementStateButton = document.createElement("button");
41    this._elementStateButton.className = "pane-title-button element-state";
42    this._elementStateButton.title = WebInspector.UIString("Toggle Element State");
43    this._elementStateButton.addEventListener("click", this._toggleElementStatePane.bind(this), false);
44    this.titleElement.appendChild(this._elementStateButton);
45
46    var addButton = document.createElement("button");
47    addButton.className = "pane-title-button add";
48    addButton.id = "add-style-button-test-id";
49    addButton.title = WebInspector.UIString("New Style Rule");
50    addButton.addEventListener("click", this._createNewRuleInViaInspectorStyleSheet.bind(this), false);
51    this.titleElement.appendChild(addButton);
52    addButton.createChild("div", "long-click-glyph fill");
53
54    this._addButtonLongClickController = new WebInspector.LongClickController(addButton);
55    this._addButtonLongClickController.addEventListener(WebInspector.LongClickController.Events.LongClick, this._onAddButtonLongClick.bind(this));
56    this._addButtonLongClickController.enable();
57
58    this._computedStylePane = computedStylePane;
59    computedStylePane.setHostingPane(this);
60    this._setPseudoClassCallback = setPseudoClassCallback;
61    this.element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true);
62    WebInspector.settings.colorFormat.addChangeListener(this._colorFormatSettingChanged.bind(this));
63    WebInspector.settings.showUserAgentStyles.addChangeListener(this._showUserAgentStylesSettingChanged.bind(this));
64    WebInspector.settings.showInheritedComputedStyleProperties.addChangeListener(this._showInheritedComputedStyleChanged.bind(this));
65
66    this._createElementStatePane();
67    this.bodyElement.appendChild(this._elementStatePane);
68    this._sectionsContainer = document.createElement("div");
69    this.bodyElement.appendChild(this._sectionsContainer);
70
71    this._spectrumHelper = new WebInspector.SpectrumPopupHelper();
72    this._linkifier = new WebInspector.Linkifier(new WebInspector.Linkifier.DefaultCSSFormatter());
73
74    this.element.classList.add("styles-pane");
75    this.element.classList.toggle("show-user-styles", WebInspector.settings.showUserAgentStyles.get());
76    this.element.addEventListener("mousemove", this._mouseMovedOverElement.bind(this), false);
77    document.body.addEventListener("keydown", this._keyDown.bind(this), false);
78    document.body.addEventListener("keyup", this._keyUp.bind(this), false);
79}
80
81// Keep in sync with RenderStyleConstants.h PseudoId enum. Array below contains pseudo id names for corresponding enum indexes.
82// First item is empty due to its artificial NOPSEUDO nature in the enum.
83// FIXME: find a way of generating this mapping or getting it from combination of RenderStyleConstants and CSSSelector.cpp at
84// runtime.
85WebInspector.StylesSidebarPane.PseudoIdNames = [
86    "", "first-line", "first-letter", "before", "after", "backdrop", "selection", "", "-webkit-scrollbar",
87    "-webkit-scrollbar-thumb", "-webkit-scrollbar-button", "-webkit-scrollbar-track", "-webkit-scrollbar-track-piece",
88    "-webkit-scrollbar-corner", "-webkit-resizer"
89];
90
91WebInspector.StylesSidebarPane._colorRegex = /((?:rgb|hsl)a?\([^)]+\)|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}|\b\w+\b(?!-))/g;
92
93/**
94 * @enum {string}
95 */
96WebInspector.StylesSidebarPane.Events = {
97    SelectorEditingStarted: "SelectorEditingStarted",
98    SelectorEditingEnded: "SelectorEditingEnded"
99};
100
101/**
102 * @param {!WebInspector.CSSProperty} property
103 * @return {!Element}
104 */
105WebInspector.StylesSidebarPane.createExclamationMark = function(property)
106{
107    var exclamationElement = document.createElement("div");
108    exclamationElement.className = "exclamation-mark" + (WebInspector.StylesSidebarPane._ignoreErrorsForProperty(property) ? "" : " warning-icon-small");
109    exclamationElement.title = WebInspector.CSSMetadata.cssPropertiesMetainfo.keySet()[property.name.toLowerCase()] ? WebInspector.UIString("Invalid property value.") : WebInspector.UIString("Unknown property name.");
110    return exclamationElement;
111}
112
113/**
114 * @param {!WebInspector.Color} color
115 */
116WebInspector.StylesSidebarPane._colorFormat = function(color)
117{
118    const cf = WebInspector.Color.Format;
119    var format;
120    var formatSetting = WebInspector.settings.colorFormat.get();
121    if (formatSetting === cf.Original)
122        format = cf.Original;
123    else if (formatSetting === cf.RGB)
124        format = (color.hasAlpha() ? cf.RGBA : cf.RGB);
125    else if (formatSetting === cf.HSL)
126        format = (color.hasAlpha() ? cf.HSLA : cf.HSL);
127    else if (!color.hasAlpha())
128        format = (color.canBeShortHex() ? cf.ShortHEX : cf.HEX);
129    else
130        format = cf.RGBA;
131
132    return format;
133}
134
135/**
136 * @param {!WebInspector.CSSProperty} property
137 */
138WebInspector.StylesSidebarPane._ignoreErrorsForProperty = function(property) {
139    function hasUnknownVendorPrefix(string)
140    {
141        return !string.startsWith("-webkit-") && /^[-_][\w\d]+-\w/.test(string);
142    }
143
144    var name = property.name.toLowerCase();
145
146    // IE hack.
147    if (name.charAt(0) === "_")
148        return true;
149
150    // IE has a different format for this.
151    if (name === "filter")
152        return true;
153
154    // Common IE-specific property prefix.
155    if (name.startsWith("scrollbar-"))
156        return true;
157    if (hasUnknownVendorPrefix(name))
158        return true;
159
160    var value = property.value.toLowerCase();
161
162    // IE hack.
163    if (value.endsWith("\9"))
164        return true;
165    if (hasUnknownVendorPrefix(value))
166        return true;
167
168    return false;
169}
170
171WebInspector.StylesSidebarPane.prototype = {
172    _showInheritedComputedStyleChanged: function()
173    {
174        if (!this.sections || !this.sections[0])
175            return;
176        for (var i = 0; i < this.sections[0].length; ++i) {
177            var section = this.sections[0][i];
178            if (section instanceof WebInspector.ComputedStylePropertiesSection)
179                section.onShowInheritedChanged();
180        }
181    },
182
183    /**
184     * @param {!WebInspector.Event} event
185     */
186    _onAddButtonLongClick: function(event)
187    {
188        this._addButtonLongClickController.reset();
189        var cssModel = this._target.cssModel;
190        var headers = cssModel.styleSheetHeaders().filter(styleSheetResourceHeader);
191
192        /** @type {!Array.<{text: string, handler: function()}>} */
193        var contextMenuDescriptors = [];
194        for (var i = 0; i < headers.length; ++i) {
195            var header = headers[i];
196            var handler = this._createNewRuleInStyleSheet.bind(this, header);
197            contextMenuDescriptors.push({
198                text: WebInspector.displayNameForURL(header.resourceURL()),
199                handler: handler
200            });
201        }
202
203        contextMenuDescriptors.sort(compareDescriptors);
204
205        var contextMenu = new WebInspector.ContextMenu(/** @type {!Event} */(event.data));
206        for (var i = 0; i < contextMenuDescriptors.length; ++i) {
207            var descriptor = contextMenuDescriptors[i];
208            contextMenu.appendItem(descriptor.text, descriptor.handler);
209        }
210        if (!contextMenu.isEmpty())
211            contextMenu.appendSeparator();
212        contextMenu.appendItem("inspector-stylesheet", this._createNewRuleInViaInspectorStyleSheet.bind(this));
213        contextMenu.show();
214
215        /**
216         * @param {!{text: string, handler: function()}} descriptor1
217         * @param {!{text: string, handler: function()}} descriptor2
218         * @return {number}
219         */
220        function compareDescriptors(descriptor1, descriptor2)
221        {
222            return String.naturalOrderComparator(descriptor1.text, descriptor2.text);
223        }
224
225        /**
226         * @param {!WebInspector.CSSStyleSheetHeader} header
227         */
228        function styleSheetResourceHeader(header)
229        {
230            return !header.isViaInspector() && !header.isInline && header.resourceURL();
231        }
232    },
233
234    /**
235     * @param {!WebInspector.DOMNode} node
236     */
237    updateEditingSelectorForNode: function(node)
238    {
239        var selectorText = WebInspector.DOMPresentationUtils.simpleSelector(node);
240        if (!selectorText)
241            return;
242        this._editingSelectorSection.setSelectorText(selectorText);
243    },
244
245    /**
246     * @return {boolean}
247     */
248    isEditingSelector: function()
249    {
250        return !!this._editingSelectorSection;
251    },
252
253    /**
254     * @param {!WebInspector.StylePropertiesSection} section
255     */
256    _startEditingSelector: function(section)
257    {
258        this._editingSelectorSection = section;
259        this.dispatchEventToListeners(WebInspector.StylesSidebarPane.Events.SelectorEditingStarted);
260    },
261
262    _finishEditingSelector: function()
263    {
264        delete this._editingSelectorSection;
265        this.dispatchEventToListeners(WebInspector.StylesSidebarPane.Events.SelectorEditingEnded);
266    },
267
268    /**
269     * @param {!WebInspector.CSSRule} editedRule
270     * @param {!WebInspector.TextRange} oldRange
271     * @param {!WebInspector.TextRange} newRange
272     */
273    _styleSheetRuleEdited: function(editedRule, oldRange, newRange)
274    {
275        for (var pseudoId in this.sections) {
276            var styleRuleSections = this.sections[pseudoId];
277            for (var i = 0; i < styleRuleSections.length; ++i) {
278                var section = styleRuleSections[i];
279                if (section.computedStyle)
280                    continue;
281                section._styleSheetRuleEdited(editedRule, oldRange, newRange);
282            }
283        }
284    },
285
286    /**
287     * @param {!Event} event
288     */
289    _contextMenuEventFired: function(event)
290    {
291        // We start editing upon click -> default navigation to resources panel is not available
292        // Hence we add a soft context menu for hrefs.
293        var contextMenu = new WebInspector.ContextMenu(event);
294        contextMenu.appendApplicableItems(/** @type {!Node} */ (event.target));
295        contextMenu.show();
296    },
297
298    /**
299     * @param {!Element} matchedStylesElement
300     * @param {!Element} computedStylesElement
301     */
302    setFilterBoxContainers: function(matchedStylesElement, computedStylesElement)
303    {
304        matchedStylesElement.appendChild(this._createCSSFilterControl());
305        this._computedStylePane.setFilterBoxContainer(computedStylesElement);
306    },
307
308    /**
309     * @return {!Element}
310     */
311    _createCSSFilterControl: function()
312    {
313        var filterInput = this._createPropertyFilterElement(false, searchHandler.bind(this));
314
315        /**
316         * @param {?RegExp} regex
317         * @this {WebInspector.StylesSidebarPane}
318         */
319        function searchHandler(regex)
320        {
321            this._filterRegex = regex;
322        }
323
324        return filterInput;
325    },
326
327    get _forcedPseudoClasses()
328    {
329        return this._node ? (this._node.getUserProperty(WebInspector.CSSStyleModel.PseudoStatePropertyName) || undefined) : undefined;
330    },
331
332    _updateForcedPseudoStateInputs: function()
333    {
334        if (!this._node)
335            return;
336
337        var hasPseudoType = !!this._node.pseudoType();
338        this._elementStateButton.classList.toggle("hidden", hasPseudoType);
339        this._elementStatePane.classList.toggle("expanded", !hasPseudoType && this._elementStateButton.classList.contains("toggled"));
340
341        var nodePseudoState = this._forcedPseudoClasses;
342        if (!nodePseudoState)
343            nodePseudoState = [];
344
345        var inputs = this._elementStatePane.inputs;
346        for (var i = 0; i < inputs.length; ++i)
347            inputs[i].checked = nodePseudoState.indexOf(inputs[i].state) >= 0;
348    },
349
350    /**
351     * @param {?WebInspector.DOMNode} node
352     * @param {boolean=} forceUpdate
353     */
354    update: function(node, forceUpdate)
355    {
356        this._spectrumHelper.hide();
357        this._discardElementUnderMouse();
358
359        var refresh = false;
360
361        if (forceUpdate)
362            delete this._node;
363
364        if (!forceUpdate && (node === this._node))
365            refresh = true;
366
367        if (node && node.nodeType() === Node.TEXT_NODE && node.parentNode)
368            node = node.parentNode;
369
370        if (node && node.nodeType() !== Node.ELEMENT_NODE)
371            node = null;
372
373        if (node) {
374            this._updateTarget(node.target());
375            this._node = node;
376        } else
377            node = this._node;
378
379        this._updateForcedPseudoStateInputs();
380
381        if (refresh)
382            this._refreshUpdate();
383        else
384            this._rebuildUpdate();
385    },
386
387    /**
388     * @param {!WebInspector.Target} target
389     */
390    _updateTarget: function(target)
391    {
392        if (this._target === target)
393            return;
394        if (this._target) {
395            this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this._styleSheetOrMediaQueryResultChanged, this);
396            this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._styleSheetOrMediaQueryResultChanged, this);
397            this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetOrMediaQueryResultChanged, this);
398            this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.MediaQueryResultChanged, this._styleSheetOrMediaQueryResultChanged, this);
399            this._target.domModel.removeEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributeChanged, this);
400            this._target.domModel.removeEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributeChanged, this);
401            this._target.resourceTreeModel.removeEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameResized, this._frameResized, this);
402        }
403        this._target = target;
404        this._target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this._styleSheetOrMediaQueryResultChanged, this);
405        this._target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._styleSheetOrMediaQueryResultChanged, this);
406        this._target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetOrMediaQueryResultChanged, this);
407        this._target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.MediaQueryResultChanged, this._styleSheetOrMediaQueryResultChanged, this);
408        this._target.domModel.addEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributeChanged, this);
409        this._target.domModel.addEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributeChanged, this);
410        this._target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameResized, this._frameResized, this);
411    },
412
413    /**
414     * @param {!WebInspector.StylePropertiesSection=} editedSection
415     * @param {boolean=} forceFetchComputedStyle
416     * @param {function()=} userCallback
417     */
418    _refreshUpdate: function(editedSection, forceFetchComputedStyle, userCallback)
419    {
420        var callbackWrapper = function()
421        {
422            if (this._filterRegex)
423                this._updateFilter(false);
424            if (userCallback)
425                userCallback();
426        }.bind(this);
427
428        if (this._refreshUpdateInProgress) {
429            this._lastNodeForInnerRefresh = this._node;
430            return;
431        }
432
433        var node = this._validateNode(userCallback);
434        if (!node)
435            return;
436
437        /**
438         * @param {?WebInspector.CSSStyleDeclaration} computedStyle
439         * @this {WebInspector.StylesSidebarPane}
440         */
441        function computedStyleCallback(computedStyle)
442        {
443            delete this._refreshUpdateInProgress;
444
445            if (this._lastNodeForInnerRefresh) {
446                delete this._lastNodeForInnerRefresh;
447                this._refreshUpdate(editedSection, forceFetchComputedStyle, callbackWrapper);
448                return;
449            }
450
451            if (this._node === node && computedStyle)
452                this._innerRefreshUpdate(node, computedStyle, editedSection);
453
454            callbackWrapper();
455        }
456
457        if (this._computedStylePane.isShowing() || forceFetchComputedStyle) {
458            this._refreshUpdateInProgress = true;
459            this._target.cssModel.getComputedStyleAsync(node.id, computedStyleCallback.bind(this));
460        } else {
461            this._innerRefreshUpdate(node, null, editedSection);
462            callbackWrapper();
463        }
464    },
465
466    _rebuildUpdate: function()
467    {
468        if (this._rebuildUpdateInProgress) {
469            this._lastNodeForInnerRebuild = this._node;
470            return;
471        }
472
473        var node = this._validateNode();
474        if (!node)
475            return;
476
477        this._rebuildUpdateInProgress = true;
478
479        var resultStyles = {};
480
481        /**
482         * @param {?*} matchedResult
483         * @this {WebInspector.StylesSidebarPane}
484         */
485        function stylesCallback(matchedResult)
486        {
487            delete this._rebuildUpdateInProgress;
488
489            var lastNodeForRebuild = this._lastNodeForInnerRebuild;
490            if (lastNodeForRebuild) {
491                delete this._lastNodeForInnerRebuild;
492                if (lastNodeForRebuild !== this._node) {
493                    this._rebuildUpdate();
494                    return;
495                }
496            }
497
498            if (matchedResult && this._node === node) {
499                resultStyles.matchedCSSRules = matchedResult.matchedCSSRules;
500                resultStyles.pseudoElements = matchedResult.pseudoElements;
501                resultStyles.inherited = matchedResult.inherited;
502                this._innerRebuildUpdate(node, resultStyles);
503            }
504
505            if (lastNodeForRebuild) {
506                // lastNodeForRebuild is the same as this.node - another rebuild has been requested.
507                this._rebuildUpdate();
508                return;
509            }
510            if (matchedResult && this._node === node)
511                this._nodeStylesUpdatedForTest(node, true);
512        }
513
514        /**
515         * @param {?WebInspector.CSSStyleDeclaration} inlineStyle
516         * @param {?WebInspector.CSSStyleDeclaration} attributesStyle
517         */
518        function inlineCallback(inlineStyle, attributesStyle)
519        {
520            resultStyles.inlineStyle = inlineStyle;
521            resultStyles.attributesStyle = attributesStyle;
522        }
523
524        /**
525         * @param {?WebInspector.CSSStyleDeclaration} computedStyle
526         */
527        function computedCallback(computedStyle)
528        {
529            resultStyles.computedStyle = computedStyle;
530        }
531
532        if (this._computedStylePane.isShowing())
533            this._target.cssModel.getComputedStyleAsync(node.id, computedCallback);
534        this._target.cssModel.getInlineStylesAsync(node.id, inlineCallback);
535        this._target.cssModel.getMatchedStylesAsync(node.id, false, false, stylesCallback.bind(this));
536    },
537
538    /**
539     * @param {function()=} userCallback
540     */
541    _validateNode: function(userCallback)
542    {
543        if (!this._node) {
544            this._sectionsContainer.removeChildren();
545            this._computedStylePane.bodyElement.removeChildren();
546            this.sections = {};
547            if (userCallback)
548                userCallback();
549            return null;
550        }
551        return this._node;
552    },
553
554    _styleSheetOrMediaQueryResultChanged: function()
555    {
556        if (this._userOperation || this._isEditingStyle)
557            return;
558
559        this._rebuildUpdate();
560    },
561
562    _frameResized: function()
563    {
564        /**
565         * @this {WebInspector.StylesSidebarPane}
566         */
567        function refreshContents()
568        {
569            this._styleSheetOrMediaQueryResultChanged();
570            delete this._activeTimer;
571        }
572
573        if (this._activeTimer)
574            clearTimeout(this._activeTimer);
575
576        this._activeTimer = setTimeout(refreshContents.bind(this), 100);
577    },
578
579    _attributeChanged: function(event)
580    {
581        // Any attribute removal or modification can affect the styles of "related" nodes.
582        // Do not touch the styles if they are being edited.
583        if (this._isEditingStyle || this._userOperation)
584            return;
585
586        if (!this._canAffectCurrentStyles(event.data.node))
587            return;
588
589        this._rebuildUpdate();
590    },
591
592    _canAffectCurrentStyles: function(node)
593    {
594        return this._node && (this._node === node || node.parentNode === this._node.parentNode || node.isAncestor(this._node));
595    },
596
597    _innerRefreshUpdate: function(node, computedStyle, editedSection)
598    {
599        for (var pseudoId in this.sections) {
600            var styleRules = this._refreshStyleRules(this.sections[pseudoId], computedStyle);
601            var usedProperties = {};
602            this._markUsedProperties(styleRules, usedProperties);
603            this._refreshSectionsForStyleRules(styleRules, usedProperties, editedSection);
604        }
605        if (computedStyle)
606            this.sections[0][0].rebuildComputedTrace(this.sections[0]);
607
608        this._nodeStylesUpdatedForTest(node, false);
609    },
610
611    _innerRebuildUpdate: function(node, styles)
612    {
613        this._sectionsContainer.removeChildren();
614        this._computedStylePane.bodyElement.removeChildren();
615        this._linkifier.reset();
616
617        var styleRules = this._rebuildStyleRules(node, styles);
618        var usedProperties = {};
619        this._markUsedProperties(styleRules, usedProperties);
620        this.sections[0] = this._rebuildSectionsForStyleRules(styleRules, usedProperties, null);
621        var anchorElement = this.sections[0].inheritedPropertiesSeparatorElement;
622
623        if (styles.computedStyle)
624            this.sections[0][0].rebuildComputedTrace(this.sections[0]);
625
626        for (var i = 0; i < styles.pseudoElements.length; ++i) {
627            var pseudoElementCSSRules = styles.pseudoElements[i];
628
629            styleRules = [];
630            var pseudoId = pseudoElementCSSRules.pseudoId;
631
632            var entry = { isStyleSeparator: true, pseudoId: pseudoId };
633            styleRules.push(entry);
634
635            // Add rules in reverse order to match the cascade order.
636            for (var j = pseudoElementCSSRules.rules.length - 1; j >= 0; --j) {
637                var rule = pseudoElementCSSRules.rules[j];
638                styleRules.push({ style: rule.style, selectorText: rule.selectorText, media: rule.media, rule: rule, editable: !!(rule.style && rule.style.styleSheetId) });
639            }
640            usedProperties = {};
641            this._markUsedProperties(styleRules, usedProperties);
642            this.sections[pseudoId] = this._rebuildSectionsForStyleRules(styleRules, usedProperties, anchorElement);
643        }
644
645        if (this._filterRegex)
646            this._updateFilter(false);
647    },
648
649    _nodeStylesUpdatedForTest: function(node, rebuild)
650    {
651        // Tests override this method.
652    },
653
654    _refreshStyleRules: function(sections, computedStyle)
655    {
656        var nodeComputedStyle = computedStyle;
657        var styleRules = [];
658        for (var i = 0; sections && i < sections.length; ++i) {
659            var section = sections[i];
660            if (section.isBlank)
661                continue;
662            if (section.computedStyle)
663                section.styleRule.style = nodeComputedStyle;
664            var styleRule = { section: section, style: section.styleRule.style, computedStyle: section.computedStyle, rule: section.rule, editable: !!(section.styleRule.style && section.styleRule.style.styleSheetId),
665                isAttribute: section.styleRule.isAttribute, isInherited: section.styleRule.isInherited, parentNode: section.styleRule.parentNode };
666            styleRules.push(styleRule);
667        }
668        return styleRules;
669    },
670
671    _rebuildStyleRules: function(node, styles)
672    {
673        var nodeComputedStyle = styles.computedStyle;
674        this.sections = {};
675
676        var styleRules = [];
677
678        function addAttributesStyle()
679        {
680            if (!styles.attributesStyle)
681                return;
682            var attrStyle = { style: styles.attributesStyle, editable: false };
683            attrStyle.selectorText = node.nodeNameInCorrectCase() + "[" + WebInspector.UIString("Attributes Style") + "]";
684            styleRules.push(attrStyle);
685        }
686
687        styleRules.push({ computedStyle: true, selectorText: "", style: nodeComputedStyle, editable: false });
688
689        if (!!node.pseudoType())
690            styleRules.push({ isStyleSeparator: true, isPlaceholder: true });
691
692        // Inline style has the greatest specificity.
693        if (styles.inlineStyle && node.nodeType() === Node.ELEMENT_NODE) {
694            var inlineStyle = { selectorText: "element.style", style: styles.inlineStyle, isAttribute: true };
695            styleRules.push(inlineStyle);
696        }
697
698        // Add rules in reverse order to match the cascade order.
699        var addedAttributesStyle;
700        for (var i = styles.matchedCSSRules.length - 1; i >= 0; --i) {
701            var rule = styles.matchedCSSRules[i];
702            if ((rule.isUser || rule.isUserAgent) && !addedAttributesStyle) {
703                // Show element's Style Attributes after all author rules.
704                addedAttributesStyle = true;
705                addAttributesStyle();
706            }
707            styleRules.push({ style: rule.style, selectorText: rule.selectorText, media: rule.media, rule: rule, editable: !!(rule.style && rule.style.styleSheetId) });
708        }
709
710        if (!addedAttributesStyle)
711            addAttributesStyle();
712
713        // Walk the node structure and identify styles with inherited properties.
714        var parentNode = node.parentNode;
715        function insertInheritedNodeSeparator(node)
716        {
717            var entry = {};
718            entry.isStyleSeparator = true;
719            entry.node = node;
720            styleRules.push(entry);
721        }
722
723        for (var parentOrdinal = 0; parentOrdinal < styles.inherited.length; ++parentOrdinal) {
724            var parentStyles = styles.inherited[parentOrdinal];
725            var separatorInserted = false;
726            if (parentStyles.inlineStyle) {
727                if (this._containsInherited(parentStyles.inlineStyle)) {
728                    var inlineStyle = { selectorText: WebInspector.UIString("Style Attribute"), style: parentStyles.inlineStyle, isAttribute: true, isInherited: true, parentNode: parentNode };
729                    if (!separatorInserted) {
730                        insertInheritedNodeSeparator(parentNode);
731                        separatorInserted = true;
732                    }
733                    styleRules.push(inlineStyle);
734                }
735            }
736
737            for (var i = parentStyles.matchedCSSRules.length - 1; i >= 0; --i) {
738                var rulePayload = parentStyles.matchedCSSRules[i];
739                if (!this._containsInherited(rulePayload.style))
740                    continue;
741                var rule = rulePayload;
742
743                if (!separatorInserted) {
744                    insertInheritedNodeSeparator(parentNode);
745                    separatorInserted = true;
746                }
747                styleRules.push({ style: rule.style, selectorText: rule.selectorText, media: rule.media, rule: rule, isInherited: true, parentNode: parentNode, editable: !!(rule.style && rule.style.styleSheetId) });
748            }
749            parentNode = parentNode.parentNode;
750        }
751        return styleRules;
752    },
753
754    _markUsedProperties: function(styleRules, usedProperties)
755    {
756        var foundImportantProperties = {};
757        var propertyToEffectiveRule = {};
758        var inheritedPropertyToNode = {};
759        for (var i = 0; i < styleRules.length; ++i) {
760            var styleRule = styleRules[i];
761            if (styleRule.computedStyle || styleRule.isStyleSeparator)
762                continue;
763            if (styleRule.section && styleRule.section.noAffect)
764                continue;
765
766            styleRule.usedProperties = {};
767
768            var style = styleRule.style;
769            var allProperties = style.allProperties;
770            for (var j = 0; j < allProperties.length; ++j) {
771                var property = allProperties[j];
772                if (!property.isLive || !property.parsedOk)
773                    continue;
774
775                // Do not pick non-inherited properties from inherited styles.
776                if (styleRule.isInherited && !WebInspector.CSSMetadata.isPropertyInherited(property.name))
777                    continue;
778
779                var canonicalName = WebInspector.CSSMetadata.canonicalPropertyName(property.name);
780                if (foundImportantProperties.hasOwnProperty(canonicalName))
781                    continue;
782
783                if (!property.important && usedProperties.hasOwnProperty(canonicalName))
784                    continue;
785
786                var isKnownProperty = propertyToEffectiveRule.hasOwnProperty(canonicalName);
787                if (!isKnownProperty && styleRule.isInherited && !inheritedPropertyToNode[canonicalName])
788                    inheritedPropertyToNode[canonicalName] = styleRule.parentNode;
789
790                if (property.important) {
791                    if (styleRule.isInherited && isKnownProperty && styleRule.parentNode !== inheritedPropertyToNode[canonicalName])
792                        continue;
793
794                    foundImportantProperties[canonicalName] = true;
795                    if (isKnownProperty)
796                        delete propertyToEffectiveRule[canonicalName].usedProperties[canonicalName];
797                }
798
799                styleRule.usedProperties[canonicalName] = true;
800                usedProperties[canonicalName] = true;
801                propertyToEffectiveRule[canonicalName] = styleRule;
802            }
803        }
804    },
805
806    _refreshSectionsForStyleRules: function(styleRules, usedProperties, editedSection)
807    {
808        // Walk the style rules and update the sections with new overloaded and used properties.
809        for (var i = 0; i < styleRules.length; ++i) {
810            var styleRule = styleRules[i];
811            var section = styleRule.section;
812            if (styleRule.computedStyle) {
813                section._usedProperties = usedProperties;
814                section.update();
815            } else {
816                section._usedProperties = styleRule.usedProperties;
817                section.update(section === editedSection);
818            }
819        }
820    },
821
822    /**
823     * @param {!Array.<!Object>} styleRules
824     * @param {!Object.<string, boolean>} usedProperties
825     * @param {?Element} anchorElement
826     */
827    _rebuildSectionsForStyleRules: function(styleRules, usedProperties, anchorElement)
828    {
829        // Make a property section for each style rule.
830        var sections = [];
831        for (var i = 0; i < styleRules.length; ++i) {
832            var styleRule = styleRules[i];
833            if (styleRule.isStyleSeparator) {
834                var separatorElement = document.createElement("div");
835                if (styleRule.isPlaceholder) {
836                    separatorElement.className = "styles-sidebar-placeholder";
837                    this._sectionsContainer.insertBefore(separatorElement, anchorElement);
838                    continue;
839                }
840                separatorElement.className = "sidebar-separator";
841                if (styleRule.node) {
842                    var link = WebInspector.DOMPresentationUtils.linkifyNodeReference(styleRule.node);
843                    separatorElement.createTextChild(WebInspector.UIString("Inherited from") + " ");
844                    separatorElement.appendChild(link);
845                    if (!sections.inheritedPropertiesSeparatorElement)
846                        sections.inheritedPropertiesSeparatorElement = separatorElement;
847                } else if ("pseudoId" in styleRule) {
848                    var pseudoName = WebInspector.StylesSidebarPane.PseudoIdNames[styleRule.pseudoId];
849                    if (pseudoName)
850                        separatorElement.textContent = WebInspector.UIString("Pseudo ::%s element", pseudoName);
851                    else
852                        separatorElement.textContent = WebInspector.UIString("Pseudo element");
853                } else
854                    separatorElement.textContent = styleRule.text;
855                this._sectionsContainer.insertBefore(separatorElement, anchorElement);
856                continue;
857            }
858            var computedStyle = styleRule.computedStyle;
859
860            // Default editable to true if it was omitted.
861            var editable = styleRule.editable;
862            if (typeof editable === "undefined")
863                editable = true;
864
865            if (computedStyle)
866                var section = new WebInspector.ComputedStylePropertiesSection(this, styleRule, usedProperties);
867            else {
868                var section = new WebInspector.StylePropertiesSection(this, styleRule, editable, styleRule.isInherited);
869                section._markSelectorMatches();
870            }
871            section.expanded = true;
872
873            if (computedStyle)
874                this._computedStylePane.bodyElement.appendChild(section.element);
875            else
876                this._sectionsContainer.insertBefore(section.element, anchorElement);
877            sections.push(section);
878        }
879        return sections;
880    },
881
882    _containsInherited: function(style)
883    {
884        var properties = style.allProperties;
885        for (var i = 0; i < properties.length; ++i) {
886            var property = properties[i];
887            // Does this style contain non-overridden inherited property?
888            if (property.isLive && WebInspector.CSSMetadata.isPropertyInherited(property.name))
889                return true;
890        }
891        return false;
892    },
893
894    _colorFormatSettingChanged: function(event)
895    {
896        for (var pseudoId in this.sections) {
897            var sections = this.sections[pseudoId];
898            for (var i = 0; i < sections.length; ++i)
899                sections[i].update(true);
900        }
901    },
902
903    /**
904     * @param {?Event} event
905     */
906    _createNewRuleInViaInspectorStyleSheet: function(event)
907    {
908        var cssModel = this._target.cssModel;
909        cssModel.requestViaInspectorStylesheet(this._node, this._createNewRuleInStyleSheet.bind(this));
910    },
911
912    /**
913     * @param {?WebInspector.CSSStyleSheetHeader} styleSheetHeader
914     */
915    _createNewRuleInStyleSheet: function(styleSheetHeader)
916    {
917        if (!styleSheetHeader)
918            return;
919        styleSheetHeader.requestContent(onStyleSheetContent.bind(this, styleSheetHeader.id));
920
921        /**
922         * @param {string} styleSheetId
923         * @param {string} text
924         * @this {WebInspector.StylesSidebarPane}
925         */
926        function onStyleSheetContent(styleSheetId, text)
927        {
928            var lines = text.split("\n");
929            var range = WebInspector.TextRange.createFromLocation(lines.length - 1, lines[lines.length - 1].length);
930            this._addBlankSection(this.sections[0][1], styleSheetId, range);
931        }
932    },
933
934    /**
935     * @param {!WebInspector.StylePropertiesSection} insertAfterSection
936     * @param {string} styleSheetId
937     * @param {!WebInspector.TextRange} ruleLocation
938     */
939    _addBlankSection: function(insertAfterSection, styleSheetId, ruleLocation)
940    {
941        this.expand();
942        var blankSection = new WebInspector.BlankStylePropertiesSection(this, this._node ? WebInspector.DOMPresentationUtils.simpleSelector(this._node) : "", styleSheetId, ruleLocation, insertAfterSection.rule);
943
944        this._sectionsContainer.insertBefore(blankSection.element, insertAfterSection.element.nextSibling);
945
946        var index = this.sections[0].indexOf(insertAfterSection);
947        this.sections[0].splice(index + 1, 0, blankSection);
948        blankSection.startEditingSelector();
949    },
950
951    removeSection: function(section)
952    {
953        for (var pseudoId in this.sections) {
954            var sections = this.sections[pseudoId];
955            var index = sections.indexOf(section);
956            if (index === -1)
957                continue;
958            sections.splice(index, 1);
959            section.element.remove();
960        }
961    },
962
963    _toggleElementStatePane: function(event)
964    {
965        event.consume();
966
967        var buttonToggled = !this._elementStateButton.classList.contains("toggled");
968        if (buttonToggled)
969            this.expand();
970        this._elementStateButton.classList.toggle("toggled", buttonToggled);
971        this._elementStatePane.classList.toggle("expanded", buttonToggled);
972    },
973
974    _createElementStatePane: function()
975    {
976        this._elementStatePane = document.createElement("div");
977        this._elementStatePane.className = "styles-element-state-pane source-code";
978        var table = document.createElement("table");
979
980        var inputs = [];
981        this._elementStatePane.inputs = inputs;
982
983        /**
984         * @param {!Event} event
985         * @this {WebInspector.StylesSidebarPane}
986         */
987        function clickListener(event)
988        {
989            var node = this._validateNode();
990            if (!node)
991                return;
992            this._setPseudoClassCallback(node, event.target.state, event.target.checked);
993        }
994
995        /**
996         * @param {string} state
997         * @return {!Element}
998         * @this {WebInspector.StylesSidebarPane}
999         */
1000        function createCheckbox(state)
1001        {
1002            var td = document.createElement("td");
1003            var label = document.createElement("label");
1004            var input = document.createElement("input");
1005            input.type = "checkbox";
1006            input.state = state;
1007            input.addEventListener("click", clickListener.bind(this), false);
1008            inputs.push(input);
1009            label.appendChild(input);
1010            label.createTextChild(":" + state);
1011            td.appendChild(label);
1012            return td;
1013        }
1014
1015        var tr = table.createChild("tr");
1016        tr.appendChild(createCheckbox.call(this, "active"));
1017        tr.appendChild(createCheckbox.call(this, "hover"));
1018
1019        tr = table.createChild("tr");
1020        tr.appendChild(createCheckbox.call(this, "focus"));
1021        tr.appendChild(createCheckbox.call(this, "visited"));
1022
1023        this._elementStatePane.appendChild(table);
1024    },
1025
1026    /**
1027     * @return {?RegExp}
1028     */
1029    filterRegex: function()
1030    {
1031        return this._filterRegex;
1032    },
1033
1034    /**
1035     * @param {boolean} isComputedStyleFilter
1036     * @return {!Element}
1037     * @param {function(?RegExp)} filterCallback
1038     */
1039    _createPropertyFilterElement: function(isComputedStyleFilter, filterCallback)
1040    {
1041        var input = document.createElement("input");
1042        input.type = "text";
1043        input.placeholder = isComputedStyleFilter ? WebInspector.UIString("Filter") : WebInspector.UIString("Find in Styles");
1044        var boundSearchHandler = searchHandler.bind(this);
1045
1046        /**
1047         * @this {WebInspector.StylesSidebarPane}
1048         */
1049        function searchHandler()
1050        {
1051            var regex = input.value ? new RegExp(input.value.escapeForRegExp(), "i") : null;
1052            filterCallback(regex);
1053            input.parentNode.classList.toggle("styles-filter-engaged", !!input.value);
1054            this._updateFilter(isComputedStyleFilter);
1055        }
1056        input.addEventListener("input", boundSearchHandler, false);
1057
1058        /**
1059         * @param {!Event} event
1060         */
1061        function keydownHandler(event)
1062        {
1063            var Esc = "U+001B";
1064            if (event.keyIdentifier !== Esc || !input.value)
1065                return;
1066            event.consume(true);
1067            input.value = "";
1068            boundSearchHandler();
1069        }
1070        input.addEventListener("keydown", keydownHandler, false);
1071
1072        return input;
1073    },
1074
1075    /**
1076     * @param {boolean} isComputedStyleFilter
1077     */
1078    _updateFilter: function(isComputedStyleFilter)
1079    {
1080        for (var pseudoId in this.sections) {
1081            var sections = this.sections[pseudoId];
1082            for (var i = 0; i < sections.length; ++i) {
1083                var section = sections[i];
1084                if (isComputedStyleFilter !== !!section.computedStyle)
1085                    continue;
1086                section._updateFilter();
1087            }
1088        }
1089    },
1090
1091    /**
1092     * @param {!WebInspector.Event} event
1093     */
1094    _showUserAgentStylesSettingChanged: function(event)
1095    {
1096        var showStyles = /** @type {boolean} */ (event.data);
1097        this.element.classList.toggle("show-user-styles", showStyles);
1098    },
1099
1100    willHide: function()
1101    {
1102        this._spectrumHelper.hide();
1103        this._discardElementUnderMouse();
1104    },
1105
1106    _discardElementUnderMouse: function()
1107    {
1108        if (this._elementUnderMouse)
1109            this._elementUnderMouse.classList.remove("styles-panel-hovered");
1110        delete this._elementUnderMouse;
1111    },
1112
1113    _mouseMovedOverElement: function(e)
1114    {
1115        if (this._elementUnderMouse && e.target !== this._elementUnderMouse)
1116            this._discardElementUnderMouse();
1117        this._elementUnderMouse = e.target;
1118        if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(e))
1119            this._elementUnderMouse.classList.add("styles-panel-hovered");
1120    },
1121
1122    _keyDown: function(e)
1123    {
1124        if ((!WebInspector.isMac() && e.keyCode === WebInspector.KeyboardShortcut.Keys.Ctrl.code) ||
1125            (WebInspector.isMac() && e.keyCode === WebInspector.KeyboardShortcut.Keys.Meta.code)) {
1126            if (this._elementUnderMouse)
1127                this._elementUnderMouse.classList.add("styles-panel-hovered");
1128        }
1129    },
1130
1131    _keyUp: function(e)
1132    {
1133        if ((!WebInspector.isMac() && e.keyCode === WebInspector.KeyboardShortcut.Keys.Ctrl.code) ||
1134            (WebInspector.isMac() && e.keyCode === WebInspector.KeyboardShortcut.Keys.Meta.code)) {
1135            this._discardElementUnderMouse();
1136        }
1137    },
1138
1139    __proto__: WebInspector.SidebarPane.prototype
1140}
1141
1142/**
1143 * @constructor
1144 * @extends {WebInspector.SidebarPane}
1145 */
1146WebInspector.ComputedStyleSidebarPane = function()
1147{
1148    WebInspector.SidebarPane.call(this, WebInspector.UIString("Computed Style"));
1149}
1150
1151WebInspector.ComputedStyleSidebarPane.prototype = {
1152    /**
1153     * @param {!WebInspector.StylesSidebarPane} pane
1154     */
1155    setHostingPane: function(pane)
1156    {
1157        this._stylesSidebarPane = pane;
1158    },
1159
1160    setFilterBoxContainer: function(element)
1161    {
1162        element.appendChild(this._stylesSidebarPane._createPropertyFilterElement(true, filterCallback.bind(this)));
1163
1164        /**
1165         * @param {?RegExp} regex
1166         * @this {WebInspector.ComputedStyleSidebarPane}
1167         */
1168        function filterCallback(regex)
1169        {
1170            this._filterRegex = regex;
1171        }
1172    },
1173
1174    wasShown: function()
1175    {
1176        WebInspector.SidebarPane.prototype.wasShown.call(this);
1177        if (!this._hasFreshContent)
1178            this.prepareContent();
1179    },
1180
1181    /**
1182     * @param {function()=} callback
1183     */
1184    prepareContent: function(callback)
1185    {
1186        /**
1187         * @this {WebInspector.ComputedStyleSidebarPane}
1188         */
1189        function wrappedCallback() {
1190            this._hasFreshContent = true;
1191            if (callback)
1192                callback();
1193            delete this._hasFreshContent;
1194        }
1195        this._stylesSidebarPane._refreshUpdate(null, true, wrappedCallback.bind(this));
1196    },
1197
1198    /**
1199     * @return {?RegExp}
1200     */
1201    filterRegex: function()
1202    {
1203        return this._filterRegex;
1204    },
1205
1206    __proto__: WebInspector.SidebarPane.prototype
1207}
1208
1209/**
1210 * @constructor
1211 * @extends {WebInspector.PropertiesSection}
1212 * @param {!WebInspector.StylesSidebarPane} parentPane
1213 * @param {!Object} styleRule
1214 * @param {boolean} editable
1215 * @param {boolean} isInherited
1216 */
1217WebInspector.StylePropertiesSection = function(parentPane, styleRule, editable, isInherited)
1218{
1219    WebInspector.PropertiesSection.call(this, "");
1220
1221    this._parentPane = parentPane;
1222    this.styleRule = styleRule;
1223    this.rule = this.styleRule.rule;
1224    this.editable = editable;
1225    this.isInherited = isInherited;
1226
1227    var extraClasses = (this.rule && (this.rule.isUser || this.rule.isUserAgent) ? " user-rule" : "");
1228    this.element.className = "styles-section matched-styles monospace" + extraClasses;
1229    // We don't really use properties' disclosure.
1230    this.propertiesElement.classList.remove("properties-tree");
1231
1232    var selectorContainer = document.createElement("div");
1233    this._selectorElement = document.createElement("span");
1234    this._selectorElement.textContent = styleRule.selectorText;
1235    selectorContainer.appendChild(this._selectorElement);
1236
1237    var openBrace = document.createElement("span");
1238    openBrace.textContent = " {";
1239    selectorContainer.appendChild(openBrace);
1240    selectorContainer.addEventListener("mousedown", this._handleEmptySpaceMouseDown.bind(this), false);
1241    selectorContainer.addEventListener("click", this._handleSelectorContainerClick.bind(this), false);
1242
1243    var closeBrace = document.createElement("div");
1244    closeBrace.textContent = "}";
1245    this.element.appendChild(closeBrace);
1246
1247    if (this.editable && this.rule) {
1248        var newRuleButton = closeBrace.createChild("div", "sidebar-pane-button-new-rule");
1249        newRuleButton.title = WebInspector.UIString("Insert Style Rule");
1250        newRuleButton.addEventListener("click", this._onNewRuleClick.bind(this), false);
1251    }
1252
1253    this._selectorElement.addEventListener("click", this._handleSelectorClick.bind(this), false);
1254    this.element.addEventListener("mousedown", this._handleEmptySpaceMouseDown.bind(this), false);
1255    this.element.addEventListener("click", this._handleEmptySpaceClick.bind(this), false);
1256
1257    if (this.rule) {
1258        // Prevent editing the user agent and user rules.
1259        if (this.rule.isUserAgent || this.rule.isUser)
1260            this.editable = false;
1261        else {
1262            // Check this is a real CSSRule, not a bogus object coming from WebInspector.BlankStylePropertiesSection.
1263            if (this.rule.styleSheetId)
1264                this.navigable = !!this.rule.resourceURL();
1265        }
1266        this.titleElement.classList.add("styles-selector");
1267    }
1268
1269    this._usedProperties = styleRule.usedProperties;
1270
1271    this._selectorRefElement = document.createElement("div");
1272    this._selectorRefElement.className = "subtitle";
1273    this._mediaListElement = this.titleElement.createChild("div", "media-list");
1274    this._updateMediaList();
1275    this._updateRuleOrigin();
1276    selectorContainer.insertBefore(this._selectorRefElement, selectorContainer.firstChild);
1277    this.titleElement.appendChild(selectorContainer);
1278    this._selectorContainer = selectorContainer;
1279
1280    if (isInherited)
1281        this.element.classList.add("styles-show-inherited"); // This one is related to inherited rules, not computed style.
1282
1283    if (this.navigable)
1284        this.element.classList.add("navigable");
1285
1286    if (!this.editable)
1287        this.element.classList.add("read-only");
1288}
1289
1290WebInspector.StylePropertiesSection.prototype = {
1291    /**
1292     * @param {?Event} event
1293     */
1294    _onNewRuleClick: function(event)
1295    {
1296        event.consume();
1297        var range = WebInspector.TextRange.createFromLocation(this.rule.style.range.endLine, this.rule.style.range.endColumn + 1);
1298        this._parentPane._addBlankSection(this, this.rule.styleSheetId, range);
1299    },
1300
1301    /**
1302     * @param {!WebInspector.CSSRule} editedRule
1303     * @param {!WebInspector.TextRange} oldRange
1304     * @param {!WebInspector.TextRange} newRange
1305     */
1306    _styleSheetRuleEdited: function(editedRule, oldRange, newRange)
1307    {
1308        if (!this.rule || !this.rule.styleSheetId)
1309            return;
1310        if (this.rule !== editedRule)
1311            this.rule.sourceStyleSheetEdited(editedRule.styleSheetId, oldRange, newRange);
1312        this._updateMediaList();
1313        this._updateRuleOrigin();
1314    },
1315
1316    /**
1317     * @param {?Array.<!WebInspector.CSSMedia>} mediaRules
1318     */
1319    _createMediaList: function(mediaRules)
1320    {
1321        if (!mediaRules)
1322            return;
1323        for (var i = mediaRules.length - 1; i >= 0; --i) {
1324            var media = mediaRules[i];
1325            var mediaDataElement = this._mediaListElement.createChild("div", "media");
1326            var mediaText;
1327            switch (media.source) {
1328            case WebInspector.CSSMedia.Source.LINKED_SHEET:
1329            case WebInspector.CSSMedia.Source.INLINE_SHEET:
1330                mediaText = "media=\"" + media.text + "\"";
1331                break;
1332            case WebInspector.CSSMedia.Source.MEDIA_RULE:
1333                mediaText = "@media " + media.text;
1334                break;
1335            case WebInspector.CSSMedia.Source.IMPORT_RULE:
1336                mediaText = "@import " + media.text;
1337                break;
1338            }
1339
1340            if (media.sourceURL) {
1341                var refElement = mediaDataElement.createChild("div", "subtitle");
1342                var anchor = this._parentPane._linkifier.linkifyMedia(media);
1343                anchor.style.float = "right";
1344                refElement.appendChild(anchor);
1345            }
1346
1347            var mediaTextElement = mediaDataElement.createChild("span");
1348            mediaTextElement.textContent = mediaText;
1349            mediaTextElement.title = media.text;
1350        }
1351    },
1352
1353    _updateMediaList: function()
1354    {
1355        this._mediaListElement.removeChildren();
1356        this._createMediaList(this.styleRule.media);
1357    },
1358
1359    collapse: function()
1360    {
1361        // Overriding with empty body.
1362    },
1363
1364    handleClick: function()
1365    {
1366        // Avoid consuming events.
1367    },
1368
1369    /**
1370     * @param {string} propertyName
1371     * @return {boolean}
1372     */
1373    isPropertyInherited: function(propertyName)
1374    {
1375        if (this.isInherited) {
1376            // While rendering inherited stylesheet, reverse meaning of this property.
1377            // Render truly inherited properties with black, i.e. return them as non-inherited.
1378            return !WebInspector.CSSMetadata.isPropertyInherited(propertyName);
1379        }
1380        return false;
1381    },
1382
1383    /**
1384     * @param {string} propertyName
1385     * @param {boolean=} isShorthand
1386     * @return {boolean}
1387     */
1388    isPropertyOverloaded: function(propertyName, isShorthand)
1389    {
1390        if (!this._usedProperties || this.noAffect)
1391            return false;
1392
1393        if (this.isInherited && !WebInspector.CSSMetadata.isPropertyInherited(propertyName)) {
1394            // In the inherited sections, only show overrides for the potentially inherited properties.
1395            return false;
1396        }
1397
1398        var canonicalName = WebInspector.CSSMetadata.canonicalPropertyName(propertyName);
1399        var used = (canonicalName in this._usedProperties);
1400        if (used || !isShorthand)
1401            return !used;
1402
1403        // Find out if any of the individual longhand properties of the shorthand
1404        // are used, if none are then the shorthand is overloaded too.
1405        var longhandProperties = this.styleRule.style.longhandProperties(propertyName);
1406        for (var j = 0; j < longhandProperties.length; ++j) {
1407            var individualProperty = longhandProperties[j];
1408            if (WebInspector.CSSMetadata.canonicalPropertyName(individualProperty.name) in this._usedProperties)
1409                return false;
1410        }
1411
1412        return true;
1413    },
1414
1415    /**
1416     * @return {?WebInspector.StylePropertiesSection}
1417     */
1418    nextEditableSibling: function()
1419    {
1420        var curSection = this;
1421        do {
1422            curSection = curSection.nextSibling;
1423        } while (curSection && !curSection.editable);
1424
1425        if (!curSection) {
1426            curSection = this.firstSibling;
1427            while (curSection && !curSection.editable)
1428                curSection = curSection.nextSibling;
1429        }
1430
1431        return (curSection && curSection.editable) ? curSection : null;
1432    },
1433
1434    /**
1435     * @return {?WebInspector.StylePropertiesSection}
1436     */
1437    previousEditableSibling: function()
1438    {
1439        var curSection = this;
1440        do {
1441            curSection = curSection.previousSibling;
1442        } while (curSection && !curSection.editable);
1443
1444        if (!curSection) {
1445            curSection = this.lastSibling;
1446            while (curSection && !curSection.editable)
1447                curSection = curSection.previousSibling;
1448        }
1449
1450        return (curSection && curSection.editable) ? curSection : null;
1451    },
1452
1453    update: function(full)
1454    {
1455        if (this.styleRule.selectorText)
1456            this._selectorElement.textContent = this.styleRule.selectorText;
1457        this._markSelectorMatches();
1458        if (full) {
1459            this.propertiesTreeOutline.removeChildren();
1460            this.populated = false;
1461        } else {
1462            var child = this.propertiesTreeOutline.children[0];
1463            while (child) {
1464                child.overloaded = this.isPropertyOverloaded(child.name, child.isShorthand);
1465                child = child.traverseNextTreeElement(false, null, true);
1466            }
1467        }
1468        this.afterUpdate();
1469    },
1470
1471    afterUpdate: function()
1472    {
1473        if (this._afterUpdate) {
1474            this._afterUpdate(this);
1475            delete this._afterUpdate;
1476            this._afterUpdateFinishedForTest();
1477        }
1478    },
1479
1480    _afterUpdateFinishedForTest: function()
1481    {
1482    },
1483
1484    onpopulate: function()
1485    {
1486        var style = this.styleRule.style;
1487        var allProperties = style.allProperties;
1488        this.uniqueProperties = [];
1489
1490        var styleHasEditableSource = this.editable && !!style.range;
1491        if (styleHasEditableSource) {
1492            for (var i = 0; i < allProperties.length; ++i) {
1493                var property = allProperties[i];
1494                this.uniqueProperties.push(property);
1495                if (property.styleBased)
1496                    continue;
1497
1498                var isShorthand = !!WebInspector.CSSMetadata.cssPropertiesMetainfo.longhands(property.name);
1499                var inherited = this.isPropertyInherited(property.name);
1500                var overloaded = property.inactive || this.isPropertyOverloaded(property.name);
1501                var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this.styleRule, style, property, isShorthand, inherited, overloaded);
1502                this.propertiesTreeOutline.appendChild(item);
1503            }
1504            return;
1505        }
1506
1507        var generatedShorthands = {};
1508        // For style-based properties, generate shorthands with values when possible.
1509        for (var i = 0; i < allProperties.length; ++i) {
1510            var property = allProperties[i];
1511            this.uniqueProperties.push(property);
1512            var isShorthand = !!WebInspector.CSSMetadata.cssPropertiesMetainfo.longhands(property.name);
1513
1514            // For style-based properties, try generating shorthands.
1515            var shorthands = isShorthand ? null : WebInspector.CSSMetadata.cssPropertiesMetainfo.shorthands(property.name);
1516            var shorthandPropertyAvailable = false;
1517            for (var j = 0; shorthands && !shorthandPropertyAvailable && j < shorthands.length; ++j) {
1518                var shorthand = shorthands[j];
1519                if (shorthand in generatedShorthands) {
1520                    shorthandPropertyAvailable = true;
1521                    continue;  // There already is a shorthand this longhands falls under.
1522                }
1523                if (style.getLiveProperty(shorthand)) {
1524                    shorthandPropertyAvailable = true;
1525                    continue;  // There is an explict shorthand property this longhands falls under.
1526                }
1527                if (!style.shorthandValue(shorthand)) {
1528                    shorthandPropertyAvailable = false;
1529                    continue;  // Never generate synthetic shorthands when no value is available.
1530                }
1531
1532                // Generate synthetic shorthand we have a value for.
1533                var shorthandProperty = new WebInspector.CSSProperty(style, style.allProperties.length, shorthand, style.shorthandValue(shorthand), false, false, true, true);
1534                var overloaded = property.inactive || this.isPropertyOverloaded(property.name, true);
1535                var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this.styleRule, style, shorthandProperty,  /* isShorthand */ true, /* inherited */ false, overloaded);
1536                this.propertiesTreeOutline.appendChild(item);
1537                generatedShorthands[shorthand] = shorthandProperty;
1538                shorthandPropertyAvailable = true;
1539            }
1540            if (shorthandPropertyAvailable)
1541                continue;  // Shorthand for the property found.
1542
1543            var inherited = this.isPropertyInherited(property.name);
1544            var overloaded = property.inactive || this.isPropertyOverloaded(property.name, isShorthand);
1545            var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this.styleRule, style, property, isShorthand, inherited, overloaded);
1546            this.propertiesTreeOutline.appendChild(item);
1547        }
1548    },
1549
1550    _updateFilter: function()
1551    {
1552        if (this.styleRule.isAttribute)
1553            return;
1554        var regex = this._parentPane.filterRegex();
1555        var hideRule = regex && !regex.test(this.element.textContent);
1556        this.element.classList.toggle("hidden", hideRule);
1557        if (hideRule)
1558            return;
1559
1560        var children = this.propertiesTreeOutline.children;
1561        for (var i = 0; i < children.length; ++i)
1562            children[i]._updateFilter();
1563
1564        if (this.styleRule.rule)
1565            this._markSelectorHighlights();
1566    },
1567
1568    _markSelectorMatches: function()
1569    {
1570        var rule = this.styleRule.rule;
1571        if (!rule)
1572            return;
1573
1574        var matchingSelectors = rule.matchingSelectors;
1575        // .selector is rendered as non-affecting selector by default.
1576        if (this.noAffect || matchingSelectors)
1577            this._selectorElement.className = "selector";
1578        if (!matchingSelectors)
1579            return;
1580
1581        var selectors = rule.selectors;
1582        var fragment = document.createDocumentFragment();
1583        var currentMatch = 0;
1584        for (var i = 0; i < selectors.length ; ++i) {
1585            if (i)
1586                fragment.createTextChild(", ");
1587            var isSelectorMatching = matchingSelectors[currentMatch] === i;
1588            if (isSelectorMatching)
1589                ++currentMatch;
1590            var matchingSelectorClass = isSelectorMatching ? " selector-matches" : "";
1591            var selectorElement = document.createElement("span");
1592            selectorElement.className = "simple-selector" + matchingSelectorClass;
1593            if (rule.styleSheetId)
1594                selectorElement._selectorIndex = i;
1595            selectorElement.textContent = selectors[i].value;
1596
1597            fragment.appendChild(selectorElement);
1598        }
1599
1600        this._selectorElement.removeChildren();
1601        this._selectorElement.appendChild(fragment);
1602        this._markSelectorHighlights();
1603    },
1604
1605    _markSelectorHighlights: function()
1606    {
1607        var selectors = this._selectorElement.getElementsByClassName("simple-selector");
1608        var regex = this._parentPane.filterRegex();
1609        for (var i = 0; i < selectors.length; ++i) {
1610            var selectorMatchesFilter = regex && regex.test(selectors[i].textContent);
1611            selectors[i].classList.toggle("filter-match", selectorMatchesFilter);
1612        }
1613    },
1614
1615    _checkWillCancelEditing: function()
1616    {
1617        var willCauseCancelEditing = this._willCauseCancelEditing;
1618        delete this._willCauseCancelEditing;
1619        return willCauseCancelEditing;
1620    },
1621
1622    _handleSelectorContainerClick: function(event)
1623    {
1624        if (this._checkWillCancelEditing() || !this.editable)
1625            return;
1626        if (event.target === this._selectorContainer) {
1627            this.addNewBlankProperty(0).startEditing();
1628            event.consume(true);
1629        }
1630    },
1631
1632    /**
1633     * @param {number=} index
1634     * @return {!WebInspector.StylePropertyTreeElement}
1635     */
1636    addNewBlankProperty: function(index)
1637    {
1638        var style = this.styleRule.style;
1639        var property = style.newBlankProperty(index);
1640        var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this.styleRule, style, property, false, false, false);
1641        index = property.index;
1642        this.propertiesTreeOutline.insertChild(item, index);
1643        item.listItemElement.textContent = "";
1644        item._newProperty = true;
1645        item.updateTitle();
1646        return item;
1647    },
1648
1649    /**
1650     * @param {?WebInspector.CSSRule} rule
1651     * @param {!WebInspector.TextRange=} ruleLocation
1652     * @return {!Node}
1653     */
1654    _createRuleOriginNode: function(rule, ruleLocation)
1655    {
1656        if (!rule)
1657            return document.createTextNode("");
1658
1659        if (!ruleLocation) {
1660            var firstMatchingIndex = rule.matchingSelectors && rule.matchingSelectors.length ? rule.matchingSelectors[0] : 0;
1661            ruleLocation = rule.selectors[firstMatchingIndex].range;
1662        }
1663
1664        if (ruleLocation && rule.styleSheetId)
1665            return this._linkifyRuleLocation(rule.styleSheetId, ruleLocation);
1666
1667        if (rule.isUserAgent)
1668            return document.createTextNode(WebInspector.UIString("user agent stylesheet"));
1669        if (rule.isUser)
1670            return document.createTextNode(WebInspector.UIString("user stylesheet"));
1671        if (rule.isViaInspector)
1672            return document.createTextNode(WebInspector.UIString("via inspector"));
1673        return document.createTextNode("");
1674    },
1675
1676    /**
1677     * @param {string} styleSheetId
1678     * @param {!WebInspector.TextRange} ruleLocation
1679     * @return {!Node}
1680     */
1681    _linkifyRuleLocation: function(styleSheetId, ruleLocation)
1682    {
1683        /**
1684         * @param {string} url
1685         * @param {number} line
1686         */
1687        function linkifyUncopyable(url, line)
1688        {
1689            var link = WebInspector.linkifyResourceAsNode(url, line, "", url + ":" + (line + 1));
1690            link.classList.add("webkit-html-resource-link");
1691            link.setAttribute("data-uncopyable", link.textContent);
1692            link.textContent = "";
1693            return link;
1694        }
1695
1696        var styleSheetHeader = this._parentPane._target.cssModel.styleSheetHeaderForId(styleSheetId);
1697        var sourceURL = styleSheetHeader.resourceURL();
1698        var lineNumber = styleSheetHeader.lineNumberInSource(ruleLocation.startLine);
1699        var columnNumber = styleSheetHeader.columnNumberInSource(ruleLocation.startLine, ruleLocation.startColumn);
1700        var matchingSelectorLocation = new WebInspector.CSSLocation(this._parentPane._target, styleSheetId, sourceURL, lineNumber, columnNumber);
1701        return this._parentPane._linkifier.linkifyCSSLocation(matchingSelectorLocation) || linkifyUncopyable(sourceURL, 0);
1702    },
1703
1704    _handleEmptySpaceMouseDown: function()
1705    {
1706        this._willCauseCancelEditing = this._parentPane._isEditingStyle;
1707    },
1708
1709    _handleEmptySpaceClick: function(event)
1710    {
1711        if (!this.editable)
1712            return;
1713
1714        if (!window.getSelection().isCollapsed)
1715            return;
1716
1717        if (this._checkWillCancelEditing())
1718            return;
1719
1720        if (event.target.classList.contains("header") || this.element.classList.contains("read-only") || event.target.enclosingNodeOrSelfWithClass("media")) {
1721            event.consume();
1722            return;
1723        }
1724        this.expand();
1725        this.addNewBlankProperty().startEditing();
1726        event.consume(true)
1727    },
1728
1729    _handleSelectorClick: function(event)
1730    {
1731        if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) && this.navigable && event.target.classList.contains("simple-selector")) {
1732            var index = event.target._selectorIndex;
1733            var target = this._parentPane._target;
1734            var rawLocation = new WebInspector.CSSLocation(target, this.rule.styleSheetId, this.rule.sourceURL, this.rule.lineNumberInSource(index), this.rule.columnNumberInSource(index));
1735            var uiLocation = WebInspector.cssWorkspaceBinding.rawLocationToUILocation(rawLocation);
1736            WebInspector.Revealer.reveal(uiLocation);
1737            event.consume(true);
1738            return;
1739        }
1740        this._startEditingOnMouseEvent();
1741        event.consume(true);
1742    },
1743
1744    _startEditingOnMouseEvent: function()
1745    {
1746        if (!this.editable)
1747            return;
1748
1749        if (!this.rule && this.propertiesTreeOutline.children.length === 0) {
1750            this.expand();
1751            this.addNewBlankProperty().startEditing();
1752            return;
1753        }
1754
1755        if (!this.rule)
1756            return;
1757
1758        this.startEditingSelector();
1759    },
1760
1761    startEditingSelector: function()
1762    {
1763        var element = this._selectorElement;
1764        if (WebInspector.isBeingEdited(element))
1765            return;
1766
1767        element.scrollIntoViewIfNeeded(false);
1768        element.textContent = element.textContent; // Reset selector marks in group.
1769
1770        var config = new WebInspector.InplaceEditor.Config(this.editingSelectorCommitted.bind(this), this.editingSelectorCancelled.bind(this), undefined, this._editingSelectorBlurHandler.bind(this));
1771        WebInspector.InplaceEditor.startEditing(this._selectorElement, config);
1772
1773        window.getSelection().setBaseAndExtent(element, 0, element, 1);
1774        this._parentPane._isEditingStyle = true;
1775        this._parentPane._startEditingSelector(this);
1776    },
1777
1778    /**
1779     * @param {string} text
1780     */
1781    setSelectorText: function(text)
1782    {
1783        this._selectorElement.textContent = text;
1784        window.getSelection().setBaseAndExtent(this._selectorElement, 0, this._selectorElement, 1);
1785    },
1786
1787    /**
1788     * @param {!Element} editor
1789     * @param {!Event} blurEvent
1790     * @return {boolean}
1791     */
1792    _editingSelectorBlurHandler: function(editor, blurEvent)
1793    {
1794        if (!blurEvent.relatedTarget)
1795            return true;
1796        var elementTreeOutline = blurEvent.relatedTarget.enclosingNodeOrSelfWithClass("elements-tree-outline");
1797        if (!elementTreeOutline)
1798            return true;
1799        editor.focus();
1800        return false;
1801    },
1802
1803    _moveEditorFromSelector: function(moveDirection)
1804    {
1805        this._markSelectorMatches();
1806
1807        if (!moveDirection)
1808            return;
1809
1810        if (moveDirection === "forward") {
1811            this.expand();
1812            var firstChild = this.propertiesTreeOutline.children[0];
1813            while (firstChild && firstChild.inherited)
1814                firstChild = firstChild.nextSibling;
1815            if (!firstChild)
1816                this.addNewBlankProperty().startEditing();
1817            else
1818                firstChild.startEditing(firstChild.nameElement);
1819        } else {
1820            var previousSection = this.previousEditableSibling();
1821            if (!previousSection)
1822                return;
1823
1824            previousSection.expand();
1825            previousSection.addNewBlankProperty().startEditing();
1826        }
1827    },
1828
1829    editingSelectorCommitted: function(element, newContent, oldContent, context, moveDirection)
1830    {
1831        this._editingSelectorEnded();
1832        if (newContent)
1833            newContent = newContent.trim();
1834        if (newContent === oldContent) {
1835            // Revert to a trimmed version of the selector if need be.
1836            this._selectorElement.textContent = newContent;
1837            this._moveEditorFromSelector(moveDirection);
1838            return;
1839        }
1840
1841        /**
1842         * @param {!WebInspector.CSSRule} newRule
1843         * @this {WebInspector.StylePropertiesSection}
1844         */
1845        function successCallback(newRule)
1846        {
1847            var doesAffectSelectedNode = newRule.matchingSelectors.length > 0;
1848            if (!doesAffectSelectedNode) {
1849                this.noAffect = true;
1850                this.element.classList.add("no-affect");
1851            } else {
1852                delete this.noAffect;
1853                this.element.classList.remove("no-affect");
1854            }
1855
1856            var oldSelectorRange = this.rule.selectorRange;
1857            this.rule = newRule;
1858            this.styleRule = { section: this, style: newRule.style, selectorText: newRule.selectorText, media: newRule.media, rule: newRule };
1859
1860            this._parentPane._refreshUpdate(this, false);
1861            this._parentPane._styleSheetRuleEdited(this.rule, oldSelectorRange, this.rule.selectorRange);
1862
1863            finishOperationAndMoveEditor.call(this, moveDirection);
1864        }
1865
1866        /**
1867         * @this {WebInspector.StylePropertiesSection}
1868         */
1869        function finishOperationAndMoveEditor(direction)
1870        {
1871            delete this._parentPane._userOperation;
1872            this._moveEditorFromSelector(direction);
1873        }
1874
1875        // This gets deleted in finishOperationAndMoveEditor(), which is called both on success and failure.
1876        this._parentPane._userOperation = true;
1877        var selectedNode = this._parentPane._node;
1878        this._parentPane._target.cssModel.setRuleSelector(this.rule, selectedNode ? selectedNode.id : 0, newContent, successCallback.bind(this), finishOperationAndMoveEditor.bind(this, moveDirection));
1879    },
1880
1881    _updateRuleOrigin: function()
1882    {
1883        this._selectorRefElement.removeChildren();
1884        this._selectorRefElement.appendChild(this._createRuleOriginNode(this.rule));
1885    },
1886
1887    _editingSelectorEnded: function()
1888    {
1889        delete this._parentPane._isEditingStyle;
1890        this._parentPane._finishEditingSelector();
1891    },
1892
1893    editingSelectorCancelled: function()
1894    {
1895        this._editingSelectorEnded();
1896
1897        // Mark the selectors in group if necessary.
1898        // This is overridden by BlankStylePropertiesSection.
1899        this._markSelectorMatches();
1900    },
1901
1902    __proto__: WebInspector.PropertiesSection.prototype
1903}
1904
1905/**
1906 * @constructor
1907 * @extends {WebInspector.PropertiesSection}
1908 * @param {!WebInspector.StylesSidebarPane} stylesPane
1909 * @param {!Object} styleRule
1910 * @param {!Object.<string, boolean>} usedProperties
1911 */
1912WebInspector.ComputedStylePropertiesSection = function(stylesPane, styleRule, usedProperties)
1913{
1914    WebInspector.PropertiesSection.call(this, "");
1915    this._hasFreshContent = false;
1916    this.element.className = "styles-section monospace read-only computed-style";
1917
1918    this.headerElement.appendChild(WebInspector.ComputedStylePropertiesSection._showInheritedCheckbox());
1919    this.onShowInheritedChanged();
1920
1921    this._stylesPane = stylesPane;
1922    this.styleRule = styleRule;
1923    this._usedProperties = usedProperties;
1924    this._alwaysShowComputedProperties = { "display": true, "height": true, "width": true };
1925    this.computedStyle = true;
1926    this._propertyTreeElements = {};
1927    this._expandedPropertyNames = {};
1928}
1929
1930/**
1931 * @return {!Element}
1932 */
1933WebInspector.ComputedStylePropertiesSection._showInheritedCheckbox = function()
1934{
1935    if (!WebInspector.ComputedStylePropertiesSection._showInheritedCheckboxElement) {
1936        WebInspector.ComputedStylePropertiesSection._showInheritedCheckboxElement = WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Show inherited properties"), WebInspector.settings.showInheritedComputedStyleProperties, true);
1937        WebInspector.ComputedStylePropertiesSection._showInheritedCheckboxElement.classList.add("checkbox-with-label");
1938    }
1939    return WebInspector.ComputedStylePropertiesSection._showInheritedCheckboxElement;
1940}
1941
1942WebInspector.ComputedStylePropertiesSection.prototype = {
1943    onShowInheritedChanged: function()
1944    {
1945        this.element.classList.toggle("styles-show-inherited", WebInspector.settings.showInheritedComputedStyleProperties.get());
1946    },
1947
1948    collapse: function(dontRememberState)
1949    {
1950        // Overriding with empty body.
1951    },
1952
1953    _isPropertyInherited: function(propertyName)
1954    {
1955        var canonicalName = WebInspector.CSSMetadata.canonicalPropertyName(propertyName);
1956        return !(canonicalName in this._usedProperties) && !(canonicalName in this._alwaysShowComputedProperties);
1957    },
1958
1959    update: function()
1960    {
1961        this._expandedPropertyNames = {};
1962        for (var name in this._propertyTreeElements) {
1963            if (this._propertyTreeElements[name].expanded)
1964                this._expandedPropertyNames[name] = true;
1965        }
1966        this._propertyTreeElements = {};
1967        this.propertiesTreeOutline.removeChildren();
1968        this.populated = false;
1969    },
1970
1971    _updateFilter: function()
1972    {
1973        var children = this.propertiesTreeOutline.children;
1974        for (var i = 0; i < children.length; ++i)
1975            children[i]._updateFilter();
1976    },
1977
1978    onpopulate: function()
1979    {
1980        function sorter(a, b)
1981        {
1982            return a.name.compareTo(b.name);
1983        }
1984
1985        var style = this.styleRule.style;
1986        if (!style)
1987            return;
1988
1989        var uniqueProperties = [];
1990        var allProperties = style.allProperties;
1991        for (var i = 0; i < allProperties.length; ++i)
1992            uniqueProperties.push(allProperties[i]);
1993        uniqueProperties.sort(sorter);
1994
1995        this._propertyTreeElements = {};
1996        for (var i = 0; i < uniqueProperties.length; ++i) {
1997            var property = uniqueProperties[i];
1998            var inherited = this._isPropertyInherited(property.name);
1999            var item = new WebInspector.ComputedStylePropertyTreeElement(this._stylesPane, this.styleRule, style, property, inherited);
2000            this.propertiesTreeOutline.appendChild(item);
2001            this._propertyTreeElements[property.name] = item;
2002        }
2003    },
2004
2005    rebuildComputedTrace: function(sections)
2006    {
2007        for (var i = 0; i < sections.length; ++i) {
2008            var section = sections[i];
2009            if (section.computedStyle || section.isBlank)
2010                continue;
2011
2012            for (var j = 0; j < section.uniqueProperties.length; ++j) {
2013                var property = section.uniqueProperties[j];
2014                if (property.disabled)
2015                    continue;
2016                if (section.isInherited && !WebInspector.CSSMetadata.isPropertyInherited(property.name))
2017                    continue;
2018
2019                var treeElement = this._propertyTreeElements[property.name.toLowerCase()];
2020                if (treeElement) {
2021                    var fragment = document.createDocumentFragment();
2022                    var selector = fragment.createChild("span");
2023                    selector.style.color = "gray";
2024                    selector.textContent = section.styleRule.selectorText;
2025                    fragment.createTextChild(" - " + property.value + " ");
2026                    var subtitle = fragment.createChild("span");
2027                    subtitle.style.float = "right";
2028                    subtitle.appendChild(section._createRuleOriginNode(section.rule));
2029                    var childElement = new TreeElement(fragment, null, false);
2030                    treeElement.appendChild(childElement);
2031                    if (property.inactive || section.isPropertyOverloaded(property.name))
2032                        childElement.listItemElement.classList.add("overloaded");
2033                    if (!property.parsedOk) {
2034                        childElement.listItemElement.classList.add("not-parsed-ok");
2035                        childElement.listItemElement.insertBefore(WebInspector.StylesSidebarPane.createExclamationMark(property), childElement.listItemElement.firstChild);
2036                        if (WebInspector.StylesSidebarPane._ignoreErrorsForProperty(property))
2037                            childElement.listItemElement.classList.add("has-ignorable-error");
2038                    }
2039                }
2040            }
2041        }
2042
2043        // Restore expanded state after update.
2044        for (var name in this._expandedPropertyNames) {
2045            if (name in this._propertyTreeElements)
2046                this._propertyTreeElements[name].expand();
2047        }
2048    },
2049
2050    __proto__: WebInspector.PropertiesSection.prototype
2051}
2052
2053/**
2054 * @constructor
2055 * @extends {WebInspector.StylePropertiesSection}
2056 * @param {!WebInspector.StylesSidebarPane} stylesPane
2057 * @param {string} defaultSelectorText
2058 * @param {string} styleSheetId
2059 * @param {!WebInspector.TextRange} ruleLocation
2060 * @param {!WebInspector.CSSRule=} insertAfterRule
2061 */
2062WebInspector.BlankStylePropertiesSection = function(stylesPane, defaultSelectorText, styleSheetId, ruleLocation, insertAfterRule)
2063{
2064    var styleSheetHeader = WebInspector.cssModel.styleSheetHeaderForId(styleSheetId);
2065    WebInspector.StylePropertiesSection.call(this, stylesPane, { selectorText: defaultSelectorText }, true, false);
2066    this._ruleLocation = ruleLocation;
2067    this._styleSheetId = styleSheetId;
2068    this._selectorRefElement.removeChildren();
2069    this._selectorRefElement.appendChild(this._linkifyRuleLocation(styleSheetId, this._actualRuleLocation()));
2070    if (insertAfterRule)
2071        this._createMediaList(insertAfterRule.media);
2072    this.element.classList.add("blank-section");
2073}
2074
2075WebInspector.BlankStylePropertiesSection.prototype = {
2076    /**
2077     * @return {!WebInspector.TextRange}
2078     */
2079    _actualRuleLocation: function()
2080    {
2081        var prefix = this._rulePrefix();
2082        var lines = prefix.split("\n");
2083        var editRange = new WebInspector.TextRange(0, 0, lines.length - 1, lines.peekLast().length);
2084        return this._ruleLocation.rebaseAfterTextEdit(WebInspector.TextRange.createFromLocation(0, 0), editRange);
2085    },
2086
2087    /**
2088     * @return {string}
2089     */
2090    _rulePrefix: function()
2091    {
2092        return this._ruleLocation.startLine === 0 && this._ruleLocation.startColumn === 0 ? "" : "\n\n";
2093    },
2094
2095    get isBlank()
2096    {
2097        return !this._normal;
2098    },
2099
2100    expand: function()
2101    {
2102        if (!this.isBlank)
2103            WebInspector.StylePropertiesSection.prototype.expand.call(this);
2104    },
2105
2106    editingSelectorCommitted: function(element, newContent, oldContent, context, moveDirection)
2107    {
2108        if (!this.isBlank) {
2109            WebInspector.StylePropertiesSection.prototype.editingSelectorCommitted.call(this, element, newContent, oldContent, context, moveDirection);
2110            return;
2111        }
2112
2113        /**
2114         * @param {!WebInspector.CSSRule} newRule
2115         * @this {WebInspector.StylePropertiesSection}
2116         */
2117        function successCallback(newRule)
2118        {
2119            var doesSelectorAffectSelectedNode = newRule.matchingSelectors.length > 0;
2120            var styleRule = { media: newRule.media, section: this, style: newRule.style, selectorText: newRule.selectorText, rule: newRule };
2121            this._makeNormal(styleRule);
2122
2123            if (!doesSelectorAffectSelectedNode) {
2124                this.noAffect = true;
2125                this.element.classList.add("no-affect");
2126            }
2127
2128            var ruleTextLines = ruleText.split("\n");
2129            var startLine = this._ruleLocation.startLine;
2130            var startColumn = this._ruleLocation.startColumn;
2131            var newRange = new WebInspector.TextRange(startLine, startColumn, startLine + ruleTextLines.length - 1, startColumn + ruleTextLines[ruleTextLines.length - 1].length);
2132            this._parentPane._styleSheetRuleEdited(newRule, this._ruleLocation, newRange);
2133
2134            this._updateRuleOrigin();
2135            this.expand();
2136            if (this.element.parentElement) // Might have been detached already.
2137                this._moveEditorFromSelector(moveDirection);
2138
2139            delete this._parentPane._userOperation;
2140            this._editingSelectorEnded();
2141            this._markSelectorMatches();
2142        }
2143
2144        if (newContent)
2145            newContent = newContent.trim();
2146        this._parentPane._userOperation = true;
2147
2148        var cssModel = this._parentPane._target.cssModel;
2149        var ruleText = this._rulePrefix() + newContent + " {}";
2150        cssModel.addRule(this._styleSheetId, this._parentPane._node, ruleText, this._ruleLocation, successCallback.bind(this), this.editingSelectorCancelled.bind(this));
2151    },
2152
2153    editingSelectorCancelled: function()
2154    {
2155        delete this._parentPane._userOperation;
2156        if (!this.isBlank) {
2157            WebInspector.StylePropertiesSection.prototype.editingSelectorCancelled.call(this);
2158            return;
2159        }
2160
2161        this._editingSelectorEnded();
2162        this._parentPane.removeSection(this);
2163    },
2164
2165    _makeNormal: function(styleRule)
2166    {
2167        this.element.classList.remove("blank-section");
2168        this.styleRule = styleRule;
2169        this.rule = styleRule.rule;
2170
2171        // FIXME: replace this instance by a normal WebInspector.StylePropertiesSection.
2172        this._normal = true;
2173    },
2174
2175    __proto__: WebInspector.StylePropertiesSection.prototype
2176}
2177
2178/**
2179 * @constructor
2180 * @extends {TreeElement}
2181 * @param {!Object} styleRule
2182 * @param {!WebInspector.CSSStyleDeclaration} style
2183 * @param {!WebInspector.CSSProperty} property
2184 * @param {boolean} inherited
2185 * @param {boolean} overloaded
2186 * @param {boolean} hasChildren
2187 */
2188WebInspector.StylePropertyTreeElementBase = function(styleRule, style, property, inherited, overloaded, hasChildren)
2189{
2190    this._styleRule = styleRule;
2191    this.style = style;
2192    this.property = property;
2193    this._inherited = inherited;
2194    this._overloaded = overloaded;
2195
2196    // Pass an empty title, the title gets made later in onattach.
2197    TreeElement.call(this, "", null, hasChildren);
2198
2199    this.selectable = false;
2200}
2201
2202WebInspector.StylePropertyTreeElementBase.prototype = {
2203    /**
2204     * @return {?WebInspector.DOMNode}
2205     */
2206    node: function()
2207    {
2208        return null;  // Overridden by ancestors.
2209    },
2210
2211    /**
2212     * @return {?WebInspector.StylesSidebarPane}
2213     */
2214    editablePane: function()
2215    {
2216        return null;  // Overridden by ancestors.
2217    },
2218
2219    /**
2220     * @return {!WebInspector.StylesSidebarPane|!WebInspector.ComputedStyleSidebarPane}
2221     */
2222    parentPane: function()
2223    {
2224        throw "Not implemented";
2225    },
2226
2227    get inherited()
2228    {
2229        return this._inherited;
2230    },
2231
2232    /**
2233     * @return {boolean}
2234     */
2235    hasIgnorableError: function()
2236    {
2237        return !this.parsedOk && WebInspector.StylesSidebarPane._ignoreErrorsForProperty(this.property);
2238    },
2239
2240    set inherited(x)
2241    {
2242        if (x === this._inherited)
2243            return;
2244        this._inherited = x;
2245        this.updateState();
2246    },
2247
2248    get overloaded()
2249    {
2250        return this._overloaded;
2251    },
2252
2253    set overloaded(x)
2254    {
2255        if (x === this._overloaded)
2256            return;
2257        this._overloaded = x;
2258        this.updateState();
2259    },
2260
2261    get disabled()
2262    {
2263        return this.property.disabled;
2264    },
2265
2266    get name()
2267    {
2268        if (!this.disabled || !this.property.text)
2269            return this.property.name;
2270
2271        var text = this.property.text;
2272        var index = text.indexOf(":");
2273        if (index < 1)
2274            return this.property.name;
2275
2276        text = text.substring(0, index).trim();
2277        if (text.startsWith("/*"))
2278            text = text.substring(2).trim();
2279        return text;
2280    },
2281
2282    get value()
2283    {
2284        if (!this.disabled || !this.property.text)
2285            return this.property.value;
2286
2287        var match = this.property.text.match(/(.*);\s*/);
2288        if (!match || !match[1])
2289            return this.property.value;
2290
2291        var text = match[1];
2292        var index = text.indexOf(":");
2293        if (index < 1)
2294            return this.property.value;
2295
2296        return text.substring(index + 1).trim();
2297    },
2298
2299    get parsedOk()
2300    {
2301        return this.property.parsedOk;
2302    },
2303
2304    onattach: function()
2305    {
2306        this.updateTitle();
2307    },
2308
2309    updateTitle: function()
2310    {
2311        var value = this.value;
2312
2313        this.updateState();
2314
2315        var nameElement = document.createElement("span");
2316        nameElement.className = "webkit-css-property";
2317        nameElement.textContent = this.name;
2318        nameElement.title = this.property.propertyText;
2319        this.nameElement = nameElement;
2320
2321        this._expandElement = document.createElement("span");
2322        this._expandElement.className = "expand-element";
2323
2324        var valueElement = document.createElement("span");
2325        valueElement.className = "value";
2326        this.valueElement = valueElement;
2327
2328        /**
2329         * @param {!RegExp} regex
2330         * @param {function(string):!Node} processor
2331         * @param {?function(string):!Node} nextProcessor
2332         * @param {string} valueText
2333         * @return {!DocumentFragment}
2334         */
2335        function processValue(regex, processor, nextProcessor, valueText)
2336        {
2337            var container = document.createDocumentFragment();
2338
2339            var items = valueText.replace(regex, "\0$1\0").split("\0");
2340            for (var i = 0; i < items.length; ++i) {
2341                if ((i % 2) === 0) {
2342                    if (nextProcessor)
2343                        container.appendChild(nextProcessor(items[i]));
2344                    else
2345                        container.createTextChild(items[i]);
2346                } else {
2347                    var processedNode = processor(items[i]);
2348                    if (processedNode)
2349                        container.appendChild(processedNode);
2350                }
2351            }
2352
2353            return container;
2354        }
2355
2356        /**
2357         * @param {string} url
2358         * @return {!Node}
2359         * @this {WebInspector.StylePropertyTreeElementBase}
2360         */
2361        function linkifyURL(url)
2362        {
2363            var hrefUrl = url;
2364            var match = hrefUrl.match(/['"]?([^'"]+)/);
2365            if (match)
2366                hrefUrl = match[1];
2367            var container = document.createDocumentFragment();
2368            container.createTextChild("url(");
2369            if (this._styleRule.rule && this._styleRule.rule.resourceURL())
2370                hrefUrl = WebInspector.ParsedURL.completeURL(this._styleRule.rule.resourceURL(), hrefUrl);
2371            else if (this.node())
2372                hrefUrl = this.node().resolveURL(hrefUrl);
2373            var hasResource = hrefUrl && !!WebInspector.resourceForURL(hrefUrl);
2374            // FIXME: WebInspector.linkifyURLAsNode() should really use baseURI.
2375            container.appendChild(WebInspector.linkifyURLAsNode(hrefUrl || url, url, undefined, !hasResource));
2376            container.createTextChild(")");
2377            return container;
2378        }
2379
2380        if (value) {
2381            var colorProcessor = processValue.bind(null, WebInspector.StylesSidebarPane._colorRegex, this._processColor.bind(this, nameElement, valueElement), null);
2382            valueElement.appendChild(processValue(/url\(\s*([^)]+)\s*\)/g, linkifyURL.bind(this), WebInspector.CSSMetadata.isColorAwareProperty(this.name) && this.parsedOk ? colorProcessor : null, value));
2383        }
2384
2385        this.listItemElement.removeChildren();
2386        nameElement.normalize();
2387        valueElement.normalize();
2388
2389        if (!this.treeOutline)
2390            return;
2391
2392        if (this.disabled)
2393            this.listItemElement.createChild("span", "styles-clipboard-only").createTextChild("/* ");
2394        this.listItemElement.appendChild(nameElement);
2395        this.listItemElement.createTextChild(": ");
2396        this.listItemElement.appendChild(this._expandElement);
2397        this.listItemElement.appendChild(valueElement);
2398        this.listItemElement.createTextChild(";");
2399        if (this.disabled)
2400            this.listItemElement.createChild("span", "styles-clipboard-only").createTextChild(" */");
2401
2402        if (!this.parsedOk) {
2403            // Avoid having longhands under an invalid shorthand.
2404            this.hasChildren = false;
2405            this.listItemElement.classList.add("not-parsed-ok");
2406
2407            // Add a separate exclamation mark IMG element with a tooltip.
2408            this.listItemElement.insertBefore(WebInspector.StylesSidebarPane.createExclamationMark(this.property), this.listItemElement.firstChild);
2409        }
2410        if (this.property.inactive)
2411            this.listItemElement.classList.add("inactive");
2412        this._updateFilter();
2413    },
2414
2415    _updateFilter: function()
2416    {
2417        var regEx = this.parentPane().filterRegex();
2418        this.listItemElement.classList.toggle("filter-match", !!regEx && (regEx.test(this.property.name) || regEx.test(this.property.value)));
2419    },
2420
2421    /**
2422     * @param {!Element} nameElement
2423     * @param {!Element} valueElement
2424     * @param {string} text
2425     * @return {!Node}
2426     */
2427    _processColor: function(nameElement, valueElement, text)
2428    {
2429        var color = WebInspector.Color.parse(text);
2430
2431        // We can be called with valid non-color values of |text| (like 'none' from border style)
2432        if (!color)
2433            return document.createTextNode(text);
2434
2435        var format = WebInspector.StylesSidebarPane._colorFormat(color);
2436        var spectrumHelper = this.editablePane() && this.editablePane()._spectrumHelper;
2437        var spectrum = spectrumHelper ? spectrumHelper.spectrum() : null;
2438
2439        var isEditable = !!(this._styleRule && this._styleRule.editable !== false); // |editable| is true by default.
2440        var colorSwatch = new WebInspector.ColorSwatch(!isEditable);
2441        colorSwatch.setColorString(text);
2442        colorSwatch.element.addEventListener("click", swatchClick.bind(this), false);
2443
2444        var scrollerElement;
2445        var boundSpectrumChanged = spectrumChanged.bind(this);
2446        var boundSpectrumHidden = spectrumHidden.bind(this);
2447
2448        /**
2449         * @param {!WebInspector.Event} e
2450         * @this {WebInspector.StylePropertyTreeElementBase}
2451         */
2452        function spectrumChanged(e)
2453        {
2454            var colorString = /** @type {string} */ (e.data);
2455            spectrum.displayText = colorString;
2456            colorValueElement.textContent = colorString;
2457            colorSwatch.setColorString(colorString);
2458            this.applyStyleText(nameElement.textContent + ": " + valueElement.textContent, false, false, false);
2459        }
2460
2461        /**
2462         * @param {!WebInspector.Event} event
2463         * @this {WebInspector.StylePropertyTreeElementBase}
2464         */
2465        function spectrumHidden(event)
2466        {
2467            if (scrollerElement)
2468                scrollerElement.removeEventListener("scroll", repositionSpectrum, false);
2469            var commitEdit = event.data;
2470            var propertyText = !commitEdit && this.originalPropertyText ? this.originalPropertyText : (nameElement.textContent + ": " + valueElement.textContent);
2471            this.applyStyleText(propertyText, true, true, false);
2472            spectrum.removeEventListener(WebInspector.Spectrum.Events.ColorChanged, boundSpectrumChanged);
2473            spectrumHelper.removeEventListener(WebInspector.SpectrumPopupHelper.Events.Hidden, boundSpectrumHidden);
2474
2475            delete this.editablePane()._isEditingStyle;
2476            delete this.originalPropertyText;
2477        }
2478
2479        function repositionSpectrum()
2480        {
2481            spectrumHelper.reposition(colorSwatch.element);
2482        }
2483
2484        /**
2485         * @param {!Event} e
2486         * @this {WebInspector.StylePropertyTreeElementBase}
2487         */
2488        function swatchClick(e)
2489        {
2490            e.consume(true);
2491
2492            // Shift + click toggles color formats.
2493            // Click opens colorpicker, only if the element is not in computed styles section.
2494            if (!spectrumHelper || e.shiftKey) {
2495                changeColorDisplay();
2496                return;
2497            }
2498
2499            if (!isEditable)
2500                return;
2501
2502            var visible = spectrumHelper.toggle(colorSwatch.element, color, format);
2503            if (visible) {
2504                spectrum.displayText = color.toString(format);
2505                this.originalPropertyText = this.property.propertyText;
2506                this.editablePane()._isEditingStyle = true;
2507                spectrum.addEventListener(WebInspector.Spectrum.Events.ColorChanged, boundSpectrumChanged);
2508                spectrumHelper.addEventListener(WebInspector.SpectrumPopupHelper.Events.Hidden, boundSpectrumHidden);
2509
2510                scrollerElement = colorSwatch.element.enclosingNodeOrSelfWithClass("style-panes-wrapper");
2511                if (scrollerElement)
2512                    scrollerElement.addEventListener("scroll", repositionSpectrum, false);
2513                else
2514                    console.error("Unable to handle color picker scrolling");
2515            }
2516        }
2517
2518        var colorValueElement = document.createElement("span");
2519        if (format === WebInspector.Color.Format.Original)
2520            colorValueElement.textContent = text;
2521        else
2522            colorValueElement.textContent = color.toString(format);
2523
2524        /**
2525         * @param {string} curFormat
2526         */
2527        function nextFormat(curFormat)
2528        {
2529            // The format loop is as follows:
2530            // * original
2531            // * rgb(a)
2532            // * hsl(a)
2533            // * nickname (if the color has a nickname)
2534            // * if the color is simple:
2535            //   - shorthex (if has short hex)
2536            //   - hex
2537            var cf = WebInspector.Color.Format;
2538
2539            switch (curFormat) {
2540                case cf.Original:
2541                    return !color.hasAlpha() ? cf.RGB : cf.RGBA;
2542
2543                case cf.RGB:
2544                case cf.RGBA:
2545                    return !color.hasAlpha() ? cf.HSL : cf.HSLA;
2546
2547                case cf.HSL:
2548                case cf.HSLA:
2549                    if (color.nickname())
2550                        return cf.Nickname;
2551                    if (!color.hasAlpha())
2552                        return color.canBeShortHex() ? cf.ShortHEX : cf.HEX;
2553                    else
2554                        return cf.Original;
2555
2556                case cf.ShortHEX:
2557                    return cf.HEX;
2558
2559                case cf.HEX:
2560                    return cf.Original;
2561
2562                case cf.Nickname:
2563                    if (!color.hasAlpha())
2564                        return color.canBeShortHex() ? cf.ShortHEX : cf.HEX;
2565                    else
2566                        return cf.Original;
2567
2568                default:
2569                    return cf.RGBA;
2570            }
2571        }
2572
2573        function changeColorDisplay()
2574        {
2575            do {
2576                format = nextFormat(format);
2577                var currentValue = color.toString(format);
2578            } while (currentValue === colorValueElement.textContent);
2579            colorValueElement.textContent = currentValue;
2580        }
2581
2582        var container = document.createElement("nobr");
2583        container.appendChild(colorSwatch.element);
2584        container.appendChild(colorValueElement);
2585        return container;
2586    },
2587
2588    updateState: function()
2589    {
2590        if (!this.listItemElement)
2591            return;
2592
2593        if (this.style.isPropertyImplicit(this.name))
2594            this.listItemElement.classList.add("implicit");
2595        else
2596            this.listItemElement.classList.remove("implicit");
2597
2598        if (this.hasIgnorableError())
2599            this.listItemElement.classList.add("has-ignorable-error");
2600        else
2601            this.listItemElement.classList.remove("has-ignorable-error");
2602
2603        if (this.inherited)
2604            this.listItemElement.classList.add("inherited");
2605        else
2606            this.listItemElement.classList.remove("inherited");
2607
2608        if (this.overloaded)
2609            this.listItemElement.classList.add("overloaded");
2610        else
2611            this.listItemElement.classList.remove("overloaded");
2612
2613        if (this.disabled)
2614            this.listItemElement.classList.add("disabled");
2615        else
2616            this.listItemElement.classList.remove("disabled");
2617    },
2618
2619    __proto__: TreeElement.prototype
2620}
2621
2622/**
2623 * @constructor
2624 * @extends {WebInspector.StylePropertyTreeElementBase}
2625 * @param {!WebInspector.StylesSidebarPane} stylesPane
2626 * @param {!Object} styleRule
2627 * @param {!WebInspector.CSSStyleDeclaration} style
2628 * @param {!WebInspector.CSSProperty} property
2629 * @param {boolean} inherited
2630 */
2631WebInspector.ComputedStylePropertyTreeElement = function(stylesPane, styleRule, style, property, inherited)
2632{
2633    WebInspector.StylePropertyTreeElementBase.call(this, styleRule, style, property, inherited, false, false);
2634    this._stylesPane = stylesPane;
2635}
2636
2637WebInspector.ComputedStylePropertyTreeElement.prototype = {
2638    /**
2639     * @return {?WebInspector.DOMNode}
2640     */
2641    node: function()
2642    {
2643        return this._stylesPane._node;
2644    },
2645
2646    /**
2647     * @return {?WebInspector.StylesSidebarPane}
2648     */
2649    editablePane: function()
2650    {
2651        return null;
2652    },
2653
2654    /**
2655     * @return {!WebInspector.ComputedStyleSidebarPane}
2656     */
2657    parentPane: function()
2658    {
2659        return this._stylesPane._computedStylePane;
2660    },
2661
2662    _updateFilter: function()
2663    {
2664        var regEx = this.parentPane().filterRegex();
2665        this.listItemElement.classList.toggle("hidden", !!regEx && (!regEx.test(this.property.name) && !regEx.test(this.property.value)));
2666    },
2667
2668    __proto__: WebInspector.StylePropertyTreeElementBase.prototype
2669}
2670
2671/**
2672 * @constructor
2673 * @extends {WebInspector.StylePropertyTreeElementBase}
2674 * @param {!WebInspector.StylesSidebarPane} stylesPane
2675 * @param {!Object} styleRule
2676 * @param {!WebInspector.CSSStyleDeclaration} style
2677 * @param {!WebInspector.CSSProperty} property
2678 * @param {boolean} isShorthand
2679 * @param {boolean} inherited
2680 * @param {boolean} overloaded
2681 */
2682WebInspector.StylePropertyTreeElement = function(stylesPane, styleRule, style, property, isShorthand, inherited, overloaded)
2683{
2684    WebInspector.StylePropertyTreeElementBase.call(this, styleRule, style, property, inherited, overloaded, isShorthand);
2685    this._parentPane = stylesPane;
2686    this.isShorthand = isShorthand;
2687    this._applyStyleThrottler = new WebInspector.Throttler(0);
2688}
2689
2690/** @typedef {{expanded: boolean, hasChildren: boolean, isEditingName: boolean, previousContent: string}} */
2691WebInspector.StylePropertyTreeElement.Context;
2692
2693WebInspector.StylePropertyTreeElement.prototype = {
2694    /**
2695     * @return {?WebInspector.DOMNode}
2696     */
2697    node: function()
2698    {
2699        return this._parentPane._node;
2700    },
2701
2702    /**
2703     * @return {?WebInspector.StylesSidebarPane}
2704     */
2705    editablePane: function()
2706    {
2707        return this._parentPane;
2708    },
2709
2710    /**
2711     * @return {!WebInspector.StylesSidebarPane}
2712     */
2713    parentPane: function()
2714    {
2715        return this._parentPane;
2716    },
2717
2718    /**
2719     * @return {?WebInspector.StylePropertiesSection}
2720     */
2721    section: function()
2722    {
2723        return this.treeOutline && this.treeOutline.section;
2724    },
2725
2726    /**
2727     * @param {function()=} userCallback
2728     */
2729    _updatePane: function(userCallback)
2730    {
2731        var section = this.section();
2732        if (section && section._parentPane)
2733            section._parentPane._refreshUpdate(section, false, userCallback);
2734        else  {
2735            if (userCallback)
2736                userCallback();
2737        }
2738    },
2739
2740    /**
2741     * @param {!WebInspector.CSSStyleDeclaration} newStyle
2742     */
2743    _applyNewStyle: function(newStyle)
2744    {
2745        newStyle.parentRule = this.style.parentRule;
2746        var oldStyleRange = /** @type {!WebInspector.TextRange} */ (this.style.range);
2747        var newStyleRange = /** @type {!WebInspector.TextRange} */ (newStyle.range);
2748        this.style = newStyle;
2749        this._styleRule.style = newStyle;
2750        if (this.style.parentRule) {
2751            this.style.parentRule.style = this.style;
2752            this._parentPane._styleSheetRuleEdited(this.style.parentRule, oldStyleRange, newStyleRange);
2753        }
2754    },
2755
2756    /**
2757     * @param {!Event} event
2758     */
2759    toggleEnabled: function(event)
2760    {
2761        var disabled = !event.target.checked;
2762
2763        /**
2764         * @param {?WebInspector.CSSStyleDeclaration} newStyle
2765         * @this {WebInspector.StylePropertyTreeElement}
2766         */
2767        function callback(newStyle)
2768        {
2769            delete this._parentPane._userOperation;
2770
2771            if (!newStyle)
2772                return;
2773            this._applyNewStyle(newStyle);
2774
2775            var section = this.section();
2776            if (section && section._parentPane)
2777                section._parentPane.dispatchEventToListeners("style property toggled");
2778
2779            this._updatePane();
2780        }
2781
2782        this._parentPane._userOperation = true;
2783        this.property.setDisabled(disabled, callback.bind(this));
2784        event.consume();
2785    },
2786
2787    onpopulate: function()
2788    {
2789        // Only populate once and if this property is a shorthand.
2790        if (this.children.length || !this.isShorthand)
2791            return;
2792
2793        var longhandProperties = this.style.longhandProperties(this.name);
2794        for (var i = 0; i < longhandProperties.length; ++i) {
2795            var name = longhandProperties[i].name;
2796            var inherited = false;
2797            var overloaded = false;
2798
2799            var section = this.section();
2800            if (section) {
2801                inherited = section.isPropertyInherited(name);
2802                overloaded = section.isPropertyOverloaded(name);
2803            }
2804
2805            var liveProperty = this.style.getLiveProperty(name);
2806            if (!liveProperty)
2807                continue;
2808
2809            var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this._styleRule, this.style, liveProperty, false, inherited, overloaded);
2810            this.appendChild(item);
2811        }
2812    },
2813
2814    onattach: function()
2815    {
2816        WebInspector.StylePropertyTreeElementBase.prototype.onattach.call(this);
2817
2818        this.listItemElement.addEventListener("mousedown", this._mouseDown.bind(this));
2819        this.listItemElement.addEventListener("mouseup", this._resetMouseDownElement.bind(this));
2820        this.listItemElement.addEventListener("click", this._mouseClick.bind(this));
2821    },
2822
2823    _mouseDown: function(event)
2824    {
2825        if (this._parentPane) {
2826            this._parentPane._mouseDownTreeElement = this;
2827            this._parentPane._mouseDownTreeElementIsName = this._isNameElement(event.target);
2828            this._parentPane._mouseDownTreeElementIsValue = this._isValueElement(event.target);
2829        }
2830    },
2831
2832    _resetMouseDownElement: function()
2833    {
2834        if (this._parentPane) {
2835            delete this._parentPane._mouseDownTreeElement;
2836            delete this._parentPane._mouseDownTreeElementIsName;
2837            delete this._parentPane._mouseDownTreeElementIsValue;
2838        }
2839    },
2840
2841    updateTitle: function()
2842    {
2843        WebInspector.StylePropertyTreeElementBase.prototype.updateTitle.call(this);
2844
2845        if (this.parsedOk && this.section() && this.parent.root) {
2846            var enabledCheckboxElement = document.createElement("input");
2847            enabledCheckboxElement.className = "enabled-button";
2848            enabledCheckboxElement.type = "checkbox";
2849            enabledCheckboxElement.checked = !this.disabled;
2850            enabledCheckboxElement.addEventListener("click", this.toggleEnabled.bind(this), false);
2851            this.listItemElement.insertBefore(enabledCheckboxElement, this.listItemElement.firstChild);
2852        }
2853    },
2854
2855    _mouseClick: function(event)
2856    {
2857        if (!window.getSelection().isCollapsed)
2858            return;
2859
2860        event.consume(true);
2861
2862        if (event.target === this.listItemElement) {
2863            var section = this.section();
2864            if (!section || !section.editable)
2865                return;
2866
2867            if (section._checkWillCancelEditing())
2868                return;
2869            section.addNewBlankProperty(this.property.index + 1).startEditing();
2870            return;
2871        }
2872
2873        if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) && this.section().navigable) {
2874            this._navigateToSource(event.target);
2875            return;
2876        }
2877
2878        this.startEditing(event.target);
2879    },
2880
2881    /**
2882     * @param {!Element} element
2883     */
2884    _navigateToSource: function(element)
2885    {
2886        console.assert(this.section().navigable);
2887        var propertyNameClicked = element === this.nameElement;
2888        WebInspector.Revealer.reveal(WebInspector.cssWorkspaceBinding.propertyUILocation(this.property, propertyNameClicked));
2889    },
2890
2891    /**
2892     * @param {!Element} element
2893     */
2894    _isNameElement: function(element)
2895    {
2896        return element.enclosingNodeOrSelfWithClass("webkit-css-property") === this.nameElement;
2897    },
2898
2899    /**
2900     * @param {!Element} element
2901     */
2902    _isValueElement: function(element)
2903    {
2904        return !!element.enclosingNodeOrSelfWithClass("value");
2905    },
2906
2907    /**
2908     * @param {?Element=} selectElement
2909     */
2910    startEditing: function(selectElement)
2911    {
2912        // FIXME: we don't allow editing of longhand properties under a shorthand right now.
2913        if (this.parent.isShorthand)
2914            return;
2915
2916        if (selectElement === this._expandElement)
2917            return;
2918
2919        var section = this.section();
2920        if (section && !section.editable)
2921            return;
2922
2923        if (!selectElement)
2924            selectElement = this.nameElement; // No arguments passed in - edit the name element by default.
2925        else
2926            selectElement = selectElement.enclosingNodeOrSelfWithClass("webkit-css-property") || selectElement.enclosingNodeOrSelfWithClass("value");
2927
2928        if (WebInspector.isBeingEdited(selectElement))
2929            return;
2930
2931        var isEditingName = selectElement === this.nameElement;
2932        if (!isEditingName)
2933            this.valueElement.textContent = restoreURLs(this.valueElement.textContent, this.value);
2934
2935        /**
2936         * @param {string} fieldValue
2937         * @param {string} modelValue
2938         * @return {string}
2939         */
2940        function restoreURLs(fieldValue, modelValue)
2941        {
2942            const urlRegex = /\b(url\([^)]*\))/g;
2943            var splitFieldValue = fieldValue.split(urlRegex);
2944            if (splitFieldValue.length === 1)
2945                return fieldValue;
2946            var modelUrlRegex = new RegExp(urlRegex);
2947            for (var i = 1; i < splitFieldValue.length; i += 2) {
2948                var match = modelUrlRegex.exec(modelValue);
2949                if (match)
2950                    splitFieldValue[i] = match[0];
2951            }
2952            return splitFieldValue.join("");
2953        }
2954
2955        /** @type {!WebInspector.StylePropertyTreeElement.Context} */
2956        var context = {
2957            expanded: this.expanded,
2958            hasChildren: this.hasChildren,
2959            isEditingName: isEditingName,
2960            previousContent: selectElement.textContent
2961        };
2962
2963        // Lie about our children to prevent expanding on double click and to collapse shorthands.
2964        this.hasChildren = false;
2965
2966        if (selectElement.parentElement)
2967            selectElement.parentElement.classList.add("child-editing");
2968        selectElement.textContent = selectElement.textContent; // remove color swatch and the like
2969
2970        /**
2971         * @param {!WebInspector.StylePropertyTreeElement.Context} context
2972         * @param {!Event} event
2973         * @this {WebInspector.StylePropertyTreeElement}
2974         */
2975        function pasteHandler(context, event)
2976        {
2977            var data = event.clipboardData.getData("Text");
2978            if (!data)
2979                return;
2980            var colonIdx = data.indexOf(":");
2981            if (colonIdx < 0)
2982                return;
2983            var name = data.substring(0, colonIdx).trim();
2984            var value = data.substring(colonIdx + 1).trim();
2985
2986            event.preventDefault();
2987
2988            if (!("originalName" in context)) {
2989                context.originalName = this.nameElement.textContent;
2990                context.originalValue = this.valueElement.textContent;
2991            }
2992            this.property.name = name;
2993            this.property.value = value;
2994            this.nameElement.textContent = name;
2995            this.valueElement.textContent = value;
2996            this.nameElement.normalize();
2997            this.valueElement.normalize();
2998
2999            this.editingCommitted(event.target.textContent, context, "forward");
3000        }
3001
3002        /**
3003         * @param {!WebInspector.StylePropertyTreeElement.Context} context
3004         * @param {!Event} event
3005         * @this {WebInspector.StylePropertyTreeElement}
3006         */
3007        function blurListener(context, event)
3008        {
3009            var treeElement = this._parentPane._mouseDownTreeElement;
3010            var moveDirection = "";
3011            if (treeElement === this) {
3012                if (isEditingName && this._parentPane._mouseDownTreeElementIsValue)
3013                    moveDirection = "forward";
3014                if (!isEditingName && this._parentPane._mouseDownTreeElementIsName)
3015                    moveDirection = "backward";
3016            }
3017            this.editingCommitted(event.target.textContent, context, moveDirection);
3018        }
3019
3020        delete this.originalPropertyText;
3021
3022        this._parentPane._isEditingStyle = true;
3023        if (selectElement.parentElement)
3024            selectElement.parentElement.scrollIntoViewIfNeeded(false);
3025
3026        var applyItemCallback = !isEditingName ? this._applyFreeFlowStyleTextEdit.bind(this) : undefined;
3027        this._prompt = new WebInspector.StylesSidebarPane.CSSPropertyPrompt(isEditingName ? WebInspector.CSSMetadata.cssPropertiesMetainfo : WebInspector.CSSMetadata.keywordsForProperty(this.nameElement.textContent), this, isEditingName);
3028        if (applyItemCallback) {
3029            this._prompt.addEventListener(WebInspector.TextPrompt.Events.ItemApplied, applyItemCallback, this);
3030            this._prompt.addEventListener(WebInspector.TextPrompt.Events.ItemAccepted, applyItemCallback, this);
3031        }
3032        var proxyElement = this._prompt.attachAndStartEditing(selectElement, blurListener.bind(this, context));
3033
3034        proxyElement.addEventListener("keydown", this._editingNameValueKeyDown.bind(this, context), false);
3035        proxyElement.addEventListener("keypress", this._editingNameValueKeyPress.bind(this, context), false);
3036        proxyElement.addEventListener("input", this._editingNameValueInput.bind(this, context), false);
3037        if (isEditingName)
3038            proxyElement.addEventListener("paste", pasteHandler.bind(this, context), false);
3039
3040        window.getSelection().setBaseAndExtent(selectElement, 0, selectElement, 1);
3041    },
3042
3043    /**
3044     * @param {!WebInspector.StylePropertyTreeElement.Context} context
3045     * @param {!Event} event
3046     */
3047    _editingNameValueKeyDown: function(context, event)
3048    {
3049        if (event.handled)
3050            return;
3051
3052        var result;
3053
3054        if (isEnterKey(event)) {
3055            event.preventDefault();
3056            result = "forward";
3057        } else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code || event.keyIdentifier === "U+001B")
3058            result = "cancel";
3059        else if (!context.isEditingName && this._newProperty && event.keyCode === WebInspector.KeyboardShortcut.Keys.Backspace.code) {
3060            // For a new property, when Backspace is pressed at the beginning of new property value, move back to the property name.
3061            var selection = window.getSelection();
3062            if (selection.isCollapsed && !selection.focusOffset) {
3063                event.preventDefault();
3064                result = "backward";
3065            }
3066        } else if (event.keyIdentifier === "U+0009") { // Tab key.
3067            result = event.shiftKey ? "backward" : "forward";
3068            event.preventDefault();
3069        }
3070
3071        if (result) {
3072            switch (result) {
3073            case "cancel":
3074                this.editingCancelled(null, context);
3075                break;
3076            case "forward":
3077            case "backward":
3078                this.editingCommitted(event.target.textContent, context, result);
3079                break;
3080            }
3081
3082            event.consume();
3083            return;
3084        }
3085    },
3086
3087    /**
3088     * @param {!WebInspector.StylePropertyTreeElement.Context} context
3089     * @param {!Event} event
3090     */
3091    _editingNameValueKeyPress: function(context, event)
3092    {
3093        function shouldCommitValueSemicolon(text, cursorPosition)
3094        {
3095            // FIXME: should this account for semicolons inside comments?
3096            var openQuote = "";
3097            for (var i = 0; i < cursorPosition; ++i) {
3098                var ch = text[i];
3099                if (ch === "\\" && openQuote !== "")
3100                    ++i; // skip next character inside string
3101                else if (!openQuote && (ch === "\"" || ch === "'"))
3102                    openQuote = ch;
3103                else if (openQuote === ch)
3104                    openQuote = "";
3105            }
3106            return !openQuote;
3107        }
3108
3109        var keyChar = String.fromCharCode(event.charCode);
3110        var isFieldInputTerminated = (context.isEditingName ? keyChar === ":" : keyChar === ";" && shouldCommitValueSemicolon(event.target.textContent, event.target.selectionLeftOffset()));
3111        if (isFieldInputTerminated) {
3112            // Enter or colon (for name)/semicolon outside of string (for value).
3113            event.consume(true);
3114            this.editingCommitted(event.target.textContent, context, "forward");
3115            return;
3116        }
3117    },
3118
3119    /**
3120     * @param {!WebInspector.StylePropertyTreeElement.Context} context
3121     * @param {!Event} event
3122     */
3123    _editingNameValueInput: function(context, event)
3124    {
3125        if (!context.isEditingName)
3126            this._applyFreeFlowStyleTextEdit();
3127    },
3128
3129    _applyFreeFlowStyleTextEdit: function()
3130    {
3131        var valueText = this.valueElement.textContent;
3132        if (valueText.indexOf(";") === -1)
3133            this.applyStyleText(this.nameElement.textContent + ": " + valueText, false, false, false);
3134    },
3135
3136    kickFreeFlowStyleEditForTest: function()
3137    {
3138        this._applyFreeFlowStyleTextEdit();
3139    },
3140
3141    editingEnded: function(context)
3142    {
3143        this._resetMouseDownElement();
3144
3145        this.hasChildren = context.hasChildren;
3146        if (context.expanded)
3147            this.expand();
3148        var editedElement = context.isEditingName ? this.nameElement : this.valueElement;
3149        // The proxyElement has been deleted, no need to remove listener.
3150        if (editedElement.parentElement)
3151            editedElement.parentElement.classList.remove("child-editing");
3152
3153        delete this._parentPane._isEditingStyle;
3154    },
3155
3156    editingCancelled: function(element, context)
3157    {
3158        this._removePrompt();
3159        this._revertStyleUponEditingCanceled(this.originalPropertyText);
3160        // This should happen last, as it clears the info necessary to restore the property value after [Page]Up/Down changes.
3161        this.editingEnded(context);
3162    },
3163
3164    _revertStyleUponEditingCanceled: function(originalPropertyText)
3165    {
3166        if (typeof originalPropertyText === "string") {
3167            delete this.originalPropertyText;
3168            this.applyStyleText(originalPropertyText, true, false, true);
3169        } else {
3170            if (this._newProperty)
3171                this.treeOutline.removeChild(this);
3172            else
3173                this.updateTitle();
3174        }
3175    },
3176
3177    _findSibling: function(moveDirection)
3178    {
3179        var target = this;
3180        do {
3181            target = (moveDirection === "forward" ? target.nextSibling : target.previousSibling);
3182        } while(target && target.inherited);
3183
3184        return target;
3185    },
3186
3187    /**
3188     * @param {string} userInput
3189     * @param {!Object} context
3190     * @param {string} moveDirection
3191     */
3192    editingCommitted: function(userInput, context, moveDirection)
3193    {
3194        this._removePrompt();
3195        this.editingEnded(context);
3196        var isEditingName = context.isEditingName;
3197
3198        // Determine where to move to before making changes
3199        var createNewProperty, moveToPropertyName, moveToSelector;
3200        var isDataPasted = "originalName" in context;
3201        var isDirtyViaPaste = isDataPasted && (this.nameElement.textContent !== context.originalName || this.valueElement.textContent !== context.originalValue);
3202        var isPropertySplitPaste = isDataPasted && isEditingName && this.valueElement.textContent !== context.originalValue;
3203        var moveTo = this;
3204        var moveToOther = (isEditingName ^ (moveDirection === "forward"));
3205        var abandonNewProperty = this._newProperty && !userInput && (moveToOther || isEditingName);
3206        if (moveDirection === "forward" && (!isEditingName || isPropertySplitPaste) || moveDirection === "backward" && isEditingName) {
3207            moveTo = moveTo._findSibling(moveDirection);
3208            if (moveTo)
3209                moveToPropertyName = moveTo.name;
3210            else if (moveDirection === "forward" && (!this._newProperty || userInput))
3211                createNewProperty = true;
3212            else if (moveDirection === "backward")
3213                moveToSelector = true;
3214        }
3215
3216        // Make the Changes and trigger the moveToNextCallback after updating.
3217        var moveToIndex = moveTo && this.treeOutline ? this.treeOutline.children.indexOf(moveTo) : -1;
3218        var blankInput = /^\s*$/.test(userInput);
3219        var shouldCommitNewProperty = this._newProperty && (isPropertySplitPaste || moveToOther || (!moveDirection && !isEditingName) || (isEditingName && blankInput));
3220        var section = this.section();
3221        if (((userInput !== context.previousContent || isDirtyViaPaste) && !this._newProperty) || shouldCommitNewProperty) {
3222            section._afterUpdate = moveToNextCallback.bind(this, this._newProperty, !blankInput, section);
3223            var propertyText;
3224            if (blankInput || (this._newProperty && /^\s*$/.test(this.valueElement.textContent)))
3225                propertyText = "";
3226            else {
3227                if (isEditingName)
3228                    propertyText = userInput + ": " + this.property.value;
3229                else
3230                    propertyText = this.property.name + ": " + userInput;
3231            }
3232            this.applyStyleText(propertyText, true, true, false);
3233        } else {
3234            if (isEditingName)
3235                this.property.name = userInput;
3236            else
3237                this.property.value = userInput;
3238            if (!isDataPasted && !this._newProperty)
3239                this.updateTitle();
3240            moveToNextCallback.call(this, this._newProperty, false, section);
3241        }
3242
3243        /**
3244         * The Callback to start editing the next/previous property/selector.
3245         * @this {WebInspector.StylePropertyTreeElement}
3246         */
3247        function moveToNextCallback(alreadyNew, valueChanged, section)
3248        {
3249            if (!moveDirection)
3250                return;
3251
3252            // User just tabbed through without changes.
3253            if (moveTo && moveTo.parent) {
3254                moveTo.startEditing(!isEditingName ? moveTo.nameElement : moveTo.valueElement);
3255                return;
3256            }
3257
3258            // User has made a change then tabbed, wiping all the original treeElements.
3259            // Recalculate the new treeElement for the same property we were going to edit next.
3260            if (moveTo && !moveTo.parent) {
3261                var propertyElements = section.propertiesTreeOutline.children;
3262                if (moveDirection === "forward" && blankInput && !isEditingName)
3263                    --moveToIndex;
3264                if (moveToIndex >= propertyElements.length && !this._newProperty)
3265                    createNewProperty = true;
3266                else {
3267                    var treeElement = moveToIndex >= 0 ? propertyElements[moveToIndex] : null;
3268                    if (treeElement) {
3269                        var elementToEdit = !isEditingName || isPropertySplitPaste ? treeElement.nameElement : treeElement.valueElement;
3270                        if (alreadyNew && blankInput)
3271                            elementToEdit = moveDirection === "forward" ? treeElement.nameElement : treeElement.valueElement;
3272                        treeElement.startEditing(elementToEdit);
3273                        return;
3274                    } else if (!alreadyNew)
3275                        moveToSelector = true;
3276                }
3277            }
3278
3279            // Create a new attribute in this section (or move to next editable selector if possible).
3280            if (createNewProperty) {
3281                if (alreadyNew && !valueChanged && (isEditingName ^ (moveDirection === "backward")))
3282                    return;
3283
3284                section.addNewBlankProperty().startEditing();
3285                return;
3286            }
3287
3288            if (abandonNewProperty) {
3289                moveTo = this._findSibling(moveDirection);
3290                var sectionToEdit = (moveTo || moveDirection === "backward") ? section : section.nextEditableSibling();
3291                if (sectionToEdit) {
3292                    if (sectionToEdit.rule)
3293                        sectionToEdit.startEditingSelector();
3294                    else
3295                        sectionToEdit._moveEditorFromSelector(moveDirection);
3296                }
3297                return;
3298            }
3299
3300            if (moveToSelector) {
3301                if (section.rule)
3302                    section.startEditingSelector();
3303                else
3304                    section._moveEditorFromSelector(moveDirection);
3305            }
3306        }
3307    },
3308
3309    _removePrompt: function()
3310    {
3311        // BUG 53242. This cannot go into editingEnded(), as it should always happen first for any editing outcome.
3312        if (this._prompt) {
3313            this._prompt.detach();
3314            delete this._prompt;
3315        }
3316    },
3317
3318    _hasBeenModifiedIncrementally: function()
3319    {
3320        // New properties applied via up/down or live editing have an originalPropertyText and will be deleted later
3321        // on, if cancelled, when the empty string gets applied as their style text.
3322        return typeof this.originalPropertyText === "string" || (!!this.property.propertyText && this._newProperty);
3323    },
3324
3325    styleTextAppliedForTest: function()
3326    {
3327    },
3328
3329    /**
3330     * @param {string} styleText
3331     * @param {boolean} updateInterface
3332     * @param {boolean} majorChange
3333     * @param {boolean} isRevert
3334     */
3335    applyStyleText: function(styleText, updateInterface, majorChange, isRevert)
3336    {
3337        this._applyStyleThrottler.schedule(this._innerApplyStyleText.bind(this, styleText, updateInterface, majorChange, isRevert));
3338    },
3339
3340    /**
3341     * @param {string} styleText
3342     * @param {boolean} updateInterface
3343     * @param {boolean} majorChange
3344     * @param {boolean} isRevert
3345     * @param {!WebInspector.Throttler.FinishCallback} finishedCallback
3346     */
3347    _innerApplyStyleText: function(styleText, updateInterface, majorChange, isRevert, finishedCallback)
3348    {
3349        /**
3350         * @param {!WebInspector.StylesSidebarPane} parentPane
3351         * @param {boolean} updateInterface
3352         */
3353        function userOperationFinishedCallback(parentPane, updateInterface)
3354        {
3355            if (updateInterface)
3356                delete parentPane._userOperation;
3357            finishedCallback();
3358        }
3359
3360        // Leave a way to cancel editing after incremental changes.
3361        if (!isRevert && !updateInterface && !this._hasBeenModifiedIncrementally()) {
3362            // Remember the rule's original CSS text on [Page](Up|Down), so it can be restored
3363            // if the editing is canceled.
3364            this.originalPropertyText = this.property.propertyText;
3365        }
3366
3367        if (!this.treeOutline)
3368            return;
3369
3370        var section = this.section();
3371        styleText = styleText.replace(/\s/g, " ").trim(); // Replace &nbsp; with whitespace.
3372        var styleTextLength = styleText.length;
3373        if (!styleTextLength && updateInterface && !isRevert && this._newProperty && !this._hasBeenModifiedIncrementally()) {
3374            // The user deleted everything and never applied a new property value via Up/Down scrolling/live editing, so remove the tree element and update.
3375            this.parent.removeChild(this);
3376            section.afterUpdate();
3377            return;
3378        }
3379
3380        var currentNode = this._parentPane._node;
3381        if (updateInterface)
3382            this._parentPane._userOperation = true;
3383
3384        /**
3385         * @param {function()} userCallback
3386         * @param {string} originalPropertyText
3387         * @param {?WebInspector.CSSStyleDeclaration} newStyle
3388         * @this {WebInspector.StylePropertyTreeElement}
3389         */
3390        function callback(userCallback, originalPropertyText, newStyle)
3391        {
3392            if (!newStyle) {
3393                if (updateInterface) {
3394                    // It did not apply, cancel editing.
3395                    this._revertStyleUponEditingCanceled(originalPropertyText);
3396                }
3397                userCallback();
3398                this.styleTextAppliedForTest();
3399                return;
3400            }
3401            this._applyNewStyle(newStyle);
3402
3403            if (this._newProperty)
3404                this._newPropertyInStyle = true;
3405
3406            this.property = newStyle.propertyAt(this.property.index);
3407            if (section && section._parentPane)
3408                section._parentPane.dispatchEventToListeners("style edited");
3409
3410            if (updateInterface && currentNode === this.node()) {
3411                this._updatePane(userCallback);
3412                this.styleTextAppliedForTest();
3413                return;
3414            }
3415
3416            userCallback();
3417            this.styleTextAppliedForTest();
3418        }
3419
3420        // Append a ";" if the new text does not end in ";".
3421        // FIXME: this does not handle trailing comments.
3422        if (styleText.length && !/;\s*$/.test(styleText))
3423            styleText += ";";
3424        var overwriteProperty = !!(!this._newProperty || this._newPropertyInStyle);
3425        var boundCallback = callback.bind(this, userOperationFinishedCallback.bind(null, this._parentPane, updateInterface), this.originalPropertyText);
3426        if (overwriteProperty && styleText === this.property.propertyText)
3427            boundCallback.call(null, this.property.ownerStyle)
3428        else
3429            this.property.setText(styleText, majorChange, overwriteProperty, boundCallback);
3430    },
3431
3432    /**
3433     * @return {boolean}
3434     */
3435    ondblclick: function()
3436    {
3437        return true; // handled
3438    },
3439
3440    /**
3441     * @param {!Event} event
3442     * @return {boolean}
3443     */
3444    isEventWithinDisclosureTriangle: function(event)
3445    {
3446        return event.target === this._expandElement;
3447    },
3448
3449    __proto__: WebInspector.StylePropertyTreeElementBase.prototype
3450}
3451
3452/**
3453 * @constructor
3454 * @extends {WebInspector.TextPrompt}
3455 * @param {!WebInspector.CSSMetadata} cssCompletions
3456 * @param {!WebInspector.StylePropertyTreeElement} sidebarPane
3457 * @param {boolean} isEditingName
3458 */
3459WebInspector.StylesSidebarPane.CSSPropertyPrompt = function(cssCompletions, sidebarPane, isEditingName)
3460{
3461    // Use the same callback both for applyItemCallback and acceptItemCallback.
3462    WebInspector.TextPrompt.call(this, this._buildPropertyCompletions.bind(this), WebInspector.StyleValueDelimiters);
3463    this.setSuggestBoxEnabled(true);
3464    this._cssCompletions = cssCompletions;
3465    this._sidebarPane = sidebarPane;
3466    this._isEditingName = isEditingName;
3467
3468    if (!isEditingName)
3469        this.disableDefaultSuggestionForEmptyInput();
3470}
3471
3472WebInspector.StylesSidebarPane.CSSPropertyPrompt.prototype = {
3473    /**
3474     * @param {!Event} event
3475     */
3476    onKeyDown: function(event)
3477    {
3478        switch (event.keyIdentifier) {
3479        case "Up":
3480        case "Down":
3481        case "PageUp":
3482        case "PageDown":
3483            if (this._handleNameOrValueUpDown(event)) {
3484                event.preventDefault();
3485                return;
3486            }
3487            break;
3488        case "Enter":
3489            if (this.autoCompleteElement && !this.autoCompleteElement.textContent.length) {
3490                this.tabKeyPressed();
3491                return;
3492            }
3493            break;
3494        }
3495
3496        WebInspector.TextPrompt.prototype.onKeyDown.call(this, event);
3497    },
3498
3499    onMouseWheel: function(event)
3500    {
3501        if (this._handleNameOrValueUpDown(event)) {
3502            event.consume(true);
3503            return;
3504        }
3505        WebInspector.TextPrompt.prototype.onMouseWheel.call(this, event);
3506    },
3507
3508    /**
3509     * @override
3510     * @return {boolean}
3511     */
3512    tabKeyPressed: function()
3513    {
3514        this.acceptAutoComplete();
3515
3516        // Always tab to the next field.
3517        return false;
3518    },
3519
3520    /**
3521     * @param {!Event} event
3522     * @return {boolean}
3523     */
3524    _handleNameOrValueUpDown: function(event)
3525    {
3526        /**
3527         * @param {string} originalValue
3528         * @param {string} replacementString
3529         * @this {WebInspector.StylesSidebarPane.CSSPropertyPrompt}
3530         */
3531        function finishHandler(originalValue, replacementString)
3532        {
3533            // Synthesize property text disregarding any comments, custom whitespace etc.
3534            this._sidebarPane.applyStyleText(this._sidebarPane.nameElement.textContent + ": " + this._sidebarPane.valueElement.textContent, false, false, false);
3535        }
3536
3537        /**
3538         * @param {string} prefix
3539         * @param {number} number
3540         * @param {string} suffix
3541         * @return {string}
3542         * @this {WebInspector.StylesSidebarPane.CSSPropertyPrompt}
3543         */
3544        function customNumberHandler(prefix, number, suffix)
3545        {
3546            if (number !== 0 && !suffix.length && WebInspector.CSSMetadata.isLengthProperty(this._sidebarPane.property.name))
3547                suffix = "px";
3548            return prefix + number + suffix;
3549        }
3550
3551        // Handle numeric value increment/decrement only at this point.
3552        if (!this._isEditingName && WebInspector.handleElementValueModifications(event, this._sidebarPane.valueElement, finishHandler.bind(this), this._isValueSuggestion.bind(this), customNumberHandler.bind(this)))
3553            return true;
3554
3555        return false;
3556    },
3557
3558    /**
3559     * @param {string} word
3560     * @return {boolean}
3561     */
3562    _isValueSuggestion: function(word)
3563    {
3564        if (!word)
3565            return false;
3566        word = word.toLowerCase();
3567        return this._cssCompletions.keySet().hasOwnProperty(word);
3568    },
3569
3570    /**
3571     * @param {!Element} proxyElement
3572     * @param {!Range} wordRange
3573     * @param {boolean} force
3574     * @param {function(!Array.<string>, number=)} completionsReadyCallback
3575     */
3576    _buildPropertyCompletions: function(proxyElement, wordRange, force, completionsReadyCallback)
3577    {
3578        var prefix = wordRange.toString().toLowerCase();
3579        if (!prefix && !force && (this._isEditingName || proxyElement.textContent.length)) {
3580            completionsReadyCallback([]);
3581            return;
3582        }
3583
3584        var results = this._cssCompletions.startsWith(prefix);
3585        var userEnteredText = wordRange.toString().replace("-", "");
3586        if (userEnteredText && (userEnteredText === userEnteredText.toUpperCase())) {
3587            for (var i = 0; i < results.length; ++i)
3588                results[i] = results[i].toUpperCase();
3589        }
3590        var selectedIndex = this._cssCompletions.mostUsedOf(results);
3591        completionsReadyCallback(results, selectedIndex);
3592    },
3593
3594    __proto__: WebInspector.TextPrompt.prototype
3595}
3596