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