HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Optimize CSS Rule Dumper

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
dumperoptimizecssrule

Problem

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

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"

Solution

-
// @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.le

Code Snippets

var RuleDumper = function(){
    this.rulesList = [];
}
RuleDumper.prototype.run = function(){
    this.searchStyleSheets();
}
// ...
var RD = new RuleDumper();
RD.run();
(function() {
}())
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 {
(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, '    '));
}());

Context

StackExchange Code Review Q#78837, answer score: 2

Revisions (0)

No revisions yet.