re. #1070: rewrite redirect engine to use ES6 Sets/Maps

This commit is contained in:
gorhill 2016-10-10 09:01:05 -04:00
parent cd81f866b9
commit 0454ad1b1f
2 changed files with 128 additions and 111 deletions

View File

@ -95,7 +95,7 @@ return {
// read-only // read-only
systemSettings: { systemSettings: {
compiledMagic: 'lbmqiweqbvha', compiledMagic: 'lbmqiweqbvha',
selfieMagic: 'lbmqiweqbvha' selfieMagic: 'mhirtyetynnf'
}, },
restoreBackupSettings: { restoreBackupSettings: {

View File

@ -1,7 +1,7 @@
/******************************************************************************* /*******************************************************************************
uBlock Origin - a browser extension to block requests. uBlock Origin - a browser extension to block requests.
Copyright (C) 2015 Raymond Hill Copyright (C) 2015-2016 Raymond Hill
This program is free software: you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
@ -19,21 +19,11 @@
Home: https://github.com/gorhill/uBlock Home: https://github.com/gorhill/uBlock
*/ */
/******************************************************************************/
µBlock.redirectEngine = (function(){
'use strict'; 'use strict';
/******************************************************************************/ /******************************************************************************/
var toBroaderHostname = function(hostname) { µBlock.redirectEngine = (function(){
var pos = hostname.indexOf('.');
if ( pos !== -1 ) {
return hostname.slice(pos + 1);
}
return hostname !== '*' && hostname !== '' ? '*' : '';
};
/******************************************************************************/ /******************************************************************************/
/******************************************************************************/ /******************************************************************************/
@ -92,15 +82,19 @@ RedirectEntry.fromSelfie = function(selfie) {
/******************************************************************************/ /******************************************************************************/
var RedirectEngine = function() { var RedirectEngine = function() {
this.resources = Object.create(null); this.resources = new Map();
this.reset(); this.reset();
this.resourceNameRegister = ''; this.resourceNameRegister = '';
this._desAll = []; // re-use better than re-allocate
}; };
/******************************************************************************/ /******************************************************************************/
RedirectEngine.prototype.reset = function() { RedirectEngine.prototype.reset = function() {
this.rules = Object.create(null); this.rules = new Map();
this.ruleTypes = new Set();
this.ruleSources = new Set();
this.ruleDestinations = new Set();
}; };
/******************************************************************************/ /******************************************************************************/
@ -110,41 +104,57 @@ RedirectEngine.prototype.freeze = function() {
/******************************************************************************/ /******************************************************************************/
RedirectEngine.prototype.toBroaderHostname = function(hostname) {
var pos = hostname.indexOf('.');
if ( pos !== -1 ) {
return hostname.slice(pos + 1);
}
return hostname !== '*' ? '*' : '';
};
/******************************************************************************/
RedirectEngine.prototype.lookup = function(context) { RedirectEngine.prototype.lookup = function(context) {
var typeEntry = this.rules[context.requestType]; var type = context.requestType;
if ( typeEntry === undefined ) { if ( this.ruleTypes.has(type) === false ) { return; }
return; var src = context.pageHostname,
} des = context.requestHostname,
var src, des = context.requestHostname, desAll = this._desAll,
srcHostname = context.pageHostname, reqURL = context.requestURL;
reqURL = context.requestURL, var n = 0;
desEntry, rules, rule, pattern;
for (;;) { for (;;) {
desEntry = typeEntry[des]; if ( this.ruleDestinations.has(des) ) {
if ( desEntry !== undefined ) { desAll[n] = des; n += 1;
src = srcHostname; }
des = this.toBroaderHostname(des);
if ( des === '' ) { break; }
}
if ( n === 0 ) { return; }
var entries;
for (;;) { for (;;) {
rules = desEntry[src]; if ( this.ruleSources.has(src) ) {
if ( rules !== undefined ) { for ( var i = 0; i < n; i++ ) {
for ( rule in rules ) { entries = this.rules.get(src + ' ' + desAll[i] + ' ' + type);
pattern = rules[rule]; if ( entries && this.lookupToken(entries, reqURL) ) {
if ( pattern instanceof RegExp === false ) { return this.resourceNameRegister;
pattern = rules[rule] = new RegExp(pattern, 'i');
}
if ( pattern.test(reqURL) ) {
return (this.resourceNameRegister = rule);
} }
} }
} }
src = toBroaderHostname(src); src = this.toBroaderHostname(src);
if ( src === '' ) { if ( src === '' ) { break; }
break;
} }
};
RedirectEngine.prototype.lookupToken = function(entries, reqURL) {
var j = entries.length, entry;
while ( j-- ) {
entry = entries[j];
if ( entry.pat instanceof RegExp === false ) {
entry.pat = new RegExp(entry.pat, 'i');
} }
} if ( entry.pat.test(reqURL) ) {
des = toBroaderHostname(des); this.resourceNameRegister = entry.tok;
if ( des === '' ) { return true;
break;
} }
} }
}; };
@ -156,7 +166,7 @@ RedirectEngine.prototype.toURL = function(context) {
if ( token === undefined ) { if ( token === undefined ) {
return; return;
} }
var entry = this.resources[token]; var entry = this.resources.get(token);
if ( entry !== undefined ) { if ( entry !== undefined ) {
return entry.toURL(); return entry.toURL();
} }
@ -166,29 +176,31 @@ RedirectEngine.prototype.toURL = function(context) {
RedirectEngine.prototype.matches = function(context) { RedirectEngine.prototype.matches = function(context) {
var token = this.lookup(context); var token = this.lookup(context);
return token !== undefined && this.resources[token] !== undefined; return token !== undefined && this.resources.has(token);
}; };
/******************************************************************************/ /******************************************************************************/
RedirectEngine.prototype.addRule = function(src, des, type, pattern, redirect) { RedirectEngine.prototype.addRule = function(src, des, type, pattern, redirect) {
var typeEntry = this.rules[type]; this.ruleSources.add(src);
if ( typeEntry === undefined ) { this.ruleDestinations.add(des);
typeEntry = this.rules[type] = Object.create(null); this.ruleTypes.add(type);
} var key = src + ' ' + des + ' ' + type,
var desEntry = typeEntry[des]; entries = this.rules.get(key);
if ( desEntry === undefined ) { if ( entries === undefined ) {
desEntry = typeEntry[des] = Object.create(null); this.rules.set(key, [ { tok: redirect, pat: pattern } ]);
}
var rules = desEntry[src];
if ( rules === undefined ) {
rules = desEntry[src] = Object.create(null);
}
var p = rules[redirect];
if ( p === undefined ) {
rules[redirect] = pattern;
return; return;
} }
var entry;
for ( var i = 0, n = entries.length; i < n; i++ ) {
entry = entries[i];
if ( redirect === entry.tok ) { break; }
}
if ( i === n ) {
entries.push({ tok: redirect, pat: pattern });
return;
}
var p = entry.pat;
if ( p instanceof RegExp ) { if ( p instanceof RegExp ) {
p = p.source; p = p.source;
} }
@ -197,12 +209,10 @@ RedirectEngine.prototype.addRule = function(src, des, type, pattern, redirect) {
if ( pos !== -1 ) { if ( pos !== -1 ) {
if ( pos === 0 || p.charAt(pos - 1) === '|' ) { if ( pos === 0 || p.charAt(pos - 1) === '|' ) {
pos += pattern.length; pos += pattern.length;
if ( pos === p.length || p.charAt(pos) === '|' ) { if ( pos === p.length || p.charAt(pos) === '|' ) { return; }
return;
} }
} }
} entry.pat = p + '|' + pattern;
rules[redirect] = p + '|' + pattern;
}; };
/******************************************************************************/ /******************************************************************************/
@ -308,56 +318,55 @@ RedirectEngine.prototype.supportedTypes = (function() {
/******************************************************************************/ /******************************************************************************/
RedirectEngine.prototype.toSelfie = function() { RedirectEngine.prototype.toSelfie = function() {
var r = { // Because rules may contains RegExp instances, we need to manually
resources: this.resources, // convert it to a serializable format. The serialized format must be
rules: [] // suitable to be used as an argument to the Map() constructor.
var rules = [],
iter = this.rules.entries(),
item, rule, entries, i, entry;
for (;;) {
item = iter.next();
if ( item.done ) { break; }
rule = [ item.value[0], [] ];
entries = item.value[1];
i = entries.length;
while ( i-- ) {
entry = entries[i];
rule[1].push({
tok: entry.tok,
pat: entry.pat instanceof RegExp ? entry.pat.source : entry.pat
});
}
rules.push(rule);
}
var µb = µBlock;
return {
resources: µb.mapToArray(this.resources),
rules: rules,
ruleTypes: µb.setToArray(this.ruleTypes),
ruleSources: µb.setToArray(this.ruleSources),
ruleDestinations: µb.setToArray(this.ruleDestinations)
}; };
var typeEntry, desEntry, rules, pattern;
for ( var type in this.rules ) {
typeEntry = this.rules[type];
for ( var des in typeEntry ) {
desEntry = typeEntry[des];
for ( var src in desEntry ) {
rules = desEntry[src];
for ( var rule in rules ) {
pattern = rules[rule];
if ( pattern instanceof RegExp ) {
pattern = pattern.source;
}
r.rules.push(
src + '\t' +
des + '\t' +
type + '\t' +
pattern + '\t' +
rule
);
}
}
}
}
return r;
}; };
/******************************************************************************/ /******************************************************************************/
RedirectEngine.prototype.fromSelfie = function(selfie) { RedirectEngine.prototype.fromSelfie = function(selfie) {
// Resources. // Resources.
var resources = selfie.resources; this.resources = new Map();
for ( var token in resources ) { var resources = selfie.resources,
if ( resources.hasOwnProperty(token) === false ) { item;
continue; for ( var i = 0, n = resources.length; i < n; i++ ) {
} item = resources[i];
this.resources[token] = RedirectEntry.fromSelfie(resources[token]); this.resources.set(item[0], RedirectEntry.fromSelfie(item[1]));
} }
// Rules. // Rules.
var rules = selfie.rules; var µb = µBlock;
var i = rules.length; this.rules = µb.mapFromArray(selfie.rules);
while ( i-- ) { this.ruleTypes = µb.setFromArray(selfie.ruleTypes);
this.fromCompiledRule(rules[i]); this.ruleSources = µb.setFromArray(selfie.ruleSources);
} this.ruleDestinations = µb.setFromArray(selfie.ruleDestinations);
return true; return true;
}; };
@ -365,7 +374,7 @@ RedirectEngine.prototype.fromSelfie = function(selfie) {
/******************************************************************************/ /******************************************************************************/
RedirectEngine.prototype.resourceURIFromName = function(name, mime) { RedirectEngine.prototype.resourceURIFromName = function(name, mime) {
var entry = this.resources[name]; var entry = this.resources.get(name);
if ( entry && (mime === undefined || entry.mime.startsWith(mime)) ) { if ( entry && (mime === undefined || entry.mime.startsWith(mime)) ) {
return entry.toURL(); return entry.toURL();
} }
@ -374,8 +383,16 @@ RedirectEngine.prototype.resourceURIFromName = function(name, mime) {
/******************************************************************************/ /******************************************************************************/
RedirectEngine.prototype.resourceContentFromName = function(name, mime) { RedirectEngine.prototype.resourceContentFromName = function(name, mime) {
var entry = this.resources[name]; var entry;
if ( entry && (mime === undefined || entry.mime.startsWith(mime)) ) { for (;;) {
entry = this.resources.get(name);
if ( entry === undefined ) { return; }
if ( entry.mime.startsWith('alias/') === false ) {
break;
}
name = entry.mime.slice(6);
}
if ( mime === undefined || entry.mime.startsWith(mime) ) {
return entry.toContent(); return entry.toContent();
} }
}; };
@ -390,7 +407,7 @@ RedirectEngine.prototype.resourcesFromString = function(text) {
var line, fields, encoded; var line, fields, encoded;
var reNonEmptyLine = /\S/; var reNonEmptyLine = /\S/;
this.resources = Object.create(null); this.resources = new Map();
while ( lineBeg < textEnd ) { while ( lineBeg < textEnd ) {
lineEnd = text.indexOf('\n', lineBeg); lineEnd = text.indexOf('\n', lineBeg);
@ -423,14 +440,14 @@ RedirectEngine.prototype.resourcesFromString = function(text) {
} }
// No more data, add the resource. // No more data, add the resource.
this.resources[fields[0]] = RedirectEntry.fromFields(fields[1], fields.slice(2)); this.resources.set(fields[0], RedirectEntry.fromFields(fields[1], fields.slice(2)));
fields = undefined; fields = undefined;
} }
// Process pending resource data. // Process pending resource data.
if ( fields !== undefined ) { if ( fields !== undefined ) {
this.resources[fields[0]] = RedirectEntry.fromFields(fields[1], fields.slice(2)); this.resources.set(fields[0], RedirectEntry.fromFields(fields[1], fields.slice(2)));
} }
}; };