mirror of https://github.com/gorhill/uBlock.git
fix #2598: refactor to address the cause rather than the symptoms
This commit is contained in:
parent
a8caba9cfd
commit
f3e6057e07
|
@ -121,8 +121,8 @@ var µBlock = (function() { // jshint ignore:line
|
|||
|
||||
// read-only
|
||||
systemSettings: {
|
||||
compiledMagic: 'alufjifllsxz',
|
||||
selfieMagic: 'alufjifllsxz'
|
||||
compiledMagic: 'ohszqbtqggmp',
|
||||
selfieMagic: 'ohszqbtqggmp'
|
||||
},
|
||||
|
||||
restoreBackupSettings: {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 re, assetKey, entry;
|
||||
for ( var candidate in candidates ) {
|
||||
re = candidates[candidate];
|
||||
for ( assetKey in listEntries ) {
|
||||
entry = listEntries[assetKey];
|
||||
if ( entry === undefined ) {
|
||||
continue;
|
||||
var response = Object.create(null),
|
||||
assetKey, entry, content, found, fargs;
|
||||
|
||||
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
|
||||
|
|
|
@ -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
|
@ -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
|
||||
);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
|
@ -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));
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
Loading…
Reference in New Issue