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