Skip to content

Commit 433adac

Browse files
committed
[mv3] Add ability to handle entity-based CSS and scriptlet injection filters
This commit adds the ability to inject entity-based plain CSS filters and also a set of the most commonly used entity-based scriptlet injection filters. Since the scripting API is not compatible with entity patterns, the entity-related content scripts are injected in all documents and the entity-matching is done by the content script themselves. Given this, entity-based content scripts are enabled only when working in the Complete filtering mode, there won't be any entity-based filters injected in lower modes. Also, since there is no way to reasonably have access to the Public Suffix List in the content scripts, the entity-matching algorithm is an approximation, though I expect false positives to be rare (time will tell). In the event of such false positive, simply falling back to Optimal mode will fix the issue. The following issues have been fixed at the same time: Fixed the no-filtering mode related rules having lower priority then redirect rules, i.e. redirect rules would still be applied despite disabling all filtering on a site. Fixed improper detection of changes to the generic-related CSS content script, potentially causing undue delays when for example trying to access the popup panel while working in Complete mode. The scripting MV3 can be quite slow when registering/updating large content scripts, so uBOL does its best to call the API only if really needed, but there had been a regression in the recent builds preventing uBO from properly detecting unchanged content script parameters.
1 parent e84b374 commit 433adac

15 files changed

+1546
-61
lines changed

platform/mv3/extension/js/mode-manager.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ async function setFilteringModeDetails(afterDetails) {
134134
requestDomains: [],
135135
resourceTypes: [ 'main_frame' ],
136136
},
137+
priority: 100,
137138
};
138139
if ( actualDetails.none.size ) {
139140
rule.condition.requestDomains = Array.from(actualDetails.none);

platform/mv3/extension/js/popup.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,12 +295,17 @@ async function init() {
295295
if ( popupPanelData.hasOmnipotence ) {
296296
ruleCount += rules.removeparam + rules.redirect;
297297
}
298+
let specificCount = 0;
299+
if ( css.specific instanceof Object ) {
300+
specificCount += css.specific.domainBased;
301+
specificCount += css.specific.entityBased;
302+
}
298303
dom.text(
299304
qs$('p', div),
300305
i18n$('perRulesetStats')
301306
.replace('{{ruleCount}}', ruleCount.toLocaleString())
302307
.replace('{{filterCount}}', filters.accepted.toLocaleString())
303-
.replace('{{cssSpecificCount}}', css.specific.toLocaleString())
308+
.replace('{{cssSpecificCount}}', specificCount.toLocaleString())
304309
);
305310
parent.append(div);
306311
}

platform/mv3/extension/js/scripting-manager.js

Lines changed: 133 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,9 @@ function registerGeneric(context, genericDetails) {
133133
if ( hostnames !== undefined ) {
134134
excludeHostnames.push(...hostnames);
135135
}
136+
if ( details.css.generic instanceof Object === false ) { continue; }
136137
if ( details.css.generic.count === 0 ) { continue; }
137-
js.push(`/rulesets/scripting/generic/${details.id}.generic.js`);
138+
js.push(`/rulesets/scripting/generic/${details.id}.js`);
138139
}
139140

140141
if ( js.length === 0 ) { return; }
@@ -202,7 +203,7 @@ function registerProcedural(context, proceduralDetails) {
202203
const hostnameMatches = [];
203204
for ( const details of rulesetsDetails ) {
204205
if ( details.css.procedural === 0 ) { continue; }
205-
js.push(`/rulesets/scripting/procedural/${details.id}.procedural.js`);
206+
js.push(`/rulesets/scripting/procedural/${details.id}.js`);
206207
if ( proceduralDetails.has(details.id) ) {
207208
hostnameMatches.push(...proceduralDetails.get(details.id));
208209
}
@@ -278,7 +279,7 @@ function registerDeclarative(context, declarativeDetails) {
278279
const hostnameMatches = [];
279280
for ( const details of rulesetsDetails ) {
280281
if ( details.css.declarative === 0 ) { continue; }
281-
js.push(`/rulesets/scripting/declarative/${details.id}.declarative.js`);
282+
js.push(`/rulesets/scripting/declarative/${details.id}.js`);
282283
if ( declarativeDetails.has(details.id) ) {
283284
hostnameMatches.push(...declarativeDetails.get(details.id));
284285
}
@@ -420,6 +421,71 @@ function registerScriptlet(context, scriptletDetails) {
420421

421422
/******************************************************************************/
422423

424+
function registerScriptletEntity(context) {
425+
const { before, filteringModeDetails, rulesetsDetails } = context;
426+
427+
const js = [];
428+
for ( const details of rulesetsDetails ) {
429+
const { scriptlets } = details;
430+
if ( scriptlets instanceof Object === false ) { continue; }
431+
if ( Array.isArray(scriptlets.entityBasedTokens) === false ) { continue; }
432+
if ( scriptlets.entityBasedTokens.length === 0 ) { continue; }
433+
for ( const token of scriptlets.entityBasedTokens ) {
434+
js.push(`/rulesets/scripting/scriptlet-entity/${details.id}.${token}.js`);
435+
}
436+
}
437+
438+
if ( js.length === 0 ) { return; }
439+
440+
const matches = [];
441+
const excludeMatches = [];
442+
if ( filteringModeDetails.extendedGeneric.has('all-urls') ) {
443+
excludeMatches.push(...ut.matchesFromHostnames(filteringModeDetails.none));
444+
excludeMatches.push(...ut.matchesFromHostnames(filteringModeDetails.network));
445+
excludeMatches.push(...ut.matchesFromHostnames(filteringModeDetails.extendedSpecific));
446+
matches.push('<all_urls>');
447+
} else {
448+
matches.push(
449+
...ut.matchesFromHostnames(filteringModeDetails.extendedGeneric)
450+
);
451+
}
452+
453+
if ( matches.length === 0 ) { return; }
454+
455+
const registered = before.get('scriptlet.entity');
456+
before.delete('scriptlet.entity'); // Important!
457+
458+
// register
459+
if ( registered === undefined ) {
460+
context.toAdd.push({
461+
id: 'scriptlet.entity',
462+
js,
463+
matches,
464+
excludeMatches,
465+
runAt: 'document_start',
466+
world: 'MAIN',
467+
});
468+
return;
469+
}
470+
471+
// update
472+
const directive = { id: 'scriptlet.entity' };
473+
if ( arrayEq(registered.js, js, false) === false ) {
474+
directive.js = js;
475+
}
476+
if ( arrayEq(registered.matches, matches) === false ) {
477+
directive.matches = matches;
478+
}
479+
if ( arrayEq(registered.excludeMatches, excludeMatches) === false ) {
480+
directive.excludeMatches = excludeMatches;
481+
}
482+
if ( directive.js || directive.matches || directive.excludeMatches ) {
483+
context.toUpdate.push(directive);
484+
}
485+
}
486+
487+
/******************************************************************************/
488+
423489
function registerSpecific(context, specificDetails) {
424490
const { filteringModeDetails } = context;
425491

@@ -561,6 +627,68 @@ const toUpdatableScript = (context, fname, hostnames) => {
561627

562628
/******************************************************************************/
563629

630+
function registerSpecificEntity(context) {
631+
const { before, filteringModeDetails, rulesetsDetails } = context;
632+
633+
const js = [];
634+
for ( const details of rulesetsDetails ) {
635+
if ( details.css.specific instanceof Object === false ) { continue; }
636+
if ( details.css.specific.entityBased === 0 ) { continue; }
637+
js.push(`/rulesets/scripting/specific-entity/${details.id}.js`);
638+
}
639+
640+
if ( js.length === 0 ) { return; }
641+
642+
const matches = [];
643+
const excludeMatches = [];
644+
if ( filteringModeDetails.extendedGeneric.has('all-urls') ) {
645+
excludeMatches.push(...ut.matchesFromHostnames(filteringModeDetails.none));
646+
excludeMatches.push(...ut.matchesFromHostnames(filteringModeDetails.network));
647+
excludeMatches.push(...ut.matchesFromHostnames(filteringModeDetails.extendedSpecific));
648+
matches.push('<all_urls>');
649+
} else {
650+
matches.push(
651+
...ut.matchesFromHostnames(filteringModeDetails.extendedGeneric)
652+
);
653+
}
654+
655+
if ( matches.length === 0 ) { return; }
656+
657+
js.push('/js/scripting/css-specific.entity.js');
658+
659+
const registered = before.get('css-specific.entity');
660+
before.delete('css-specific.entity'); // Important!
661+
662+
// register
663+
if ( registered === undefined ) {
664+
context.toAdd.push({
665+
id: 'css-specific.entity',
666+
js,
667+
matches,
668+
excludeMatches,
669+
runAt: 'document_start',
670+
});
671+
return;
672+
}
673+
674+
// update
675+
const directive = { id: 'css-specific.entity' };
676+
if ( arrayEq(registered.js, js, false) === false ) {
677+
directive.js = js;
678+
}
679+
if ( arrayEq(registered.matches, matches) === false ) {
680+
directive.matches = matches;
681+
}
682+
if ( arrayEq(registered.excludeMatches, excludeMatches) === false ) {
683+
directive.excludeMatches = excludeMatches;
684+
}
685+
if ( directive.js || directive.matches || directive.excludeMatches ) {
686+
context.toUpdate.push(directive);
687+
}
688+
}
689+
690+
/******************************************************************************/
691+
564692
async function registerInjectables(origins) {
565693
void origins;
566694

@@ -604,7 +732,9 @@ async function registerInjectables(origins) {
604732
registerDeclarative(context, declarativeDetails);
605733
registerProcedural(context, proceduralDetails);
606734
registerScriptlet(context, scriptletDetails);
735+
registerScriptletEntity(context);
607736
registerSpecific(context, specificDetails);
737+
registerSpecificEntity(context);
608738
registerGeneric(context, genericDetails);
609739

610740
toRemove.push(...Array.from(before.keys()));
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*******************************************************************************
2+
3+
uBlock Origin - a browser extension to block requests.
4+
Copyright (C) 2019-present Raymond Hill
5+
6+
This program is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with this program. If not, see {http://www.gnu.org/licenses/}.
18+
19+
Home: https://github.com/gorhill/uBlock
20+
*/
21+
22+
/* jshint esversion:11 */
23+
24+
'use strict';
25+
26+
/******************************************************************************/
27+
28+
// Important!
29+
// Isolate from global scope
30+
(function uBOL_cssSpecificEntity() {
31+
32+
/******************************************************************************/
33+
34+
// $rulesetId$
35+
36+
const specificEntityImports = self.specificEntityImports || [];
37+
38+
/******************************************************************************/
39+
40+
const lookupSelectors = (hn, entity, out) => {
41+
for ( const { argsList, entitiesMap } of specificEntityImports ) {
42+
let argsIndices = entitiesMap.get(entity);
43+
if ( argsIndices === undefined ) { continue; }
44+
if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; }
45+
for ( const argsIndex of argsIndices ) {
46+
const details = argsList[argsIndex];
47+
if ( details.n && details.n.includes(hn) ) { continue; }
48+
out.push(details.a);
49+
}
50+
}
51+
};
52+
53+
let hn = '';
54+
try { hn = document.location.hostname; } catch(ex) { }
55+
const selectors = [];
56+
const hnparts = hn.split('.');
57+
const hnpartslen = hnparts.length - 1;
58+
for ( let i = 0; i < hnpartslen; i++ ) {
59+
for ( let j = hnpartslen; j > i; j-- ) {
60+
lookupSelectors(
61+
hnparts.slice(i).join('.'),
62+
hnparts.slice(i,j).join('.'),
63+
selectors
64+
);
65+
}
66+
}
67+
68+
self.specificEntityImports = undefined;
69+
70+
if ( selectors.length === 0 ) { return; }
71+
72+
try {
73+
const sheet = new CSSStyleSheet();
74+
sheet.replace(`@layer{${selectors.join(',')}{display:none!important;}}`);
75+
document.adoptedStyleSheets = [
76+
...document.adoptedStyleSheets,
77+
sheet
78+
];
79+
} catch(ex) {
80+
}
81+
82+
/******************************************************************************/
83+
84+
})();
85+
86+
/******************************************************************************/

0 commit comments

Comments
 (0)