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 WebInspector.StylesSidebarPane = function() 31 { 32 WebInspector.SidebarPane.call(this, WebInspector.UIString("Styles")); 33 34 this.settingsSelectElement = document.createElement("select"); 35 36 var option = document.createElement("option"); 37 option.value = "hex"; 38 option.action = this._changeColorFormat.bind(this); 39 option.label = WebInspector.UIString("Hex Colors"); 40 this.settingsSelectElement.appendChild(option); 41 42 option = document.createElement("option"); 43 option.value = "rgb"; 44 option.action = this._changeColorFormat.bind(this); 45 option.label = WebInspector.UIString("RGB Colors"); 46 this.settingsSelectElement.appendChild(option); 47 48 option = document.createElement("option"); 49 option.value = "hsl"; 50 option.action = this._changeColorFormat.bind(this); 51 option.label = WebInspector.UIString("HSL Colors"); 52 this.settingsSelectElement.appendChild(option); 53 54 this.settingsSelectElement.appendChild(document.createElement("hr")); 55 56 option = document.createElement("option"); 57 option.action = this._createNewRule.bind(this); 58 option.label = WebInspector.UIString("New Style Rule"); 59 this.settingsSelectElement.appendChild(option); 60 61 this.settingsSelectElement.addEventListener("click", function(event) { event.stopPropagation() }, false); 62 this.settingsSelectElement.addEventListener("change", this._changeSetting.bind(this), false); 63 WebInspector.settings.addEventListener("loaded", this._settingsLoaded, this); 64 65 this.titleElement.appendChild(this.settingsSelectElement); 66 } 67 68 WebInspector.StylesSidebarPane.prototype = { 69 _settingsLoaded: function() 70 { 71 var format = WebInspector.settings.colorFormat; 72 if (format === "hex") 73 this.settingsSelectElement[0].selected = true; 74 if (format === "rgb") 75 this.settingsSelectElement[1].selected = true; 76 if (format === "hsl") 77 this.settingsSelectElement[2].selected = true; 78 }, 79 80 update: function(node, editedSection, forceUpdate) 81 { 82 var refresh = false; 83 84 if (forceUpdate) 85 delete this.node; 86 87 if (!forceUpdate && (!node || node === this.node)) 88 refresh = true; 89 90 if (node && node.nodeType === Node.TEXT_NODE && node.parentNode) 91 node = node.parentNode; 92 93 if (node && node.nodeType !== Node.ELEMENT_NODE) 94 node = null; 95 96 if (node) 97 this.node = node; 98 else 99 node = this.node; 100 101 var body = this.bodyElement; 102 103 if (!node) { 104 body.removeChildren(); 105 this.sections = []; 106 return; 107 } 108 109 var self = this; 110 function callback(styles) 111 { 112 if (!styles) 113 return; 114 node._setStyles(styles.computedStyle, styles.inlineStyle, styles.styleAttributes, styles.matchedCSSRules); 115 self._update(refresh, body, node, editedSection, forceUpdate); 116 } 117 118 InjectedScriptAccess.get(node.injectedScriptId).getStyles(node.id, !WebInspector.settings.showUserAgentStyles, callback); 119 }, 120 121 _update: function(refresh, body, node, editedSection, forceUpdate) 122 { 123 if (!refresh) { 124 body.removeChildren(); 125 this.sections = []; 126 } 127 128 var styleRules = []; 129 130 if (refresh) { 131 for (var i = 0; i < this.sections.length; ++i) { 132 var section = this.sections[i]; 133 if (section instanceof WebInspector.BlankStylePropertiesSection) 134 continue; 135 if (section.computedStyle) 136 section.styleRule.style = node.ownerDocument.defaultView.getComputedStyle(node); 137 var styleRule = { section: section, style: section.styleRule.style, computedStyle: section.computedStyle, rule: section.rule }; 138 styleRules.push(styleRule); 139 } 140 } else { 141 var computedStyle = node.ownerDocument.defaultView.getComputedStyle(node); 142 styleRules.push({ computedStyle: true, selectorText: WebInspector.UIString("Computed Style"), style: computedStyle, editable: false }); 143 144 var nodeName = node.nodeName.toLowerCase(); 145 for (var i = 0; i < node.attributes.length; ++i) { 146 var attr = node.attributes[i]; 147 if (attr.style) { 148 var attrStyle = { style: attr.style, editable: false }; 149 attrStyle.subtitle = WebInspector.UIString("elements %s attribute", attr.name); 150 attrStyle.selectorText = nodeName + "[" + attr.name; 151 if (attr.value.length) 152 attrStyle.selectorText += "=" + attr.value; 153 attrStyle.selectorText += "]"; 154 styleRules.push(attrStyle); 155 } 156 } 157 158 // Always Show element's Style Attributes 159 if (node.nodeType === Node.ELEMENT_NODE) { 160 var inlineStyle = { selectorText: WebInspector.UIString("Style Attribute"), style: node.style, isAttribute: true }; 161 inlineStyle.subtitle = WebInspector.UIString("elements %s attribute", "style"); 162 styleRules.push(inlineStyle); 163 } 164 165 var matchedStyleRules = node.ownerDocument.defaultView.getMatchedCSSRules(node, "", !WebInspector.settings.showUserAgentStyles); 166 if (matchedStyleRules) { 167 // Add rules in reverse order to match the cascade order. 168 for (var i = (matchedStyleRules.length - 1); i >= 0; --i) { 169 var rule = matchedStyleRules[i]; 170 styleRules.push({ style: rule.style, selectorText: rule.selectorText, parentStyleSheet: rule.parentStyleSheet, rule: rule }); 171 } 172 } 173 } 174 175 function deleteDisabledProperty(style, name) 176 { 177 if (!style || !name) 178 return; 179 if (style.__disabledPropertyValues) 180 delete style.__disabledPropertyValues[name]; 181 if (style.__disabledPropertyPriorities) 182 delete style.__disabledPropertyPriorities[name]; 183 if (style.__disabledProperties) 184 delete style.__disabledProperties[name]; 185 } 186 187 var usedProperties = {}; 188 var disabledComputedProperties = {}; 189 var priorityUsed = false; 190 191 // Walk the style rules and make a list of all used and overloaded properties. 192 for (var i = 0; i < styleRules.length; ++i) { 193 var styleRule = styleRules[i]; 194 if (styleRule.computedStyle) 195 continue; 196 if (styleRule.section && styleRule.section.noAffect) 197 continue; 198 199 styleRule.usedProperties = {}; 200 201 var style = styleRule.style; 202 for (var j = 0; j < style.length; ++j) { 203 var name = style[j]; 204 205 if (!priorityUsed && style.getPropertyPriority(name).length) 206 priorityUsed = true; 207 208 // If the property name is already used by another rule then this rule's 209 // property is overloaded, so don't add it to the rule's usedProperties. 210 if (!(name in usedProperties)) 211 styleRule.usedProperties[name] = true; 212 213 if (name === "font") { 214 // The font property is not reported as a shorthand. Report finding the individual 215 // properties so they are visible in computed style. 216 // FIXME: remove this when http://bugs.webkit.org/show_bug.cgi?id=15598 is fixed. 217 styleRule.usedProperties["font-family"] = true; 218 styleRule.usedProperties["font-size"] = true; 219 styleRule.usedProperties["font-style"] = true; 220 styleRule.usedProperties["font-variant"] = true; 221 styleRule.usedProperties["font-weight"] = true; 222 styleRule.usedProperties["line-height"] = true; 223 } 224 225 // Delete any disabled properties, since the property does exist. 226 // This prevents it from showing twice. 227 deleteDisabledProperty(style, name); 228 deleteDisabledProperty(style, style.getPropertyShorthand(name)); 229 } 230 231 // Add all the properties found in this style to the used properties list. 232 // Do this here so only future rules are affect by properties used in this rule. 233 for (var name in styleRules[i].usedProperties) 234 usedProperties[name] = true; 235 236 // Remember all disabled properties so they show up in computed style. 237 if (style.__disabledProperties) 238 for (var name in style.__disabledProperties) 239 disabledComputedProperties[name] = true; 240 } 241 242 if (priorityUsed) { 243 // Walk the properties again and account for !important. 244 var foundPriorityProperties = []; 245 246 // Walk in reverse to match the order !important overrides. 247 for (var i = (styleRules.length - 1); i >= 0; --i) { 248 if (styleRules[i].computedStyle) 249 continue; 250 251 var style = styleRules[i].style; 252 var uniqueProperties = style.uniqueStyleProperties; 253 for (var j = 0; j < uniqueProperties.length; ++j) { 254 var name = uniqueProperties[j]; 255 if (style.getPropertyPriority(name).length) { 256 if (!(name in foundPriorityProperties)) 257 styleRules[i].usedProperties[name] = true; 258 else 259 delete styleRules[i].usedProperties[name]; 260 foundPriorityProperties[name] = true; 261 } else if (name in foundPriorityProperties) 262 delete styleRules[i].usedProperties[name]; 263 } 264 } 265 } 266 267 if (refresh) { 268 // Walk the style rules and update the sections with new overloaded and used properties. 269 for (var i = 0; i < styleRules.length; ++i) { 270 var styleRule = styleRules[i]; 271 var section = styleRule.section; 272 if (styleRule.computedStyle) 273 section.disabledComputedProperties = disabledComputedProperties; 274 section._usedProperties = (styleRule.usedProperties || usedProperties); 275 section.update((section === editedSection) || styleRule.computedStyle); 276 } 277 } else { 278 // Make a property section for each style rule. 279 for (var i = 0; i < styleRules.length; ++i) { 280 var styleRule = styleRules[i]; 281 var subtitle = styleRule.subtitle; 282 delete styleRule.subtitle; 283 284 var computedStyle = styleRule.computedStyle; 285 delete styleRule.computedStyle; 286 287 var ruleUsedProperties = styleRule.usedProperties; 288 delete styleRule.usedProperties; 289 290 var editable = styleRule.editable; 291 delete styleRule.editable; 292 293 var isAttribute = styleRule.isAttribute; 294 delete styleRule.isAttribute; 295 296 // Default editable to true if it was omitted. 297 if (typeof editable === "undefined") 298 editable = true; 299 300 var section = new WebInspector.StylePropertiesSection(styleRule, subtitle, computedStyle, (ruleUsedProperties || usedProperties), editable); 301 if (computedStyle) 302 section.disabledComputedProperties = disabledComputedProperties; 303 section.pane = this; 304 305 if (Preferences.styleRulesExpandedState && section.identifier in Preferences.styleRulesExpandedState) 306 section.expanded = Preferences.styleRulesExpandedState[section.identifier]; 307 else if (computedStyle) 308 section.collapse(true); 309 else if (isAttribute && styleRule.style.length === 0) 310 section.collapse(true); 311 else 312 section.expand(true); 313 314 body.appendChild(section.element); 315 this.sections.push(section); 316 } 317 } 318 }, 319 320 _changeSetting: function(event) 321 { 322 var options = this.settingsSelectElement.options; 323 var selectedOption = options[this.settingsSelectElement.selectedIndex]; 324 selectedOption.action(event); 325 326 // Select the correct color format setting again, since it needs to be selected. 327 var selectedIndex = 0; 328 for (var i = 0; i < options.length; ++i) { 329 if (options[i].value === WebInspector.settings.colorFormat) { 330 selectedIndex = i; 331 break; 332 } 333 } 334 335 this.settingsSelectElement.selectedIndex = selectedIndex; 336 }, 337 338 _changeColorFormat: function(event) 339 { 340 var selectedOption = this.settingsSelectElement[this.settingsSelectElement.selectedIndex]; 341 WebInspector.settings.colorFormat = selectedOption.value; 342 343 for (var i = 0; i < this.sections.length; ++i) 344 this.sections[i].update(true); 345 }, 346 347 _createNewRule: function(event) 348 { 349 this.addBlankSection().startEditingSelector(); 350 }, 351 352 addBlankSection: function() 353 { 354 var blankSection = new WebInspector.BlankStylePropertiesSection(appropriateSelectorForNode(this.node, true)); 355 blankSection.pane = this; 356 357 var elementStyleSection = this.sections[1]; 358 this.bodyElement.insertBefore(blankSection.element, elementStyleSection.element.nextSibling); 359 360 this.sections.splice(2, 0, blankSection); 361 362 return blankSection; 363 }, 364 365 removeSection: function(section) 366 { 367 var index = this.sections.indexOf(section); 368 if (index === -1) 369 return; 370 this.sections.splice(index, 1); 371 if (section.element.parentNode) 372 section.element.parentNode.removeChild(section.element); 373 } 374 } 375 376 WebInspector.StylesSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; 377 378 WebInspector.StylePropertiesSection = function(styleRule, subtitle, computedStyle, usedProperties, editable) 379 { 380 WebInspector.PropertiesSection.call(this, styleRule.selectorText); 381 382 this.titleElement.addEventListener("click", this._clickSelector.bind(this), false); 383 this.titleElement.addEventListener("dblclick", this._dblclickSelector.bind(this), false); 384 this.element.addEventListener("dblclick", this._dblclickEmptySpace.bind(this), false); 385 386 this.styleRule = styleRule; 387 this.rule = this.styleRule.rule; 388 this.computedStyle = computedStyle; 389 this.editable = (editable && !computedStyle); 390 391 // Prevent editing the user agent and user rules. 392 var isUserAgent = this.rule && this.rule.isUserAgent; 393 var isUser = this.rule && this.rule.isUser; 394 var isViaInspector = this.rule && this.rule.isViaInspector; 395 396 if (isUserAgent || isUser) 397 this.editable = false; 398 399 this._usedProperties = usedProperties; 400 401 if (computedStyle) { 402 this.element.addStyleClass("computed-style"); 403 404 if (WebInspector.settings.showInheritedComputedStyleProperties) 405 this.element.addStyleClass("show-inherited"); 406 407 var showInheritedLabel = document.createElement("label"); 408 var showInheritedInput = document.createElement("input"); 409 showInheritedInput.type = "checkbox"; 410 showInheritedInput.checked = WebInspector.settings.showInheritedComputedStyleProperties; 411 412 var computedStyleSection = this; 413 var showInheritedToggleFunction = function(event) { 414 WebInspector.settings.showInheritedComputedStyleProperties = showInheritedInput.checked; 415 if (WebInspector.settings.showInheritedComputedStyleProperties) 416 computedStyleSection.element.addStyleClass("show-inherited"); 417 else 418 computedStyleSection.element.removeStyleClass("show-inherited"); 419 event.stopPropagation(); 420 }; 421 422 showInheritedLabel.addEventListener("click", showInheritedToggleFunction, false); 423 424 showInheritedLabel.appendChild(showInheritedInput); 425 showInheritedLabel.appendChild(document.createTextNode(WebInspector.UIString("Show inherited"))); 426 this.subtitleElement.appendChild(showInheritedLabel); 427 } else { 428 if (!subtitle) { 429 if (this.styleRule.parentStyleSheet && this.styleRule.parentStyleSheet.href) { 430 var url = this.styleRule.parentStyleSheet.href; 431 subtitle = WebInspector.linkifyURL(url, WebInspector.displayNameForURL(url)); 432 this.subtitleElement.addStyleClass("file"); 433 } else if (isUserAgent) 434 subtitle = WebInspector.UIString("user agent stylesheet"); 435 else if (isUser) 436 subtitle = WebInspector.UIString("user stylesheet"); 437 else if (isViaInspector) 438 subtitle = WebInspector.UIString("via inspector"); 439 else 440 subtitle = WebInspector.UIString("inline stylesheet"); 441 } 442 443 this.subtitle = subtitle; 444 } 445 446 this.identifier = styleRule.selectorText; 447 if (this.subtitle) 448 this.identifier += ":" + this.subtitleElement.textContent; 449 } 450 451 WebInspector.StylePropertiesSection.prototype = { 452 get usedProperties() 453 { 454 return this._usedProperties || {}; 455 }, 456 457 set usedProperties(x) 458 { 459 this._usedProperties = x; 460 this.update(); 461 }, 462 463 expand: function(dontRememberState) 464 { 465 WebInspector.PropertiesSection.prototype.expand.call(this); 466 if (dontRememberState) 467 return; 468 469 if (!Preferences.styleRulesExpandedState) 470 Preferences.styleRulesExpandedState = {}; 471 Preferences.styleRulesExpandedState[this.identifier] = true; 472 }, 473 474 collapse: function(dontRememberState) 475 { 476 WebInspector.PropertiesSection.prototype.collapse.call(this); 477 if (dontRememberState) 478 return; 479 480 if (!Preferences.styleRulesExpandedState) 481 Preferences.styleRulesExpandedState = {}; 482 Preferences.styleRulesExpandedState[this.identifier] = false; 483 }, 484 485 isPropertyInherited: function(property) 486 { 487 if (!this.computedStyle || !this._usedProperties || this.noAffect) 488 return false; 489 // These properties should always show for Computed Style. 490 var alwaysShowComputedProperties = { "display": true, "height": true, "width": true }; 491 return !(property in this.usedProperties) && !(property in alwaysShowComputedProperties) && !(property in this.disabledComputedProperties); 492 }, 493 494 isPropertyOverloaded: function(property, shorthand) 495 { 496 if (this.computedStyle || !this._usedProperties || this.noAffect) 497 return false; 498 499 var used = (property in this.usedProperties); 500 if (used || !shorthand) 501 return !used; 502 503 // Find out if any of the individual longhand properties of the shorthand 504 // are used, if none are then the shorthand is overloaded too. 505 var longhandProperties = this.styleRule.style.getLonghandProperties(property); 506 for (var j = 0; j < longhandProperties.length; ++j) { 507 var individualProperty = longhandProperties[j]; 508 if (individualProperty in this.usedProperties) 509 return false; 510 } 511 512 return true; 513 }, 514 515 isInspectorStylesheet: function() 516 { 517 return (this.styleRule.parentStyleSheet === WebInspector.panels.elements.stylesheet); 518 }, 519 520 update: function(full) 521 { 522 if (full || this.computedStyle) { 523 this.propertiesTreeOutline.removeChildren(); 524 this.populated = false; 525 } else { 526 var child = this.propertiesTreeOutline.children[0]; 527 while (child) { 528 child.overloaded = this.isPropertyOverloaded(child.name, child.shorthand); 529 child = child.traverseNextTreeElement(false, null, true); 530 } 531 } 532 533 this.afterUpdate(); 534 }, 535 536 afterUpdate: function() 537 { 538 if (this._afterUpdate) { 539 this._afterUpdate(this); 540 delete this._afterUpdate; 541 } 542 }, 543 544 onpopulate: function() 545 { 546 var style = this.styleRule.style; 547 548 var foundShorthands = {}; 549 var uniqueProperties = style.uniqueStyleProperties; 550 var disabledProperties = style.__disabledPropertyValues || {}; 551 552 for (var name in disabledProperties) 553 uniqueProperties.push(name); 554 555 uniqueProperties.sort(); 556 557 for (var i = 0; i < uniqueProperties.length; ++i) { 558 var name = uniqueProperties[i]; 559 var disabled = name in disabledProperties; 560 if (!disabled && this.disabledComputedProperties && !(name in this.usedProperties) && name in this.disabledComputedProperties) 561 disabled = true; 562 563 var shorthand = !disabled ? style.getPropertyShorthand(name) : null; 564 565 if (shorthand && shorthand in foundShorthands) 566 continue; 567 568 if (shorthand) { 569 foundShorthands[shorthand] = true; 570 name = shorthand; 571 } 572 573 var isShorthand = (shorthand ? true : false); 574 var inherited = this.isPropertyInherited(name); 575 var overloaded = this.isPropertyOverloaded(name, isShorthand); 576 577 var item = new WebInspector.StylePropertyTreeElement(this.styleRule, style, name, isShorthand, inherited, overloaded, disabled); 578 this.propertiesTreeOutline.appendChild(item); 579 } 580 }, 581 582 findTreeElementWithName: function(name) 583 { 584 var treeElement = this.propertiesTreeOutline.children[0]; 585 while (treeElement) { 586 if (treeElement.name === name) 587 return treeElement; 588 treeElement = treeElement.traverseNextTreeElement(true, null, true); 589 } 590 return null; 591 }, 592 593 addNewBlankProperty: function() 594 { 595 var item = new WebInspector.StylePropertyTreeElement(this.styleRule, this.styleRule.style, "", false, false, false, false); 596 this.propertiesTreeOutline.appendChild(item); 597 item.listItemElement.textContent = ""; 598 item._newProperty = true; 599 return item; 600 }, 601 602 _clickSelector: function(event) 603 { 604 event.stopPropagation(); 605 606 // Don't search "Computed Styles", "Style Attribute", or Mapped Attributes. 607 if (this.computedStyle || !this.rule || this.rule.isUser) 608 return; 609 610 var searchElement = document.getElementById("search"); 611 searchElement.value = this.styleRule.selectorText; 612 WebInspector.performSearch({ target: searchElement }); 613 }, 614 615 _dblclickEmptySpace: function(event) 616 { 617 this.expand(); 618 this.addNewBlankProperty().startEditing(); 619 }, 620 621 _dblclickSelector: function(event) 622 { 623 if (!this.editable) 624 return; 625 626 if (!this.rule && this.propertiesTreeOutline.children.length === 0) { 627 this.expand(); 628 this.addNewBlankProperty().startEditing(); 629 return; 630 } 631 632 if (!this.rule) 633 return; 634 635 this.startEditingSelector(); 636 event.stopPropagation(); 637 }, 638 639 startEditingSelector: function() 640 { 641 var element = this.titleElement; 642 if (WebInspector.isBeingEdited(element)) 643 return; 644 645 WebInspector.startEditing(this.titleElement, this.editingSelectorCommitted.bind(this), this.editingSelectorCancelled.bind(this), null); 646 window.getSelection().setBaseAndExtent(element, 0, element, 1); 647 }, 648 649 editingSelectorCommitted: function(element, newContent, oldContent, context, moveDirection) 650 { 651 function moveToNextIfNeeded() { 652 if (!moveDirection || moveDirection !== "forward") 653 return; 654 655 this.expand(); 656 if (this.propertiesTreeOutline.children.length === 0) 657 this.addNewBlankProperty().startEditing(); 658 else { 659 var item = this.propertiesTreeOutline.children[0] 660 item.startEditing(item.valueElement); 661 } 662 } 663 664 if (newContent === oldContent) 665 return moveToNextIfNeeded.call(this); 666 667 var self = this; 668 function callback(result) 669 { 670 if (!result) { 671 // Invalid Syntax for a Selector 672 moveToNextIfNeeded.call(self); 673 return; 674 } 675 676 var newRulePayload = result[0]; 677 var doesAffectSelectedNode = result[1]; 678 if (!doesAffectSelectedNode) { 679 self.noAffect = true; 680 self.element.addStyleClass("no-affect"); 681 } else { 682 delete self.noAffect; 683 self.element.removeStyleClass("no-affect"); 684 } 685 686 var newRule = WebInspector.CSSStyleDeclaration.parseRule(newRulePayload); 687 self.rule = newRule; 688 self.styleRule = { section: self, style: newRule.style, selectorText: newRule.selectorText, parentStyleSheet: newRule.parentStyleSheet, rule: newRule }; 689 690 var oldIdentifier = this.identifier; 691 self.identifier = newRule.selectorText + ":" + self.subtitleElement.textContent; 692 693 self.pane.update(); 694 695 WebInspector.panels.elements.renameSelector(oldIdentifier, this.identifier, oldContent, newContent); 696 697 moveToNextIfNeeded.call(self); 698 } 699 700 InjectedScriptAccess.get(this.rule.injectedScriptId).applyStyleRuleText(this.rule.id, newContent, this.pane.node.id, callback); 701 }, 702 703 editingSelectorCancelled: function() 704 { 705 // Do nothing, this is overridden by BlankStylePropertiesSection. 706 } 707 } 708 709 WebInspector.StylePropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype; 710 711 WebInspector.BlankStylePropertiesSection = function(defaultSelectorText) 712 { 713 WebInspector.StylePropertiesSection.call(this, {selectorText: defaultSelectorText, rule: {isViaInspector: true}}, "", false, {}, false); 714 715 this.element.addStyleClass("blank-section"); 716 } 717 718 WebInspector.BlankStylePropertiesSection.prototype = { 719 expand: function() 720 { 721 // Do nothing, blank sections are not expandable. 722 }, 723 724 editingSelectorCommitted: function(element, newContent, oldContent, context) 725 { 726 var self = this; 727 function callback(result) 728 { 729 if (!result) { 730 // Invalid Syntax for a Selector 731 self.editingSelectorCancelled(); 732 return; 733 } 734 735 var rule = result[0]; 736 var doesSelectorAffectSelectedNode = result[1]; 737 738 var styleRule = WebInspector.CSSStyleDeclaration.parseRule(rule); 739 styleRule.rule = rule; 740 741 self.makeNormal(styleRule); 742 743 if (!doesSelectorAffectSelectedNode) { 744 self.noAffect = true; 745 self.element.addStyleClass("no-affect"); 746 } 747 748 self.subtitleElement.textContent = WebInspector.UIString("via inspector"); 749 self.expand(); 750 751 self.addNewBlankProperty().startEditing(); 752 } 753 754 InjectedScriptAccess.get(this.pane.node.injectedScriptId).addStyleSelector(newContent, this.pane.node.id, callback); 755 }, 756 757 editingSelectorCancelled: function() 758 { 759 this.pane.removeSection(this); 760 }, 761 762 makeNormal: function(styleRule) 763 { 764 this.element.removeStyleClass("blank-section"); 765 766 this.styleRule = styleRule; 767 this.rule = styleRule.rule; 768 this.computedStyle = false; 769 this.editable = true; 770 this.identifier = styleRule.selectorText + ":via inspector"; 771 772 this.__proto__ = WebInspector.StylePropertiesSection.prototype; 773 } 774 } 775 776 WebInspector.BlankStylePropertiesSection.prototype.__proto__ = WebInspector.StylePropertiesSection.prototype; 777 778 WebInspector.StylePropertyTreeElement = function(styleRule, style, name, shorthand, inherited, overloaded, disabled) 779 { 780 this._styleRule = styleRule; 781 this.style = style; 782 this.name = name; 783 this.shorthand = shorthand; 784 this._inherited = inherited; 785 this._overloaded = overloaded; 786 this._disabled = disabled; 787 788 // Pass an empty title, the title gets made later in onattach. 789 TreeElement.call(this, "", null, shorthand); 790 } 791 792 WebInspector.StylePropertyTreeElement.prototype = { 793 get inherited() 794 { 795 return this._inherited; 796 }, 797 798 set inherited(x) 799 { 800 if (x === this._inherited) 801 return; 802 this._inherited = x; 803 this.updateState(); 804 }, 805 806 get overloaded() 807 { 808 return this._overloaded; 809 }, 810 811 set overloaded(x) 812 { 813 if (x === this._overloaded) 814 return; 815 this._overloaded = x; 816 this.updateState(); 817 }, 818 819 get disabled() 820 { 821 return this._disabled; 822 }, 823 824 set disabled(x) 825 { 826 if (x === this._disabled) 827 return; 828 this._disabled = x; 829 this.updateState(); 830 }, 831 832 get priority() 833 { 834 if (this.disabled && this.style.__disabledPropertyPriorities && this.name in this.style.__disabledPropertyPriorities) 835 return this.style.__disabledPropertyPriorities[this.name]; 836 return (this.shorthand ? this.style.getShorthandPriority(this.name) : this.style.getPropertyPriority(this.name)); 837 }, 838 839 get value() 840 { 841 if (this.disabled && this.style.__disabledPropertyValues && this.name in this.style.__disabledPropertyValues) 842 return this.style.__disabledPropertyValues[this.name]; 843 return (this.shorthand ? this.style.getShorthandValue(this.name) : this.style.getPropertyValue(this.name)); 844 }, 845 846 onattach: function() 847 { 848 this.updateTitle(); 849 }, 850 851 updateTitle: function() 852 { 853 var priority = this.priority; 854 var value = this.value; 855 856 if (priority && !priority.length) 857 delete priority; 858 if (priority) 859 priority = "!" + priority; 860 861 this.updateState(); 862 863 var enabledCheckboxElement = document.createElement("input"); 864 enabledCheckboxElement.className = "enabled-button"; 865 enabledCheckboxElement.type = "checkbox"; 866 enabledCheckboxElement.checked = !this.disabled; 867 enabledCheckboxElement.addEventListener("change", this.toggleEnabled.bind(this), false); 868 869 var nameElement = document.createElement("span"); 870 nameElement.className = "name"; 871 nameElement.textContent = this.name; 872 this.nameElement = nameElement; 873 874 var valueElement = document.createElement("span"); 875 valueElement.className = "value"; 876 this.valueElement = valueElement; 877 878 if (value) { 879 function processValue(regex, processor, nextProcessor, valueText) 880 { 881 var container = document.createDocumentFragment(); 882 883 var items = valueText.replace(regex, "\0$1\0").split("\0"); 884 for (var i = 0; i < items.length; ++i) { 885 if ((i % 2) === 0) { 886 if (nextProcessor) 887 container.appendChild(nextProcessor(items[i])); 888 else 889 container.appendChild(document.createTextNode(items[i])); 890 } else { 891 var processedNode = processor(items[i]); 892 if (processedNode) 893 container.appendChild(processedNode); 894 } 895 } 896 897 return container; 898 } 899 900 function linkifyURL(url) 901 { 902 var container = document.createDocumentFragment(); 903 container.appendChild(document.createTextNode("url(")); 904 container.appendChild(WebInspector.linkifyURLAsNode(url, url, null, (url in WebInspector.resourceURLMap))); 905 container.appendChild(document.createTextNode(")")); 906 return container; 907 } 908 909 function processColor(text) 910 { 911 try { 912 var color = new WebInspector.Color(text); 913 } catch (e) { 914 return document.createTextNode(text); 915 } 916 917 var swatchElement = document.createElement("span"); 918 swatchElement.title = WebInspector.UIString("Click to change color format"); 919 swatchElement.className = "swatch"; 920 swatchElement.style.setProperty("background-color", text); 921 922 swatchElement.addEventListener("click", changeColorDisplay, false); 923 swatchElement.addEventListener("dblclick", function(event) { event.stopPropagation() }, false); 924 925 var format; 926 if (Preferences.showColorNicknames && color.nickname) 927 format = "nickname"; 928 else if (WebInspector.settings.colorFormat === "rgb") 929 format = (color.simple ? "rgb" : "rgba"); 930 else if (WebInspector.settings.colorFormat === "hsl") 931 format = (color.simple ? "hsl" : "hsla"); 932 else if (color.simple) 933 format = (color.hasShortHex() ? "shorthex" : "hex"); 934 else 935 format = "rgba"; 936 937 var colorValueElement = document.createElement("span"); 938 colorValueElement.textContent = color.toString(format); 939 940 function changeColorDisplay(event) 941 { 942 switch (format) { 943 case "rgb": 944 format = "hsl"; 945 break; 946 947 case "shorthex": 948 format = "hex"; 949 break; 950 951 case "hex": 952 format = "rgb"; 953 break; 954 955 case "nickname": 956 if (color.simple) { 957 if (color.hasShortHex()) 958 format = "shorthex"; 959 else 960 format = "hex"; 961 break; 962 } 963 964 format = "rgba"; 965 break; 966 967 case "hsl": 968 if (color.nickname) 969 format = "nickname"; 970 else if (color.hasShortHex()) 971 format = "shorthex"; 972 else 973 format = "hex"; 974 break; 975 976 case "rgba": 977 format = "hsla"; 978 break; 979 980 case "hsla": 981 if (color.nickname) 982 format = "nickname"; 983 else 984 format = "rgba"; 985 break; 986 } 987 988 colorValueElement.textContent = color.toString(format); 989 } 990 991 var container = document.createDocumentFragment(); 992 container.appendChild(swatchElement); 993 container.appendChild(colorValueElement); 994 return container; 995 } 996 997 var colorRegex = /((?:rgb|hsl)a?\([^)]+\)|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}|\b\w+\b)/g; 998 var colorProcessor = processValue.bind(window, colorRegex, processColor, null); 999 1000 valueElement.appendChild(processValue(/url\(([^)]+)\)/g, linkifyURL, colorProcessor, value)); 1001 } 1002 1003 if (priority) { 1004 var priorityElement = document.createElement("span"); 1005 priorityElement.className = "priority"; 1006 priorityElement.textContent = priority; 1007 } 1008 1009 this.listItemElement.removeChildren(); 1010 1011 // Append the checkbox for root elements of an editable section. 1012 if (this.treeOutline.section && this.treeOutline.section.editable && this.parent.root) 1013 this.listItemElement.appendChild(enabledCheckboxElement); 1014 this.listItemElement.appendChild(nameElement); 1015 this.listItemElement.appendChild(document.createTextNode(": ")); 1016 this.listItemElement.appendChild(valueElement); 1017 1018 if (priorityElement) { 1019 this.listItemElement.appendChild(document.createTextNode(" ")); 1020 this.listItemElement.appendChild(priorityElement); 1021 } 1022 1023 this.listItemElement.appendChild(document.createTextNode(";")); 1024 1025 this.tooltip = this.name + ": " + valueElement.textContent + (priority ? " " + priority : ""); 1026 }, 1027 1028 updateAll: function(updateAllRules) 1029 { 1030 if (updateAllRules && this.treeOutline.section && this.treeOutline.section.pane) 1031 this.treeOutline.section.pane.update(null, this.treeOutline.section); 1032 else if (this.treeOutline.section) 1033 this.treeOutline.section.update(true); 1034 else 1035 this.updateTitle(); // FIXME: this will not show new properties. But we don't hit his case yet. 1036 }, 1037 1038 toggleEnabled: function(event) 1039 { 1040 var disabled = !event.target.checked; 1041 1042 var self = this; 1043 function callback(newPayload) 1044 { 1045 if (!newPayload) 1046 return; 1047 1048 self.style = WebInspector.CSSStyleDeclaration.parseStyle(newPayload); 1049 self._styleRule.style = self.style; 1050 1051 // Set the disabled property here, since the code above replies on it not changing 1052 // until after the value and priority are retrieved. 1053 self.disabled = disabled; 1054 1055 if (self.treeOutline.section && self.treeOutline.section.pane) 1056 self.treeOutline.section.pane.dispatchEventToListeners("style property toggled"); 1057 1058 self.updateAll(true); 1059 } 1060 1061 InjectedScriptAccess.get(this.style.injectedScriptId).toggleStyleEnabled(this.style.id, this.name, disabled, callback); 1062 }, 1063 1064 updateState: function() 1065 { 1066 if (!this.listItemElement) 1067 return; 1068 1069 if (this.style.isPropertyImplicit(this.name) || this.value === "initial") 1070 this.listItemElement.addStyleClass("implicit"); 1071 else 1072 this.listItemElement.removeStyleClass("implicit"); 1073 1074 if (this.inherited) 1075 this.listItemElement.addStyleClass("inherited"); 1076 else 1077 this.listItemElement.removeStyleClass("inherited"); 1078 1079 if (this.overloaded) 1080 this.listItemElement.addStyleClass("overloaded"); 1081 else 1082 this.listItemElement.removeStyleClass("overloaded"); 1083 1084 if (this.disabled) 1085 this.listItemElement.addStyleClass("disabled"); 1086 else 1087 this.listItemElement.removeStyleClass("disabled"); 1088 }, 1089 1090 onpopulate: function() 1091 { 1092 // Only populate once and if this property is a shorthand. 1093 if (this.children.length || !this.shorthand) 1094 return; 1095 1096 var longhandProperties = this.style.getLonghandProperties(this.name); 1097 for (var i = 0; i < longhandProperties.length; ++i) { 1098 var name = longhandProperties[i]; 1099 1100 if (this.treeOutline.section) { 1101 var inherited = this.treeOutline.section.isPropertyInherited(name); 1102 var overloaded = this.treeOutline.section.isPropertyOverloaded(name); 1103 } 1104 1105 var item = new WebInspector.StylePropertyTreeElement(this._styleRule, this.style, name, false, inherited, overloaded); 1106 this.appendChild(item); 1107 } 1108 }, 1109 1110 ondblclick: function(event) 1111 { 1112 this.startEditing(event.target); 1113 event.stopPropagation(); 1114 }, 1115 1116 startEditing: function(selectElement) 1117 { 1118 // FIXME: we don't allow editing of longhand properties under a shorthand right now. 1119 if (this.parent.shorthand) 1120 return; 1121 1122 if (WebInspector.isBeingEdited(this.listItemElement) || (this.treeOutline.section && !this.treeOutline.section.editable)) 1123 return; 1124 1125 var context = { expanded: this.expanded, hasChildren: this.hasChildren }; 1126 1127 // Lie about our children to prevent expanding on double click and to collapse shorthands. 1128 this.hasChildren = false; 1129 1130 if (!selectElement) 1131 selectElement = this.listItemElement; 1132 1133 this.listItemElement.handleKeyEvent = this.editingKeyDown.bind(this); 1134 1135 WebInspector.startEditing(this.listItemElement, this.editingCommitted.bind(this), this.editingCancelled.bind(this), context); 1136 window.getSelection().setBaseAndExtent(selectElement, 0, selectElement, 1); 1137 }, 1138 1139 editingKeyDown: function(event) 1140 { 1141 var arrowKeyPressed = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down"); 1142 var pageKeyPressed = (event.keyIdentifier === "PageUp" || event.keyIdentifier === "PageDown"); 1143 if (!arrowKeyPressed && !pageKeyPressed) 1144 return; 1145 1146 var selection = window.getSelection(); 1147 if (!selection.rangeCount) 1148 return; 1149 1150 var selectionRange = selection.getRangeAt(0); 1151 if (selectionRange.commonAncestorContainer !== this.listItemElement && !selectionRange.commonAncestorContainer.isDescendant(this.listItemElement)) 1152 return; 1153 1154 const styleValueDelimeters = " \t\n\"':;,/()"; 1155 var wordRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, styleValueDelimeters, this.listItemElement); 1156 var wordString = wordRange.toString(); 1157 var replacementString = wordString; 1158 1159 var matches = /(.*?)(-?\d+(?:\.\d+)?)(.*)/.exec(wordString); 1160 if (matches && matches.length) { 1161 var prefix = matches[1]; 1162 var number = parseFloat(matches[2]); 1163 var suffix = matches[3]; 1164 1165 // If the number is near zero or the number is one and the direction will take it near zero. 1166 var numberNearZero = (number < 1 && number > -1); 1167 if (number === 1 && event.keyIdentifier === "Down") 1168 numberNearZero = true; 1169 else if (number === -1 && event.keyIdentifier === "Up") 1170 numberNearZero = true; 1171 1172 if (numberNearZero && event.altKey && arrowKeyPressed) { 1173 if (event.keyIdentifier === "Down") 1174 number = Math.ceil(number - 1); 1175 else 1176 number = Math.floor(number + 1); 1177 } else { 1178 // Jump by 10 when shift is down or jump by 0.1 when near zero or Alt/Option is down. 1179 // Also jump by 10 for page up and down, or by 100 if shift is held with a page key. 1180 var changeAmount = 1; 1181 if (event.shiftKey && pageKeyPressed) 1182 changeAmount = 100; 1183 else if (event.shiftKey || pageKeyPressed) 1184 changeAmount = 10; 1185 else if (event.altKey || numberNearZero) 1186 changeAmount = 0.1; 1187 1188 if (event.keyIdentifier === "Down" || event.keyIdentifier === "PageDown") 1189 changeAmount *= -1; 1190 1191 // Make the new number and constrain it to a precision of 6, this matches numbers the engine returns. 1192 // Use the Number constructor to forget the fixed precision, so 1.100000 will print as 1.1. 1193 number = Number((number + changeAmount).toFixed(6)); 1194 } 1195 1196 replacementString = prefix + number + suffix; 1197 } else { 1198 // FIXME: this should cycle through known keywords for the current property name. 1199 return; 1200 } 1201 1202 var replacementTextNode = document.createTextNode(replacementString); 1203 1204 wordRange.deleteContents(); 1205 wordRange.insertNode(replacementTextNode); 1206 1207 var finalSelectionRange = document.createRange(); 1208 finalSelectionRange.setStart(replacementTextNode, 0); 1209 finalSelectionRange.setEnd(replacementTextNode, replacementString.length); 1210 1211 selection.removeAllRanges(); 1212 selection.addRange(finalSelectionRange); 1213 1214 event.preventDefault(); 1215 event.handled = true; 1216 1217 if (!this.originalCSSText) { 1218 // Remember the rule's original CSS text, so it can be restored 1219 // if the editing is canceled and before each apply. 1220 this.originalCSSText = this.style.styleTextWithShorthands(); 1221 } else { 1222 // Restore the original CSS text before applying user changes. This is needed to prevent 1223 // new properties from sticking around if the user adds one, then removes it. 1224 InjectedScriptAccess.get(this.style.injectedScriptId).setStyleText(this.style.id, this.originalCSSText); 1225 } 1226 1227 this.applyStyleText(this.listItemElement.textContent); 1228 }, 1229 1230 editingEnded: function(context) 1231 { 1232 this.hasChildren = context.hasChildren; 1233 if (context.expanded) 1234 this.expand(); 1235 delete this.listItemElement.handleKeyEvent; 1236 delete this.originalCSSText; 1237 }, 1238 1239 editingCancelled: function(element, context) 1240 { 1241 if (this._newProperty) 1242 this.treeOutline.removeChild(this); 1243 else if (this.originalCSSText) { 1244 InjectedScriptAccess.get(this.style.injectedScriptId).setStyleText(this.style.id, this.originalCSSText); 1245 1246 if (this.treeOutline.section && this.treeOutline.section.pane) 1247 this.treeOutline.section.pane.dispatchEventToListeners("style edited"); 1248 1249 this.updateAll(); 1250 } else 1251 this.updateTitle(); 1252 1253 this.editingEnded(context); 1254 }, 1255 1256 editingCommitted: function(element, userInput, previousContent, context, moveDirection) 1257 { 1258 this.editingEnded(context); 1259 1260 // Determine where to move to before making changes 1261 var newProperty, moveToPropertyName, moveToSelector; 1262 var moveTo = (moveDirection === "forward" ? this.nextSibling : this.previousSibling); 1263 if (moveTo) 1264 moveToPropertyName = moveTo.name; 1265 else if (moveDirection === "forward") 1266 newProperty = true; 1267 else if (moveDirection === "backward" && this.treeOutline.section.rule) 1268 moveToSelector = true; 1269 1270 // Make the Changes and trigger the moveToNextCallback after updating 1271 var blankInput = /^\s*$/.test(userInput); 1272 if (userInput !== previousContent || (this._newProperty && blankInput)) { // only if something changed, or adding a new style and it was blank 1273 this.treeOutline.section._afterUpdate = moveToNextCallback.bind(this, this._newProperty, !blankInput); 1274 this.applyStyleText(userInput, true); 1275 } else 1276 moveToNextCallback(this._newProperty, false, this.treeOutline.section, false); 1277 1278 // The Callback to start editing the next property 1279 function moveToNextCallback(alreadyNew, valueChanged, section) 1280 { 1281 if (!moveDirection) 1282 return; 1283 1284 // User just tabbed through without changes 1285 if (moveTo && moveTo.parent) { 1286 moveTo.startEditing(moveTo.valueElement); 1287 return; 1288 } 1289 1290 // User has made a change then tabbed, wiping all the original treeElements, 1291 // recalculate the new treeElement for the same property we were going to edit next 1292 if (moveTo && !moveTo.parent) { 1293 var treeElement = section.findTreeElementWithName(moveToPropertyName); 1294 if (treeElement) 1295 treeElement.startEditing(treeElement.valueElement); 1296 return; 1297 } 1298 1299 // Create a new attribute in this section 1300 if (newProperty) { 1301 if (alreadyNew && !valueChanged) 1302 return; 1303 1304 section.addNewBlankProperty().startEditing(); 1305 return; 1306 } 1307 1308 if (moveToSelector) 1309 section.startEditingSelector(); 1310 } 1311 }, 1312 1313 applyStyleText: function(styleText, updateInterface) 1314 { 1315 var section = this.treeOutline.section; 1316 var elementsPanel = WebInspector.panels.elements; 1317 var styleTextLength = styleText.trim().length; 1318 if (!styleTextLength && updateInterface) { 1319 if (this._newProperty) { 1320 // The user deleted everything, so remove the tree element and update. 1321 this.parent.removeChild(this); 1322 section.afterUpdate(); 1323 return; 1324 } else { 1325 delete section._afterUpdate; 1326 } 1327 } 1328 1329 var self = this; 1330 function callback(result) 1331 { 1332 if (!result) { 1333 // The user typed something, but it didn't parse. Just abort and restore 1334 // the original title for this property. If this was a new attribute and 1335 // we couldn't parse, then just remove it. 1336 if (self._newProperty) { 1337 self.parent.removeChild(self); 1338 return; 1339 } 1340 if (updateInterface) 1341 self.updateTitle(); 1342 return; 1343 } 1344 1345 var newPayload = result[0]; 1346 var changedProperties = result[1]; 1347 elementsPanel.removeStyleChange(section.identifier, self.style, self.name); 1348 1349 if (!styleTextLength) { 1350 // Do remove ourselves from UI when the property removal is confirmed. 1351 self.parent.removeChild(self); 1352 } else { 1353 self.style = WebInspector.CSSStyleDeclaration.parseStyle(newPayload); 1354 for (var i = 0; i < changedProperties.length; ++i) 1355 elementsPanel.addStyleChange(section.identifier, self.style, changedProperties[i]); 1356 self._styleRule.style = self.style; 1357 } 1358 1359 if (section && section.pane) 1360 section.pane.dispatchEventToListeners("style edited"); 1361 1362 if (updateInterface) 1363 self.updateAll(true); 1364 1365 if (!section.rule) 1366 WebInspector.panels.elements.treeOutline.update(); 1367 } 1368 1369 InjectedScriptAccess.get(this.style.injectedScriptId).applyStyleText(this.style.id, styleText.trim(), this.name, callback); 1370 } 1371 } 1372 1373 WebInspector.StylePropertyTreeElement.prototype.__proto__ = TreeElement.prototype; 1374