css-logic.js 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585
  1. /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. /*
  6. * About the objects defined in this file:
  7. * - CssLogic contains style information about a view context. It provides
  8. * access to 2 sets of objects: Css[Sheet|Rule|Selector] provide access to
  9. * information that does not change when the selected element changes while
  10. * Css[Property|Selector]Info provide information that is dependent on the
  11. * selected element.
  12. * Its key methods are highlight(), getPropertyInfo() and forEachSheet(), etc
  13. *
  14. * - CssSheet provides a more useful API to a DOM CSSSheet for our purposes,
  15. * including shortSource and href.
  16. * - CssRule a more useful API to a nsIDOMCSSRule including access to the group
  17. * of CssSelectors that the rule provides properties for
  18. * - CssSelector A single selector - i.e. not a selector group. In other words
  19. * a CssSelector does not contain ','. This terminology is different from the
  20. * standard DOM API, but more inline with the definition in the spec.
  21. *
  22. * - CssPropertyInfo contains style information for a single property for the
  23. * highlighted element.
  24. * - CssSelectorInfo is a wrapper around CssSelector, which adds sorting with
  25. * reference to the selected element.
  26. */
  27. "use strict";
  28. const { Cc, Ci, Cu } = require("chrome");
  29. const DevToolsUtils = require("devtools/shared/DevToolsUtils");
  30. const { getRootBindingParent } = require("devtools/shared/layout/utils");
  31. const nodeConstants = require("devtools/shared/dom-node-constants");
  32. const {l10n, isContentStylesheet, shortSource, FILTER, STATUS} = require("devtools/shared/inspector/css-logic");
  33. loader.lazyRequireGetter(this, "CSSLexer", "devtools/shared/css/lexer");
  34. /**
  35. * @param {function} isInherited A function that determines if the CSS property
  36. * is inherited.
  37. */
  38. function CssLogic(isInherited) {
  39. // The cache of examined CSS properties.
  40. this._isInherited = isInherited;
  41. this._propertyInfos = {};
  42. }
  43. exports.CssLogic = CssLogic;
  44. CssLogic.prototype = {
  45. // Both setup by highlight().
  46. viewedElement: null,
  47. viewedDocument: null,
  48. // The cache of the known sheets.
  49. _sheets: null,
  50. // Have the sheets been cached?
  51. _sheetsCached: false,
  52. // The total number of rules, in all stylesheets, after filtering.
  53. _ruleCount: 0,
  54. // The computed styles for the viewedElement.
  55. _computedStyle: null,
  56. // Source filter. Only display properties coming from the given source
  57. _sourceFilter: FILTER.USER,
  58. // Used for tracking unique CssSheet/CssRule/CssSelector objects, in a run of
  59. // processMatchedSelectors().
  60. _passId: 0,
  61. // Used for tracking matched CssSelector objects.
  62. _matchId: 0,
  63. _matchedRules: null,
  64. _matchedSelectors: null,
  65. // Cached keyframes rules in all stylesheets
  66. _keyframesRules: null,
  67. /**
  68. * Reset various properties
  69. */
  70. reset: function () {
  71. this._propertyInfos = {};
  72. this._ruleCount = 0;
  73. this._sheetIndex = 0;
  74. this._sheets = {};
  75. this._sheetsCached = false;
  76. this._matchedRules = null;
  77. this._matchedSelectors = null;
  78. this._keyframesRules = [];
  79. },
  80. /**
  81. * Focus on a new element - remove the style caches.
  82. *
  83. * @param {nsIDOMElement} aViewedElement the element the user has highlighted
  84. * in the Inspector.
  85. */
  86. highlight: function (viewedElement) {
  87. if (!viewedElement) {
  88. this.viewedElement = null;
  89. this.viewedDocument = null;
  90. this._computedStyle = null;
  91. this.reset();
  92. return;
  93. }
  94. if (viewedElement === this.viewedElement) {
  95. return;
  96. }
  97. this.viewedElement = viewedElement;
  98. let doc = this.viewedElement.ownerDocument;
  99. if (doc != this.viewedDocument) {
  100. // New document: clear/rebuild the cache.
  101. this.viewedDocument = doc;
  102. // Hunt down top level stylesheets, and cache them.
  103. this._cacheSheets();
  104. } else {
  105. // Clear cached data in the CssPropertyInfo objects.
  106. this._propertyInfos = {};
  107. }
  108. this._matchedRules = null;
  109. this._matchedSelectors = null;
  110. this._computedStyle = CssLogic.getComputedStyle(this.viewedElement);
  111. },
  112. /**
  113. * Get the values of all the computed CSS properties for the highlighted
  114. * element.
  115. * @returns {object} The computed CSS properties for a selected element
  116. */
  117. get computedStyle() {
  118. return this._computedStyle;
  119. },
  120. /**
  121. * Get the source filter.
  122. * @returns {string} The source filter being used.
  123. */
  124. get sourceFilter() {
  125. return this._sourceFilter;
  126. },
  127. /**
  128. * Source filter. Only display properties coming from the given source (web
  129. * address). Note that in order to avoid information overload we DO NOT show
  130. * unmatched system rules.
  131. * @see FILTER.*
  132. */
  133. set sourceFilter(value) {
  134. let oldValue = this._sourceFilter;
  135. this._sourceFilter = value;
  136. let ruleCount = 0;
  137. // Update the CssSheet objects.
  138. this.forEachSheet(function (sheet) {
  139. sheet._sheetAllowed = -1;
  140. if (sheet.contentSheet && sheet.sheetAllowed) {
  141. ruleCount += sheet.ruleCount;
  142. }
  143. }, this);
  144. this._ruleCount = ruleCount;
  145. // Full update is needed because the this.processMatchedSelectors() method
  146. // skips UA stylesheets if the filter does not allow such sheets.
  147. let needFullUpdate = (oldValue == FILTER.UA || value == FILTER.UA);
  148. if (needFullUpdate) {
  149. this._matchedRules = null;
  150. this._matchedSelectors = null;
  151. this._propertyInfos = {};
  152. } else {
  153. // Update the CssPropertyInfo objects.
  154. for (let property in this._propertyInfos) {
  155. this._propertyInfos[property].needRefilter = true;
  156. }
  157. }
  158. },
  159. /**
  160. * Return a CssPropertyInfo data structure for the currently viewed element
  161. * and the specified CSS property. If there is no currently viewed element we
  162. * return an empty object.
  163. *
  164. * @param {string} property The CSS property to look for.
  165. * @return {CssPropertyInfo} a CssPropertyInfo structure for the given
  166. * property.
  167. */
  168. getPropertyInfo: function (property) {
  169. if (!this.viewedElement) {
  170. return {};
  171. }
  172. let info = this._propertyInfos[property];
  173. if (!info) {
  174. info = new CssPropertyInfo(this, property, this._isInherited);
  175. this._propertyInfos[property] = info;
  176. }
  177. return info;
  178. },
  179. /**
  180. * Cache all the stylesheets in the inspected document
  181. * @private
  182. */
  183. _cacheSheets: function () {
  184. this._passId++;
  185. this.reset();
  186. // styleSheets isn't an array, but forEach can work on it anyway
  187. Array.prototype.forEach.call(this.viewedDocument.styleSheets,
  188. this._cacheSheet, this);
  189. this._sheetsCached = true;
  190. },
  191. /**
  192. * Cache a stylesheet if it falls within the requirements: if it's enabled,
  193. * and if the @media is allowed. This method also walks through the stylesheet
  194. * cssRules to find @imported rules, to cache the stylesheets of those rules
  195. * as well. In addition, the @keyframes rules in the stylesheet are cached.
  196. *
  197. * @private
  198. * @param {CSSStyleSheet} domSheet the CSSStyleSheet object to cache.
  199. */
  200. _cacheSheet: function (domSheet) {
  201. if (domSheet.disabled) {
  202. return;
  203. }
  204. // Only work with stylesheets that have their media allowed.
  205. if (!this.mediaMatches(domSheet)) {
  206. return;
  207. }
  208. // Cache the sheet.
  209. let cssSheet = this.getSheet(domSheet, this._sheetIndex++);
  210. if (cssSheet._passId != this._passId) {
  211. cssSheet._passId = this._passId;
  212. // Find import and keyframes rules.
  213. for (let aDomRule of domSheet.cssRules) {
  214. if (aDomRule.type == CSSRule.IMPORT_RULE &&
  215. aDomRule.styleSheet &&
  216. this.mediaMatches(aDomRule)) {
  217. this._cacheSheet(aDomRule.styleSheet);
  218. } else if (aDomRule.type == CSSRule.KEYFRAMES_RULE) {
  219. this._keyframesRules.push(aDomRule);
  220. }
  221. }
  222. }
  223. },
  224. /**
  225. * Retrieve the list of stylesheets in the document.
  226. *
  227. * @return {array} the list of stylesheets in the document.
  228. */
  229. get sheets() {
  230. if (!this._sheetsCached) {
  231. this._cacheSheets();
  232. }
  233. let sheets = [];
  234. this.forEachSheet(function (sheet) {
  235. if (sheet.contentSheet) {
  236. sheets.push(sheet);
  237. }
  238. }, this);
  239. return sheets;
  240. },
  241. /**
  242. * Retrieve the list of keyframes rules in the document.
  243. *
  244. * @ return {array} the list of keyframes rules in the document.
  245. */
  246. get keyframesRules() {
  247. if (!this._sheetsCached) {
  248. this._cacheSheets();
  249. }
  250. return this._keyframesRules;
  251. },
  252. /**
  253. * Retrieve a CssSheet object for a given a CSSStyleSheet object. If the
  254. * stylesheet is already cached, you get the existing CssSheet object,
  255. * otherwise the new CSSStyleSheet object is cached.
  256. *
  257. * @param {CSSStyleSheet} domSheet the CSSStyleSheet object you want.
  258. * @param {number} index the index, within the document, of the stylesheet.
  259. *
  260. * @return {CssSheet} the CssSheet object for the given CSSStyleSheet object.
  261. */
  262. getSheet: function (domSheet, index) {
  263. let cacheId = "";
  264. if (domSheet.href) {
  265. cacheId = domSheet.href;
  266. } else if (domSheet.ownerNode && domSheet.ownerNode.ownerDocument) {
  267. cacheId = domSheet.ownerNode.ownerDocument.location;
  268. }
  269. let sheet = null;
  270. let sheetFound = false;
  271. if (cacheId in this._sheets) {
  272. for (let i = 0, numSheets = this._sheets[cacheId].length;
  273. i < numSheets;
  274. i++) {
  275. sheet = this._sheets[cacheId][i];
  276. if (sheet.domSheet === domSheet) {
  277. if (index != -1) {
  278. sheet.index = index;
  279. }
  280. sheetFound = true;
  281. break;
  282. }
  283. }
  284. }
  285. if (!sheetFound) {
  286. if (!(cacheId in this._sheets)) {
  287. this._sheets[cacheId] = [];
  288. }
  289. sheet = new CssSheet(this, domSheet, index);
  290. if (sheet.sheetAllowed && sheet.contentSheet) {
  291. this._ruleCount += sheet.ruleCount;
  292. }
  293. this._sheets[cacheId].push(sheet);
  294. }
  295. return sheet;
  296. },
  297. /**
  298. * Process each cached stylesheet in the document using your callback.
  299. *
  300. * @param {function} callback the function you want executed for each of the
  301. * CssSheet objects cached.
  302. * @param {object} scope the scope you want for the callback function. scope
  303. * will be the this object when callback executes.
  304. */
  305. forEachSheet: function (callback, scope) {
  306. for (let cacheId in this._sheets) {
  307. let sheets = this._sheets[cacheId];
  308. for (let i = 0; i < sheets.length; i++) {
  309. // We take this as an opportunity to clean dead sheets
  310. try {
  311. let sheet = sheets[i];
  312. // If accessing domSheet raises an exception, then the style
  313. // sheet is a dead object.
  314. sheet.domSheet;
  315. callback.call(scope, sheet, i, sheets);
  316. } catch (e) {
  317. sheets.splice(i, 1);
  318. i--;
  319. }
  320. }
  321. }
  322. },
  323. /**
  324. /**
  325. * Get the number nsIDOMCSSRule objects in the document, counted from all of
  326. * the stylesheets. System sheets are excluded. If a filter is active, this
  327. * tells only the number of nsIDOMCSSRule objects inside the selected
  328. * CSSStyleSheet.
  329. *
  330. * WARNING: This only provides an estimate of the rule count, and the results
  331. * could change at a later date. Todo remove this
  332. *
  333. * @return {number} the number of nsIDOMCSSRule (all rules).
  334. */
  335. get ruleCount() {
  336. if (!this._sheetsCached) {
  337. this._cacheSheets();
  338. }
  339. return this._ruleCount;
  340. },
  341. /**
  342. * Process the CssSelector objects that match the highlighted element and its
  343. * parent elements. scope.callback() is executed for each CssSelector
  344. * object, being passed the CssSelector object and the match status.
  345. *
  346. * This method also includes all of the element.style properties, for each
  347. * highlighted element parent and for the highlighted element itself.
  348. *
  349. * Note that the matched selectors are cached, such that next time your
  350. * callback is invoked for the cached list of CssSelector objects.
  351. *
  352. * @param {function} callback the function you want to execute for each of
  353. * the matched selectors.
  354. * @param {object} scope the scope you want for the callback function. scope
  355. * will be the this object when callback executes.
  356. */
  357. processMatchedSelectors: function (callback, scope) {
  358. if (this._matchedSelectors) {
  359. if (callback) {
  360. this._passId++;
  361. this._matchedSelectors.forEach(function (value) {
  362. callback.call(scope, value[0], value[1]);
  363. value[0].cssRule._passId = this._passId;
  364. }, this);
  365. }
  366. return;
  367. }
  368. if (!this._matchedRules) {
  369. this._buildMatchedRules();
  370. }
  371. this._matchedSelectors = [];
  372. this._passId++;
  373. for (let i = 0; i < this._matchedRules.length; i++) {
  374. let rule = this._matchedRules[i][0];
  375. let status = this._matchedRules[i][1];
  376. rule.selectors.forEach(function (selector) {
  377. if (selector._matchId !== this._matchId &&
  378. (selector.elementStyle ||
  379. this.selectorMatchesElement(rule.domRule,
  380. selector.selectorIndex))) {
  381. selector._matchId = this._matchId;
  382. this._matchedSelectors.push([ selector, status ]);
  383. if (callback) {
  384. callback.call(scope, selector, status);
  385. }
  386. }
  387. }, this);
  388. rule._passId = this._passId;
  389. }
  390. },
  391. /**
  392. * Check if the given selector matches the highlighted element or any of its
  393. * parents.
  394. *
  395. * @private
  396. * @param {DOMRule} domRule
  397. * The DOM Rule containing the selector.
  398. * @param {Number} idx
  399. * The index of the selector within the DOMRule.
  400. * @return {boolean}
  401. * true if the given selector matches the highlighted element or any
  402. * of its parents, otherwise false is returned.
  403. */
  404. selectorMatchesElement: function (domRule, idx) {
  405. let element = this.viewedElement;
  406. do {
  407. if (domUtils.selectorMatchesElement(element, domRule, idx)) {
  408. return true;
  409. }
  410. } while ((element = element.parentNode) &&
  411. element.nodeType === nodeConstants.ELEMENT_NODE);
  412. return false;
  413. },
  414. /**
  415. * Check if the highlighted element or it's parents have matched selectors.
  416. *
  417. * @param {array} aProperties The list of properties you want to check if they
  418. * have matched selectors or not.
  419. * @return {object} An object that tells for each property if it has matched
  420. * selectors or not. Object keys are property names and values are booleans.
  421. */
  422. hasMatchedSelectors: function (properties) {
  423. if (!this._matchedRules) {
  424. this._buildMatchedRules();
  425. }
  426. let result = {};
  427. this._matchedRules.some(function (value) {
  428. let rule = value[0];
  429. let status = value[1];
  430. properties = properties.filter((property) => {
  431. // We just need to find if a rule has this property while it matches
  432. // the viewedElement (or its parents).
  433. if (rule.getPropertyValue(property) &&
  434. (status == STATUS.MATCHED ||
  435. (status == STATUS.PARENT_MATCH &&
  436. this._isInherited(property)))) {
  437. result[property] = true;
  438. return false;
  439. }
  440. // Keep the property for the next rule.
  441. return true;
  442. });
  443. return properties.length == 0;
  444. }, this);
  445. return result;
  446. },
  447. /**
  448. * Build the array of matched rules for the currently highlighted element.
  449. * The array will hold rules that match the viewedElement and its parents.
  450. *
  451. * @private
  452. */
  453. _buildMatchedRules: function () {
  454. let domRules;
  455. let element = this.viewedElement;
  456. let filter = this.sourceFilter;
  457. let sheetIndex = 0;
  458. this._matchId++;
  459. this._passId++;
  460. this._matchedRules = [];
  461. if (!element) {
  462. return;
  463. }
  464. do {
  465. let status = this.viewedElement === element ?
  466. STATUS.MATCHED : STATUS.PARENT_MATCH;
  467. try {
  468. // Handle finding rules on pseudo by reading style rules
  469. // on the parent node with proper pseudo arg to getCSSStyleRules.
  470. let {bindingElement, pseudo} =
  471. CssLogic.getBindingElementAndPseudo(element);
  472. domRules = domUtils.getCSSStyleRules(bindingElement, pseudo);
  473. } catch (ex) {
  474. console.log("CL__buildMatchedRules error: " + ex);
  475. continue;
  476. }
  477. // getCSSStyleRules can return null with a shadow DOM element.
  478. let numDomRules = domRules ? domRules.Count() : 0;
  479. for (let i = 0; i < numDomRules; i++) {
  480. let domRule = domRules.GetElementAt(i);
  481. if (domRule.type !== CSSRule.STYLE_RULE) {
  482. continue;
  483. }
  484. let sheet = this.getSheet(domRule.parentStyleSheet, -1);
  485. if (sheet._passId !== this._passId) {
  486. sheet.index = sheetIndex++;
  487. sheet._passId = this._passId;
  488. }
  489. if (filter === FILTER.USER && !sheet.contentSheet) {
  490. continue;
  491. }
  492. let rule = sheet.getRule(domRule);
  493. if (rule._passId === this._passId) {
  494. continue;
  495. }
  496. rule._matchId = this._matchId;
  497. rule._passId = this._passId;
  498. this._matchedRules.push([rule, status]);
  499. }
  500. // Add element.style information.
  501. if (element.style && element.style.length > 0) {
  502. let rule = new CssRule(null, { style: element.style }, element);
  503. rule._matchId = this._matchId;
  504. rule._passId = this._passId;
  505. this._matchedRules.push([rule, status]);
  506. }
  507. } while ((element = element.parentNode) &&
  508. element.nodeType === nodeConstants.ELEMENT_NODE);
  509. },
  510. /**
  511. * Tells if the given DOM CSS object matches the current view media.
  512. *
  513. * @param {object} domObject The DOM CSS object to check.
  514. * @return {boolean} True if the DOM CSS object matches the current view
  515. * media, or false otherwise.
  516. */
  517. mediaMatches: function (domObject) {
  518. let mediaText = domObject.media.mediaText;
  519. return !mediaText ||
  520. this.viewedDocument.defaultView.matchMedia(mediaText).matches;
  521. },
  522. };
  523. /**
  524. * If the element has an id, return '#id'. Otherwise return 'tagname[n]' where
  525. * n is the index of this element in its siblings.
  526. * <p>A technically more 'correct' output from the no-id case might be:
  527. * 'tagname:nth-of-type(n)' however this is unlikely to be more understood
  528. * and it is longer.
  529. *
  530. * @param {nsIDOMElement} element the element for which you want the short name.
  531. * @return {string} the string to be displayed for element.
  532. */
  533. CssLogic.getShortName = function (element) {
  534. if (!element) {
  535. return "null";
  536. }
  537. if (element.id) {
  538. return "#" + element.id;
  539. }
  540. let priorSiblings = 0;
  541. let temp = element;
  542. while ((temp = temp.previousElementSibling)) {
  543. priorSiblings++;
  544. }
  545. return element.tagName + "[" + priorSiblings + "]";
  546. };
  547. /**
  548. * Get a string list of selectors for a given DOMRule.
  549. *
  550. * @param {DOMRule} domRule
  551. * The DOMRule to parse.
  552. * @return {Array}
  553. * An array of string selectors.
  554. */
  555. CssLogic.getSelectors = function (domRule) {
  556. let selectors = [];
  557. let len = domUtils.getSelectorCount(domRule);
  558. for (let i = 0; i < len; i++) {
  559. let text = domUtils.getSelectorText(domRule, i);
  560. selectors.push(text);
  561. }
  562. return selectors;
  563. };
  564. /**
  565. * Given a node, check to see if it is a ::before or ::after element.
  566. * If so, return the node that is accessible from within the document
  567. * (the parent of the anonymous node), along with which pseudo element
  568. * it was. Otherwise, return the node itself.
  569. *
  570. * @returns {Object}
  571. * - {DOMNode} node The non-anonymous node
  572. * - {string} pseudo One of ':before', ':after', or null.
  573. */
  574. CssLogic.getBindingElementAndPseudo = function (node) {
  575. let bindingElement = node;
  576. let pseudo = null;
  577. if (node.nodeName == "_moz_generated_content_before") {
  578. bindingElement = node.parentNode;
  579. pseudo = ":before";
  580. } else if (node.nodeName == "_moz_generated_content_after") {
  581. bindingElement = node.parentNode;
  582. pseudo = ":after";
  583. }
  584. return {
  585. bindingElement: bindingElement,
  586. pseudo: pseudo
  587. };
  588. };
  589. /**
  590. * Get the computed style on a node. Automatically handles reading
  591. * computed styles on a ::before/::after element by reading on the
  592. * parent node with the proper pseudo argument.
  593. *
  594. * @param {Node}
  595. * @returns {CSSStyleDeclaration}
  596. */
  597. CssLogic.getComputedStyle = function (node) {
  598. if (!node ||
  599. Cu.isDeadWrapper(node) ||
  600. node.nodeType !== nodeConstants.ELEMENT_NODE ||
  601. !node.ownerDocument ||
  602. !node.ownerDocument.defaultView) {
  603. return null;
  604. }
  605. let {bindingElement, pseudo} = CssLogic.getBindingElementAndPseudo(node);
  606. return node.ownerDocument.defaultView.getComputedStyle(bindingElement,
  607. pseudo);
  608. };
  609. /**
  610. * Get a source for a stylesheet, taking into account embedded stylesheets
  611. * for which we need to use document.defaultView.location.href rather than
  612. * sheet.href
  613. *
  614. * @param {CSSStyleSheet} sheet the DOM object for the style sheet.
  615. * @return {string} the address of the stylesheet.
  616. */
  617. CssLogic.href = function (sheet) {
  618. let href = sheet.href;
  619. if (!href) {
  620. href = sheet.ownerNode.ownerDocument.location;
  621. }
  622. return href;
  623. };
  624. /**
  625. * Find the position of [element] in [nodeList].
  626. * @returns an index of the match, or -1 if there is no match
  627. */
  628. function positionInNodeList(element, nodeList) {
  629. for (let i = 0; i < nodeList.length; i++) {
  630. if (element === nodeList[i]) {
  631. return i;
  632. }
  633. }
  634. return -1;
  635. }
  636. /**
  637. * Find a unique CSS selector for a given element
  638. * @returns a string such that ele.ownerDocument.querySelector(reply) === ele
  639. * and ele.ownerDocument.querySelectorAll(reply).length === 1
  640. */
  641. CssLogic.findCssSelector = function (ele) {
  642. ele = getRootBindingParent(ele);
  643. let document = ele.ownerDocument;
  644. if (!document || !document.contains(ele)) {
  645. throw new Error("findCssSelector received element not inside document");
  646. }
  647. // document.querySelectorAll("#id") returns multiple if elements share an ID
  648. if (ele.id &&
  649. document.querySelectorAll("#" + CSS.escape(ele.id)).length === 1) {
  650. return "#" + CSS.escape(ele.id);
  651. }
  652. // Inherently unique by tag name
  653. let tagName = ele.localName;
  654. if (tagName === "html") {
  655. return "html";
  656. }
  657. if (tagName === "head") {
  658. return "head";
  659. }
  660. if (tagName === "body") {
  661. return "body";
  662. }
  663. // We might be able to find a unique class name
  664. let selector, index, matches;
  665. if (ele.classList.length > 0) {
  666. for (let i = 0; i < ele.classList.length; i++) {
  667. // Is this className unique by itself?
  668. selector = "." + CSS.escape(ele.classList.item(i));
  669. matches = document.querySelectorAll(selector);
  670. if (matches.length === 1) {
  671. return selector;
  672. }
  673. // Maybe it's unique with a tag name?
  674. selector = tagName + selector;
  675. matches = document.querySelectorAll(selector);
  676. if (matches.length === 1) {
  677. return selector;
  678. }
  679. // Maybe it's unique using a tag name and nth-child
  680. index = positionInNodeList(ele, ele.parentNode.children) + 1;
  681. selector = selector + ":nth-child(" + index + ")";
  682. matches = document.querySelectorAll(selector);
  683. if (matches.length === 1) {
  684. return selector;
  685. }
  686. }
  687. }
  688. // Not unique enough yet. As long as it's not a child of the document,
  689. // continue recursing up until it is unique enough.
  690. if (ele.parentNode !== document) {
  691. index = positionInNodeList(ele, ele.parentNode.children) + 1;
  692. selector = CssLogic.findCssSelector(ele.parentNode) + " > " +
  693. tagName + ":nth-child(" + index + ")";
  694. }
  695. return selector;
  696. };
  697. /**
  698. * Get the full CSS path for a given element.
  699. * @returns a string that can be used as a CSS selector for the element. It might not
  700. * match the element uniquely. It does however, represent the full path from the root
  701. * node to the element.
  702. */
  703. CssLogic.getCssPath = function (ele) {
  704. ele = getRootBindingParent(ele);
  705. const document = ele.ownerDocument;
  706. if (!document || !document.contains(ele)) {
  707. throw new Error("getCssPath received element not inside document");
  708. }
  709. const getElementSelector = element => {
  710. if (!element.localName) {
  711. return "";
  712. }
  713. let label = element.nodeName == element.nodeName.toUpperCase()
  714. ? element.localName.toLowerCase()
  715. : element.localName;
  716. if (element.id) {
  717. label += "#" + element.id;
  718. }
  719. if (element.classList) {
  720. for (let cl of element.classList) {
  721. label += "." + cl;
  722. }
  723. }
  724. return label;
  725. };
  726. let paths = [];
  727. while (ele) {
  728. if (!ele || ele.nodeType !== Node.ELEMENT_NODE) {
  729. break;
  730. }
  731. paths.splice(0, 0, getElementSelector(ele));
  732. ele = ele.parentNode;
  733. }
  734. return paths.length ? paths.join(" ") : "";
  735. }
  736. /**
  737. * A safe way to access cached bits of information about a stylesheet.
  738. *
  739. * @constructor
  740. * @param {CssLogic} cssLogic pointer to the CssLogic instance working with
  741. * this CssSheet object.
  742. * @param {CSSStyleSheet} domSheet reference to a DOM CSSStyleSheet object.
  743. * @param {number} index tells the index/position of the stylesheet within the
  744. * main document.
  745. */
  746. function CssSheet(cssLogic, domSheet, index) {
  747. this._cssLogic = cssLogic;
  748. this.domSheet = domSheet;
  749. this.index = this.contentSheet ? index : -100 * index;
  750. // Cache of the sheets href. Cached by the getter.
  751. this._href = null;
  752. // Short version of href for use in select boxes etc. Cached by getter.
  753. this._shortSource = null;
  754. // null for uncached.
  755. this._sheetAllowed = null;
  756. // Cached CssRules from the given stylesheet.
  757. this._rules = {};
  758. this._ruleCount = -1;
  759. }
  760. CssSheet.prototype = {
  761. _passId: null,
  762. _contentSheet: null,
  763. /**
  764. * Tells if the stylesheet is provided by the browser or not.
  765. *
  766. * @return {boolean} false if this is a browser-provided stylesheet, or true
  767. * otherwise.
  768. */
  769. get contentSheet() {
  770. if (this._contentSheet === null) {
  771. this._contentSheet = isContentStylesheet(this.domSheet);
  772. }
  773. return this._contentSheet;
  774. },
  775. /**
  776. * Tells if the stylesheet is disabled or not.
  777. * @return {boolean} true if this stylesheet is disabled, or false otherwise.
  778. */
  779. get disabled() {
  780. return this.domSheet.disabled;
  781. },
  782. /**
  783. * Get a source for a stylesheet, using CssLogic.href
  784. *
  785. * @return {string} the address of the stylesheet.
  786. */
  787. get href() {
  788. if (this._href) {
  789. return this._href;
  790. }
  791. this._href = CssLogic.href(this.domSheet);
  792. return this._href;
  793. },
  794. /**
  795. * Create a shorthand version of the href of a stylesheet.
  796. *
  797. * @return {string} the shorthand source of the stylesheet.
  798. */
  799. get shortSource() {
  800. if (this._shortSource) {
  801. return this._shortSource;
  802. }
  803. this._shortSource = shortSource(this.domSheet);
  804. return this._shortSource;
  805. },
  806. /**
  807. * Tells if the sheet is allowed or not by the current CssLogic.sourceFilter.
  808. *
  809. * @return {boolean} true if the stylesheet is allowed by the sourceFilter, or
  810. * false otherwise.
  811. */
  812. get sheetAllowed() {
  813. if (this._sheetAllowed !== null) {
  814. return this._sheetAllowed;
  815. }
  816. this._sheetAllowed = true;
  817. let filter = this._cssLogic.sourceFilter;
  818. if (filter === FILTER.USER && !this.contentSheet) {
  819. this._sheetAllowed = false;
  820. }
  821. if (filter !== FILTER.USER && filter !== FILTER.UA) {
  822. this._sheetAllowed = (filter === this.href);
  823. }
  824. return this._sheetAllowed;
  825. },
  826. /**
  827. * Retrieve the number of rules in this stylesheet.
  828. *
  829. * @return {number} the number of nsIDOMCSSRule objects in this stylesheet.
  830. */
  831. get ruleCount() {
  832. return this._ruleCount > -1 ?
  833. this._ruleCount :
  834. this.domSheet.cssRules.length;
  835. },
  836. /**
  837. * Retrieve a CssRule object for the given CSSStyleRule. The CssRule object is
  838. * cached, such that subsequent retrievals return the same CssRule object for
  839. * the same CSSStyleRule object.
  840. *
  841. * @param {CSSStyleRule} aDomRule the CSSStyleRule object for which you want a
  842. * CssRule object.
  843. * @return {CssRule} the cached CssRule object for the given CSSStyleRule
  844. * object.
  845. */
  846. getRule: function (domRule) {
  847. let cacheId = domRule.type + domRule.selectorText;
  848. let rule = null;
  849. let ruleFound = false;
  850. if (cacheId in this._rules) {
  851. for (let i = 0, rulesLen = this._rules[cacheId].length;
  852. i < rulesLen;
  853. i++) {
  854. rule = this._rules[cacheId][i];
  855. if (rule.domRule === domRule) {
  856. ruleFound = true;
  857. break;
  858. }
  859. }
  860. }
  861. if (!ruleFound) {
  862. if (!(cacheId in this._rules)) {
  863. this._rules[cacheId] = [];
  864. }
  865. rule = new CssRule(this, domRule);
  866. this._rules[cacheId].push(rule);
  867. }
  868. return rule;
  869. },
  870. toString: function () {
  871. return "CssSheet[" + this.shortSource + "]";
  872. }
  873. };
  874. /**
  875. * Information about a single CSSStyleRule.
  876. *
  877. * @param {CSSSheet|null} cssSheet the CssSheet object of the stylesheet that
  878. * holds the CSSStyleRule. If the rule comes from element.style, set this
  879. * argument to null.
  880. * @param {CSSStyleRule|object} domRule the DOM CSSStyleRule for which you want
  881. * to cache data. If the rule comes from element.style, then provide
  882. * an object of the form: {style: element.style}.
  883. * @param {Element} [element] If the rule comes from element.style, then this
  884. * argument must point to the element.
  885. * @constructor
  886. */
  887. function CssRule(cssSheet, domRule, element) {
  888. this._cssSheet = cssSheet;
  889. this.domRule = domRule;
  890. let parentRule = domRule.parentRule;
  891. if (parentRule && parentRule.type == CSSRule.MEDIA_RULE) {
  892. this.mediaText = parentRule.media.mediaText;
  893. }
  894. if (this._cssSheet) {
  895. // parse domRule.selectorText on call to this.selectors
  896. this._selectors = null;
  897. this.line = domUtils.getRuleLine(this.domRule);
  898. this.source = this._cssSheet.shortSource + ":" + this.line;
  899. if (this.mediaText) {
  900. this.source += " @media " + this.mediaText;
  901. }
  902. this.href = this._cssSheet.href;
  903. this.contentRule = this._cssSheet.contentSheet;
  904. } else if (element) {
  905. this._selectors = [ new CssSelector(this, "@element.style", 0) ];
  906. this.line = -1;
  907. this.source = l10n("rule.sourceElement");
  908. this.href = "#";
  909. this.contentRule = true;
  910. this.sourceElement = element;
  911. }
  912. }
  913. CssRule.prototype = {
  914. _passId: null,
  915. mediaText: "",
  916. get isMediaRule() {
  917. return !!this.mediaText;
  918. },
  919. /**
  920. * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter.
  921. *
  922. * @return {boolean} true if the parent stylesheet is allowed by the current
  923. * sourceFilter, or false otherwise.
  924. */
  925. get sheetAllowed() {
  926. return this._cssSheet ? this._cssSheet.sheetAllowed : true;
  927. },
  928. /**
  929. * Retrieve the parent stylesheet index/position in the viewed document.
  930. *
  931. * @return {number} the parent stylesheet index/position in the viewed
  932. * document.
  933. */
  934. get sheetIndex() {
  935. return this._cssSheet ? this._cssSheet.index : 0;
  936. },
  937. /**
  938. * Retrieve the style property value from the current CSSStyleRule.
  939. *
  940. * @param {string} property the CSS property name for which you want the
  941. * value.
  942. * @return {string} the property value.
  943. */
  944. getPropertyValue: function (property) {
  945. return this.domRule.style.getPropertyValue(property);
  946. },
  947. /**
  948. * Retrieve the style property priority from the current CSSStyleRule.
  949. *
  950. * @param {string} property the CSS property name for which you want the
  951. * priority.
  952. * @return {string} the property priority.
  953. */
  954. getPropertyPriority: function (property) {
  955. return this.domRule.style.getPropertyPriority(property);
  956. },
  957. /**
  958. * Retrieve the list of CssSelector objects for each of the parsed selectors
  959. * of the current CSSStyleRule.
  960. *
  961. * @return {array} the array hold the CssSelector objects.
  962. */
  963. get selectors() {
  964. if (this._selectors) {
  965. return this._selectors;
  966. }
  967. // Parse the CSSStyleRule.selectorText string.
  968. this._selectors = [];
  969. if (!this.domRule.selectorText) {
  970. return this._selectors;
  971. }
  972. let selectors = CssLogic.getSelectors(this.domRule);
  973. for (let i = 0, len = selectors.length; i < len; i++) {
  974. this._selectors.push(new CssSelector(this, selectors[i], i));
  975. }
  976. return this._selectors;
  977. },
  978. toString: function () {
  979. return "[CssRule " + this.domRule.selectorText + "]";
  980. },
  981. };
  982. /**
  983. * The CSS selector class allows us to document the ranking of various CSS
  984. * selectors.
  985. *
  986. * @constructor
  987. * @param {CssRule} cssRule the CssRule instance from where the selector comes.
  988. * @param {string} selector The selector that we wish to investigate.
  989. * @param {Number} index The index of the selector within it's rule.
  990. */
  991. function CssSelector(cssRule, selector, index) {
  992. this.cssRule = cssRule;
  993. this.text = selector;
  994. this.elementStyle = this.text == "@element.style";
  995. this._specificity = null;
  996. this.selectorIndex = index;
  997. }
  998. exports.CssSelector = CssSelector;
  999. CssSelector.prototype = {
  1000. _matchId: null,
  1001. /**
  1002. * Retrieve the CssSelector source, which is the source of the CssSheet owning
  1003. * the selector.
  1004. *
  1005. * @return {string} the selector source.
  1006. */
  1007. get source() {
  1008. return this.cssRule.source;
  1009. },
  1010. /**
  1011. * Retrieve the CssSelector source element, which is the source of the CssRule
  1012. * owning the selector. This is only available when the CssSelector comes from
  1013. * an element.style.
  1014. *
  1015. * @return {string} the source element selector.
  1016. */
  1017. get sourceElement() {
  1018. return this.cssRule.sourceElement;
  1019. },
  1020. /**
  1021. * Retrieve the address of the CssSelector. This points to the address of the
  1022. * CssSheet owning this selector.
  1023. *
  1024. * @return {string} the address of the CssSelector.
  1025. */
  1026. get href() {
  1027. return this.cssRule.href;
  1028. },
  1029. /**
  1030. * Check if the selector comes from a browser-provided stylesheet.
  1031. *
  1032. * @return {boolean} true if the selector comes from a content-provided
  1033. * stylesheet, or false otherwise.
  1034. */
  1035. get contentRule() {
  1036. return this.cssRule.contentRule;
  1037. },
  1038. /**
  1039. * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter.
  1040. *
  1041. * @return {boolean} true if the parent stylesheet is allowed by the current
  1042. * sourceFilter, or false otherwise.
  1043. */
  1044. get sheetAllowed() {
  1045. return this.cssRule.sheetAllowed;
  1046. },
  1047. /**
  1048. * Retrieve the parent stylesheet index/position in the viewed document.
  1049. *
  1050. * @return {number} the parent stylesheet index/position in the viewed
  1051. * document.
  1052. */
  1053. get sheetIndex() {
  1054. return this.cssRule.sheetIndex;
  1055. },
  1056. /**
  1057. * Retrieve the line of the parent CSSStyleRule in the parent CSSStyleSheet.
  1058. *
  1059. * @return {number} the line of the parent CSSStyleRule in the parent
  1060. * stylesheet.
  1061. */
  1062. get ruleLine() {
  1063. return this.cssRule.line;
  1064. },
  1065. /**
  1066. * Retrieve specificity information for the current selector.
  1067. *
  1068. * @see http://www.w3.org/TR/css3-selectors/#specificity
  1069. * @see http://www.w3.org/TR/CSS2/selector.html
  1070. *
  1071. * @return {Number} The selector's specificity.
  1072. */
  1073. get specificity() {
  1074. if (this.elementStyle) {
  1075. // We can't ask specificity from DOMUtils as element styles don't provide
  1076. // CSSStyleRule interface DOMUtils expect. However, specificity of element
  1077. // style is constant, 1,0,0,0 or 0x01000000, just return the constant
  1078. // directly. @see http://www.w3.org/TR/CSS2/cascade.html#specificity
  1079. return 0x01000000;
  1080. }
  1081. if (this._specificity) {
  1082. return this._specificity;
  1083. }
  1084. this._specificity = domUtils.getSpecificity(this.cssRule.domRule,
  1085. this.selectorIndex);
  1086. return this._specificity;
  1087. },
  1088. toString: function () {
  1089. return this.text;
  1090. },
  1091. };
  1092. /**
  1093. * A cache of information about the matched rules, selectors and values attached
  1094. * to a CSS property, for the highlighted element.
  1095. *
  1096. * The heart of the CssPropertyInfo object is the _findMatchedSelectors()
  1097. * method. This are invoked when the PropertyView tries to access the
  1098. * .matchedSelectors array.
  1099. * Results are cached, for later reuse.
  1100. *
  1101. * @param {CssLogic} cssLogic Reference to the parent CssLogic instance
  1102. * @param {string} property The CSS property we are gathering information for
  1103. * @param {function} isInherited A function that determines if the CSS property
  1104. * is inherited.
  1105. * @constructor
  1106. */
  1107. function CssPropertyInfo(cssLogic, property, isInherited) {
  1108. this._cssLogic = cssLogic;
  1109. this.property = property;
  1110. this._value = "";
  1111. this._isInherited = isInherited;
  1112. // An array holding CssSelectorInfo objects for each of the matched selectors
  1113. // that are inside a CSS rule. Only rules that hold the this.property are
  1114. // counted. This includes rules that come from filtered stylesheets (those
  1115. // that have sheetAllowed = false).
  1116. this._matchedSelectors = null;
  1117. }
  1118. CssPropertyInfo.prototype = {
  1119. /**
  1120. * Retrieve the computed style value for the current property, for the
  1121. * highlighted element.
  1122. *
  1123. * @return {string} the computed style value for the current property, for the
  1124. * highlighted element.
  1125. */
  1126. get value() {
  1127. if (!this._value && this._cssLogic.computedStyle) {
  1128. try {
  1129. this._value =
  1130. this._cssLogic.computedStyle.getPropertyValue(this.property);
  1131. } catch (ex) {
  1132. console.log("Error reading computed style for " + this.property);
  1133. console.log(ex);
  1134. }
  1135. }
  1136. return this._value;
  1137. },
  1138. /**
  1139. * Retrieve the array holding CssSelectorInfo objects for each of the matched
  1140. * selectors, from each of the matched rules. Only selectors coming from
  1141. * allowed stylesheets are included in the array.
  1142. *
  1143. * @return {array} the list of CssSelectorInfo objects of selectors that match
  1144. * the highlighted element and its parents.
  1145. */
  1146. get matchedSelectors() {
  1147. if (!this._matchedSelectors) {
  1148. this._findMatchedSelectors();
  1149. } else if (this.needRefilter) {
  1150. this._refilterSelectors();
  1151. }
  1152. return this._matchedSelectors;
  1153. },
  1154. /**
  1155. * Find the selectors that match the highlighted element and its parents.
  1156. * Uses CssLogic.processMatchedSelectors() to find the matched selectors,
  1157. * passing in a reference to CssPropertyInfo._processMatchedSelector() to
  1158. * create CssSelectorInfo objects, which we then sort
  1159. * @private
  1160. */
  1161. _findMatchedSelectors: function () {
  1162. this._matchedSelectors = [];
  1163. this.needRefilter = false;
  1164. this._cssLogic.processMatchedSelectors(this._processMatchedSelector, this);
  1165. // Sort the selectors by how well they match the given element.
  1166. this._matchedSelectors.sort(function (selectorInfo1, selectorInfo2) {
  1167. if (selectorInfo1.status > selectorInfo2.status) {
  1168. return -1;
  1169. } else if (selectorInfo2.status > selectorInfo1.status) {
  1170. return 1;
  1171. }
  1172. return selectorInfo1.compareTo(selectorInfo2);
  1173. });
  1174. // Now we know which of the matches is best, we can mark it BEST_MATCH.
  1175. if (this._matchedSelectors.length > 0 &&
  1176. this._matchedSelectors[0].status > STATUS.UNMATCHED) {
  1177. this._matchedSelectors[0].status = STATUS.BEST;
  1178. }
  1179. },
  1180. /**
  1181. * Process a matched CssSelector object.
  1182. *
  1183. * @private
  1184. * @param {CssSelector} selector the matched CssSelector object.
  1185. * @param {STATUS} status the CssSelector match status.
  1186. */
  1187. _processMatchedSelector: function (selector, status) {
  1188. let cssRule = selector.cssRule;
  1189. let value = cssRule.getPropertyValue(this.property);
  1190. if (value &&
  1191. (status == STATUS.MATCHED ||
  1192. (status == STATUS.PARENT_MATCH &&
  1193. this._isInherited(this.property)))) {
  1194. let selectorInfo = new CssSelectorInfo(selector, this.property, value,
  1195. status);
  1196. this._matchedSelectors.push(selectorInfo);
  1197. }
  1198. },
  1199. /**
  1200. * Refilter the matched selectors array when the CssLogic.sourceFilter
  1201. * changes. This allows for quick filter changes.
  1202. * @private
  1203. */
  1204. _refilterSelectors: function () {
  1205. let passId = ++this._cssLogic._passId;
  1206. let iterator = function (selectorInfo) {
  1207. let cssRule = selectorInfo.selector.cssRule;
  1208. if (cssRule._passId != passId) {
  1209. cssRule._passId = passId;
  1210. }
  1211. };
  1212. if (this._matchedSelectors) {
  1213. this._matchedSelectors.forEach(iterator);
  1214. }
  1215. this.needRefilter = false;
  1216. },
  1217. toString: function () {
  1218. return "CssPropertyInfo[" + this.property + "]";
  1219. },
  1220. };
  1221. /**
  1222. * A class that holds information about a given CssSelector object.
  1223. *
  1224. * Instances of this class are given to CssHtmlTree in the array of matched
  1225. * selectors. Each such object represents a displayable row in the PropertyView
  1226. * objects. The information given by this object blends data coming from the
  1227. * CssSheet, CssRule and from the CssSelector that own this object.
  1228. *
  1229. * @param {CssSelector} selector The CssSelector object for which to
  1230. * present information.
  1231. * @param {string} property The property for which information should
  1232. * be retrieved.
  1233. * @param {string} value The property value from the CssRule that owns
  1234. * the selector.
  1235. * @param {STATUS} status The selector match status.
  1236. * @constructor
  1237. */
  1238. function CssSelectorInfo(selector, property, value, status) {
  1239. this.selector = selector;
  1240. this.property = property;
  1241. this.status = status;
  1242. this.value = value;
  1243. let priority = this.selector.cssRule.getPropertyPriority(this.property);
  1244. this.important = (priority === "important");
  1245. }
  1246. CssSelectorInfo.prototype = {
  1247. /**
  1248. * Retrieve the CssSelector source, which is the source of the CssSheet owning
  1249. * the selector.
  1250. *
  1251. * @return {string} the selector source.
  1252. */
  1253. get source() {
  1254. return this.selector.source;
  1255. },
  1256. /**
  1257. * Retrieve the CssSelector source element, which is the source of the CssRule
  1258. * owning the selector. This is only available when the CssSelector comes from
  1259. * an element.style.
  1260. *
  1261. * @return {string} the source element selector.
  1262. */
  1263. get sourceElement() {
  1264. return this.selector.sourceElement;
  1265. },
  1266. /**
  1267. * Retrieve the address of the CssSelector. This points to the address of the
  1268. * CssSheet owning this selector.
  1269. *
  1270. * @return {string} the address of the CssSelector.
  1271. */
  1272. get href() {
  1273. return this.selector.href;
  1274. },
  1275. /**
  1276. * Check if the CssSelector comes from element.style or not.
  1277. *
  1278. * @return {boolean} true if the CssSelector comes from element.style, or
  1279. * false otherwise.
  1280. */
  1281. get elementStyle() {
  1282. return this.selector.elementStyle;
  1283. },
  1284. /**
  1285. * Retrieve specificity information for the current selector.
  1286. *
  1287. * @return {object} an object holding specificity information for the current
  1288. * selector.
  1289. */
  1290. get specificity() {
  1291. return this.selector.specificity;
  1292. },
  1293. /**
  1294. * Retrieve the parent stylesheet index/position in the viewed document.
  1295. *
  1296. * @return {number} the parent stylesheet index/position in the viewed
  1297. * document.
  1298. */
  1299. get sheetIndex() {
  1300. return this.selector.sheetIndex;
  1301. },
  1302. /**
  1303. * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter.
  1304. *
  1305. * @return {boolean} true if the parent stylesheet is allowed by the current
  1306. * sourceFilter, or false otherwise.
  1307. */
  1308. get sheetAllowed() {
  1309. return this.selector.sheetAllowed;
  1310. },
  1311. /**
  1312. * Retrieve the line of the parent CSSStyleRule in the parent CSSStyleSheet.
  1313. *
  1314. * @return {number} the line of the parent CSSStyleRule in the parent
  1315. * stylesheet.
  1316. */
  1317. get ruleLine() {
  1318. return this.selector.ruleLine;
  1319. },
  1320. /**
  1321. * Check if the selector comes from a browser-provided stylesheet.
  1322. *
  1323. * @return {boolean} true if the selector comes from a browser-provided
  1324. * stylesheet, or false otherwise.
  1325. */
  1326. get contentRule() {
  1327. return this.selector.contentRule;
  1328. },
  1329. /**
  1330. * Compare the current CssSelectorInfo instance to another instance, based on
  1331. * specificity information.
  1332. *
  1333. * @param {CssSelectorInfo} that The instance to compare ourselves against.
  1334. * @return number -1, 0, 1 depending on how that compares with this.
  1335. */
  1336. compareTo: function (that) {
  1337. if (!this.contentRule && that.contentRule) {
  1338. return 1;
  1339. }
  1340. if (this.contentRule && !that.contentRule) {
  1341. return -1;
  1342. }
  1343. if (this.elementStyle && !that.elementStyle) {
  1344. if (!this.important && that.important) {
  1345. return 1;
  1346. }
  1347. return -1;
  1348. }
  1349. if (!this.elementStyle && that.elementStyle) {
  1350. if (this.important && !that.important) {
  1351. return -1;
  1352. }
  1353. return 1;
  1354. }
  1355. if (this.important && !that.important) {
  1356. return -1;
  1357. }
  1358. if (that.important && !this.important) {
  1359. return 1;
  1360. }
  1361. if (this.specificity > that.specificity) {
  1362. return -1;
  1363. }
  1364. if (that.specificity > this.specificity) {
  1365. return 1;
  1366. }
  1367. if (this.sheetIndex > that.sheetIndex) {
  1368. return -1;
  1369. }
  1370. if (that.sheetIndex > this.sheetIndex) {
  1371. return 1;
  1372. }
  1373. if (this.ruleLine > that.ruleLine) {
  1374. return -1;
  1375. }
  1376. if (that.ruleLine > this.ruleLine) {
  1377. return 1;
  1378. }
  1379. return 0;
  1380. },
  1381. toString: function () {
  1382. return this.selector + " -> " + this.value;
  1383. },
  1384. };
  1385. DevToolsUtils.defineLazyGetter(this, "domUtils", function () {
  1386. return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
  1387. });