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