patternjavascriptMinor
Optimize CSS Rule Dumper
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.
{
"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"
// ==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
-
-
-
-
-
In your
-
In
-
-
-
Your comment is helpful, but you should swap this magic number (
-
Your closure looks like the following:
Consider adding a semicolon at the end, it's better form.
I have a problem with the following:
Other than the re-definition of
For
I can see basically the same thing for
Another huge problem I have is your constant passing of
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
// @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.