@@ -25,6 +25,7 @@ import {
2525 localRead ,
2626 localRemove ,
2727 localWrite ,
28+ runtime ,
2829} from './ext.js' ;
2930
3031import {
@@ -33,7 +34,16 @@ import {
3334 subtractHostnameIters ,
3435} from './utils.js' ;
3536
36- import { ubolErr } from './debug.js' ;
37+ import {
38+ ubolErr ,
39+ ubolLog ,
40+ } from './debug.js' ;
41+
42+ /******************************************************************************/
43+
44+ const isProcedural = a => a . startsWith ( '{' ) ;
45+ const isScriptlet = a => a . startsWith ( '+js' ) ;
46+ const isCSS = a => isProcedural ( a ) === false && isScriptlet ( a ) === false ;
3747
3848/******************************************************************************/
3949
@@ -82,7 +92,7 @@ export async function customFiltersFromHostname(hostname) {
8292 const selectors = results [ i ] ;
8393 if ( selectors === undefined ) { continue ; }
8494 selectors . forEach ( selector => {
85- out . push ( selector . startsWith ( '0' ) ? selector . slice ( 1 ) : selector ) ;
95+ out . push ( selector ) ;
8696 } ) ;
8797 }
8898 return out . sort ( ) ;
@@ -107,7 +117,7 @@ async function getAllCustomFilterKeys() {
107117export async function getAllCustomFilters ( ) {
108118 const collect = async key => {
109119 const selectors = await readFromStorage ( key ) ;
110- return [ key . slice ( 5 ) , selectors . map ( a => a . startsWith ( '0' ) ? a . slice ( 1 ) : a ) ] ;
120+ return [ key . slice ( 5 ) , selectors ] ;
111121 } ;
112122 const keys = await getAllCustomFilterKeys ( ) ;
113123 const promises = keys . map ( k => collect ( k ) ) ;
@@ -142,7 +152,7 @@ export async function injectCustomFilters(tabId, frameId, hostname) {
142152 const selectors = await customFiltersFromHostname ( hostname ) ;
143153 if ( selectors . length === 0 ) { return ; }
144154 const promises = [ ] ;
145- const plainSelectors = selectors . filter ( a => a . startsWith ( '{' ) === false ) ;
155+ const plainSelectors = selectors . filter ( a => isCSS ( a ) ) ;
146156 if ( plainSelectors . length !== 0 ) {
147157 promises . push (
148158 browser . scripting . insertCSS ( {
@@ -154,7 +164,7 @@ export async function injectCustomFilters(tabId, frameId, hostname) {
154164 } )
155165 ) ;
156166 }
157- const proceduralSelectors = selectors . filter ( a => a . startsWith ( '{' ) ) ;
167+ const proceduralSelectors = selectors . filter ( a => isProcedural ( a ) ) ;
158168 if ( proceduralSelectors . length !== 0 ) {
159169 promises . push (
160170 browser . scripting . executeScript ( {
@@ -173,11 +183,11 @@ export async function injectCustomFilters(tabId, frameId, hostname) {
173183/******************************************************************************/
174184
175185export async function registerCustomFilters ( context ) {
176- const siteKeys = await getAllCustomFilterKeys ( ) ;
177- if ( siteKeys . length === 0 ) { return ; }
186+ const customFilters = new Map ( await getAllCustomFilters ( ) ) ;
187+ if ( customFilters . size === 0 ) { return ; }
178188
179189 const { none } = context . filteringModeDetails ;
180- let hostnames = siteKeys . map ( a => a . slice ( 5 ) ) ;
190+ let hostnames = Array . from ( customFilters . keys ( ) ) ;
181191 if ( none . has ( 'all-urls' ) ) {
182192 const { basic, optimal, complete } = context . filteringModeDetails ;
183193 hostnames = intersectHostnameIters ( hostnames , [
@@ -186,6 +196,9 @@ export async function registerCustomFilters(context) {
186196 } else if ( none . size !== 0 ) {
187197 hostnames = [ ...subtractHostnameIters ( hostnames , none ) ] ;
188198 }
199+ hostnames = hostnames . filter ( a => {
200+ return customFilters . get ( a ) . some ( a => isCSS ( a ) || isProcedural ( a ) ) ;
201+ } ) ;
189202 if ( hostnames . length === 0 ) { return ; }
190203
191204 const directive = {
@@ -251,10 +264,6 @@ async function removeCustomFiltersByKey(key, toRemove) {
251264 const beforeCount = selectors . length ;
252265 for ( const selector of toRemove ) {
253266 let i = selectors . indexOf ( selector ) ;
254- if ( i === - 1 ) {
255- i = selectors . indexOf ( `0${ selector } ` ) ;
256- if ( i === - 1 ) { continue ; }
257- }
258267 selectors . splice ( i , 1 ) ;
259268 }
260269 const afterCount = selectors . length ;
@@ -266,3 +275,113 @@ async function removeCustomFiltersByKey(key, toRemove) {
266275 }
267276 return true ;
268277}
278+
279+ /******************************************************************************/
280+
281+ export function isUserScriptsAvailable ( ) {
282+ if ( browser . offscreen === undefined ) { return false ; }
283+ try {
284+ chrome . userScripts . getScripts ( ) ;
285+ } catch {
286+ return false ;
287+ }
288+ return true ;
289+ }
290+
291+ export async function registerCustomScriptlets ( context ) {
292+ if ( isUserScriptsAvailable ( ) === false ) { return ; }
293+ const { none, basic, optimal, complete } = context . filteringModeDetails ;
294+ const notNone = [ ...basic , ...optimal , ...complete ] ;
295+ if ( registerCustomScriptlets . promise ) {
296+ registerCustomScriptlets . promise = registerCustomScriptlets . promise . then ( ( ) =>
297+ registerCustomScriptlets . create ( ) . then ( worlds =>
298+ registerCustomScriptlets . register ( worlds , none , notNone )
299+ )
300+ ) ;
301+ } else {
302+ registerCustomScriptlets . promise = registerCustomScriptlets . create ( ) . then ( worlds =>
303+ registerCustomScriptlets . register ( worlds , none , notNone )
304+ ) ;
305+ }
306+ return registerCustomScriptlets . promise ;
307+ }
308+
309+ registerCustomScriptlets . register = async function ( worlds , none , notNone ) {
310+ if ( Boolean ( worlds ) === false ) { return ; }
311+ const toAdd = [ ] ;
312+ const prepare = world => {
313+ let { hostnames } = world ;
314+ if ( none . has ( 'all-urls' ) ) {
315+ hostnames = intersectHostnameIters ( hostnames , notNone ) ;
316+ } else if ( none . size !== 0 ) {
317+ hostnames = [ ...subtractHostnameIters ( hostnames , none ) ] ;
318+ }
319+ if ( hostnames . length === 0 ) { return ; }
320+ return {
321+ allFrames : true ,
322+ js : [ { code : world . code } ] ,
323+ matches : matchesFromHostnames ( hostnames ) ,
324+ runAt : 'document_start' ,
325+ } ;
326+ } ;
327+ if ( worlds . ISOLATED ) {
328+ const directive = prepare ( worlds . ISOLATED ) ;
329+ if ( directive ) {
330+ directive . id = 'user.isolated' ;
331+ directive . world = 'USER_SCRIPT' ;
332+ toAdd . push ( directive ) ;
333+ }
334+ }
335+ if ( worlds . MAIN ) {
336+ const directive = prepare ( worlds . MAIN ) ;
337+ if ( directive ) {
338+ directive . id = 'user.main' ;
339+ directive . world = 'MAIN' ;
340+ toAdd . push ( directive ) ;
341+ }
342+ }
343+ if ( toAdd . length === 0 ) { return ; }
344+ await browser . userScripts . register ( toAdd ) . then ( ( ) => {
345+ ubolLog ( `Registered userscript ${ toAdd . map ( v => v . id ) } ` ) ;
346+ } ) ;
347+ } ;
348+
349+ registerCustomScriptlets . create = async function ( ) {
350+ const toRemove = await browser . userScripts . getScripts ( ) ;
351+ if ( toRemove . length !== 0 ) {
352+ await browser . userScripts . unregister ( ) ;
353+ ubolLog ( `Unregistered userscript ${ toRemove . map ( v => v . id ) } ` ) ;
354+ }
355+ const { promise : offscreenPromise , resolve : offscreenResolve } = Promise . withResolvers ( ) ;
356+ const handler = ( msg , sender , callback ) => {
357+ if ( typeof msg !== 'object' ) { return ; }
358+ switch ( msg ?. what ) {
359+ case 'getAllCustomFilters' :
360+ getAllCustomFilters ( ) . then ( result => {
361+ callback ( result ) ;
362+ } ) ;
363+ break ;
364+ case 'registerCustomScriptlets' :
365+ offscreenResolve ( msg ) ;
366+ break ;
367+ default :
368+ break ;
369+ }
370+ } ;
371+ const { promise : timeoutPromise , resolve : timeoutResolve } = Promise . withResolvers ( ) ;
372+ self . setTimeout ( timeoutResolve , 1000 ) ;
373+ runtime . onMessage . addListener ( handler ) ;
374+ const [ worlds ] = await Promise . all ( [
375+ Promise . race ( [ offscreenPromise , timeoutPromise ] ) ,
376+ browser . offscreen . createDocument ( {
377+ url : '/js/offscreen/compile-scriptlets.html' ,
378+ reasons : [ 'WORKERS' ] ,
379+ justification : 'To compile custom user script-based filters in a modular way from service worker (service workers do not allow dynamic module import)' ,
380+ } ) ,
381+ ] ) ;
382+ runtime . onMessage . removeListener ( handler ) ;
383+ await browser . offscreen . closeDocument ( ) ;
384+ return worlds ;
385+ } ;
386+
387+ registerCustomScriptlets . promise = null ;
0 commit comments