[mv3] Refactor content scripts related to specific cosmetic filtering

Specifically, avoid long list of hostnames for the `matches`
property[1] when registering the content scripts, as this was causing
whole browser freeze for long seconds in Chromium-based browsers
(reason unknown).

The content scripts themselves will sort out which cosmetic filters to
apply on which websites.

This change makes it now possible to support annoyances-related lists,
and thus two lists have been added:
- EasyList -- Annoyances
- EasyList -- Cookies

Related issue:
- https://github.com/uBlockOrigin/uBOL-issues/issues/5

These annoyances-related lists contains many thousands of specific
cosmetic filters and as a result, before the above change this was
causing long seconds of whole browser freeze when simply modifying
the blocking mode of a specific site via the slider in the popup
panel.

It is now virtually instantaneous, at the cost of injecting larger
cosmetic filtering-related content scripts (which typically should
be garbage-collected within single-digit milliseconds).

Also, added support for entity-based cosmetic filters. (They were
previously discarded).

---

[1] https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/RegisteredContentScript
This commit is contained in:
Raymond Hill 2023-06-03 21:47:40 -04:00
parent ec230be331
commit 72726a4759
No known key found for this signature in database
GPG key ID: 25E1490B761470C2
14 changed files with 567 additions and 761 deletions

View file

@ -307,9 +307,14 @@ async function init() {
ruleCount += rules.removeparam + rules.redirect + rules.csp; ruleCount += rules.removeparam + rules.redirect + rules.csp;
} }
let specificCount = 0; let specificCount = 0;
if ( css.specific instanceof Object ) { if ( typeof css.specific === 'number' ) {
specificCount += css.specific.domainBased; specificCount += css.specific;
specificCount += css.specific.entityBased; }
if ( typeof css.declarative === 'number' ) {
specificCount += css.declarative;
}
if ( typeof css.procedural === 'number' ) {
specificCount += css.procedural;
} }
dom.text( dom.text(
qs$(div, 'p'), qs$(div, 'p'),

View file

@ -394,10 +394,10 @@ async function updateCspRules() {
if ( addRules.length === 0 && removeRuleIds.length === 0 ) { return; } if ( addRules.length === 0 && removeRuleIds.length === 0 ) { return; }
if ( removeRuleIds.length !== 0 ) { if ( removeRuleIds.length !== 0 ) {
console.info(`Remove ${removeRuleIds.length} DNR redirect rules`); console.info(`Remove ${removeRuleIds.length} DNR csp rules`);
} }
if ( addRules.length !== 0 ) { if ( addRules.length !== 0 ) {
console.info(`Add ${addRules.length} DNR redirect rules`); console.info(`Add ${addRules.length} DNR csp rules`);
} }
return dnr.updateDynamicRules({ addRules, removeRuleIds }); return dnr.updateDynamicRules({ addRules, removeRuleIds });

View file

@ -38,40 +38,6 @@ const isGecko = browser.runtime.getURL('').startsWith('moz-extension://');
const resourceDetailPromises = new Map(); const resourceDetailPromises = new Map();
function getSpecificDetails() {
let promise = resourceDetailPromises.get('specific');
if ( promise !== undefined ) { return promise; }
promise = fetchJSON('/rulesets/specific-details').then(entries => {
const out = new Map();
for ( const entry of entries ) {
out.set(entry[0], new Map(entry[1]));
}
return out;
});
resourceDetailPromises.set('specific', promise);
return promise;
}
function getDeclarativeDetails() {
let promise = resourceDetailPromises.get('declarative');
if ( promise !== undefined ) { return promise; }
promise = fetchJSON('/rulesets/declarative-details').then(
entries => new Map(entries)
);
resourceDetailPromises.set('declarative', promise);
return promise;
}
function getProceduralDetails() {
let promise = resourceDetailPromises.get('procedural');
if ( promise !== undefined ) { return promise; }
promise = fetchJSON('/rulesets/procedural-details').then(
entries => new Map(entries)
);
resourceDetailPromises.set('procedural', promise);
return promise;
}
function getScriptletDetails() { function getScriptletDetails() {
let promise = resourceDetailPromises.get('scriptlet'); let promise = resourceDetailPromises.get('scriptlet');
if ( promise !== undefined ) { return promise; } if ( promise !== undefined ) { return promise; }
@ -135,8 +101,8 @@ function registerGeneric(context, genericDetails) {
if ( hostnames !== undefined ) { if ( hostnames !== undefined ) {
excludeHostnames.push(...hostnames); excludeHostnames.push(...hostnames);
} }
if ( details.css.generic instanceof Object === false ) { continue; } const count = details.css?.generic || 0;
if ( details.css.generic.count === 0 ) { continue; } if ( count === 0 ) { continue; }
js.push(`/rulesets/scripting/generic/${details.id}.js`); js.push(`/rulesets/scripting/generic/${details.id}.js`);
} }
@ -144,19 +110,20 @@ function registerGeneric(context, genericDetails) {
js.push('/js/scripting/css-generic.js'); js.push('/js/scripting/css-generic.js');
const { none, network, extendedSpecific, extendedGeneric } = filteringModeDetails;
const matches = []; const matches = [];
const excludeMatches = []; const excludeMatches = [];
if ( filteringModeDetails.extendedGeneric.has('all-urls') ) { if ( extendedGeneric.has('all-urls') ) {
excludeMatches.push(...ut.matchesFromHostnames(filteringModeDetails.none)); excludeMatches.push(...ut.matchesFromHostnames(none));
excludeMatches.push(...ut.matchesFromHostnames(filteringModeDetails.network)); excludeMatches.push(...ut.matchesFromHostnames(network));
excludeMatches.push(...ut.matchesFromHostnames(filteringModeDetails.extendedSpecific)); excludeMatches.push(...ut.matchesFromHostnames(extendedSpecific));
excludeMatches.push(...ut.matchesFromHostnames(excludeHostnames)); excludeMatches.push(...ut.matchesFromHostnames(excludeHostnames));
matches.push('<all_urls>'); matches.push('<all_urls>');
} else { } else {
matches.push( matches.push(
...ut.matchesFromHostnames( ...ut.matchesFromHostnames(
ut.subtractHostnameIters( ut.subtractHostnameIters(
Array.from(filteringModeDetails.extendedGeneric), Array.from(extendedGeneric),
excludeHostnames excludeHostnames
) )
) )
@ -198,50 +165,33 @@ function registerGeneric(context, genericDetails) {
/******************************************************************************/ /******************************************************************************/
function registerProcedural(context, proceduralDetails) { function registerProcedural(context) {
const { before, filteringModeDetails, rulesetsDetails } = context; const { before, filteringModeDetails, rulesetsDetails } = context;
const js = []; const js = [];
const hostnameMatches = new Set(); for ( const rulesetDetails of rulesetsDetails ) {
for ( const details of rulesetsDetails ) { const count = rulesetDetails.css?.procedural || 0;
if ( details.css.procedural === 0 ) { continue; } if ( count === 0 ) { continue; }
js.push(`/rulesets/scripting/procedural/${details.id}.js`); js.push(`/rulesets/scripting/procedural/${rulesetDetails.id}.js`);
if ( proceduralDetails.has(details.id) ) {
for ( const hn of proceduralDetails.get(details.id) ) {
hostnameMatches.add(hn);
} }
}
}
if ( js.length === 0 ) { return; } if ( js.length === 0 ) { return; }
const { none, network, extendedSpecific, extendedGeneric } = filteringModeDetails;
const matches = [
...ut.matchesFromHostnames(extendedSpecific),
...ut.matchesFromHostnames(extendedGeneric),
];
if ( matches.length === 0 ) { return; }
js.push('/js/scripting/css-procedural.js'); js.push('/js/scripting/css-procedural.js');
const {
none,
network,
extendedSpecific,
extendedGeneric,
} = filteringModeDetails;
const matches = [];
const excludeMatches = []; const excludeMatches = [];
if ( extendedSpecific.has('all-urls') || extendedGeneric.has('all-urls') ) { if ( none.has('all-urls') === false ) {
excludeMatches.push(...ut.matchesFromHostnames(none)); excludeMatches.push(...ut.matchesFromHostnames(none));
excludeMatches.push(...ut.matchesFromHostnames(network));
matches.push(...ut.matchesFromHostnames(hostnameMatches));
} else if ( extendedSpecific.size !== 0 || extendedGeneric.size !== 0 ) {
matches.push(
...ut.matchesFromHostnames(
ut.intersectHostnameIters(
[ ...extendedSpecific, ...extendedGeneric ],
hostnameMatches
)
)
);
} }
if ( network.has('all-urls') === false ) {
if ( matches.length === 0 ) { return; } excludeMatches.push(...ut.matchesFromHostnames(network));
}
const registered = before.get('css-procedural'); const registered = before.get('css-procedural');
before.delete('css-procedural'); // Important! before.delete('css-procedural'); // Important!
@ -277,48 +227,33 @@ function registerProcedural(context, proceduralDetails) {
/******************************************************************************/ /******************************************************************************/
function registerDeclarative(context, declarativeDetails) { function registerDeclarative(context) {
const { before, filteringModeDetails, rulesetsDetails } = context; const { before, filteringModeDetails, rulesetsDetails } = context;
const js = []; const js = [];
const hostnameMatches = []; for ( const rulesetDetails of rulesetsDetails ) {
for ( const details of rulesetsDetails ) { const count = rulesetDetails.css?.declarative || 0;
if ( details.css.declarative === 0 ) { continue; } if ( count === 0 ) { continue; }
js.push(`/rulesets/scripting/declarative/${details.id}.js`); js.push(`/rulesets/scripting/declarative/${rulesetDetails.id}.js`);
if ( declarativeDetails.has(details.id) ) {
hostnameMatches.push(...declarativeDetails.get(details.id));
} }
}
if ( js.length === 0 ) { return; } if ( js.length === 0 ) { return; }
const { none, network, extendedSpecific, extendedGeneric } = filteringModeDetails;
const matches = [
...ut.matchesFromHostnames(extendedSpecific),
...ut.matchesFromHostnames(extendedGeneric),
];
if ( matches.length === 0 ) { return; }
js.push('/js/scripting/css-declarative.js'); js.push('/js/scripting/css-declarative.js');
const {
none,
network,
extendedSpecific,
extendedGeneric,
} = filteringModeDetails;
const matches = [];
const excludeMatches = []; const excludeMatches = [];
if ( extendedSpecific.has('all-urls') || extendedGeneric.has('all-urls') ) { if ( none.has('all-urls') === false ) {
excludeMatches.push(...ut.matchesFromHostnames(none)); excludeMatches.push(...ut.matchesFromHostnames(none));
excludeMatches.push(...ut.matchesFromHostnames(network));
matches.push(...ut.matchesFromHostnames(hostnameMatches));
} else if ( extendedSpecific.size !== 0 || extendedGeneric.size !== 0 ) {
matches.push(
...ut.matchesFromHostnames(
ut.intersectHostnameIters(
[ ...extendedSpecific, ...extendedGeneric ],
hostnameMatches
)
)
);
} }
if ( network.has('all-urls') === false ) {
if ( matches.length === 0 ) { return; } excludeMatches.push(...ut.matchesFromHostnames(network));
}
const registered = before.get('css-declarative'); const registered = before.get('css-declarative');
before.delete('css-declarative'); // Important! before.delete('css-declarative'); // Important!
@ -354,6 +289,68 @@ function registerDeclarative(context, declarativeDetails) {
/******************************************************************************/ /******************************************************************************/
function registerSpecific(context) {
const { before, filteringModeDetails, rulesetsDetails } = context;
const js = [];
for ( const rulesetDetails of rulesetsDetails ) {
const count = rulesetDetails.css?.specific || 0;
if ( count === 0 ) { continue; }
js.push(`/rulesets/scripting/specific/${rulesetDetails.id}.js`);
}
if ( js.length === 0 ) { return; }
const { none, network, extendedSpecific, extendedGeneric } = filteringModeDetails;
const matches = [
...ut.matchesFromHostnames(extendedSpecific),
...ut.matchesFromHostnames(extendedGeneric),
];
if ( matches.length === 0 ) { return; }
js.push('/js/scripting/css-specific.js');
const excludeMatches = [];
if ( none.has('all-urls') === false ) {
excludeMatches.push(...ut.matchesFromHostnames(none));
}
if ( network.has('all-urls') === false ) {
excludeMatches.push(...ut.matchesFromHostnames(network));
}
const registered = before.get('css-specific');
before.delete('css-specific'); // Important!
// register
if ( registered === undefined ) {
context.toAdd.push({
id: 'css-specific',
js,
allFrames: true,
matches,
excludeMatches,
runAt: 'document_start',
});
return;
}
// update
const directive = { id: 'css-specific' };
if ( arrayEq(registered.js, js, false) === false ) {
directive.js = js;
}
if ( arrayEq(registered.matches, matches) === false ) {
directive.matches = matches;
}
if ( arrayEq(registered.excludeMatches, excludeMatches) === false ) {
directive.excludeMatches = excludeMatches;
}
if ( directive.js || directive.matches || directive.excludeMatches ) {
context.toUpdate.push(directive);
}
}
/******************************************************************************/
function registerScriptlet(context, scriptletDetails) { function registerScriptlet(context, scriptletDetails) {
// https://bugzilla.mozilla.org/show_bug.cgi?id=1736575 // https://bugzilla.mozilla.org/show_bug.cgi?id=1736575
// `MAIN` world not yet supported in Firefox // `MAIN` world not yet supported in Firefox
@ -432,210 +429,6 @@ function registerScriptlet(context, scriptletDetails) {
/******************************************************************************/ /******************************************************************************/
function registerSpecific(context, specificDetails) {
const { filteringModeDetails } = context;
let toRegisterMap;
if (
filteringModeDetails.extendedSpecific.has('all-urls') ||
filteringModeDetails.extendedGeneric.has('all-urls')
) {
toRegisterMap = registerSpecificAll(context, specificDetails);
} else {
toRegisterMap = registerSpecificSome(context, specificDetails);
}
for ( const [ fname, hostnames ] of toRegisterMap ) {
toRegisterableScript(context, fname, hostnames);
}
}
function registerSpecificSome(context, specificDetails) {
const { filteringModeDetails, rulesetsDetails } = context;
const toRegisterMap = new Map();
const targetHostnames = [
...filteringModeDetails.extendedSpecific,
...filteringModeDetails.extendedGeneric,
];
const checkMatches = (hostnamesToFidsMap, hn) => {
let fids = hostnamesToFidsMap.get(hn);
if ( fids === undefined ) { return; }
if ( typeof fids === 'number' ) { fids = [ fids ]; }
for ( const fid of fids ) {
const fname = ut.fnameFromFileId(fid);
let existing = toRegisterMap.get(fname);
if ( existing ) {
if ( existing[0] === '*' ) { continue; }
existing.push(hn);
} else {
toRegisterMap.set(fname, existing = [ hn ]);
}
if ( hn !== '*' ) { continue; }
existing.length = 0;
existing.push('*');
break;
}
};
for ( const rulesetDetails of rulesetsDetails ) {
const hostnamesToFidsMap = specificDetails.get(rulesetDetails.id);
if ( hostnamesToFidsMap === undefined ) { continue; }
for ( let hn of targetHostnames ) {
while ( hn ) {
checkMatches(hostnamesToFidsMap, hn);
hn = ut.toBroaderHostname(hn);
}
}
}
return toRegisterMap;
}
function registerSpecificAll(context, specificDetails) {
const { filteringModeDetails, rulesetsDetails } = context;
const toRegisterMap = new Map();
const excludeSet = new Set([
...filteringModeDetails.network,
...filteringModeDetails.none,
]);
for ( const rulesetDetails of rulesetsDetails ) {
const hostnamesToFidsMap = specificDetails.get(rulesetDetails.id);
if ( hostnamesToFidsMap === undefined ) { continue; }
for ( let [ hn, fids ] of hostnamesToFidsMap ) {
if ( excludeSet.has(hn) ) { continue; }
if ( ut.isDescendantHostnameOfIter(hn, excludeSet) ) { continue; }
if ( typeof fids === 'number' ) { fids = [ fids ]; }
for ( const fid of fids ) {
const fname = ut.fnameFromFileId(fid);
let existing = toRegisterMap.get(fname);
if ( existing ) {
if ( existing[0] === '*' ) { continue; }
existing.push(hn);
} else {
toRegisterMap.set(fname, existing = [ hn ]);
}
if ( hn !== '*' ) { continue; }
existing.length = 0;
existing.push('*');
break;
}
}
}
return toRegisterMap;
}
const toRegisterableScript = (context, fname, hostnames) => {
if ( context.before.has(fname) ) {
return toUpdatableScript(context, fname, hostnames);
}
const matches = hostnames
? ut.matchesFromHostnames(hostnames)
: [ '<all_urls>' ];
const excludeMatches = matches.length === 1 && matches[0] === '<all_urls>'
? ut.matchesFromHostnames(context.filteringModeDetails.none)
: [];
const directive = {
id: fname,
allFrames: true,
matches,
excludeMatches,
js: [ `/rulesets/scripting/specific/${fname.slice(-1)}/${fname.slice(0,-1)}.js` ],
runAt: 'document_start',
};
context.toAdd.push(directive);
};
const toUpdatableScript = (context, fname, hostnames) => {
const registered = context.before.get(fname);
context.before.delete(fname); // Important!
const directive = { id: fname };
const matches = hostnames
? ut.matchesFromHostnames(hostnames)
: [ '<all_urls>' ];
if ( arrayEq(registered.matches, matches) === false ) {
directive.matches = matches;
}
const excludeMatches = matches.length === 1 && matches[0] === '<all_urls>'
? ut.matchesFromHostnames(context.filteringModeDetails.none)
: [];
if ( arrayEq(registered.excludeMatches, excludeMatches) === false ) {
directive.excludeMatches = excludeMatches;
}
if ( directive.matches || directive.excludeMatches ) {
context.toUpdate.push(directive);
}
};
/******************************************************************************/
function registerSpecificEntity(context) {
const { before, filteringModeDetails, rulesetsDetails } = context;
const js = [];
for ( const details of rulesetsDetails ) {
if ( details.css.specific instanceof Object === false ) { continue; }
if ( details.css.specific.entityBased === 0 ) { continue; }
js.push(`/rulesets/scripting/specific-entity/${details.id}.js`);
}
if ( js.length === 0 ) { return; }
const matches = [];
const excludeMatches = [];
if ( filteringModeDetails.extendedGeneric.has('all-urls') ) {
excludeMatches.push(...ut.matchesFromHostnames(filteringModeDetails.none));
excludeMatches.push(...ut.matchesFromHostnames(filteringModeDetails.network));
excludeMatches.push(...ut.matchesFromHostnames(filteringModeDetails.extendedSpecific));
matches.push('<all_urls>');
} else {
matches.push(
...ut.matchesFromHostnames(filteringModeDetails.extendedGeneric)
);
}
if ( matches.length === 0 ) { return; }
js.push('/js/scripting/css-specific.entity.js');
const registered = before.get('css-specific.entity');
before.delete('css-specific.entity'); // Important!
// register
if ( registered === undefined ) {
context.toAdd.push({
id: 'css-specific.entity',
js,
allFrames: true,
matches,
excludeMatches,
runAt: 'document_start',
});
return;
}
// update
const directive = { id: 'css-specific.entity' };
if ( arrayEq(registered.js, js, false) === false ) {
directive.js = js;
}
if ( arrayEq(registered.matches, matches) === false ) {
directive.matches = matches;
}
if ( arrayEq(registered.excludeMatches, excludeMatches) === false ) {
directive.excludeMatches = excludeMatches;
}
if ( directive.js || directive.matches || directive.excludeMatches ) {
context.toUpdate.push(directive);
}
}
/******************************************************************************/
async function registerInjectables(origins) { async function registerInjectables(origins) {
void origins; void origins;
@ -644,19 +437,13 @@ async function registerInjectables(origins) {
const [ const [
filteringModeDetails, filteringModeDetails,
rulesetsDetails, rulesetsDetails,
declarativeDetails,
proceduralDetails,
scriptletDetails, scriptletDetails,
specificDetails,
genericDetails, genericDetails,
registered, registered,
] = await Promise.all([ ] = await Promise.all([
getFilteringModeDetails(), getFilteringModeDetails(),
getEnabledRulesetsDetails(), getEnabledRulesetsDetails(),
getDeclarativeDetails(),
getProceduralDetails(),
getScriptletDetails(), getScriptletDetails(),
getSpecificDetails(),
getGenericDetails(), getGenericDetails(),
browser.scripting.getRegisteredContentScripts(), browser.scripting.getRegisteredContentScripts(),
]); ]);
@ -676,11 +463,10 @@ async function registerInjectables(origins) {
toRemove, toRemove,
}; };
registerDeclarative(context, declarativeDetails); registerDeclarative(context);
registerProcedural(context, proceduralDetails); registerProcedural(context);
registerScriptlet(context, scriptletDetails); registerScriptlet(context, scriptletDetails);
registerSpecific(context, specificDetails); registerSpecific(context);
registerSpecificEntity(context);
registerGeneric(context, genericDetails); registerGeneric(context, genericDetails);
toRemove.push(...Array.from(before.keys())); toRemove.push(...Array.from(before.keys()));

View file

@ -32,41 +32,77 @@
/******************************************************************************/ /******************************************************************************/
const declarativeImports = self.declarativeImports || []; const declarativeImports = self.declarativeImports || [];
self.declarativeImports = undefined;
delete self.declarativeImports; delete self.declarativeImports;
const lookupSelectors = (hn, out) => {
for ( const { argsList, hostnamesMap } of declarativeImports ) {
let argsIndices = hostnamesMap.get(hn);
if ( argsIndices === undefined ) { continue; }
if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; }
for ( const argsIndex of argsIndices ) {
const details = argsList[argsIndex];
if ( details.n && details.n.includes(hn) ) { continue; }
out.push(...details.a.map(json => JSON.parse(json)));
}
}
};
let hn;
try { hn = document.location.hostname; } catch(ex) { }
const selectors = [];
while ( hn ) {
lookupSelectors(hn, selectors);
if ( hn === '*' ) { break; }
const pos = hn.indexOf('.');
if ( pos !== -1 ) {
hn = hn.slice(pos + 1);
} else {
hn = '*';
}
}
declarativeImports.length = 0;
/******************************************************************************/ /******************************************************************************/
const hnParts = [];
try { hnParts.push(...document.location.hostname.split('.')); }
catch(ex) { }
const hnpartslen = hnParts.length;
if ( hnpartslen === 0 ) { return; }
const selectors = [];
for ( const { argsList, exceptionsMap, hostnamesMap, entitiesMap } of declarativeImports ) {
const todoIndices = new Set();
const tonotdoIndices = [];
// Exceptions
if ( exceptionsMap.size !== 0 ) {
for ( let i = 0; i < hnpartslen; i++ ) {
const hn = hnParts.slice(i).join('.');
const excepted = exceptionsMap.get(hn);
if ( excepted ) { tonotdoIndices.push(...excepted); }
}
exceptionsMap.clear();
}
// Hostname-based
if ( hostnamesMap.size !== 0 ) {
const collectArgIndices = hn => {
let argsIndices = hostnamesMap.get(hn);
if ( argsIndices === undefined ) { return; }
if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; }
for ( const argsIndex of argsIndices ) {
if ( tonotdoIndices.includes(argsIndex) ) { continue; }
todoIndices.add(argsIndex);
}
};
for ( let i = 0; i < hnpartslen; i++ ) {
const hn = hnParts.slice(i).join('.');
collectArgIndices(hn);
}
collectArgIndices('*');
hostnamesMap.clear();
}
// Entity-based
if ( entitiesMap.size !== 0 ) {
const n = hnpartslen - 1;
for ( let i = 0; i < n; i++ ) {
for ( let j = n; j > i; j-- ) {
const en = hnParts.slice(i,j).join('.');
let argsIndices = entitiesMap.get(en);
if ( argsIndices === undefined ) { continue; }
if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; }
for ( const argsIndex of argsIndices ) {
if ( tonotdoIndices.includes(argsIndex) ) { continue; }
todoIndices.add(argsIndex);
}
}
}
entitiesMap.clear();
}
for ( const i of todoIndices ) {
selectors.push(...argsList[i].map(json => JSON.parse(json)));
}
argsList.length = 0;
}
declarativeImports.length = 0;
if ( selectors.length === 0 ) { return; } if ( selectors.length === 0 ) { return; }
/******************************************************************************/
const cssRuleFromProcedural = details => { const cssRuleFromProcedural = details => {
const { tasks, action } = details; const { tasks, action } = details;
let mq; let mq;
@ -114,3 +150,5 @@ try {
})(); })();
/******************************************************************************/ /******************************************************************************/
void 0;

View file

@ -31,7 +31,75 @@
/******************************************************************************/ /******************************************************************************/
let proceduralFilterer; const proceduralImports = self.proceduralImports || [];
self.proceduralImports = undefined;
delete self.proceduralImports;
/******************************************************************************/
const hnParts = [];
try { hnParts.push(...document.location.hostname.split('.')); }
catch(ex) { }
const hnpartslen = hnParts.length;
if ( hnpartslen === 0 ) { return; }
const selectors = [];
for ( const { argsList, exceptionsMap, hostnamesMap, entitiesMap } of proceduralImports ) {
const todoIndices = new Set();
const tonotdoIndices = [];
// Exceptions
if ( exceptionsMap.size !== 0 ) {
for ( let i = 0; i < hnpartslen; i++ ) {
const hn = hnParts.slice(i).join('.');
const excepted = exceptionsMap.get(hn);
if ( excepted ) { tonotdoIndices.push(...excepted); }
}
exceptionsMap.clear();
}
// Hostname-based
if ( hostnamesMap.size !== 0 ) {
const collectArgIndices = hn => {
let argsIndices = hostnamesMap.get(hn);
if ( argsIndices === undefined ) { return; }
if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; }
for ( const argsIndex of argsIndices ) {
if ( tonotdoIndices.includes(argsIndex) ) { continue; }
todoIndices.add(argsIndex);
}
};
for ( let i = 0; i < hnpartslen; i++ ) {
const hn = hnParts.slice(i).join('.');
collectArgIndices(hn);
}
collectArgIndices('*');
hostnamesMap.clear();
}
// Entity-based
if ( entitiesMap.size !== 0 ) {
const n = hnpartslen - 1;
for ( let i = 0; i < n; i++ ) {
for ( let j = n; j > i; j-- ) {
const en = hnParts.slice(i,j).join('.');
let argsIndices = entitiesMap.get(en);
if ( argsIndices === undefined ) { continue; }
if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; }
for ( const argsIndex of argsIndices ) {
if ( tonotdoIndices.includes(argsIndex) ) { continue; }
todoIndices.add(argsIndex);
}
}
}
entitiesMap.clear();
}
for ( const i of todoIndices ) {
selectors.push(...argsList[i].map(json => JSON.parse(json)));
}
argsList.length = 0;
}
proceduralImports.length = 0;
if ( selectors.length === 0 ) { return; }
/******************************************************************************/ /******************************************************************************/
@ -658,43 +726,7 @@ class ProceduralFilterer {
/******************************************************************************/ /******************************************************************************/
const proceduralImports = self.proceduralImports || []; const proceduralFilterer = new ProceduralFilterer(selectors);
delete self.proceduralImports;
const lookupSelectors = (hn, out) => {
for ( const { argsList, hostnamesMap } of proceduralImports ) {
let argsIndices = hostnamesMap.get(hn);
if ( argsIndices === undefined ) { continue; }
if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; }
for ( const argsIndex of argsIndices ) {
const details = argsList[argsIndex];
if ( details.n && details.n.includes(hn) ) { continue; }
out.push(...details.a.map(json => JSON.parse(json)));
}
}
};
let hn;
try { hn = document.location.hostname; } catch(ex) { }
const selectors = [];
while ( hn ) {
lookupSelectors(hn, selectors);
if ( hn === '*' ) { break; }
const pos = hn.indexOf('.');
if ( pos !== -1 ) {
hn = hn.slice(pos + 1);
} else {
hn = '*';
}
}
proceduralImports.length = 0;
/******************************************************************************/
if ( selectors.length === 0 ) { return; }
proceduralFilterer = new ProceduralFilterer(selectors);
const observer = new MutationObserver(mutations => { const observer = new MutationObserver(mutations => {
let domChanged = false; let domChanged = false;
@ -727,3 +759,5 @@ observer.observe(document, {
})(); })();
/******************************************************************************/ /******************************************************************************/
void 0;

View file

@ -1,85 +0,0 @@
/*******************************************************************************
uBlock Origin - a browser extension to block requests.
Copyright (C) 2019-present Raymond Hill
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
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uBlock
*/
/* jshint esversion:11 */
'use strict';
/******************************************************************************/
// Important!
// Isolate from global scope
(function uBOL_cssSpecificEntity() {
/******************************************************************************/
// $rulesetId$
const specificEntityImports = self.specificEntityImports || [];
delete self.specificEntityImports;
/******************************************************************************/
const lookupSelectors = (hn, entity, out) => {
for ( const { argsList, entitiesMap } of specificEntityImports ) {
let argsIndices = entitiesMap.get(entity);
if ( argsIndices === undefined ) { continue; }
if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; }
for ( const argsIndex of argsIndices ) {
const details = argsList[argsIndex];
if ( details.n && details.n.includes(hn) ) { continue; }
out.push(details.a);
}
}
};
let hn = '';
try { hn = document.location.hostname; } catch(ex) { }
const selectors = [];
const hnparts = hn.split('.');
const hnpartslen = hnparts.length - 1;
for ( let i = 0; i < hnpartslen; i++ ) {
for ( let j = hnpartslen; j > i; j-- ) {
lookupSelectors(
hnparts.slice(i).join('.'),
hnparts.slice(i,j).join('.'),
selectors
);
}
}
if ( selectors.length === 0 ) { return; }
try {
const sheet = new CSSStyleSheet();
sheet.replace(`@layer{${selectors.join(',')}{display:none!important;}}`);
document.adoptedStyleSheets = [
...document.adoptedStyleSheets,
sheet
];
} catch(ex) {
}
/******************************************************************************/
})();
/******************************************************************************/

View file

@ -0,0 +1,122 @@
/*******************************************************************************
uBlock Origin - a browser extension to block requests.
Copyright (C) 2019-present Raymond Hill
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
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uBlock
*/
/* jshint esversion:11 */
'use strict';
/******************************************************************************/
// Important!
// Isolate from global scope
(function uBOL_cssSpecific() {
/******************************************************************************/
const specificImports = self.specificImports || [];
self.specificImports = undefined;
delete self.specificImports;
/******************************************************************************/
const hnParts = [];
try { hnParts.push(...document.location.hostname.split('.')); }
catch(ex) { }
const hnpartslen = hnParts.length;
if ( hnpartslen === 0 ) { return; }
const selectors = [];
for ( const { argsList, exceptionsMap, hostnamesMap, entitiesMap } of specificImports ) {
const todoIndices = new Set();
const tonotdoIndices = [];
// Exceptions
if ( exceptionsMap.size !== 0 ) {
for ( let i = 0; i < hnpartslen; i++ ) {
const hn = hnParts.slice(i).join('.');
const excepted = exceptionsMap.get(hn);
if ( excepted ) { tonotdoIndices.push(...excepted); }
}
exceptionsMap.clear();
}
// Hostname-based
if ( hostnamesMap.size !== 0 ) {
const collectArgIndices = hn => {
let argsIndices = hostnamesMap.get(hn);
if ( argsIndices === undefined ) { return; }
if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; }
for ( const argsIndex of argsIndices ) {
if ( tonotdoIndices.includes(argsIndex) ) { continue; }
todoIndices.add(argsIndex);
}
};
for ( let i = 0; i < hnpartslen; i++ ) {
const hn = hnParts.slice(i).join('.');
collectArgIndices(hn);
}
collectArgIndices('*');
hostnamesMap.clear();
}
// Entity-based
if ( entitiesMap.size !== 0 ) {
const n = hnpartslen - 1;
for ( let i = 0; i < n; i++ ) {
for ( let j = n; j > i; j-- ) {
const en = hnParts.slice(i,j).join('.');
let argsIndices = entitiesMap.get(en);
if ( argsIndices === undefined ) { continue; }
if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; }
for ( const argsIndex of argsIndices ) {
if ( tonotdoIndices.includes(argsIndex) ) { continue; }
todoIndices.add(argsIndex);
}
}
}
entitiesMap.clear();
}
for ( const i of todoIndices ) {
selectors.push(argsList[i]);
}
argsList.length = 0;
}
specificImports.length = 0;
if ( selectors.length === 0 ) { return; }
/******************************************************************************/
try {
const sheet = new CSSStyleSheet();
sheet.replace(`@layer{${selectors.join(',')}{display:none!important;}}`);
document.adoptedStyleSheets = [
...document.adoptedStyleSheets,
sheet
];
} catch(ex) {
}
/******************************************************************************/
})();
/******************************************************************************/
void 0;

View file

@ -163,7 +163,7 @@ function renderFilterLists(soft = false) {
// DOM list entries. // DOM list entries.
dom.cl.add('#lists .listEntries .listEntry[data-listkey]', 'discard'); dom.cl.add('#lists .listEntries .listEntry[data-listkey]', 'discard');
// Visually split the filter lists in three groups // Visually split the filter lists in groups
const ulLists = qs$('#lists'); const ulLists = qs$('#lists');
const groups = new Map([ const groups = new Map([
[ [
@ -172,10 +172,18 @@ function renderFilterLists(soft = false) {
ruleset.id === 'default' ruleset.id === 'default'
), ),
], ],
[
'annoyances',
rulesetDetails.filter(ruleset =>
ruleset.group === 'annoyances'
),
],
[ [
'misc', 'misc',
rulesetDetails.filter(ruleset => rulesetDetails.filter(ruleset =>
ruleset.id !== 'default' && typeof ruleset.lang !== 'string' ruleset.id !== 'default' &&
ruleset.group === undefined &&
typeof ruleset.lang !== 'string'
), ),
], ],
[ [

View file

@ -119,14 +119,6 @@ const hostnamesFromMatches = origins => {
/******************************************************************************/ /******************************************************************************/
const fnameFromFileId = fid =>
fid.toString(32).padStart(7, '0');
const fidFromFileName = fname =>
parseInt(fname, 32);
/******************************************************************************/
export { export {
parsedURLromOrigin, parsedURLromOrigin,
toBroaderHostname, toBroaderHostname,
@ -136,6 +128,4 @@ export {
subtractHostnameIters, subtractHostnameIters,
matchesFromHostnames, matchesFromHostnames,
hostnamesFromMatches, hostnamesFromMatches,
fnameFromFileId,
fidFromFileName,
}; };

View file

@ -30,7 +30,6 @@ import process from 'process';
import { createHash } from 'crypto'; import { createHash } from 'crypto';
import redirectResourcesMap from './js/redirect-resources.js'; import redirectResourcesMap from './js/redirect-resources.js';
import { dnrRulesetFromRawLists } from './js/static-dnr-filtering.js'; import { dnrRulesetFromRawLists } from './js/static-dnr-filtering.js';
import { fnameFromFileId } from './js/utils.js';
import * as sfp from './js/static-filtering-parser.js'; import * as sfp from './js/static-filtering-parser.js';
import * as makeScriptlet from './make-scriptlets.js'; import * as makeScriptlet from './make-scriptlets.js';
@ -158,10 +157,7 @@ const writeOps = [];
const ruleResources = []; const ruleResources = [];
const rulesetDetails = []; const rulesetDetails = [];
const declarativeDetails = new Map();
const proceduralDetails = new Map();
const scriptletStats = new Map(); const scriptletStats = new Map();
const specificDetails = new Map();
const genericDetails = new Map(); const genericDetails = new Map();
const requiredRedirectResources = new Set(); const requiredRedirectResources = new Set();
@ -403,45 +399,12 @@ function loadAllSourceScriptlets() {
/******************************************************************************/ /******************************************************************************/
const globalPatchedScriptletsSet = new Set();
function addScriptingAPIResources(id, hostnames, fid) {
if ( hostnames === undefined ) { return; }
for ( const hn of hostnames ) {
let hostnamesToFidMap = specificDetails.get(id);
if ( hostnamesToFidMap === undefined ) {
hostnamesToFidMap = new Map();
specificDetails.set(id, hostnamesToFidMap);
}
let fids = hostnamesToFidMap.get(hn);
if ( fids === undefined ) {
hostnamesToFidMap.set(hn, fid);
} else if ( fids instanceof Set ) {
fids.add(fid);
} else if ( fid !== fids ) {
fids = new Set([ fids, fid ]);
hostnamesToFidMap.set(hn, fids);
}
}
}
const toCSSSpecific = s => (uidint32(s) & ~0b11) | 0b00;
const pathFromFileName = fname => `${fname.slice(-1)}/${fname.slice(0,-1)}.js`;
/******************************************************************************/
async function processGenericCosmeticFilters(assetDetails, bucketsMap, exclusions) { async function processGenericCosmeticFilters(assetDetails, bucketsMap, exclusions) {
const out = { if ( bucketsMap === undefined ) { return 0; }
count: 0, if ( bucketsMap.size === 0 ) { return 0; }
exclusionCount: 0,
};
if ( bucketsMap === undefined ) { return out; }
if ( bucketsMap.size === 0 ) { return out; }
const bucketsList = Array.from(bucketsMap); const bucketsList = Array.from(bucketsMap);
const count = bucketsList.reduce((a, v) => a += v[1].length, 0); const count = bucketsList.reduce((a, v) => a += v[1].length, 0);
if ( count === 0 ) { return out; } if ( count === 0 ) { return 0; }
out.count = count;
const selectorLists = bucketsList.map(v => [ v[0], v[1].join(',') ]); const selectorLists = bucketsList.map(v => [ v[0], v[1].join(',') ]);
const originalScriptletMap = await loadAllSourceScriptlets(); const originalScriptletMap = await loadAllSourceScriptlets();
@ -464,13 +427,11 @@ async function processGenericCosmeticFilters(assetDetails, bucketsMap, exclusion
log(`CSS-generic: ${count} plain CSS selectors`); log(`CSS-generic: ${count} plain CSS selectors`);
return out; return count;
} }
/******************************************************************************/ /******************************************************************************/
const MAX_COSMETIC_FILTERS_PER_FILE = 256;
// This merges selectors which are used by the same hostnames // This merges selectors which are used by the same hostnames
function groupSelectorsByHostnames(mapin) { function groupSelectorsByHostnames(mapin) {
@ -588,52 +549,13 @@ function argsMap2List(argsMap, hostnamesMap) {
/******************************************************************************/ /******************************************************************************/
function splitDomainAndEntity(mapin) {
const domainBased = new Map();
const entityBased = new Map();
for ( const [ selector, domainDetails ] of mapin ) {
domainBased.set(selector, domainDetails);
if ( domainDetails.rejected ) { continue; }
if ( Array.isArray(domainDetails.matches) === false ) { continue; }
const domainMatches = [];
const entityMatches = [];
for ( const hn of domainDetails.matches ) {
if ( hn.endsWith('.*') ) {
entityMatches.push(hn.slice(0, -2));
} else {
domainMatches.push(hn);
}
}
if ( entityMatches.length === 0 ) { continue; }
if ( domainMatches.length !== 0 ) {
domainDetails.matches = domainMatches;
} else {
domainBased.delete(selector);
}
const entityDetails = {
matches: entityMatches,
};
if ( Array.isArray(domainDetails.excludeMatches) ) {
entityDetails.excludeMatches = domainDetails.excludeMatches.slice();
}
entityBased.set(selector, entityDetails);
}
return { domainBased, entityBased };
}
/******************************************************************************/
async function processCosmeticFilters(assetDetails, mapin) { async function processCosmeticFilters(assetDetails, mapin) {
if ( mapin === undefined ) { return; } if ( mapin === undefined ) { return 0; }
if ( mapin.size === 0 ) { return 0; }
const { domainBased, entityBased } = splitDomainAndEntity(mapin);
const entityBasedEntries = groupHostnamesBySelectors(
groupSelectorsByHostnames(entityBased)
);
const domainBasedEntries = groupHostnamesBySelectors( const domainBasedEntries = groupHostnamesBySelectors(
groupSelectorsByHostnames(domainBased) groupSelectorsByHostnames(mapin)
); );
// We do not want more than n CSS files per subscription, so we will // We do not want more than n CSS files per subscription, so we will
// group multiple unrelated selectors in the same file, and distinct // group multiple unrelated selectors in the same file, and distinct
// css declarations will be injected programmatically according to the // css declarations will be injected programmatically according to the
@ -645,9 +567,7 @@ async function processCosmeticFilters(assetDetails, mapin) {
const originalScriptletMap = await loadAllSourceScriptlets(); const originalScriptletMap = await loadAllSourceScriptlets();
const generatedFiles = []; const generatedFiles = [];
for ( let i = 0; i < domainBasedEntries.length; i += MAX_COSMETIC_FILTERS_PER_FILE ) { const argsMap = domainBasedEntries.map(entry => [
const slice = domainBasedEntries.slice(i, i + MAX_COSMETIC_FILTERS_PER_FILE);
const argsMap = slice.map(entry => [
entry[0], entry[0],
{ {
a: entry[1].a ? entry[1].a.join(',\n') : undefined, a: entry[1].a ? entry[1].a.join(',\n') : undefined,
@ -655,11 +575,33 @@ async function processCosmeticFilters(assetDetails, mapin) {
} }
]); ]);
const hostnamesMap = new Map(); const hostnamesMap = new Map();
for ( const [ id, details ] of slice ) { for ( const [ id, details ] of domainBasedEntries ) {
if ( details.y === undefined ) { continue; } if ( details.y === undefined ) { continue; }
scriptletHostnameToIdMap(details.y, id, hostnamesMap); scriptletHostnameToIdMap(details.y, id, hostnamesMap);
} }
const argsList = argsMap2List(argsMap, hostnamesMap); const argsList = argsMap2List(argsMap, hostnamesMap);
const entitiesMap = new Map();
for ( const [ hn, details ] of hostnamesMap ) {
if ( hn.endsWith('.*') === false ) { continue; }
hostnamesMap.delete(hn);
entitiesMap.set(hn.slice(0, -2), details);
}
// Extract exceptions from argsList, simplify argsList entries
const exceptionsMap = new Map();
for ( let i = 0; i < argsList.length; i++ ) {
const details = argsList[i];
if ( details.n ) {
for ( const hn of details.n ) {
if ( exceptionsMap.has(hn) === false ) {
exceptionsMap.set(hn, []);
}
exceptionsMap.get(hn).push(i);
}
}
argsList[i] = details.a;
}
const patchedScriptlet = originalScriptletMap.get('css-specific') const patchedScriptlet = originalScriptletMap.get('css-specific')
.replace( .replace(
'$rulesetId$', '$rulesetId$',
@ -670,64 +612,23 @@ async function processCosmeticFilters(assetDetails, mapin) {
).replace( ).replace(
/\bself\.\$hostnamesMap\$/m, /\bself\.\$hostnamesMap\$/m,
`${JSON.stringify(hostnamesMap, scriptletJsonReplacer)}` `${JSON.stringify(hostnamesMap, scriptletJsonReplacer)}`
);
const fid = toCSSSpecific(patchedScriptlet);
if ( globalPatchedScriptletsSet.has(fid) === false ) {
globalPatchedScriptletsSet.add(fid);
const fname = fnameFromFileId(fid);
writeFile(`${scriptletDir}/specific/${pathFromFileName(fname)}`, patchedScriptlet);
generatedFiles.push(fname);
}
for ( const entry of slice ) {
addScriptingAPIResources(assetDetails.id, entry[1].y, fid);
}
}
// For entity-based entries, we generate a single scriptlet which will be
// injected only in Complete mode.
if ( entityBasedEntries.length !== 0 ) {
const argsMap = entityBasedEntries.map(entry => [
entry[0],
{
a: entry[1].a ? entry[1].a.join(',') : undefined,
n: entry[1].n,
}
]);
const entitiesMap = new Map();
for ( const [ id, details ] of entityBasedEntries ) {
if ( details.y === undefined ) { continue; }
scriptletHostnameToIdMap(details.y, id, entitiesMap);
}
const argsList = argsMap2List(argsMap, entitiesMap);
const patchedScriptlet = originalScriptletMap.get('css-specific.entity')
.replace(
'$rulesetId$',
assetDetails.id
).replace(
/\bself\.\$argsList\$/m,
`${JSON.stringify(argsList, scriptletJsonReplacer)}`
).replace( ).replace(
/\bself\.\$entitiesMap\$/m, /\bself\.\$entitiesMap\$/m,
`${JSON.stringify(entitiesMap, scriptletJsonReplacer)}` `${JSON.stringify(entitiesMap, scriptletJsonReplacer)}`
).replace(
/\bself\.\$exceptionsMap\$/m,
`${JSON.stringify(exceptionsMap, scriptletJsonReplacer)}`
); );
const fname = `${assetDetails.id}`; writeFile(`${scriptletDir}/specific/${assetDetails.id}.js`, patchedScriptlet);
writeFile(`${scriptletDir}/specific-entity/${fname}.js`, patchedScriptlet); generatedFiles.push(`${assetDetails.id}`);
generatedFiles.push(fname);
}
if ( generatedFiles.length !== 0 ) { if ( generatedFiles.length !== 0 ) {
log(`CSS-specific domain-based: ${domainBased.size} distinct filters`); log(`CSS-specific: ${mapin.size} distinct filters`);
log(`\tCombined into ${domainBasedEntries.length} distinct entries`); log(`\tCombined into ${hostnamesMap.size} distinct hostnames`);
log(`CSS-specific entity-based: ${entityBased.size} distinct filters`); log(`\tCombined into ${entitiesMap.size} distinct entities`);
log(`\tCombined into ${entityBasedEntries.length} distinct entries`);
log(`CSS-specific injectable files: ${generatedFiles.length}`);
log(`\t${generatedFiles.join(', ')}`);
} }
return { return hostnamesMap.size + entitiesMap.size;
domainBased: domainBasedEntries.length,
entityBased: entityBasedEntries.length,
};
} }
/******************************************************************************/ /******************************************************************************/
@ -761,8 +662,29 @@ async function processDeclarativeCosmeticFilters(assetDetails, mapin) {
if ( details.y === undefined ) { continue; } if ( details.y === undefined ) { continue; }
scriptletHostnameToIdMap(details.y, id, hostnamesMap); scriptletHostnameToIdMap(details.y, id, hostnamesMap);
} }
const argsList = argsMap2List(argsMap, hostnamesMap); const argsList = argsMap2List(argsMap, hostnamesMap);
const entitiesMap = new Map();
for ( const [ hn, details ] of hostnamesMap ) {
if ( hn.endsWith('.*') === false ) { continue; }
hostnamesMap.delete(hn);
entitiesMap.set(hn.slice(0, -2), details);
}
// Extract exceptions from argsList, simplify argsList entries
const exceptionsMap = new Map();
for ( let i = 0; i < argsList.length; i++ ) {
const details = argsList[i];
if ( details.n ) {
for ( const hn of details.n ) {
if ( exceptionsMap.has(hn) === false ) {
exceptionsMap.set(hn, []);
}
exceptionsMap.get(hn).push(i);
}
}
argsList[i] = details.a;
}
const originalScriptletMap = await loadAllSourceScriptlets(); const originalScriptletMap = await loadAllSourceScriptlets();
const patchedScriptlet = originalScriptletMap.get('css-declarative') const patchedScriptlet = originalScriptletMap.get('css-declarative')
.replace( .replace(
@ -774,29 +696,22 @@ async function processDeclarativeCosmeticFilters(assetDetails, mapin) {
).replace( ).replace(
/\bself\.\$hostnamesMap\$/m, /\bself\.\$hostnamesMap\$/m,
`${JSON.stringify(hostnamesMap, scriptletJsonReplacer)}` `${JSON.stringify(hostnamesMap, scriptletJsonReplacer)}`
).replace(
/\bself\.\$entitiesMap\$/m,
`${JSON.stringify(entitiesMap, scriptletJsonReplacer)}`
).replace(
/\bself\.\$exceptionsMap\$/m,
`${JSON.stringify(exceptionsMap, scriptletJsonReplacer)}`
); );
writeFile(`${scriptletDir}/declarative/${assetDetails.id}.js`, patchedScriptlet); writeFile(`${scriptletDir}/declarative/${assetDetails.id}.js`, patchedScriptlet);
{
const hostnames = new Set();
for ( const entry of contentArray ) {
if ( Array.isArray(entry[1].y) === false ) { continue; }
for ( const hn of entry[1].y ) {
hostnames.add(hn);
}
}
if ( hostnames.has('*') ) {
hostnames.clear();
hostnames.add('*');
}
declarativeDetails.set(assetDetails.id, Array.from(hostnames).sort());
}
if ( contentArray.length !== 0 ) { if ( contentArray.length !== 0 ) {
log(`Declarative-related distinct filters: ${contentArray.length} distinct combined selectors`); log(`CSS-declarative: ${declaratives.size} distinct filters`);
log(`\tCombined into ${hostnamesMap.size} distinct hostnames`);
log(`\tCombined into ${entitiesMap.size} distinct entities`);
} }
return contentArray.length; return hostnamesMap.size + entitiesMap.size;
} }
/******************************************************************************/ /******************************************************************************/
@ -830,8 +745,29 @@ async function processProceduralCosmeticFilters(assetDetails, mapin) {
if ( details.y === undefined ) { continue; } if ( details.y === undefined ) { continue; }
scriptletHostnameToIdMap(details.y, id, hostnamesMap); scriptletHostnameToIdMap(details.y, id, hostnamesMap);
} }
const argsList = argsMap2List(argsMap, hostnamesMap); const argsList = argsMap2List(argsMap, hostnamesMap);
const entitiesMap = new Map();
for ( const [ hn, details ] of hostnamesMap ) {
if ( hn.endsWith('.*') === false ) { continue; }
hostnamesMap.delete(hn);
entitiesMap.set(hn.slice(0, -2), details);
}
// Extract exceptions from argsList, simplify argsList entries
const exceptionsMap = new Map();
for ( let i = 0; i < argsList.length; i++ ) {
const details = argsList[i];
if ( details.n ) {
for ( const hn of details.n ) {
if ( exceptionsMap.has(hn) === false ) {
exceptionsMap.set(hn, []);
}
exceptionsMap.get(hn).push(i);
}
}
argsList[i] = details.a;
}
const originalScriptletMap = await loadAllSourceScriptlets(); const originalScriptletMap = await loadAllSourceScriptlets();
const patchedScriptlet = originalScriptletMap.get('css-procedural') const patchedScriptlet = originalScriptletMap.get('css-procedural')
.replace( .replace(
@ -843,35 +779,29 @@ async function processProceduralCosmeticFilters(assetDetails, mapin) {
).replace( ).replace(
/\bself\.\$hostnamesMap\$/m, /\bself\.\$hostnamesMap\$/m,
`${JSON.stringify(hostnamesMap, scriptletJsonReplacer)}` `${JSON.stringify(hostnamesMap, scriptletJsonReplacer)}`
).replace(
/\bself\.\$entitiesMap\$/m,
`${JSON.stringify(entitiesMap, scriptletJsonReplacer)}`
).replace(
/\bself\.\$exceptionsMap\$/m,
`${JSON.stringify(exceptionsMap, scriptletJsonReplacer)}`
); );
writeFile(`${scriptletDir}/procedural/${assetDetails.id}.js`, patchedScriptlet); writeFile(`${scriptletDir}/procedural/${assetDetails.id}.js`, patchedScriptlet);
{
const hostnames = new Set();
for ( const entry of contentArray ) {
if ( Array.isArray(entry[1].y) === false ) { continue; }
for ( const hn of entry[1].y ) {
hostnames.add(hn);
}
}
if ( hostnames.has('*') ) {
hostnames.clear();
hostnames.add('*');
}
proceduralDetails.set(assetDetails.id, Array.from(hostnames).sort());
}
if ( contentArray.length !== 0 ) { if ( contentArray.length !== 0 ) {
log(`Procedural-related distinct filters: ${contentArray.length} distinct combined selectors`); log(`Procedural-related distinct filters: ${procedurals.size} distinct combined selectors`);
log(`\tCombined into ${hostnamesMap.size} distinct hostnames`);
log(`\tCombined into ${entitiesMap.size} distinct entities`);
} }
return contentArray.length; return hostnamesMap.size + entitiesMap.size;
} }
/******************************************************************************/ /******************************************************************************/
async function processScriptletFilters(assetDetails, mapin) { async function processScriptletFilters(assetDetails, mapin) {
if ( mapin === undefined ) { return; } if ( mapin === undefined ) { return 0; }
if ( mapin.size === 0 ) { return 0; }
makeScriptlet.init(); makeScriptlet.init();
@ -887,7 +817,7 @@ async function processScriptletFilters(assetDetails, mapin) {
scriptletStats.set(assetDetails.id, stats); scriptletStats.set(assetDetails.id, stats);
} }
makeScriptlet.reset(); makeScriptlet.reset();
return { domainBasedTokens: stats.length }; return stats.length;
} }
/******************************************************************************/ /******************************************************************************/
@ -942,13 +872,6 @@ async function rulesetFromURLs(assetDetails) {
continue; continue;
} }
const parsed = JSON.parse(selector); const parsed = JSON.parse(selector);
const matches =
details.matches.filter(hn => hn.endsWith('.*') === false);
if ( matches.length === 0 ) {
rejectedCosmetic.push(`Entity-based filter not supported: ${parsed.raw}`);
continue;
}
details.matches = matches;
parsed.raw = undefined; parsed.raw = undefined;
proceduralCosmetic.set(JSON.stringify(parsed), details); proceduralCosmetic.set(JSON.stringify(parsed), details);
} }
@ -983,6 +906,7 @@ async function rulesetFromURLs(assetDetails) {
rulesetDetails.push({ rulesetDetails.push({
id: assetDetails.id, id: assetDetails.id,
name: assetDetails.name, name: assetDetails.name,
group: assetDetails.group,
enabled: assetDetails.enabled, enabled: assetDetails.enabled,
lang: assetDetails.lang, lang: assetDetails.lang,
homeURL: assetDetails.homeURL, homeURL: assetDetails.homeURL,
@ -1110,7 +1034,11 @@ async function main() {
} }
// Handpicked rulesets from assets.json // Handpicked rulesets from assets.json
const handpicked = [ 'block-lan', 'dpollock-0', 'adguard-spyware-url' ]; const handpicked = [
'block-lan',
'dpollock-0',
'adguard-spyware-url',
];
for ( const id of handpicked ) { for ( const id of handpicked ) {
const asset = assets[id]; const asset = assets[id];
if ( asset.content !== 'filters' ) { continue; } if ( asset.content !== 'filters' ) { continue; }
@ -1127,6 +1055,33 @@ async function main() {
}); });
} }
// Handpicked annoyance rulesets from assets.json
await rulesetFromURLs({
id: 'easylist-cookies',
name: 'EasyList Cookies Notices' ,
group: 'annoyances',
enabled: false,
urls: [
'https://ublockorigin.github.io/uAssets/thirdparties/easylist-cookies.txt',
],
homeURL: 'https://github.com/uBlockOrigin/uAssets',
});
await rulesetFromURLs({
id: 'easylist-annoyances',
name: 'EasyList Annoyances' ,
group: 'annoyances',
enabled: false,
urls: [
'https://ublockorigin.github.io/uAssets/thirdparties/easylist-annoyances.txt',
'https://ublockorigin.github.io/uAssets/thirdparties/easylist-chat.txt',
'https://ublockorigin.github.io/uAssets/thirdparties/easylist-newsletters.txt',
'https://ublockorigin.github.io/uAssets/thirdparties/easylist-notifications.txt',
'https://ublockorigin.github.io/uAssets/thirdparties/easylist-social.txt',
'https://ublockorigin.github.io/uAssets/filters/annoyances.txt',
],
homeURL: 'https://github.com/uBlockOrigin/uAssets',
});
// Handpicked rulesets from abroad // Handpicked rulesets from abroad
await rulesetFromURLs({ await rulesetFromURLs({
id: 'stevenblack-hosts', id: 'stevenblack-hosts',
@ -1141,29 +1096,6 @@ async function main() {
`${JSON.stringify(rulesetDetails, null, 1)}\n` `${JSON.stringify(rulesetDetails, null, 1)}\n`
); );
// We sort the hostnames for convenience/performance in the extension's
// script manager -- the scripting API does a sort() internally.
for ( const [ rulesetId, hostnamesToFidsMap ] of specificDetails ) {
specificDetails.set(
rulesetId,
Array.from(hostnamesToFidsMap).sort()
);
}
writeFile(
`${rulesetDir}/specific-details.json`,
`${JSON.stringify(specificDetails, jsonSetMapReplacer)}\n`
);
writeFile(
`${rulesetDir}/declarative-details.json`,
`${JSON.stringify(declarativeDetails, jsonSetMapReplacer, 1)}\n`
);
writeFile(
`${rulesetDir}/procedural-details.json`,
`${JSON.stringify(proceduralDetails, jsonSetMapReplacer, 1)}\n`
);
writeFile( writeFile(
`${rulesetDir}/scriptlet-details.json`, `${rulesetDir}/scriptlet-details.json`,
`${JSON.stringify(scriptletStats, jsonSetMapReplacer, 1)}\n` `${JSON.stringify(scriptletStats, jsonSetMapReplacer, 1)}\n`

View file

@ -99,7 +99,7 @@ if ( entitiesMap.size !== 0 ) {
if ( argsIndices === undefined ) { continue; } if ( argsIndices === undefined ) { continue; }
if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; }
for ( const argsIndex of argsIndices ) { for ( const argsIndex of argsIndices ) {
if ( tonotdoIndices(argsIndex) ) { continue; } if ( tonotdoIndices.includes(argsIndex) ) { continue; }
todoIndices.add(argsIndex); todoIndices.add(argsIndex);
} }
} }

View file

@ -23,6 +23,8 @@
'use strict'; 'use strict';
// ruleset: $rulesetId$
/******************************************************************************/ /******************************************************************************/
/// name css-declarative /// name css-declarative
@ -35,14 +37,16 @@
/******************************************************************************/ /******************************************************************************/
// $rulesetId$
const argsList = self.$argsList$; const argsList = self.$argsList$;
const hostnamesMap = new Map(self.$hostnamesMap$); const hostnamesMap = new Map(self.$hostnamesMap$);
const entitiesMap = new Map(self.$entitiesMap$);
const exceptionsMap = new Map(self.$exceptionsMap$);
self.declarativeImports = self.declarativeImports || []; self.declarativeImports = self.declarativeImports || [];
self.declarativeImports.push({ argsList, hostnamesMap }); self.declarativeImports.push({ argsList, hostnamesMap, entitiesMap, exceptionsMap });
/******************************************************************************/ /******************************************************************************/

View file

@ -23,6 +23,8 @@
'use strict'; 'use strict';
// ruleset: $rulesetId$
/******************************************************************************/ /******************************************************************************/
/// name css-procedural /// name css-procedural
@ -35,14 +37,16 @@
/******************************************************************************/ /******************************************************************************/
// $rulesetId$
const argsList = self.$argsList$; const argsList = self.$argsList$;
const hostnamesMap = new Map(self.$hostnamesMap$); const hostnamesMap = new Map(self.$hostnamesMap$);
const entitiesMap = new Map(self.$entitiesMap$);
const exceptionsMap = new Map(self.$exceptionsMap$);
self.proceduralImports = self.proceduralImports || []; self.proceduralImports = self.proceduralImports || [];
self.proceduralImports.push({ argsList, hostnamesMap }); self.proceduralImports.push({ argsList, hostnamesMap, entitiesMap, exceptionsMap });
/******************************************************************************/ /******************************************************************************/

View file

@ -23,6 +23,8 @@
'use strict'; 'use strict';
// ruleset: $rulesetId$
/******************************************************************************/ /******************************************************************************/
/// name css-specific /// name css-specific
@ -31,54 +33,20 @@
// Important! // Important!
// Isolate from global scope // Isolate from global scope
(function uBOL_cssSpecific() { (function uBOL_cssSpecificImports() {
/******************************************************************************/ /******************************************************************************/
// $rulesetId$
const argsList = self.$argsList$; const argsList = self.$argsList$;
const hostnamesMap = new Map(self.$hostnamesMap$); const hostnamesMap = new Map(self.$hostnamesMap$);
/******************************************************************************/ const entitiesMap = new Map(self.$entitiesMap$);
let hn; const exceptionsMap = new Map(self.$exceptionsMap$);
try { hn = document.location.hostname; } catch(ex) { }
const styles = [];
while ( hn ) {
if ( hostnamesMap.has(hn) ) {
let argsIndices = hostnamesMap.get(hn);
if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; }
for ( const argsIndex of argsIndices ) {
const details = argsList[argsIndex];
if ( details.n && details.n.includes(hn) ) { continue; }
styles.push(details.a);
}
}
if ( hn === '*' ) { break; }
const pos = hn.indexOf('.');
if ( pos !== -1 ) {
hn = hn.slice(pos + 1);
} else {
hn = '*';
}
}
argsList.length = 0; self.specificImports = self.specificImports || [];
hostnamesMap.clear(); self.specificImports.push({ argsList, hostnamesMap, entitiesMap, exceptionsMap });
if ( styles.length === 0 ) { return; }
try {
const sheet = new CSSStyleSheet();
sheet.replace(`@layer{${styles.join(',')}{display:none!important;}}`);
document.adoptedStyleSheets = [
...document.adoptedStyleSheets,
sheet
];
} catch(ex) {
}
/******************************************************************************/ /******************************************************************************/