[mv3] Load specific cosmetic filtering data on-demand only

The cosmetic filtering data embedded in the content scripts has been
extracted in corresponding JSON files. When a list is enabled, the
corresponding cosmetic filtering data from the JSON files, if any, is
persisted into the extension's local storage.

The cosmetic filtering-related content scripts will load the data
from these JSON files on-demand only and matching cosmetic filters
will be extracted, then the result is cached in the session storage,
ensuring there is no longer a need to perform lookup for the rest
of the browser session.

As a result this further reduces the time to First Contentful Paint.

Related issue:
https://github.com/uBlockOrigin/uBOL-home/issues/557
This commit is contained in:
Raymond Hill 2025-12-09 15:32:02 -05:00
parent e036156f3a
commit 0fb845d1ac
No known key found for this signature in database
GPG key ID: F5630CAE62A14316
9 changed files with 244 additions and 220 deletions

View file

@ -19,6 +19,8 @@
Home: https://github.com/gorhill/uBlock Home: https://github.com/gorhill/uBlock
*/ */
import * as scrmgr from './scripting-manager.js';
import { import {
MODE_BASIC, MODE_BASIC,
MODE_OPTIMAL, MODE_OPTIMAL,
@ -63,6 +65,7 @@ import {
browser, browser,
localRead, localRemove, localWrite, localRead, localRemove, localWrite,
runtime, runtime,
sessionAccessLevel,
webextFlavor, webextFlavor,
} from './ext.js'; } from './ext.js';
@ -99,14 +102,13 @@ import {
} from './debug.js'; } from './debug.js';
import { dnr } from './ext-compat.js'; import { dnr } from './ext-compat.js';
import { registerInjectables } from './scripting-manager.js';
import { toggleToolbarIcon } from './action.js'; import { toggleToolbarIcon } from './action.js';
/******************************************************************************/ /******************************************************************************/
const UBOL_ORIGIN = runtime.getURL('').replace(/\/$/, '').toLowerCase(); const UBOL_ORIGIN = runtime.getURL('').replace(/\/$/, '').toLowerCase();
const canShowBlockedCount = typeof dnr.setExtensionActionOptions === 'function'; const canShowBlockedCount = typeof dnr.setExtensionActionOptions === 'function';
const { registerInjectables } = scrmgr;
let pendingPermissionRequest; let pendingPermissionRequest;
@ -223,7 +225,7 @@ function onMessage(request, sender, callback) {
switch ( request.what ) { switch ( request.what ) {
case 'insertCSS': { case 'insertCSS':
if ( frameId === false ) { return false; } if ( frameId === false ) { return false; }
// https://bugs.webkit.org/show_bug.cgi?id=262491 // https://bugs.webkit.org/show_bug.cgi?id=262491
if ( frameId !== 0 && webextFlavor === 'safari' ) { return false; } if ( frameId !== 0 && webextFlavor === 'safari' ) { return false; }
@ -235,10 +237,11 @@ function onMessage(request, sender, callback) {
ubolErr(`insertCSS/${reason}`); ubolErr(`insertCSS/${reason}`);
}); });
return false; return false;
}
case 'removeCSS': { case 'removeCSS':
if ( frameId === false ) { return false; } if ( frameId === false ) { return false; }
// https://bugs.webkit.org/show_bug.cgi?id=262491
if ( frameId !== 0 && webextFlavor === 'safari' ) { return false; }
browser.scripting.removeCSS({ browser.scripting.removeCSS({
css: request.css, css: request.css,
origin: 'USER', origin: 'USER',
@ -247,7 +250,6 @@ function onMessage(request, sender, callback) {
ubolErr(`removeCSS/${reason}`); ubolErr(`removeCSS/${reason}`);
}); });
return false; return false;
}
case 'toggleToolbarIcon': { case 'toggleToolbarIcon': {
if ( tabId ) { if ( tabId ) {
@ -667,6 +669,10 @@ async function startSession() {
// launch time whether content css/scripts are properly registered. // launch time whether content css/scripts are properly registered.
registerInjectables(); registerInjectables();
// Cosmetic filtering-related content scripts cache fitlering data in
// session storage.
sessionAccessLevel({ accessLevel: 'TRUSTED_AND_UNTRUSTED_CONTEXTS' });
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/declarativeNetRequest // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/declarativeNetRequest
// Firefox API does not support `dnr.setExtensionActionOptions` // Firefox API does not support `dnr.setExtensionActionOptions`
if ( canShowBlockedCount ) { if ( canShowBlockedCount ) {
@ -706,6 +712,8 @@ async function start() {
if ( process.wakeupRun === false ) { if ( process.wakeupRun === false ) {
await startSession(); await startSession();
} else {
scrmgr.onWakeupRun();
} }
toggleDeveloperMode(rulesetConfig.developerMode); toggleDeveloperMode(rulesetConfig.developerMode);

View file

@ -100,6 +100,24 @@ export async function sessionRemove(key) {
return browser.storage.session.remove(key); return browser.storage.session.remove(key);
} }
export async function sessionKeys() {
if ( notAnObject(browser?.storage?.session) ) { return; }
if ( browser.storage.session.getKeys ) {
return browser.storage.session.getKeys();
}
const bin = await browser.storage.session.get(null);
if ( notAnObject(bin) ) { return; }
return Object.keys(bin);
}
export async function sessionAccessLevel(level) {
try {
browser.storage.session.setAccessLevel(level);
} catch {
}
}
/******************************************************************************/ /******************************************************************************/
export async function adminRead(key) { export async function adminRead(key) {

View file

@ -21,7 +21,11 @@
import * as ut from './utils.js'; import * as ut from './utils.js';
import { browser, localRemove } from './ext.js'; import {
browser,
localKeys, localRemove, localWrite,
sessionKeys, sessionRead, sessionRemove, sessionWrite,
} from './ext.js';
import { ubolErr, ubolLog } from './debug.js'; import { ubolErr, ubolLog } from './debug.js';
import { fetchJSON } from './fetch.js'; import { fetchJSON } from './fetch.js';
@ -90,6 +94,15 @@ const normalizeRegisteredContentScripts = registered => {
/******************************************************************************/ /******************************************************************************/
async function resetCSSCache() {
const keys = await sessionKeys();
return Promise.all(
keys.filter(a => a.startsWith('cache.css.')).map(a => sessionRemove(a))
);
}
/******************************************************************************/
function registerHighGeneric(context, genericDetails) { function registerHighGeneric(context, genericDetails) {
const { before, filteringModeDetails, rulesetsDetails } = context; const { before, filteringModeDetails, rulesetsDetails } = context;
@ -286,16 +299,24 @@ function registerGeneric(context, genericDetails) {
/******************************************************************************/ /******************************************************************************/
function registerProcedural(context) { async function registerCosmetic(realm, context) {
const { before, filteringModeDetails, rulesetsDetails } = context; const { before, filteringModeDetails, rulesetsDetails } = context;
const js = []; {
for ( const rulesetDetails of rulesetsDetails ) { const keys = await localKeys();
const count = rulesetDetails.css?.procedural || 0; for ( const key of keys ) {
if ( count === 0 ) { continue; } if ( key.startsWith(`css.${realm}.`) === false ) { continue; }
js.push(`/rulesets/scripting/procedural/${rulesetDetails.id}.js`); localRemove(key);
}
} }
if ( js.length === 0 ) { return; }
const rulesetIds = [];
for ( const rulesetDetails of rulesetsDetails ) {
const count = rulesetDetails.css?.[realm] || 0;
if ( count === 0 ) { continue; }
rulesetIds.push(rulesetDetails.id);
}
if ( rulesetIds.length === 0 ) { return; }
const { none, basic, optimal, complete } = filteringModeDetails; const { none, basic, optimal, complete } = filteringModeDetails;
const matches = [ const matches = [
@ -304,10 +325,24 @@ function registerProcedural(context) {
]; ];
if ( matches.length === 0 ) { return; } if ( matches.length === 0 ) { return; }
{
const promises = [];
for ( const id of rulesetIds ) {
promises.push(
fetchJSON(`/rulesets/scripting/${realm}/${id}`).then(data => {
return localWrite(`css.${realm}.${id}`, data);
})
);
}
await Promise.all(promises);
}
normalizeMatches(matches); normalizeMatches(matches);
const realmid = `css-${realm}`;
const js = rulesetIds.map(id => `/rulesets/scripting/${realm}/${id}.js`);
js.unshift('/js/scripting/css-api.js', '/js/scripting/isolated-api.js'); js.unshift('/js/scripting/css-api.js', '/js/scripting/isolated-api.js');
js.push('/js/scripting/css-procedural.js'); js.push(`/js/scripting/${realmid}.js`);
const excludeMatches = []; const excludeMatches = [];
if ( none.has('all-urls') === false && basic.has('all-urls') === false ) { if ( none.has('all-urls') === false && basic.has('all-urls') === false ) {
@ -320,11 +355,11 @@ function registerProcedural(context) {
} }
} }
const registered = before.get('css-procedural'); const registered = before.get(realmid);
before.delete('css-procedural'); // Important! before.delete(realmid); // Important!
const directive = { const directive = {
id: 'css-procedural', id: realmid,
js, js,
matches, matches,
allFrames: true, allFrames: true,
@ -346,71 +381,7 @@ function registerProcedural(context) {
ut.strArrayEq(registered.matches, matches) === false || ut.strArrayEq(registered.matches, matches) === false ||
ut.strArrayEq(registered.excludeMatches, excludeMatches) === false ut.strArrayEq(registered.excludeMatches, excludeMatches) === false
) { ) {
context.toRemove.push('css-procedural'); context.toRemove.push(realmid);
context.toAdd.push(directive);
}
}
/******************************************************************************/
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, basic, optimal, complete } = filteringModeDetails;
const matches = [
...ut.matchesFromHostnames(optimal),
...ut.matchesFromHostnames(complete),
];
if ( matches.length === 0 ) { return; }
normalizeMatches(matches);
js.unshift('/js/scripting/css-api.js', '/js/scripting/isolated-api.js');
js.push('/js/scripting/css-specific.js');
const excludeMatches = [];
if ( none.has('all-urls') === false ) {
excludeMatches.push(...ut.matchesFromHostnames(none));
}
if ( basic.has('all-urls') === false ) {
excludeMatches.push(...ut.matchesFromHostnames(basic));
}
const registered = before.get('css-specific');
before.delete('css-specific'); // Important!
const directive = {
id: 'css-specific',
js,
matches,
allFrames: true,
runAt: 'document_start',
};
if ( excludeMatches.length !== 0 ) {
directive.excludeMatches = excludeMatches;
}
// register
if ( registered === undefined ) {
context.toAdd.push(directive);
return;
}
// update
if (
ut.strArrayEq(registered.js, js, false) === false ||
ut.strArrayEq(registered.matches, matches) === false ||
ut.strArrayEq(registered.excludeMatches, excludeMatches) === false
) {
context.toRemove.push('css-specific');
context.toAdd.push(directive); context.toAdd.push(directive);
} }
} }
@ -499,7 +470,7 @@ function registerScriptlet(context, scriptletDetails) {
// Issue: Safari appears to completely ignore excludeMatches // Issue: Safari appears to completely ignore excludeMatches
// https://github.com/radiolondra/ExcludeMatches-Test // https://github.com/radiolondra/ExcludeMatches-Test
async function registerInjectables() { export async function registerInjectables() {
if ( browser.scripting === undefined ) { return false; } if ( browser.scripting === undefined ) { return false; }
if ( registerInjectables.barrier ) { return true; } if ( registerInjectables.barrier ) { return true; }
@ -533,9 +504,9 @@ async function registerInjectables() {
}; };
await Promise.all([ await Promise.all([
registerProcedural(context),
registerScriptlet(context, scriptletDetails), registerScriptlet(context, scriptletDetails),
registerSpecific(context), registerCosmetic('specific', context),
registerCosmetic('procedural', context),
registerGeneric(context, genericDetails), registerGeneric(context, genericDetails),
registerHighGeneric(context, genericDetails), registerHighGeneric(context, genericDetails),
registerCustomFilters(context), registerCustomFilters(context),
@ -564,6 +535,8 @@ async function registerInjectables() {
} }
} }
await resetCSSCache();
registerInjectables.barrier = false; registerInjectables.barrier = false;
return true; return true;
@ -571,6 +544,25 @@ async function registerInjectables() {
/******************************************************************************/ /******************************************************************************/
export { export async function onWakeupRun() {
registerInjectables const cleanupTime = await sessionRead('scripting.manager.cleanup.time') || 0;
}; const now = Date.now();
const since = now - cleanupTime;
if ( since < (15 * 60 * 1000) ) { return; } // 15 minutes
const MAX_CACHE_ENTRY_LOW = 256;
const MAX_CACHE_ENTRY_HIGH = MAX_CACHE_ENTRY_LOW +
Math.min(Math.round(MAX_CACHE_ENTRY_LOW + MAX_CACHE_ENTRY_LOW / 8), 1);
const keys = await sessionKeys() || [];
const cacheKeys = keys.filter(a => a.startsWith('cache.css.'));
if ( cacheKeys.length < MAX_CACHE_ENTRY_HIGH ) { return; }
const entries = await Promise.all(cacheKeys.map(async a => {
const entry = await sessionRead(a) || {};
entry.key = a;
return entry;
}));
entries.sort((a, b) => b.t - a.t);
entries.slice(MAX_CACHE_ENTRY_LOW).map(a => sessionRemove(a.key));
sessionWrite('scripting.manager.cleanup.time', now)
}
/******************************************************************************/

View file

@ -21,7 +21,7 @@
// Important! // Important!
// Isolate from global scope // Isolate from global scope
(function uBOL_cssProcedural() { (async function uBOL_cssProcedural() {
/******************************************************************************/ /******************************************************************************/
@ -30,49 +30,13 @@ self.proceduralImports = undefined;
/******************************************************************************/ /******************************************************************************/
const isolatedAPI = self.isolatedAPI; const selectors = await self.cosmeticAPI.getSelectors('procedural', proceduralImports);
const selectors = new Set(); self.cosmeticAPI.release();
const exceptions = new Set(); if ( selectors.length === 0 ) { return; }
const lookupHostname = (hostname, details) => {
const listref = isolatedAPI.binarySearch(details.hostnames, hostname);
if ( listref === -1 ) { return; }
if ( Array.isArray(details.selectorLists) === false ) {
details.selectorLists = details.selectorLists.split(';');
details.selectorListRefs = JSON.parse(`[${details.selectorListRefs}]`);
}
const ilist = details.selectorListRefs[listref];
const list = JSON.parse(`[${details.selectorLists[ilist]}]`);
for ( const iselector of list ) {
if ( iselector >= 0 ) {
selectors.add(details.selectors[iselector]);
} else {
exceptions.add(details.selectors[~iselector]);
}
}
};
const lookupAll = hostname => {
for ( const details of proceduralImports ) {
lookupHostname(hostname, details);
}
};
isolatedAPI.forEachHostname(lookupAll, {
hasEntities: proceduralImports.some(a => a.hasEntities)
});
proceduralImports.length = 0; proceduralImports.length = 0;
for ( const selector of exceptions ) { const declaratives = selectors.filter(a => a.cssable);
selectors.delete(selector);
}
if ( selectors.size === 0 ) { return; }
const exceptedSelectors = Array.from(selectors).map(a => JSON.parse(a));
const declaratives = exceptedSelectors.filter(a => a.cssable);
if ( declaratives.length !== 0 ) { if ( declaratives.length !== 0 ) {
const cssRuleFromProcedural = details => { const cssRuleFromProcedural = details => {
const { tasks, action } = details; const { tasks, action } = details;
@ -112,7 +76,7 @@ if ( declaratives.length !== 0 ) {
} }
} }
const procedurals = exceptedSelectors.filter(a => a.cssable === undefined); const procedurals = selectors.filter(a => a.cssable === undefined);
if ( procedurals.length !== 0 ) { if ( procedurals.length !== 0 ) {
const addSelectors = selectors => { const addSelectors = selectors => {
if ( self.listsProceduralFiltererAPI instanceof Object === false ) { return; } if ( self.listsProceduralFiltererAPI instanceof Object === false ) { return; }

View file

@ -21,7 +21,7 @@
// Important! // Important!
// Isolate from global scope // Isolate from global scope
(function uBOL_cssSpecific() { (async function uBOL_cssSpecific() {
/******************************************************************************/ /******************************************************************************/
@ -30,48 +30,10 @@ self.specificImports = undefined;
/******************************************************************************/ /******************************************************************************/
const isolatedAPI = self.isolatedAPI; const selectors = await self.cosmeticAPI.getSelectors('specific', specificImports);
const selectors = new Set(); self.cosmeticAPI.release();
const exceptions = new Set(); if ( selectors.length === 0 ) { return; }
self.cssAPI.insert(`${selectors.join(',\n')}{display:none!important;}`);
const lookupHostname = (hostname, details) => {
const listref = isolatedAPI.binarySearch(details.hostnames, hostname);
if ( listref === -1 ) { return; }
if ( Array.isArray(details.selectorLists) === false ) {
details.selectorLists = details.selectorLists.split(';');
details.selectorListRefs = JSON.parse(`[${details.selectorListRefs}]`);
}
const ilist = details.selectorListRefs[listref];
const list = JSON.parse(`[${details.selectorLists[ilist]}]`);
for ( const iselector of list ) {
if ( iselector >= 0 ) {
selectors.add(details.selectors[iselector]);
} else {
exceptions.add(details.selectors[~iselector]);
}
}
};
const lookupAll = hostname => {
for ( const details of specificImports ) {
lookupHostname(hostname, details);
}
};
isolatedAPI.forEachHostname(lookupAll, {
hasEntities: specificImports.some(a => a.hasEntities)
});
specificImports.length = 0;
for ( const selector of exceptions ) {
selectors.delete(selector);
}
if ( selectors.size === 0 ) { return; }
const css = `${Array.from(selectors).join(',\n')}{display:none!important;}`;
self.cssAPI.insert(css);
/******************************************************************************/ /******************************************************************************/

View file

@ -74,7 +74,38 @@
} }
}; };
isolatedAPI.binarySearch = (sorted, target) => { })(self.isolatedAPI);
(api => {
if ( typeof api === 'object' ) { return; }
const cosmeticAPI = self.cosmeticAPI = {};
const sessionRead = async function(key) {
try {
const bin = await chrome.storage.session.get(key);
return bin?.[key] ?? undefined;
} catch {
}
};
const sessionWrite = function(key, data) {
try {
chrome.storage.session.set({ [key]: data });
} catch {
}
};
const localRead = async function(key) {
try {
const bin = await chrome.storage.local.get(key);
return bin?.[key] ?? undefined;
} catch {
}
};
const binarySearch = (sorted, target) => {
let l = 0, i = 0, d = 0; let l = 0, i = 0, d = 0;
let r = sorted.length; let r = sorted.length;
let candidate; let candidate;
@ -95,7 +126,79 @@
return -1; return -1;
}; };
})(self.isolatedAPI); const lookupHostname = (hostname, data) => {
const listref = binarySearch(data.hostnames, hostname);
if ( listref === -1 ) { return; }
const ilist = data.selectorListRefs[listref];
const list = JSON.parse(`[${data.selectorLists[ilist]}]`);
const { result } = data;
for ( const iselector of list ) {
if ( iselector >= 0 ) {
result.selectors.add(data.selectors[iselector]);
} else {
result.exceptions.add(data.selectors[~iselector]);
}
}
};
const selectorsFromRuleset = async (realm, rulesetId, result) => {
const data = await localRead(`css.${realm}.${rulesetId}`);
if ( typeof data !== 'object' || data === null ) { return; }
data.result = result;
self.isolatedAPI.forEachHostname(lookupHostname, data);
};
const fillCache = async function(realm, rulesetIds) {
const selectors = new Set();
const exceptions = new Set();
const result = { selectors, exceptions };
await Promise.all(rulesetIds.map(a => selectorsFromRuleset(realm, a, result)));
for ( const selector of exceptions ) {
selectors.delete(selector);
}
cacheEntry[cacheSlots[realm]] = Array.from(selectors).map(a =>
a.startsWith('{') ? JSON.parse(a) : a
);
};
const readCache = async ( ) => {
cacheEntry = await sessionRead(cacheKey) || {};
};
const cacheSlots = { 'specific': 's', 'procedural': 'p' };
const cacheKey = `cache.css.${document.location.hostname || ''}`;
let clientCount = 0;
let cacheEntry;
cosmeticAPI.getSelectors = async function(realm, rulesetIds) {
clientCount += 1;
const slot = cacheSlots[realm];
if ( cacheEntry === undefined ) {
cacheEntry = readCache();
}
if ( cacheEntry instanceof Promise ) {
await cacheEntry;
}
if ( cacheEntry[slot] === undefined ) {
cacheEntry[slot] = fillCache(realm, rulesetIds);
}
if ( cacheEntry[slot] instanceof Promise ) {
await cacheEntry[slot];
}
return cacheEntry[slot];
};
cosmeticAPI.release = function() {
clientCount -= 1;
if ( clientCount !== 0 ) { return; }
self.cosmeticAPI = undefined;
const now = Math.round(Date.now() / 15000);
const since = now - (cacheEntry.t || 0);
if ( since <= 1 ) { return; }
cacheEntry.t = now;
sessionWrite(cacheKey, cacheEntry);
};
})(self.cosmeticAPI);
/******************************************************************************/ /******************************************************************************/

View file

@ -863,38 +863,28 @@ async function processCosmeticFilters(assetDetails, realm, mapin) {
allHostnames.set(hn, allSelectorLists.get(list)); allHostnames.set(hn, allSelectorLists.get(list));
} }
// The cosmetic filters will be injected programmatically as content
// script and the decisions to activate the cosmetic filters will be
// done at injection time according to the document's hostname.
const originalScriptletMap = await loadAllSourceScriptlets();
let patchedScriptlet = originalScriptletMap.get(`css-${realm}`).replace(
'$rulesetId$',
assetDetails.id
);
patchedScriptlet = safeReplace(patchedScriptlet,
/\bself\.\$selectors\$/,
`/* ${allSelectors.size} */ ${JSON.stringify(Array.from(allSelectors.keys()))}`
);
patchedScriptlet = safeReplace(patchedScriptlet,
/\bself\.\$selectorLists\$/,
`/* ${allSelectorLists.size} */ ${JSON.stringify(Array.from(allSelectorLists.keys()).join(';'))}`
);
const sortedHostnames = Array.from(allHostnames.keys()).toSorted((a, b) => { const sortedHostnames = Array.from(allHostnames.keys()).toSorted((a, b) => {
const d = a.length - b.length; const d = a.length - b.length;
if ( d !== 0 ) { return d; } if ( d !== 0 ) { return d; }
return a < b ? -1 : 1; return a < b ? -1 : 1;
}); });
patchedScriptlet = safeReplace(patchedScriptlet,
/\bself\.\$selectorListRefs\$/, const data = JSON.stringify({
`/* ${sortedHostnames.length} */ "${JSON.stringify(sortedHostnames.map(a => allHostnames.get(a))).slice(1, -1)}"` selectors: Array.from(allSelectors.keys()),
); selectorLists: Array.from(allSelectorLists.keys()),
patchedScriptlet = safeReplace(patchedScriptlet, selectorListRefs: sortedHostnames.map(a => allHostnames.get(a)),
/\bself\.\$hostnames\$/, hostnames: sortedHostnames,
`/* ${sortedHostnames.length} */ ${JSON.stringify(sortedHostnames)}` hasEntities,
); });
patchedScriptlet = safeReplace(patchedScriptlet, writeFile(`${scriptletDir}/${realm}/${assetDetails.id}.json`, data);
'self.$hasEntities$',
JSON.stringify(hasEntities) // The cosmetic filters will be injected programmatically as content
// script and the decisions to activate the cosmetic filters will be
// done at injection time according to the document's hostname.
const originalScriptletMap = await loadAllSourceScriptlets();
let patchedScriptlet = originalScriptletMap.get(`css-${realm}`).replace(
'self.$rulesetId$',
JSON.stringify(assetDetails.id)
); );
writeFile(`${scriptletDir}/${realm}/${assetDetails.id}.js`, patchedScriptlet); writeFile(`${scriptletDir}/${realm}/${assetDetails.id}.js`, patchedScriptlet);

View file

@ -19,23 +19,16 @@
Home: https://github.com/gorhill/uBlock Home: https://github.com/gorhill/uBlock
*/ */
// ruleset: $rulesetId$
// Important! // Important!
// Isolate from global scope // Isolate from global scope
(function uBOL_cssProceduralImport() { (function uBOL_cssProceduralImport() {
/******************************************************************************/ /******************************************************************************/
const rulesetId = self.$rulesetId$;
const selectors = self.$selectors$;
const selectorLists = self.$selectorLists$;
const selectorListRefs = self.$selectorListRefs$;
const hostnames = self.$hostnames$;
const hasEntities = self.$hasEntities$;
self.proceduralImports = self.proceduralImports || []; self.proceduralImports = self.proceduralImports || [];
self.proceduralImports.push({ selectors, selectorLists, selectorListRefs, hostnames, hasEntities }); self.proceduralImports.push(rulesetId);
/******************************************************************************/ /******************************************************************************/

View file

@ -19,22 +19,16 @@
Home: https://github.com/gorhill/uBlock Home: https://github.com/gorhill/uBlock
*/ */
// ruleset: $rulesetId$
// Important! // Important!
// Isolate from global scope // Isolate from global scope
(function uBOL_cssSpecificImports() { (function uBOL_cssSpecificImports() {
/******************************************************************************/ /******************************************************************************/
const selectors = self.$selectors$; const rulesetId = self.$rulesetId$;
const selectorLists = self.$selectorLists$;
const selectorListRefs = self.$selectorListRefs$;
const hostnames = self.$hostnames$;
const hasEntities = self.$hasEntities$;
self.specificImports = self.specificImports || []; self.specificImports = self.specificImports || [];
self.specificImports.push({ selectors, selectorLists, selectorListRefs, hostnames, hasEntities }); self.specificImports.push(rulesetId);
/******************************************************************************/ /******************************************************************************/