[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;
}
let specificCount = 0;
if ( css.specific instanceof Object ) {
specificCount += css.specific.domainBased;
specificCount += css.specific.entityBased;
if ( typeof css.specific === 'number' ) {
specificCount += css.specific;
}
if ( typeof css.declarative === 'number' ) {
specificCount += css.declarative;
}
if ( typeof css.procedural === 'number' ) {
specificCount += css.procedural;
}
dom.text(
qs$(div, 'p'),

View file

@ -394,10 +394,10 @@ async function updateCspRules() {
if ( addRules.length === 0 && removeRuleIds.length === 0 ) { return; }
if ( removeRuleIds.length !== 0 ) {
console.info(`Remove ${removeRuleIds.length} DNR redirect rules`);
console.info(`Remove ${removeRuleIds.length} DNR csp rules`);
}
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 });

View file

@ -38,40 +38,6 @@ const isGecko = browser.runtime.getURL('').startsWith('moz-extension://');
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() {
let promise = resourceDetailPromises.get('scriptlet');
if ( promise !== undefined ) { return promise; }
@ -135,8 +101,8 @@ function registerGeneric(context, genericDetails) {
if ( hostnames !== undefined ) {
excludeHostnames.push(...hostnames);
}
if ( details.css.generic instanceof Object === false ) { continue; }
if ( details.css.generic.count === 0 ) { continue; }
const count = details.css?.generic || 0;
if ( count === 0 ) { continue; }
js.push(`/rulesets/scripting/generic/${details.id}.js`);
}
@ -144,19 +110,20 @@ function registerGeneric(context, genericDetails) {
js.push('/js/scripting/css-generic.js');
const { none, network, extendedSpecific, extendedGeneric } = filteringModeDetails;
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));
if ( extendedGeneric.has('all-urls') ) {
excludeMatches.push(...ut.matchesFromHostnames(none));
excludeMatches.push(...ut.matchesFromHostnames(network));
excludeMatches.push(...ut.matchesFromHostnames(extendedSpecific));
excludeMatches.push(...ut.matchesFromHostnames(excludeHostnames));
matches.push('<all_urls>');
} else {
matches.push(
...ut.matchesFromHostnames(
ut.subtractHostnameIters(
Array.from(filteringModeDetails.extendedGeneric),
Array.from(extendedGeneric),
excludeHostnames
)
)
@ -198,50 +165,33 @@ function registerGeneric(context, genericDetails) {
/******************************************************************************/
function registerProcedural(context, proceduralDetails) {
function registerProcedural(context) {
const { before, filteringModeDetails, rulesetsDetails } = context;
const js = [];
const hostnameMatches = new Set();
for ( const details of rulesetsDetails ) {
if ( details.css.procedural === 0 ) { continue; }
js.push(`/rulesets/scripting/procedural/${details.id}.js`);
if ( proceduralDetails.has(details.id) ) {
for ( const hn of proceduralDetails.get(details.id) ) {
hostnameMatches.add(hn);
}
}
for ( const rulesetDetails of rulesetsDetails ) {
const count = rulesetDetails.css?.procedural || 0;
if ( count === 0 ) { continue; }
js.push(`/rulesets/scripting/procedural/${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-procedural.js');
const {
none,
network,
extendedSpecific,
extendedGeneric,
} = filteringModeDetails;
const matches = [];
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(network));
matches.push(...ut.matchesFromHostnames(hostnameMatches));
} else if ( extendedSpecific.size !== 0 || extendedGeneric.size !== 0 ) {
matches.push(
...ut.matchesFromHostnames(
ut.intersectHostnameIters(
[ ...extendedSpecific, ...extendedGeneric ],
hostnameMatches
)
)
);
}
if ( matches.length === 0 ) { return; }
if ( network.has('all-urls') === false ) {
excludeMatches.push(...ut.matchesFromHostnames(network));
}
const registered = before.get('css-procedural');
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 js = [];
const hostnameMatches = [];
for ( const details of rulesetsDetails ) {
if ( details.css.declarative === 0 ) { continue; }
js.push(`/rulesets/scripting/declarative/${details.id}.js`);
if ( declarativeDetails.has(details.id) ) {
hostnameMatches.push(...declarativeDetails.get(details.id));
}
for ( const rulesetDetails of rulesetsDetails ) {
const count = rulesetDetails.css?.declarative || 0;
if ( count === 0 ) { continue; }
js.push(`/rulesets/scripting/declarative/${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-declarative.js');
const {
none,
network,
extendedSpecific,
extendedGeneric,
} = filteringModeDetails;
const matches = [];
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(network));
matches.push(...ut.matchesFromHostnames(hostnameMatches));
} else if ( extendedSpecific.size !== 0 || extendedGeneric.size !== 0 ) {
matches.push(
...ut.matchesFromHostnames(
ut.intersectHostnameIters(
[ ...extendedSpecific, ...extendedGeneric ],
hostnameMatches
)
)
);
}
if ( matches.length === 0 ) { return; }
if ( network.has('all-urls') === false ) {
excludeMatches.push(...ut.matchesFromHostnames(network));
}
const registered = before.get('css-declarative');
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) {
// https://bugzilla.mozilla.org/show_bug.cgi?id=1736575
// `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) {
void origins;
@ -644,19 +437,13 @@ async function registerInjectables(origins) {
const [
filteringModeDetails,
rulesetsDetails,
declarativeDetails,
proceduralDetails,
scriptletDetails,
specificDetails,
genericDetails,
registered,
] = await Promise.all([
getFilteringModeDetails(),
getEnabledRulesetsDetails(),
getDeclarativeDetails(),
getProceduralDetails(),
getScriptletDetails(),
getSpecificDetails(),
getGenericDetails(),
browser.scripting.getRegisteredContentScripts(),
]);
@ -676,11 +463,10 @@ async function registerInjectables(origins) {
toRemove,
};
registerDeclarative(context, declarativeDetails);
registerProcedural(context, proceduralDetails);
registerDeclarative(context);
registerProcedural(context);
registerScriptlet(context, scriptletDetails);
registerSpecific(context, specificDetails);
registerSpecificEntity(context);
registerSpecific(context);
registerGeneric(context, genericDetails);
toRemove.push(...Array.from(before.keys()));

View file

@ -32,41 +32,77 @@
/******************************************************************************/
const declarativeImports = self.declarativeImports || [];
self.declarativeImports = undefined;
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; }
/******************************************************************************/
const cssRuleFromProcedural = details => {
const { tasks, action } = details;
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 || [];
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 proceduralFilterer = new ProceduralFilterer(selectors);
const observer = new MutationObserver(mutations => {
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.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 groups = new Map([
[
@ -172,10 +172,18 @@ function renderFilterLists(soft = false) {
ruleset.id === 'default'
),
],
[
'annoyances',
rulesetDetails.filter(ruleset =>
ruleset.group === 'annoyances'
),
],
[
'misc',
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 {
parsedURLromOrigin,
toBroaderHostname,
@ -136,6 +128,4 @@ export {
subtractHostnameIters,
matchesFromHostnames,
hostnamesFromMatches,
fnameFromFileId,
fidFromFileName,
};

View file

@ -30,7 +30,6 @@ import process from 'process';
import { createHash } from 'crypto';
import redirectResourcesMap from './js/redirect-resources.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 makeScriptlet from './make-scriptlets.js';
@ -158,10 +157,7 @@ const writeOps = [];
const ruleResources = [];
const rulesetDetails = [];
const declarativeDetails = new Map();
const proceduralDetails = new Map();
const scriptletStats = new Map();
const specificDetails = new Map();
const genericDetails = new Map();
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) {
const out = {
count: 0,
exclusionCount: 0,
};
if ( bucketsMap === undefined ) { return out; }
if ( bucketsMap.size === 0 ) { return out; }
if ( bucketsMap === undefined ) { return 0; }
if ( bucketsMap.size === 0 ) { return 0; }
const bucketsList = Array.from(bucketsMap);
const count = bucketsList.reduce((a, v) => a += v[1].length, 0);
if ( count === 0 ) { return out; }
out.count = count;
if ( count === 0 ) { return 0; }
const selectorLists = bucketsList.map(v => [ v[0], v[1].join(',') ]);
const originalScriptletMap = await loadAllSourceScriptlets();
@ -464,13 +427,11 @@ async function processGenericCosmeticFilters(assetDetails, bucketsMap, exclusion
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
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) {
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(
groupSelectorsByHostnames(domainBased)
groupSelectorsByHostnames(mapin)
);
// We do not want more than n CSS files per subscription, so we will
// group multiple unrelated selectors in the same file, and distinct
// css declarations will be injected programmatically according to the
@ -645,89 +567,68 @@ async function processCosmeticFilters(assetDetails, mapin) {
const originalScriptletMap = await loadAllSourceScriptlets();
const generatedFiles = [];
for ( let i = 0; i < domainBasedEntries.length; i += MAX_COSMETIC_FILTERS_PER_FILE ) {
const slice = domainBasedEntries.slice(i, i + MAX_COSMETIC_FILTERS_PER_FILE);
const argsMap = slice.map(entry => [
entry[0],
{
a: entry[1].a ? entry[1].a.join(',\n') : undefined,
n: entry[1].n
}
]);
const hostnamesMap = new Map();
for ( const [ id, details ] of slice ) {
if ( details.y === undefined ) { continue; }
scriptletHostnameToIdMap(details.y, id, hostnamesMap);
}
const argsList = argsMap2List(argsMap, hostnamesMap);
const patchedScriptlet = originalScriptletMap.get('css-specific')
.replace(
'$rulesetId$',
assetDetails.id
).replace(
/\bself\.\$argsList\$/m,
`${JSON.stringify(argsList, scriptletJsonReplacer)}`
).replace(
/\bself\.\$hostnamesMap\$/m,
`${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);
const argsMap = domainBasedEntries.map(entry => [
entry[0],
{
a: entry[1].a ? entry[1].a.join(',\n') : undefined,
n: entry[1].n
}
]);
const hostnamesMap = new Map();
for ( const [ id, details ] of domainBasedEntries ) {
if ( details.y === undefined ) { continue; }
scriptletHostnameToIdMap(details.y, id, 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);
}
// 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,
// 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);
}
]);
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(
/\bself\.\$entitiesMap\$/m,
`${JSON.stringify(entitiesMap, scriptletJsonReplacer)}`
);
const fname = `${assetDetails.id}`;
writeFile(`${scriptletDir}/specific-entity/${fname}.js`, patchedScriptlet);
generatedFiles.push(fname);
argsList[i] = details.a;
}
const patchedScriptlet = originalScriptletMap.get('css-specific')
.replace(
'$rulesetId$',
assetDetails.id
).replace(
/\bself\.\$argsList\$/m,
`${JSON.stringify(argsList, scriptletJsonReplacer)}`
).replace(
/\bself\.\$hostnamesMap\$/m,
`${JSON.stringify(hostnamesMap, scriptletJsonReplacer)}`
).replace(
/\bself\.\$entitiesMap\$/m,
`${JSON.stringify(entitiesMap, scriptletJsonReplacer)}`
).replace(
/\bself\.\$exceptionsMap\$/m,
`${JSON.stringify(exceptionsMap, scriptletJsonReplacer)}`
);
writeFile(`${scriptletDir}/specific/${assetDetails.id}.js`, patchedScriptlet);
generatedFiles.push(`${assetDetails.id}`);
if ( generatedFiles.length !== 0 ) {
log(`CSS-specific domain-based: ${domainBased.size} distinct filters`);
log(`\tCombined into ${domainBasedEntries.length} distinct entries`);
log(`CSS-specific entity-based: ${entityBased.size} distinct filters`);
log(`\tCombined into ${entityBasedEntries.length} distinct entries`);
log(`CSS-specific injectable files: ${generatedFiles.length}`);
log(`\t${generatedFiles.join(', ')}`);
log(`CSS-specific: ${mapin.size} distinct filters`);
log(`\tCombined into ${hostnamesMap.size} distinct hostnames`);
log(`\tCombined into ${entitiesMap.size} distinct entities`);
}
return {
domainBased: domainBasedEntries.length,
entityBased: entityBasedEntries.length,
};
return hostnamesMap.size + entitiesMap.size;
}
/******************************************************************************/
@ -761,8 +662,29 @@ async function processDeclarativeCosmeticFilters(assetDetails, mapin) {
if ( details.y === undefined ) { continue; }
scriptletHostnameToIdMap(details.y, id, 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 patchedScriptlet = originalScriptletMap.get('css-declarative')
.replace(
@ -774,29 +696,22 @@ async function processDeclarativeCosmeticFilters(assetDetails, mapin) {
).replace(
/\bself\.\$hostnamesMap\$/m,
`${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);
{
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 ) {
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; }
scriptletHostnameToIdMap(details.y, id, 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 patchedScriptlet = originalScriptletMap.get('css-procedural')
.replace(
@ -843,35 +779,29 @@ async function processProceduralCosmeticFilters(assetDetails, mapin) {
).replace(
/\bself\.\$hostnamesMap\$/m,
`${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);
{
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 ) {
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) {
if ( mapin === undefined ) { return; }
if ( mapin === undefined ) { return 0; }
if ( mapin.size === 0 ) { return 0; }
makeScriptlet.init();
@ -887,7 +817,7 @@ async function processScriptletFilters(assetDetails, mapin) {
scriptletStats.set(assetDetails.id, stats);
}
makeScriptlet.reset();
return { domainBasedTokens: stats.length };
return stats.length;
}
/******************************************************************************/
@ -942,13 +872,6 @@ async function rulesetFromURLs(assetDetails) {
continue;
}
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;
proceduralCosmetic.set(JSON.stringify(parsed), details);
}
@ -983,6 +906,7 @@ async function rulesetFromURLs(assetDetails) {
rulesetDetails.push({
id: assetDetails.id,
name: assetDetails.name,
group: assetDetails.group,
enabled: assetDetails.enabled,
lang: assetDetails.lang,
homeURL: assetDetails.homeURL,
@ -1110,7 +1034,11 @@ async function main() {
}
// 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 ) {
const asset = assets[id];
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
await rulesetFromURLs({
id: 'stevenblack-hosts',
@ -1141,29 +1096,6 @@ async function main() {
`${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(
`${rulesetDir}/scriptlet-details.json`,
`${JSON.stringify(scriptletStats, jsonSetMapReplacer, 1)}\n`

View file

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

View file

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

View file

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

View file

@ -23,6 +23,8 @@
'use strict';
// ruleset: $rulesetId$
/******************************************************************************/
/// name css-specific
@ -31,54 +33,20 @@
// Important!
// Isolate from global scope
(function uBOL_cssSpecific() {
(function uBOL_cssSpecificImports() {
/******************************************************************************/
// $rulesetId$
const argsList = self.$argsList$;
const hostnamesMap = new Map(self.$hostnamesMap$);
/******************************************************************************/
const entitiesMap = new Map(self.$entitiesMap$);
let hn;
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 = '*';
}
}
const exceptionsMap = new Map(self.$exceptionsMap$);
argsList.length = 0;
hostnamesMap.clear();
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) {
}
self.specificImports = self.specificImports || [];
self.specificImports.push({ argsList, hostnamesMap, entitiesMap, exceptionsMap });
/******************************************************************************/