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 30WebInspector.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 84WebInspector.StylesSidebarPane.StyleValueDelimiters = " \t\n\"':;,/()"; 85 86// Taken from http://www.w3.org/TR/CSS21/propidx.html. 87WebInspector.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. 99WebInspector.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 113WebInspector.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 568WebInspector.StylesSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; 569 570WebInspector.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 593WebInspector.ComputedStyleSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; 594 595WebInspector.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 667WebInspector.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 971WebInspector.StylePropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype; 972 973WebInspector.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 987WebInspector.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 1071WebInspector.ComputedStylePropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype; 1072 1073WebInspector.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 1079WebInspector.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 1122WebInspector.BlankStylePropertiesSection.prototype.__proto__ = WebInspector.StylePropertiesSection.prototype; 1123 1124WebInspector.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 1138WebInspector.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 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 1960WebInspector.StylePropertyTreeElement.prototype.__proto__ = TreeElement.prototype; 1961 1962WebInspector.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 1968WebInspector.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 2035WebInspector.StylesSidebarPane.CSSPropertyPrompt.prototype.__proto__ = WebInspector.TextPrompt.prototype; 2036