experimental

This commit is contained in:
Raymond Hill 2025-12-05 16:37:52 -05:00
parent 87cb6eb602
commit 7414e67505
No known key found for this signature in database
GPG key ID: F5630CAE62A14316
9 changed files with 209 additions and 75 deletions

View file

@ -63,6 +63,7 @@ import {
browser,
localRead, localRemove, localWrite,
runtime,
sessionAccessLevel,
webextFlavor,
} from './ext.js';
@ -667,6 +668,8 @@ async function startSession() {
// launch time whether content css/scripts are properly registered.
registerInjectables();
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 ) {

View file

@ -100,6 +100,14 @@ export async function sessionRemove(key) {
return browser.storage.session.remove(key);
}
export async function sessionAccessLevel(level) {
try {
browser.storage.session.setAccessLevel(level);
} catch {
}
}
/******************************************************************************/
export async function adminRead(key) {

View file

@ -21,7 +21,11 @@
import * as ut from './utils.js';
import { browser, localRemove } from './ext.js';
import {
browser,
localKeys, localRemove, localWrite,
sessionRemove,
} from './ext.js';
import { ubolErr, ubolLog } from './debug.js';
import { fetchJSON } from './fetch.js';
@ -286,16 +290,25 @@ function registerGeneric(context, genericDetails) {
/******************************************************************************/
function registerProcedural(context) {
async function registerProcedural(context) {
const { before, filteringModeDetails, rulesetsDetails } = context;
const js = [];
{
const keys = await localKeys();
for ( const key of keys ) {
if ( key.startsWith('css.procedural.data.') === false ) { continue; }
sessionRemove(key);
localRemove(key);
}
}
const rulesetIds = [];
for ( const rulesetDetails of rulesetsDetails ) {
const count = rulesetDetails.css?.procedural || 0;
if ( count === 0 ) { continue; }
js.push(`/rulesets/scripting/procedural/${rulesetDetails.id}.js`);
rulesetIds.push(rulesetDetails.id);
}
if ( js.length === 0 ) { return; }
if ( rulesetIds.length === 0 ) { return; }
const { none, basic, optimal, complete } = filteringModeDetails;
const matches = [
@ -304,8 +317,21 @@ function registerProcedural(context) {
];
if ( matches.length === 0 ) { return; }
{
const promises = [];
for ( const id of rulesetIds ) {
promises.push(
fetchJSON(`/rulesets/scripting/procedural/data/${id}`).then(data => {
return localWrite(`css.procedural.data.${id}`, data);
})
);
}
await Promise.all(promises);
}
normalizeMatches(matches);
const js = rulesetIds.map(id => `/rulesets/scripting/procedural/${id}.js`);
js.unshift('/js/scripting/css-api.js', '/js/scripting/isolated-api.js');
js.push('/js/scripting/css-procedural.js');
@ -353,16 +379,25 @@ function registerProcedural(context) {
/******************************************************************************/
function registerSpecific(context) {
async function registerSpecific(context) {
const { before, filteringModeDetails, rulesetsDetails } = context;
const js = [];
{
const keys = await localKeys();
for ( const key of keys ) {
if ( key.startsWith('css.specific.data.') === false ) { continue; }
sessionRemove(key);
localRemove(key);
}
}
const rulesetIds = [];
for ( const rulesetDetails of rulesetsDetails ) {
const count = rulesetDetails.css?.specific || 0;
if ( count === 0 ) { continue; }
js.push(`/rulesets/scripting/specific/${rulesetDetails.id}.js`);
rulesetIds.push(rulesetDetails.id);
}
if ( js.length === 0 ) { return; }
if ( rulesetIds.length === 0 ) { return; }
const { none, basic, optimal, complete } = filteringModeDetails;
const matches = [
@ -371,8 +406,21 @@ function registerSpecific(context) {
];
if ( matches.length === 0 ) { return; }
{
const promises = [];
for ( const id of rulesetIds ) {
promises.push(
fetchJSON(`/rulesets/scripting/specific/data/${id}`).then(data => {
return localWrite(`css.specific.data.${id}`, data);
})
);
}
await Promise.all(promises);
}
normalizeMatches(matches);
const js = rulesetIds.map(id => `/rulesets/scripting/specific/${id}.js`);
js.unshift('/js/scripting/css-api.js', '/js/scripting/isolated-api.js');
js.push('/js/scripting/css-specific.js');
@ -533,9 +581,9 @@ async function registerInjectables() {
};
await Promise.all([
registerProcedural(context),
registerScriptlet(context, scriptletDetails),
registerSpecific(context),
registerScriptlet(context, scriptletDetails),
registerProcedural(context),
registerGeneric(context, genericDetails),
registerHighGeneric(context, genericDetails),
registerCustomFilters(context),

View file

@ -21,7 +21,7 @@
// Important!
// Isolate from global scope
(function uBOL_cssProcedural() {
(async function uBOL_cssProcedural() {
/******************************************************************************/
@ -31,25 +31,12 @@ 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]);
}
}
details.listrefs ||= [];
details.listrefs.push(listref);
};
const lookupAll = hostname => {
@ -62,6 +49,38 @@ isolatedAPI.forEachHostname(lookupAll, {
hasEntities: proceduralImports.some(a => a.hasEntities)
});
const toLookup = proceduralImports.filter(a => Array.isArray(a.listrefs));
if ( toLookup.length === 0 ) { return; }
const selectors = new Set();
const exceptions = new Set();
const lookupSelectors = async details => {
const { rulesetId } = details;
const key = `css.procedural.data.${rulesetId}`;
const data = await isolatedAPI.storageGet(key);
if ( Boolean(data) === false ) { return; }
if ( data.signature !== details.signature ) { return; }
for ( const listref of details.listrefs ) {
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 promises = [];
for ( const details of toLookup ) {
promises.push(lookupSelectors(details));
}
await Promise.all(promises);
proceduralImports.length = 0;
for ( const selector of exceptions ) {

View file

@ -21,7 +21,7 @@
// Important!
// Isolate from global scope
(function uBOL_cssSpecific() {
(async function uBOL_cssSpecific() {
/******************************************************************************/
@ -31,25 +31,12 @@ 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]);
}
}
details.listrefs ||= [];
details.listrefs.push(listref);
};
const lookupAll = hostname => {
@ -62,7 +49,37 @@ isolatedAPI.forEachHostname(lookupAll, {
hasEntities: specificImports.some(a => a.hasEntities)
});
specificImports.length = 0;
const toLookup = specificImports.filter(a => Array.isArray(a.listrefs));
if ( toLookup.length === 0 ) { return; }
const selectors = new Set();
const exceptions = new Set();
const lookupSelectors = async details => {
const { rulesetId } = details;
const key = `css.specific.data.${rulesetId}`;
const data = await isolatedAPI.storageGet(key);
if ( Boolean(data) === false ) { return; }
if ( data.signature !== details.signature ) { return; }
for ( const listref of details.listrefs ) {
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 promises = [];
for ( const details of toLookup ) {
promises.push(lookupSelectors(details));
}
await Promise.all(promises);
for ( const selector of exceptions ) {
selectors.delete(selector);

View file

@ -95,6 +95,47 @@
return -1;
};
const sessionGet = async function(key) {
let data;
try {
const bin = await chrome.storage.session.get(key);
data = bin?.[key] ?? undefined;
} catch (error) {
console.trace(error);
}
return data;
};
const sessionSet = function(key, data) {
try {
chrome.storage.session.set({ [key]: data });
} catch (error) {
console.trace(error);
}
};
const localGet = async function(key) {
let data;
try {
const bin = await chrome.storage.local.get(key);
data = bin?.[key] ?? undefined;
} catch (error) {
console.trace(error);
}
return data;
};
isolatedAPI.storageGet = async function(key) {
let data = await sessionGet(key);
if ( data === undefined ) {
data = await localGet(key);
if ( data !== undefined ) {
sessionSet(key, data);
}
}
return data;
};
})(self.isolatedAPI);
/******************************************************************************/

View file

@ -863,30 +863,31 @@ 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;
});
const data = JSON.stringify({
signature: secret,
selectors: Array.from(allSelectors.keys()),
selectorLists: Array.from(allSelectorLists.keys()),
selectorListRefs: sortedHostnames.map(a => allHostnames.get(a)),
});
writeFile(`${scriptletDir}/${realm}/data/${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)
);
patchedScriptlet = safeReplace(patchedScriptlet,
/\bself\.\$selectorListRefs\$/,
`/* ${sortedHostnames.length} */ "${JSON.stringify(sortedHostnames.map(a => allHostnames.get(a))).slice(1, -1)}"`
'self.$signature$',
JSON.stringify(secret)
);
patchedScriptlet = safeReplace(patchedScriptlet,
/\bself\.\$hostnames\$/,

View file

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

View file

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