This commit is contained in:
Raymond Hill 2025-12-06 10:10:45 -05:00
parent 7414e67505
commit 66a1c5347b
No known key found for this signature in database
GPG key ID: F5630CAE62A14316
14 changed files with 105 additions and 194 deletions

View file

@ -224,29 +224,28 @@ function onMessage(request, sender, callback) {
switch ( request.what ) {
case 'insertCSS': {
case 'updateCSS': {
if ( frameId === false ) { return false; }
// https://bugs.webkit.org/show_bug.cgi?id=262491
if ( frameId !== 0 && webextFlavor === 'safari' ) { return false; }
browser.scripting.insertCSS({
css: request.css,
origin: 'USER',
target: { tabId, frameIds: [ frameId ] },
}).catch(reason => {
ubolErr(`insertCSS/${reason}`);
});
return false;
}
case 'removeCSS': {
if ( frameId === false ) { return false; }
browser.scripting.removeCSS({
css: request.css,
origin: 'USER',
target: { tabId, frameIds: [ frameId ] },
}).catch(reason => {
ubolErr(`removeCSS/${reason}`);
});
if ( request.insert ) {
browser.scripting.insertCSS({
css: request.insert,
origin: 'USER',
target: { tabId, frameIds: [ frameId ] },
}).catch(reason => {
ubolErr(`insertCSS/${reason}`);
});
}
if ( request.remove ) {
browser.scripting.removeCSS({
css: request.remove,
origin: 'USER',
target: { tabId, frameIds: [ frameId ] },
}).catch(reason => {
ubolErr(`removeCSS/${reason}`);
});
}
return false;
}
@ -668,6 +667,8 @@ 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

View file

@ -296,7 +296,7 @@ async function registerProcedural(context) {
{
const keys = await localKeys();
for ( const key of keys ) {
if ( key.startsWith('css.procedural.data.') === false ) { continue; }
if ( key.startsWith('css.procedural.') === false ) { continue; }
sessionRemove(key);
localRemove(key);
}
@ -321,8 +321,8 @@ async function registerProcedural(context) {
const promises = [];
for ( const id of rulesetIds ) {
promises.push(
fetchJSON(`/rulesets/scripting/procedural/data/${id}`).then(data => {
return localWrite(`css.procedural.data.${id}`, data);
fetchJSON(`/rulesets/scripting/procedural/${id}`).then(data => {
return localWrite(`css.procedural.json.${id}`, data);
})
);
}
@ -385,7 +385,7 @@ async function registerSpecific(context) {
{
const keys = await localKeys();
for ( const key of keys ) {
if ( key.startsWith('css.specific.data.') === false ) { continue; }
if ( key.startsWith('css.specific.') === false ) { continue; }
sessionRemove(key);
localRemove(key);
}
@ -410,8 +410,8 @@ async function registerSpecific(context) {
const promises = [];
for ( const id of rulesetIds ) {
promises.push(
fetchJSON(`/rulesets/scripting/specific/data/${id}`).then(data => {
return localWrite(`css.specific.data.${id}`, data);
fetchJSON(`/rulesets/scripting/specific/${id}`).then(data => {
return localWrite(`css.specific.json.${id}`, data);
})
);
}

View file

@ -22,10 +22,11 @@
(api => {
if ( typeof api === 'object' ) { return; }
self.cssAPI = {
insert(css) {
update(insert, remove) {
chrome.runtime.sendMessage({
what: 'insertCSS',
css,
what: 'updateCSS',
insert,
remove
}).catch(( ) => {
});
},

View file

@ -188,7 +188,7 @@ const uBOL_processNodes = ( ) => {
if ( styleSheetTimer !== undefined ) { return; }
styleSheetTimer = self.requestAnimationFrame(( ) => {
styleSheetTimer = undefined;
self.cssAPI.insert(`${styleSheetSelectors.join(',')}{display:none!important;}`);
self.cssAPI.update(`${styleSheetSelectors.join(',')}{display:none!important;}`);
styleSheetSelectors.length = 0;
});
};

View file

@ -608,9 +608,11 @@ class ProceduralFilterer {
for ( const elem of this.styledNodes ) {
elem.removeAttribute(token);
}
const css = `[${token}]\n{${style}}\n`;
promises.push(
chrome.runtime.sendMessage({ what: 'removeCSS', css }).catch(( ) => { })
chrome.runtime.sendMessage({
what: 'updateCSS',
remove: `[${token}]\n{${style}}\n`,
}).catch(( ) => { })
);
}
this.styleTokenMap.clear();
@ -680,7 +682,7 @@ class ProceduralFilterer {
if ( styleToken !== undefined ) { return styleToken; }
styleToken = randomToken();
this.styleTokenMap.set(style, styleToken);
self.cssAPI.insert(`[${styleToken}]\n{${style}}\n`);
self.cssAPI.update(`[${styleToken}]\n{${style}}\n`);
return styleToken;
}

View file

@ -32,66 +32,21 @@ self.proceduralImports = undefined;
const isolatedAPI = self.isolatedAPI;
const lookupHostname = (hostname, details) => {
const listref = isolatedAPI.binarySearch(details.hostnames, hostname);
if ( listref === -1 ) { return; }
details.listrefs ||= [];
details.listrefs.push(listref);
};
const selectors = [];
const lookupAll = hostname => {
for ( const details of proceduralImports ) {
lookupHostname(hostname, details);
}
};
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));
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);
}
await Promise.all(promises);
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;
@ -127,11 +82,11 @@ if ( declaratives.length !== 0 ) {
sheetText.push(ruleText);
}
if ( sheetText.length !== 0 ) {
self.cssAPI.insert(sheetText.join('\n'));
self.cssAPI.update(sheetText.join('\n'));
}
}
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; }

View file

@ -32,63 +32,22 @@ self.specificImports = undefined;
const isolatedAPI = self.isolatedAPI;
const lookupHostname = (hostname, details) => {
const listref = isolatedAPI.binarySearch(details.hostnames, hostname);
if ( listref === -1 ) { return; }
details.listrefs ||= [];
details.listrefs.push(listref);
};
const selectors = [];
const lookupAll = hostname => {
for ( const details of specificImports ) {
lookupHostname(hostname, details);
}
};
self.cssAPI.update('*{visibility:hidden!important;}');
isolatedAPI.forEachHostname(lookupAll, {
hasEntities: specificImports.some(a => a.hasEntities)
});
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));
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);
}
await Promise.all(promises);
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 insert = selectors.length !== 0
? `${Array.from(selectors).join(',\n')}{display:none!important;}`
: undefined;
self.cssAPI.update(insert, '*{visibility:hidden!important;}');
/******************************************************************************/

View file

@ -26,8 +26,8 @@
const plainSelectors = self.customFilters?.plainSelectors;
if ( plainSelectors ) {
chrome.runtime.sendMessage({
what: 'removeCSS',
css: `${plainSelectors.join(',\n')}{display:none!important;}`,
what: 'updateCSS',
remove: `${plainSelectors.join(',\n')}{display:none!important;}`,
}).catch(( ) => {
});
}

View file

@ -29,6 +29,7 @@
const hostnameStack = (( ) => {
const docloc = document.location;
isolatedAPI.docHostname = docloc.hostname;
const origins = [ docloc.origin ];
if ( docloc.ancestorOrigins ) {
origins.push(...docloc.ancestorOrigins);
@ -95,45 +96,54 @@
return -1;
};
const sessionGet = async function(key) {
let data;
isolatedAPI.sessionGet = async function(key) {
try {
const bin = await chrome.storage.session.get(key);
data = bin?.[key] ?? undefined;
} catch (error) {
console.trace(error);
return bin?.[key] ?? undefined;
} catch {
}
return data;
};
const sessionSet = function(key, data) {
isolatedAPI.sessionSet = function(key, data) {
try {
chrome.storage.session.set({ [key]: data });
} catch (error) {
console.trace(error);
} catch {
}
};
const localGet = async function(key) {
let data;
isolatedAPI.localGet = async function(key) {
try {
const bin = await chrome.storage.local.get(key);
data = bin?.[key] ?? undefined;
} catch (error) {
console.trace(error);
return bin?.[key] ?? undefined;
} catch {
}
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);
isolatedAPI.getSelectors = async function(realm, details) {
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)));
for ( const selector of exceptions ) {
selectors.delete(selector);
}
return data;
return Array.from(selectors);
};
})(self.isolatedAPI);

View file

@ -246,7 +246,7 @@ async function previewSelector(selector) {
}
}
if ( previewedCSS !== '' ) {
await ubolOverlay.sendMessage({ what: 'removeCSS', css: previewedCSS });
await ubolOverlay.sendMessage({ what: 'updateCSS', remove: previewedCSS });
previewedCSS = '';
}
}
@ -261,7 +261,7 @@ async function previewSelector(selector) {
return;
}
previewedCSS = `${selector}{display:none!important;}`;
await ubolOverlay.sendMessage({ what: 'insertCSS', css: previewedCSS });
await ubolOverlay.sendMessage({ what: 'updateCSS', insert: previewedCSS });
}
let previewedSelector = '';

View file

@ -78,7 +78,7 @@ self.ubolOverlay = {
`:root > [${this.secretAttr}-loaded] { visibility: visible !important; }`,
`:root > [${this.secretAttr}-click] { pointer-events: none !important; }`,
].join('\n');
this.sendMessage({ what: 'insertCSS', css: this.pickerCSS });
this.sendMessage({ what: 'updateCSS', insert: this.pickerCSS });
self.addEventListener('scroll', this.onViewportChanged, { passive: true });
self.addEventListener('resize', this.onViewportChanged, { passive: true });
self.addEventListener('keydown', this.onKeyPressed, true);
@ -86,7 +86,7 @@ self.ubolOverlay = {
stop() {
if ( this.pickerCSS ) {
this.sendMessage({ what: 'removeCSS', css: this.pickerCSS });
this.sendMessage({ what: 'updateCSS', remove: this.pickerCSS });
this.pickerCSS = undefined;
}
self.removeEventListener('scroll', this.onViewportChanged, { passive: true });

View file

@ -870,12 +870,13 @@ async function processCosmeticFilters(assetDetails, realm, mapin) {
});
const data = JSON.stringify({
signature: secret,
selectors: Array.from(allSelectors.keys()),
selectorLists: Array.from(allSelectorLists.keys()),
selectorListRefs: sortedHostnames.map(a => allHostnames.get(a)),
hostnames: sortedHostnames,
hasEntities,
});
writeFile(`${scriptletDir}/${realm}/data/${assetDetails.id}.json`, data);
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
@ -885,18 +886,6 @@ async function processCosmeticFilters(assetDetails, realm, mapin) {
'self.$rulesetId$',
JSON.stringify(assetDetails.id)
);
patchedScriptlet = safeReplace(patchedScriptlet,
'self.$signature$',
JSON.stringify(secret)
);
patchedScriptlet = safeReplace(patchedScriptlet,
/\bself\.\$hostnames\$/,
`/* ${sortedHostnames.length} */ ${JSON.stringify(sortedHostnames)}`
);
patchedScriptlet = safeReplace(patchedScriptlet,
'self.$hasEntities$',
JSON.stringify(hasEntities)
);
writeFile(`${scriptletDir}/${realm}/${assetDetails.id}.js`, patchedScriptlet);
log(`CSS-${realm}: ${allSelectors.size} distinct filters for ${allHostnames.size} distinct hostnames`);

View file

@ -28,12 +28,9 @@ if ( Boolean(chrome?.storage?.local) === false ) { return; }
/******************************************************************************/
const rulesetId = self.$rulesetId$;
const signature = self.$signature$;
const hostnames = self.$hostnames$;
const hasEntities = self.$hasEntities$;
self.proceduralImports = self.proceduralImports || [];
self.proceduralImports.push({ rulesetId, signature, hostnames, hasEntities });
self.proceduralImports.push({ rulesetId });
/******************************************************************************/

View file

@ -28,12 +28,9 @@ if ( Boolean(chrome?.storage?.local) === false ) { return; }
/******************************************************************************/
const rulesetId = self.$rulesetId$;
const signature = self.$signature$;
const hostnames = self.$hostnames$;
const hasEntities = self.$hasEntities$;
self.specificImports = self.specificImports || [];
self.specificImports.push({ rulesetId, signature, hostnames, hasEntities });
self.specificImports.push({ rulesetId });
/******************************************************************************/