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