fix #2598: refactor to address the cause rather than the symptoms

This commit is contained in:
gorhill 2017-05-25 17:46:59 -04:00
parent a8caba9cfd
commit f3e6057e07
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
7 changed files with 618 additions and 819 deletions

View File

@ -121,8 +121,8 @@ var µBlock = (function() { // jshint ignore:line
// read-only
systemSettings: {
compiledMagic: 'alufjifllsxz',
selfieMagic: 'alufjifllsxz'
compiledMagic: 'ohszqbtqggmp',
selfieMagic: 'ohszqbtqggmp'
},
restoreBackupSettings: {

View File

@ -34,11 +34,6 @@ var µb = µBlock;
/******************************************************************************/
// Could be replaced with encodeURIComponent/decodeURIComponent,
// which seems faster on Firefox.
var encode = JSON.stringify;
var decode = JSON.parse;
var isValidCSSSelector = (function() {
var div = document.createElement('div'),
matchesFn;
@ -118,56 +113,23 @@ var histogram = function(label, buckets) {
console.log('\tTotal buckets count: %d', total);
};
*/
/******************************************************************************/
/*******************************************************************************
// Pure id- and class-based filters
// Examples:
// #A9AdsMiddleBoxTop
// .AD-POST
Each filter class will register itself in the map.
var FilterPlain = function() {
IMPORTANT: any change which modifies the mapping will have to be
reflected with µBlock.systemSettings.compiledMagic.
**/
var filterClasses = [];
var registerFilterClass = function(ctor) {
filterClasses[ctor.prototype.fid] = ctor;
};
FilterPlain.prototype.retrieve = function(s, out) {
out.push(s);
};
FilterPlain.prototype.fid = '#';
FilterPlain.prototype.toSelfie = function() {
};
FilterPlain.fromSelfie = function() {
return filterPlain;
};
var filterPlain = new FilterPlain();
/******************************************************************************/
// Id- and class-based filters with extra selector stuff following.
// Examples:
// #center_col > div[style="font-size:14px;margin-right:0;min-height:5px"] ...
// #adframe:not(frameset)
// .l-container > #fishtank
// body #sliding-popup
var FilterPlainMore = function(s) {
this.s = s;
};
FilterPlainMore.prototype.retrieve = function(s, out) {
out.push(this.s);
};
FilterPlainMore.prototype.fid = '#+';
FilterPlainMore.prototype.toSelfie = function() {
return this.s;
};
FilterPlainMore.fromSelfie = function(s) {
return new FilterPlainMore(s);
var filterFromCompiledData = function(args) {
return filterClasses[args[0]].load(args);
};
/******************************************************************************/
@ -187,23 +149,24 @@ var FilterHostname = function(s, hostname) {
this.hostname = hostname;
};
FilterHostname.prototype.fid = 8;
FilterHostname.prototype.retrieve = function(hostname, out) {
if ( hostname.endsWith(this.hostname) ) {
out.push(this.s);
}
};
FilterHostname.prototype.fid = 'h';
FilterHostname.prototype.toSelfie = function() {
return encode(this.s) + '\t' + this.hostname;
FilterHostname.prototype.compile = function() {
return [ this.fid, this.s, this.hostname ];
};
FilterHostname.fromSelfie = function(s) {
var pos = s.indexOf('\t');
return new FilterHostname(decode(s.slice(0, pos)), s.slice(pos + 1));
FilterHostname.load = function(data) {
return new FilterHostname(data[1], data[2]);
};
registerFilterClass(FilterHostname);
/******************************************************************************/
var FilterBucket = function(a, b) {
@ -211,12 +174,12 @@ var FilterBucket = function(a, b) {
this.filters = [];
if ( a !== undefined ) {
this.filters[0] = a;
if ( b !== undefined ) {
this.filters[1] = b;
}
this.filters[1] = b;
}
};
FilterBucket.prototype.fid = 10;
FilterBucket.prototype.add = function(a) {
this.filters.push(a);
};
@ -228,16 +191,26 @@ FilterBucket.prototype.retrieve = function(s, out) {
}
};
FilterBucket.prototype.fid = '[]';
FilterBucket.prototype.toSelfie = function() {
return this.filters.length.toString();
FilterBucket.prototype.compile = function() {
var out = [],
filters = this.filters;
for ( var i = 0, n = filters.length; i < n; i++ ) {
out[i] = filters[i].compile();
}
return [ this.fid, out ];
};
FilterBucket.fromSelfie = function() {
return new FilterBucket();
FilterBucket.load = function(data) {
var bucket = new FilterBucket(),
entries = data[1];
for ( var i = 0, n = entries.length; i < n; i++ ) {
bucket.filters[i] = filterFromCompiledData(entries[i]);
}
return bucket;
};
registerFilterClass(FilterBucket);
/******************************************************************************/
/******************************************************************************/
@ -1029,7 +1002,7 @@ FilterContainer.prototype.keyFromSelector = function(selector) {
/******************************************************************************/
FilterContainer.prototype.compile = function(s, out) {
FilterContainer.prototype.compile = function(s, writer) {
var parsed = this.parser.parse(s);
if ( parsed.cosmetic === false ) {
return false;
@ -1042,7 +1015,7 @@ FilterContainer.prototype.compile = function(s, out) {
var hostnames = parsed.hostnames;
var i = hostnames.length;
if ( i === 0 ) {
this.compileGenericSelector(parsed, out);
this.compileGenericSelector(parsed, writer);
return true;
}
@ -1056,10 +1029,10 @@ FilterContainer.prototype.compile = function(s, out) {
if ( hostname.startsWith('~') === false ) {
applyGlobally = false;
}
this.compileHostnameSelector(hostname, parsed, out);
this.compileHostnameSelector(hostname, parsed, writer);
}
if ( applyGlobally ) {
this.compileGenericSelector(parsed, out);
this.compileGenericSelector(parsed, writer);
}
return true;
@ -1067,17 +1040,17 @@ FilterContainer.prototype.compile = function(s, out) {
/******************************************************************************/
FilterContainer.prototype.compileGenericSelector = function(parsed, out) {
FilterContainer.prototype.compileGenericSelector = function(parsed, writer) {
if ( parsed.unhide === 0 ) {
this.compileGenericHideSelector(parsed, out);
this.compileGenericHideSelector(parsed, writer);
} else {
this.compileGenericUnhideSelector(parsed, out);
this.compileGenericUnhideSelector(parsed, writer);
}
};
/******************************************************************************/
FilterContainer.prototype.compileGenericHideSelector = function(parsed, out) {
FilterContainer.prototype.compileGenericHideSelector = function(parsed, writer) {
var selector = parsed.suffix,
type = selector.charAt(0),
key, matches;
@ -1089,12 +1062,12 @@ FilterContainer.prototype.compileGenericHideSelector = function(parsed, out) {
// is valid, the regex took care of this. Most generic selector falls
// into that category.
if ( key === selector ) {
out.push(4, 'lg\v' + key);
writer.push([ 0 /* lg */, key ]);
return;
}
// Composite CSS rule.
if ( this.compileSelector(selector) !== undefined ) {
out.push(4, 'lg+\v' + key + '\v' + selector);
writer.push([ 1 /* lg+ */, key, selector ]);
}
return;
}
@ -1105,21 +1078,14 @@ FilterContainer.prototype.compileGenericHideSelector = function(parsed, out) {
// ["title"] and ["alt"] will go in high-low generic bin.
if ( this.reHighLow.test(selector) ) {
out.push(4, 'hlg0\v' + selector);
writer.push([ 2 /* hlg0 */, selector ]);
return;
}
// [href^="..."] will go in high-medium generic bin.
matches = this.reHighMedium.exec(selector);
if ( matches && matches.length === 2 ) {
out.push(4, 'hmg0\v' + matches[1] + '\v' + selector);
return;
}
// script:contains(...)
// script:inject(...)
if ( this.reScriptSelector.test(selector) ) {
out.push(4, 'js\v0\v\v' + selector);
writer.push([ 3 /* hmg0 */, matches[1], selector ]);
return;
}
@ -1128,28 +1094,28 @@ FilterContainer.prototype.compileGenericHideSelector = function(parsed, out) {
// as a low generic cosmetic filter.
matches = this.rePlainSelectorEx.exec(selector);
if ( matches && matches.length === 2 ) {
out.push(4, 'lg+\v' + matches[1] + '\v' + selector);
writer.push([ 1 /* lg+ */, matches[1], selector ]);
return;
}
// All else: high-high generics.
// Distinguish simple vs complex selectors.
if ( selector.indexOf(' ') === -1 ) {
out.push(4, 'hhsg0\v' + selector);
writer.push([ 4 /* hhsg0 */, selector ]);
} else {
out.push(4, 'hhcg0\v' + selector);
writer.push([ 5 /* hhcg0 */, selector ]);
}
};
/******************************************************************************/
FilterContainer.prototype.compileGenericUnhideSelector = function(parsed, out) {
FilterContainer.prototype.compileGenericUnhideSelector = function(parsed, writer) {
var selector = parsed.suffix;
// script:contains(...)
// script:inject(...)
if ( this.reScriptSelector.test(selector) ) {
out.push(4, 'js\v1\v\v' + selector);
writer.push([ 6 /* js */, '!', '', selector ]);
return;
}
@ -1160,12 +1126,12 @@ FilterContainer.prototype.compileGenericUnhideSelector = function(parsed, out) {
// https://github.com/chrisaljoudi/uBlock/issues/497
// All generic exception filters are put in the same bucket: they are
// expected to be very rare.
out.push(4, 'g1\v' + compiled);
writer.push([ 7 /* g1 */, compiled ]);
};
/******************************************************************************/
FilterContainer.prototype.compileHostnameSelector = function(hostname, parsed, out) {
FilterContainer.prototype.compileHostnameSelector = function(hostname, parsed, writer) {
// https://github.com/chrisaljoudi/uBlock/issues/145
var unhide = parsed.unhide;
if ( hostname.startsWith('~') ) {
@ -1189,7 +1155,7 @@ FilterContainer.prototype.compileHostnameSelector = function(hostname, parsed, o
if ( unhide ) {
hash = '!' + hash;
}
out.push(4, 'js\v' + hash + '\v' + hostname + '\v' + selector);
writer.push([ 6 /* js */, hash, hostname, selector ]);
return;
}
@ -1207,251 +1173,212 @@ FilterContainer.prototype.compileHostnameSelector = function(hostname, parsed, o
hash = '!' + hash;
}
out.push(4, 'h\v' + hash + '\v' + hostname + '\v' + compiled);
// h, hash, example.com, .promoted-tweet
// h, hash, example.*, .promoted-tweet
writer.push([ 8 /* h */, hash, hostname, compiled ]);
};
/******************************************************************************/
FilterContainer.prototype.fromCompiledContent = function(
lineIter,
reader,
skipGenericCosmetic,
skipCosmetic
) {
if ( skipCosmetic ) {
this.skipCompiledContent(lineIter);
this.skipCompiledContent(reader);
return;
}
if ( skipGenericCosmetic ) {
this.skipGenericCompiledContent(lineIter);
this.skipGenericCompiledContent(reader);
return;
}
var lineBits, line, field0, field1, field2, field3, filter, bucket,
aCharCode = 'a'.charCodeAt(0),
fieldIter = new µb.FieldIterator('\v');
while ( lineIter.eot() === false ) {
lineBits = lineIter.charCodeAt(0) - aCharCode;
if ( (lineBits & 0x04) === 0 ) {
return;
}
line = lineIter.next(1);
if ( (lineBits & 0x02) !== 0 ) {
line = decodeURIComponent(line);
}
var fingerprint, args, filter, bucket;
while ( reader.next() === true ) {
this.acceptedCount += 1;
if ( this.duplicateBuster.has(line) ) {
fingerprint = reader.fingerprint();
if ( this.duplicateBuster.has(fingerprint) ) {
this.discardedCount += 1;
continue;
}
this.duplicateBuster.add(line);
this.duplicateBuster.add(fingerprint);
field0 = fieldIter.first(line);
field1 = fieldIter.next();
args = reader.args();
// h [\v] hash [\v] example.com [\v] .promoted-tweet
// h [\v] hash [\v] example.* [\v] .promoted-tweet
if ( field0 === 'h' ) {
field2 = fieldIter.next();
field3 = fieldIter.next();
filter = new FilterHostname(field3, field2);
bucket = this.specificFilters.get(field1);
switch ( args[0] ) {
// .largeAd
case 0:
bucket = this.lowGenericHideEx.get(args[1]);
if ( bucket === undefined ) {
this.specificFilters.set(field1, filter);
} else if ( bucket instanceof FilterBucket ) {
bucket.add(filter);
} else {
this.specificFilters.set(field1, new FilterBucket(bucket, filter));
}
continue;
}
// lg [\v] .largeAd
if ( field0 === 'lg' ) {
bucket = this.lowGenericHideEx.get(field1);
if ( bucket === undefined ) {
this.lowGenericHide.add(field1);
this.lowGenericHide.add(args[1]);
} else if ( Array.isArray(bucket) ) {
bucket.push(field1);
bucket.push(args[1]);
} else {
this.lowGenericHideEx.set(field1, [ bucket, field1 ]);
this.lowGenericHideEx.set(args[1], [ bucket, args[1] ]);
}
this.lowGenericHideCount += 1;
continue;
}
break;
// lg+ [\v] .Mpopup [\v] .Mpopup + #Mad > #MadZone
if ( field0 === 'lg+' ) {
field2 = fieldIter.next();
bucket = this.lowGenericHideEx.get(field1);
// .Mpopup, .Mpopup + #Mad > #MadZone
case 1:
bucket = this.lowGenericHideEx.get(args[1]);
if ( bucket === undefined ) {
if ( this.lowGenericHide.has(field1) ) {
this.lowGenericHideEx.set(field1, [ field1, field2 ]);
if ( this.lowGenericHide.has(args[1]) ) {
this.lowGenericHideEx.set(args[1], [ args[1], args[2] ]);
} else {
this.lowGenericHideEx.set(field1, field2);
this.lowGenericHide.add(field1);
this.lowGenericHideEx.set(args[1], args[2]);
this.lowGenericHide.add(args[1]);
}
} else if ( Array.isArray(bucket) ) {
bucket.push(field2);
bucket.push(args[2]);
} else {
this.lowGenericHideEx.set(field1, [ bucket, field2 ]);
this.lowGenericHideEx.set(args[1], [ bucket, args[2] ]);
}
this.lowGenericHideCount += 1;
continue;
}
break;
if ( field0 === 'hlg0' ) {
this.highLowGenericHide[field1] = true;
// ["title"]
// ["alt"]
case 2:
this.highLowGenericHide[args[1]] = true;
this.highLowGenericHideCount += 1;
continue;
}
break;
if ( field0 === 'hmg0' ) {
field2 = fieldIter.next();
bucket = this.highMediumGenericHide[field1];
// [href^="..."]
case 3:
bucket = this.highMediumGenericHide[args[1]];
if ( bucket === undefined ) {
this.highMediumGenericHide[field1] = field2;
this.highMediumGenericHide[args[1]] = args[2];
} else if ( Array.isArray(bucket) ) {
bucket.push(field2);
bucket.push(args[2]);
} else {
this.highMediumGenericHide[field1] = [bucket, field2];
this.highMediumGenericHide[args[1]] = [bucket, args[2]];
}
this.highMediumGenericHideCount += 1;
continue;
}
break;
if ( field0 === 'hhsg0' ) {
this.highHighSimpleGenericHideArray.push(field1);
// High-high generic hide/simple selectors
// div[id^="allo"]
case 4:
this.highHighSimpleGenericHideArray.push(args[1]);
this.highHighSimpleGenericHideCount += 1;
continue;
}
break;
if ( field0 === 'hhcg0' ) {
this.highHighComplexGenericHideArray.push(field1);
// High-high generic hide/complex selectors
// div[id^="allo"] > span
case 5:
this.highHighComplexGenericHideArray.push(args[1]);
this.highHighComplexGenericHideCount += 1;
continue;
}
break;
// js [\v] hash [\v] example.com [\v] script:contains(...)
// js [\v] hash [\v] example.com [\v] script:inject(...)
if ( field0 === 'js' ) {
field2 = fieldIter.next();
field3 = fieldIter.next();
this.createScriptFilter(field1, field2, field3);
continue;
}
// js, hash, example.com, script:contains(...)
// js, hash, example.com, script:inject(...)
case 6:
this.createScriptFilter(args[1], args[2], args[3]);
break;
// https://github.com/chrisaljoudi/uBlock/issues/497
// Generic exception filters: expected to be a rare occurrence.
if ( field0 === 'g1' ) {
this.genericDonthide.push(field1);
continue;
}
// #@#.tweet
case 7:
this.genericDonthide.push(args[1]);
break;
this.discardedCount += 1;
// h, hash, example.com, .promoted-tweet
// h, hash, example.*, .promoted-tweet
case 8:
filter = new FilterHostname(args[3], args[2]);
bucket = this.specificFilters.get(args[1]);
if ( bucket === undefined ) {
this.specificFilters.set(args[1], filter);
} else if ( bucket instanceof FilterBucket ) {
bucket.add(filter);
} else {
this.specificFilters.set(args[1], new FilterBucket(bucket, filter));
}
break;
default:
this.discardedCount += 1;
break;
}
}
};
/******************************************************************************/
FilterContainer.prototype.skipGenericCompiledContent = function(lineIter) {
var lineBits, line, field0, field1, field2, field3, filter, bucket,
aCharCode = 'a'.charCodeAt(0),
fieldIter = new µb.FieldIterator('\v');
while ( lineIter.eot() === false ) {
lineBits = lineIter.charCodeAt(0) - aCharCode;
if ( (lineBits & 0x04) === 0 ) {
return;
}
line = lineIter.next(1);
if ( (lineBits & 0x02) !== 0 ) {
line = decodeURIComponent(line);
}
FilterContainer.prototype.skipGenericCompiledContent = function(reader) {
var fingerprint, args, filter, bucket;
while ( reader.next() === true ) {
this.acceptedCount += 1;
if ( this.duplicateBuster.has(line) ) {
fingerprint = reader.fingerprint();
if ( this.duplicateBuster.has(fingerprint) ) {
this.discardedCount += 1;
continue;
}
field0 = fieldIter.first(line);
field1 = fieldIter.next();
args = reader.args();
// h [\v] hash [\v] example.com [\v] .promoted-tweet
// h [\v] hash [\v] example.* [\v] .promoted-tweet
if ( field0 === 'h' ) {
field2 = fieldIter.next();
field3 = fieldIter.next();
this.duplicateBuster.add(line);
filter = new FilterHostname(field3, field2);
bucket = this.specificFilters.get(field1);
if ( bucket === undefined ) {
this.specificFilters.set(field1, filter);
} else if ( bucket instanceof FilterBucket ) {
bucket.add(filter);
} else {
this.specificFilters.set(field1, new FilterBucket(bucket, filter));
}
continue;
}
switch ( args[0] ) {
// js [\v] hash [\v] example.com [\v] script:contains(...)
// js [\v] hash [\v] example.com [\v] script:inject(...)
if ( field0 === 'js' ) {
field2 = fieldIter.next();
field3 = fieldIter.next();
this.duplicateBuster.add(line);
this.createScriptFilter(field1, field2, field3);
continue;
}
// js, hash, example.com, script:contains(...)
// js, hash, example.com, script:inject(...)
case 6:
this.duplicateBuster.add(fingerprint);
this.createScriptFilter(args[1], args[2], args[3]);
break;
// https://github.com/chrisaljoudi/uBlock/issues/497
// Generic exception filters: expected to be a rare occurrence.
if ( field0 === 'g1' ) {
this.duplicateBuster.add(line);
this.genericDonthide.push(field1);
continue;
}
case 7:
this.duplicateBuster.add(fingerprint);
this.genericDonthide.push(args[1]);
break;
this.discardedCount += 1;
// h, hash, example.com, .promoted-tweet
// h, hash, example.*, .promoted-tweet
case 8:
this.duplicateBuster.add(fingerprint);
filter = new FilterHostname(args[3], args[2]);
bucket = this.specificFilters.get(args[1]);
if ( bucket === undefined ) {
this.specificFilters.set(args[1], filter);
} else if ( bucket instanceof FilterBucket ) {
bucket.add(filter);
} else {
this.specificFilters.set(args[1], new FilterBucket(bucket, filter));
}
break;
default:
this.discardedCount += 1;
break;
}
}
};
/******************************************************************************/
FilterContainer.prototype.skipCompiledContent = function(lineIter) {
var lineBits, line, field0, field1, field2, field3,
aCharCode = 'a'.charCodeAt(0),
fieldIter = new µb.FieldIterator('\v');
while ( lineIter.eot() === false ) {
lineBits = lineIter.charCodeAt(0) - aCharCode;
if ( (lineBits & 0x04) === 0 ) {
return;
}
line = lineIter.next(1);
if ( (lineBits & 0x02) !== 0 ) {
line = decodeURIComponent(line);
}
FilterContainer.prototype.skipCompiledContent = function(reader) {
var fingerprint, args;
while ( reader.next() === true ) {
this.acceptedCount += 1;
if ( this.duplicateBuster.has(line) ) {
this.discardedCount += 1;
continue;
}
field0 = fieldIter.first(line);
args = reader.args();
// js [\v] hash [\v] example.com [\v] script:contains(...)
// js [\v] hash [\v] example.com [\v] script:inject(...)
if ( field0 === 'js' ) {
this.duplicateBuster.add(line);
field1 = fieldIter.next();
field2 = fieldIter.next();
field3 = fieldIter.next();
this.createScriptFilter(field1, field2, field3);
// js, hash, example.com, script:contains(...)
// js, hash, example.com, script:inject(...)
if ( args[0] === 6 ) {
fingerprint = reader.fingerprint();
if ( this.duplicateBuster.has(fingerprint) === false ) {
this.duplicateBuster.add(fingerprint);
this.createScriptFilter(args[1], args[2], args[3]);
}
continue;
}
@ -1665,21 +1592,12 @@ FilterContainer.prototype._reEscapeScriptArg = /[\\'"]/g;
FilterContainer.prototype.toSelfie = function() {
var selfieFromMap = function(map) {
var selfie = [],
bucket, ff, f;
var selfie = [];
// Note: destructuring assignment not supported before Chromium 49.
for ( var entry of map ) {
selfie.push('k\t' + entry[0]);
bucket = entry[1];
selfie.push(bucket.fid + '\t' + bucket.toSelfie());
if ( bucket.fid !== '[]' ) { continue; }
ff = bucket.filters;
for ( var j = 0, nj = ff.length; j < nj; j++ ) {
f = ff[j];
selfie.push(f.fid + '\t' + f.toSelfie());
}
selfie.push([ entry[0], entry[1].compile() ]);
}
return selfie.join('\n');
return JSON.stringify(selfie);
};
return {
@ -1709,46 +1627,15 @@ FilterContainer.prototype.toSelfie = function() {
/******************************************************************************/
FilterContainer.prototype.fromSelfie = function(selfie) {
var factories = {
'[]': FilterBucket,
'#': FilterPlain,
'#+': FilterPlainMore,
'h': FilterHostname
};
var mapFromSelfie = function(selfie) {
var map = new Map(),
key,
bucket = null,
rawText = selfie,
rawEnd = rawText.length,
lineBeg = 0, lineEnd,
line, pos, what, factory;
while ( lineBeg < rawEnd ) {
lineEnd = rawText.indexOf('\n', lineBeg);
if ( lineEnd < 0 ) {
lineEnd = rawEnd;
}
line = rawText.slice(lineBeg, lineEnd);
lineBeg = lineEnd + 1;
pos = line.indexOf('\t');
what = line.slice(0, pos);
if ( what === 'k' ) {
key = line.slice(pos + 1);
bucket = null;
continue;
}
factory = factories[what];
if ( bucket === null ) {
bucket = factory.fromSelfie(line.slice(pos + 1));
map.set(key, bucket);
continue;
}
// When token key is reused, it can't be anything
// else than FilterBucket
bucket.add(factory.fromSelfie(line.slice(pos + 1)));
var entries = JSON.parse(selfie),
out = new Map(),
entry;
for ( var i = 0, n = entries.length; i < n; i++ ) {
entry = entries[i];
out.set(entry[0], filterFromCompiledData(entry[1]));
}
return map;
return out;
};
this.acceptedCount = selfie.acceptedCount;
@ -1909,10 +1796,8 @@ FilterContainer.prototype.retrieveGenericSelectors = function(request) {
selector, bucket;
while ( i-- ) {
selector = selectors[i];
if ( this.lowGenericHide.has(selector) === false ) {
continue;
}
if ( (bucket = this.lowGenericHideEx.get(selector)) ) {
if ( this.lowGenericHide.has(selector) === false ) { continue; }
if ( (bucket = this.lowGenericHideEx.get(selector)) !== undefined ) {
if ( Array.isArray(bucket) ) {
hideSelectors = hideSelectors.concat(bucket);
} else {

View File

@ -25,7 +25,8 @@
/******************************************************************************/
var listEntries = Object.create(null);
var listEntries = Object.create(null),
filterClassSeparator = '\n/* end of network - start of cosmetic */\n';
/******************************************************************************/
@ -35,20 +36,19 @@ var reEscape = function(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
};
var reSpecialNetworkChars = /[a-d]/;
/******************************************************************************/
var fromNetFilter = function(details) {
var lists = [];
var compiledFilter = details.compiledFilter;
var entry, content, pos, notFound;
var lists = [],
compiledFilter = details.compiledFilter,
entry, content, pos, notFound;
for ( var assetKey in listEntries ) {
entry = listEntries[assetKey];
if ( entry === undefined ) {
continue;
}
content = entry.content;
if ( entry === undefined ) { continue; }
content = entry.content.slice(
0,
entry.content.indexOf(filterClassSeparator)
);
pos = 0;
for (;;) {
pos = content.indexOf(compiledFilter, pos);
@ -56,19 +56,19 @@ var fromNetFilter = function(details) {
// We need an exact match.
// https://github.com/gorhill/uBlock/issues/1392
// https://github.com/gorhill/uBlock/issues/835
pos -= 1;
notFound =
reSpecialNetworkChars.test(content.charAt(pos)) === false ||
pos !== 0 && content.charCodeAt(pos - 1) !== 0x0A /* '\n' */;
pos += 1 + compiledFilter.length;
if ( notFound ) { continue; }
if ( pos === content.length || content.charCodeAt(pos) === 0x0A ) {
lists.push({
title: entry.title,
supportURL: entry.supportURL
});
break;
notFound = pos !== 0 && content.charCodeAt(pos - 1) !== 0x0A;
pos += compiledFilter.length;
if (
notFound ||
pos !== content.length && content.charCodeAt(pos) !== 0x0A
) {
continue;
}
lists.push({
title: entry.title,
supportURL: entry.supportURL
});
break;
}
}
@ -104,93 +104,92 @@ var fromNetFilter = function(details) {
// the various compiled versions.
var fromCosmeticFilter = function(details) {
var filter = details.rawFilter;
var exception = filter.startsWith('#@#');
var match = /^#@?#/.exec(details.rawFilter),
prefix = match[0],
filter = details.rawFilter.slice(prefix.length);
filter = exception ? filter.slice(3) : filter.slice(2);
var compiled = JSON.stringify(filter),
reFilter = new RegExp('(^|\\n).*?' + reEscape(compiled) + '.*?(\\n|$)', 'g');
var candidates = Object.create(null);
var response = Object.create(null);
var reHostname = new RegExp(
'^' +
details.hostname.split('.').reduce(
function(acc, item) {
return acc === ''
? item
: '(' + acc + '\\.)?' + item;
},
''
) +
'$'
);
// First step: assuming the filter is generic, find out its compiled
// representation.
// Reference: FilterContainer.compileGenericSelector().
var reStr = [];
var matches = rePlainSelector.exec(filter);
if ( matches ) {
if ( matches[0] === filter ) { // simple CSS selector
reStr.push('[e-h]lg', reEscape(filter));
} else { // complex CSS selector
reStr.push('[e-h]lg\\+', reEscape(matches[0]), reEscape(filter));
}
} else if ( reHighLow.test(filter) ) { // [alt] or [title]
reStr.push('[e-h]hlg0', reEscape(filter));
} else if ( reHighMedium.test(filter) ) { // [href^="..."]
reStr.push('[e-h]hmg0', '[^"]{8}', '[a-z]*' + reEscape(filter));
} else if ( filter.indexOf(' ') === -1 ) { // high-high-simple selector
reStr.push('[e-h]hhsg0', reEscape(filter));
} else { // high-high-complex selector
reStr.push('[e-h]hhcg0', reEscape(filter));
}
candidates[details.rawFilter] = new RegExp(reStr.join('\\v') + '(?:\\n|$)');
// Procedural filters, which are pre-compiled, make thing sort of
// complicated. We are going to also search for one portion of the
// compiled form of a filter.
var filterEx = '(' +
reEscape(filter) +
'|\{[^\\v]*' +
reEscape(JSON.stringify({ raw: filter }).slice(1,-1)) +
'[^\\v]*\})';
// Second step: find hostname-based versions.
// Reference: FilterContainer.compileHostnameSelector().
var pos,
hostname = details.hostname;
if ( hostname !== '' ) {
for ( ;; ) {
candidates[hostname + '##' + filter] = new RegExp(
['[e-h]h', '[^\\v]+', reEscape(hostname), filterEx].join('\\v') +
'(?:\\n|$)'
);
pos = hostname.indexOf('.');
if ( pos === -1 ) {
break;
}
hostname = hostname.slice(pos + 1);
}
}
// Last step: find entity-based versions.
// Reference: FilterContainer.compileEntitySelector().
var domain = details.domain;
pos = domain.indexOf('.');
var reEntity,
domain = details.domain,
pos = domain.indexOf('.');
if ( pos !== -1 ) {
var entity = domain.slice(0, pos) + '.*';
candidates[entity + '##' + filter] = new RegExp(
['[e-h]h', '[^\\v]+', reEscape(entity), filterEx].join('\\v') +
'(?:\\n|$)'
reEntity = new RegExp(
'^' +
domain.slice(0, pos).split('.').reduce(
function(acc, item) {
return acc === ''
? item
: '(' + acc + '\\.)?' + item;
},
''
) +
'\\.\\*$'
);
}
var response = Object.create(null),
assetKey, entry, content, found, fargs;
var re, assetKey, entry;
for ( var candidate in candidates ) {
re = candidates[candidate];
for ( assetKey in listEntries ) {
entry = listEntries[assetKey];
if ( entry === undefined ) {
continue;
for ( assetKey in listEntries ) {
entry = listEntries[assetKey];
if ( entry === undefined ) { continue; }
content = entry.content.slice(
entry.content.indexOf(filterClassSeparator) +
filterClassSeparator.length
);
found = undefined;
while ( (match = reFilter.exec(content)) !== null ) {
fargs = JSON.parse(match[0]);
switch ( fargs[0] ) {
case 0:
case 2:
case 4:
case 5:
case 7:
found = prefix + filter;
break;
case 1:
case 3:
if ( fargs[2] === filter ) {
found = prefix + filter;
}
break;
case 6:
case 8:
if (
fargs[2] === '' ||
reHostname.test(fargs[2]) === true ||
reEntity !== undefined && reEntity.test(fargs[2]) === true
) {
found = fargs[2] + prefix + filter;
}
break;
}
if ( re.test(entry.content) === false ) {
continue;
if ( found !== undefined ) {
if ( response[found] === undefined ) {
response[found] = [];
}
response[found].push({
title: entry.title,
supportURL: entry.supportURL
});
break;
}
if ( response[candidate] === undefined ) {
response[candidate] = [];
}
response[candidate].push({
title: entry.title,
supportURL: entry.supportURL
});
}
}
@ -200,10 +199,6 @@ var fromCosmeticFilter = function(details) {
});
};
var rePlainSelector = /^([#.][\w-]+)/;
var reHighLow = /^[a-z]*\[(?:alt|title)="[^"]+"\]$/;
var reHighMedium = /^\[href\^="https?:\/\/([^"]{8})[^"]*"\]$/;
/******************************************************************************/
onmessage = function(e) { // jshint ignore:line

View File

@ -28,7 +28,7 @@
/******************************************************************************/
var worker = null;
var workerTTL = 11 * 60 * 1000;
var workerTTL = 5 * 60 * 1000;
var workerTTLTimer = null;
var needLists = true;
var messageId = 1;

File diff suppressed because it is too large Load Diff

View File

@ -737,7 +737,8 @@
/******************************************************************************/
µBlock.compileFilters = function(rawText) {
var compiledFilters = new this.CompiledOutput();
var networkFilters = new this.CompiledLineWriter(),
cosmeticFilters = new this.CompiledLineWriter();
// Useful references:
// https://adblockplus.org/en/filter-cheatsheet
@ -766,7 +767,7 @@
// Parse or skip cosmetic filters
// All cosmetic filters are caught here
if ( cosmeticFilteringEngine.compile(line, compiledFilters) ) {
if ( cosmeticFilteringEngine.compile(line, cosmeticFilters) ) {
continue;
}
@ -800,10 +801,12 @@
if ( line.length === 0 ) { continue; }
staticNetFilteringEngine.compile(line, compiledFilters);
staticNetFilteringEngine.compile(line, networkFilters);
}
return compiledFilters.toString();
return networkFilters.toString() +
'\n/* end of network - start of cosmetic */\n' +
cosmeticFilters.toString();
};
/******************************************************************************/
@ -813,15 +816,16 @@
// applying 1st-party filters.
µBlock.applyCompiledFilters = function(rawText, firstparty) {
var skipCosmetic = !firstparty && !this.userSettings.parseAllABPHideFilters,
skipGenericCosmetic = this.userSettings.ignoreGenericCosmeticFilters,
staticNetFilteringEngine = this.staticNetFilteringEngine,
cosmeticFilteringEngine = this.cosmeticFilteringEngine,
lineIter = new this.LineIterator(rawText);
while ( lineIter.eot() === false ) {
cosmeticFilteringEngine.fromCompiledContent(lineIter, skipGenericCosmetic, skipCosmetic);
staticNetFilteringEngine.fromCompiledContent(lineIter);
}
if ( rawText === '' ) { return; }
var separator = '\n/* end of network - start of cosmetic */\n',
pos = rawText.indexOf(separator),
reader = new this.CompiledLineReader(rawText.slice(0, pos));
this.staticNetFilteringEngine.fromCompiledContent(reader);
this.cosmeticFilteringEngine.fromCompiledContent(
reader.reset(rawText.slice(pos + separator.length)),
this.userSettings.ignoreGenericCosmeticFilters,
!firstparty && !this.userSettings.parseAllABPHideFilters
);
};
/******************************************************************************/

View File

@ -223,51 +223,58 @@
/******************************************************************************/
µBlock.CompiledOutput = function() {
this.bufferLen = 8192;
this.buffer = new Uint8Array(this.bufferLen);
this.offset = 0;
µBlock.CompiledLineWriter = function() {
this.output = [];
this.stringifier = JSON.stringify;
};
µBlock.CompiledOutput.prototype.push = function(lineBits, line) {
var lineLen = line.length,
offset = this.offset,
need = offset + 2 + lineLen; // lineBits, line, \n
if ( need > this.bufferLen ) {
this.grow(need);
µBlock.CompiledLineWriter.fingerprint = function(args) {
return JSON.stringify(args);
};
µBlock.CompiledLineWriter.prototype = {
push: function(args) {
this.output[this.output.length] = this.stringifier(args);
},
toString: function() {
return this.output.join('\n');
}
var buffer = this.buffer;
if ( offset !== 0 ) {
buffer[offset++] = 0x0A /* '\n' */;
}
buffer[offset++] = 0x61 /* 'a' */ + lineBits;
for ( var i = 0, c; i < lineLen; i++ ) {
c = line.charCodeAt(i);
if ( c > 0x7F ) {
return this.push(lineBits | 0x02, encodeURIComponent(line));
};
µBlock.CompiledLineReader = function(raw) {
this.reset(raw);
this.parser = JSON.parse;
};
µBlock.CompiledLineReader.prototype = {
reset: function(raw) {
this.input = raw;
this.len = raw.length;
this.offset = 0;
this.s = '';
return this;
},
next: function() {
if ( this.offset === this.len ) {
this.s = '';
return false;
}
buffer[offset++] = c;
var pos = this.input.indexOf('\n', this.offset);
if ( pos !== -1 ) {
this.s = this.input.slice(this.offset, pos);
this.offset = pos + 1;
} else {
this.s = this.input.slice(this.offset);
this.offset = this.len;
}
return true;
},
fingerprint: function() {
return this.s;
},
args: function() {
return this.parser(this.s);
}
this.offset = offset;
};
µBlock.CompiledOutput.prototype.grow = function(need) {
var newBufferLen = Math.min(
2097152,
1 << Math.ceil(Math.log(need) / Math.log(2))
);
while ( newBufferLen < need ) {
newBufferLen += 1048576;
}
var newBuffer = new Uint8Array(newBufferLen);
newBuffer.set(this.buffer);
this.buffer = newBuffer;
this.bufferLen = newBufferLen;
};
µBlock.CompiledOutput.prototype.toString = function() {
var decoder = new TextDecoder();
return decoder.decode(new Uint8Array(this.buffer.buffer, 0, this.offset));
};
/******************************************************************************/