1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-11-02 00:42:45 +01:00

#1004: reduce overhead of CSS selector validation

This commit is contained in:
gorhill 2015-03-17 08:26:35 -04:00
parent 58e8b5bf5f
commit c81a9925b2

View File

@ -239,15 +239,6 @@ var FilterParser = function() {
this.invalid = false; this.invalid = false;
this.cosmetic = true; this.cosmetic = true;
this.reParser = /^\s*([^#]*)(##|#@#)(.+)\s*$/; this.reParser = /^\s*([^#]*)(##|#@#)(.+)\s*$/;
// Not all browsers support `Element.matches`:
// http://caniuse.com/#feat=matchesselector
this.div = document.createElement('div');
if ( typeof this.div.matches !== 'function' ) {
this.div = {
matches: function() { return true; }
};
}
}; };
/******************************************************************************/ /******************************************************************************/
@ -264,18 +255,6 @@ FilterParser.prototype.reset = function() {
/******************************************************************************/ /******************************************************************************/
FilterParser.prototype.isValidSelector = function(s) {
try {
this.div.matches(s);
} catch (e) {
console.error('uBlock> invalid cosmetic filter:', s);
return false;
}
return true;
};
/******************************************************************************/
FilterParser.prototype.parse = function(s) { FilterParser.prototype.parse = function(s) {
// important! // important!
this.reset(); this.reset();
@ -306,13 +285,6 @@ FilterParser.prototype.parse = function(s) {
this.suffix = this.suffix.slice(1); this.suffix = this.suffix.slice(1);
} }
// https://github.com/gorhill/uBlock/issues/1004
// Detect and report invalid CSS selectors.
if ( this.isValidSelector(this.suffix) === false ) {
this.invalid = true;
return this;
}
this.unhide = matches[2].charAt(1) === '@' ? 1 : 0; this.unhide = matches[2].charAt(1) === '@' ? 1 : 0;
if ( this.prefix !== '' ) { if ( this.prefix !== '' ) {
this.hostnames = this.prefix.split(/\s*,\s*/); this.hostnames = this.prefix.split(/\s*,\s*/);
@ -587,6 +559,32 @@ FilterContainer.prototype.reset = function() {
/******************************************************************************/ /******************************************************************************/
// https://github.com/gorhill/uBlock/issues/1004
// Detect and report invalid CSS selectors.
FilterContainer.prototype.div = document.createElement('div');
// Not all browsers support `Element.matches`:
// http://caniuse.com/#feat=matchesselector
if ( typeof FilterContainer.prototype.div.matches === 'function' ) {
FilterContainer.prototype.isValidSelector = function(s) {
try {
this.div.matches(s);
} catch (e) {
console.error('uBlock> invalid cosmetic filter:', s);
return false;
}
return true;
};
} else {
FilterContainer.prototype.isValidSelector = function() {
return true;
};
}
/******************************************************************************/
FilterContainer.prototype.compile = function(s, out) { FilterContainer.prototype.compile = function(s, out) {
var parsed = this.parser.parse(s); var parsed = this.parser.parse(s);
if ( parsed.cosmetic === false ) { if ( parsed.cosmetic === false ) {
@ -603,6 +601,15 @@ FilterContainer.prototype.compile = function(s, out) {
return true; return true;
} }
// For hostname- or entity-based filters, class- or id-based selectors are
// still the most common, and can easily be tested using a plain regex.
if (
this.reClassOrIdSelector.test(parsed.suffix) === false &&
this.isValidSelector(parsed.suffix) === false
) {
return true;
}
// https://github.com/gorhill/uBlock/issues/151 // https://github.com/gorhill/uBlock/issues/151
// Negated hostname means the filter applies to all non-negated hostnames // Negated hostname means the filter applies to all non-negated hostnames
// of same filter OR globally if there is no non-negated hostnames. // of same filter OR globally if there is no non-negated hostnames.
@ -635,11 +642,9 @@ FilterContainer.prototype.compileGenericSelector = function(parsed, out) {
// All generic exception filters are put in the same bucket: they are // All generic exception filters are put in the same bucket: they are
// expected to be very rare. // expected to be very rare.
if ( parsed.unhide ) { if ( parsed.unhide ) {
out.push( if ( this.isValidSelector(selector) ) {
'c\v' + out.push('c\vg1\v' + selector);
'g1\v' + }
selector
);
return; return;
} }
@ -651,45 +656,56 @@ FilterContainer.prototype.compileGenericSelector = function(parsed, out) {
if ( matches === null ) { if ( matches === null ) {
return; return;
} }
out.push( // Single-CSS rule: no need to test for whether the selector
'c\v' + // is valid, the regex took care of this. Most generic selector falls
(matches[1] === selector ? 'lg\v' : 'lg+\v') + // into that category.
makeHash(0, matches[1], this.genericHashMask) + '\v' + if ( matches[1] === selector ) {
selector out.push(
); 'c\vlg\v' +
makeHash(0, matches[1], this.genericHashMask) + '\v' +
selector
);
return;
}
// Many-CSS rules
if ( this.isValidSelector(selector) ) {
out.push(
'c\vlg+\v' +
makeHash(0, matches[1], this.genericHashMask) + '\v' +
selector
);
}
return; return;
} }
// ["title"] and ["alt"] will go in high-low generic bin. // ["title"] and ["alt"] will go in high-low generic bin.
if ( this.reHighLow.test(selector) ) { if ( this.reHighLow.test(selector) ) {
out.push( if ( this.isValidSelector(selector) ) {
'c\v' + out.push('c\vhlg0\v' + selector);
'hlg0\v' + }
selector
);
return; return;
} }
// [href^="..."] will go in high-medium generic bin. // [href^="..."] will go in high-medium generic bin.
matches = this.reHighMedium.exec(selector); matches = this.reHighMedium.exec(selector);
if ( matches && matches.length === 2 ) { if ( matches && matches.length === 2 ) {
out.push( if ( this.isValidSelector(selector) ) {
'c\v' + out.push(
'hmg0\v' + 'c\vhmg0\v' +
matches[1] + '\v' + matches[1] + '\v' +
selector selector
); );
}
return; return;
} }
// All else // All else
out.push( if ( this.isValidSelector(selector) ) {
'c\v' + out.push('c\vhhg0\v' + selector);
'hhg0\v' + }
selector
);
}; };
FilterContainer.prototype.reClassOrIdSelector = /^([#.][\w-]+)$/;
FilterContainer.prototype.rePlainSelector = /^([#.][\w-]+)/; FilterContainer.prototype.rePlainSelector = /^([#.][\w-]+)/;
FilterContainer.prototype.reHighLow = /^[a-z]*\[(?:alt|title)="[^"]+"\]$/; FilterContainer.prototype.reHighLow = /^[a-z]*\[(?:alt|title)="[^"]+"\]$/;
FilterContainer.prototype.reHighMedium = /^\[href\^="https?:\/\/([^"]{8})[^"]*"\]$/; FilterContainer.prototype.reHighMedium = /^\[href\^="https?:\/\/([^"]{8})[^"]*"\]$/;