/* * Software License Agreement (BSD License) * * Copyright (c) 2007, Parakey Inc. * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * * 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. * * * Neither the name of Parakey Inc. nor the names of its * contributors may be used to endorse or promote products * derived from this software without specific prior * written permission of Parakey Inc. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT OWNER OR * 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. */ /* * Creator: * Joe Hewitt * Contributors * John J. Barton (IBM Almaden) * Jan Odvarko (Mozilla Corp.) * Max Stepanov (Aptana Inc.) * Rob Campbell (Mozilla Corp.) * Hans Hillen (Paciello Group, Mozilla) * Curtis Bartley (Mozilla Corp.) * Mike Collins (IBM Almaden) * Kevin Decker * Mike Ratcliffe (Comartis AG) * Hernan Rodríguez Colmeiro * Austin Andrews * Christoph Dorn * Steven Roussey (AppCenter Inc, Network54) */ var EXPORTED_SYMBOLS = ["style"]; const Cc = Components.classes; const Ci = Components.interfaces; const Cu = Components.utils; Cu.import("resource://gre/modules/Services.jsm"); var style = { /** * initialize domUtils */ initialize: function CSS_initialize() { this.domUtils = Cc["@mozilla.org/inspector/dom-utils;1"]. getService(Ci["inIDOMUtils"]); }, /** * Is the given property sheet a system (user agent) stylesheet? * * @param aSheet * a stylesheet */ isSystemStyleSheet: function CSS_isSystemStyleSheet(aSheet) { if (!aSheet) return true; let url = aSheet.href; if (!url) return false; if (url.length == 0) return true; if (url[0] == 'h') return false; if (url.substr(0, 9) == "resource:") return true; if (url.substr(0, 7) == "chrome:") return true; if (url == "XPCSafeJSObjectWrapper.cpp") return true; if (url.substr(0, 6) == "about:") return true; return false; }, /** * Parse properties from a given style object. * Borrowed from Firebug's css.js. * * @param aStyle * a style object */ parseCSSProperties: function CSS_parseCSSProps(aStyle) { let properties = []; let lines = aStyle.cssText.match(/(?:[^;\(]*(?:\([^\)]*?\))?[^;\(]*)*;?/g); let propRE = /\s*([^:\s]*)\s*:\s*(.*?)\s*(! important)?;?$/; let line, i = 0; while(line = lines[i++]) { let match = propRE.exec(line); if (!match) continue; let name = match[1]; let value = match[2]; let important = !!match[3]; // true if match[3] is non-empty properties.unshift({name: name, value: value, important: important}); } return properties; }, /** * Mark properties overridden further up the hierarchy. * * @param aProps * Array of properties. * @param aUsedProps * Object of arrays keyed by property name. * @param aInherit * Boolean of whether or not we are in inherited mode. */ markOverriddenProperties: function CSS_markOverriddenProperties(aProps, aUsedProps, aInherit) { for (let i = 0; i < aProps.length; ++i) { let prop = aProps[i]; if (aUsedProps.hasOwnProperty(prop.name)) { // all previous occurrences of this property let deadProps = aUsedProps[prop.name]; for (let j = 0; j < deadProps.length; ++j) { let deadProp = deadProps[j]; if (!deadProp.disabled && !deadProp.wasInherited && deadProp.important && !prop.important) { prop.overridden = true; // new occurrence overridden } else if (!prop.disabled) { deadProp.overridden = true; // previous occurrences overridden } else { aUsedProps[prop.name] = []; } prop.wasInherited = aInherit ? true : false; // all occurrences of a property seen so far, by name aUsedProps[prop.name].push(prop); } } } }, /** * Sort given properties in lexical order by name. * * @param properties * An array of properties. * @returns sorted array. */ sortProperties: function CSS_sortProperties(properties) { properties.sort(function(a, b) { if (a.name < b.name) { return -1; } if (a.name > b.name) { return 1; } return 0; }); }, /** * Get properties for a given element and push them to the rules array. * * @param aNode * a DOM node * @param rules * An array of rules to add properties to. * @param usedProps * Object of arrays keyed by property name. * @param inherit * boolean determining whether or not we're in inherit mode */ getStyleProperties: function CSS_getStyleProperties(aNode, aRules, aUsedProps, aInherit) { let properties = this.parseCSSProperties(aNode.style, aInherit); this.sortProperties(properties); this.markOverriddenProperties(properties, aUsedProps, aInherit); if (properties.length) { aRules.push({rule: aNode, selector: "element.style", properties: properties, inherited: aInherit}); } }, /** * Get properties for a given rule. * * @param aRule * A Rule from a stylesheet. */ getRuleProperties: function CSS_getRuleProperties(aRule) { let style = aRule.style; return this.parseCSSProperties(style); }, /** * Recursively get rules for an element's parents and add them to the * sections array. * * @param aNode * an element in a DOM tree. * @param sections * an array of sections * @param usedProps * Object of arrays keyed by property name. */ getInheritedRules: function CSS_getInheritedRules(aNode, aSections, aUsedProps) { let parent = aNode.parentNode; if (parent && parent.nodeType == 1) { this.getInheritedRules(parent, aSections, aUsedProps); let rules = []; this.getElementRules(parent, rules, aUsedProps, true); if (rules.length) { aSections.unshift({element: parent, rules: rules}); } } }, /** * Get the CSS style rules for a given node in the DOM and append them to the * rules array. * * @param aNode * an element in the DOM tree. * @param aRules * an array of rules. * @param aUsedProps * Object of arrays keyed by property name. * @param aInherit * boolean indicating whether we are in an inherited mode or not */ getElementRules: function CSS_getElementRules(aNode, aRules, aUsedProps, aInherit) { let inspectedRules; try { inspectedRules = this.domUtils.getCSSStyleRules(aNode); } catch (ex) { Services.console.logStringMessage(ex); } if (!inspectedRules) return; for (let i = 0; i < inspectedRules.Count(); ++i) { let rule = inspectedRules.GetElementAt(i); let href = rule.parentStyleSheet.href; if (!href) { // Null href means inline style. href = aNode.ownerDocument.location.href; } let isSystemSheet = this.isSystemStyleSheet(rule.parentStyleSheet); if (isSystemSheet) continue; let properties = this.getRuleProperties(rule, aInherit); if (aInherit && !properties.length) continue; let line = this.domUtils.getRuleLine(rule); let ruleId = rule.selectorText + " " + href + " (" + line + ")"; let sourceLink = "view-source:" + href + "#" + line; this.markOverriddenProperties(properties, aUsedProps, aInherit); aRules.unshift( {rule: rule, id: ruleId, selector: rule.selectorText, properties: properties, inherited: aInherit, sourceLink: sourceLink, isSystemSheet: isSystemSheet}); } if (aNode.style) { this.getStyleProperties(aNode, aRules, aUsedProps, aInherit); } }, };