mirror of https://github.com/gorhill/uBlock.git
re. #1070: rewrite redirect engine to use ES6 Sets/Maps
This commit is contained in:
parent
cd81f866b9
commit
0454ad1b1f
|
@ -95,7 +95,7 @@ return {
|
||||||
// read-only
|
// read-only
|
||||||
systemSettings: {
|
systemSettings: {
|
||||||
compiledMagic: 'lbmqiweqbvha',
|
compiledMagic: 'lbmqiweqbvha',
|
||||||
selfieMagic: 'lbmqiweqbvha'
|
selfieMagic: 'mhirtyetynnf'
|
||||||
},
|
},
|
||||||
|
|
||||||
restoreBackupSettings: {
|
restoreBackupSettings: {
|
||||||
|
|
|
@ -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)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue