3
\$\begingroup\$

I have written the following Tampermonkey/Greasemonkey script. I am trying to obtain a list of all CSS rules for a page; across all stylesheets. As of now, I am just iterating over everything. Is there a faster way to obtain the rules? Also, I feel that there is a more efficient way of converting the CSS text to an object, but right now, all I can think to do is to tokenize the property-value pairs by spliting.

// ==UserScript==
// @name         Dump CSS Rules
// @namespace    example.css.rules
// @version      0.1
// @description  Print out all the CSS rules
// @author       You
// @grant        GM_log
// ==/UserScript==
(function() {
    /*jslint browser: true*/ /*global  $*/
    'use strict';
    function getRules() {
        var rulesList = [];
        searchStyleSheets(rulesList);
        return rulesList;
    }
    function searchStyleSheets(rulesList) {
        var styleSheets = document.styleSheets || [];
        for (var i = 0; i < styleSheets.length; i++) {
            // CSSStyleSheet
            searchRulesList(styleSheets[i], undefined, undefined, undefined, rulesList);
        }
    }
    function searchRulesList(styleSheet, href, media, rules, rulesList) {
        // CSSRuleList
        var rules = rules || styleSheet.rules || styleSheet.cssRules || [];
        var href = href || styleSheet.href || '';
        var media = media || styleSheet.media.mediaText || '';
        for (var i = 0; i < rules.length; i++) {
            addRule(rules[i], href, media, rulesList);
        }
    }
    function addRule(rule, href, media, rulesList) {
        // CSSMediaRule
        if (rule.type === 4) {
            var rules = rule.rules || rule.cssRules || [];
            var href = rule.parentStyleSheet.href || '';
            var media = rule.media.mediaText || '';
            searchRulesList(rule, href, media, rules, rulesList);
        } else {
            // CSSStyleRule
            rulesList.push({
                'selector' : rule.selectorText,
                'css'      : cssTextToObject(getCssText(rule), rule),
                'href'     : href,
                //'self'   : rule,
                'media'    : media,
                'type'     : rule.type
            });
        }
    }
    function getCssText(rule) {
        return (function(text) {
            return text.substr(text.indexOf('{')) ;
        }(rule.cssText ? rule.cssText : rule.style.cssText));
    }
    function cssTextToObject(cssText, rule) {
        var m = cssText.match(/^\{(?:[ ]*)?(.*)(?:[ ]*)?\}$/);
        if (m != null) {
            var obj = {};
            if (m.length === 2) {
                var properties = m[1].split(/(?:[ ]*)?[;](?:[ ]*)?/);
                for (var i = 0; i < properties.length; i++) {
                    var pair = properties[i].split(/(?:[ ]*)?[:](?:[ ]*)?/);
                    if (pair.length === 2) {
                        obj[pair[0]] = pair[1];
                    }
                }
            }
            return obj;
        } else {
            return cssText;
        }
    }
    GM_log(JSON.stringify(getRules(), undefined, '    '));
}())

Here are the rules for this page:

[
    {
        "selector": ".MathJax_Hover_Frame",
        "css": {
            "border-radius": "0.25em",
            "box-shadow": "rgb(136, 51, 170) 0px 0px 15px",
            "-webkit-box-shadow": "rgb(136, 51, 170) 0px 0px 15px",
            "display": "inline-block",
            "position": "absolute",
            "border": "1px solid rgb(170, 102, 221) !important"
        },
        "href": "",
        "media": "",
        "type": 1
    },
    {
        "selector": ".MathJax_Hover_Arrow",
        "css": {
            "position": "absolute",
            "width": "15px",
            "height": "11px",
            "cursor": "pointer"
        },
        "href": "",
        "media": "",
        "type": 1
    },
    {
        "selector": "#MathJax_About",
        "css": {
            "position": "fixed",
            "left": "50%",
            "width": "auto",
            "text-align": "center",
            "border": "3px outset",
            "padding": "1em 2em",
            "color": "black",
            "cursor": "default",
            "font-family": "message-box",
            "font-size": "120%",
            "font-style": "normal",
            "text-indent": "0px",
            "text-transform": "none",
            "line-height": "normal",
            "letter-spacing": "normal",
            "word-spacing": "normal",
            "word-wrap": "normal",
            "white-space": "nowrap",
            "float": "none",
            "z-index": "201",
            "border-radius": "15px",
            "box-shadow": "rgb(128, 128, 128) 0px 10px 20px",
            "-webkit-box-shadow": "rgb(128, 128, 128) 0px 10px 20px",
            "background-color": "rgb(221, 221, 221)"
        },
        "href": "",
        "media": "",
        "type": 1
    },
    {
        "selector": ".MathJax_Menu",
        "css": {
            "position": "absolute",
            "color": "black",
            "width": "auto",
            "padding": "2px",
            "border": "1px solid rgb(204, 204, 204)",
            "margin": "0px",
            "cursor": "default",
            "font-style": "normal",
            "font-weight": "normal",
            "font-size": "12px",
            "font-family": "'Segoe UI'",
            "font-variant": "normal",
            "text-align": "left",
            "text-indent": "0px",
            "text-transform": "none",
            "line-height": "normal",
            "letter-spacing": "normal",
            "word-spacing": "normal",
            "word-wrap": "normal",
            "white-space": "nowrap",
            "float": "none",
            "z-index": "201",
            "box-shadow": "rgb(128, 128, 128) 0px 10px 20px",
            "-webkit-box-shadow": "rgb(128, 128, 128) 0px 10px 20px",
            "background-color": "white"
        },
        "href": "",
        "media": "",
        "type": 1
    },
    {
        "selector": ".MathJax_MenuItem",
        "css": {
            "padding": "2px 2em",
            "background": "transparent"
        },
        "href": "",
        "media": "",
        "type": 1
    },
    {
        "selector": ".MathJax_MenuArrow",
        "css": {
            "position": "absolute",
            "right": "0.5em",
            "color": "rgb(102, 102, 102)"
        },
        "href": "",
        "media": "",
        "type": 1
    },
    {
        "selector": ".MathJax_MenuActive .MathJax_MenuArrow",
        "css": {
            "color": "white"
        },
        "href": "",
        "media": "",
        "type": 1
    },
    {
        "selector": ".MathJax_MenuArrow.RTL",
        "css": {
            "left": "0.5em",
            "right": "auto"
        },
        "href": "",
        "media": "",
        "type": 1
    },
    {
        "selector": ".MathJax_MenuCheck",
        "css": {
            "position": "absolute",
            "left": "0.7em"
        },
        "href": "",
        "media": "",
        "type": 1
    },
    {
        "selector": ".MathJax_MenuCheck.RTL",
        "css": {
            "right": "0.7em",
            "left": "auto"
        },
        "href": "",
        "media": "",
        "type": 1
    },
    {
        "selector": ".MathJax_MenuRadioCheck",
        "css": {
            "position": "absolute",
            "left": "1em"
        },
        "href": "",
        "media": "",
        "type": 1
    },
    {
        "selector": ".MathJax_MenuRadioCheck.RTL",
        "css": {
            "right": "1em",
            "left": "auto"
        },
        "href": "",
        "media": "",
        "type": 1
    },
    {
        "selector": ".MathJax_MenuLabel",
        "css": {
            "padding": "2px 2em 4px 1.33em",
            "font-style": "italic"
        },
        "href": "",
        "media": "",
        "type": 1
    },
    {
        "selector": ".MathJax_MenuRule",
        "css": {
            "border-top-width": "1px",
            "border-top-style": "solid",
            "border-top-color": "rgb(204, 204, 204)",
            "margin": "4px 1px 0px"
        },
        "href": "",
        "media": "",
        "type": 1
    },
    {
        "selector": ".MathJax_MenuDisabled",
        "css": {
            "color": "graytext"
        },
        "href": "",
        "media": "",
        "type": 1
    },
    {
        "selector": ".MathJax_MenuActive",
        "css": {
            "color": "highlighttext",
            "background-color": "highlight"
        },
        "href": "",
        "media": "",
        "type": 1
    },
    {
        "selector": ".MathJax_Menu_Close",
        "css": {
            "position": "absolute",
            "width": "31px",
            "height": "31px",
            "top": "-15px",
            "left": "-15px"
        },
        "href": "",
        "media": "",
        "type": 1
    },
    {
        "selector": "#MathJax_Zoom",
        "css": {
            "position": "absolute",
            "overflow": "auto",
            "display": "block",
            "z-index": "301",
            "padding": "0.5em",
            "border": "1px solid black",
            "margin": "0px",
            "font-weight": "normal",
            "font-style": "normal",
            "text-align": "left",
            "text-indent": "0px",
            "text-transform": "none",
            "line-height": "normal",
            "letter-spacing": "normal",
            "word-spacing": "normal",
            "word-wrap": "normal",
            "white-space": "nowrap",
            "float": "none",
            "box-shadow": "rgb(170, 170, 170) 5px 5px 15px",
            "-webkit-box-shadow": "rgb(170, 170, 170) 5px 5px 15px",
            "background-color": "rgb(240, 240, 240)"
        },
        "href": "",
        "media": "",
        "type": 1
    },
    {
        "selector": "#MathJax_ZoomOverlay",
        "css": {
            "position": "absolute",
            "left": "0px",
            "top": "0px",
            "z-index": "300",
            "display": "inline-block",
            "width": "100%",
            "height": "100%",
            "border": "0px",
            "padding": "0px",
            "margin": "0px",
            "opacity": "0",
            "background-color": "white"
        },
        "href": "",
        "media": "",
        "type": 1
    },
    {
        "selector": "#MathJax_ZoomFrame",
        "css": {
            "position": "relative",
            "display": "inline-block",
            "height": "0px",
            "width": "0px"
        },
        "href": "",
        "media": "",
        "type": 1
    },
    {
        "selector": "#MathJax_ZoomEventTrap",
        "css": {
            "position": "absolute",
            "left": "0px",
            "top": "0px",
            "z-index": "302",
            "display": "inline-block",
            "border": "0px",
            "padding": "0px",
            "margin": "0px",
            "opacity": "0",
            "background-color": "white"
        },
        "href": "",
        "media": "",
        "type": 1
    },
    {
        "selector": ".MathJax_Preview",
        "css": {
            "color": "rgb(136, 136, 136)"
        },
        "href": "",
        "media": "",
        "type": 1
    },
    {
        "selector": "#MathJax_Message",
        "css": {
            "position": "fixed",
            "left": "1em",
            "bottom": "1.5em",
            "border": "1px solid rgb(149, 149, 149)",
            "margin": "0px",
            "padding": "2px 8px",
            "z-index": "102",
            "color": "black",
            "font-size": "80%",
            "width": "auto",
            "white-space": "nowrap",
            "background-color": "rgb(230, 230, 230)"
        },
        "href": "",
        "media": "",
        "type": 1
    },
    {
        "selector": "#MathJax_MSIE_Frame",
        "css": {
            "position": "absolute",
            "top": "0px",
            "left": "0px",
            "width": "0px",
            "z-index": "101",
            "border": "0px",
            "margin": "0px",
            "padding": "0px"
        },
        "href": "",
        "media": "",
        "type": 1
    },
    {
        "selector": ".MathJax_Error",
        "css": {
            "color": "rgb(204, 0, 0)",
            "font-style": "italic"
        },
        "href": "",
        "media": "",
        "type": 1
    }
]
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$
  • // @author You: this is a default value when you make a new userscript in Tampermonkey, but you really ought to change it.

  • getRules: instead of having this function in here, and keeping rulesList as a global, consider using a constructor model instead:

var RuleDumper = function(){
    this.rulesList = [];
}
RuleDumper.prototype.run = function(){
    this.searchStyleSheets();
}
// ...
var RD = new RuleDumper();
RD.run();
  • searchStyleSheets: you shouldn't be passing in undefined, and as JavaScript explicitly require all the arguments, you can just pass in styleSheets[i].

  • searchRulesList: you don't need the var keyword for href, media and rules because they're parameters and defined there.

  • In your for loop in searchStyleSheets, define the length instead of checking it each time.

  • In cssTextToObject, reverse the initial condition (m != null) and return first, that way you can remove a layer from the rest of the code.

  • rule.cssText ? rule.cssText : rule.style.cssText can be simplified to rule.cssText || rule.style.cssText.

  • getCssText is effectively useless, and should be moved into cssTextToObject, which would then be called cssToObject.

  • Your comment is helpful, but you should swap this magic number (rule.type === 4) with a constant. E.G. var MEDIA_RULE_TYPE = 4; (note the case, it signifies a constant)

  • Your closure looks like the following:

(function() {
}())

Consider adding a semicolon at the end, it's better form.


I have a problem with the following:

function searchRulesList(styleSheet, href, media, rules) {
    // CSSRuleList
    var rules = rules || styleSheet.rules || styleSheet.cssRules || [];
    var href = href || styleSheet.href || '';
    var media = media || styleSheet.media.mediaText || '';
    for (var i = 0; i < rules.length; i++) {
        addRule(rules[i], href, media);
    }
}
function addRule(rule, href, media) {
    // CSSMediaRule
    if (rule.type === 4) {
        var rules = rule.rules || rule.cssRules || [];
        var href = rule.parentStyleSheet.href || '';
        var media = rule.media.mediaText || '';
        searchRulesList(rule, href, media, rules);
    } else {

Other than the re-definition of rules, href and media in both functions, what you pass into addRule as href and media are completely unused.

For href, you pass in the style sheet's href, and then redefine it as the rule's parent stylesheet's href. Literally the same effect.

I can see basically the same thing for media, so don't redefine them.


Another huge problem I have is your constant passing of rulesList between functions, even though only one function uses it. Normally, you might consider a global instead, but, using the constructor model I described earlier, you can avoid both from occurring.


With all those changes in mind, this is what your code would look like:

(function() {
    /*jslint browser: true*/ /*global  $*/
    'use strict';
    var RuleDumper = function(){
        this.rulesList = [];
    };
    RuleDumper.prototype.run = function(){
        this.searchStyleSheets();
        return this.rulesList;
    };
    RuleDumper.prototype.searchStyleSheets = function(){
        var styleSheets = document.styleSheets || [];
            for (var i = 0, length = styleSheets.length; i < length; i++) {
            // CSSStyleSheet
            this.searchRulesList(styleSheets[i]);
        }
    };
    RuleDumper.prototype.searchRulesList = function(styleSheet, href, media, rules) {
        // CSSRuleList
        rules = rules || styleSheet.rules || styleSheet.cssRules || [];
        href  = href  || styleSheet.href  || '';
        media = media || styleSheet.media.mediaText || '';
        for (var i = 0; i < rules.length; i++) {
            this.addRule(rules[i], href, media);
        }
    };
    RuleDumper.prototype.addRule = function(rule, href, media) {
        // CSSMediaRule
        if (rule.type === 4) {
            var rules = rule.rules || rule.cssRules || [];
            this.searchRulesList(rule, href, media, rules);
        } else {
            // CSSStyleRule
            this.rulesList.push({
                'selector' : rule.selectorText,
                'css'      : this.cssToObject(rule),
                'href'     : href,
                //'self'   : rule,
                'media'    : media,
                'type'     : rule.type
            });
        }
    }
    RuleDumper.prototype.cssToObject = function(rule) {
        var cssText = (function(text){
                          return text.substr(text.indexOf('{'))
                      })(rule.cssText || rule.style.cssText);
        var m = cssText.match(/^\{(?:[ ]*)?(.*)(?:[ ]*)?\}$/);
        if (m == null) {
            return cssText;
        }
        var obj = {};
        if (m.length === 2) {
            var properties = m[1].split(/(?:[ ]*)?[;](?:[ ]*)?/);
            for (var i = 0, length = properties.length; i < length; i++) {
                var pair = properties[i].split(/(?:[ ]*)?[:](?:[ ]*)?/);
                if (pair.length === 2) {
                    obj[pair[0]] = pair[1];
                }
            }
        }
        return obj;
    }
    var RD = new RuleDumper();
    GM_log(JSON.stringify(RD.run(), undefined, '    '));
}());
\$\endgroup\$
3
  • \$\begingroup\$ Nice improvements, but I don't think this has ever passed JSLint. The code has missing semicolons and incorrect whitespace. \$\endgroup\$
    – XP1
    Commented Jun 22, 2016 at 1:54
  • \$\begingroup\$ @XP1 JSLint is outdated, and whitespace and semicolons are usually preferences set by the project author. Standard JS doesn't even use semicolons \$\endgroup\$
    – Quill
    Commented Jun 22, 2016 at 1:58
  • \$\begingroup\$ Sorry, I didn't notice that the original poster was the one who put the JSLint directive. \$\endgroup\$
    – XP1
    Commented Jun 22, 2016 at 6:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.