Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
1 /* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /*
8 * About the objects defined in this file:
9 * - CssLogic contains style information about a view context. It provides
10 * access to 2 sets of objects: Css[Sheet|Rule|Selector] provide access to
11 * information that does not change when the selected element changes while
12 * Css[Property|Selector]Info provide information that is dependent on the
13 * selected element.
14 * Its key methods are highlight(), getPropertyInfo() and forEachSheet(), etc
15 * It also contains a number of static methods for l10n, naming, etc
16 *
17 * - CssSheet provides a more useful API to a DOM CSSSheet for our purposes,
18 * including shortSource and href.
19 * - CssRule a more useful API to a nsIDOMCSSRule including access to the group
20 * of CssSelectors that the rule provides properties for
21 * - CssSelector A single selector - i.e. not a selector group. In other words
22 * a CssSelector does not contain ','. This terminology is different from the
23 * standard DOM API, but more inline with the definition in the spec.
24 *
25 * - CssPropertyInfo contains style information for a single property for the
26 * highlighted element.
27 * - CssSelectorInfo is a wrapper around CssSelector, which adds sorting with
28 * reference to the selected element.
29 */
31 /**
32 * Provide access to the style information in a page.
33 * CssLogic uses the standard DOM API, and the Gecko inIDOMUtils API to access
34 * styling information in the page, and present this to the user in a way that
35 * helps them understand:
36 * - why their expectations may not have been fulfilled
37 * - how browsers process CSS
38 * @constructor
39 */
41 const {Cc, Ci, Cu} = require("chrome");
43 const RX_UNIVERSAL_SELECTOR = /\s*\*\s*/g;
44 const RX_NOT = /:not\((.*?)\)/g;
45 const RX_PSEUDO_CLASS_OR_ELT = /(:[\w-]+\().*?\)/g;
46 const RX_CONNECTORS = /\s*[\s>+~]\s*/g;
47 const RX_ID = /\s*#\w+\s*/g;
48 const RX_CLASS_OR_ATTRIBUTE = /\s*(?:\.\w+|\[.+?\])\s*/g;
49 const RX_PSEUDO = /\s*:?:([\w-]+)(\(?\)?)\s*/g;
51 Cu.import("resource://gre/modules/Services.jsm");
52 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
54 function CssLogic()
55 {
56 // The cache of examined CSS properties.
57 _propertyInfos: {};
58 }
60 exports.CssLogic = CssLogic;
62 /**
63 * Special values for filter, in addition to an href these values can be used
64 */
65 CssLogic.FILTER = {
66 USER: "user", // show properties for all user style sheets.
67 UA: "ua", // USER, plus user-agent (i.e. browser) style sheets
68 };
70 /**
71 * Known media values. To distinguish "all" stylesheets (above) from "all" media
72 * The full list includes braille, embossed, handheld, print, projection,
73 * speech, tty, and tv, but this is only a hack because these are not defined
74 * in the DOM at all.
75 * @see http://www.w3.org/TR/CSS21/media.html#media-types
76 */
77 CssLogic.MEDIA = {
78 ALL: "all",
79 SCREEN: "screen",
80 };
82 /**
83 * Each rule has a status, the bigger the number, the better placed it is to
84 * provide styling information.
85 *
86 * These statuses are localized inside the styleinspector.properties string bundle.
87 * @see csshtmltree.js RuleView._cacheStatusNames()
88 */
89 CssLogic.STATUS = {
90 BEST: 3,
91 MATCHED: 2,
92 PARENT_MATCH: 1,
93 UNMATCHED: 0,
94 UNKNOWN: -1,
95 };
97 CssLogic.prototype = {
98 // Both setup by highlight().
99 viewedElement: null,
100 viewedDocument: null,
102 // The cache of the known sheets.
103 _sheets: null,
105 // Have the sheets been cached?
106 _sheetsCached: false,
108 // The total number of rules, in all stylesheets, after filtering.
109 _ruleCount: 0,
111 // The computed styles for the viewedElement.
112 _computedStyle: null,
114 // Source filter. Only display properties coming from the given source
115 _sourceFilter: CssLogic.FILTER.USER,
117 // Used for tracking unique CssSheet/CssRule/CssSelector objects, in a run of
118 // processMatchedSelectors().
119 _passId: 0,
121 // Used for tracking matched CssSelector objects.
122 _matchId: 0,
124 _matchedRules: null,
125 _matchedSelectors: null,
127 /**
128 * Reset various properties
129 */
130 reset: function CssLogic_reset()
131 {
132 this._propertyInfos = {};
133 this._ruleCount = 0;
134 this._sheetIndex = 0;
135 this._sheets = {};
136 this._sheetsCached = false;
137 this._matchedRules = null;
138 this._matchedSelectors = null;
139 },
141 /**
142 * Focus on a new element - remove the style caches.
143 *
144 * @param {nsIDOMElement} aViewedElement the element the user has highlighted
145 * in the Inspector.
146 */
147 highlight: function CssLogic_highlight(aViewedElement)
148 {
149 if (!aViewedElement) {
150 this.viewedElement = null;
151 this.viewedDocument = null;
152 this._computedStyle = null;
153 this.reset();
154 return;
155 }
157 this.viewedElement = aViewedElement;
159 let doc = this.viewedElement.ownerDocument;
160 if (doc != this.viewedDocument) {
161 // New document: clear/rebuild the cache.
162 this.viewedDocument = doc;
164 // Hunt down top level stylesheets, and cache them.
165 this._cacheSheets();
166 } else {
167 // Clear cached data in the CssPropertyInfo objects.
168 this._propertyInfos = {};
169 }
171 this._matchedRules = null;
172 this._matchedSelectors = null;
173 let win = this.viewedDocument.defaultView;
174 this._computedStyle = win.getComputedStyle(this.viewedElement, "");
175 },
177 /**
178 * Get the source filter.
179 * @returns {string} The source filter being used.
180 */
181 get sourceFilter() {
182 return this._sourceFilter;
183 },
185 /**
186 * Source filter. Only display properties coming from the given source (web
187 * address). Note that in order to avoid information overload we DO NOT show
188 * unmatched system rules.
189 * @see CssLogic.FILTER.*
190 */
191 set sourceFilter(aValue) {
192 let oldValue = this._sourceFilter;
193 this._sourceFilter = aValue;
195 let ruleCount = 0;
197 // Update the CssSheet objects.
198 this.forEachSheet(function(aSheet) {
199 aSheet._sheetAllowed = -1;
200 if (aSheet.contentSheet && aSheet.sheetAllowed) {
201 ruleCount += aSheet.ruleCount;
202 }
203 }, this);
205 this._ruleCount = ruleCount;
207 // Full update is needed because the this.processMatchedSelectors() method
208 // skips UA stylesheets if the filter does not allow such sheets.
209 let needFullUpdate = (oldValue == CssLogic.FILTER.UA ||
210 aValue == CssLogic.FILTER.UA);
212 if (needFullUpdate) {
213 this._matchedRules = null;
214 this._matchedSelectors = null;
215 this._propertyInfos = {};
216 } else {
217 // Update the CssPropertyInfo objects.
218 for each (let propertyInfo in this._propertyInfos) {
219 propertyInfo.needRefilter = true;
220 }
221 }
222 },
224 /**
225 * Return a CssPropertyInfo data structure for the currently viewed element
226 * and the specified CSS property. If there is no currently viewed element we
227 * return an empty object.
228 *
229 * @param {string} aProperty The CSS property to look for.
230 * @return {CssPropertyInfo} a CssPropertyInfo structure for the given
231 * property.
232 */
233 getPropertyInfo: function CssLogic_getPropertyInfo(aProperty)
234 {
235 if (!this.viewedElement) {
236 return {};
237 }
239 let info = this._propertyInfos[aProperty];
240 if (!info) {
241 info = new CssPropertyInfo(this, aProperty);
242 this._propertyInfos[aProperty] = info;
243 }
245 return info;
246 },
248 /**
249 * Cache all the stylesheets in the inspected document
250 * @private
251 */
252 _cacheSheets: function CssLogic_cacheSheets()
253 {
254 this._passId++;
255 this.reset();
257 // styleSheets isn't an array, but forEach can work on it anyway
258 Array.prototype.forEach.call(this.viewedDocument.styleSheets,
259 this._cacheSheet, this);
261 this._sheetsCached = true;
262 },
264 /**
265 * Cache a stylesheet if it falls within the requirements: if it's enabled,
266 * and if the @media is allowed. This method also walks through the stylesheet
267 * cssRules to find @imported rules, to cache the stylesheets of those rules
268 * as well.
269 *
270 * @private
271 * @param {CSSStyleSheet} aDomSheet the CSSStyleSheet object to cache.
272 */
273 _cacheSheet: function CssLogic_cacheSheet(aDomSheet)
274 {
275 if (aDomSheet.disabled) {
276 return;
277 }
279 // Only work with stylesheets that have their media allowed.
280 if (!this.mediaMatches(aDomSheet)) {
281 return;
282 }
284 // Cache the sheet.
285 let cssSheet = this.getSheet(aDomSheet, this._sheetIndex++);
286 if (cssSheet._passId != this._passId) {
287 cssSheet._passId = this._passId;
289 // Find import rules.
290 Array.prototype.forEach.call(aDomSheet.cssRules, function(aDomRule) {
291 if (aDomRule.type == Ci.nsIDOMCSSRule.IMPORT_RULE && aDomRule.styleSheet &&
292 this.mediaMatches(aDomRule)) {
293 this._cacheSheet(aDomRule.styleSheet);
294 }
295 }, this);
296 }
297 },
299 /**
300 * Retrieve the list of stylesheets in the document.
301 *
302 * @return {array} the list of stylesheets in the document.
303 */
304 get sheets()
305 {
306 if (!this._sheetsCached) {
307 this._cacheSheets();
308 }
310 let sheets = [];
311 this.forEachSheet(function (aSheet) {
312 if (aSheet.contentSheet) {
313 sheets.push(aSheet);
314 }
315 }, this);
317 return sheets;
318 },
320 /**
321 * Retrieve a CssSheet object for a given a CSSStyleSheet object. If the
322 * stylesheet is already cached, you get the existing CssSheet object,
323 * otherwise the new CSSStyleSheet object is cached.
324 *
325 * @param {CSSStyleSheet} aDomSheet the CSSStyleSheet object you want.
326 * @param {number} aIndex the index, within the document, of the stylesheet.
327 *
328 * @return {CssSheet} the CssSheet object for the given CSSStyleSheet object.
329 */
330 getSheet: function CL_getSheet(aDomSheet, aIndex)
331 {
332 let cacheId = "";
334 if (aDomSheet.href) {
335 cacheId = aDomSheet.href;
336 } else if (aDomSheet.ownerNode && aDomSheet.ownerNode.ownerDocument) {
337 cacheId = aDomSheet.ownerNode.ownerDocument.location;
338 }
340 let sheet = null;
341 let sheetFound = false;
343 if (cacheId in this._sheets) {
344 for (let i = 0, numSheets = this._sheets[cacheId].length; i < numSheets; i++) {
345 sheet = this._sheets[cacheId][i];
346 if (sheet.domSheet === aDomSheet) {
347 if (aIndex != -1) {
348 sheet.index = aIndex;
349 }
350 sheetFound = true;
351 break;
352 }
353 }
354 }
356 if (!sheetFound) {
357 if (!(cacheId in this._sheets)) {
358 this._sheets[cacheId] = [];
359 }
361 sheet = new CssSheet(this, aDomSheet, aIndex);
362 if (sheet.sheetAllowed && sheet.contentSheet) {
363 this._ruleCount += sheet.ruleCount;
364 }
366 this._sheets[cacheId].push(sheet);
367 }
369 return sheet;
370 },
372 /**
373 * Process each cached stylesheet in the document using your callback.
374 *
375 * @param {function} aCallback the function you want executed for each of the
376 * CssSheet objects cached.
377 * @param {object} aScope the scope you want for the callback function. aScope
378 * will be the this object when aCallback executes.
379 */
380 forEachSheet: function CssLogic_forEachSheet(aCallback, aScope)
381 {
382 for each (let sheets in this._sheets) {
383 for (let i = 0; i < sheets.length; i ++) {
384 // We take this as an opportunity to clean dead sheets
385 try {
386 let sheet = sheets[i];
387 sheet.domSheet; // If accessing domSheet raises an exception, then the
388 // style sheet is a dead object
389 aCallback.call(aScope, sheet, i, sheets);
390 } catch (e) {
391 sheets.splice(i, 1);
392 i --;
393 }
394 }
395 }
396 },
398 /**
399 * Process *some* cached stylesheets in the document using your callback. The
400 * callback function should return true in order to halt processing.
401 *
402 * @param {function} aCallback the function you want executed for some of the
403 * CssSheet objects cached.
404 * @param {object} aScope the scope you want for the callback function. aScope
405 * will be the this object when aCallback executes.
406 * @return {Boolean} true if aCallback returns true during any iteration,
407 * otherwise false is returned.
408 */
409 forSomeSheets: function CssLogic_forSomeSheets(aCallback, aScope)
410 {
411 for each (let sheets in this._sheets) {
412 if (sheets.some(aCallback, aScope)) {
413 return true;
414 }
415 }
416 return false;
417 },
419 /**
420 * Get the number nsIDOMCSSRule objects in the document, counted from all of
421 * the stylesheets. System sheets are excluded. If a filter is active, this
422 * tells only the number of nsIDOMCSSRule objects inside the selected
423 * CSSStyleSheet.
424 *
425 * WARNING: This only provides an estimate of the rule count, and the results
426 * could change at a later date. Todo remove this
427 *
428 * @return {number} the number of nsIDOMCSSRule (all rules).
429 */
430 get ruleCount()
431 {
432 if (!this._sheetsCached) {
433 this._cacheSheets();
434 }
436 return this._ruleCount;
437 },
439 /**
440 * Process the CssSelector objects that match the highlighted element and its
441 * parent elements. aScope.aCallback() is executed for each CssSelector
442 * object, being passed the CssSelector object and the match status.
443 *
444 * This method also includes all of the element.style properties, for each
445 * highlighted element parent and for the highlighted element itself.
446 *
447 * Note that the matched selectors are cached, such that next time your
448 * callback is invoked for the cached list of CssSelector objects.
449 *
450 * @param {function} aCallback the function you want to execute for each of
451 * the matched selectors.
452 * @param {object} aScope the scope you want for the callback function. aScope
453 * will be the this object when aCallback executes.
454 */
455 processMatchedSelectors: function CL_processMatchedSelectors(aCallback, aScope)
456 {
457 if (this._matchedSelectors) {
458 if (aCallback) {
459 this._passId++;
460 this._matchedSelectors.forEach(function(aValue) {
461 aCallback.call(aScope, aValue[0], aValue[1]);
462 aValue[0].cssRule._passId = this._passId;
463 }, this);
464 }
465 return;
466 }
468 if (!this._matchedRules) {
469 this._buildMatchedRules();
470 }
472 this._matchedSelectors = [];
473 this._passId++;
475 for (let i = 0; i < this._matchedRules.length; i++) {
476 let rule = this._matchedRules[i][0];
477 let status = this._matchedRules[i][1];
479 rule.selectors.forEach(function (aSelector) {
480 if (aSelector._matchId !== this._matchId &&
481 (aSelector.elementStyle ||
482 this.selectorMatchesElement(rule.domRule, aSelector.selectorIndex))) {
484 aSelector._matchId = this._matchId;
485 this._matchedSelectors.push([ aSelector, status ]);
486 if (aCallback) {
487 aCallback.call(aScope, aSelector, status);
488 }
489 }
490 }, this);
492 rule._passId = this._passId;
493 }
494 },
496 /**
497 * Check if the given selector matches the highlighted element or any of its
498 * parents.
499 *
500 * @private
501 * @param {DOMRule} domRule
502 * The DOM Rule containing the selector.
503 * @param {Number} idx
504 * The index of the selector within the DOMRule.
505 * @return {boolean}
506 * true if the given selector matches the highlighted element or any
507 * of its parents, otherwise false is returned.
508 */
509 selectorMatchesElement: function CL_selectorMatchesElement2(domRule, idx)
510 {
511 let element = this.viewedElement;
512 do {
513 if (domUtils.selectorMatchesElement(element, domRule, idx)) {
514 return true;
515 }
516 } while ((element = element.parentNode) &&
517 element.nodeType === Ci.nsIDOMNode.ELEMENT_NODE);
519 return false;
520 },
522 /**
523 * Check if the highlighted element or it's parents have matched selectors.
524 *
525 * @param {array} aProperties The list of properties you want to check if they
526 * have matched selectors or not.
527 * @return {object} An object that tells for each property if it has matched
528 * selectors or not. Object keys are property names and values are booleans.
529 */
530 hasMatchedSelectors: function CL_hasMatchedSelectors(aProperties)
531 {
532 if (!this._matchedRules) {
533 this._buildMatchedRules();
534 }
536 let result = {};
538 this._matchedRules.some(function(aValue) {
539 let rule = aValue[0];
540 let status = aValue[1];
541 aProperties = aProperties.filter(function(aProperty) {
542 // We just need to find if a rule has this property while it matches
543 // the viewedElement (or its parents).
544 if (rule.getPropertyValue(aProperty) &&
545 (status == CssLogic.STATUS.MATCHED ||
546 (status == CssLogic.STATUS.PARENT_MATCH &&
547 domUtils.isInheritedProperty(aProperty)))) {
548 result[aProperty] = true;
549 return false;
550 }
551 return true; // Keep the property for the next rule.
552 }.bind(this));
553 return aProperties.length == 0;
554 }, this);
556 return result;
557 },
559 /**
560 * Build the array of matched rules for the currently highlighted element.
561 * The array will hold rules that match the viewedElement and its parents.
562 *
563 * @private
564 */
565 _buildMatchedRules: function CL__buildMatchedRules()
566 {
567 let domRules;
568 let element = this.viewedElement;
569 let filter = this.sourceFilter;
570 let sheetIndex = 0;
572 this._matchId++;
573 this._passId++;
574 this._matchedRules = [];
576 if (!element) {
577 return;
578 }
580 do {
581 let status = this.viewedElement === element ?
582 CssLogic.STATUS.MATCHED : CssLogic.STATUS.PARENT_MATCH;
584 try {
585 domRules = domUtils.getCSSStyleRules(element);
586 } catch (ex) {
587 Services.console.
588 logStringMessage("CL__buildMatchedRules error: " + ex);
589 continue;
590 }
592 for (let i = 0, n = domRules.Count(); i < n; i++) {
593 let domRule = domRules.GetElementAt(i);
594 if (domRule.type !== Ci.nsIDOMCSSRule.STYLE_RULE) {
595 continue;
596 }
598 let sheet = this.getSheet(domRule.parentStyleSheet, -1);
599 if (sheet._passId !== this._passId) {
600 sheet.index = sheetIndex++;
601 sheet._passId = this._passId;
602 }
604 if (filter === CssLogic.FILTER.USER && !sheet.contentSheet) {
605 continue;
606 }
608 let rule = sheet.getRule(domRule);
609 if (rule._passId === this._passId) {
610 continue;
611 }
613 rule._matchId = this._matchId;
614 rule._passId = this._passId;
615 this._matchedRules.push([rule, status]);
616 }
619 // Add element.style information.
620 if (element.style && element.style.length > 0) {
621 let rule = new CssRule(null, { style: element.style }, element);
622 rule._matchId = this._matchId;
623 rule._passId = this._passId;
624 this._matchedRules.push([rule, status]);
625 }
626 } while ((element = element.parentNode) &&
627 element.nodeType === Ci.nsIDOMNode.ELEMENT_NODE);
628 },
630 /**
631 * Tells if the given DOM CSS object matches the current view media.
632 *
633 * @param {object} aDomObject The DOM CSS object to check.
634 * @return {boolean} True if the DOM CSS object matches the current view
635 * media, or false otherwise.
636 */
637 mediaMatches: function CL_mediaMatches(aDomObject)
638 {
639 let mediaText = aDomObject.media.mediaText;
640 return !mediaText || this.viewedDocument.defaultView.
641 matchMedia(mediaText).matches;
642 },
643 };
645 /**
646 * If the element has an id, return '#id'. Otherwise return 'tagname[n]' where
647 * n is the index of this element in its siblings.
648 * <p>A technically more 'correct' output from the no-id case might be:
649 * 'tagname:nth-of-type(n)' however this is unlikely to be more understood
650 * and it is longer.
651 *
652 * @param {nsIDOMElement} aElement the element for which you want the short name.
653 * @return {string} the string to be displayed for aElement.
654 */
655 CssLogic.getShortName = function CssLogic_getShortName(aElement)
656 {
657 if (!aElement) {
658 return "null";
659 }
660 if (aElement.id) {
661 return "#" + aElement.id;
662 }
663 let priorSiblings = 0;
664 let temp = aElement;
665 while (temp = temp.previousElementSibling) {
666 priorSiblings++;
667 }
668 return aElement.tagName + "[" + priorSiblings + "]";
669 };
671 /**
672 * Get an array of short names from the given element to document.body.
673 *
674 * @param {nsIDOMElement} aElement the element for which you want the array of
675 * short names.
676 * @return {array} The array of elements.
677 * <p>Each element is an object of the form:
678 * <ul>
679 * <li>{ display: "what to display for the given (parent) element",
680 * <li> element: referenceToTheElement }
681 * </ul>
682 */
683 CssLogic.getShortNamePath = function CssLogic_getShortNamePath(aElement)
684 {
685 let doc = aElement.ownerDocument;
686 let reply = [];
688 if (!aElement) {
689 return reply;
690 }
692 // We want to exclude nodes high up the tree (body/html) unless the user
693 // has selected that node, in which case we need to report something.
694 do {
695 reply.unshift({
696 display: CssLogic.getShortName(aElement),
697 element: aElement
698 });
699 aElement = aElement.parentNode;
700 } while (aElement && aElement != doc.body && aElement != doc.head && aElement != doc);
702 return reply;
703 };
705 /**
706 * Get a string list of selectors for a given DOMRule.
707 *
708 * @param {DOMRule} aDOMRule
709 * The DOMRule to parse.
710 * @return {Array}
711 * An array of string selectors.
712 */
713 CssLogic.getSelectors = function CssLogic_getSelectors(aDOMRule)
714 {
715 let selectors = [];
717 let len = domUtils.getSelectorCount(aDOMRule);
718 for (let i = 0; i < len; i++) {
719 let text = domUtils.getSelectorText(aDOMRule, i);
720 selectors.push(text);
721 }
722 return selectors;
723 }
725 /**
726 * Memonized lookup of a l10n string from a string bundle.
727 * @param {string} aName The key to lookup.
728 * @returns A localized version of the given key.
729 */
730 CssLogic.l10n = function(aName) CssLogic._strings.GetStringFromName(aName);
732 XPCOMUtils.defineLazyGetter(CssLogic, "_strings", function() Services.strings
733 .createBundle("chrome://global/locale/devtools/styleinspector.properties"));
735 /**
736 * Is the given property sheet a content stylesheet?
737 *
738 * @param {CSSStyleSheet} aSheet a stylesheet
739 * @return {boolean} true if the given stylesheet is a content stylesheet,
740 * false otherwise.
741 */
742 CssLogic.isContentStylesheet = function CssLogic_isContentStylesheet(aSheet)
743 {
744 // All sheets with owner nodes have been included by content.
745 if (aSheet.ownerNode) {
746 return true;
747 }
749 // If the sheet has a CSSImportRule we need to check the parent stylesheet.
750 if (aSheet.ownerRule instanceof Ci.nsIDOMCSSImportRule) {
751 return CssLogic.isContentStylesheet(aSheet.parentStyleSheet);
752 }
754 return false;
755 };
757 /**
758 * Get a source for a stylesheet, taking into account embedded stylesheets
759 * for which we need to use document.defaultView.location.href rather than
760 * sheet.href
761 *
762 * @param {CSSStyleSheet} aSheet the DOM object for the style sheet.
763 * @return {string} the address of the stylesheet.
764 */
765 CssLogic.href = function CssLogic_href(aSheet)
766 {
767 let href = aSheet.href;
768 if (!href) {
769 href = aSheet.ownerNode.ownerDocument.location;
770 }
772 return href;
773 };
775 /**
776 * Return a shortened version of a style sheet's source.
777 *
778 * @param {CSSStyleSheet} aSheet the DOM object for the style sheet.
779 */
780 CssLogic.shortSource = function CssLogic_shortSource(aSheet)
781 {
782 // Use a string like "inline" if there is no source href
783 if (!aSheet || !aSheet.href) {
784 return CssLogic.l10n("rule.sourceInline");
785 }
787 // We try, in turn, the filename, filePath, query string, whole thing
788 let url = {};
789 try {
790 url = Services.io.newURI(aSheet.href, null, null);
791 url = url.QueryInterface(Ci.nsIURL);
792 } catch (ex) {
793 // Some UA-provided stylesheets are not valid URLs.
794 }
796 if (url.fileName) {
797 return url.fileName;
798 }
800 if (url.filePath) {
801 return url.filePath;
802 }
804 if (url.query) {
805 return url.query;
806 }
808 let dataUrl = aSheet.href.match(/^(data:[^,]*),/);
809 return dataUrl ? dataUrl[1] : aSheet.href;
810 }
812 /**
813 * Extract the background image URL (if any) from a property value.
814 * Used, for example, for the preview tooltip in the rule view and
815 * computed view.
816 *
817 * @param {String} aProperty
818 * @param {String} aSheetHref
819 * @return {string} a image URL
820 */
821 CssLogic.getBackgroundImageUriFromProperty = function(aProperty, aSheetHref) {
822 let startToken = "url(", start = aProperty.indexOf(startToken), end;
823 if (start === -1) {
824 return null;
825 }
827 aProperty = aProperty.substring(start + startToken.length).trim();
828 let quote = aProperty.substring(0, 1);
829 if (quote === "'" || quote === '"') {
830 end = aProperty.search(new RegExp(quote + "\\s*\\)"));
831 start = 1;
832 } else {
833 end = aProperty.indexOf(")");
834 start = 0;
835 }
837 let uri = aProperty.substring(start, end).trim();
838 if (aSheetHref) {
839 let IOService = Cc["@mozilla.org/network/io-service;1"]
840 .getService(Ci.nsIIOService);
841 let sheetUri = IOService.newURI(aSheetHref, null, null);
842 uri = sheetUri.resolve(uri);
843 }
845 return uri;
846 }
848 /**
849 * Find the position of [element] in [nodeList].
850 * @returns an index of the match, or -1 if there is no match
851 */
852 function positionInNodeList(element, nodeList) {
853 for (var i = 0; i < nodeList.length; i++) {
854 if (element === nodeList[i]) {
855 return i;
856 }
857 }
858 return -1;
859 }
861 /**
862 * Find a unique CSS selector for a given element
863 * @returns a string such that ele.ownerDocument.querySelector(reply) === ele
864 * and ele.ownerDocument.querySelectorAll(reply).length === 1
865 */
866 CssLogic.findCssSelector = function CssLogic_findCssSelector(ele) {
867 var document = ele.ownerDocument;
868 if (ele.id && document.getElementById(ele.id) === ele) {
869 return '#' + ele.id;
870 }
872 // Inherently unique by tag name
873 var tagName = ele.tagName.toLowerCase();
874 if (tagName === 'html') {
875 return 'html';
876 }
877 if (tagName === 'head') {
878 return 'head';
879 }
880 if (tagName === 'body') {
881 return 'body';
882 }
884 if (ele.parentNode == null) {
885 console.log('danger: ' + tagName);
886 }
888 // We might be able to find a unique class name
889 var selector, index, matches;
890 if (ele.classList.length > 0) {
891 for (var i = 0; i < ele.classList.length; i++) {
892 // Is this className unique by itself?
893 selector = '.' + ele.classList.item(i);
894 matches = document.querySelectorAll(selector);
895 if (matches.length === 1) {
896 return selector;
897 }
898 // Maybe it's unique with a tag name?
899 selector = tagName + selector;
900 matches = document.querySelectorAll(selector);
901 if (matches.length === 1) {
902 return selector;
903 }
904 // Maybe it's unique using a tag name and nth-child
905 index = positionInNodeList(ele, ele.parentNode.children) + 1;
906 selector = selector + ':nth-child(' + index + ')';
907 matches = document.querySelectorAll(selector);
908 if (matches.length === 1) {
909 return selector;
910 }
911 }
912 }
914 // So we can be unique w.r.t. our parent, and use recursion
915 index = positionInNodeList(ele, ele.parentNode.children) + 1;
916 selector = CssLogic_findCssSelector(ele.parentNode) + ' > ' +
917 tagName + ':nth-child(' + index + ')';
919 return selector;
920 };
922 /**
923 * A safe way to access cached bits of information about a stylesheet.
924 *
925 * @constructor
926 * @param {CssLogic} aCssLogic pointer to the CssLogic instance working with
927 * this CssSheet object.
928 * @param {CSSStyleSheet} aDomSheet reference to a DOM CSSStyleSheet object.
929 * @param {number} aIndex tells the index/position of the stylesheet within the
930 * main document.
931 */
932 function CssSheet(aCssLogic, aDomSheet, aIndex)
933 {
934 this._cssLogic = aCssLogic;
935 this.domSheet = aDomSheet;
936 this.index = this.contentSheet ? aIndex : -100 * aIndex;
938 // Cache of the sheets href. Cached by the getter.
939 this._href = null;
940 // Short version of href for use in select boxes etc. Cached by getter.
941 this._shortSource = null;
943 // null for uncached.
944 this._sheetAllowed = null;
946 // Cached CssRules from the given stylesheet.
947 this._rules = {};
949 this._ruleCount = -1;
950 }
952 CssSheet.prototype = {
953 _passId: null,
954 _contentSheet: null,
955 _mediaMatches: null,
957 /**
958 * Tells if the stylesheet is provided by the browser or not.
959 *
960 * @return {boolean} false if this is a browser-provided stylesheet, or true
961 * otherwise.
962 */
963 get contentSheet()
964 {
965 if (this._contentSheet === null) {
966 this._contentSheet = CssLogic.isContentStylesheet(this.domSheet);
967 }
968 return this._contentSheet;
969 },
971 /**
972 * Tells if the stylesheet is disabled or not.
973 * @return {boolean} true if this stylesheet is disabled, or false otherwise.
974 */
975 get disabled()
976 {
977 return this.domSheet.disabled;
978 },
980 /**
981 * Tells if the stylesheet matches the current browser view media.
982 * @return {boolean} true if this stylesheet matches the current browser view
983 * media, or false otherwise.
984 */
985 get mediaMatches()
986 {
987 if (this._mediaMatches === null) {
988 this._mediaMatches = this._cssLogic.mediaMatches(this.domSheet);
989 }
990 return this._mediaMatches;
991 },
993 /**
994 * Get a source for a stylesheet, using CssLogic.href
995 *
996 * @return {string} the address of the stylesheet.
997 */
998 get href()
999 {
1000 if (this._href) {
1001 return this._href;
1002 }
1004 this._href = CssLogic.href(this.domSheet);
1005 return this._href;
1006 },
1008 /**
1009 * Create a shorthand version of the href of a stylesheet.
1010 *
1011 * @return {string} the shorthand source of the stylesheet.
1012 */
1013 get shortSource()
1014 {
1015 if (this._shortSource) {
1016 return this._shortSource;
1017 }
1019 this._shortSource = CssLogic.shortSource(this.domSheet);
1020 return this._shortSource;
1021 },
1023 /**
1024 * Tells if the sheet is allowed or not by the current CssLogic.sourceFilter.
1025 *
1026 * @return {boolean} true if the stylesheet is allowed by the sourceFilter, or
1027 * false otherwise.
1028 */
1029 get sheetAllowed()
1030 {
1031 if (this._sheetAllowed !== null) {
1032 return this._sheetAllowed;
1033 }
1035 this._sheetAllowed = true;
1037 let filter = this._cssLogic.sourceFilter;
1038 if (filter === CssLogic.FILTER.USER && !this.contentSheet) {
1039 this._sheetAllowed = false;
1040 }
1041 if (filter !== CssLogic.FILTER.USER && filter !== CssLogic.FILTER.UA) {
1042 this._sheetAllowed = (filter === this.href);
1043 }
1045 return this._sheetAllowed;
1046 },
1048 /**
1049 * Retrieve the number of rules in this stylesheet.
1050 *
1051 * @return {number} the number of nsIDOMCSSRule objects in this stylesheet.
1052 */
1053 get ruleCount()
1054 {
1055 return this._ruleCount > -1 ?
1056 this._ruleCount :
1057 this.domSheet.cssRules.length;
1058 },
1060 /**
1061 * Retrieve a CssRule object for the given CSSStyleRule. The CssRule object is
1062 * cached, such that subsequent retrievals return the same CssRule object for
1063 * the same CSSStyleRule object.
1064 *
1065 * @param {CSSStyleRule} aDomRule the CSSStyleRule object for which you want a
1066 * CssRule object.
1067 * @return {CssRule} the cached CssRule object for the given CSSStyleRule
1068 * object.
1069 */
1070 getRule: function CssSheet_getRule(aDomRule)
1071 {
1072 let cacheId = aDomRule.type + aDomRule.selectorText;
1074 let rule = null;
1075 let ruleFound = false;
1077 if (cacheId in this._rules) {
1078 for (let i = 0, rulesLen = this._rules[cacheId].length; i < rulesLen; i++) {
1079 rule = this._rules[cacheId][i];
1080 if (rule.domRule === aDomRule) {
1081 ruleFound = true;
1082 break;
1083 }
1084 }
1085 }
1087 if (!ruleFound) {
1088 if (!(cacheId in this._rules)) {
1089 this._rules[cacheId] = [];
1090 }
1092 rule = new CssRule(this, aDomRule);
1093 this._rules[cacheId].push(rule);
1094 }
1096 return rule;
1097 },
1099 /**
1100 * Process each rule in this stylesheet using your callback function. Your
1101 * function receives one argument: the CssRule object for each CSSStyleRule
1102 * inside the stylesheet.
1103 *
1104 * Note that this method also iterates through @media rules inside the
1105 * stylesheet.
1106 *
1107 * @param {function} aCallback the function you want to execute for each of
1108 * the style rules.
1109 * @param {object} aScope the scope you want for the callback function. aScope
1110 * will be the this object when aCallback executes.
1111 */
1112 forEachRule: function CssSheet_forEachRule(aCallback, aScope)
1113 {
1114 let ruleCount = 0;
1115 let domRules = this.domSheet.cssRules;
1117 function _iterator(aDomRule) {
1118 if (aDomRule.type == Ci.nsIDOMCSSRule.STYLE_RULE) {
1119 aCallback.call(aScope, this.getRule(aDomRule));
1120 ruleCount++;
1121 } else if (aDomRule.type == Ci.nsIDOMCSSRule.MEDIA_RULE &&
1122 aDomRule.cssRules && this._cssLogic.mediaMatches(aDomRule)) {
1123 Array.prototype.forEach.call(aDomRule.cssRules, _iterator, this);
1124 }
1125 }
1127 Array.prototype.forEach.call(domRules, _iterator, this);
1129 this._ruleCount = ruleCount;
1130 },
1132 /**
1133 * Process *some* rules in this stylesheet using your callback function. Your
1134 * function receives one argument: the CssRule object for each CSSStyleRule
1135 * inside the stylesheet. In order to stop processing the callback function
1136 * needs to return a value.
1137 *
1138 * Note that this method also iterates through @media rules inside the
1139 * stylesheet.
1140 *
1141 * @param {function} aCallback the function you want to execute for each of
1142 * the style rules.
1143 * @param {object} aScope the scope you want for the callback function. aScope
1144 * will be the this object when aCallback executes.
1145 * @return {Boolean} true if aCallback returns true during any iteration,
1146 * otherwise false is returned.
1147 */
1148 forSomeRules: function CssSheet_forSomeRules(aCallback, aScope)
1149 {
1150 let domRules = this.domSheet.cssRules;
1151 function _iterator(aDomRule) {
1152 if (aDomRule.type == Ci.nsIDOMCSSRule.STYLE_RULE) {
1153 return aCallback.call(aScope, this.getRule(aDomRule));
1154 } else if (aDomRule.type == Ci.nsIDOMCSSRule.MEDIA_RULE &&
1155 aDomRule.cssRules && this._cssLogic.mediaMatches(aDomRule)) {
1156 return Array.prototype.some.call(aDomRule.cssRules, _iterator, this);
1157 }
1158 }
1159 return Array.prototype.some.call(domRules, _iterator, this);
1160 },
1162 toString: function CssSheet_toString()
1163 {
1164 return "CssSheet[" + this.shortSource + "]";
1165 }
1166 };
1168 /**
1169 * Information about a single CSSStyleRule.
1170 *
1171 * @param {CSSSheet|null} aCssSheet the CssSheet object of the stylesheet that
1172 * holds the CSSStyleRule. If the rule comes from element.style, set this
1173 * argument to null.
1174 * @param {CSSStyleRule|object} aDomRule the DOM CSSStyleRule for which you want
1175 * to cache data. If the rule comes from element.style, then provide
1176 * an object of the form: {style: element.style}.
1177 * @param {Element} [aElement] If the rule comes from element.style, then this
1178 * argument must point to the element.
1179 * @constructor
1180 */
1181 function CssRule(aCssSheet, aDomRule, aElement)
1182 {
1183 this._cssSheet = aCssSheet;
1184 this.domRule = aDomRule;
1186 let parentRule = aDomRule.parentRule;
1187 if (parentRule && parentRule.type == Ci.nsIDOMCSSRule.MEDIA_RULE) {
1188 this.mediaText = parentRule.media.mediaText;
1189 }
1191 if (this._cssSheet) {
1192 // parse domRule.selectorText on call to this.selectors
1193 this._selectors = null;
1194 this.line = domUtils.getRuleLine(this.domRule);
1195 this.source = this._cssSheet.shortSource + ":" + this.line;
1196 if (this.mediaText) {
1197 this.source += " @media " + this.mediaText;
1198 }
1199 this.href = this._cssSheet.href;
1200 this.contentRule = this._cssSheet.contentSheet;
1201 } else if (aElement) {
1202 this._selectors = [ new CssSelector(this, "@element.style", 0) ];
1203 this.line = -1;
1204 this.source = CssLogic.l10n("rule.sourceElement");
1205 this.href = "#";
1206 this.contentRule = true;
1207 this.sourceElement = aElement;
1208 }
1209 }
1211 CssRule.prototype = {
1212 _passId: null,
1214 mediaText: "",
1216 get isMediaRule()
1217 {
1218 return !!this.mediaText;
1219 },
1221 /**
1222 * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter.
1223 *
1224 * @return {boolean} true if the parent stylesheet is allowed by the current
1225 * sourceFilter, or false otherwise.
1226 */
1227 get sheetAllowed()
1228 {
1229 return this._cssSheet ? this._cssSheet.sheetAllowed : true;
1230 },
1232 /**
1233 * Retrieve the parent stylesheet index/position in the viewed document.
1234 *
1235 * @return {number} the parent stylesheet index/position in the viewed
1236 * document.
1237 */
1238 get sheetIndex()
1239 {
1240 return this._cssSheet ? this._cssSheet.index : 0;
1241 },
1243 /**
1244 * Retrieve the style property value from the current CSSStyleRule.
1245 *
1246 * @param {string} aProperty the CSS property name for which you want the
1247 * value.
1248 * @return {string} the property value.
1249 */
1250 getPropertyValue: function(aProperty)
1251 {
1252 return this.domRule.style.getPropertyValue(aProperty);
1253 },
1255 /**
1256 * Retrieve the style property priority from the current CSSStyleRule.
1257 *
1258 * @param {string} aProperty the CSS property name for which you want the
1259 * priority.
1260 * @return {string} the property priority.
1261 */
1262 getPropertyPriority: function(aProperty)
1263 {
1264 return this.domRule.style.getPropertyPriority(aProperty);
1265 },
1267 /**
1268 * Retrieve the list of CssSelector objects for each of the parsed selectors
1269 * of the current CSSStyleRule.
1270 *
1271 * @return {array} the array hold the CssSelector objects.
1272 */
1273 get selectors()
1274 {
1275 if (this._selectors) {
1276 return this._selectors;
1277 }
1279 // Parse the CSSStyleRule.selectorText string.
1280 this._selectors = [];
1282 if (!this.domRule.selectorText) {
1283 return this._selectors;
1284 }
1286 let selectors = CssLogic.getSelectors(this.domRule);
1288 for (let i = 0, len = selectors.length; i < len; i++) {
1289 this._selectors.push(new CssSelector(this, selectors[i], i));
1290 }
1292 return this._selectors;
1293 },
1295 toString: function CssRule_toString()
1296 {
1297 return "[CssRule " + this.domRule.selectorText + "]";
1298 },
1299 };
1301 /**
1302 * The CSS selector class allows us to document the ranking of various CSS
1303 * selectors.
1304 *
1305 * @constructor
1306 * @param {CssRule} aCssRule the CssRule instance from where the selector comes.
1307 * @param {string} aSelector The selector that we wish to investigate.
1308 * @param {Number} aIndex The index of the selector within it's rule.
1309 */
1310 function CssSelector(aCssRule, aSelector, aIndex)
1311 {
1312 this.cssRule = aCssRule;
1313 this.text = aSelector;
1314 this.elementStyle = this.text == "@element.style";
1315 this._specificity = null;
1316 this.selectorIndex = aIndex;
1317 }
1319 exports.CssSelector = CssSelector;
1321 CssSelector.prototype = {
1322 _matchId: null,
1324 /**
1325 * Retrieve the CssSelector source, which is the source of the CssSheet owning
1326 * the selector.
1327 *
1328 * @return {string} the selector source.
1329 */
1330 get source()
1331 {
1332 return this.cssRule.source;
1333 },
1335 /**
1336 * Retrieve the CssSelector source element, which is the source of the CssRule
1337 * owning the selector. This is only available when the CssSelector comes from
1338 * an element.style.
1339 *
1340 * @return {string} the source element selector.
1341 */
1342 get sourceElement()
1343 {
1344 return this.cssRule.sourceElement;
1345 },
1347 /**
1348 * Retrieve the address of the CssSelector. This points to the address of the
1349 * CssSheet owning this selector.
1350 *
1351 * @return {string} the address of the CssSelector.
1352 */
1353 get href()
1354 {
1355 return this.cssRule.href;
1356 },
1358 /**
1359 * Check if the selector comes from a browser-provided stylesheet.
1360 *
1361 * @return {boolean} true if the selector comes from a content-provided
1362 * stylesheet, or false otherwise.
1363 */
1364 get contentRule()
1365 {
1366 return this.cssRule.contentRule;
1367 },
1369 /**
1370 * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter.
1371 *
1372 * @return {boolean} true if the parent stylesheet is allowed by the current
1373 * sourceFilter, or false otherwise.
1374 */
1375 get sheetAllowed()
1376 {
1377 return this.cssRule.sheetAllowed;
1378 },
1380 /**
1381 * Retrieve the parent stylesheet index/position in the viewed document.
1382 *
1383 * @return {number} the parent stylesheet index/position in the viewed
1384 * document.
1385 */
1386 get sheetIndex()
1387 {
1388 return this.cssRule.sheetIndex;
1389 },
1391 /**
1392 * Retrieve the line of the parent CSSStyleRule in the parent CSSStyleSheet.
1393 *
1394 * @return {number} the line of the parent CSSStyleRule in the parent
1395 * stylesheet.
1396 */
1397 get ruleLine()
1398 {
1399 return this.cssRule.line;
1400 },
1402 /**
1403 * Retrieve the pseudo-elements that we support. This list should match the
1404 * elements specified in layout/style/nsCSSPseudoElementList.h
1405 */
1406 get pseudoElements()
1407 {
1408 if (!CssSelector._pseudoElements) {
1409 let pseudos = CssSelector._pseudoElements = new Set();
1410 pseudos.add("after");
1411 pseudos.add("before");
1412 pseudos.add("first-letter");
1413 pseudos.add("first-line");
1414 pseudos.add("selection");
1415 pseudos.add("-moz-color-swatch");
1416 pseudos.add("-moz-focus-inner");
1417 pseudos.add("-moz-focus-outer");
1418 pseudos.add("-moz-list-bullet");
1419 pseudos.add("-moz-list-number");
1420 pseudos.add("-moz-math-anonymous");
1421 pseudos.add("-moz-math-stretchy");
1422 pseudos.add("-moz-progress-bar");
1423 pseudos.add("-moz-selection");
1424 }
1425 return CssSelector._pseudoElements;
1426 },
1428 /**
1429 * Retrieve specificity information for the current selector.
1430 *
1431 * @see http://www.w3.org/TR/css3-selectors/#specificity
1432 * @see http://www.w3.org/TR/CSS2/selector.html
1433 *
1434 * @return {Number} The selector's specificity.
1435 */
1436 get specificity()
1437 {
1438 if (this._specificity) {
1439 return this._specificity;
1440 }
1442 this._specificity = domUtils.getSpecificity(this.cssRule.domRule,
1443 this.selectorIndex);
1445 return this._specificity;
1446 },
1448 toString: function CssSelector_toString()
1449 {
1450 return this.text;
1451 },
1452 };
1454 /**
1455 * A cache of information about the matched rules, selectors and values attached
1456 * to a CSS property, for the highlighted element.
1457 *
1458 * The heart of the CssPropertyInfo object is the _findMatchedSelectors()
1459 * method. This are invoked when the PropertyView tries to access the
1460 * .matchedSelectors array.
1461 * Results are cached, for later reuse.
1462 *
1463 * @param {CssLogic} aCssLogic Reference to the parent CssLogic instance
1464 * @param {string} aProperty The CSS property we are gathering information for
1465 * @constructor
1466 */
1467 function CssPropertyInfo(aCssLogic, aProperty)
1468 {
1469 this._cssLogic = aCssLogic;
1470 this.property = aProperty;
1471 this._value = "";
1473 // The number of matched rules holding the this.property style property.
1474 // Additionally, only rules that come from allowed stylesheets are counted.
1475 this._matchedRuleCount = 0;
1477 // An array holding CssSelectorInfo objects for each of the matched selectors
1478 // that are inside a CSS rule. Only rules that hold the this.property are
1479 // counted. This includes rules that come from filtered stylesheets (those
1480 // that have sheetAllowed = false).
1481 this._matchedSelectors = null;
1482 }
1484 CssPropertyInfo.prototype = {
1485 /**
1486 * Retrieve the computed style value for the current property, for the
1487 * highlighted element.
1488 *
1489 * @return {string} the computed style value for the current property, for the
1490 * highlighted element.
1491 */
1492 get value()
1493 {
1494 if (!this._value && this._cssLogic._computedStyle) {
1495 try {
1496 this._value = this._cssLogic._computedStyle.getPropertyValue(this.property);
1497 } catch (ex) {
1498 Services.console.logStringMessage('Error reading computed style for ' +
1499 this.property);
1500 Services.console.logStringMessage(ex);
1501 }
1502 }
1503 return this._value;
1504 },
1506 /**
1507 * Retrieve the number of matched rules holding the this.property style
1508 * property. Only rules that come from allowed stylesheets are counted.
1509 *
1510 * @return {number} the number of matched rules.
1511 */
1512 get matchedRuleCount()
1513 {
1514 if (!this._matchedSelectors) {
1515 this._findMatchedSelectors();
1516 } else if (this.needRefilter) {
1517 this._refilterSelectors();
1518 }
1520 return this._matchedRuleCount;
1521 },
1523 /**
1524 * Retrieve the array holding CssSelectorInfo objects for each of the matched
1525 * selectors, from each of the matched rules. Only selectors coming from
1526 * allowed stylesheets are included in the array.
1527 *
1528 * @return {array} the list of CssSelectorInfo objects of selectors that match
1529 * the highlighted element and its parents.
1530 */
1531 get matchedSelectors()
1532 {
1533 if (!this._matchedSelectors) {
1534 this._findMatchedSelectors();
1535 } else if (this.needRefilter) {
1536 this._refilterSelectors();
1537 }
1539 return this._matchedSelectors;
1540 },
1542 /**
1543 * Find the selectors that match the highlighted element and its parents.
1544 * Uses CssLogic.processMatchedSelectors() to find the matched selectors,
1545 * passing in a reference to CssPropertyInfo._processMatchedSelector() to
1546 * create CssSelectorInfo objects, which we then sort
1547 * @private
1548 */
1549 _findMatchedSelectors: function CssPropertyInfo_findMatchedSelectors()
1550 {
1551 this._matchedSelectors = [];
1552 this._matchedRuleCount = 0;
1553 this.needRefilter = false;
1555 this._cssLogic.processMatchedSelectors(this._processMatchedSelector, this);
1557 // Sort the selectors by how well they match the given element.
1558 this._matchedSelectors.sort(function(aSelectorInfo1, aSelectorInfo2) {
1559 if (aSelectorInfo1.status > aSelectorInfo2.status) {
1560 return -1;
1561 } else if (aSelectorInfo2.status > aSelectorInfo1.status) {
1562 return 1;
1563 } else {
1564 return aSelectorInfo1.compareTo(aSelectorInfo2);
1565 }
1566 });
1568 // Now we know which of the matches is best, we can mark it BEST_MATCH.
1569 if (this._matchedSelectors.length > 0 &&
1570 this._matchedSelectors[0].status > CssLogic.STATUS.UNMATCHED) {
1571 this._matchedSelectors[0].status = CssLogic.STATUS.BEST;
1572 }
1573 },
1575 /**
1576 * Process a matched CssSelector object.
1577 *
1578 * @private
1579 * @param {CssSelector} aSelector the matched CssSelector object.
1580 * @param {CssLogic.STATUS} aStatus the CssSelector match status.
1581 */
1582 _processMatchedSelector: function CssPropertyInfo_processMatchedSelector(aSelector, aStatus)
1583 {
1584 let cssRule = aSelector.cssRule;
1585 let value = cssRule.getPropertyValue(this.property);
1586 if (value &&
1587 (aStatus == CssLogic.STATUS.MATCHED ||
1588 (aStatus == CssLogic.STATUS.PARENT_MATCH &&
1589 domUtils.isInheritedProperty(this.property)))) {
1590 let selectorInfo = new CssSelectorInfo(aSelector, this.property, value,
1591 aStatus);
1592 this._matchedSelectors.push(selectorInfo);
1593 if (this._cssLogic._passId !== cssRule._passId && cssRule.sheetAllowed) {
1594 this._matchedRuleCount++;
1595 }
1596 }
1597 },
1599 /**
1600 * Refilter the matched selectors array when the CssLogic.sourceFilter
1601 * changes. This allows for quick filter changes.
1602 * @private
1603 */
1604 _refilterSelectors: function CssPropertyInfo_refilterSelectors()
1605 {
1606 let passId = ++this._cssLogic._passId;
1607 let ruleCount = 0;
1609 let iterator = function(aSelectorInfo) {
1610 let cssRule = aSelectorInfo.selector.cssRule;
1611 if (cssRule._passId != passId) {
1612 if (cssRule.sheetAllowed) {
1613 ruleCount++;
1614 }
1615 cssRule._passId = passId;
1616 }
1617 };
1619 if (this._matchedSelectors) {
1620 this._matchedSelectors.forEach(iterator);
1621 this._matchedRuleCount = ruleCount;
1622 }
1624 this.needRefilter = false;
1625 },
1627 toString: function CssPropertyInfo_toString()
1628 {
1629 return "CssPropertyInfo[" + this.property + "]";
1630 },
1631 };
1633 /**
1634 * A class that holds information about a given CssSelector object.
1635 *
1636 * Instances of this class are given to CssHtmlTree in the array of matched
1637 * selectors. Each such object represents a displayable row in the PropertyView
1638 * objects. The information given by this object blends data coming from the
1639 * CssSheet, CssRule and from the CssSelector that own this object.
1640 *
1641 * @param {CssSelector} aSelector The CssSelector object for which to present information.
1642 * @param {string} aProperty The property for which information should be retrieved.
1643 * @param {string} aValue The property value from the CssRule that owns the selector.
1644 * @param {CssLogic.STATUS} aStatus The selector match status.
1645 * @constructor
1646 */
1647 function CssSelectorInfo(aSelector, aProperty, aValue, aStatus)
1648 {
1649 this.selector = aSelector;
1650 this.property = aProperty;
1651 this.status = aStatus;
1652 this.value = aValue;
1653 let priority = this.selector.cssRule.getPropertyPriority(this.property);
1654 this.important = (priority === "important");
1655 }
1657 CssSelectorInfo.prototype = {
1658 /**
1659 * Retrieve the CssSelector source, which is the source of the CssSheet owning
1660 * the selector.
1661 *
1662 * @return {string} the selector source.
1663 */
1664 get source()
1665 {
1666 return this.selector.source;
1667 },
1669 /**
1670 * Retrieve the CssSelector source element, which is the source of the CssRule
1671 * owning the selector. This is only available when the CssSelector comes from
1672 * an element.style.
1673 *
1674 * @return {string} the source element selector.
1675 */
1676 get sourceElement()
1677 {
1678 return this.selector.sourceElement;
1679 },
1681 /**
1682 * Retrieve the address of the CssSelector. This points to the address of the
1683 * CssSheet owning this selector.
1684 *
1685 * @return {string} the address of the CssSelector.
1686 */
1687 get href()
1688 {
1689 return this.selector.href;
1690 },
1692 /**
1693 * Check if the CssSelector comes from element.style or not.
1694 *
1695 * @return {boolean} true if the CssSelector comes from element.style, or
1696 * false otherwise.
1697 */
1698 get elementStyle()
1699 {
1700 return this.selector.elementStyle;
1701 },
1703 /**
1704 * Retrieve specificity information for the current selector.
1705 *
1706 * @return {object} an object holding specificity information for the current
1707 * selector.
1708 */
1709 get specificity()
1710 {
1711 return this.selector.specificity;
1712 },
1714 /**
1715 * Retrieve the parent stylesheet index/position in the viewed document.
1716 *
1717 * @return {number} the parent stylesheet index/position in the viewed
1718 * document.
1719 */
1720 get sheetIndex()
1721 {
1722 return this.selector.sheetIndex;
1723 },
1725 /**
1726 * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter.
1727 *
1728 * @return {boolean} true if the parent stylesheet is allowed by the current
1729 * sourceFilter, or false otherwise.
1730 */
1731 get sheetAllowed()
1732 {
1733 return this.selector.sheetAllowed;
1734 },
1736 /**
1737 * Retrieve the line of the parent CSSStyleRule in the parent CSSStyleSheet.
1738 *
1739 * @return {number} the line of the parent CSSStyleRule in the parent
1740 * stylesheet.
1741 */
1742 get ruleLine()
1743 {
1744 return this.selector.ruleLine;
1745 },
1747 /**
1748 * Check if the selector comes from a browser-provided stylesheet.
1749 *
1750 * @return {boolean} true if the selector comes from a browser-provided
1751 * stylesheet, or false otherwise.
1752 */
1753 get contentRule()
1754 {
1755 return this.selector.contentRule;
1756 },
1758 /**
1759 * Compare the current CssSelectorInfo instance to another instance, based on
1760 * specificity information.
1761 *
1762 * @param {CssSelectorInfo} aThat The instance to compare ourselves against.
1763 * @return number -1, 0, 1 depending on how aThat compares with this.
1764 */
1765 compareTo: function CssSelectorInfo_compareTo(aThat)
1766 {
1767 if (!this.contentRule && aThat.contentRule) return 1;
1768 if (this.contentRule && !aThat.contentRule) return -1;
1770 if (this.elementStyle && !aThat.elementStyle) {
1771 if (!this.important && aThat.important) return 1;
1772 else return -1;
1773 }
1775 if (!this.elementStyle && aThat.elementStyle) {
1776 if (this.important && !aThat.important) return -1;
1777 else return 1;
1778 }
1780 if (this.important && !aThat.important) return -1;
1781 if (aThat.important && !this.important) return 1;
1783 if (this.specificity > aThat.specificity) return -1;
1784 if (aThat.specificity > this.specificity) return 1;
1786 if (this.sheetIndex > aThat.sheetIndex) return -1;
1787 if (aThat.sheetIndex > this.sheetIndex) return 1;
1789 if (this.ruleLine > aThat.ruleLine) return -1;
1790 if (aThat.ruleLine > this.ruleLine) return 1;
1792 return 0;
1793 },
1795 toString: function CssSelectorInfo_toString()
1796 {
1797 return this.selector + " -> " + this.value;
1798 },
1799 };
1801 XPCOMUtils.defineLazyGetter(this, "domUtils", function() {
1802 return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
1803 });