[mv3] Add support for explicit generichide filter option

`generichide` option is implicitly enforced on all sites unless an
exception overrides `generichide`. Though rare, sometimes a
`generichide` exception needs to be overridden so that generic
cosmetic filtering is made possible on a specific site.

This commit is to add support for restoring generic cosmetic
filtering on sites which were excluded through a `generichide`
exception.

Concretely, this is needed to ensure the test suite can properly
verify that generic cosmetic filtering is working when the
filtering mode is set to "complete":

||ublockorigin.github.io^$generichide,important
This commit is contained in:
Raymond Hill 2025-03-18 17:15:35 -04:00
parent 839e240ec1
commit 98b011f64c
No known key found for this signature in database
GPG key ID: 25E1490B761470C2
4 changed files with 151 additions and 59 deletions

View file

@ -66,6 +66,15 @@ const arrayEq = (a = [], b = [], sort = true) => {
/******************************************************************************/
const normalizeMatches = matches => {
if ( matches.length <= 1 ) { return; }
if ( matches.includes('<all_urls>') === false ) { return; }
matches.length = 0;
matches.push('<all_urls>');
};
/******************************************************************************/
// The extensions API does not always return exactly what we fed it, so we
// need to normalize some entries to be sure we properly detect changes when
// comparing registered entries vs. entries to register.
@ -93,11 +102,17 @@ function registerHighGeneric(context, genericDetails) {
const { before, filteringModeDetails, rulesetsDetails } = context;
const excludeHostnames = [];
const includeHostnames = [];
const css = [];
for ( const details of rulesetsDetails ) {
const hostnames = genericDetails.get(details.id);
if ( hostnames !== undefined ) {
excludeHostnames.push(...hostnames);
if ( hostnames ) {
if ( hostnames.unhide ) {
excludeHostnames.push(...hostnames.unhide);
}
if ( hostnames.hide ) {
includeHostnames.push(...hostnames.hide);
}
}
const count = details.css?.generichigh || 0;
if ( count === 0 ) { continue; }
@ -165,12 +180,18 @@ function registerHighGeneric(context, genericDetails) {
function registerGeneric(context, genericDetails) {
const { before, filteringModeDetails, rulesetsDetails } = context;
const excludeHostnames = [];
const excludedByFilter = [];
const includedByFilter = [];
const js = [];
for ( const details of rulesetsDetails ) {
const hostnames = genericDetails.get(details.id);
if ( hostnames !== undefined ) {
excludeHostnames.push(...hostnames);
if ( hostnames ) {
if ( hostnames.unhide ) {
excludedByFilter.push(...hostnames.unhide);
}
if ( hostnames.hide ) {
includedByFilter.push(...hostnames.hide);
}
}
const count = details.css?.generic || 0;
if ( count === 0 ) { continue; }
@ -183,53 +204,86 @@ function registerGeneric(context, genericDetails) {
js.push('/js/scripting/css-generic.js');
const { none, basic, optimal, complete } = filteringModeDetails;
const matches = [];
const excludeMatches = [];
if ( complete.has('all-urls') ) {
excludeMatches.push(...ut.matchesFromHostnames(none));
excludeMatches.push(...ut.matchesFromHostnames(basic));
excludeMatches.push(...ut.matchesFromHostnames(optimal));
excludeMatches.push(...ut.matchesFromHostnames(excludeHostnames));
matches.push('<all_urls>');
} else {
matches.push(
const includedByMode = [ ...complete ];
const excludedByMode = [ ...none, ...basic, ...optimal ];
if ( complete.has('all-urls') === false ) {
const matches = [
...ut.matchesFromHostnames(
ut.subtractHostnameIters(
Array.from(complete),
excludeHostnames
)
)
);
}
if ( matches.length === 0 ) { return; }
const registered = before.get('css-generic');
before.delete('css-generic'); // Important!
const directive = {
id: 'css-generic',
js,
allFrames: true,
matches,
excludeMatches,
runAt: 'document_idle',
};
// register
if ( registered === undefined ) {
context.toAdd.push(directive);
ut.subtractHostnameIters(includedByMode, excludedByFilter)
),
...ut.matchesFromHostnames(
ut.intersectHostnameIters(includedByMode, includedByFilter)
),
];
if ( matches.length === 0 ) { return; }
const registered = before.get('css-generic-some');
before.delete('css-generic-some'); // Important!
const directive = {
id: 'css-generic-some',
js,
allFrames: true,
matches,
runAt: 'document_idle',
};
if ( registered === undefined ) { // register
context.toAdd.push(directive);
} else if ( // update
arrayEq(registered.js, js, false) === false ||
arrayEq(registered.matches, directive.matches) === false
) {
context.toRemove.push('css-generic-some');
context.toAdd.push(directive);
}
return;
}
// update
if (
arrayEq(registered.js, js, false) === false ||
arrayEq(registered.matches, matches) === false ||
arrayEq(registered.excludeMatches, excludeMatches) === false
const excludeMatches = [
...ut.matchesFromHostnames(excludedByMode),
...ut.matchesFromHostnames(excludedByFilter),
];
const registeredAll = before.get('css-generic-all');
before.delete('css-generic-all'); // Important!
const directiveAll = {
id: 'css-generic-all',
js,
allFrames: true,
matches: [ '<all_urls>' ],
excludeMatches,
runAt: 'document_idle',
};
if ( registeredAll === undefined ) { // register
context.toAdd.push(directiveAll);
} else if ( // update
arrayEq(registeredAll.js, js, false) === false ||
arrayEq(registeredAll.excludeMatches, directiveAll.excludeMatches) === false
) {
context.toRemove.push('css-generic');
context.toAdd.push(directive);
context.toRemove.push('css-generic-all');
context.toAdd.push(directiveAll);
}
const matches = [
...ut.matchesFromHostnames(
ut.subtractHostnameIters(includedByFilter, excludedByMode)
),
];
if ( matches.length === 0 ) { return; }
const registeredSome = before.get('css-generic-some');
before.delete('css-generic-some'); // Important!
const directiveSome = {
id: 'css-generic-some',
js,
allFrames: true,
matches,
runAt: 'document_idle',
};
if ( registeredSome === undefined ) { // register
context.toAdd.push(directiveSome);
} else if ( // update
arrayEq(registeredSome.js, js, false) === false ||
arrayEq(registeredSome.matches, directiveSome.matches) === false
) {
context.toRemove.push('css-generic-some');
context.toAdd.push(directiveSome);
}
}
@ -253,6 +307,8 @@ function registerProcedural(context) {
];
if ( matches.length === 0 ) { return; }
normalizeMatches(matches);
js.unshift('/js/scripting/isolated-api.js');
js.push('/js/scripting/css-procedural.js');
@ -313,6 +369,8 @@ function registerDeclarative(context) {
];
if ( matches.length === 0 ) { return; }
normalizeMatches(matches);
js.unshift('/js/scripting/isolated-api.js');
js.push('/js/scripting/css-declarative.js');
@ -373,6 +431,8 @@ function registerSpecific(context) {
];
if ( matches.length === 0 ) { return; }
normalizeMatches(matches);
js.unshift('/js/scripting/isolated-api.js');
js.push('/js/scripting/css-specific.js');

View file

@ -100,15 +100,13 @@ const subtractHostnameIters = (itera, iterb) => {
/******************************************************************************/
const matchFromHostname = hn =>
hn === '*' || hn === 'all-urls' ? '<all_urls>' : `*://*.${hn}/*`;
const matchesFromHostnames = hostnames => {
const out = [];
for ( const hn of hostnames ) {
if ( hn === '*' || hn === 'all-urls' ) {
out.length = 0;
out.push('<all_urls>');
break;
}
out.push(`*://*.${hn}/*`);
out.push(matchFromHostname(hn));
}
return out;
};
@ -144,6 +142,7 @@ export {
isDescendantHostnameOfIter,
intersectHostnameIters,
subtractHostnameIters,
matchFromHostname,
matchesFromHostnames,
hostnamesFromMatches,
};

View file

@ -1245,14 +1245,25 @@ async function rulesetFromURLs(assetDetails) {
log(rejectedCosmetic.map(line => `\t${line}`).join('\n'), true);
}
const genericDetailsForRuleset = {};
if (
Array.isArray(results.network.generichideExclusions) &&
results.network.generichideExclusions.length !== 0
) {
genericDetails.set(
assetDetails.id,
results.network.generichideExclusions.filter(hn => hn.endsWith('.*') === false).sort()
);
genericDetailsForRuleset.unhide = results.network.generichideExclusions
.filter(hn => hn.endsWith('.*') === false)
.sort();
}
if (
Array.isArray(results.network.generichideInclusions) &&
results.network.generichideInclusions.length !== 0
) {
genericDetailsForRuleset.hide = results.network.generichideInclusions
.filter(hn => hn.endsWith('.*') === false)
.sort();
}
if ( genericDetailsForRuleset.unhide || genericDetailsForRuleset.hide ) {
genericDetails.set(assetDetails.id, genericDetailsForRuleset);
}
const genericCosmeticStats = await processGenericCosmeticFilters(
@ -1367,8 +1378,8 @@ async function main() {
],
dnrURL: 'https://ublockorigin.github.io/uAssets/dnr/default.json',
homeURL: 'https://github.com/uBlockOrigin/uAssets',
filters: [
],
filters: [`
`],
});
await rulesetFromURLs({

View file

@ -4511,6 +4511,7 @@ StaticNetFilteringEngine.prototype.dnrFromCompiled = function(op, context, ...ar
// Collect generichide filters
const generichideExclusions = [];
const generichideInclusions = [];
{
const bucket = buckets.get(ALLOW_REALM | typeNameToTypeValue['generichide']);
if ( bucket ) {
@ -4522,6 +4523,26 @@ StaticNetFilteringEngine.prototype.dnrFromCompiled = function(op, context, ...ar
} else if ( rule.condition.requestDomains ) {
generichideExclusions.push(...rule.condition.requestDomains);
}
if ( rule.condition.excludedInitiatorDomains ) {
generichideInclusions.push(...rule.condition.excludedInitiatorDomains);
} else if ( rule.condition.excludedRequestDomains ) {
generichideInclusions.push(...rule.condition.excludedRequestDomains);
}
}
}
}
}
{
const bucket = buckets.get(BLOCKIMPORTANT_REALM | typeNameToTypeValue['generichide']);
if ( bucket ) {
for ( const rules of bucket.values() ) {
for ( const rule of rules ) {
if ( rule.condition === undefined ) { continue; }
if ( rule.condition.initiatorDomains ) {
generichideInclusions.push(...rule.condition.initiatorDomains);
} else if ( rule.condition.requestDomains ) {
generichideInclusions.push(...rule.condition.requestDomains);
}
}
}
}
@ -4714,6 +4735,7 @@ StaticNetFilteringEngine.prototype.dnrFromCompiled = function(op, context, ...ar
acceptedFilterCount: context.acceptedFilterCount,
rejectedFilterCount: context.rejectedFilterCount,
generichideExclusions: Array.from(new Set(generichideExclusions)),
generichideInclusions: Array.from(new Set(generichideInclusions)),
};
};