diff options
Diffstat (limited to 'WebCore/page/inspector/StylesSidebarPane.js')
-rw-r--r-- | WebCore/page/inspector/StylesSidebarPane.js | 682 |
1 files changed, 682 insertions, 0 deletions
diff --git a/WebCore/page/inspector/StylesSidebarPane.js b/WebCore/page/inspector/StylesSidebarPane.js new file mode 100644 index 0000000..a701a35 --- /dev/null +++ b/WebCore/page/inspector/StylesSidebarPane.js @@ -0,0 +1,682 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.StylesSidebarPane = function() +{ + WebInspector.SidebarPane.call(this, WebInspector.UIString("Styles")); +} + +WebInspector.StylesSidebarPane.prototype = { + update: function(node, editedSection) + { + var refresh = false; + + if (!node || node === this.node) + refresh = true; + + if (node && node.nodeType === Node.TEXT_NODE && node.parentNode) + node = node.parentNode; + + if (node && node.nodeType !== Node.ELEMENT_NODE) + node = null; + + if (node) + this.node = node; + else + node = this.node; + + var body = this.bodyElement; + if (!refresh || !node) { + body.removeChildren(); + this.sections = []; + } + + if (!node) + return; + + var styleRules = []; + + if (refresh) { + for (var i = 0; i < this.sections.length; ++i) { + var section = this.sections[i]; + if (section.computedStyle) + section.styleRule.style = node.ownerDocument.defaultView.getComputedStyle(node); + var styleRule = { section: section, style: section.styleRule.style, computedStyle: section.computedStyle }; + styleRules.push(styleRule); + } + } else { + var computedStyle = node.ownerDocument.defaultView.getComputedStyle(node); + styleRules.push({ computedStyle: true, selectorText: WebInspector.UIString("Computed Style"), style: computedStyle, editable: false }); + + var nodeName = node.nodeName.toLowerCase(); + for (var i = 0; i < node.attributes.length; ++i) { + var attr = node.attributes[i]; + if (attr.style) { + var attrStyle = { style: attr.style, editable: false }; + attrStyle.subtitle = WebInspector.UIString("element’s “%s” attribute", attr.name); + attrStyle.selectorText = nodeName + "[" + attr.name; + if (attr.value.length) + attrStyle.selectorText += "=" + attr.value; + attrStyle.selectorText += "]"; + styleRules.push(attrStyle); + } + } + + if (node.style && node.style.length) { + var inlineStyle = { selectorText: WebInspector.UIString("Inline Style Attribute"), style: node.style }; + inlineStyle.subtitle = WebInspector.UIString("element’s “%s” attribute", "style"); + styleRules.push(inlineStyle); + } + + var matchedStyleRules = node.ownerDocument.defaultView.getMatchedCSSRules(node, "", !Preferences.showUserAgentStyles); + if (matchedStyleRules) { + // Add rules in reverse order to match the cascade order. + for (var i = (matchedStyleRules.length - 1); i >= 0; --i) + styleRules.push(matchedStyleRules[i]); + } + } + + var usedProperties = {}; + var priorityUsed = false; + + // Walk the style rules and make a list of all used and overloaded properties. + for (var i = 0; i < styleRules.length; ++i) { + var styleRule = styleRules[i]; + if (styleRule.computedStyle) + continue; + + styleRule.usedProperties = {}; + + var style = styleRule.style; + for (var j = 0; j < style.length; ++j) { + var name = style[j]; + + if (!priorityUsed && style.getPropertyPriority(name).length) + priorityUsed = true; + + // If the property name is already used by another rule then this rule's + // property is overloaded, so don't add it to the rule's usedProperties. + if (!(name in usedProperties)) + styleRule.usedProperties[name] = true; + + if (name === "font") { + // The font property is not reported as a shorthand. Report finding the individual + // properties so they are visible in computed style. + // FIXME: remove this when http://bugs.webkit.org/show_bug.cgi?id=15598 is fixed. + styleRule.usedProperties["font-family"] = true; + styleRule.usedProperties["font-size"] = true; + styleRule.usedProperties["font-style"] = true; + styleRule.usedProperties["font-variant"] = true; + styleRule.usedProperties["font-weight"] = true; + styleRule.usedProperties["line-height"] = true; + } + } + + // Add all the properties found in this style to the used properties list. + // Do this here so only future rules are affect by properties used in this rule. + for (var name in styleRules[i].usedProperties) + usedProperties[name] = true; + } + + if (priorityUsed) { + // Walk the properties again and account for !important. + var foundPriorityProperties = []; + + // Walk in reverse to match the order !important overrides. + for (var i = (styleRules.length - 1); i >= 0; --i) { + if (styleRules[i].computedStyle) + continue; + + var style = styleRules[i].style; + var uniqueProperties = style.getUniqueProperties(); + for (var j = 0; j < uniqueProperties.length; ++j) { + var name = uniqueProperties[j]; + if (style.getPropertyPriority(name).length) { + if (!(name in foundPriorityProperties)) + styleRules[i].usedProperties[name] = true; + else + delete styleRules[i].usedProperties[name]; + foundPriorityProperties[name] = true; + } else if (name in foundPriorityProperties) + delete styleRules[i].usedProperties[name]; + } + } + } + + if (refresh) { + // Walk the style rules and update the sections with new overloaded and used properties. + for (var i = 0; i < styleRules.length; ++i) { + var styleRule = styleRules[i]; + var section = styleRule.section; + section._usedProperties = (styleRule.usedProperties || usedProperties); + section.update((section === editedSection) || styleRule.computedStyle); + } + } else { + // Make a property section for each style rule. + for (var i = 0; i < styleRules.length; ++i) { + var styleRule = styleRules[i]; + var subtitle = styleRule.subtitle; + delete styleRule.subtitle; + + var computedStyle = styleRule.computedStyle; + delete styleRule.computedStyle; + + var ruleUsedProperties = styleRule.usedProperties; + delete styleRule.usedProperties; + + var editable = styleRule.editable; + delete styleRule.editable; + + // Default editable to true if it was omitted. + if (typeof editable === "undefined") + editable = true; + + var section = new WebInspector.StylePropertiesSection(styleRule, subtitle, computedStyle, (ruleUsedProperties || usedProperties), editable); + section.expanded = true; + section.pane = this; + + body.appendChild(section.element); + this.sections.push(section); + } + } + } +} + +WebInspector.StylesSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; + +WebInspector.StylePropertiesSection = function(styleRule, subtitle, computedStyle, usedProperties, editable) +{ + WebInspector.PropertiesSection.call(this, styleRule.selectorText); + + this.styleRule = styleRule; + this.computedStyle = computedStyle; + this.editable = (editable && !computedStyle); + + // Prevent editing the user agent rules. + if (this.styleRule.parentStyleSheet && !this.styleRule.parentStyleSheet.ownerNode && !this.styleRule.parentStyleSheet.href) + this.editable = false; + + this._usedProperties = usedProperties; + + if (computedStyle) { + if (Preferences.showInheritedComputedStyleProperties) + this.element.addStyleClass("show-inherited"); + + var showInheritedLabel = document.createElement("label"); + var showInheritedInput = document.createElement("input"); + showInheritedInput.type = "checkbox"; + showInheritedInput.checked = Preferences.showInheritedComputedStyleProperties; + + var computedStyleSection = this; + var showInheritedToggleFunction = function(event) { + Preferences.showInheritedComputedStyleProperties = showInheritedInput.checked; + if (Preferences.showInheritedComputedStyleProperties) + computedStyleSection.element.addStyleClass("show-inherited"); + else + computedStyleSection.element.removeStyleClass("show-inherited"); + event.stopPropagation(); + }; + + showInheritedLabel.addEventListener("click", showInheritedToggleFunction, false); + + showInheritedLabel.appendChild(showInheritedInput); + showInheritedLabel.appendChild(document.createTextNode(WebInspector.UIString("Show inherited properties"))); + this.subtitleElement.appendChild(showInheritedLabel); + } else { + if (!subtitle) { + if (this.styleRule.parentStyleSheet && this.styleRule.parentStyleSheet.href) { + var url = this.styleRule.parentStyleSheet.href; + subtitle = WebInspector.linkifyURL(url, url.trimURL(WebInspector.mainResource.domain).escapeHTML()); + this.subtitleElement.addStyleClass("file"); + } else if (this.styleRule.parentStyleSheet && !this.styleRule.parentStyleSheet.ownerNode) + subtitle = WebInspector.UIString("user agent stylesheet"); + else + subtitle = WebInspector.UIString("inline stylesheet"); + } + + this.subtitle = subtitle; + } +} + +WebInspector.StylePropertiesSection.prototype = { + get usedProperties() + { + return this._usedProperties || {}; + }, + + set usedProperties(x) + { + this._usedProperties = x; + this.update(); + }, + + isPropertyInherited: function(property) + { + if (!this.computedStyle || !this._usedProperties) + return false; + // These properties should always show for Computed Style. + var alwaysShowComputedProperties = { "display": true, "height": true, "width": true }; + return !(property in this.usedProperties) && !(property in alwaysShowComputedProperties); + }, + + isPropertyOverloaded: function(property, shorthand) + { + if (this.computedStyle || !this._usedProperties) + return false; + + var used = (property in this.usedProperties); + if (used || !shorthand) + return !used; + + // Find out if any of the individual longhand properties of the shorthand + // are used, if none are then the shorthand is overloaded too. + var longhandProperties = this.styleRule.style.getLonghandProperties(property); + for (var j = 0; j < longhandProperties.length; ++j) { + var individualProperty = longhandProperties[j]; + if (individualProperty in this.usedProperties) + return false; + } + + return true; + }, + + update: function(full) + { + if (full || this.computedStyle) { + this.propertiesTreeOutline.removeChildren(); + this.populated = false; + } else { + var child = this.propertiesTreeOutline.children[0]; + while (child) { + child.overloaded = this.isPropertyOverloaded(child.name, child.shorthand); + child = child.traverseNextTreeElement(false, null, true); + } + } + }, + + onpopulate: function() + { + var style = this.styleRule.style; + if (!style.length) + return; + + var foundShorthands = {}; + var uniqueProperties = style.getUniqueProperties(); + uniqueProperties.sort(); + + for (var i = 0; i < uniqueProperties.length; ++i) { + var name = uniqueProperties[i]; + var shorthand = style.getPropertyShorthand(name); + + if (shorthand && shorthand in foundShorthands) + continue; + + if (shorthand) { + foundShorthands[shorthand] = true; + name = shorthand; + } + + var isShorthand = (shorthand ? true : false); + var inherited = this.isPropertyInherited(name); + var overloaded = this.isPropertyOverloaded(name, isShorthand); + + var item = new WebInspector.StylePropertyTreeElement(style, name, isShorthand, inherited, overloaded); + this.propertiesTreeOutline.appendChild(item); + } + } +} + +WebInspector.StylePropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype; + +WebInspector.StylePropertyTreeElement = function(style, name, shorthand, inherited, overloaded) +{ + this.style = style; + this.name = name; + this.shorthand = shorthand; + this._inherited = inherited; + this._overloaded = overloaded; + + // Pass an empty title, the title gets made later in onattach. + TreeElement.call(this, "", null, shorthand); +} + +WebInspector.StylePropertyTreeElement.prototype = { + get inherited() + { + return this._inherited; + }, + + set inherited(x) + { + if (x === this._inherited) + return; + this._inherited = x; + this.updateState(); + }, + + get overloaded() + { + return this._overloaded; + }, + + set overloaded(x) + { + if (x === this._overloaded) + return; + this._overloaded = x; + this.updateState(); + }, + + onattach: function() + { + this.updateTitle(); + }, + + updateTitle: function() + { + // "Nicknames" for some common values that are easier to read. + var valueNicknames = { + "rgb(0, 0, 0)": "black", + "#000": "black", + "#000000": "black", + "rgb(255, 255, 255)": "white", + "#fff": "white", + "#ffffff": "white", + "#FFF": "white", + "#FFFFFF": "white", + "rgba(0, 0, 0, 0)": "transparent", + "rgb(255, 0, 0)": "red", + "rgb(0, 255, 0)": "lime", + "rgb(0, 0, 255)": "blue", + "rgb(255, 255, 0)": "yellow", + "rgb(255, 0, 255)": "magenta", + "rgb(0, 255, 255)": "cyan" + }; + + var priority = (this.shorthand ? this.style.getShorthandPriority(this.name) : this.style.getPropertyPriority(this.name)); + var value = (this.shorthand ? this.style.getShorthandValue(this.name) : this.style.getPropertyValue(this.name)); + var htmlValue = value; + + if (priority && !priority.length) + delete priority; + if (priority) + priority = "!" + priority; + + if (value) { + var urls = value.match(/url\([^)]+\)/); + if (urls) { + for (var i = 0; i < urls.length; ++i) { + var url = urls[i].substring(4, urls[i].length - 1); + htmlValue = htmlValue.replace(urls[i], "url(" + WebInspector.linkifyURL(url) + ")"); + } + } else { + if (value in valueNicknames) + htmlValue = valueNicknames[value]; + htmlValue = htmlValue.escapeHTML(); + } + } else + htmlValue = value = ""; + + this.updateState(); + + var nameElement = document.createElement("span"); + nameElement.className = "name"; + nameElement.textContent = this.name; + + var valueElement = document.createElement("span"); + valueElement.className = "value"; + valueElement.innerHTML = htmlValue; + + if (priority) { + var priorityElement = document.createElement("span"); + priorityElement.className = "priority"; + priorityElement.textContent = priority; + } + + this.listItemElement.removeChildren(); + + this.listItemElement.appendChild(nameElement); + this.listItemElement.appendChild(document.createTextNode(": ")); + this.listItemElement.appendChild(valueElement); + + if (priorityElement) { + this.listItemElement.appendChild(document.createTextNode(" ")); + this.listItemElement.appendChild(priorityElement); + } + + this.listItemElement.appendChild(document.createTextNode(";")); + + if (value) { + // FIXME: this dosen't catch keyword based colors like black and white + var colors = value.match(/((rgb|hsl)a?\([^)]+\))|(#[0-9a-fA-F]{6})|(#[0-9a-fA-F]{3})/g); + if (colors) { + var colorsLength = colors.length; + for (var i = 0; i < colorsLength; ++i) { + var swatchElement = document.createElement("span"); + swatchElement.className = "swatch"; + swatchElement.style.setProperty("background-color", colors[i]); + this.listItemElement.appendChild(swatchElement); + } + } + } + + this.tooltip = this.name + ": " + (valueNicknames[value] || value) + (priority ? " " + priority : ""); + }, + + updateState: function() + { + if (!this.listItemElement) + return; + + var value = (this.shorthand ? this.style.getShorthandValue(this.name) : this.style.getPropertyValue(this.name)); + if (this.style.isPropertyImplicit(this.name) || value === "initial") + this.listItemElement.addStyleClass("implicit"); + else + this.listItemElement.removeStyleClass("implicit"); + + if (this.inherited) + this.listItemElement.addStyleClass("inherited"); + else + this.listItemElement.removeStyleClass("inherited"); + + if (this.overloaded) + this.listItemElement.addStyleClass("overloaded"); + else + this.listItemElement.removeStyleClass("overloaded"); + }, + + onpopulate: function() + { + // Only populate once and if this property is a shorthand. + if (this.children.length || !this.shorthand) + return; + + var longhandProperties = this.style.getLonghandProperties(this.name); + for (var i = 0; i < longhandProperties.length; ++i) { + var name = longhandProperties[i]; + + if (this.treeOutline.section) { + var inherited = this.treeOutline.section.isPropertyInherited(name); + var overloaded = this.treeOutline.section.isPropertyOverloaded(name); + } + + var item = new WebInspector.StylePropertyTreeElement(this.style, name, false, inherited, overloaded); + this.appendChild(item); + } + }, + + ondblclick: function(element, event) + { + this.startEditing(event.target); + }, + + startEditing: function(selectElement) + { + // FIXME: we don't allow editing of longhand properties under a shorthand right now. + if (this.parent.shorthand) + return; + + if (this.editing || (this.treeOutline.section && !this.treeOutline.section.editable)) + return; + + this.editing = true; + this.previousTextContent = this.listItemElement.textContent; + + this.listItemElement.addStyleClass("focusable"); + this.listItemElement.addStyleClass("editing"); + this.wasExpanded = this.expanded; + this.collapse(); + // Lie about out children to prevent toggling on click. + this.hasChildren = false; + + if (!selectElement) + selectElement = this.listItemElement; + + window.getSelection().setBaseAndExtent(selectElement, 0, selectElement, 1); + + var treeElement = this; + this.listItemElement.blurred = function() { treeElement.commitEditing() }; + this.listItemElement.handleKeyEvent = function(event) { + if (event.keyIdentifier === "Enter") { + treeElement.commitEditing(); + event.preventDefault(); + } else if (event.keyCode === 27) { // Escape key + treeElement.cancelEditing(); + event.preventDefault(); + } + }; + + this.previousFocusElement = WebInspector.currentFocusElement; + WebInspector.currentFocusElement = this.listItemElement; + }, + + endEditing: function() + { + // Revert the changes done in startEditing(). + delete this.listItemElement.blurred; + delete this.listItemElement.handleKeyEvent; + + WebInspector.currentFocusElement = this.previousFocusElement; + delete this.previousFocusElement; + + delete this.previousTextContent; + delete this.editing; + + this.listItemElement.removeStyleClass("focusable"); + this.listItemElement.removeStyleClass("editing"); + this.hasChildren = (this.children.length ? true : false); + if (this.wasExpanded) { + delete this.wasExpanded; + this.expand(); + } + }, + + cancelEditing: function() + { + this.endEditing(); + this.updateTitle(); + }, + + commitEditing: function() + { + var previousContent = this.previousTextContent; + + this.endEditing(); + + var userInput = this.listItemElement.textContent; + if (userInput === previousContent) + return; // nothing changed, so do nothing else + + var userInputLength = userInput.trimWhitespace().length; + + // Create a new element to parse the user input CSS. + var parseElement = document.createElement("span"); + parseElement.setAttribute("style", userInput); + + var userInputStyle = parseElement.style; + if (userInputStyle.length || !userInputLength) { + // The input was parsable or the user deleted everything, so remove the + // original property from the real style declaration. If this represents + // a shorthand remove all the longhand properties. + if (this.shorthand) { + var longhandProperties = this.style.getLonghandProperties(this.name); + for (var i = 0; i < longhandProperties.length; ++i) + this.style.removeProperty(longhandProperties[i]); + } else + this.style.removeProperty(this.name); + } + + if (!userInputLength) { + // The user deleted the everything, so remove the tree element and update. + if (this.treeOutline.section && this.treeOutline.section.pane) + this.treeOutline.section.pane.update(); + this.parent.removeChild(this); + return; + } + + if (!userInputStyle.length) { + // The user typed something, but it didn't parse. Just abort and restore + // the original title for this property. + this.updateTitle(); + return; + } + + // Iterate of the properties on the test element's style declaration and + // add them to the real style declaration. We take care to move shorthands. + var foundShorthands = {}; + var uniqueProperties = userInputStyle.getUniqueProperties(); + for (var i = 0; i < uniqueProperties.length; ++i) { + var name = uniqueProperties[i]; + var shorthand = userInputStyle.getPropertyShorthand(name); + + if (shorthand && shorthand in foundShorthands) + continue; + + if (shorthand) { + var value = userInputStyle.getShorthandValue(shorthand); + var priority = userInputStyle.getShorthandPriority(shorthand); + foundShorthands[shorthand] = true; + } else { + var value = userInputStyle.getPropertyValue(name); + var priority = userInputStyle.getPropertyPriority(name); + } + + // Set the property on the real style declaration. + this.style.setProperty((shorthand || name), value, priority); + } + + if (this.treeOutline.section && this.treeOutline.section.pane) + this.treeOutline.section.pane.update(null, this.treeOutline.section); + else if (this.treeOutline.section) + this.treeOutline.section.update(true); + else + this.updateTitle(); // FIXME: this will not show new properties. But we don't hit his case yet. + } +} + +WebInspector.StylePropertyTreeElement.prototype.__proto__ = TreeElement.prototype; |