Add optional support for generic procedural cosmetic filters

Related issue:
- https://github.com/uBlockOrigin/uBlock-issues/issues/131

The new advanced setting and its default value is:

    allowGenericProceduralFilters false

Whenever this setting is toggled, the user is responsible
of forcing a reload of all filter lists so as to allow uBO
to process differently any existing generic procedural
cosmetic filters.
This commit is contained in:
Raymond Hill 2019-05-18 18:57:32 -04:00
parent e66e4496ed
commit 1caff7429e
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
2 changed files with 24 additions and 43 deletions

View File

@ -36,6 +36,7 @@ if ( vAPI.webextFlavor === undefined ) {
const µBlock = (function() { // jshint ignore:line
const hiddenSettingsDefault = {
allowGenericProceduralFilters: false,
assetFetchTimeout: 30,
autoCommentFilterTemplate: '{{date}} {{origin}}',
autoUpdateAssetFetchPeriod: 120,
@ -54,7 +55,7 @@ const µBlock = (function() { // jshint ignore:line
selfieAfter: 3,
strictBlockingBypassDuration: 120,
suspendTabsUntilReady: 'unset',
userResourcesLocation: 'unset'
userResourcesLocation: 'unset',
};
return {
@ -135,7 +136,7 @@ const µBlock = (function() { // jshint ignore:line
// Read-only
systemSettings: {
compiledMagic: 15, // Increase when compiled format changes
compiledMagic: 16, // Increase when compiled format changes
selfieMagic: 16 // Increase when selfie format changes
},

View File

@ -62,8 +62,8 @@ const SelectorCacheEntry = class {
}
addCosmetic(details) {
let selectors = details.selectors,
i = selectors.length || 0;
const selectors = details.selectors;
let i = selectors.length || 0;
// https://github.com/gorhill/uBlock/issues/2011
// Avoiding seemingly pointless surveys only if they appear costly.
if ( details.first && i === 0 ) {
@ -90,8 +90,8 @@ const SelectorCacheEntry = class {
if ( this.net.size < SelectorCacheEntry.netHighWaterMark ) {
return;
}
let dict = this.net;
let keys = Array.from(dict.keys()).sort(function(a, b) {
const dict = this.net;
const keys = Array.from(dict.keys()).sort(function(a, b) {
return dict.get(b) - dict.get(a);
}).slice(SelectorCacheEntry.netLowWaterMark);
let i = keys.length;
@ -146,7 +146,7 @@ const SelectorCacheEntry = class {
retrieve(type, out) {
this.lastAccessTime = Date.now();
let iterator = type === 'cosmetic' ? this.cosmetic : this.net.keys();
const iterator = type === 'cosmetic' ? this.cosmetic : this.net.keys();
if ( Array.isArray(out) ) {
this.retrieveToArray(iterator, out);
} else {
@ -201,9 +201,6 @@ const FilterContainer = function() {
this.netSelectorCacheCountMax = SelectorCacheEntry.netHighWaterMark;
this.selectorCacheTimer = null;
// generic exception filters
this.genericDonthideSet = new Set();
// specific filters
this.specificFilters = new µb.staticExtFilteringEngine.HostnameBasedDB(2);
@ -269,9 +266,6 @@ FilterContainer.prototype.reset = function() {
// generic filters
this.hasGenericHide = false;
// generic exception filters
this.genericDonthideSet.clear();
// hostname, entity-based filters
this.specificFilters.clear();
@ -312,7 +306,6 @@ FilterContainer.prototype.freeze = function() {
this.frozen = true;
};
/******************************************************************************/
// https://github.com/gorhill/uBlock/issues/1668
@ -429,11 +422,17 @@ FilterContainer.prototype.compileGenericHideSelector = function(
// https://github.com/uBlockOrigin/uBlock-issues/issues/464
// Pseudoclass-based selectors can be compiled, but are also valid
// plain selectors.
// https://github.com/uBlockOrigin/uBlock-issues/issues/131
// Support generic procedural filters as per advanced settings.
// TODO: prevent double compilation.
if (
compiled === undefined ||
compiled !== selector &&
µb.staticExtFilteringEngine.compileSelector.pseudoclass !== true
) {
if ( µb.hiddenSettings.allowGenericProceduralFilters === true ) {
return this.compileSpecificSelector('', parsed, writer);
}
const who = writer.properties.get('assetKey') || '?';
µb.logger.writeOne({
realm: 'message',
@ -450,8 +449,8 @@ FilterContainer.prototype.compileGenericHideSelector = function(
writer.push([
type === 0x23 /* '#' */ ? 1 : 3,
key.slice(1),
selector ]
);
selector
]);
return;
}
@ -493,7 +492,7 @@ FilterContainer.prototype.compileGenericUnhideSelector = function(
writer
) {
// Procedural cosmetic filters are acceptable as generic exception filters.
let compiled = µb.staticExtFilteringEngine.compileSelector(parsed.suffix);
const compiled = µb.staticExtFilteringEngine.compileSelector(parsed.suffix);
if ( compiled === undefined ) {
const who = writer.properties.get('assetKey') || '?';
µb.logger.writeOne({
@ -505,9 +504,12 @@ FilterContainer.prototype.compileGenericUnhideSelector = function(
}
// https://github.com/chrisaljoudi/uBlock/issues/497
// All generic exception filters are put in the same bucket: they are
// expected to be very rare.
writer.push([ 7 /* g1 */, compiled ]);
// All generic exception filters are stored as hostname-based filter
// whereas the hostname is the empty string (which matches all
// hostnames). No distinction is made between declarative and
// procedural selectors, since they really exist only to cancel
// out other cosmetic filters.
writer.push([ 8, '', 0b01, compiled ]);
};
/******************************************************************************/
@ -622,13 +624,6 @@ FilterContainer.prototype.fromCompiledContent = function(reader, options) {
this.highlyGeneric.complex.dict.add(args[1]);
break;
// https://github.com/chrisaljoudi/uBlock/issues/497
// Generic exception filters: expected to be a rare occurrence.
// #@#.tweet
case 7:
this.genericDonthideSet.add(args[1]);
break;
// hash, example.com, .promoted-tweet
// hash, example.*, .promoted-tweet
case 8:
@ -660,13 +655,6 @@ FilterContainer.prototype.skipGenericCompiledContent = function(reader) {
switch ( args[0] ) {
// https://github.com/chrisaljoudi/uBlock/issues/497
// Generic exception filters: expected to be a rare occurrence.
case 7:
this.duplicateBuster.add(fingerprint);
this.genericDonthideSet.add(args[1]);
break;
// hash, example.com, .promoted-tweet
// hash, example.*, .promoted-tweet
case 8:
@ -707,7 +695,6 @@ FilterContainer.prototype.toSelfie = function() {
lowlyGenericCCL: Array.from(this.lowlyGeneric.cl.complex),
highSimpleGenericHideArray: Array.from(this.highlyGeneric.simple.dict),
highComplexGenericHideArray: Array.from(this.highlyGeneric.complex.dict),
genericDonthideArray: Array.from(this.genericDonthideSet)
};
};
@ -726,7 +713,6 @@ FilterContainer.prototype.fromSelfie = function(selfie) {
this.highlyGeneric.simple.str = selfie.highSimpleGenericHideArray.join(',\n');
this.highlyGeneric.complex.dict = new Set(selfie.highComplexGenericHideArray);
this.highlyGeneric.complex.str = selfie.highComplexGenericHideArray.join(',\n');
this.genericDonthideSet = new Set(selfie.genericDonthideArray);
this.frozen = true;
};
@ -989,13 +975,6 @@ FilterContainer.prototype.retrieveSpecificSelectors = function(
};
if ( options.noCosmeticFiltering !== true ) {
// Exception cosmetic filters: prime with generic exception filters.
const exceptionSet = this.setRegister0;
// Genetic exceptions (should be extremely rare).
for ( let exception of this.genericDonthideSet ) {
exceptionSet.add(exception);
}
const specificSet = this.setRegister1;
// Cached cosmetic filters: these are always declarative.
if ( cacheEntry !== undefined ) {
@ -1006,6 +985,7 @@ FilterContainer.prototype.retrieveSpecificSelectors = function(
}
}
const exceptionSet = this.setRegister0;
const proceduralSet = this.setRegister2;
this.specificFilters.retrieve(