mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-05 18:32:30 +01:00
this fixes #145 + refactoring/performance
This commit is contained in:
parent
fe2f996910
commit
c6b7dfabb1
@ -176,14 +176,11 @@ var FilterParser = function() {
|
||||
this.prefix = '';
|
||||
this.suffix = '';
|
||||
this.anchor = 0;
|
||||
this.filterType = '#';
|
||||
this.unhide = 0;
|
||||
this.hostnames = [];
|
||||
this.invalid = false;
|
||||
this.unsupported = false;
|
||||
this.reParser = /^\s*([^#]*)(##|#@#)(.+)\s*$/;
|
||||
this.rePlain = /^([#.][\w-]+)/;
|
||||
this.rePlainMore = /^[#.][\w-]+[^\w-]/;
|
||||
this.reElement = /^[a-z]/i;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
@ -193,8 +190,8 @@ FilterParser.prototype.reset = function() {
|
||||
this.prefix = '';
|
||||
this.suffix = '';
|
||||
this.anchor = '';
|
||||
this.filterType = '#';
|
||||
this.hostnames = [];
|
||||
this.unhide = 0;
|
||||
this.hostnames.length = 0;
|
||||
this.invalid = false;
|
||||
return this;
|
||||
};
|
||||
@ -221,12 +218,19 @@ FilterParser.prototype.parse = function(s) {
|
||||
// https://github.com/gorhill/httpswitchboard/issues/260
|
||||
// Any sequence of `#` longer than one means the line is not a valid
|
||||
// cosmetic filter.
|
||||
if ( this.suffix.indexOf('##') >= 0 ) {
|
||||
if ( this.suffix.indexOf('##') !== -1 ) {
|
||||
this.invalid = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
this.filterType = this.anchor.charAt(1);
|
||||
// Normalize high-medium selectors: `href` is assumed to imply `a` tag. We
|
||||
// need to do this here in order to correctly avoid duplicates. The test
|
||||
// is designed to minimize overhead -- this is a low occurrence filter.
|
||||
if ( this.suffix.charAt(1) === '[' && this.suffix.slice(2, 9) === 'href^="' ) {
|
||||
this.suffix = this.suffix.slice(1);
|
||||
}
|
||||
|
||||
this.unhide = this.anchor.charAt(1) === '@' ? 1 : 0;
|
||||
if ( this.prefix !== '' ) {
|
||||
this.hostnames = this.prefix.split(/\s*,\s*/);
|
||||
}
|
||||
@ -234,30 +238,79 @@ FilterParser.prototype.parse = function(s) {
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
FilterParser.prototype.isPlainMore = function() {
|
||||
return this.rePlainMore.test(this.suffix);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
FilterParser.prototype.isElement = function() {
|
||||
return this.reElement.test(this.suffix);
|
||||
};
|
||||
// Two Unicode characters:
|
||||
// T0HHHHHHH HHHHHHHHH
|
||||
// | | |
|
||||
// | | |
|
||||
// | | |
|
||||
// | | +-- bit 8-0 of FNV
|
||||
// | |
|
||||
// | +-- bit 15-9 of FNV
|
||||
// |
|
||||
// +-- filter type (0=hide 1=unhide)
|
||||
//
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
FilterParser.prototype.extractPlain = function() {
|
||||
var matches = this.rePlain.exec(this.suffix);
|
||||
if ( matches && matches.length === 2 ) {
|
||||
return matches[1];
|
||||
}
|
||||
return '';
|
||||
var makeHash = function(unhide, token, mask) {
|
||||
// Ref: Given a URL, returns a unique 4-character long hash string
|
||||
// Based on: FNV32a
|
||||
// http://www.isthe.com/chongo/tech/comp/fnv/index.html#FNV-reference-source
|
||||
// The rest is custom, suited for µBlock.
|
||||
var i1 = token.length;
|
||||
var i2 = i1 >> 1;
|
||||
var i4 = i1 >> 2;
|
||||
var i8 = i1 >> 3;
|
||||
var hval = (0x811c9dc5 ^ token.charCodeAt(0)) >>> 0;
|
||||
hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24);
|
||||
hval >>>= 0;
|
||||
hval ^= token.charCodeAt(i8);
|
||||
hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24);
|
||||
hval >>>= 0;
|
||||
hval ^= token.charCodeAt(i4);
|
||||
hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24);
|
||||
hval >>>= 0;
|
||||
hval ^= token.charCodeAt(i4+i8);
|
||||
hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24);
|
||||
hval >>>= 0;
|
||||
hval ^= token.charCodeAt(i2);
|
||||
hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24);
|
||||
hval >>>= 0;
|
||||
hval ^= token.charCodeAt(i2+i8);
|
||||
hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24);
|
||||
hval >>>= 0;
|
||||
hval ^= token.charCodeAt(i2+i4);
|
||||
hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24);
|
||||
hval >>>= 0;
|
||||
hval ^= token.charCodeAt(i1-1);
|
||||
hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24);
|
||||
hval >>>= 0;
|
||||
hval &= mask;
|
||||
if ( unhide !== 0 ) {
|
||||
hval |= 0x20000;
|
||||
}
|
||||
return String.fromCharCode(hval >>> 9, hval & 0x1FF);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
// Cosmetic filter family tree:
|
||||
//
|
||||
// Generic
|
||||
// Low generic simple: class or id only
|
||||
// Low generic complex: class or id + extra stuff after
|
||||
// High generic:
|
||||
// High-low generic: [alt="..."],[title="..."]
|
||||
// High-medium generic: [href^="..."]
|
||||
// High-high generic: everything else
|
||||
// Specific
|
||||
// Specfic hostname
|
||||
// Specific entity
|
||||
//
|
||||
// Generic filters can only be enforced once the main document is loaded.
|
||||
// Specific filers can be enforced before the main document is loaded.
|
||||
|
||||
var FilterContainer = function() {
|
||||
this.filterParser = new FilterParser();
|
||||
this.reset();
|
||||
@ -271,25 +324,44 @@ FilterContainer.prototype.reset = function() {
|
||||
this.filterParser.reset();
|
||||
this.frozen = false;
|
||||
this.acceptedCount = 0;
|
||||
this.processedCount = 0;
|
||||
this.duplicateCount = 0;
|
||||
this.domainHashMask = (1 << 10) - 1;
|
||||
this.genericHashMask = (1 << 15) - 1;
|
||||
this.genericFilters = {};
|
||||
|
||||
// temporary (at parse time)
|
||||
this.lowGenericHide = {};
|
||||
this.lowGenericDonthide = {};
|
||||
this.highGenericHide = {};
|
||||
this.highGenericDonthide = {};
|
||||
this.hostnameHide = {};
|
||||
this.hostnameDonthide = {};
|
||||
this.hostnameFilters = {};
|
||||
this.entityHide = {};
|
||||
this.entityDonthide = {};
|
||||
|
||||
// permanent
|
||||
// [class], [id]
|
||||
this.lowGenericFilters = {};
|
||||
|
||||
// [alt="..."], [title="..."]
|
||||
this.highLowGenericHide = {};
|
||||
this.highLowGenericDonthide = {};
|
||||
this.highLowGenericHideCount = 0;
|
||||
this.highLowGenericDonthideCount = 0;
|
||||
|
||||
// a[href^="http..."]
|
||||
this.highMediumGenericHide = {};
|
||||
this.highMediumGenericDonthide = {};
|
||||
this.highMediumGenericHideCount = 0;
|
||||
this.highMediumGenericDonthideCount = 0;
|
||||
|
||||
// everything else
|
||||
this.highHighGenericHide = [];
|
||||
this.highHighGenericDonthide = [];
|
||||
this.highHighGenericHideCount = 0;
|
||||
this.highHighGenericDonthideCount = 0;
|
||||
|
||||
this.hostnameFilters = {};
|
||||
this.entityFilters = {};
|
||||
this.hideUnfiltered = [];
|
||||
this.hideLowGenerics = {};
|
||||
this.hideHighGenerics = [];
|
||||
this.donthideUnfiltered = [];
|
||||
this.donthideLowGenerics = {};
|
||||
this.donthideHighGenerics = [];
|
||||
this.rejected = [];
|
||||
this.duplicates = {};
|
||||
this.duplicateCount = 0;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
@ -301,43 +373,132 @@ FilterContainer.prototype.add = function(s) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.processedCount += 1;
|
||||
|
||||
//if ( s === 'mail.google.com##.nH.adC > .nH > .nH > .u5 > .azN' ) {
|
||||
// debugger;
|
||||
//}
|
||||
|
||||
// hostname-based filters: with a hostname, narrowing is good enough, no
|
||||
// need to further narrow.
|
||||
if ( parsed.hostnames.length ) {
|
||||
return this.addSpecificFilter(parsed);
|
||||
}
|
||||
|
||||
if ( this.duplicates[s] ) {
|
||||
this.duplicateCount++;
|
||||
return false;
|
||||
}
|
||||
this.duplicates[s] = true;
|
||||
|
||||
// no specific hostname, narrow using class or id.
|
||||
var selectorType = parsed.suffix.charAt(0);
|
||||
if ( selectorType === '#' || selectorType === '.' ) {
|
||||
return this.addPlainFilter(parsed);
|
||||
}
|
||||
|
||||
// no specific hostname, no class, no id.
|
||||
if ( parsed.filterType === '#' ) {
|
||||
this.hideUnfiltered.push(parsed.suffix);
|
||||
var hostnames = parsed.hostnames;
|
||||
var i = hostnames.length;
|
||||
if ( i === 0 ) {
|
||||
this.addGenericSelector(parsed);
|
||||
} else {
|
||||
this.donthideUnfiltered.push(parsed.suffix);
|
||||
while ( i-- ) {
|
||||
this.addSpecificSelector(hostnames[i], parsed);
|
||||
}
|
||||
}
|
||||
this.acceptedCount += 1;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
FilterContainer.prototype.addGenericSelector = function(parsed) {
|
||||
var entries;
|
||||
var selectorType = parsed.suffix.charAt(0);
|
||||
if ( selectorType === '#' || selectorType === '.' ) {
|
||||
entries = parsed.unhide === 0 ?
|
||||
this.lowGenericHide :
|
||||
this.lowGenericDonthide;
|
||||
} else {
|
||||
entries = parsed.unhide === 0 ?
|
||||
this.highGenericHide :
|
||||
this.highGenericDonthide;
|
||||
}
|
||||
if ( entries[parsed.suffix] === undefined ) {
|
||||
entries[parsed.suffix] = true;
|
||||
this.acceptedCount += 1;
|
||||
} else {
|
||||
this.duplicateCount += 1;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
FilterContainer.prototype.addSpecificSelector = function(hostname, parsed) {
|
||||
// rhill 2014-07-13: new filter class: entity.
|
||||
if ( hostname.slice(-2) === '.*' ) {
|
||||
this.addEntitySelector(hostname, parsed);
|
||||
} else {
|
||||
this.addHostnameSelector(hostname, parsed);
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
FilterContainer.prototype.addHostnameSelector = function(hostname, parsed) {
|
||||
// https://github.com/gorhill/uBlock/issues/145
|
||||
var unhide = parsed.unhide;
|
||||
if ( hostname.charAt(0) === '~' ) {
|
||||
this.addGenericSelector(parsed);
|
||||
hostname = hostname.slice(1);
|
||||
unhide ^= 1;
|
||||
}
|
||||
var entries = unhide === 0 ?
|
||||
this.hostnameHide :
|
||||
this.hostnameDonthide;
|
||||
var entry = entries[hostname];
|
||||
if ( entry === undefined ) {
|
||||
entry = entries[hostname] = {};
|
||||
entry[parsed.suffix] = true;
|
||||
this.acceptedCount += 1;
|
||||
} else if ( entry[parsed.suffix] === undefined ) {
|
||||
entry[parsed.suffix] = true;
|
||||
this.acceptedCount += 1;
|
||||
} else {
|
||||
this.duplicateCount += 1;
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
FilterContainer.prototype.addEntitySelector = function(hostname, parsed) {
|
||||
var entries = parsed.unhide === 0 ?
|
||||
this.entityHide :
|
||||
this.entityDonthide;
|
||||
var entity = hostname.slice(0, -2);
|
||||
var entry = entries[entity];
|
||||
if ( entry === undefined ) {
|
||||
entry = entries[entity] = {};
|
||||
entry[parsed.suffix] = true;
|
||||
this.acceptedCount += 1;
|
||||
} else if ( entry[parsed.suffix] === undefined ) {
|
||||
entry[parsed.suffix] = true;
|
||||
this.acceptedCount += 1;
|
||||
} else {
|
||||
this.duplicateCount += 1;
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
FilterContainer.prototype.freezeLowGenerics = function(what, type) {
|
||||
var selectors = this[what];
|
||||
var matches, selectorPrefix, f, hash, bucket;
|
||||
for ( var selector in selectors ) {
|
||||
if ( selectors.hasOwnProperty(selector) === false ) {
|
||||
continue;
|
||||
}
|
||||
matches = this.rePlainSelector.exec(selector);
|
||||
if ( !matches ) {
|
||||
continue;
|
||||
}
|
||||
selectorPrefix = matches[1];
|
||||
f = selectorPrefix === selector ?
|
||||
new FilterPlain(selector) :
|
||||
new FilterPlainMore(selector);
|
||||
hash = makeHash(type, selectorPrefix, this.genericHashMask);
|
||||
bucket = this.lowGenericFilters[hash];
|
||||
if ( bucket === undefined ) {
|
||||
this.lowGenericFilters[hash] = f;
|
||||
} else if ( bucket instanceof FilterBucket ) {
|
||||
bucket.add(f);
|
||||
} else {
|
||||
this.lowGenericFilters[hash] = new FilterBucket(bucket, f);
|
||||
}
|
||||
}
|
||||
this[what] = {};
|
||||
};
|
||||
|
||||
FilterContainer.prototype.rePlainSelector = /^([#.][\w-]+)/;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
FilterContainer.prototype.freezeHostnameSpecifics = function(what, type) {
|
||||
var µburi = µb.URI;
|
||||
var entries = this[what];
|
||||
@ -387,215 +548,72 @@ FilterContainer.prototype.freezeEntitySpecifics = function(what, type) {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
FilterContainer.prototype.freezeGenerics = function(what) {
|
||||
var selectors = this[what + 'Unfiltered'];
|
||||
//console.log('%d highly generic selectors:\n', selectors.length, selectors.sort().join('\n'));
|
||||
FilterContainer.prototype.freezeHighGenerics = function(what) {
|
||||
var selectors = this['highGeneric' + what];
|
||||
|
||||
// ["title"] and ["alt"] will be sorted out manually, these are the most
|
||||
// common generic selectors, aka "low generics". The rest will be put in
|
||||
// the high genericity bin.
|
||||
var lowGenerics = {};
|
||||
var lowGenericCount = 0;
|
||||
var re = /^(([a-z]*)\[(alt|title)="([^"]+)"\])$/;
|
||||
var i = selectors.length;
|
||||
var selector, matches;
|
||||
while ( i-- ) {
|
||||
selector = selectors[i];
|
||||
matches = re.exec(selector);
|
||||
if ( !matches ) {
|
||||
// ["title"] and ["alt"] will go in high-low generic bin.
|
||||
// [href^="..."] wil go in high-mdium generic bin.
|
||||
// The rest will be put in the high-high generic bin.
|
||||
var highLowGeneric = {};
|
||||
var highLowGenericCount = 0;
|
||||
var highMediumGeneric = {};
|
||||
var highMediumGenericCount = 0;
|
||||
var highHighGeneric = [];
|
||||
var reHighLow = /^[a-z]*(\[(?:alt|title)="[^"]+"\])$/;
|
||||
var reHighMedium = /^\[href\^="https?:\/\/([^"]{8})[^"]*"\]$/;
|
||||
var matches, hash;
|
||||
for ( var selector in selectors ) {
|
||||
if ( selectors.hasOwnProperty(selector) === false ) {
|
||||
continue;
|
||||
}
|
||||
lowGenerics[matches[1]] = true;
|
||||
lowGenericCount++;
|
||||
selectors.splice(i, 1);
|
||||
}
|
||||
|
||||
// Chunksize is a compromise between number of selectors per chunk (the
|
||||
// number of selectors querySelector() will have to deal with), and the
|
||||
// number of chunks (the number of times querySelector() will have to
|
||||
// be called.)
|
||||
// Benchmarking shows this is a hot spot performance-wise for "heavy"
|
||||
// sites (like say, Sports Illustrated, good test case). Not clear what
|
||||
// better can be done at this point, I doubt javascript-side code can beat
|
||||
// querySelector().
|
||||
var chunkSize = Math.max(selectors.length >>> 3, 8);
|
||||
var chunkified = [], chunk;
|
||||
for (;;) {
|
||||
chunk = selectors.splice(0, chunkSize);
|
||||
if ( chunk.length === 0 ) {
|
||||
break;
|
||||
matches = reHighLow.exec(selector);
|
||||
if ( matches && matches.length === 2 ) {
|
||||
highLowGeneric[matches[1]] = true;
|
||||
highLowGenericCount += 1;
|
||||
continue;
|
||||
}
|
||||
chunkified.push(chunk.join(','));
|
||||
matches = reHighMedium.exec(selector);
|
||||
if ( matches && matches.length === 2 ) {
|
||||
hash = matches[1];
|
||||
if ( highMediumGeneric[hash] === undefined ) {
|
||||
highMediumGeneric[hash] = matches[0];
|
||||
} else {
|
||||
highMediumGeneric[hash] += ',\n' + matches[0];
|
||||
}
|
||||
highMediumGenericCount += 1;
|
||||
continue;
|
||||
}
|
||||
highHighGeneric.push(selector);
|
||||
}
|
||||
|
||||
this[what + 'LowGenerics'] = lowGenerics;
|
||||
this[what + 'LowGenericCount'] = lowGenericCount;
|
||||
this[what + 'HighGenerics'] = chunkified;
|
||||
this[what + 'Unfiltered'] = [];
|
||||
this['highLowGeneric' + what] = highLowGeneric;
|
||||
this['highLowGeneric' + what + 'Count'] = highLowGenericCount;
|
||||
this['highMediumGeneric' + what] = highMediumGeneric;
|
||||
this['highMediumGeneric' + what + 'Count'] = highMediumGenericCount;
|
||||
this['highHighGeneric' + what] = highHighGeneric.join(',\n');
|
||||
this['highHighGeneric' + what + 'Count'] = highHighGeneric.length;
|
||||
this['highGeneric' + what] = {};
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
FilterContainer.prototype.freeze = function() {
|
||||
this.freezeHostnameSpecifics('hostnameHide', '#');
|
||||
this.freezeHostnameSpecifics('hostnameDonthide', '@');
|
||||
this.freezeEntitySpecifics('entityHide', '#');
|
||||
this.freezeEntitySpecifics('entityDonthide', '@');
|
||||
this.freezeGenerics('hide');
|
||||
this.freezeGenerics('donthide');
|
||||
|
||||
this.freezeLowGenerics('lowGenericHide', 0);
|
||||
this.freezeLowGenerics('lowGenericDonthide', 1);
|
||||
this.freezeHighGenerics('Hide');
|
||||
this.freezeHighGenerics('Donthide');
|
||||
this.freezeHostnameSpecifics('hostnameHide', 0);
|
||||
this.freezeHostnameSpecifics('hostnameDonthide', 1);
|
||||
this.freezeEntitySpecifics('entityHide', 0);
|
||||
this.freezeEntitySpecifics('entityDonthide', 1);
|
||||
this.filterParser.reset();
|
||||
|
||||
// console.debug('Number of duplicate cosmetic filters skipped:', this.duplicateCount);
|
||||
this.duplicates = {};
|
||||
this.frozen = true;
|
||||
|
||||
//histogram('genericFilters', this.genericFilters);
|
||||
//histogram('lowGenericFilters', this.lowGenericFilters);
|
||||
//histogram('hostnameFilters', this.hostnameFilters);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// Two Unicode characters:
|
||||
// T0HHHHHHH HHHHHHHHH
|
||||
// | | |
|
||||
// | | |
|
||||
// | | |
|
||||
// | | +-- bit 8-0 of FNV
|
||||
// | |
|
||||
// | +-- bit 15-9 of FNV
|
||||
// |
|
||||
// +-- filter type (0=hide 1=unhide)
|
||||
//
|
||||
|
||||
var makeHash = function(type, token, mask) {
|
||||
// Ref: Given a URL, returns a unique 4-character long hash string
|
||||
// Based on: FNV32a
|
||||
// http://www.isthe.com/chongo/tech/comp/fnv/index.html#FNV-reference-source
|
||||
// The rest is custom, suited for µBlock.
|
||||
var i1 = token.length;
|
||||
var i2 = i1 >> 1;
|
||||
var i4 = i1 >> 2;
|
||||
var i8 = i1 >> 3;
|
||||
var hval = (0x811c9dc5 ^ token.charCodeAt(0)) >>> 0;
|
||||
hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24);
|
||||
hval >>>= 0;
|
||||
hval ^= token.charCodeAt(i8);
|
||||
hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24);
|
||||
hval >>>= 0;
|
||||
hval ^= token.charCodeAt(i4);
|
||||
hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24);
|
||||
hval >>>= 0;
|
||||
hval ^= token.charCodeAt(i4+i8);
|
||||
hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24);
|
||||
hval >>>= 0;
|
||||
hval ^= token.charCodeAt(i2);
|
||||
hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24);
|
||||
hval >>>= 0;
|
||||
hval ^= token.charCodeAt(i2+i8);
|
||||
hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24);
|
||||
hval >>>= 0;
|
||||
hval ^= token.charCodeAt(i2+i4);
|
||||
hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24);
|
||||
hval >>>= 0;
|
||||
hval ^= token.charCodeAt(i1-1);
|
||||
hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24);
|
||||
hval >>>= 0;
|
||||
hval &= mask;
|
||||
if ( type === '@' ) {
|
||||
hval |= 0x20000;
|
||||
}
|
||||
return String.fromCharCode(hval >>> 9, hval & 0x1FF);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
FilterContainer.prototype.addPlainFilter = function(parsed) {
|
||||
// Verify whether the plain selector is followed by extra selector stuff
|
||||
if ( parsed.isPlainMore() ) {
|
||||
return this.addPlainMoreFilter(parsed);
|
||||
}
|
||||
var f = new FilterPlain(parsed.suffix);
|
||||
var hash = makeHash(parsed.filterType, parsed.suffix, this.genericHashMask);
|
||||
this.addFilterEntry(this.genericFilters, hash, f);
|
||||
this.acceptedCount += 1;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
FilterContainer.prototype.addPlainMoreFilter = function(parsed) {
|
||||
var selectorSuffix = parsed.extractPlain();
|
||||
if ( selectorSuffix === '' ) {
|
||||
return;
|
||||
}
|
||||
var f = new FilterPlainMore(parsed.suffix);
|
||||
var hash = makeHash(parsed.filterType, selectorSuffix, this.genericHashMask);
|
||||
this.addFilterEntry(this.genericFilters, hash, f);
|
||||
this.acceptedCount += 1;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
FilterContainer.prototype.addHostnameFilter = function(hostname, parsed) {
|
||||
var entries = parsed.filterType === '#' ?
|
||||
this.hostnameHide :
|
||||
this.hostnameDonthide;
|
||||
var entry = entries[hostname];
|
||||
if ( entry === undefined ) {
|
||||
entry = entries[hostname] = {};
|
||||
}
|
||||
entry[parsed.suffix] = true;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
FilterContainer.prototype.addEntityFilter = function(hostname, parsed) {
|
||||
var entries = parsed.filterType === '#' ?
|
||||
this.entityHide :
|
||||
this.entityDonthide;
|
||||
var entity = hostname.slice(0, -2);
|
||||
var entry = entries[entity];
|
||||
if ( entry === undefined ) {
|
||||
entry = entries[entity] = {};
|
||||
}
|
||||
entry[parsed.suffix] = true;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
FilterContainer.prototype.addSpecificFilter = function(parsed) {
|
||||
var hostnames = parsed.hostnames;
|
||||
var i = hostnames.length, hostname;
|
||||
while ( i-- ) {
|
||||
hostname = hostnames[i];
|
||||
if ( !hostname ) {
|
||||
continue;
|
||||
}
|
||||
// rhill 2014-07-13: new filter class: entity.
|
||||
if ( hostname.slice(-2) === '.*' ) {
|
||||
this.addEntityFilter(hostname, parsed);
|
||||
} else {
|
||||
this.addHostnameFilter(hostname, parsed);
|
||||
}
|
||||
}
|
||||
this.acceptedCount += 1;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
FilterContainer.prototype.addFilterEntry = function(filterDict, hash, f) {
|
||||
var bucket = filterDict[hash];
|
||||
if ( bucket === undefined ) {
|
||||
filterDict[hash] = f;
|
||||
} else if ( bucket instanceof FilterBucket ) {
|
||||
bucket.add(f);
|
||||
} else {
|
||||
filterDict[hash] = new FilterBucket(bucket, f);
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
FilterContainer.prototype.retrieveGenericSelectors = function(request) {
|
||||
if ( µb.userSettings.parseAllABPHideFilters !== true ) {
|
||||
return;
|
||||
@ -608,15 +626,26 @@ FilterContainer.prototype.retrieveGenericSelectors = function(request) {
|
||||
|
||||
var r = {
|
||||
hide: [],
|
||||
donthide: [],
|
||||
hideLowGenerics: this.hideLowGenerics,
|
||||
hideLowGenericCount: this.hideLowGenericCount,
|
||||
hideHighGenerics: this.hideHighGenerics,
|
||||
donthideLowGenerics: this.donthideLowGenerics,
|
||||
donthideLowGenericCount: this.donthideLowGenericCount,
|
||||
donthideHighGenerics: this.donthideHighGenerics
|
||||
donthide: []
|
||||
};
|
||||
|
||||
if ( request.highGenerics ) {
|
||||
r.highGenerics = {
|
||||
hideLow: this.highLowGenericHide,
|
||||
hideLowCount: this.highLowGenericHideCount,
|
||||
hideMedium: this.highMediumGenericHide,
|
||||
hideMediumCount: this.highMediumGenericHideCount,
|
||||
hideHigh: this.highHighGenericHide,
|
||||
hideHighCount: this.highHighGenericHideCount,
|
||||
donthideLow: this.highLowGenericDonthide,
|
||||
donthideLowCount: this.highLowGenericDonthideCount,
|
||||
donthideMedium: this.highMediumGenericDonthide,
|
||||
donthideMediumCount: this.highMediumGenericDonthideCount,
|
||||
donthideHigh: this.highHighGenericDonthide,
|
||||
donthideHighCount: this.highHighGenericDonthideCount
|
||||
};
|
||||
}
|
||||
|
||||
var hash, bucket;
|
||||
var hashMask = this.genericHashMask;
|
||||
var hideSelectors = r.hide;
|
||||
@ -628,8 +657,8 @@ FilterContainer.prototype.retrieveGenericSelectors = function(request) {
|
||||
if ( !selector ) {
|
||||
continue;
|
||||
}
|
||||
hash = makeHash('#', selector, hashMask);
|
||||
if ( bucket = this.genericFilters[hash] ) {
|
||||
hash = makeHash(0, selector, hashMask);
|
||||
if ( bucket = this.lowGenericFilters[hash] ) {
|
||||
bucket.retrieve(selector, hideSelectors);
|
||||
}
|
||||
}
|
||||
@ -668,15 +697,15 @@ FilterContainer.prototype.retrieveDomainSelectors = function(request) {
|
||||
};
|
||||
|
||||
var hash, bucket;
|
||||
hash = makeHash('#', r.domain, this.domainHashMask);
|
||||
hash = makeHash(0, r.domain, this.domainHashMask);
|
||||
if ( bucket = this.hostnameFilters[hash] ) {
|
||||
bucket.retrieve(hostname, r.hide);
|
||||
}
|
||||
hash = makeHash('#', r.entity, this.domainHashMask);
|
||||
hash = makeHash(0, r.entity, this.domainHashMask);
|
||||
if ( bucket = this.entityFilters[hash] ) {
|
||||
bucket.retrieve(pos === -1 ? domain : hostname.slice(0, pos - domain.length), r.hide);
|
||||
}
|
||||
hash = makeHash('@', r.domain, this.domainHashMask);
|
||||
hash = makeHash(1, r.domain, this.domainHashMask);
|
||||
if ( bucket = this.hostnameFilters[hash] ) {
|
||||
bucket.retrieve(hostname, r.donthide);
|
||||
}
|
||||
|
@ -26,13 +26,9 @@
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
(function() {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// https://github.com/gorhill/httpswitchboard/issues/345
|
||||
|
||||
var messaging = (function(name){
|
||||
var uBlockMessaging = (function(name){
|
||||
var port = null;
|
||||
var dangling = false;
|
||||
var requestId = 1;
|
||||
@ -127,10 +123,13 @@ var messaging = (function(name){
|
||||
// ABP cosmetic filters
|
||||
|
||||
(function() {
|
||||
var messaging = uBlockMessaging;
|
||||
var queriedSelectors = {};
|
||||
var injectedSelectors = {};
|
||||
var classSelectors = null;
|
||||
var idSelectors = null;
|
||||
var highGenerics = null;
|
||||
var contextNodes = [document];
|
||||
|
||||
var domLoaded = function() {
|
||||
// https://github.com/gorhill/uBlock/issues/14
|
||||
@ -155,46 +154,70 @@ var messaging = (function(name){
|
||||
if ( idSelectors !== null ) {
|
||||
selectors = selectors.concat(idSelectors);
|
||||
}
|
||||
if ( selectors.length > 0 ) {
|
||||
if ( selectors.length > 0 || highGenerics === null ) {
|
||||
//console.log('µBlock> ABP cosmetic filters: retrieving CSS rules using %d selectors', selectors.length);
|
||||
messaging.ask({
|
||||
what: 'retrieveGenericCosmeticSelectors',
|
||||
pageURL: window.location.href,
|
||||
selectors: selectors
|
||||
selectors: selectors,
|
||||
highGenerics: highGenerics === null
|
||||
},
|
||||
retrieveHandler
|
||||
);
|
||||
} else {
|
||||
retrieveHandler(null);
|
||||
}
|
||||
idSelectors = null;
|
||||
classSelectors = null;
|
||||
};
|
||||
|
||||
var retrieveHandler = function(selectors) {
|
||||
if ( !selectors ) {
|
||||
return;
|
||||
//console.debug('µBlock> contextNodes = %o', contextNodes);
|
||||
if ( selectors && selectors.highGenerics ) {
|
||||
highGenerics = selectors.highGenerics;
|
||||
}
|
||||
filterLowGenerics(selectors, 'donthide');
|
||||
filterHighGenerics(selectors, 'donthide');
|
||||
if ( selectors.donthide.length ) {
|
||||
var i = selectors.donthide.length;
|
||||
while ( i-- ) {
|
||||
injectedSelectors[selectors.donthide[i]] = true;
|
||||
if ( selectors && selectors.donthide.length ) {
|
||||
processLowGenerics(selectors.donthide);
|
||||
}
|
||||
if ( highGenerics ) {
|
||||
if ( highGenerics.donthideLowCount ) {
|
||||
processHighLowGenerics(highGenerics.donthideLow);
|
||||
}
|
||||
if ( highGenerics.donthideMediumCount ) {
|
||||
processHighMediumGenerics(highGenerics.donthideMedium);
|
||||
}
|
||||
}
|
||||
filterLowGenerics(selectors, 'hide');
|
||||
filterHighGenerics(selectors, 'hide');
|
||||
reduce(selectors.hide, injectedSelectors);
|
||||
if ( selectors.hide.length ) {
|
||||
applyCSS(selectors.hide, 'display', 'none');
|
||||
// No such thing as high-high generic exceptions
|
||||
//if ( highGenerics.donthideHighCount ) {
|
||||
// processHighHighGenerics(document, highGenerics.donthideHigh);
|
||||
//}
|
||||
var hideSelectors = [];
|
||||
if ( selectors && selectors.hide.length ) {
|
||||
processLowGenerics(selectors.hide, hideSelectors);
|
||||
}
|
||||
if ( highGenerics ) {
|
||||
if ( highGenerics.hideLowCount ) {
|
||||
processHighLowGenerics(highGenerics.hideLow, hideSelectors);
|
||||
}
|
||||
if ( highGenerics.hideMediumCount ) {
|
||||
processHighMediumGenerics(highGenerics.hideMedium, hideSelectors);
|
||||
}
|
||||
if ( highGenerics.hideHighCount ) {
|
||||
processHighHighGenerics(highGenerics.hideHigh, hideSelectors);
|
||||
}
|
||||
}
|
||||
if ( hideSelectors.length ) {
|
||||
applyCSS(hideSelectors, 'display', 'none');
|
||||
var style = document.createElement('style');
|
||||
var text = selectors.hide.join(',\n') + ' {display:none !important;}';
|
||||
var text = hideSelectors.join(',\n') + ' {display:none !important;}';
|
||||
style.appendChild(document.createTextNode(text));
|
||||
var parent = document.body || document.documentElement;
|
||||
if ( parent ) {
|
||||
parent.appendChild(style);
|
||||
}
|
||||
//console.debug('µBlock> generic cosmetic filters: injecting %d CSS rules:', selectors.hide.length, hideStyleText);
|
||||
//console.debug('µBlock> generic cosmetic filters: injecting %d CSS rules:', hideSelectors.length, text);
|
||||
}
|
||||
contextNodes.length = 0;
|
||||
};
|
||||
|
||||
var applyCSS = function(selectors, prop, value) {
|
||||
@ -208,88 +231,103 @@ var messaging = (function(name){
|
||||
}
|
||||
};
|
||||
|
||||
var filterTitleGeneric = function(generics, root, out) {
|
||||
if ( !root.title.length ) {
|
||||
return;
|
||||
}
|
||||
var selector = '[title="' + root.title + '"]';
|
||||
if ( generics[selector] && !injectedSelectors[selector] ) {
|
||||
out.push(selector);
|
||||
}
|
||||
selector = root.tagName + selector;
|
||||
if ( generics[selector] && !injectedSelectors[selector] ) {
|
||||
out.push(selector);
|
||||
var selectNodes = function(selector) {
|
||||
var targetNodes = [];
|
||||
var i = contextNodes.length;
|
||||
var node, nodeList, j;
|
||||
var doc = document;
|
||||
while ( i-- ) {
|
||||
node = contextNodes[i];
|
||||
if ( node === doc ) {
|
||||
return doc.querySelectorAll(selector);
|
||||
}
|
||||
targetNodes.push(node);
|
||||
nodeList = node.querySelectorAll(selector);
|
||||
j = nodeList.length;
|
||||
while ( j-- ) {
|
||||
targetNodes.push(nodeList[j]);
|
||||
}
|
||||
}
|
||||
return targetNodes;
|
||||
};
|
||||
|
||||
var filterAltGeneric = function(generics, root, out) {
|
||||
var alt = root.getAttribute('alt');
|
||||
if ( !alt || !alt.length ) {
|
||||
return;
|
||||
}
|
||||
var selector = '[alt="' + root.title + '"]';
|
||||
if ( generics[selector] && !injectedSelectors[selector] ) {
|
||||
out.push(selector);
|
||||
}
|
||||
selector = root.tagName + selector;
|
||||
if ( generics[selector] && !injectedSelectors[selector] ) {
|
||||
out.push(selector);
|
||||
}
|
||||
};
|
||||
|
||||
var filterLowGenerics = function(selectors, what) {
|
||||
if ( selectors[what + 'LowGenericCount'] === 0 ) {
|
||||
return;
|
||||
}
|
||||
var out = selectors[what];
|
||||
var generics = selectors[what + 'LowGenerics'];
|
||||
var nodeList, iNode;
|
||||
// Low generics: ["title"]
|
||||
nodeList = document.querySelectorAll('[title]');
|
||||
iNode = nodeList.length;
|
||||
while ( iNode-- ) {
|
||||
filterTitleGeneric(generics, nodeList[iNode], out);
|
||||
}
|
||||
// Low generics: ["alt"]
|
||||
nodeList = document.querySelectorAll('[alt]');
|
||||
iNode = nodeList.length;
|
||||
while ( iNode-- ) {
|
||||
filterAltGeneric(generics, nodeList[iNode], out);
|
||||
}
|
||||
};
|
||||
|
||||
var filterHighGenerics = function(selectors, what) {
|
||||
var out = selectors[what];
|
||||
var generics = selectors[what + 'HighGenerics'];
|
||||
var iGeneric = generics.length;
|
||||
var processLowGenerics = function(generics, out) {
|
||||
var i = generics.length;
|
||||
var selector;
|
||||
while ( iGeneric-- ) {
|
||||
selector = generics[iGeneric];
|
||||
if ( injectedSelectors[selector] ) {
|
||||
while ( i-- ) {
|
||||
selector = generics[i];
|
||||
if ( injectedSelectors[selector] !== undefined ) {
|
||||
continue;
|
||||
}
|
||||
if ( document.querySelector(selector) !== null ) {
|
||||
injectedSelectors[selector] = true;
|
||||
if ( out !== undefined ) {
|
||||
out.push(selector);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var reduce = function(selectors, dict) {
|
||||
var i = selectors.length, selector, end;
|
||||
while ( i-- ) {
|
||||
selector = selectors[i];
|
||||
if ( !dict[selector] ) {
|
||||
if ( end !== undefined ) {
|
||||
selectors.splice(i+1, end-i);
|
||||
end = undefined;
|
||||
var processHighLowGenerics = function(generics, out) {
|
||||
var attrs = ['title', 'alt'];
|
||||
var attr, attrValue, nodeList, iNode, node, selector;
|
||||
while ( attr = attrs.pop() ) {
|
||||
nodeList = selectNodes('[' + attr + ']');
|
||||
iNode = nodeList.length;
|
||||
while ( iNode-- ) {
|
||||
node = nodeList[iNode];
|
||||
attrValue = node.getAttribute(attr);
|
||||
if ( !attrValue ) { continue; }
|
||||
selector = '[' + attr + '="' + attrValue + '"]';
|
||||
if ( injectedSelectors[selector] === undefined && generics[selector] ) {
|
||||
injectedSelectors[selector] = true;
|
||||
if ( out !== undefined ) {
|
||||
out.push(selector);
|
||||
}
|
||||
}
|
||||
selector = node.tagName.toLowerCase() + selector;
|
||||
if ( injectedSelectors[selector] === undefined && generics[selector] ) {
|
||||
injectedSelectors[selector] = true;
|
||||
if ( out !== undefined ) {
|
||||
out.push(selector);
|
||||
}
|
||||
}
|
||||
dict[selector] = true;
|
||||
} else if ( end === undefined ) {
|
||||
end = i;
|
||||
}
|
||||
}
|
||||
if ( end !== undefined ) {
|
||||
selectors.splice(0, end+1);
|
||||
};
|
||||
|
||||
var processHighMediumGenerics = function(generics, out) {
|
||||
var nodeList = selectNodes('a[href^="http"]');
|
||||
var iNode = nodeList.length;
|
||||
var node, href, pos, hash, selector;
|
||||
while ( iNode-- ) {
|
||||
node = nodeList[iNode];
|
||||
href = node.getAttribute('href');
|
||||
if ( !href ) { continue; }
|
||||
pos = href.indexOf('://');
|
||||
if ( pos === -1 ) { continue; }
|
||||
hash = href.slice(pos + 3, pos + 11);
|
||||
selector = generics[hash];
|
||||
if ( selector === undefined ) { continue; }
|
||||
if ( injectedSelectors[selector] !== undefined ) { continue; }
|
||||
injectedSelectors[selector] = true;
|
||||
if ( out !== undefined ) {
|
||||
out.push(selector);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var processHighHighGenerics = function(generics, out) {
|
||||
if ( injectedSelectors[generics] !== undefined ) { return; }
|
||||
if ( document.querySelectorAll(generics) === null ) { return; }
|
||||
injectedSelectors[generics] = true;
|
||||
if ( out !== undefined ) {
|
||||
var selectors = generics.split(',\n');
|
||||
var i = selectors.length;
|
||||
while ( i-- ) {
|
||||
if ( injectedSelectors[selectors[i]] !== undefined ) {
|
||||
selectors.splice(i, 1);
|
||||
}
|
||||
}
|
||||
out.push(selectors.join(',\n'));
|
||||
}
|
||||
};
|
||||
|
||||
@ -362,25 +400,6 @@ var messaging = (function(name){
|
||||
}
|
||||
};
|
||||
|
||||
var processNodeLists = function(nodeLists) {
|
||||
var i = nodeLists.length;
|
||||
var nodeList, j, node;
|
||||
while ( i-- ) {
|
||||
nodeList = nodeLists[i];
|
||||
idsFromNodeList(nodeList);
|
||||
classesFromNodeList(nodeList);
|
||||
j = nodeList.length;
|
||||
while ( j-- ) {
|
||||
node = nodeList[j];
|
||||
if ( typeof node.querySelectorAll === 'function' ) {
|
||||
idsFromNodeList(node.querySelectorAll('[id]'));
|
||||
classesFromNodeList(node.querySelectorAll('[class]'));
|
||||
}
|
||||
}
|
||||
}
|
||||
retrieveGenericSelectors();
|
||||
};
|
||||
|
||||
domLoaded();
|
||||
|
||||
// Observe changes in the DOM only if...
|
||||
@ -390,19 +409,41 @@ var messaging = (function(name){
|
||||
return;
|
||||
}
|
||||
|
||||
var ignoreTags = {
|
||||
'style': true,
|
||||
'STYLE': true,
|
||||
'script': true,
|
||||
'SCRIPT': true
|
||||
};
|
||||
|
||||
var mutationObservedHandler = function(mutations) {
|
||||
var iMutation = mutations.length;
|
||||
var nodeLists = [], nodeList;
|
||||
var nodes = [];
|
||||
var nodeList, iNode, node;
|
||||
while ( iMutation-- ) {
|
||||
nodeList = mutations[iMutation].addedNodes;
|
||||
if ( nodeList && nodeList.length ) {
|
||||
nodeLists.push(nodeList);
|
||||
if ( !nodeList ) {
|
||||
continue;
|
||||
}
|
||||
iNode = nodeList.length;
|
||||
while ( iNode-- ) {
|
||||
node = nodeList[iNode];
|
||||
if ( typeof node.querySelectorAll !== 'function' ) {
|
||||
continue;
|
||||
}
|
||||
if ( ignoreTags[node.tagName] ) {
|
||||
continue;
|
||||
}
|
||||
contextNodes.push(node);
|
||||
}
|
||||
}
|
||||
if ( nodeLists.length ) {
|
||||
processNodeLists(nodeLists);
|
||||
if ( contextNodes.length !== 0 ) {
|
||||
idsFromNodeList(selectNodes('[id]'));
|
||||
classesFromNodeList(selectNodes('[class]'));
|
||||
retrieveGenericSelectors();
|
||||
}
|
||||
};
|
||||
|
||||
// https://github.com/gorhill/httpswitchboard/issues/176
|
||||
var observer = new MutationObserver(mutationObservedHandler);
|
||||
observer.observe(document.body, {
|
||||
@ -419,6 +460,8 @@ var messaging = (function(name){
|
||||
// https://github.com/gorhill/uBlock/issues/7
|
||||
|
||||
(function() {
|
||||
var messaging = uBlockMessaging;
|
||||
|
||||
var hideOne = function(elem, collapse) {
|
||||
// If `!important` is not there, going back using history will likely
|
||||
// cause the hidden element to re-appear.
|
||||
@ -430,7 +473,7 @@ var messaging = (function(name){
|
||||
|
||||
// First pass
|
||||
messaging.ask({ what: 'blockedRequests' }, function(details) {
|
||||
var elems = document.querySelectorAll('img,iframe');
|
||||
var elems = document.querySelectorAll('img,iframe,embed');
|
||||
var blockedRequests = details.blockedRequests;
|
||||
var collapse = details.collapse;
|
||||
var i = elems.length;
|
||||
@ -453,8 +496,9 @@ var messaging = (function(name){
|
||||
// - Elements which resource URL changes
|
||||
var onResourceLoaded = function(ev) {
|
||||
var target = ev.target;
|
||||
if ( target.tagName.toLowerCase() !== 'iframe' ) { return; }
|
||||
//console.debug('Loaded %s[src="%s"]', target.tagName, target.src);
|
||||
if ( !target || !target.src ) { return; }
|
||||
if ( target.tagName.toLowerCase() !== 'iframe' ) { return; }
|
||||
var onAnswerReceived = function(details) {
|
||||
if ( details.blocked ) {
|
||||
hideOne(target, details.collapse);
|
||||
@ -464,8 +508,9 @@ var messaging = (function(name){
|
||||
};
|
||||
var onResourceFailed = function(ev) {
|
||||
var target = ev.target;
|
||||
if ( target.tagName.toLowerCase() !== 'img' ) { return; }
|
||||
//console.debug('Failed to load %s[src="%s"]', target.tagName, target.src);
|
||||
if ( !target || !target.src ) { return; }
|
||||
if ( target.tagName.toLowerCase() !== 'img' ) { return; }
|
||||
var onAnswerReceived = function(details) {
|
||||
if ( details.blocked ) {
|
||||
hideOne(target, details.collapse);
|
||||
@ -478,5 +523,3 @@ var messaging = (function(name){
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
})();
|
||||
|
Loading…
Reference in New Issue
Block a user