2 * Copyright (C) 2007 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 WebInspector.StylesSidebarPane = function()
31 WebInspector.SidebarPane.call(this, "Styles");
34 WebInspector.StylesSidebarPane.prototype = {
35 update: function(node, editedSection)
39 if (!node || node === this.node)
42 if (node && node.nodeType === Node.TEXT_NODE && node.parentNode)
43 node = node.parentNode;
45 if (node && node.nodeType !== Node.ELEMENT_NODE)
53 var body = this.bodyElement;
54 if (!refresh || !node) {
55 body.removeChildren();
65 for (var i = 0; i < this.sections.length; ++i) {
66 var section = this.sections[i];
67 if (section.computedStyle)
68 section.styleRule.style = node.ownerDocument.defaultView.getComputedStyle(node);
69 var styleRule = { section: section, style: section.styleRule.style, computedStyle: section.computedStyle };
70 styleRules.push(styleRule);
73 var computedStyle = node.ownerDocument.defaultView.getComputedStyle(node);
74 styleRules.push({ computedStyle: true, selectorText: "Computed Style", style: computedStyle, editable: false });
76 var nodeName = node.nodeName.toLowerCase();
77 for (var i = 0; i < node.attributes.length; ++i) {
78 var attr = node.attributes[i];
80 var attrStyle = { style: attr.style, editable: false };
81 attrStyle.subtitle = "element\u2019s \u201C" + attr.name + "\u201D attribute";
82 attrStyle.selectorText = nodeName + "[" + attr.name;
83 if (attr.value.length)
84 attrStyle.selectorText += "=" + attr.value;
85 attrStyle.selectorText += "]";
86 styleRules.push(attrStyle);
90 if (node.style && node.style.length) {
91 var inlineStyle = { selectorText: "Inline Style Attribute", style: node.style };
92 inlineStyle.subtitle = "element\u2019s \u201Cstyle\u201D attribute";
93 styleRules.push(inlineStyle);
96 var matchedStyleRules = node.ownerDocument.defaultView.getMatchedCSSRules(node, "", !Preferences.showUserAgentStyles);
97 if (matchedStyleRules) {
98 // Add rules in reverse order to match the cascade order.
99 for (var i = (matchedStyleRules.length - 1); i >= 0; --i)
100 styleRules.push(matchedStyleRules[i]);
104 var usedProperties = {};
105 var priorityUsed = false;
107 // Walk the style rules and make a list of all used and overloaded properties.
108 for (var i = 0; i < styleRules.length; ++i) {
109 var styleRule = styleRules[i];
110 if (styleRule.computedStyle)
113 styleRule.usedProperties = {};
115 var style = styleRule.style;
116 for (var j = 0; j < style.length; ++j) {
119 if (!priorityUsed && style.getPropertyPriority(name).length)
122 // If the property name is already used by another rule this is rule's
123 // property is overloaded, so don't add it to the rule's usedProperties.
124 if (!(name in usedProperties))
125 styleRule.usedProperties[name] = true;
127 if (name === "font") {
128 // The font property is not reported as a shorthand. Report finding the individual
129 // properties so they are visible in computed style.
130 // FIXME: remove this when http://bugs.webkit.org/show_bug.cgi?id=15598 is fixed.
131 styleRule.usedProperties["font-family"] = true;
132 styleRule.usedProperties["font-size"] = true;
133 styleRule.usedProperties["font-style"] = true;
134 styleRule.usedProperties["font-variant"] = true;
135 styleRule.usedProperties["font-weight"] = true;
136 styleRule.usedProperties["line-height"] = true;
140 // Add all the properties found in this style to the used properties list.
141 // Do this here so only future rules are affect by properties used in this rule.
142 for (var name in styleRules[i].usedProperties)
143 usedProperties[name] = true;
147 // Walk the properties again and account for !important.
148 var foundPriorityProperties = [];
150 // Walk in reverse to match the order !important overrides.
151 for (var i = (styleRules.length - 1); i >= 0; --i) {
152 if (styleRules[i].computedStyle)
155 var foundProperties = {};
156 var style = styleRules[i].style;
157 for (var j = 0; j < style.length; ++j) {
160 // Skip duplicate properties in the same rule.
161 if (name in foundProperties)
164 foundProperties[name] = true;
166 if (style.getPropertyPriority(name).length) {
167 if (!(name in foundPriorityProperties))
168 styleRules[i].usedProperties[name] = true;
170 delete styleRules[i].usedProperties[name];
171 foundPriorityProperties[name] = true;
172 } else if (name in foundPriorityProperties)
173 delete styleRules[i].usedProperties[name];
179 // Walk the style rules and update the sections with new overloaded and used properties.
180 for (var i = 0; i < styleRules.length; ++i) {
181 var styleRule = styleRules[i];
182 var section = styleRule.section;
183 section._usedProperties = (styleRule.usedProperties || usedProperties);
184 section.update((section === editedSection) || styleRule.computedStyle);
187 // Make a property section for each style rule.
188 for (var i = 0; i < styleRules.length; ++i) {
189 var styleRule = styleRules[i];
190 var subtitle = styleRule.subtitle;
191 delete styleRule.subtitle;
193 var computedStyle = styleRule.computedStyle;
194 delete styleRule.computedStyle;
196 var ruleUsedProperties = styleRule.usedProperties;
197 delete styleRule.usedProperties;
199 var editable = styleRule.editable;
200 delete styleRule.editable;
202 // Default editable to true if it was omitted.
203 if (typeof editable === "undefined")
206 var section = new WebInspector.StylePropertiesSection(styleRule, subtitle, computedStyle, (ruleUsedProperties || usedProperties), editable);
207 section.expanded = true;
210 body.appendChild(section.element);
211 this.sections.push(section);
217 WebInspector.StylesSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;
219 WebInspector.StylePropertiesSection = function(styleRule, subtitle, computedStyle, usedProperties, editable)
221 WebInspector.PropertiesSection.call(this, styleRule.selectorText);
223 this.styleRule = styleRule;
224 this.computedStyle = computedStyle;
225 this.editable = (editable && !computedStyle);
227 // Prevent editing the user agent rules.
228 if (this.styleRule.parentStyleSheet && !this.styleRule.parentStyleSheet.ownerNode)
229 this.editable = false;
231 this._usedProperties = usedProperties;
234 if (Preferences.showInheritedComputedStyleProperties)
235 this.element.addStyleClass("show-inherited");
237 var showInheritedLabel = document.createElement("label");
238 var showInheritedInput = document.createElement("input");
239 showInheritedInput.type = "checkbox";
240 showInheritedInput.checked = Preferences.showInheritedComputedStyleProperties;
242 var computedStyleSection = this;
243 var showInheritedToggleFunction = function(event) {
244 Preferences.showInheritedComputedStyleProperties = showInheritedInput.checked;
245 if (Preferences.showInheritedComputedStyleProperties)
246 computedStyleSection.element.addStyleClass("show-inherited");
248 computedStyleSection.element.removeStyleClass("show-inherited");
249 event.stopPropagation();
252 showInheritedLabel.addEventListener("click", showInheritedToggleFunction, false);
254 showInheritedLabel.appendChild(showInheritedInput);
255 showInheritedLabel.appendChild(document.createTextNode("Show inherited properties"));
256 this.subtitleElement.appendChild(showInheritedLabel);
259 if (this.styleRule.parentStyleSheet && this.styleRule.parentStyleSheet.href) {
260 var url = this.styleRule.parentStyleSheet.href;
261 subtitle = WebInspector.linkifyURL(url, url.trimURL(WebInspector.mainResource.domain).escapeHTML());
262 this.subtitleElement.addStyleClass("file");
263 } else if (this.styleRule.parentStyleSheet && !this.styleRule.parentStyleSheet.ownerNode)
264 subtitle = "user agent stylesheet";
266 subtitle = "inline stylesheet";
269 this.subtitle = subtitle;
273 WebInspector.StylePropertiesSection.prototype = {
276 return this._usedProperties || {};
279 set usedProperties(x)
281 this._usedProperties = x;
285 isPropertyInherited: function(property)
287 if (!this.computedStyle || !this._usedProperties)
289 // These properties should always show for Computed Style.
290 var alwaysShowComputedProperties = { "display": true, "height": true, "width": true };
291 return !(property in this.usedProperties) && !(property in alwaysShowComputedProperties);
294 isPropertyOverloaded: function(property, shorthand)
296 if (this.computedStyle || !this._usedProperties)
299 var used = (property in this.usedProperties);
300 if (used || !shorthand)
303 // Find out if any of the individual longhand properties of the shorthand
304 // are used, if none are then the shorthand is overloaded too.
305 var longhandProperties = this.styleRule.style.getLonghandProperties(property);
306 for (var j = 0; j < longhandProperties.length; ++j) {
307 var individualProperty = longhandProperties[j];
308 if (individualProperty in this.usedProperties)
315 update: function(full)
317 if (full || this.computedStyle) {
318 this.propertiesTreeOutline.removeChildren();
319 this.populated = false;
321 var child = this.propertiesTreeOutline.children[0];
323 child.overloaded = this.isPropertyOverloaded(child.name, child.shorthand);
324 child = child.traverseNextTreeElement(false, null, true);
329 onpopulate: function()
331 var style = this.styleRule.style;
335 var foundShorthands = {};
336 var uniqueProperties = style.getUniqueProperties();
337 uniqueProperties.sort();
339 for (var i = 0; i < uniqueProperties.length; ++i) {
340 var name = uniqueProperties[i];
341 var shorthand = style.getPropertyShorthand(name);
343 if (shorthand && shorthand in foundShorthands)
347 foundShorthands[shorthand] = true;
351 var isShorthand = (shorthand ? true : false);
352 var inherited = this.isPropertyInherited(name);
353 var overloaded = this.isPropertyOverloaded(name, isShorthand);
355 var item = new WebInspector.StylePropertyTreeElement(style, name, isShorthand, inherited, overloaded);
356 this.propertiesTreeOutline.appendChild(item);
361 WebInspector.StylePropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype;
363 WebInspector.StylePropertyTreeElement = function(style, name, shorthand, inherited, overloaded)
367 this.shorthand = shorthand;
368 this._inherited = inherited;
369 this._overloaded = overloaded;
371 // Pass an empty title, the title gets made later in onattach.
372 TreeElement.call(this, "", null, shorthand);
375 WebInspector.StylePropertyTreeElement.prototype = {
378 return this._inherited;
383 if (x === this._inherited)
391 return this._overloaded;
396 if (x === this._overloaded)
398 this._overloaded = x;
407 updateTitle: function()
409 // "Nicknames" for some common values that are easier to read.
410 var valueNicknames = {
411 "rgb(0, 0, 0)": "black",
414 "rgb(255, 255, 255)": "white",
419 "rgba(0, 0, 0, 0)": "transparent",
420 "rgb(255, 0, 0)": "red",
421 "rgb(0, 255, 0)": "lime",
422 "rgb(0, 0, 255)": "blue",
423 "rgb(255, 255, 0)": "yellow",
424 "rgb(255, 0, 255)": "magenta",
425 "rgb(0, 255, 255)": "cyan"
428 var priority = (this.shorthand ? this.style.getShorthandPriority(this.name) : this.style.getPropertyPriority(this.name));
429 var value = (this.shorthand ? this.style.getShorthandValue(this.name) : this.style.getPropertyValue(this.name));
430 var htmlValue = value;
432 if (priority && !priority.length)
435 priority = "!" + priority;
438 var urls = value.match(/url\([^)]+\)/);
440 for (var i = 0; i < urls.length; ++i) {
441 var url = urls[i].substring(4, urls[i].length - 1);
442 htmlValue = htmlValue.replace(urls[i], "url(" + WebInspector.linkifyURL(url) + ")");
445 if (value in valueNicknames)
446 htmlValue = valueNicknames[value];
447 htmlValue = htmlValue.escapeHTML();
450 htmlValue = value = "";
454 var nameElement = document.createElement("span");
455 nameElement.className = "name";
456 nameElement.textContent = this.name;
458 var valueElement = document.createElement("span");
459 valueElement.className = "value";
460 valueElement.innerHTML = htmlValue;
463 var priorityElement = document.createElement("span");
464 priorityElement.className = "priority";
465 priorityElement.textContent = priority;
468 this.listItemElement.removeChildren();
470 this.listItemElement.appendChild(nameElement);
471 this.listItemElement.appendChild(document.createTextNode(": "));
472 this.listItemElement.appendChild(valueElement);
474 if (priorityElement) {
475 this.listItemElement.appendChild(document.createTextNode(" "));
476 this.listItemElement.appendChild(priorityElement);
479 this.listItemElement.appendChild(document.createTextNode(";"));
482 // FIXME: this dosen't catch keyword based colors like black and white
483 var colors = value.match(/((rgb|hsl)a?\([^)]+\))|(#[0-9a-fA-F]{6})|(#[0-9a-fA-F]{3})/g);
485 var colorsLength = colors.length;
486 for (var i = 0; i < colorsLength; ++i) {
487 var swatchElement = document.createElement("span");
488 swatchElement.className = "swatch";
489 swatchElement.style.setProperty("background-color", colors[i]);
490 this.listItemElement.appendChild(swatchElement);
495 this.tooltip = this.name + ": " + (valueNicknames[value] || value) + (priority ? " " + priority : "");
498 updateState: function()
500 if (!this.listItemElement)
503 var value = (this.shorthand ? this.style.getShorthandValue(this.name) : this.style.getPropertyValue(this.name));
504 if (this.style.isPropertyImplicit(this.name) || value === "initial")
505 this.listItemElement.addStyleClass("implicit");
507 this.listItemElement.removeStyleClass("implicit");
510 this.listItemElement.addStyleClass("inherited");
512 this.listItemElement.removeStyleClass("inherited");
515 this.listItemElement.addStyleClass("overloaded");
517 this.listItemElement.removeStyleClass("overloaded");
520 onpopulate: function()
522 // Only populate once and if this property is a shorthand.
523 if (this.children.length || !this.shorthand)
526 var longhandProperties = this.style.getLonghandProperties(this.name);
527 for (var i = 0; i < longhandProperties.length; ++i) {
528 var name = longhandProperties[i];
530 if (this.treeOutline.section) {
531 var inherited = this.treeOutline.section.isPropertyInherited(name);
532 var overloaded = this.treeOutline.section.isPropertyOverloaded(name);
535 var item = new WebInspector.StylePropertyTreeElement(this.style, name, false, inherited, overloaded);
536 this.appendChild(item);
540 ondblclick: function(element, event)
542 this.startEditing(event.target);
545 startEditing: function(selectElement)
547 // FIXME: we don't allow editing of longhand properties under a shorthand right now.
548 if (this.parent.shorthand)
551 if (this.editing || (this.treeOutline.section && !this.treeOutline.section.editable))
555 this.previousTextContent = this.listItemElement.textContent;
557 this.listItemElement.addStyleClass("focusable");
558 this.listItemElement.addStyleClass("editing");
559 this.wasExpanded = this.expanded;
561 // Lie about out children to prevent toggling on click.
562 this.hasChildren = false;
565 selectElement = this.listItemElement;
567 window.getSelection().setBaseAndExtent(selectElement, 0, selectElement, 1);
569 var treeElement = this;
570 this.listItemElement.blurred = function() { treeElement.commitEditing() };
571 this.listItemElement.handleKeyEvent = function(event) {
572 if (event.keyIdentifier === "Enter") {
573 treeElement.commitEditing();
574 event.preventDefault();
575 } else if (event.keyCode === 27) { // Escape key
576 treeElement.cancelEditing();
577 event.preventDefault();
581 this.previousFocusElement = WebInspector.currentFocusElement;
582 WebInspector.currentFocusElement = this.listItemElement;
585 endEditing: function()
587 // Revert the changes done in startEditing().
588 delete this.listItemElement.blurred;
589 delete this.listItemElement.handleKeyEvent;
591 WebInspector.currentFocusElement = this.previousFocusElement;
592 delete this.previousFocusElement;
594 delete this.previousTextContent;
597 this.listItemElement.removeStyleClass("focusable");
598 this.listItemElement.removeStyleClass("editing");
599 this.hasChildren = (this.children.length ? true : false);
600 if (this.wasExpanded) {
601 delete this.wasExpanded;
606 cancelEditing: function()
612 commitEditing: function()
614 var previousContent = this.previousTextContent;
618 var userInput = this.listItemElement.textContent;
619 if (userInput === previousContent)
620 return; // nothing changed, so do nothing else
622 var userInputLength = userInput.trimWhitespace().length;
624 // Create a new element to parse the user input CSS.
625 var parseElement = document.createElement("span");
626 parseElement.setAttribute("style", userInput);
628 var userInputStyle = parseElement.style;
629 if (userInputStyle.length || !userInputLength) {
630 // The input was parsable or the user deleted everything, so remove the
631 // original property from the real style declaration. If this represents
632 // a shorthand remove all the longhand properties.
633 if (this.shorthand) {
634 var longhandProperties = this.style.getLonghandProperties(this.name);
635 for (var i = 0; i < longhandProperties.length; ++i)
636 this.style.removeProperty(longhandProperties[i]);
638 this.style.removeProperty(this.name);
641 if (!userInputLength) {
642 // The user deleted the everything, so remove the tree element and update.
643 if (this.treeOutline.section && this.treeOutline.section.pane)
644 this.treeOutline.section.pane.update();
645 this.parent.removeChild(this);
649 if (!userInputStyle.length) {
650 // The user typed something, but it didn't parse. Just abort and restore
651 // the original title for this property.
656 // Iterate of the properties on the test element's style declaration and
657 // add them to the real style declaration. We take care to move shorthands.
658 var foundShorthands = {};
659 var uniqueProperties = userInputStyle.getUniqueProperties();
660 for (var i = 0; i < uniqueProperties.length; ++i) {
661 var name = uniqueProperties[i];
662 var shorthand = userInputStyle.getPropertyShorthand(name);
664 if (shorthand && shorthand in foundShorthands)
668 var value = userInputStyle.getShorthandValue(shorthand);
669 var priority = userInputStyle.getShorthandPriority(shorthand);
670 foundShorthands[shorthand] = true;
672 var value = userInputStyle.getPropertyValue(name);
673 var priority = userInputStyle.getPropertyPriority(name);
676 // Set the property on the real style declaration.
677 this.style.setProperty((shorthand || name), value, priority);
680 if (this.treeOutline.section && this.treeOutline.section.pane)
681 this.treeOutline.section.pane.update(null, this.treeOutline.section);
682 else if (this.treeOutline.section)
683 this.treeOutline.section.update(true);
685 this.updateTitle(); // FIXME: this will not show new properties. But we don't hit his case yet.
689 WebInspector.StylePropertyTreeElement.prototype.__proto__ = TreeElement.prototype;