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