refactor where appropriate to make use of ES6 Set/Map (#1070)

At the same time, the following issues were fixed:
- #1954: automatically lookup site-specific scriptlets
- https://github.com/uBlockOrigin/uAssets/issues/23
This commit is contained in:
gorhill 2016-09-12 10:22:25 -04:00
parent aa20b6185d
commit a7fe367eec
8 changed files with 843 additions and 620 deletions

View File

@ -0,0 +1,214 @@
/*******************************************************************************
uBlock Origin - a browser extension to block requests.
Copyright (C) 2014-2016 The uBlock Origin authors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uBlock
*/
// For background page or non-background pages
'use strict';
/******************************************************************************/
/******************************************************************************/
// https://github.com/gorhill/uBlock/issues/1067
// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith
// Firefox 17/Chromium 41 supports `startsWith`.
if ( String.prototype.startsWith instanceof Function === false ) {
String.prototype.startsWith = function(needle, pos) {
if ( typeof pos !== 'number' ) {
pos = 0;
}
return this.lastIndexOf(needle, pos) === pos;
};
}
/******************************************************************************/
// https://github.com/gorhill/uBlock/issues/1067
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith
// Firefox 17/Chromium 41 supports `endsWith`.
if ( String.prototype.endsWith instanceof Function === false ) {
String.prototype.endsWith = function(needle, pos) {
if ( typeof pos !== 'number' ) {
pos = this.length;
}
pos -= needle.length;
return this.indexOf(needle, pos) === pos;
};
}
/******************************************************************************/
// https://github.com/gorhill/uBlock/issues/1070
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set#Browser_compatibility
// This polyfill is designed to fulfill *only* what uBlock Origin needs -- this
// is not an accurate API of the real Set() type.
if ( self.Set instanceof Function === false ) {
self.Set = function(iter) {
this.clear();
if ( Array.isArray(iter) ) {
for ( var i = 0, n = iter.length; i < n; i++ ) {
this.add(iter[i]);
}
return;
}
};
self.Set.polyfill = true;
self.Set.prototype.clear = function() {
this._set = Object.create(null);
this.size = 0;
// Iterator stuff
this._values = undefined;
this._i = undefined;
this.value = undefined;
this.done = true;
};
self.Set.prototype.add = function(k) {
if ( this._set[k] === undefined ) {
this._set[k] = true;
this.size += 1;
}
return this;
};
self.Set.prototype.delete = function(k) {
if ( this._set[k] !== undefined ) {
delete this._set[k];
this.size -= 1;
return true;
}
return false;
};
self.Set.prototype.has = function(k) {
return this._set[k] !== undefined;
};
self.Set.prototype.next = function() {
if ( this._i < this.size ) {
this.value = this._values[this._i++];
} else {
this._values = undefined;
this.value = undefined;
this.done = true;
}
return this;
};
self.Set.prototype.values = function() {
this._values = Object.keys(this._set);
this._i = 0;
this.value = undefined;
this.done = false;
return this;
};
}
/******************************************************************************/
// https://github.com/gorhill/uBlock/issues/1070
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set#Browser_compatibility
// This polyfill is designed to fulfill *only* what uBlock Origin needs -- this
// is not an accurate API of the real Map() type.
if ( self.Map instanceof Function === false ) {
self.Map = function(iter) {
this.clear();
if ( Array.isArray(iter) ) {
for ( var i = 0, n = iter.length, entry; i < n; i++ ) {
entry = iter[i];
this.set(entry[0], entry[1]);
}
return;
}
};
self.Map.polyfill = true;
self.Map.prototype.clear = function() {
this._map = Object.create(null);
this.size = 0;
// Iterator stuff
this._keys = undefined;
this._i = undefined;
this.value = undefined;
this.done = true;
};
self.Map.prototype.delete = function(k) {
if ( this._map[k] !== undefined ) {
delete this._map[k];
this.size -= 1;
return true;
}
return false;
};
self.Map.prototype.entries = function() {
this._keys = Object.keys(this._map);
this._i = 0;
this.value = [ undefined, undefined ];
this.done = false;
return this;
};
self.Map.prototype.get = function(k) {
return this._map[k];
};
self.Map.prototype.has = function(k) {
return this._map[k] !== undefined;
};
self.Map.prototype.next = function() {
if ( this._i < this.size ) {
var key = this._keys[this._i++];
this.value[0] = key;
this.value[1] = this._map[key];
} else {
this._keys = undefined;
this.value = undefined;
this.done = true;
}
return this;
};
self.Map.prototype.set = function(k, v) {
if ( v !== undefined ) {
if ( this._map[k] === undefined ) {
this.size += 1;
}
this._map[k] = v;
} else {
if ( this._map[k] !== undefined ) {
this.size -= 1;
}
delete this._map[k];
}
return this;
};
}
/******************************************************************************/

View File

@ -0,0 +1,74 @@
/*******************************************************************************
uBlock Origin - a browser extension to block requests.
Copyright (C) 2016 The uBlock Origin authors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uBlock
*/
// For background page or non-background pages
'use strict';
/******************************************************************************/
// Patching for Pale Moon which does not implement ES6 Set/Map.
// Test for non-ES6 Set/Map: check if property `iterator` is present.
// The code is strictly to satisfy uBO's core, not to be an accurate
// implementation of ES6.
if ( self.Set.prototype.iterator instanceof Function ) {
//console.log('Patching non-ES6 Set() to be more ES6-like.');
self.Set.prototype._values = self.Set.prototype.values;
self.Set.prototype.values = function() {
this._valueIter = this._values();
this.value = undefined;
this.done = false;
return this;
};
self.Set.prototype.next = function() {
try {
this.value = this._valueIter.next();
} catch (ex) {
this._valueIter = undefined;
this.value = undefined;
this.done = true;
}
return this;
};
}
if ( self.Map.prototype.iterator instanceof Function ) {
//console.log('Patching non-ES6 Map() to be more ES6-like.');
self.Map.prototype._entries = self.Map.prototype.entries;
self.Map.prototype.entries = function() {
this._entryIter = this._entries();
this.value = undefined;
this.done = false;
return this;
};
self.Map.prototype.next = function() {
try {
this.value = this._entryIter.next();
} catch (ex) {
this._entryIter = undefined;
this.value = undefined;
this.done = true;
}
return this;
};
}

View File

@ -5,10 +5,10 @@
<title>uBlock Origin</title> <title>uBlock Origin</title>
</head> </head>
<body> <body>
<script src="js/polyfill.js"></script>
<script src="lib/punycode.js"></script> <script src="lib/punycode.js"></script>
<script src="lib/publicsuffixlist.js"></script> <script src="lib/publicsuffixlist.js"></script>
<script src="lib/yamd5.js"></script> <script src="lib/yamd5.js"></script>
<script src="js/vapi-polyfill.js"></script>
<script src="js/vapi-common.js"></script> <script src="js/vapi-common.js"></script>
<script src="js/vapi-background.js"></script> <script src="js/vapi-background.js"></script>
<script src="js/background.js"></script> <script src="js/background.js"></script>

View File

@ -93,8 +93,8 @@ return {
// read-only // read-only
systemSettings: { systemSettings: {
compiledMagic: 'splsmclwnvoj', compiledMagic: 'ryegxvatkfxe',
selfieMagic: 'rkzqonintytj' selfieMagic: 'ryegxvatkfxe'
}, },
restoreBackupSettings: { restoreBackupSettings: {

File diff suppressed because it is too large Load Diff

View File

@ -987,166 +987,24 @@ FilterRegexHostname.fromSelfie = function(s) {
// Dictionary of hostnames // Dictionary of hostnames
// //
// FilterHostnameDict is the main reason why uBlock is not equipped to keep
// track of which filter comes from which list, and also why it's not equipped
// to be able to disable a specific filter -- other than through using a
// counter-filter.
//
// On the other hand it is also *one* of the reason uBlock's memory and CPU
// footprint is smaller. Compacting huge list of hostnames into single strings
// saves a lot of memory compared to having one dictionary entry per hostname.
var FilterHostnameDict = function() { var FilterHostnameDict = function() {
this.h = ''; // short-lived register this.h = ''; // short-lived register
this.dict = {}; this.dict = new Set();
this.count = 0;
};
// Somewhat arbitrary: I need to come up with hard data to know at which
// point binary search is better than indexOf.
//
// http://jsperf.com/string-indexof-vs-binary-search
// Tuning above performance benchmark, it appears 250 is roughly a good value
// for both Chromium/Firefox.
// Example of benchmark values: '------30', '-----100', etc. -- the
// needle string must always be 8-character long.
FilterHostnameDict.prototype.cutoff = 250;
// Probably not needed under normal circumstances.
FilterHostnameDict.prototype.meltBucket = function(len, bucket) {
var map = {};
if ( bucket.startsWith(' ') ) {
bucket.trim().split(' ').map(function(k) {
map[k] = true;
});
} else {
var offset = 0;
while ( offset < bucket.length ) {
map[bucket.substr(offset, len)] = true;
offset += len;
}
}
return map;
};
FilterHostnameDict.prototype.freezeBucket = function(bucket) {
var hostnames = Object.keys(bucket);
if ( hostnames[0].length * hostnames.length < this.cutoff ) {
return ' ' + hostnames.join(' ') + ' ';
}
return hostnames.sort().join('');
};
// How the key is derived dictates the number and size of buckets:
// - more bits = more buckets = higher memory footprint
// - less bits = less buckets = lower memory footprint
// - binary search mitigates very well the fact that some buckets may grow
// large when fewer bits are used (or when a large number of items are
// stored). Binary search also mitigate to the point of non-issue the
// CPU footprint requirement with large buckets, as far as reference
// benchmark shows.
//
// A hash key capable of better spread while being as fast would be
// just great.
FilterHostnameDict.prototype.makeKey = function(hn) {
var len = hn.length;
if ( len > 255 ) {
len = 255;
}
var i8 = len >>> 3;
var i4 = len >>> 2;
var i2 = len >>> 1;
// http://jsperf.com/makekey-concat-vs-join/3
// Be sure the msb is not set, this will guarantee a valid unicode
// character (because 0xD800-0xDFFF).
return String.fromCharCode(
(hn.charCodeAt( i8) & 0x01) << 14 |
// (hn.charCodeAt( i4 ) & 0x01) << 13 |
(hn.charCodeAt( i4+i8) & 0x01) << 12 |
(hn.charCodeAt(i2 ) & 0x01) << 11 |
(hn.charCodeAt(i2 +i8) & 0x01) << 10 |
// (hn.charCodeAt(i2+i4 ) & 0x01) << 9 |
(hn.charCodeAt(i2+i4+i8) & 0x01) << 8 ,
len
);
}; };
FilterHostnameDict.prototype.add = function(hn) { FilterHostnameDict.prototype.add = function(hn) {
var key = this.makeKey(hn); if ( this.dict.has(hn) ) {
var bucket = this.dict[key];
if ( bucket === undefined ) {
bucket = this.dict[key] = {};
bucket[hn] = true;
this.count += 1;
return true;
}
if ( typeof bucket === 'string' ) {
bucket = this.dict[key] = this.meltBucket(hn.length, bucket);
}
if ( bucket.hasOwnProperty(hn) ) {
return false; return false;
} }
bucket[hn] = true; this.dict.add(hn);
this.count += 1;
return true; return true;
}; };
FilterHostnameDict.prototype.freeze = function() {
var buckets = this.dict;
var bucket;
for ( var key in buckets ) {
bucket = buckets[key];
if ( typeof bucket === 'object' ) {
buckets[key] = this.freezeBucket(bucket);
}
}
};
FilterHostnameDict.prototype.matchesExactly = function(hn) {
// TODO: Handle IP address
var key = this.makeKey(hn);
var bucket = this.dict[key];
if ( bucket === undefined ) {
return false;
}
if ( typeof bucket === 'object' ) {
bucket = this.dict[key] = this.freezeBucket(bucket);
}
if ( bucket.startsWith(' ') ) {
return bucket.indexOf(' ' + hn + ' ') !== -1;
}
// binary search
var len = hn.length;
var left = 0;
// http://jsperf.com/or-vs-floor/17
var right = (bucket.length / len + 0.5) | 0;
var i, needle;
while ( left < right ) {
i = left + right >> 1;
needle = bucket.substr( len * i, len );
if ( hn < needle ) {
right = i;
} else if ( hn > needle ) {
left = i + 1;
} else {
return true;
}
}
return false;
};
FilterHostnameDict.prototype.match = function() { FilterHostnameDict.prototype.match = function() {
// TODO: mind IP addresses // TODO: mind IP addresses
var pos, var pos,
hostname = requestHostnameRegister; hostname = requestHostnameRegister;
while ( this.matchesExactly(hostname) === false ) { while ( this.dict.has(hostname) === false ) {
pos = hostname.indexOf('.'); pos = hostname.indexOf('.');
if ( pos === -1 ) { if ( pos === -1 ) {
this.h = ''; this.h = '';
@ -1167,17 +1025,12 @@ FilterHostnameDict.prototype.rtCompile = function() {
}; };
FilterHostnameDict.prototype.toSelfie = function() { FilterHostnameDict.prototype.toSelfie = function() {
return JSON.stringify({ return JSON.stringify(µb.setToArray(this.dict));
count: this.count,
dict: this.dict
});
}; };
FilterHostnameDict.fromSelfie = function(s) { FilterHostnameDict.fromSelfie = function(s) {
var f = new FilterHostnameDict(); var f = new FilterHostnameDict();
var o = JSON.parse(s); f.dict = µb.setFromArray(JSON.parse(s));
f.count = o.count;
f.dict = o.dict;
return f; return f;
}; };
@ -1728,8 +1581,8 @@ FilterContainer.prototype.reset = function() {
this.allowFilterCount = 0; this.allowFilterCount = 0;
this.blockFilterCount = 0; this.blockFilterCount = 0;
this.discardedCount = 0; this.discardedCount = 0;
this.duplicateBuster = {}; this.duplicateBuster = new Set();
this.categories = Object.create(null); this.categories = new Map();
this.filterParser.reset(); this.filterParser.reset();
this.filterCounts = {}; this.filterCounts = {};
@ -1743,17 +1596,7 @@ FilterContainer.prototype.reset = function() {
FilterContainer.prototype.freeze = function() { FilterContainer.prototype.freeze = function() {
histogram('allFilters', this.categories); histogram('allFilters', this.categories);
this.duplicateBuster = {}; this.duplicateBuster = new Set();
var categories = this.categories;
var bucket;
for ( var k in categories ) {
bucket = categories[k]['.'];
if ( bucket !== undefined ) {
bucket.freeze();
}
}
this.filterParser.reset(); this.filterParser.reset();
this.frozen = true; this.frozen = true;
}; };
@ -1786,20 +1629,23 @@ FilterContainer.prototype.factories = {
/******************************************************************************/ /******************************************************************************/
FilterContainer.prototype.toSelfie = function() { FilterContainer.prototype.toSelfie = function() {
var categoryToSelfie = function(dict) { var categoryToSelfie = function(map) {
var selfie = []; var selfie = [],
var bucket, ff, n, i, f; iterator = map.entries(),
for ( var token in dict ) { entry, bucket, ff, f;
// No need for hasOwnProperty() here: there is no prototype chain. for (;;) {
selfie.push('k2\t' + token); entry = iterator.next();
bucket = dict[token]; if ( entry.done ) {
break;
}
selfie.push('k2\t' + entry.value[0]);
bucket = entry.value[1];
selfie.push(bucket.fid + '\t' + bucket.toSelfie()); selfie.push(bucket.fid + '\t' + bucket.toSelfie());
if ( bucket.fid !== '[]' ) { if ( bucket.fid !== '[]' ) {
continue; continue;
} }
ff = bucket.filters; ff = bucket.filters;
n = ff.length; for ( var i = 0, ni = ff.length; i < ni; i++ ) {
for ( i = 0; i < n; i++ ) {
f = ff[i]; f = ff[i];
selfie.push(f.fid + '\t' + f.toSelfie()); selfie.push(f.fid + '\t' + f.toSelfie());
} }
@ -1807,12 +1653,17 @@ FilterContainer.prototype.toSelfie = function() {
return selfie.join('\n'); return selfie.join('\n');
}; };
var categoriesToSelfie = function(dict) { var categoriesToSelfie = function(map) {
var selfie = []; var selfie = [],
for ( var key in dict ) { iterator = map.entries(),
// No need for hasOwnProperty() here: there is no prototype chain. entry;
selfie.push('k1\t' + key); for (;;) {
selfie.push(categoryToSelfie(dict[key])); entry = iterator.next();
if ( entry.done ) {
break;
}
selfie.push('k1\t' + entry.value[0]);
selfie.push(categoryToSelfie(entry.value[1]));
} }
return selfie.join('\n'); return selfie.join('\n');
}; };
@ -1840,7 +1691,7 @@ FilterContainer.prototype.fromSelfie = function(selfie) {
this.discardedCount = selfie.discardedCount; this.discardedCount = selfie.discardedCount;
var catKey, tokenKey; var catKey, tokenKey;
var dict = this.categories, subdict; var map = this.categories, submap;
var bucket = null; var bucket = null;
var rawText = selfie.categories; var rawText = selfie.categories;
var rawEnd = rawText.length; var rawEnd = rawText.length;
@ -1857,7 +1708,8 @@ FilterContainer.prototype.fromSelfie = function(selfie) {
what = line.slice(0, pos); what = line.slice(0, pos);
if ( what === 'k1' ) { if ( what === 'k1' ) {
catKey = line.slice(pos + 1); catKey = line.slice(pos + 1);
subdict = dict[catKey] = Object.create(null); submap = new Map();
map.set(catKey, submap);
bucket = null; bucket = null;
continue; continue;
} }
@ -1868,7 +1720,8 @@ FilterContainer.prototype.fromSelfie = function(selfie) {
} }
factory = this.factories[what]; factory = this.factories[what];
if ( bucket === null ) { if ( bucket === null ) {
bucket = subdict[tokenKey] = factory.fromSelfie(line.slice(pos + 1)); bucket = factory.fromSelfie(line.slice(pos + 1));
submap.set(tokenKey, bucket);
continue; continue;
} }
// When token key is reused, it can't be anything // When token key is reused, it can't be anything
@ -2088,66 +1941,69 @@ FilterContainer.prototype.compileToAtomicFilter = function(filterClass, parsed,
/******************************************************************************/ /******************************************************************************/
FilterContainer.prototype.fromCompiledContent = function(lineIter) { FilterContainer.prototype.fromCompiledContent = function(lineIter) {
var line, fields, bucket, entry, factory, filter; var line, hash, token, fclass, fdata,
bucket, entry, factory, filter,
fieldIter = new µb.FieldIterator('\v');
while ( lineIter.eot() === false ) { while ( lineIter.eot() === false ) {
if ( lineIter.text.charCodeAt(lineIter.offset) !== 0x6E /* 'n' */ ) { if ( lineIter.text.charCodeAt(lineIter.offset) !== 0x6E /* 'n' */ ) {
return; return;
} }
line = lineIter.next().slice(2); line = lineIter.next();
fields = line.split('\v');
fieldIter.first(line);
hash = fieldIter.next();
token = fieldIter.next();
fclass = fieldIter.next();
fdata = fieldIter.next();
// Special cases: delegate to more specialized engines. // Special cases: delegate to more specialized engines.
// Redirect engine. // Redirect engine.
if ( fields[2] === '=>' ) { if ( fclass === '=>' ) {
µb.redirectEngine.fromCompiledRule(fields[3]); µb.redirectEngine.fromCompiledRule(fdata);
continue; continue;
} }
// Plain static filters. // Plain static filters.
this.acceptedCount += 1; this.acceptedCount += 1;
bucket = this.categories[fields[0]]; bucket = this.categories.get(hash);
if ( bucket === undefined ) { if ( bucket === undefined ) {
bucket = this.categories[fields[0]] = Object.create(null); bucket = new Map();
this.categories.set(hash, bucket);
} }
entry = bucket[fields[1]]; entry = bucket.get(token);
if ( fields[1] === '.' ) { if ( token === '.' ) {
if ( entry === undefined ) { if ( entry === undefined ) {
entry = bucket['.'] = new FilterHostnameDict(); entry = new FilterHostnameDict();
bucket.set('.', new FilterHostnameDict());
} }
if ( entry.add(fields[2]) === false ) { // 'fclass' is hostname
if ( entry.add(fclass) === false ) {
this.discardedCount += 1; this.discardedCount += 1;
} }
continue; continue;
} }
if ( this.duplicateBuster.hasOwnProperty(line) ) { if ( this.duplicateBuster.has(line) ) {
this.discardedCount += 1; this.discardedCount += 1;
continue; continue;
} }
this.duplicateBuster[line] = true; this.duplicateBuster.add(line);
factory = this.factories[fields[2]]; factory = this.factories[fclass];
// For development purpose filter = factory.fromSelfie(fdata);
//if ( this.filterCounts.hasOwnProperty(fields[2]) === false ) {
// this.filterCounts[fields[2]] = 1;
//} else {
// this.filterCounts[fields[2]]++;
//}
filter = factory.fromSelfie(fields[3]);
if ( entry === undefined ) { if ( entry === undefined ) {
bucket[fields[1]] = filter; bucket.set(token, filter);
continue; continue;
} }
if ( entry.fid === '[]' ) { if ( entry.fid === '[]' ) {
entry.add(filter); entry.add(filter);
continue; continue;
} }
bucket[fields[1]] = new FilterBucket(entry, filter); bucket.set(token, new FilterBucket(entry, filter));
} }
}; };
@ -2289,9 +2145,12 @@ FilterContainer.prototype.filterRegexFromCompiled = function(compiled, flags) {
/******************************************************************************/ /******************************************************************************/
// bucket: Map
// url: string
FilterContainer.prototype.matchTokens = function(bucket, url) { FilterContainer.prototype.matchTokens = function(bucket, url) {
// Hostname-only filters // Hostname-only filters
var f = bucket['.']; var f = bucket.get('.');
if ( f !== undefined && f.match() ) { if ( f !== undefined && f.match() ) {
this.tokenRegister = '.'; this.tokenRegister = '.';
this.fRegister = f; this.fRegister = f;
@ -2307,7 +2166,7 @@ FilterContainer.prototype.matchTokens = function(bucket, url) {
if ( token === '' ) { if ( token === '' ) {
break; break;
} }
f = bucket[token]; f = bucket.get(token);
if ( f !== undefined && f.match(url, tokenEntry.beg) ) { if ( f !== undefined && f.match(url, tokenEntry.beg) ) {
this.tokenRegister = token; this.tokenRegister = token;
this.fRegister = f; this.fRegister = f;
@ -2316,7 +2175,7 @@ FilterContainer.prototype.matchTokens = function(bucket, url) {
} }
// Regex-based filters // Regex-based filters
f = bucket['*']; f = bucket.get('*');
if ( f !== undefined && f.match(url) ) { if ( f !== undefined && f.match(url) ) {
this.tokenRegister = '*'; this.tokenRegister = '*';
this.fRegister = f; this.fRegister = f;
@ -2361,7 +2220,7 @@ FilterContainer.prototype.matchStringExactType = function(context, requestURL, r
if ( requestType === 'elemhide' ) { if ( requestType === 'elemhide' ) {
key = AllowAnyParty | type; key = AllowAnyParty | type;
if ( if (
(bucket = categories[toHex(key)]) && (bucket = categories.get(toHex(key))) &&
this.matchTokens(bucket, url) this.matchTokens(bucket, url)
) { ) {
this.keyRegister = key; this.keyRegister = key;
@ -2373,14 +2232,14 @@ FilterContainer.prototype.matchStringExactType = function(context, requestURL, r
// https://github.com/chrisaljoudi/uBlock/issues/139 // https://github.com/chrisaljoudi/uBlock/issues/139
// Test against important block filters // Test against important block filters
key = BlockAnyParty | Important | type; key = BlockAnyParty | Important | type;
if ( (bucket = categories[toHex(key)]) ) { if ( (bucket = categories.get(toHex(key))) ) {
if ( this.matchTokens(bucket, url) ) { if ( this.matchTokens(bucket, url) ) {
this.keyRegister = key; this.keyRegister = key;
return true; return true;
} }
} }
key = BlockAction | Important | type | party; key = BlockAction | Important | type | party;
if ( (bucket = categories[toHex(key)]) ) { if ( (bucket = categories.get(toHex(key))) ) {
if ( this.matchTokens(bucket, url) ) { if ( this.matchTokens(bucket, url) ) {
this.keyRegister = key; this.keyRegister = key;
return true; return true;
@ -2389,14 +2248,14 @@ FilterContainer.prototype.matchStringExactType = function(context, requestURL, r
// Test against block filters // Test against block filters
key = BlockAnyParty | type; key = BlockAnyParty | type;
if ( (bucket = categories[toHex(key)]) ) { if ( (bucket = categories.get(toHex(key))) ) {
if ( this.matchTokens(bucket, url) ) { if ( this.matchTokens(bucket, url) ) {
this.keyRegister = key; this.keyRegister = key;
} }
} }
if ( this.fRegister === null ) { if ( this.fRegister === null ) {
key = BlockAction | type | party; key = BlockAction | type | party;
if ( (bucket = categories[toHex(key)]) ) { if ( (bucket = categories.get(toHex(key))) ) {
if ( this.matchTokens(bucket, url) ) { if ( this.matchTokens(bucket, url) ) {
this.keyRegister = key; this.keyRegister = key;
} }
@ -2410,14 +2269,14 @@ FilterContainer.prototype.matchStringExactType = function(context, requestURL, r
// Test against allow filters // Test against allow filters
key = AllowAnyParty | type; key = AllowAnyParty | type;
if ( (bucket = categories[toHex(key)]) ) { if ( (bucket = categories.get(toHex(key))) ) {
if ( this.matchTokens(bucket, url) ) { if ( this.matchTokens(bucket, url) ) {
this.keyRegister = key; this.keyRegister = key;
return false; return false;
} }
} }
key = AllowAction | type | party; key = AllowAction | type | party;
if ( (bucket = categories[toHex(key)]) ) { if ( (bucket = categories.get(toHex(key))) ) {
if ( this.matchTokens(bucket, url) ) { if ( this.matchTokens(bucket, url) ) {
this.keyRegister = key; this.keyRegister = key;
return false; return false;
@ -2480,28 +2339,28 @@ FilterContainer.prototype.matchString = function(context) {
// evaluation. Normally, it is "evaluate block then evaluate allow", with // evaluation. Normally, it is "evaluate block then evaluate allow", with
// the `important` property it is "evaluate allow then evaluate block". // the `important` property it is "evaluate allow then evaluate block".
key = BlockAnyTypeAnyParty | Important; key = BlockAnyTypeAnyParty | Important;
if ( (bucket = categories[toHex(key)]) ) { if ( (bucket = categories.get(toHex(key))) ) {
if ( this.matchTokens(bucket, url) ) { if ( this.matchTokens(bucket, url) ) {
this.keyRegister = key; this.keyRegister = key;
return true; return true;
} }
} }
key = BlockAnyType | Important | party; key = BlockAnyType | Important | party;
if ( (bucket = categories[toHex(key)]) ) { if ( (bucket = categories.get(toHex(key))) ) {
if ( this.matchTokens(bucket, url) ) { if ( this.matchTokens(bucket, url) ) {
this.keyRegister = key; this.keyRegister = key;
return true; return true;
} }
} }
key = BlockAnyParty | Important | type; key = BlockAnyParty | Important | type;
if ( (bucket = categories[toHex(key)]) ) { if ( (bucket = categories.get(toHex(key))) ) {
if ( this.matchTokens(bucket, url) ) { if ( this.matchTokens(bucket, url) ) {
this.keyRegister = key; this.keyRegister = key;
return true; return true;
} }
} }
key = BlockAction | Important | type | party; key = BlockAction | Important | type | party;
if ( (bucket = categories[toHex(key)]) ) { if ( (bucket = categories.get(toHex(key))) ) {
if ( this.matchTokens(bucket, url) ) { if ( this.matchTokens(bucket, url) ) {
this.keyRegister = key; this.keyRegister = key;
return true; return true;
@ -2510,28 +2369,28 @@ FilterContainer.prototype.matchString = function(context) {
// Test against block filters // Test against block filters
key = BlockAnyTypeAnyParty; key = BlockAnyTypeAnyParty;
if ( (bucket = categories[toHex(key)]) ) { if ( (bucket = categories.get(toHex(key))) ) {
if ( this.matchTokens(bucket, url) ) { if ( this.matchTokens(bucket, url) ) {
this.keyRegister = key; this.keyRegister = key;
} }
} }
if ( this.fRegister === null ) { if ( this.fRegister === null ) {
key = BlockAnyType | party; key = BlockAnyType | party;
if ( (bucket = categories[toHex(key)]) ) { if ( (bucket = categories.get(toHex(key))) ) {
if ( this.matchTokens(bucket, url) ) { if ( this.matchTokens(bucket, url) ) {
this.keyRegister = key; this.keyRegister = key;
} }
} }
if ( this.fRegister === null ) { if ( this.fRegister === null ) {
key = BlockAnyParty | type; key = BlockAnyParty | type;
if ( (bucket = categories[toHex(key)]) ) { if ( (bucket = categories.get(toHex(key))) ) {
if ( this.matchTokens(bucket, url) ) { if ( this.matchTokens(bucket, url) ) {
this.keyRegister = key; this.keyRegister = key;
} }
} }
if ( this.fRegister === null ) { if ( this.fRegister === null ) {
key = BlockAction | type | party; key = BlockAction | type | party;
if ( (bucket = categories[toHex(key)]) ) { if ( (bucket = categories.get(toHex(key))) ) {
if ( this.matchTokens(bucket, url) ) { if ( this.matchTokens(bucket, url) ) {
this.keyRegister = key; this.keyRegister = key;
} }
@ -2547,28 +2406,28 @@ FilterContainer.prototype.matchString = function(context) {
// Test against allow filters // Test against allow filters
key = AllowAnyTypeAnyParty; key = AllowAnyTypeAnyParty;
if ( (bucket = categories[toHex(key)]) ) { if ( (bucket = categories.get(toHex(key))) ) {
if ( this.matchTokens(bucket, url) ) { if ( this.matchTokens(bucket, url) ) {
this.keyRegister = key; this.keyRegister = key;
return false; return false;
} }
} }
key = AllowAnyType | party; key = AllowAnyType | party;
if ( (bucket = categories[toHex(key)]) ) { if ( (bucket = categories.get(toHex(key))) ) {
if ( this.matchTokens(bucket, url) ) { if ( this.matchTokens(bucket, url) ) {
this.keyRegister = key; this.keyRegister = key;
return false; return false;
} }
} }
key = AllowAnyParty | type; key = AllowAnyParty | type;
if ( (bucket = categories[toHex(key)]) ) { if ( (bucket = categories.get(toHex(key))) ) {
if ( this.matchTokens(bucket, url) ) { if ( this.matchTokens(bucket, url) ) {
this.keyRegister = key; this.keyRegister = key;
return false; return false;
} }
} }
key = AllowAction | type | party; key = AllowAction | type | party;
if ( (bucket = categories[toHex(key)]) ) { if ( (bucket = categories.get(toHex(key))) ) {
if ( this.matchTokens(bucket, url) ) { if ( this.matchTokens(bucket, url) ) {
this.keyRegister = key; this.keyRegister = key;
return false; return false;

View File

@ -126,18 +126,16 @@
µBlock.LineIterator = function(text, offset) { µBlock.LineIterator = function(text, offset) {
this.text = text; this.text = text;
this.textLen = this.text.length;
this.offset = offset || 0; this.offset = offset || 0;
}; };
µBlock.LineIterator.prototype.next = function() { µBlock.LineIterator.prototype.next = function() {
if ( this.offset >= this.text.length ) {
return undefined;
}
var lineEnd = this.text.indexOf('\n', this.offset); var lineEnd = this.text.indexOf('\n', this.offset);
if ( lineEnd === -1 ) { if ( lineEnd === -1 ) {
lineEnd = this.text.indexOf('\r', this.offset); lineEnd = this.text.indexOf('\r', this.offset);
if ( lineEnd === -1 ) { if ( lineEnd === -1 ) {
lineEnd = this.text.length; lineEnd = this.textLen;
} }
} }
var line = this.text.slice(this.offset, lineEnd); var line = this.text.slice(this.offset, lineEnd);
@ -146,7 +144,69 @@
}; };
µBlock.LineIterator.prototype.eot = function() { µBlock.LineIterator.prototype.eot = function() {
return this.offset >= this.text.length; return this.offset >= this.textLen;
};
/******************************************************************************/
// The field iterator is less CPU-intensive than when using native
// String.split().
µBlock.FieldIterator = function(sep) {
this.text = '';
this.sep = sep;
this.sepLen = sep.length;
this.offset = 0;
};
µBlock.FieldIterator.prototype.first = function(text) {
this.text = text;
this.offset = 0;
return this.next();
};
µBlock.FieldIterator.prototype.next = function() {
var end = this.text.indexOf(this.sep, this.offset);
if ( end === -1 ) {
end = this.text.length;
}
var field = this.text.slice(this.offset, end);
this.offset = end + this.sepLen;
return field;
};
/******************************************************************************/
µBlock.mapToArray = function(map) {
var out = [],
entries = map.entries(),
entry;
for (;;) {
entry = entries.next();
if ( entry.done ) { break; }
out.push([ entry.value[0], entry.value[1] ]);
}
return out;
};
µBlock.mapFromArray = function(arr) {
return new Map(arr);
};
µBlock.setToArray = function(dict) {
var out = [],
entries = dict.values(),
entry;
for (;;) {
entry = entries.next();
if ( entry.done ) { break; }
out.push(entry.value);
}
return out;
};
µBlock.setFromArray = function(arr) {
return new Set(arr);
}; };
/******************************************************************************/ /******************************************************************************/

View File

@ -22,6 +22,7 @@ cat src/background.html | sed -e '/vapi-polyfill\.js/d' > $DES/background.html
mv $DES/img/icon_128.png $DES/icon.png mv $DES/img/icon_128.png $DES/icon.png
cp platform/firefox/css/* $DES/css/ cp platform/firefox/css/* $DES/css/
cp platform/firefox/polyfill.js $DES/js/
cp platform/firefox/vapi-*.js $DES/js/ cp platform/firefox/vapi-*.js $DES/js/
cp platform/firefox/bootstrap.js $DES/ cp platform/firefox/bootstrap.js $DES/
cp platform/firefox/frame*.js $DES/ cp platform/firefox/frame*.js $DES/