mirror of
https://github.com/gorhill/uBlock.git
synced 2026-03-11 09:04:36 +00:00
[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:
parent
e036156f3a
commit
0fb845d1ac
9 changed files with 244 additions and 220 deletions
|
|
@ -19,6 +19,8 @@
|
|||
Home: https://github.com/gorhill/uBlock
|
||||
*/
|
||||
|
||||
import * as scrmgr from './scripting-manager.js';
|
||||
|
||||
import {
|
||||
MODE_BASIC,
|
||||
MODE_OPTIMAL,
|
||||
|
|
@ -63,6 +65,7 @@ import {
|
|||
browser,
|
||||
localRead, localRemove, localWrite,
|
||||
runtime,
|
||||
sessionAccessLevel,
|
||||
webextFlavor,
|
||||
} from './ext.js';
|
||||
|
||||
|
|
@ -99,14 +102,13 @@ import {
|
|||
} from './debug.js';
|
||||
|
||||
import { dnr } from './ext-compat.js';
|
||||
import { registerInjectables } from './scripting-manager.js';
|
||||
import { toggleToolbarIcon } from './action.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const UBOL_ORIGIN = runtime.getURL('').replace(/\/$/, '').toLowerCase();
|
||||
|
||||
const canShowBlockedCount = typeof dnr.setExtensionActionOptions === 'function';
|
||||
const { registerInjectables } = scrmgr;
|
||||
|
||||
let pendingPermissionRequest;
|
||||
|
||||
|
|
@ -223,7 +225,7 @@ function onMessage(request, sender, callback) {
|
|||
|
||||
switch ( request.what ) {
|
||||
|
||||
case 'insertCSS': {
|
||||
case 'insertCSS':
|
||||
if ( frameId === false ) { return false; }
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=262491
|
||||
if ( frameId !== 0 && webextFlavor === 'safari' ) { return false; }
|
||||
|
|
@ -235,10 +237,11 @@ function onMessage(request, sender, callback) {
|
|||
ubolErr(`insertCSS/${reason}`);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
case 'removeCSS': {
|
||||
case 'removeCSS':
|
||||
if ( frameId === false ) { return false; }
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=262491
|
||||
if ( frameId !== 0 && webextFlavor === 'safari' ) { return false; }
|
||||
browser.scripting.removeCSS({
|
||||
css: request.css,
|
||||
origin: 'USER',
|
||||
|
|
@ -247,7 +250,6 @@ function onMessage(request, sender, callback) {
|
|||
ubolErr(`removeCSS/${reason}`);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
case 'toggleToolbarIcon': {
|
||||
if ( tabId ) {
|
||||
|
|
@ -667,6 +669,10 @@ async function startSession() {
|
|||
// launch time whether content css/scripts are properly registered.
|
||||
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
|
||||
// Firefox API does not support `dnr.setExtensionActionOptions`
|
||||
if ( canShowBlockedCount ) {
|
||||
|
|
@ -706,6 +712,8 @@ async function start() {
|
|||
|
||||
if ( process.wakeupRun === false ) {
|
||||
await startSession();
|
||||
} else {
|
||||
scrmgr.onWakeupRun();
|
||||
}
|
||||
|
||||
toggleDeveloperMode(rulesetConfig.developerMode);
|
||||
|
|
|
|||
|
|
@ -100,6 +100,24 @@ export async function sessionRemove(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) {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,11 @@
|
|||
|
||||
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 { 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) {
|
||||
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 js = [];
|
||||
for ( const rulesetDetails of rulesetsDetails ) {
|
||||
const count = rulesetDetails.css?.procedural || 0;
|
||||
if ( count === 0 ) { continue; }
|
||||
js.push(`/rulesets/scripting/procedural/${rulesetDetails.id}.js`);
|
||||
{
|
||||
const keys = await localKeys();
|
||||
for ( const key of keys ) {
|
||||
if ( key.startsWith(`css.${realm}.`) === false ) { continue; }
|
||||
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 matches = [
|
||||
|
|
@ -304,10 +325,24 @@ function registerProcedural(context) {
|
|||
];
|
||||
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);
|
||||
|
||||
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.push('/js/scripting/css-procedural.js');
|
||||
js.push(`/js/scripting/${realmid}.js`);
|
||||
|
||||
const excludeMatches = [];
|
||||
if ( none.has('all-urls') === false && basic.has('all-urls') === false ) {
|
||||
|
|
@ -320,11 +355,11 @@ function registerProcedural(context) {
|
|||
}
|
||||
}
|
||||
|
||||
const registered = before.get('css-procedural');
|
||||
before.delete('css-procedural'); // Important!
|
||||
const registered = before.get(realmid);
|
||||
before.delete(realmid); // Important!
|
||||
|
||||
const directive = {
|
||||
id: 'css-procedural',
|
||||
id: realmid,
|
||||
js,
|
||||
matches,
|
||||
allFrames: true,
|
||||
|
|
@ -346,71 +381,7 @@ function registerProcedural(context) {
|
|||
ut.strArrayEq(registered.matches, matches) === false ||
|
||||
ut.strArrayEq(registered.excludeMatches, excludeMatches) === false
|
||||
) {
|
||||
context.toRemove.push('css-procedural');
|
||||
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.toRemove.push(realmid);
|
||||
context.toAdd.push(directive);
|
||||
}
|
||||
}
|
||||
|
|
@ -499,7 +470,7 @@ function registerScriptlet(context, scriptletDetails) {
|
|||
// Issue: Safari appears to completely ignore excludeMatches
|
||||
// https://github.com/radiolondra/ExcludeMatches-Test
|
||||
|
||||
async function registerInjectables() {
|
||||
export async function registerInjectables() {
|
||||
if ( browser.scripting === undefined ) { return false; }
|
||||
|
||||
if ( registerInjectables.barrier ) { return true; }
|
||||
|
|
@ -533,9 +504,9 @@ async function registerInjectables() {
|
|||
};
|
||||
|
||||
await Promise.all([
|
||||
registerProcedural(context),
|
||||
registerScriptlet(context, scriptletDetails),
|
||||
registerSpecific(context),
|
||||
registerCosmetic('specific', context),
|
||||
registerCosmetic('procedural', context),
|
||||
registerGeneric(context, genericDetails),
|
||||
registerHighGeneric(context, genericDetails),
|
||||
registerCustomFilters(context),
|
||||
|
|
@ -564,6 +535,8 @@ async function registerInjectables() {
|
|||
}
|
||||
}
|
||||
|
||||
await resetCSSCache();
|
||||
|
||||
registerInjectables.barrier = false;
|
||||
|
||||
return true;
|
||||
|
|
@ -571,6 +544,25 @@ async function registerInjectables() {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
export {
|
||||
registerInjectables
|
||||
};
|
||||
export async function onWakeupRun() {
|
||||
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)
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
// Important!
|
||||
// Isolate from global scope
|
||||
(function uBOL_cssProcedural() {
|
||||
(async function uBOL_cssProcedural() {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
|
@ -30,49 +30,13 @@ self.proceduralImports = undefined;
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
const isolatedAPI = self.isolatedAPI;
|
||||
const selectors = new Set();
|
||||
const exceptions = new Set();
|
||||
|
||||
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)
|
||||
});
|
||||
const selectors = await self.cosmeticAPI.getSelectors('procedural', proceduralImports);
|
||||
self.cosmeticAPI.release();
|
||||
if ( selectors.length === 0 ) { return; }
|
||||
|
||||
proceduralImports.length = 0;
|
||||
|
||||
for ( const selector of exceptions ) {
|
||||
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);
|
||||
const declaratives = selectors.filter(a => a.cssable);
|
||||
if ( declaratives.length !== 0 ) {
|
||||
const cssRuleFromProcedural = 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 ) {
|
||||
const addSelectors = selectors => {
|
||||
if ( self.listsProceduralFiltererAPI instanceof Object === false ) { return; }
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
// Important!
|
||||
// Isolate from global scope
|
||||
(function uBOL_cssSpecific() {
|
||||
(async function uBOL_cssSpecific() {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
|
@ -30,48 +30,10 @@ self.specificImports = undefined;
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
const isolatedAPI = self.isolatedAPI;
|
||||
const selectors = new Set();
|
||||
const exceptions = new Set();
|
||||
|
||||
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);
|
||||
const selectors = await self.cosmeticAPI.getSelectors('specific', specificImports);
|
||||
self.cosmeticAPI.release();
|
||||
if ( selectors.length === 0 ) { return; }
|
||||
self.cssAPI.insert(`${selectors.join(',\n')}{display:none!important;}`);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
|
|
|||
|
|
@ -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 r = sorted.length;
|
||||
let candidate;
|
||||
|
|
@ -95,7 +126,79 @@
|
|||
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);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
|
|
|||
|
|
@ -863,38 +863,28 @@ async function processCosmeticFilters(assetDetails, realm, mapin) {
|
|||
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 d = a.length - b.length;
|
||||
if ( d !== 0 ) { return d; }
|
||||
return a < b ? -1 : 1;
|
||||
});
|
||||
patchedScriptlet = safeReplace(patchedScriptlet,
|
||||
/\bself\.\$selectorListRefs\$/,
|
||||
`/* ${sortedHostnames.length} */ "${JSON.stringify(sortedHostnames.map(a => allHostnames.get(a))).slice(1, -1)}"`
|
||||
);
|
||||
patchedScriptlet = safeReplace(patchedScriptlet,
|
||||
/\bself\.\$hostnames\$/,
|
||||
`/* ${sortedHostnames.length} */ ${JSON.stringify(sortedHostnames)}`
|
||||
);
|
||||
patchedScriptlet = safeReplace(patchedScriptlet,
|
||||
'self.$hasEntities$',
|
||||
JSON.stringify(hasEntities)
|
||||
|
||||
const data = JSON.stringify({
|
||||
selectors: Array.from(allSelectors.keys()),
|
||||
selectorLists: Array.from(allSelectorLists.keys()),
|
||||
selectorListRefs: sortedHostnames.map(a => allHostnames.get(a)),
|
||||
hostnames: sortedHostnames,
|
||||
hasEntities,
|
||||
});
|
||||
writeFile(`${scriptletDir}/${realm}/${assetDetails.id}.json`, data);
|
||||
|
||||
// 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);
|
||||
|
||||
|
|
|
|||
|
|
@ -19,23 +19,16 @@
|
|||
Home: https://github.com/gorhill/uBlock
|
||||
*/
|
||||
|
||||
// ruleset: $rulesetId$
|
||||
|
||||
// Important!
|
||||
// Isolate from global scope
|
||||
(function uBOL_cssProceduralImport() {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
||||
const selectors = self.$selectors$;
|
||||
const selectorLists = self.$selectorLists$;
|
||||
const selectorListRefs = self.$selectorListRefs$;
|
||||
const hostnames = self.$hostnames$;
|
||||
const hasEntities = self.$hasEntities$;
|
||||
const rulesetId = self.$rulesetId$;
|
||||
|
||||
self.proceduralImports = self.proceduralImports || [];
|
||||
self.proceduralImports.push({ selectors, selectorLists, selectorListRefs, hostnames, hasEntities });
|
||||
self.proceduralImports.push(rulesetId);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
|
|
|||
|
|
@ -19,22 +19,16 @@
|
|||
Home: https://github.com/gorhill/uBlock
|
||||
*/
|
||||
|
||||
// ruleset: $rulesetId$
|
||||
|
||||
// Important!
|
||||
// Isolate from global scope
|
||||
(function uBOL_cssSpecificImports() {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const selectors = self.$selectors$;
|
||||
const selectorLists = self.$selectorLists$;
|
||||
const selectorListRefs = self.$selectorListRefs$;
|
||||
const hostnames = self.$hostnames$;
|
||||
const hasEntities = self.$hasEntities$;
|
||||
const rulesetId = self.$rulesetId$;
|
||||
|
||||
self.specificImports = self.specificImports || [];
|
||||
self.specificImports.push({ selectors, selectorLists, selectorListRefs, hostnames, hasEntities });
|
||||
self.specificImports.push(rulesetId);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue