[mv3] Add support for highly generic cosmetic filters

Related issue:
- https://github.com/uBlockOrigin/uBOL-issues/issues/54
This commit is contained in:
Raymond Hill 2023-07-06 15:45:45 -04:00
parent 1809a9b32c
commit 872eafa378
No known key found for this signature in database
GPG key ID: 25E1490B761470C2
3 changed files with 136 additions and 11 deletions

View file

@ -79,7 +79,12 @@ const arrayEq = (a = [], b = [], sort = true) => {
const normalizeRegisteredContentScripts = registered => {
for ( const entry of registered ) {
const { js } = entry;
const { css = [], js = [] } = entry;
for ( let i = 0; i < css.length; i++ ) {
const path = css[i];
if ( path.startsWith('/') ) { continue; }
css[i] = `/${path}`;
}
for ( let i = 0; i < js.length; i++ ) {
const path = js[i];
if ( path.startsWith('/') ) { continue; }
@ -91,6 +96,78 @@ const normalizeRegisteredContentScripts = registered => {
/******************************************************************************/
function registerHighGeneric(context, genericDetails) {
const { before, filteringModeDetails, rulesetsDetails } = context;
const excludeHostnames = [];
const css = [];
for ( const details of rulesetsDetails ) {
const hostnames = genericDetails.get(details.id);
if ( hostnames !== undefined ) {
excludeHostnames.push(...hostnames);
}
const count = details.css?.generichigh || 0;
if ( count === 0 ) { continue; }
css.push(`/rulesets/scripting/generichigh/${details.id}.css`);
}
if ( css.length === 0 ) { return; }
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(
...ut.matchesFromHostnames(
ut.subtractHostnameIters(
Array.from(complete),
excludeHostnames
)
)
);
}
if ( matches.length === 0 ) { return; }
const registered = before.get('css-generichigh');
before.delete('css-generichigh'); // Important!
// https://github.com/w3c/webextensions/issues/414#issuecomment-1623992885
// Once supported, add:
// cssOrigin: 'USER',
const directive = {
id: 'css-generichigh',
css,
matches,
excludeMatches,
runAt: 'document_end',
};
// register
if ( registered === undefined ) {
context.toAdd.push(directive);
return;
}
// update
if (
arrayEq(registered.css, css, false) === false ||
arrayEq(registered.matches, matches) === false ||
arrayEq(registered.excludeMatches, excludeMatches) === false
) {
context.toRemove.push('css-generichigh');
context.toAdd.push(directive);
}
}
/******************************************************************************/
function registerGeneric(context, genericDetails) {
const { before, filteringModeDetails, rulesetsDetails } = context;
@ -459,6 +536,7 @@ async function registerInjectables(origins) {
registerScriptlet(context, scriptletDetails);
registerSpecific(context);
registerGeneric(context, genericDetails);
registerHighGeneric(context, genericDetails);
toRemove.push(...Array.from(before.keys()));

View file

@ -382,7 +382,8 @@ function loadAllSourceScriptlets() {
const originalScriptletMap = new Map();
for ( const details of results ) {
originalScriptletMap.set(
details.file.replace('.template.js', ''),
details.file.replace('.template.js', '')
.replace('.template.css', ''),
details.text
);
}
@ -395,7 +396,7 @@ function loadAllSourceScriptlets() {
/******************************************************************************/
async function processGenericCosmeticFilters(assetDetails, bucketsMap, exclusions) {
async function processGenericCosmeticFilters(assetDetails, bucketsMap) {
if ( bucketsMap === undefined ) { return 0; }
if ( bucketsMap.size === 0 ) { return 0; }
const bucketsList = Array.from(bucketsMap);
@ -419,8 +420,6 @@ async function processGenericCosmeticFilters(assetDetails, bucketsMap, exclusion
patchedScriptlet
);
genericDetails.set(assetDetails.id, exclusions.sort());
log(`CSS-generic: ${count} plain CSS selectors`);
return count;
@ -428,6 +427,33 @@ async function processGenericCosmeticFilters(assetDetails, bucketsMap, exclusion
/******************************************************************************/
async function processGenericHighCosmeticFilters(assetDetails, selectorSet) {
if ( selectorSet === undefined ) { return 0; }
if ( selectorSet.size === 0 ) { return 0; }
const selectorLists = Array.from(selectorSet).sort().join(',\n');
const originalScriptletMap = await loadAllSourceScriptlets();
let patchedScriptlet = originalScriptletMap.get('css-generichigh').replace(
'$rulesetId$',
assetDetails.id
);
patchedScriptlet = safeReplace(patchedScriptlet,
/\$selectorList\$/,
selectorLists
);
writeFile(
`${scriptletDir}/generichigh/${assetDetails.id}.css`,
patchedScriptlet
);
log(`CSS-generic-high: ${selectorSet.size} plain CSS selectors`);
return selectorSet.size;
}
/******************************************************************************/
// This merges selectors which are used by the same hostnames
function groupSelectorsByHostnames(mapin) {
@ -887,10 +913,23 @@ async function rulesetFromURLs(assetDetails) {
log(rejectedCosmetic.map(line => `\t${line}`).join('\n'), true);
}
if (
Array.isArray(results.network.generichideExclusions) &&
results.network.generichideExclusions.length !== 0
) {
genericDetails.set(
assetDetails.id,
results.network.generichideExclusions.filter(hn => hn.endsWith('.*') === false).sort()
);
}
const genericCosmeticStats = await processGenericCosmeticFilters(
assetDetails,
results.genericCosmetic,
results.network.generichideExclusions.filter(hn => hn.endsWith('.*') === false)
results.genericCosmetic
);
const genericHighCosmeticStats = await processGenericHighCosmeticFilters(
assetDetails,
results.genericHighCosmetic
);
const specificCosmeticStats = await processCosmeticFilters(
assetDetails,
@ -933,6 +972,7 @@ async function rulesetFromURLs(assetDetails) {
},
css: {
generic: genericCosmeticStats,
generichigh: genericHighCosmeticStats,
specific: specificCosmeticStats,
declarative: declarativeStats,
procedural: proceduralStats,

View file

@ -147,17 +147,23 @@ function addExtendedToDNR(context, parser) {
// Generic cosmetic filtering
if ( parser.hasOptions() === false ) {
if ( context.genericCosmeticFilters === undefined ) {
context.genericCosmeticFilters = new Map();
}
const { compiled } = parser.result;
if ( compiled === undefined ) { return; }
if ( compiled.length <= 1 ) { return; }
if ( compiled.charCodeAt(0) === 0x7B /* '{' */ ) { return; }
const key = keyFromSelector(compiled);
if ( key === undefined ) { return; }
if ( key === undefined ) {
if ( context.genericHighCosmeticFilters === undefined ) {
context.genericHighCosmeticFilters = new Set();
}
context.genericHighCosmeticFilters.add(compiled);
return;
}
const type = key.charCodeAt(0);
const hash = hashFromStr(type, key.slice(1));
if ( context.genericCosmeticFilters === undefined ) {
context.genericCosmeticFilters = new Map();
}
let bucket = context.genericCosmeticFilters.get(hash);
if ( bucket === undefined ) {
context.genericCosmeticFilters.set(hash, bucket = []);
@ -291,6 +297,7 @@ async function dnrRulesetFromRawLists(lists, options = {}) {
return {
network: staticNetFilteringEngine.dnrFromCompiled('end', context),
genericCosmetic: context.genericCosmeticFilters,
genericHighCosmetic: context.genericHighCosmeticFilters,
specificCosmetic: context.specificCosmeticFilters,
scriptlet: context.scriptletFilters,
};