1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585 |
- /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- /*
- * About the objects defined in this file:
- * - CssLogic contains style information about a view context. It provides
- * access to 2 sets of objects: Css[Sheet|Rule|Selector] provide access to
- * information that does not change when the selected element changes while
- * Css[Property|Selector]Info provide information that is dependent on the
- * selected element.
- * Its key methods are highlight(), getPropertyInfo() and forEachSheet(), etc
- *
- * - CssSheet provides a more useful API to a DOM CSSSheet for our purposes,
- * including shortSource and href.
- * - CssRule a more useful API to a nsIDOMCSSRule including access to the group
- * of CssSelectors that the rule provides properties for
- * - CssSelector A single selector - i.e. not a selector group. In other words
- * a CssSelector does not contain ','. This terminology is different from the
- * standard DOM API, but more inline with the definition in the spec.
- *
- * - CssPropertyInfo contains style information for a single property for the
- * highlighted element.
- * - CssSelectorInfo is a wrapper around CssSelector, which adds sorting with
- * reference to the selected element.
- */
- "use strict";
- const { Cc, Ci, Cu } = require("chrome");
- const DevToolsUtils = require("devtools/shared/DevToolsUtils");
- const { getRootBindingParent } = require("devtools/shared/layout/utils");
- const nodeConstants = require("devtools/shared/dom-node-constants");
- const {l10n, isContentStylesheet, shortSource, FILTER, STATUS} = require("devtools/shared/inspector/css-logic");
- loader.lazyRequireGetter(this, "CSSLexer", "devtools/shared/css/lexer");
- /**
- * @param {function} isInherited A function that determines if the CSS property
- * is inherited.
- */
- function CssLogic(isInherited) {
- // The cache of examined CSS properties.
- this._isInherited = isInherited;
- this._propertyInfos = {};
- }
- exports.CssLogic = CssLogic;
- CssLogic.prototype = {
- // Both setup by highlight().
- viewedElement: null,
- viewedDocument: null,
- // The cache of the known sheets.
- _sheets: null,
- // Have the sheets been cached?
- _sheetsCached: false,
- // The total number of rules, in all stylesheets, after filtering.
- _ruleCount: 0,
- // The computed styles for the viewedElement.
- _computedStyle: null,
- // Source filter. Only display properties coming from the given source
- _sourceFilter: FILTER.USER,
- // Used for tracking unique CssSheet/CssRule/CssSelector objects, in a run of
- // processMatchedSelectors().
- _passId: 0,
- // Used for tracking matched CssSelector objects.
- _matchId: 0,
- _matchedRules: null,
- _matchedSelectors: null,
- // Cached keyframes rules in all stylesheets
- _keyframesRules: null,
- /**
- * Reset various properties
- */
- reset: function () {
- this._propertyInfos = {};
- this._ruleCount = 0;
- this._sheetIndex = 0;
- this._sheets = {};
- this._sheetsCached = false;
- this._matchedRules = null;
- this._matchedSelectors = null;
- this._keyframesRules = [];
- },
- /**
- * Focus on a new element - remove the style caches.
- *
- * @param {nsIDOMElement} aViewedElement the element the user has highlighted
- * in the Inspector.
- */
- highlight: function (viewedElement) {
- if (!viewedElement) {
- this.viewedElement = null;
- this.viewedDocument = null;
- this._computedStyle = null;
- this.reset();
- return;
- }
- if (viewedElement === this.viewedElement) {
- return;
- }
- this.viewedElement = viewedElement;
- let doc = this.viewedElement.ownerDocument;
- if (doc != this.viewedDocument) {
- // New document: clear/rebuild the cache.
- this.viewedDocument = doc;
- // Hunt down top level stylesheets, and cache them.
- this._cacheSheets();
- } else {
- // Clear cached data in the CssPropertyInfo objects.
- this._propertyInfos = {};
- }
- this._matchedRules = null;
- this._matchedSelectors = null;
- this._computedStyle = CssLogic.getComputedStyle(this.viewedElement);
- },
- /**
- * Get the values of all the computed CSS properties for the highlighted
- * element.
- * @returns {object} The computed CSS properties for a selected element
- */
- get computedStyle() {
- return this._computedStyle;
- },
- /**
- * Get the source filter.
- * @returns {string} The source filter being used.
- */
- get sourceFilter() {
- return this._sourceFilter;
- },
- /**
- * Source filter. Only display properties coming from the given source (web
- * address). Note that in order to avoid information overload we DO NOT show
- * unmatched system rules.
- * @see FILTER.*
- */
- set sourceFilter(value) {
- let oldValue = this._sourceFilter;
- this._sourceFilter = value;
- let ruleCount = 0;
- // Update the CssSheet objects.
- this.forEachSheet(function (sheet) {
- sheet._sheetAllowed = -1;
- if (sheet.contentSheet && sheet.sheetAllowed) {
- ruleCount += sheet.ruleCount;
- }
- }, this);
- this._ruleCount = ruleCount;
- // Full update is needed because the this.processMatchedSelectors() method
- // skips UA stylesheets if the filter does not allow such sheets.
- let needFullUpdate = (oldValue == FILTER.UA || value == FILTER.UA);
- if (needFullUpdate) {
- this._matchedRules = null;
- this._matchedSelectors = null;
- this._propertyInfos = {};
- } else {
- // Update the CssPropertyInfo objects.
- for (let property in this._propertyInfos) {
- this._propertyInfos[property].needRefilter = true;
- }
- }
- },
- /**
- * Return a CssPropertyInfo data structure for the currently viewed element
- * and the specified CSS property. If there is no currently viewed element we
- * return an empty object.
- *
- * @param {string} property The CSS property to look for.
- * @return {CssPropertyInfo} a CssPropertyInfo structure for the given
- * property.
- */
- getPropertyInfo: function (property) {
- if (!this.viewedElement) {
- return {};
- }
- let info = this._propertyInfos[property];
- if (!info) {
- info = new CssPropertyInfo(this, property, this._isInherited);
- this._propertyInfos[property] = info;
- }
- return info;
- },
- /**
- * Cache all the stylesheets in the inspected document
- * @private
- */
- _cacheSheets: function () {
- this._passId++;
- this.reset();
- // styleSheets isn't an array, but forEach can work on it anyway
- Array.prototype.forEach.call(this.viewedDocument.styleSheets,
- this._cacheSheet, this);
- this._sheetsCached = true;
- },
- /**
- * Cache a stylesheet if it falls within the requirements: if it's enabled,
- * and if the @media is allowed. This method also walks through the stylesheet
- * cssRules to find @imported rules, to cache the stylesheets of those rules
- * as well. In addition, the @keyframes rules in the stylesheet are cached.
- *
- * @private
- * @param {CSSStyleSheet} domSheet the CSSStyleSheet object to cache.
- */
- _cacheSheet: function (domSheet) {
- if (domSheet.disabled) {
- return;
- }
- // Only work with stylesheets that have their media allowed.
- if (!this.mediaMatches(domSheet)) {
- return;
- }
- // Cache the sheet.
- let cssSheet = this.getSheet(domSheet, this._sheetIndex++);
- if (cssSheet._passId != this._passId) {
- cssSheet._passId = this._passId;
- // Find import and keyframes rules.
- for (let aDomRule of domSheet.cssRules) {
- if (aDomRule.type == CSSRule.IMPORT_RULE &&
- aDomRule.styleSheet &&
- this.mediaMatches(aDomRule)) {
- this._cacheSheet(aDomRule.styleSheet);
- } else if (aDomRule.type == CSSRule.KEYFRAMES_RULE) {
- this._keyframesRules.push(aDomRule);
- }
- }
- }
- },
- /**
- * Retrieve the list of stylesheets in the document.
- *
- * @return {array} the list of stylesheets in the document.
- */
- get sheets() {
- if (!this._sheetsCached) {
- this._cacheSheets();
- }
- let sheets = [];
- this.forEachSheet(function (sheet) {
- if (sheet.contentSheet) {
- sheets.push(sheet);
- }
- }, this);
- return sheets;
- },
- /**
- * Retrieve the list of keyframes rules in the document.
- *
- * @ return {array} the list of keyframes rules in the document.
- */
- get keyframesRules() {
- if (!this._sheetsCached) {
- this._cacheSheets();
- }
- return this._keyframesRules;
- },
- /**
- * Retrieve a CssSheet object for a given a CSSStyleSheet object. If the
- * stylesheet is already cached, you get the existing CssSheet object,
- * otherwise the new CSSStyleSheet object is cached.
- *
- * @param {CSSStyleSheet} domSheet the CSSStyleSheet object you want.
- * @param {number} index the index, within the document, of the stylesheet.
- *
- * @return {CssSheet} the CssSheet object for the given CSSStyleSheet object.
- */
- getSheet: function (domSheet, index) {
- let cacheId = "";
- if (domSheet.href) {
- cacheId = domSheet.href;
- } else if (domSheet.ownerNode && domSheet.ownerNode.ownerDocument) {
- cacheId = domSheet.ownerNode.ownerDocument.location;
- }
- let sheet = null;
- let sheetFound = false;
- if (cacheId in this._sheets) {
- for (let i = 0, numSheets = this._sheets[cacheId].length;
- i < numSheets;
- i++) {
- sheet = this._sheets[cacheId][i];
- if (sheet.domSheet === domSheet) {
- if (index != -1) {
- sheet.index = index;
- }
- sheetFound = true;
- break;
- }
- }
- }
- if (!sheetFound) {
- if (!(cacheId in this._sheets)) {
- this._sheets[cacheId] = [];
- }
- sheet = new CssSheet(this, domSheet, index);
- if (sheet.sheetAllowed && sheet.contentSheet) {
- this._ruleCount += sheet.ruleCount;
- }
- this._sheets[cacheId].push(sheet);
- }
- return sheet;
- },
- /**
- * Process each cached stylesheet in the document using your callback.
- *
- * @param {function} callback the function you want executed for each of the
- * CssSheet objects cached.
- * @param {object} scope the scope you want for the callback function. scope
- * will be the this object when callback executes.
- */
- forEachSheet: function (callback, scope) {
- for (let cacheId in this._sheets) {
- let sheets = this._sheets[cacheId];
- for (let i = 0; i < sheets.length; i++) {
- // We take this as an opportunity to clean dead sheets
- try {
- let sheet = sheets[i];
- // If accessing domSheet raises an exception, then the style
- // sheet is a dead object.
- sheet.domSheet;
- callback.call(scope, sheet, i, sheets);
- } catch (e) {
- sheets.splice(i, 1);
- i--;
- }
- }
- }
- },
- /**
- /**
- * Get the number nsIDOMCSSRule objects in the document, counted from all of
- * the stylesheets. System sheets are excluded. If a filter is active, this
- * tells only the number of nsIDOMCSSRule objects inside the selected
- * CSSStyleSheet.
- *
- * WARNING: This only provides an estimate of the rule count, and the results
- * could change at a later date. Todo remove this
- *
- * @return {number} the number of nsIDOMCSSRule (all rules).
- */
- get ruleCount() {
- if (!this._sheetsCached) {
- this._cacheSheets();
- }
- return this._ruleCount;
- },
- /**
- * Process the CssSelector objects that match the highlighted element and its
- * parent elements. scope.callback() is executed for each CssSelector
- * object, being passed the CssSelector object and the match status.
- *
- * This method also includes all of the element.style properties, for each
- * highlighted element parent and for the highlighted element itself.
- *
- * Note that the matched selectors are cached, such that next time your
- * callback is invoked for the cached list of CssSelector objects.
- *
- * @param {function} callback the function you want to execute for each of
- * the matched selectors.
- * @param {object} scope the scope you want for the callback function. scope
- * will be the this object when callback executes.
- */
- processMatchedSelectors: function (callback, scope) {
- if (this._matchedSelectors) {
- if (callback) {
- this._passId++;
- this._matchedSelectors.forEach(function (value) {
- callback.call(scope, value[0], value[1]);
- value[0].cssRule._passId = this._passId;
- }, this);
- }
- return;
- }
- if (!this._matchedRules) {
- this._buildMatchedRules();
- }
- this._matchedSelectors = [];
- this._passId++;
- for (let i = 0; i < this._matchedRules.length; i++) {
- let rule = this._matchedRules[i][0];
- let status = this._matchedRules[i][1];
- rule.selectors.forEach(function (selector) {
- if (selector._matchId !== this._matchId &&
- (selector.elementStyle ||
- this.selectorMatchesElement(rule.domRule,
- selector.selectorIndex))) {
- selector._matchId = this._matchId;
- this._matchedSelectors.push([ selector, status ]);
- if (callback) {
- callback.call(scope, selector, status);
- }
- }
- }, this);
- rule._passId = this._passId;
- }
- },
- /**
- * Check if the given selector matches the highlighted element or any of its
- * parents.
- *
- * @private
- * @param {DOMRule} domRule
- * The DOM Rule containing the selector.
- * @param {Number} idx
- * The index of the selector within the DOMRule.
- * @return {boolean}
- * true if the given selector matches the highlighted element or any
- * of its parents, otherwise false is returned.
- */
- selectorMatchesElement: function (domRule, idx) {
- let element = this.viewedElement;
- do {
- if (domUtils.selectorMatchesElement(element, domRule, idx)) {
- return true;
- }
- } while ((element = element.parentNode) &&
- element.nodeType === nodeConstants.ELEMENT_NODE);
- return false;
- },
- /**
- * Check if the highlighted element or it's parents have matched selectors.
- *
- * @param {array} aProperties The list of properties you want to check if they
- * have matched selectors or not.
- * @return {object} An object that tells for each property if it has matched
- * selectors or not. Object keys are property names and values are booleans.
- */
- hasMatchedSelectors: function (properties) {
- if (!this._matchedRules) {
- this._buildMatchedRules();
- }
- let result = {};
- this._matchedRules.some(function (value) {
- let rule = value[0];
- let status = value[1];
- properties = properties.filter((property) => {
- // We just need to find if a rule has this property while it matches
- // the viewedElement (or its parents).
- if (rule.getPropertyValue(property) &&
- (status == STATUS.MATCHED ||
- (status == STATUS.PARENT_MATCH &&
- this._isInherited(property)))) {
- result[property] = true;
- return false;
- }
- // Keep the property for the next rule.
- return true;
- });
- return properties.length == 0;
- }, this);
- return result;
- },
- /**
- * Build the array of matched rules for the currently highlighted element.
- * The array will hold rules that match the viewedElement and its parents.
- *
- * @private
- */
- _buildMatchedRules: function () {
- let domRules;
- let element = this.viewedElement;
- let filter = this.sourceFilter;
- let sheetIndex = 0;
- this._matchId++;
- this._passId++;
- this._matchedRules = [];
- if (!element) {
- return;
- }
- do {
- let status = this.viewedElement === element ?
- STATUS.MATCHED : STATUS.PARENT_MATCH;
- try {
- // Handle finding rules on pseudo by reading style rules
- // on the parent node with proper pseudo arg to getCSSStyleRules.
- let {bindingElement, pseudo} =
- CssLogic.getBindingElementAndPseudo(element);
- domRules = domUtils.getCSSStyleRules(bindingElement, pseudo);
- } catch (ex) {
- console.log("CL__buildMatchedRules error: " + ex);
- continue;
- }
- // getCSSStyleRules can return null with a shadow DOM element.
- let numDomRules = domRules ? domRules.Count() : 0;
- for (let i = 0; i < numDomRules; i++) {
- let domRule = domRules.GetElementAt(i);
- if (domRule.type !== CSSRule.STYLE_RULE) {
- continue;
- }
- let sheet = this.getSheet(domRule.parentStyleSheet, -1);
- if (sheet._passId !== this._passId) {
- sheet.index = sheetIndex++;
- sheet._passId = this._passId;
- }
- if (filter === FILTER.USER && !sheet.contentSheet) {
- continue;
- }
- let rule = sheet.getRule(domRule);
- if (rule._passId === this._passId) {
- continue;
- }
- rule._matchId = this._matchId;
- rule._passId = this._passId;
- this._matchedRules.push([rule, status]);
- }
- // Add element.style information.
- if (element.style && element.style.length > 0) {
- let rule = new CssRule(null, { style: element.style }, element);
- rule._matchId = this._matchId;
- rule._passId = this._passId;
- this._matchedRules.push([rule, status]);
- }
- } while ((element = element.parentNode) &&
- element.nodeType === nodeConstants.ELEMENT_NODE);
- },
- /**
- * Tells if the given DOM CSS object matches the current view media.
- *
- * @param {object} domObject The DOM CSS object to check.
- * @return {boolean} True if the DOM CSS object matches the current view
- * media, or false otherwise.
- */
- mediaMatches: function (domObject) {
- let mediaText = domObject.media.mediaText;
- return !mediaText ||
- this.viewedDocument.defaultView.matchMedia(mediaText).matches;
- },
- };
- /**
- * If the element has an id, return '#id'. Otherwise return 'tagname[n]' where
- * n is the index of this element in its siblings.
- * <p>A technically more 'correct' output from the no-id case might be:
- * 'tagname:nth-of-type(n)' however this is unlikely to be more understood
- * and it is longer.
- *
- * @param {nsIDOMElement} element the element for which you want the short name.
- * @return {string} the string to be displayed for element.
- */
- CssLogic.getShortName = function (element) {
- if (!element) {
- return "null";
- }
- if (element.id) {
- return "#" + element.id;
- }
- let priorSiblings = 0;
- let temp = element;
- while ((temp = temp.previousElementSibling)) {
- priorSiblings++;
- }
- return element.tagName + "[" + priorSiblings + "]";
- };
- /**
- * Get a string list of selectors for a given DOMRule.
- *
- * @param {DOMRule} domRule
- * The DOMRule to parse.
- * @return {Array}
- * An array of string selectors.
- */
- CssLogic.getSelectors = function (domRule) {
- let selectors = [];
- let len = domUtils.getSelectorCount(domRule);
- for (let i = 0; i < len; i++) {
- let text = domUtils.getSelectorText(domRule, i);
- selectors.push(text);
- }
- return selectors;
- };
- /**
- * Given a node, check to see if it is a ::before or ::after element.
- * If so, return the node that is accessible from within the document
- * (the parent of the anonymous node), along with which pseudo element
- * it was. Otherwise, return the node itself.
- *
- * @returns {Object}
- * - {DOMNode} node The non-anonymous node
- * - {string} pseudo One of ':before', ':after', or null.
- */
- CssLogic.getBindingElementAndPseudo = function (node) {
- let bindingElement = node;
- let pseudo = null;
- if (node.nodeName == "_moz_generated_content_before") {
- bindingElement = node.parentNode;
- pseudo = ":before";
- } else if (node.nodeName == "_moz_generated_content_after") {
- bindingElement = node.parentNode;
- pseudo = ":after";
- }
- return {
- bindingElement: bindingElement,
- pseudo: pseudo
- };
- };
- /**
- * Get the computed style on a node. Automatically handles reading
- * computed styles on a ::before/::after element by reading on the
- * parent node with the proper pseudo argument.
- *
- * @param {Node}
- * @returns {CSSStyleDeclaration}
- */
- CssLogic.getComputedStyle = function (node) {
- if (!node ||
- Cu.isDeadWrapper(node) ||
- node.nodeType !== nodeConstants.ELEMENT_NODE ||
- !node.ownerDocument ||
- !node.ownerDocument.defaultView) {
- return null;
- }
- let {bindingElement, pseudo} = CssLogic.getBindingElementAndPseudo(node);
- return node.ownerDocument.defaultView.getComputedStyle(bindingElement,
- pseudo);
- };
- /**
- * Get a source for a stylesheet, taking into account embedded stylesheets
- * for which we need to use document.defaultView.location.href rather than
- * sheet.href
- *
- * @param {CSSStyleSheet} sheet the DOM object for the style sheet.
- * @return {string} the address of the stylesheet.
- */
- CssLogic.href = function (sheet) {
- let href = sheet.href;
- if (!href) {
- href = sheet.ownerNode.ownerDocument.location;
- }
- return href;
- };
- /**
- * Find the position of [element] in [nodeList].
- * @returns an index of the match, or -1 if there is no match
- */
- function positionInNodeList(element, nodeList) {
- for (let i = 0; i < nodeList.length; i++) {
- if (element === nodeList[i]) {
- return i;
- }
- }
- return -1;
- }
- /**
- * Find a unique CSS selector for a given element
- * @returns a string such that ele.ownerDocument.querySelector(reply) === ele
- * and ele.ownerDocument.querySelectorAll(reply).length === 1
- */
- CssLogic.findCssSelector = function (ele) {
- ele = getRootBindingParent(ele);
- let document = ele.ownerDocument;
- if (!document || !document.contains(ele)) {
- throw new Error("findCssSelector received element not inside document");
- }
- // document.querySelectorAll("#id") returns multiple if elements share an ID
- if (ele.id &&
- document.querySelectorAll("#" + CSS.escape(ele.id)).length === 1) {
- return "#" + CSS.escape(ele.id);
- }
- // Inherently unique by tag name
- let tagName = ele.localName;
- if (tagName === "html") {
- return "html";
- }
- if (tagName === "head") {
- return "head";
- }
- if (tagName === "body") {
- return "body";
- }
- // We might be able to find a unique class name
- let selector, index, matches;
- if (ele.classList.length > 0) {
- for (let i = 0; i < ele.classList.length; i++) {
- // Is this className unique by itself?
- selector = "." + CSS.escape(ele.classList.item(i));
- matches = document.querySelectorAll(selector);
- if (matches.length === 1) {
- return selector;
- }
- // Maybe it's unique with a tag name?
- selector = tagName + selector;
- matches = document.querySelectorAll(selector);
- if (matches.length === 1) {
- return selector;
- }
- // Maybe it's unique using a tag name and nth-child
- index = positionInNodeList(ele, ele.parentNode.children) + 1;
- selector = selector + ":nth-child(" + index + ")";
- matches = document.querySelectorAll(selector);
- if (matches.length === 1) {
- return selector;
- }
- }
- }
- // Not unique enough yet. As long as it's not a child of the document,
- // continue recursing up until it is unique enough.
- if (ele.parentNode !== document) {
- index = positionInNodeList(ele, ele.parentNode.children) + 1;
- selector = CssLogic.findCssSelector(ele.parentNode) + " > " +
- tagName + ":nth-child(" + index + ")";
- }
- return selector;
- };
- /**
- * Get the full CSS path for a given element.
- * @returns a string that can be used as a CSS selector for the element. It might not
- * match the element uniquely. It does however, represent the full path from the root
- * node to the element.
- */
- CssLogic.getCssPath = function (ele) {
- ele = getRootBindingParent(ele);
- const document = ele.ownerDocument;
- if (!document || !document.contains(ele)) {
- throw new Error("getCssPath received element not inside document");
- }
- const getElementSelector = element => {
- if (!element.localName) {
- return "";
- }
- let label = element.nodeName == element.nodeName.toUpperCase()
- ? element.localName.toLowerCase()
- : element.localName;
- if (element.id) {
- label += "#" + element.id;
- }
- if (element.classList) {
- for (let cl of element.classList) {
- label += "." + cl;
- }
- }
- return label;
- };
- let paths = [];
- while (ele) {
- if (!ele || ele.nodeType !== Node.ELEMENT_NODE) {
- break;
- }
- paths.splice(0, 0, getElementSelector(ele));
- ele = ele.parentNode;
- }
- return paths.length ? paths.join(" ") : "";
- }
- /**
- * A safe way to access cached bits of information about a stylesheet.
- *
- * @constructor
- * @param {CssLogic} cssLogic pointer to the CssLogic instance working with
- * this CssSheet object.
- * @param {CSSStyleSheet} domSheet reference to a DOM CSSStyleSheet object.
- * @param {number} index tells the index/position of the stylesheet within the
- * main document.
- */
- function CssSheet(cssLogic, domSheet, index) {
- this._cssLogic = cssLogic;
- this.domSheet = domSheet;
- this.index = this.contentSheet ? index : -100 * index;
- // Cache of the sheets href. Cached by the getter.
- this._href = null;
- // Short version of href for use in select boxes etc. Cached by getter.
- this._shortSource = null;
- // null for uncached.
- this._sheetAllowed = null;
- // Cached CssRules from the given stylesheet.
- this._rules = {};
- this._ruleCount = -1;
- }
- CssSheet.prototype = {
- _passId: null,
- _contentSheet: null,
- /**
- * Tells if the stylesheet is provided by the browser or not.
- *
- * @return {boolean} false if this is a browser-provided stylesheet, or true
- * otherwise.
- */
- get contentSheet() {
- if (this._contentSheet === null) {
- this._contentSheet = isContentStylesheet(this.domSheet);
- }
- return this._contentSheet;
- },
- /**
- * Tells if the stylesheet is disabled or not.
- * @return {boolean} true if this stylesheet is disabled, or false otherwise.
- */
- get disabled() {
- return this.domSheet.disabled;
- },
- /**
- * Get a source for a stylesheet, using CssLogic.href
- *
- * @return {string} the address of the stylesheet.
- */
- get href() {
- if (this._href) {
- return this._href;
- }
- this._href = CssLogic.href(this.domSheet);
- return this._href;
- },
- /**
- * Create a shorthand version of the href of a stylesheet.
- *
- * @return {string} the shorthand source of the stylesheet.
- */
- get shortSource() {
- if (this._shortSource) {
- return this._shortSource;
- }
- this._shortSource = shortSource(this.domSheet);
- return this._shortSource;
- },
- /**
- * Tells if the sheet is allowed or not by the current CssLogic.sourceFilter.
- *
- * @return {boolean} true if the stylesheet is allowed by the sourceFilter, or
- * false otherwise.
- */
- get sheetAllowed() {
- if (this._sheetAllowed !== null) {
- return this._sheetAllowed;
- }
- this._sheetAllowed = true;
- let filter = this._cssLogic.sourceFilter;
- if (filter === FILTER.USER && !this.contentSheet) {
- this._sheetAllowed = false;
- }
- if (filter !== FILTER.USER && filter !== FILTER.UA) {
- this._sheetAllowed = (filter === this.href);
- }
- return this._sheetAllowed;
- },
- /**
- * Retrieve the number of rules in this stylesheet.
- *
- * @return {number} the number of nsIDOMCSSRule objects in this stylesheet.
- */
- get ruleCount() {
- return this._ruleCount > -1 ?
- this._ruleCount :
- this.domSheet.cssRules.length;
- },
- /**
- * Retrieve a CssRule object for the given CSSStyleRule. The CssRule object is
- * cached, such that subsequent retrievals return the same CssRule object for
- * the same CSSStyleRule object.
- *
- * @param {CSSStyleRule} aDomRule the CSSStyleRule object for which you want a
- * CssRule object.
- * @return {CssRule} the cached CssRule object for the given CSSStyleRule
- * object.
- */
- getRule: function (domRule) {
- let cacheId = domRule.type + domRule.selectorText;
- let rule = null;
- let ruleFound = false;
- if (cacheId in this._rules) {
- for (let i = 0, rulesLen = this._rules[cacheId].length;
- i < rulesLen;
- i++) {
- rule = this._rules[cacheId][i];
- if (rule.domRule === domRule) {
- ruleFound = true;
- break;
- }
- }
- }
- if (!ruleFound) {
- if (!(cacheId in this._rules)) {
- this._rules[cacheId] = [];
- }
- rule = new CssRule(this, domRule);
- this._rules[cacheId].push(rule);
- }
- return rule;
- },
- toString: function () {
- return "CssSheet[" + this.shortSource + "]";
- }
- };
- /**
- * Information about a single CSSStyleRule.
- *
- * @param {CSSSheet|null} cssSheet the CssSheet object of the stylesheet that
- * holds the CSSStyleRule. If the rule comes from element.style, set this
- * argument to null.
- * @param {CSSStyleRule|object} domRule the DOM CSSStyleRule for which you want
- * to cache data. If the rule comes from element.style, then provide
- * an object of the form: {style: element.style}.
- * @param {Element} [element] If the rule comes from element.style, then this
- * argument must point to the element.
- * @constructor
- */
- function CssRule(cssSheet, domRule, element) {
- this._cssSheet = cssSheet;
- this.domRule = domRule;
- let parentRule = domRule.parentRule;
- if (parentRule && parentRule.type == CSSRule.MEDIA_RULE) {
- this.mediaText = parentRule.media.mediaText;
- }
- if (this._cssSheet) {
- // parse domRule.selectorText on call to this.selectors
- this._selectors = null;
- this.line = domUtils.getRuleLine(this.domRule);
- this.source = this._cssSheet.shortSource + ":" + this.line;
- if (this.mediaText) {
- this.source += " @media " + this.mediaText;
- }
- this.href = this._cssSheet.href;
- this.contentRule = this._cssSheet.contentSheet;
- } else if (element) {
- this._selectors = [ new CssSelector(this, "@element.style", 0) ];
- this.line = -1;
- this.source = l10n("rule.sourceElement");
- this.href = "#";
- this.contentRule = true;
- this.sourceElement = element;
- }
- }
- CssRule.prototype = {
- _passId: null,
- mediaText: "",
- get isMediaRule() {
- return !!this.mediaText;
- },
- /**
- * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter.
- *
- * @return {boolean} true if the parent stylesheet is allowed by the current
- * sourceFilter, or false otherwise.
- */
- get sheetAllowed() {
- return this._cssSheet ? this._cssSheet.sheetAllowed : true;
- },
- /**
- * Retrieve the parent stylesheet index/position in the viewed document.
- *
- * @return {number} the parent stylesheet index/position in the viewed
- * document.
- */
- get sheetIndex() {
- return this._cssSheet ? this._cssSheet.index : 0;
- },
- /**
- * Retrieve the style property value from the current CSSStyleRule.
- *
- * @param {string} property the CSS property name for which you want the
- * value.
- * @return {string} the property value.
- */
- getPropertyValue: function (property) {
- return this.domRule.style.getPropertyValue(property);
- },
- /**
- * Retrieve the style property priority from the current CSSStyleRule.
- *
- * @param {string} property the CSS property name for which you want the
- * priority.
- * @return {string} the property priority.
- */
- getPropertyPriority: function (property) {
- return this.domRule.style.getPropertyPriority(property);
- },
- /**
- * Retrieve the list of CssSelector objects for each of the parsed selectors
- * of the current CSSStyleRule.
- *
- * @return {array} the array hold the CssSelector objects.
- */
- get selectors() {
- if (this._selectors) {
- return this._selectors;
- }
- // Parse the CSSStyleRule.selectorText string.
- this._selectors = [];
- if (!this.domRule.selectorText) {
- return this._selectors;
- }
- let selectors = CssLogic.getSelectors(this.domRule);
- for (let i = 0, len = selectors.length; i < len; i++) {
- this._selectors.push(new CssSelector(this, selectors[i], i));
- }
- return this._selectors;
- },
- toString: function () {
- return "[CssRule " + this.domRule.selectorText + "]";
- },
- };
- /**
- * The CSS selector class allows us to document the ranking of various CSS
- * selectors.
- *
- * @constructor
- * @param {CssRule} cssRule the CssRule instance from where the selector comes.
- * @param {string} selector The selector that we wish to investigate.
- * @param {Number} index The index of the selector within it's rule.
- */
- function CssSelector(cssRule, selector, index) {
- this.cssRule = cssRule;
- this.text = selector;
- this.elementStyle = this.text == "@element.style";
- this._specificity = null;
- this.selectorIndex = index;
- }
- exports.CssSelector = CssSelector;
- CssSelector.prototype = {
- _matchId: null,
- /**
- * Retrieve the CssSelector source, which is the source of the CssSheet owning
- * the selector.
- *
- * @return {string} the selector source.
- */
- get source() {
- return this.cssRule.source;
- },
- /**
- * Retrieve the CssSelector source element, which is the source of the CssRule
- * owning the selector. This is only available when the CssSelector comes from
- * an element.style.
- *
- * @return {string} the source element selector.
- */
- get sourceElement() {
- return this.cssRule.sourceElement;
- },
- /**
- * Retrieve the address of the CssSelector. This points to the address of the
- * CssSheet owning this selector.
- *
- * @return {string} the address of the CssSelector.
- */
- get href() {
- return this.cssRule.href;
- },
- /**
- * Check if the selector comes from a browser-provided stylesheet.
- *
- * @return {boolean} true if the selector comes from a content-provided
- * stylesheet, or false otherwise.
- */
- get contentRule() {
- return this.cssRule.contentRule;
- },
- /**
- * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter.
- *
- * @return {boolean} true if the parent stylesheet is allowed by the current
- * sourceFilter, or false otherwise.
- */
- get sheetAllowed() {
- return this.cssRule.sheetAllowed;
- },
- /**
- * Retrieve the parent stylesheet index/position in the viewed document.
- *
- * @return {number} the parent stylesheet index/position in the viewed
- * document.
- */
- get sheetIndex() {
- return this.cssRule.sheetIndex;
- },
- /**
- * Retrieve the line of the parent CSSStyleRule in the parent CSSStyleSheet.
- *
- * @return {number} the line of the parent CSSStyleRule in the parent
- * stylesheet.
- */
- get ruleLine() {
- return this.cssRule.line;
- },
- /**
- * Retrieve specificity information for the current selector.
- *
- * @see http://www.w3.org/TR/css3-selectors/#specificity
- * @see http://www.w3.org/TR/CSS2/selector.html
- *
- * @return {Number} The selector's specificity.
- */
- get specificity() {
- if (this.elementStyle) {
- // We can't ask specificity from DOMUtils as element styles don't provide
- // CSSStyleRule interface DOMUtils expect. However, specificity of element
- // style is constant, 1,0,0,0 or 0x01000000, just return the constant
- // directly. @see http://www.w3.org/TR/CSS2/cascade.html#specificity
- return 0x01000000;
- }
- if (this._specificity) {
- return this._specificity;
- }
- this._specificity = domUtils.getSpecificity(this.cssRule.domRule,
- this.selectorIndex);
- return this._specificity;
- },
- toString: function () {
- return this.text;
- },
- };
- /**
- * A cache of information about the matched rules, selectors and values attached
- * to a CSS property, for the highlighted element.
- *
- * The heart of the CssPropertyInfo object is the _findMatchedSelectors()
- * method. This are invoked when the PropertyView tries to access the
- * .matchedSelectors array.
- * Results are cached, for later reuse.
- *
- * @param {CssLogic} cssLogic Reference to the parent CssLogic instance
- * @param {string} property The CSS property we are gathering information for
- * @param {function} isInherited A function that determines if the CSS property
- * is inherited.
- * @constructor
- */
- function CssPropertyInfo(cssLogic, property, isInherited) {
- this._cssLogic = cssLogic;
- this.property = property;
- this._value = "";
- this._isInherited = isInherited;
- // An array holding CssSelectorInfo objects for each of the matched selectors
- // that are inside a CSS rule. Only rules that hold the this.property are
- // counted. This includes rules that come from filtered stylesheets (those
- // that have sheetAllowed = false).
- this._matchedSelectors = null;
- }
- CssPropertyInfo.prototype = {
- /**
- * Retrieve the computed style value for the current property, for the
- * highlighted element.
- *
- * @return {string} the computed style value for the current property, for the
- * highlighted element.
- */
- get value() {
- if (!this._value && this._cssLogic.computedStyle) {
- try {
- this._value =
- this._cssLogic.computedStyle.getPropertyValue(this.property);
- } catch (ex) {
- console.log("Error reading computed style for " + this.property);
- console.log(ex);
- }
- }
- return this._value;
- },
- /**
- * Retrieve the array holding CssSelectorInfo objects for each of the matched
- * selectors, from each of the matched rules. Only selectors coming from
- * allowed stylesheets are included in the array.
- *
- * @return {array} the list of CssSelectorInfo objects of selectors that match
- * the highlighted element and its parents.
- */
- get matchedSelectors() {
- if (!this._matchedSelectors) {
- this._findMatchedSelectors();
- } else if (this.needRefilter) {
- this._refilterSelectors();
- }
- return this._matchedSelectors;
- },
- /**
- * Find the selectors that match the highlighted element and its parents.
- * Uses CssLogic.processMatchedSelectors() to find the matched selectors,
- * passing in a reference to CssPropertyInfo._processMatchedSelector() to
- * create CssSelectorInfo objects, which we then sort
- * @private
- */
- _findMatchedSelectors: function () {
- this._matchedSelectors = [];
- this.needRefilter = false;
- this._cssLogic.processMatchedSelectors(this._processMatchedSelector, this);
- // Sort the selectors by how well they match the given element.
- this._matchedSelectors.sort(function (selectorInfo1, selectorInfo2) {
- if (selectorInfo1.status > selectorInfo2.status) {
- return -1;
- } else if (selectorInfo2.status > selectorInfo1.status) {
- return 1;
- }
- return selectorInfo1.compareTo(selectorInfo2);
- });
- // Now we know which of the matches is best, we can mark it BEST_MATCH.
- if (this._matchedSelectors.length > 0 &&
- this._matchedSelectors[0].status > STATUS.UNMATCHED) {
- this._matchedSelectors[0].status = STATUS.BEST;
- }
- },
- /**
- * Process a matched CssSelector object.
- *
- * @private
- * @param {CssSelector} selector the matched CssSelector object.
- * @param {STATUS} status the CssSelector match status.
- */
- _processMatchedSelector: function (selector, status) {
- let cssRule = selector.cssRule;
- let value = cssRule.getPropertyValue(this.property);
- if (value &&
- (status == STATUS.MATCHED ||
- (status == STATUS.PARENT_MATCH &&
- this._isInherited(this.property)))) {
- let selectorInfo = new CssSelectorInfo(selector, this.property, value,
- status);
- this._matchedSelectors.push(selectorInfo);
- }
- },
- /**
- * Refilter the matched selectors array when the CssLogic.sourceFilter
- * changes. This allows for quick filter changes.
- * @private
- */
- _refilterSelectors: function () {
- let passId = ++this._cssLogic._passId;
- let iterator = function (selectorInfo) {
- let cssRule = selectorInfo.selector.cssRule;
- if (cssRule._passId != passId) {
- cssRule._passId = passId;
- }
- };
- if (this._matchedSelectors) {
- this._matchedSelectors.forEach(iterator);
- }
- this.needRefilter = false;
- },
- toString: function () {
- return "CssPropertyInfo[" + this.property + "]";
- },
- };
- /**
- * A class that holds information about a given CssSelector object.
- *
- * Instances of this class are given to CssHtmlTree in the array of matched
- * selectors. Each such object represents a displayable row in the PropertyView
- * objects. The information given by this object blends data coming from the
- * CssSheet, CssRule and from the CssSelector that own this object.
- *
- * @param {CssSelector} selector The CssSelector object for which to
- * present information.
- * @param {string} property The property for which information should
- * be retrieved.
- * @param {string} value The property value from the CssRule that owns
- * the selector.
- * @param {STATUS} status The selector match status.
- * @constructor
- */
- function CssSelectorInfo(selector, property, value, status) {
- this.selector = selector;
- this.property = property;
- this.status = status;
- this.value = value;
- let priority = this.selector.cssRule.getPropertyPriority(this.property);
- this.important = (priority === "important");
- }
- CssSelectorInfo.prototype = {
- /**
- * Retrieve the CssSelector source, which is the source of the CssSheet owning
- * the selector.
- *
- * @return {string} the selector source.
- */
- get source() {
- return this.selector.source;
- },
- /**
- * Retrieve the CssSelector source element, which is the source of the CssRule
- * owning the selector. This is only available when the CssSelector comes from
- * an element.style.
- *
- * @return {string} the source element selector.
- */
- get sourceElement() {
- return this.selector.sourceElement;
- },
- /**
- * Retrieve the address of the CssSelector. This points to the address of the
- * CssSheet owning this selector.
- *
- * @return {string} the address of the CssSelector.
- */
- get href() {
- return this.selector.href;
- },
- /**
- * Check if the CssSelector comes from element.style or not.
- *
- * @return {boolean} true if the CssSelector comes from element.style, or
- * false otherwise.
- */
- get elementStyle() {
- return this.selector.elementStyle;
- },
- /**
- * Retrieve specificity information for the current selector.
- *
- * @return {object} an object holding specificity information for the current
- * selector.
- */
- get specificity() {
- return this.selector.specificity;
- },
- /**
- * Retrieve the parent stylesheet index/position in the viewed document.
- *
- * @return {number} the parent stylesheet index/position in the viewed
- * document.
- */
- get sheetIndex() {
- return this.selector.sheetIndex;
- },
- /**
- * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter.
- *
- * @return {boolean} true if the parent stylesheet is allowed by the current
- * sourceFilter, or false otherwise.
- */
- get sheetAllowed() {
- return this.selector.sheetAllowed;
- },
- /**
- * Retrieve the line of the parent CSSStyleRule in the parent CSSStyleSheet.
- *
- * @return {number} the line of the parent CSSStyleRule in the parent
- * stylesheet.
- */
- get ruleLine() {
- return this.selector.ruleLine;
- },
- /**
- * Check if the selector comes from a browser-provided stylesheet.
- *
- * @return {boolean} true if the selector comes from a browser-provided
- * stylesheet, or false otherwise.
- */
- get contentRule() {
- return this.selector.contentRule;
- },
- /**
- * Compare the current CssSelectorInfo instance to another instance, based on
- * specificity information.
- *
- * @param {CssSelectorInfo} that The instance to compare ourselves against.
- * @return number -1, 0, 1 depending on how that compares with this.
- */
- compareTo: function (that) {
- if (!this.contentRule && that.contentRule) {
- return 1;
- }
- if (this.contentRule && !that.contentRule) {
- return -1;
- }
- if (this.elementStyle && !that.elementStyle) {
- if (!this.important && that.important) {
- return 1;
- }
- return -1;
- }
- if (!this.elementStyle && that.elementStyle) {
- if (this.important && !that.important) {
- return -1;
- }
- return 1;
- }
- if (this.important && !that.important) {
- return -1;
- }
- if (that.important && !this.important) {
- return 1;
- }
- if (this.specificity > that.specificity) {
- return -1;
- }
- if (that.specificity > this.specificity) {
- return 1;
- }
- if (this.sheetIndex > that.sheetIndex) {
- return -1;
- }
- if (that.sheetIndex > this.sheetIndex) {
- return 1;
- }
- if (this.ruleLine > that.ruleLine) {
- return -1;
- }
- if (that.ruleLine > this.ruleLine) {
- return 1;
- }
- return 0;
- },
- toString: function () {
- return this.selector + " -> " + this.value;
- },
- };
- DevToolsUtils.defineLazyGetter(this, "domUtils", function () {
- return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
- });
|