This commit is contained in:
Raymond Hill 2025-12-07 09:14:48 -05:00
parent 66a1c5347b
commit d4743cf570
No known key found for this signature in database
GPG key ID: F5630CAE62A14316
10 changed files with 163 additions and 92 deletions

View file

@ -19,6 +19,8 @@
Home: https://github.com/gorhill/uBlock
*/
import * as scrmgr from './scripting-manager.js';
import {
MODE_BASIC,
MODE_OPTIMAL,
@ -100,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;
@ -710,6 +711,8 @@ async function start() {
if ( process.wakeupRun === false ) {
await startSession();
} else {
scrmgr.onWakeupRun();
}
toggleDeveloperMode(rulesetConfig.developerMode);

View file

@ -100,6 +100,16 @@ 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);

View file

@ -24,7 +24,7 @@ import * as ut from './utils.js';
import {
browser,
localKeys, localRemove, localWrite,
sessionRemove,
sessionKeys, sessionRead, sessionRemove, sessionWrite,
} from './ext.js';
import { ubolErr, ubolLog } from './debug.js';
@ -94,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;
@ -297,7 +306,6 @@ async function registerProcedural(context) {
const keys = await localKeys();
for ( const key of keys ) {
if ( key.startsWith('css.procedural.') === false ) { continue; }
sessionRemove(key);
localRemove(key);
}
}
@ -322,7 +330,7 @@ async function registerProcedural(context) {
for ( const id of rulesetIds ) {
promises.push(
fetchJSON(`/rulesets/scripting/procedural/${id}`).then(data => {
return localWrite(`css.procedural.json.${id}`, data);
return localWrite(`css.procedural.${id}`, data);
})
);
}
@ -386,7 +394,6 @@ async function registerSpecific(context) {
const keys = await localKeys();
for ( const key of keys ) {
if ( key.startsWith('css.specific.') === false ) { continue; }
sessionRemove(key);
localRemove(key);
}
}
@ -411,7 +418,7 @@ async function registerSpecific(context) {
for ( const id of rulesetIds ) {
promises.push(
fetchJSON(`/rulesets/scripting/specific/${id}`).then(data => {
return localWrite(`css.specific.json.${id}`, data);
return localWrite(`css.specific.${id}`, data);
})
);
}
@ -547,7 +554,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; }
@ -612,6 +619,8 @@ async function registerInjectables() {
}
}
await resetCSSCache();
registerInjectables.barrier = false;
return true;
@ -619,6 +628,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)
}
/******************************************************************************/

View file

@ -30,18 +30,8 @@ self.proceduralImports = undefined;
/******************************************************************************/
const isolatedAPI = self.isolatedAPI;
const selectors = [];
const cachedCSS = await isolatedAPI.sessionGet('css.procedural.cache') || {};
if ( cachedCSS[isolatedAPI.docHostname] ) {
selectors.push(...cachedCSS[isolatedAPI.docHostname]);
} else {
selectors.push(...await isolatedAPI.getSelectors('procedural', proceduralImports));
cachedCSS[isolatedAPI.docHostname] = selectors;
isolatedAPI.sessionSet('css.procedural.cache', cachedCSS);
}
const selectors = await self.cosmeticAPI.getSelectors('procedural', proceduralImports);
self.cosmeticAPI.release();
if ( selectors.length === 0 ) { return; }
proceduralImports.length = 0;

View file

@ -30,24 +30,10 @@ self.specificImports = undefined;
/******************************************************************************/
const isolatedAPI = self.isolatedAPI;
const selectors = [];
self.cssAPI.update('*{visibility:hidden!important;}');
const cachedCSS = await isolatedAPI.sessionGet('css.specific.cache') || {};
if ( cachedCSS[isolatedAPI.docHostname] ) {
selectors.push(...cachedCSS[isolatedAPI.docHostname]);
} else {
selectors.push(...await isolatedAPI.getSelectors('specific', specificImports));
cachedCSS[isolatedAPI.docHostname] = selectors;
isolatedAPI.sessionSet('css.specific.cache', cachedCSS);
}
const insert = selectors.length !== 0
? `${Array.from(selectors).join(',\n')}{display:none!important;}`
: undefined;
self.cssAPI.update(insert, '*{visibility:hidden!important;}');
const selectors = await self.cosmeticAPI.getSelectors('specific', specificImports);
self.cosmeticAPI.release();
if ( selectors.length === 0 ) { return; }
self.cssAPI.update(`${selectors.join(',\n')}{display:none!important;}`);
/******************************************************************************/

View file

@ -29,7 +29,6 @@
const hostnameStack = (( ) => {
const docloc = document.location;
isolatedAPI.docHostname = docloc.hostname;
const origins = [ docloc.origin ];
if ( docloc.ancestorOrigins ) {
origins.push(...docloc.ancestorOrigins);
@ -75,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;
@ -96,57 +126,79 @@
return -1;
};
isolatedAPI.sessionGet = async function(key) {
try {
const bin = await chrome.storage.session.get(key);
return bin?.[key] ?? undefined;
} catch {
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]);
}
}
};
isolatedAPI.sessionSet = function(key, data) {
try {
chrome.storage.session.set({ [key]: data });
} catch {
}
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);
};
isolatedAPI.localGet = async function(key) {
try {
const bin = await chrome.storage.local.get(key);
return bin?.[key] ?? undefined;
} catch {
}
};
isolatedAPI.getSelectors = async function(realm, details) {
const fillCache = async function(realm, rulesetIds) {
const selectors = new Set();
const exceptions = new Set();
const lookupHostname = (hostname, data) => {
const listref = isolatedAPI.binarySearch(data.hostnames, hostname);
if ( listref === -1 ) { return; }
const ilist = data.selectorListRefs[listref];
const list = JSON.parse(`[${data.selectorLists[ilist]}]`);
for ( const iselector of list ) {
if ( iselector >= 0 ) {
selectors.add(data.selectors[iselector]);
} else {
exceptions.add(data.selectors[~iselector]);
}
}
};
const selectorsFromRuleset = async rulesetId => {
const data = await isolatedAPI.localGet(`css.${realm}.json.${rulesetId}`);
isolatedAPI.forEachHostname(lookupHostname, data);
};
await Promise.all(details.map(a => selectorsFromRuleset(a.rulesetId)));
const result = { selectors, exceptions };
await Promise.all(rulesetIds.map(a => selectorsFromRuleset(realm, a, result)));
for ( const selector of exceptions ) {
selectors.delete(selector);
}
return Array.from(selectors);
cacheEntry[cacheSlots[realm]] = Array.from(selectors).map(a =>
a.startsWith('{') ? JSON.parse(a) : a
);
};
})(self.isolatedAPI);
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

@ -25,20 +25,22 @@
const inserted = new Set();
self.cssAPI = {
insert(css) {
update(insert, remove) {
chrome.runtime.sendMessage({
what: 'insertCSS',
css,
what: 'updateCSS',
insert,
remove,
}).catch(( ) => {
});
inserted.add(css);
inserted.add(insert);
inserted.delete(remove);
},
};
self.addEventListener('pageshow', ( ) => {
chrome.runtime.sendMessage({
what: 'insertCSS',
css: Array.from(inserted).join('\n'),
what: 'updateCSS',
insert: Array.from(inserted).join('\n'),
}).catch(( ) => {
});
});

View file

@ -43,8 +43,8 @@ if ( details?.plainSelectors?.length ) {
const selectors = details.plainSelectors;
self.addEventListener('pageshow', ( ) => {
chrome.runtime.sendMessage({
what: 'insertCSS',
css: `${selectors.join(',\n')}{display:none!important;}`,
what: 'updateCSS',
insert: `${selectors.join(',\n')}{display:none!important;}`,
}).catch(( ) => {
});
});

View file

@ -30,7 +30,7 @@ if ( Boolean(chrome?.storage?.local) === false ) { return; }
const rulesetId = self.$rulesetId$;
self.proceduralImports = self.proceduralImports || [];
self.proceduralImports.push({ rulesetId });
self.proceduralImports.push(rulesetId);
/******************************************************************************/

View file

@ -30,7 +30,7 @@ if ( Boolean(chrome?.storage?.local) === false ) { return; }
const rulesetId = self.$rulesetId$;
self.specificImports = self.specificImports || [];
self.specificImports.push({ rulesetId });
self.specificImports.push(rulesetId);
/******************************************************************************/