From 027e8c59779697308ced9251b4a63689e959c5bf Mon Sep 17 00:00:00 2001 From: gorhill Date: Sat, 22 Aug 2015 12:15:16 -0400 Subject: [PATCH] #621: re-factored how `domain=` filter option is tested --- src/css/logger-ui.css | 2 + src/js/background.js | 2 +- src/js/static-net-filtering.js | 305 ++++++++++++++++++++++----------- 3 files changed, 206 insertions(+), 103 deletions(-) diff --git a/src/css/logger-ui.css b/src/css/logger-ui.css index 6c2ea0db7..041d6342f 100644 --- a/src/css/logger-ui.css +++ b/src/css/logger-ui.css @@ -570,7 +570,9 @@ body.colorBlind #netFilteringDialog .dialog > div.containers > div.dynamic tr.e } #filterFinderDialog .dialog code { background: #eee; + font-size: 85%; padding: 3px; + word-break: break-all; } #filterFinderDialog .dialog ul { font-size: larger; diff --git a/src/js/background.js b/src/js/background.js index 4e996a3fc..a7fdaa340 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -93,7 +93,7 @@ return { // read-only systemSettings: { - compiledMagic: 'bxajckhlxyck', + compiledMagic: 'rzohdugizuxh', selfieMagic: 'mnigwksyvgkv' }, diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js index 5fe851cd0..92cc7af10 100644 --- a/src/js/static-net-filtering.js +++ b/src/js/static-net-filtering.js @@ -213,14 +213,138 @@ var toHex = function(n) { return n.toString(16); }; -var hostnameHit = function(hostname) { +/******************************************************************************/ + +// Hostname test helpers: the optimal test function is picked +// according to the content of the `domain` filter option, + +var hostnameTestPicker = function(owner) { + var domainOpt = owner.domainOpt; + + // Only one hostname + if ( domainOpt.indexOf('|') === -1 ) { + return domainOpt.charAt(0) !== '~' ? hostnameHitTest : hostnameMissTest; + } + + // Multiple hostnames: use a dictionary. + var dict = owner._hostnameDict = Object.create(null); + var hostnames = domainOpt.split('|'); + var i, hostname; + + // First find out whether we have a homogeneous dictionary + var hit = false, miss = false; + i = hostnames.length; + while ( i-- ) { + if ( hostnames[i].charAt(0) !== '~' ) { + hit = true; + if ( miss ) { + break; + } + } else { + miss = true; + if ( hit ) { + break; + } + } + } + + // Heterogenous dictionary: this can happen, though VERY rarely. + // Spotted one occurrence in EasyList Lite (cjxlist.txt): + // domain=photobucket.com|~secure.photobucket.com + if ( hit && miss ) { + i = hostnames.length; + while ( i-- ) { + hostname = hostnames[i]; + if ( hostname.charAt(0) !== '~' ) { + dict[hostname] = true; + } else { + dict[hostname.slice(1)] = false; + } + } + return hostnameMixedSetTest; + } + + // Homogeneous dictionary. + i = hostnames.length; + while ( i-- ) { + hostname = hostnames[i]; + if ( hostname.charAt(0) !== '~' ) { + dict[hostname] = true; + } else { + dict[hostname.slice(1)] = true; + } + } + + return hit ? hostnameHitSetTest : hostnameMissSetTest; +}; + +var hostnameHitTest = function(owner) { + var hostname = owner.domainOpt; return pageHostnameRegister.slice(0 - hostname.length) === hostname; }; -var hostnameMiss = function(hostname) { +var hostnameMissTest = function(owner) { + var hostname = owner.domainOpt; return pageHostnameRegister.slice(1 - hostname.length) !== hostname.slice(1); }; +var hostnameHitSetTest = function(owner) { + var dict = owner._hostnameDict; + var needle = pageHostnameRegister; + var pos; + for (;;) { + if ( dict[needle] ) { + return true; + } + pos = needle.indexOf('.'); + if ( pos === -1 ) { + break; + } + needle = needle.slice(pos + 1); + } + return false; +}; + +var hostnameMissSetTest = function(owner) { + var dict = owner._hostnameDict; + var needle = pageHostnameRegister; + var pos; + for (;;) { + if ( dict[needle] ) { + return false; + } + pos = needle.indexOf('.'); + if ( pos === -1 ) { + break; + } + needle = needle.slice(pos + 1); + } + + return true; +}; + +var hostnameMixedSetTest = function(owner) { + var dict = owner._hostnameDict; + var needle = pageHostnameRegister; + var hit = false; + var v, pos; + for (;;) { + v = dict[needle] || undefined; + if ( v === false ) { + return false; + } + if ( v /* === true */ ) { + hit = true; + } + pos = needle.indexOf('.'); + if ( pos === -1 ) { + break; + } + needle = needle.slice(pos + 1); + } + return hit; +}; + /******************************************************************************* Filters family tree: @@ -280,15 +404,15 @@ FilterPlain.fromSelfie = function(s) { /******************************************************************************/ -var FilterPlainHostname = function(s, tokenBeg, hostname) { +var FilterPlainHostname = function(s, tokenBeg, domainOpt) { this.s = s; this.tokenBeg = tokenBeg; - this.hostname = hostname; - this.hostnameTest = hostname.charAt(0) !== '~' ? hostnameHit : hostnameMiss; + this.domainOpt = domainOpt; + this.hostnameTest = hostnameTestPicker(this); }; FilterPlainHostname.prototype.match = function(url, tokenBeg) { - return this.hostnameTest(this.hostname) && + return this.hostnameTest(this) && url.substr(tokenBeg - this.tokenBeg, this.s.length) === this.s; }; @@ -298,11 +422,11 @@ FilterPlainHostname.prototype.rtfid = 'ah'; FilterPlainHostname.prototype.toSelfie = FilterPlainHostname.prototype.rtCompile = function() { - return this.s + '\t' + this.tokenBeg + '\t' + this.hostname; + return this.s + '\t' + this.tokenBeg + '\t' + this.domainOpt; }; -FilterPlainHostname.compile = function(details, hostname) { - return details.f + '\t' + details.tokenBeg + '\t' + hostname; +FilterPlainHostname.compile = function(details) { + return details.f + '\t' + details.tokenBeg + '\t' + details.domainOpt; }; FilterPlainHostname.fromSelfie = function(s) { @@ -339,14 +463,14 @@ FilterPlainPrefix0.fromSelfie = function(s) { /******************************************************************************/ -var FilterPlainPrefix0Hostname = function(s, hostname) { +var FilterPlainPrefix0Hostname = function(s, domainOpt) { this.s = s; - this.hostname = hostname; - this.hostnameTest = hostname.charAt(0) !== '~' ? hostnameHit : hostnameMiss; + this.domainOpt = domainOpt; + this.hostnameTest = hostnameTestPicker(this); }; FilterPlainPrefix0Hostname.prototype.match = function(url, tokenBeg) { - return this.hostnameTest(this.hostname) && + return this.hostnameTest(this) && url.substr(tokenBeg, this.s.length) === this.s; }; @@ -356,11 +480,11 @@ FilterPlainPrefix0Hostname.prototype.rtfid = '0ah'; FilterPlainPrefix0Hostname.prototype.toSelfie = FilterPlainPrefix0Hostname.prototype.rtCompile = function() { - return this.s + '\t' + this.hostname; + return this.s + '\t' + this.domainOpt; }; -FilterPlainPrefix0Hostname.compile = function(details, hostname) { - return details.f + '\t' + hostname; +FilterPlainPrefix0Hostname.compile = function(details) { + return details.f + '\t' + details.domainOpt; }; FilterPlainPrefix0Hostname.fromSelfie = function(s) { @@ -397,14 +521,14 @@ FilterPlainPrefix1.fromSelfie = function(s) { /******************************************************************************/ -var FilterPlainPrefix1Hostname = function(s, hostname) { +var FilterPlainPrefix1Hostname = function(s, domainOpt) { this.s = s; - this.hostname = hostname; - this.hostnameTest = hostname.charAt(0) !== '~' ? hostnameHit : hostnameMiss; + this.domainOpt = domainOpt; + this.hostnameTest = hostnameTestPicker(this); }; FilterPlainPrefix1Hostname.prototype.match = function(url, tokenBeg) { - return this.hostnameTest(this.hostname) && + return this.hostnameTest(this) && url.substr(tokenBeg - 1, this.s.length) === this.s; }; @@ -414,11 +538,11 @@ FilterPlainPrefix1Hostname.prototype.rtfid = '1ah'; FilterPlainPrefix1Hostname.prototype.toSelfie = FilterPlainPrefix1Hostname.prototype.rtCompile = function() { - return this.s + '\t' + this.hostname; + return this.s + '\t' + this.domainOpt; }; -FilterPlainPrefix1Hostname.compile = function(details, hostname) { - return details.f + '\t' + hostname; +FilterPlainPrefix1Hostname.compile = function(details) { + return details.f + '\t' + details.domainOpt; }; FilterPlainPrefix1Hostname.fromSelfie = function(s) { @@ -455,14 +579,14 @@ FilterPlainLeftAnchored.fromSelfie = function(s) { /******************************************************************************/ -var FilterPlainLeftAnchoredHostname = function(s, hostname) { +var FilterPlainLeftAnchoredHostname = function(s, domainOpt) { this.s = s; - this.hostname = hostname; - this.hostnameTest = hostname.charAt(0) !== '~' ? hostnameHit : hostnameMiss; + this.domainOpt = domainOpt; + this.hostnameTest = hostnameTestPicker(this); }; FilterPlainLeftAnchoredHostname.prototype.match = function(url) { - return this.hostnameTest(this.hostname) && + return this.hostnameTest(this) && url.slice(0, this.s.length) === this.s; }; @@ -472,11 +596,11 @@ FilterPlainLeftAnchoredHostname.prototype.rtfid = '|ah'; FilterPlainLeftAnchoredHostname.prototype.toSelfie = FilterPlainLeftAnchoredHostname.prototype.rtCompile = function() { - return this.s + '\t' + this.hostname; + return this.s + '\t' + this.domainOpt; }; -FilterPlainLeftAnchoredHostname.compile = function(details, hostname) { - return details.f + '\t' + hostname; +FilterPlainLeftAnchoredHostname.compile = function(details) { + return details.f + '\t' + details.domainOpt; }; FilterPlainLeftAnchoredHostname.fromSelfie = function(s) { @@ -513,14 +637,14 @@ FilterPlainRightAnchored.fromSelfie = function(s) { /******************************************************************************/ -var FilterPlainRightAnchoredHostname = function(s, hostname) { +var FilterPlainRightAnchoredHostname = function(s, domainOpt) { this.s = s; - this.hostname = hostname; - this.hostnameTest = hostname.charAt(0) !== '~' ? hostnameHit : hostnameMiss; + this.domainOpt = domainOpt; + this.hostnameTest = hostnameTestPicker(this); }; FilterPlainRightAnchoredHostname.prototype.match = function(url) { - return this.hostnameTest(this.hostname) && + return this.hostnameTest(this) && url.slice(-this.s.length) === this.s; }; @@ -530,11 +654,11 @@ FilterPlainRightAnchoredHostname.prototype.rtfid = 'a|h'; FilterPlainRightAnchoredHostname.prototype.toSelfie = FilterPlainRightAnchoredHostname.prototype.rtCompile = function() { - return this.s + '\t' + this.hostname; + return this.s + '\t' + this.domainOpt; }; -FilterPlainRightAnchoredHostname.compile = function(details, hostname) { - return details.f + '\t' + hostname; +FilterPlainRightAnchoredHostname.compile = function(details) { + return details.f + '\t' + details.domainOpt; }; FilterPlainRightAnchoredHostname.fromSelfie = function(s) { @@ -584,14 +708,14 @@ FilterPlainHnAnchored.fromSelfie = function(s) { // https://github.com/gorhill/uBlock/issues/142 -var FilterPlainHnAnchoredHostname = function(s, hostname) { +var FilterPlainHnAnchoredHostname = function(s, domainOpt) { this.s = s; - this.hostname = hostname; - this.hostnameTest = hostname.charAt(0) !== '~' ? hostnameHit : hostnameMiss; + this.domainOpt = domainOpt; + this.hostnameTest = hostnameTestPicker(this); }; FilterPlainHnAnchoredHostname.prototype.match = function(url, tokenBeg) { - if ( this.hostnameTest(this.hostname) === false ) { + if ( this.hostnameTest(this) === false ) { return false; } if ( url.substr(tokenBeg, this.s.length) !== this.s ) { @@ -609,11 +733,11 @@ FilterPlainHnAnchoredHostname.prototype.rtfid = '||ah'; FilterPlainHnAnchoredHostname.prototype.toSelfie = FilterPlainHnAnchoredHostname.prototype.rtCompile = function() { - return this.s + '\t' + this.hostname; + return this.s + '\t' + this.domainOpt; }; -FilterPlainHnAnchoredHostname.compile = function(details, hostname) { - return details.f + '\t' + hostname; +FilterPlainHnAnchoredHostname.compile = function(details) { + return details.f + '\t' + details.domainOpt; }; FilterPlainHnAnchoredHostname.fromSelfie = function(s) { @@ -660,16 +784,16 @@ FilterGeneric.fromSelfie = function(s) { // Generic filter -var FilterGenericHostname = function(s, anchor, hostname) { +var FilterGenericHostname = function(s, anchor, domainOpt) { FilterGeneric.call(this, s, anchor); - this.hostname = hostname; - this.hostnameTest = hostname.charAt(0) !== '~' ? hostnameHit : hostnameMiss; + this.domainOpt = domainOpt; + this.hostnameTest = hostnameTestPicker(this); }; FilterGenericHostname.prototype = Object.create(FilterGeneric.prototype); FilterGenericHostname.prototype.constructor = FilterGenericHostname; FilterGenericHostname.prototype.match = function(url) { - if ( this.hostnameTest(this.hostname) === false ) { + if ( this.hostnameTest(this) === false ) { return false; } return FilterGeneric.prototype.match.call(this, url); @@ -681,11 +805,11 @@ FilterGenericHostname.prototype.rtfid = '_h'; FilterGenericHostname.prototype.toSelfie = FilterGenericHostname.prototype.rtCompile = function() { - return FilterGeneric.prototype.toSelfie.call(this) + '\t' + this.hostname; + return FilterGeneric.prototype.toSelfie.call(this) + '\t' + this.domainOpt; }; -FilterGenericHostname.compile = function(details, hostname) { - return FilterGeneric.compile(details) + '\t' + hostname; +FilterGenericHostname.compile = function(details) { + return FilterGeneric.compile(details) + '\t' + details.domainOpt; }; FilterGenericHostname.fromSelfie = function(s) { @@ -739,16 +863,16 @@ FilterGenericHnAnchored.fromSelfie = function(s) { /******************************************************************************/ -var FilterGenericHnAnchoredHostname = function(s, hostname) { +var FilterGenericHnAnchoredHostname = function(s, domainOpt) { FilterGenericHnAnchored.call(this, s); - this.hostname = hostname; - this.hostnameTest = hostname.charAt(0) !== '~' ? hostnameHit : hostnameMiss; + this.domainOpt = domainOpt; + this.hostnameTest = hostnameTestPicker(this); }; FilterGenericHnAnchoredHostname.prototype = Object.create(FilterGenericHnAnchored.prototype); FilterGenericHnAnchoredHostname.prototype.constructor = FilterGenericHnAnchoredHostname; FilterGenericHnAnchoredHostname.prototype.match = function(url) { - if ( this.hostnameTest(this.hostname) === false ) { + if ( this.hostnameTest(this) === false ) { return false; } return FilterGenericHnAnchored.prototype.match.call(this, url); @@ -760,11 +884,11 @@ FilterGenericHnAnchoredHostname.prototype.rtfid = '||_h'; FilterGenericHnAnchoredHostname.prototype.toSelfie = FilterGenericHnAnchoredHostname.prototype.rtCompile = function() { - return this.s + '\t' + this.hostname; + return this.s + '\t' + this.domainOpt; }; -FilterGenericHnAnchoredHostname.compile = function(details, hostname) { - return details.f + '\t' + hostname; +FilterGenericHnAnchoredHostname.compile = function(details) { + return details.f + '\t' + details.domainOpt; }; FilterGenericHnAnchoredHostname.fromSelfie = function(s) { @@ -803,15 +927,15 @@ FilterRegex.fromSelfie = function(s) { /******************************************************************************/ -var FilterRegexHostname = function(s, hostname) { +var FilterRegexHostname = function(s, domainOpt) { this.re = new RegExp(s); - this.hostname = hostname; - this.hostnameTest = hostname.charAt(0) !== '~' ? hostnameHit : hostnameMiss; + this.domainOpt = domainOpt; + this.hostnameTest = hostnameTestPicker(this); }; FilterRegexHostname.prototype.match = function(url) { // test hostname first, it's cheaper than evaluating a regex - return this.hostnameTest(this.hostname) && + return this.hostnameTest(this) && this.re.test(url); }; @@ -821,11 +945,11 @@ FilterRegexHostname.prototype.rtfid = '//h'; FilterRegexHostname.prototype.toSelfie = FilterRegexHostname.prototype.rtCompile = function() { - return this.re.source + '\t' + this.hostname; + return this.re.source + '\t' + this.domainOpt; }; -FilterRegexHostname.compile = function(details, hostname) { - return details.f + '\t' + hostname; +FilterRegexHostname.compile = function(details) { + return details.f + '\t' + details.domainOpt; }; FilterRegexHostname.fromSelfie = function(s) { @@ -1144,6 +1268,9 @@ FilterBucket.fromSelfie = function() { /******************************************************************************/ var getFilterClass = function(details) { + if ( details.domainOpt.length !== 0 ) { + return getHostnameBasedFilterClass(details); + } if ( details.isRegex ) { return FilterRegex; } @@ -1232,7 +1359,7 @@ var FilterParser = function() { this.reCleanupHostname = /^\|\|[.*]*/; this.reIsolateHostname = /^([^\x00-\x24\x26-\x2C\x2F\x3A-\x5E\x60\x7B-\x7F]+)(.*)/; this.reHasUnicode = /[^\x00-\x7F]/; - this.hostnames = []; + this.domainOpt = ''; this.reset(); }; @@ -1265,7 +1392,7 @@ FilterParser.prototype.reset = function() { this.fopts = ''; this.hostnameAnchored = false; this.hostnamePure = false; - this.hostnames.length = 0; + this.domainOpt = ''; this.isRegex = false; this.thirdParty = false; this.token = ''; @@ -1313,15 +1440,6 @@ FilterParser.prototype.parseOptParty = function(firstParty, not) { /******************************************************************************/ -FilterParser.prototype.parseOptHostnames = function(raw) { - var hostnames = raw.split('|'); - for ( var i = 0; i < hostnames.length; i++ ) { - this.hostnames.push(hostnames[i]); - } -}; - -/******************************************************************************/ - FilterParser.prototype.parseOptions = function(s) { this.fopts = s; var opts = s.split(','); @@ -1358,7 +1476,7 @@ FilterParser.prototype.parseOptions = function(s) { continue; } if ( opt.slice(0,7) === 'domain=' ) { - this.parseOptHostnames(opt.slice(7)); + this.domainOpt = opt.slice(7); continue; } if ( opt === 'important' ) { @@ -1801,7 +1919,7 @@ FilterContainer.prototype.compile = function(raw, out) { FilterContainer.prototype.compileHostnameOnlyFilter = function(parsed, out) { // Can't fit the filter in a pure hostname dictionary. - if ( parsed.hostnames.length !== 0 ) { + if ( parsed.domainOpt.length !== 0 ) { return; } @@ -1852,34 +1970,17 @@ FilterContainer.prototype.compileFilter = function(parsed, out) { party = parsed.firstParty ? FirstParty : ThirdParty; } - var filterClass; - var i = parsed.hostnames.length; - - // Applies everywhere - if ( i === 0 ) { - filterClass = getFilterClass(parsed); - if ( filterClass === null ) { - return false; - } - this.compileToAtomicFilter(filterClass, parsed, party, out); - return true; + var filterClass = getFilterClass(parsed); + if ( filterClass === null ) { + return false; } - - // Applies to specific domains - while ( i-- ) { - filterClass = getHostnameBasedFilterClass(parsed); - if ( filterClass === null ) { - return false; - } - this.compileToAtomicFilter(filterClass, parsed, party, out, parsed.hostnames[i]); - } - + this.compileToAtomicFilter(filterClass, parsed, party, out); return true; }; /******************************************************************************/ -FilterContainer.prototype.compileToAtomicFilter = function(filterClass, parsed, party, out, hostname) { +FilterContainer.prototype.compileToAtomicFilter = function(filterClass, parsed, party, out) { var bits = parsed.action | parsed.important | party; var type = parsed.types; if ( type === 0 ) { @@ -1888,7 +1989,7 @@ FilterContainer.prototype.compileToAtomicFilter = function(filterClass, parsed, toHex(bits) + '\v' + parsed.token + '\v' + filterClass.fid + '\v' + - filterClass.compile(parsed, hostname) + filterClass.compile(parsed) ); return; } @@ -1900,7 +2001,7 @@ FilterContainer.prototype.compileToAtomicFilter = function(filterClass, parsed, toHex(bits | (bitOffset << 4)) + '\v' + parsed.token + '\v' + filterClass.fid + '\v' + - filterClass.compile(parsed, hostname) + filterClass.compile(parsed) ); } bitOffset += 1;