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