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