mirror of
https://github.com/gorhill/uBlock.git
synced 2026-03-11 09:04:36 +00:00
[mv3] Add support for procedural cosmetic filters
Related issue: https://github.com/uBlockOrigin/uBOL-home/issues/325
This commit is contained in:
parent
e713e133eb
commit
32bf5ebde3
16 changed files with 418 additions and 217 deletions
|
|
@ -37,7 +37,8 @@ import {
|
|||
injectCustomFilters,
|
||||
removeCustomFilter,
|
||||
selectorsFromCustomFilters,
|
||||
uninjectCustomFilters,
|
||||
startCustomFilters,
|
||||
terminateCustomFilters,
|
||||
} from './filter-manager.js';
|
||||
|
||||
import {
|
||||
|
|
@ -199,6 +200,20 @@ function onMessage(request, sender, callback) {
|
|||
return false;
|
||||
}
|
||||
|
||||
case 'startCustomFilters':
|
||||
if ( frameId === false ) { return false; }
|
||||
startCustomFilters(tabId, frameId).then(( ) => {
|
||||
callback();
|
||||
});
|
||||
return true;
|
||||
|
||||
case 'terminateCustomFilters':
|
||||
if ( frameId === false ) { return false; }
|
||||
terminateCustomFilters(tabId, frameId).then(( ) => {
|
||||
callback();
|
||||
});
|
||||
return true;
|
||||
|
||||
case 'injectCustomFilters':
|
||||
if ( frameId === false ) { return false; }
|
||||
injectCustomFilters(tabId, frameId, request.hostname).then(selectors => {
|
||||
|
|
@ -206,17 +221,11 @@ function onMessage(request, sender, callback) {
|
|||
});
|
||||
return true;
|
||||
|
||||
case 'uninjectCustomFilters':
|
||||
if ( frameId === false ) { return false; }
|
||||
uninjectCustomFilters(tabId, frameId, request.hostname).then(( ) => {
|
||||
callback();
|
||||
});
|
||||
return true;
|
||||
|
||||
case 'injectCSSProceduralAPI':
|
||||
browser.scripting.executeScript({
|
||||
files: [ '/js/scripting/css-procedural-api.js' ],
|
||||
target: { tabId, frameIds: [ frameId ] },
|
||||
injectImmediately: true,
|
||||
}).catch(reason => {
|
||||
console.log(reason);
|
||||
}).then(( ) => {
|
||||
|
|
@ -503,7 +512,11 @@ function onCommand(command, tab) {
|
|||
case 'enter-picker-mode': {
|
||||
if ( browser.scripting === undefined ) { return; }
|
||||
browser.scripting.executeScript({
|
||||
files: [ '/js/scripting/tool-overlay.js', '/js/scripting/picker.js' ],
|
||||
files: [
|
||||
'/js/scripting/css-procedural-api.js',
|
||||
'/js/scripting/tool-overlay.js',
|
||||
'/js/scripting/picker.js',
|
||||
],
|
||||
target: { tabId: tab.id },
|
||||
});
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -50,7 +50,9 @@ export async function selectorsFromCustomFilters(hostname) {
|
|||
for ( let i = 0; i < promises.length; i++ ) {
|
||||
const selectors = results[i];
|
||||
if ( selectors === undefined ) { continue; }
|
||||
selectors.forEach(selector => { out.push(selector.slice(1)); });
|
||||
selectors.forEach(selector => {
|
||||
out.push(selector.startsWith('0') ? selector.slice(1) : selector);
|
||||
});
|
||||
}
|
||||
return out.sort();
|
||||
}
|
||||
|
|
@ -64,38 +66,71 @@ export async function hasCustomFilters(hostname) {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
export async function injectCustomFilters(tabId, frameId, hostname) {
|
||||
const selectors = await selectorsFromCustomFilters(hostname);
|
||||
if ( selectors.length === 0 ) { return; }
|
||||
await browser.scripting.insertCSS({
|
||||
css: `${selectors.join(',\n')}{display:none!important;}`,
|
||||
origin: 'USER',
|
||||
target: { tabId, frameIds: [ frameId ] },
|
||||
}).catch(reason => {
|
||||
console.log(reason);
|
||||
});
|
||||
return selectors;
|
||||
async function getAllCustomFilterKeys() {
|
||||
const storageKeys = await localKeys() || [];
|
||||
return storageKeys.filter(a => a.startsWith('site.'));
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export async function uninjectCustomFilters(tabId, frameId, hostname) {
|
||||
const selectors = await selectorsFromCustomFilters(hostname);
|
||||
if ( selectors.length === 0 ) { return; }
|
||||
return browser.scripting.removeCSS({
|
||||
css: `${selectors.join(',\n')}{display:none!important;}`,
|
||||
origin: 'USER',
|
||||
export function startCustomFilters(tabId, frameId) {
|
||||
return browser.scripting.executeScript({
|
||||
files: [ '/js/scripting/css-user.js' ],
|
||||
target: { tabId, frameIds: [ frameId ] },
|
||||
injectImmediately: true,
|
||||
}).catch(reason => {
|
||||
console.log(reason);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
export function terminateCustomFilters(tabId, frameId) {
|
||||
return browser.scripting.executeScript({
|
||||
files: [ '/js/scripting/css-user-terminate.js' ],
|
||||
target: { tabId, frameIds: [ frameId ] },
|
||||
injectImmediately: true,
|
||||
}).catch(reason => {
|
||||
console.log(reason);
|
||||
})
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export async function injectCustomFilters(tabId, frameId, hostname) {
|
||||
const selectors = await selectorsFromCustomFilters(hostname);
|
||||
if ( selectors.length === 0 ) { return; }
|
||||
const promises = [];
|
||||
const plainSelectors = selectors.filter(a => a.startsWith('{') === false);
|
||||
if ( plainSelectors.length !== 0 ) {
|
||||
promises.push(
|
||||
browser.scripting.insertCSS({
|
||||
css: `${plainSelectors.join(',\n')}{display:none!important;}`,
|
||||
origin: 'USER',
|
||||
target: { tabId, frameIds: [ frameId ] },
|
||||
}).catch(reason => {
|
||||
console.log(reason);
|
||||
})
|
||||
);
|
||||
}
|
||||
const proceduralSelectors = selectors.filter(a => a.startsWith('{'));
|
||||
if ( proceduralSelectors.length !== 0 ) {
|
||||
promises.push(
|
||||
browser.scripting.executeScript({
|
||||
files: [ '/js/scripting/css-procedural-api.js' ],
|
||||
target: { tabId, frameIds: [ frameId ] },
|
||||
injectImmediately: true,
|
||||
}).catch(reason => {
|
||||
console.log(reason);
|
||||
})
|
||||
);
|
||||
}
|
||||
await Promise.all(promises);
|
||||
return { plainSelectors, proceduralSelectors };
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export async function registerCustomFilters(context) {
|
||||
const storageKeys = await localKeys() || [];
|
||||
const siteKeys = storageKeys.filter(a => a.startsWith('site.'));
|
||||
const siteKeys = await getAllCustomFilterKeys();
|
||||
if ( siteKeys.length === 0 ) { return; }
|
||||
|
||||
const { none } = context.filteringModeDetails;
|
||||
|
|
@ -133,9 +168,8 @@ export async function registerCustomFilters(context) {
|
|||
export async function addCustomFilter(hostname, selector) {
|
||||
const key = `site.${hostname}`;
|
||||
const selectors = await localRead(key) || [];
|
||||
const filter = `0${selector}`;
|
||||
if ( selectors.includes(filter) ) { return false; }
|
||||
selectors.push(filter);
|
||||
if ( selectors.includes(selector) ) { return false; }
|
||||
selectors.push(selector);
|
||||
selectors.sort();
|
||||
await localWrite(key, selectors);
|
||||
return true;
|
||||
|
|
@ -147,7 +181,7 @@ export async function removeCustomFilter(hostname, selector) {
|
|||
const key = `site.${hostname}`;
|
||||
const selectors = await localRead(key);
|
||||
if ( selectors === undefined ) { return false; }
|
||||
const i = selectors.indexOf(`0${selector}`);
|
||||
const i = selectors.indexOf(selector);
|
||||
if ( i === -1 ) { return false; }
|
||||
selectors.splice(i, 1);
|
||||
await selectors.length !== 0
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a comprehensive, efficient content blocker
|
||||
uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
|
||||
Copyright (C) 2025-present Raymond Hill
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
|
|
@ -21,27 +21,27 @@
|
|||
|
||||
import { dom, qs$, qsa$ } from './dom.js';
|
||||
import { localRead, localWrite } from './ext.js';
|
||||
import { ExtSelectorCompiler } from './static-filtering-parser.js';
|
||||
import { toolOverlay } from './tool-overlay-ui.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const selectorCompiler = new ExtSelectorCompiler({ nativeCssHas: true });
|
||||
|
||||
let selectorPartsDB = new Map();
|
||||
let sliderParts = [];
|
||||
let sliderPartsPos = -1;
|
||||
let previewCSS = '';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
function isValidSelector(selector) {
|
||||
isValidSelector.error = undefined;
|
||||
if ( selector === '' ) { return false; }
|
||||
try {
|
||||
void document.querySelector(`${selector},a`);
|
||||
} catch (reason) {
|
||||
isValidSelector.error = reason;
|
||||
return false;
|
||||
function validateSelector(selector) {
|
||||
validateSelector.error = undefined;
|
||||
if ( selector === '' ) { return; }
|
||||
const result = {};
|
||||
if ( selectorCompiler.compile(selector, result) ) {
|
||||
return result.compiled;
|
||||
}
|
||||
return true;
|
||||
validateSelector.error = 'Error';
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
@ -219,31 +219,22 @@ function updatePreview(state) {
|
|||
} else {
|
||||
dom.cl.toggle(dom.root, 'preview', state)
|
||||
}
|
||||
if ( previewCSS !== '' ) {
|
||||
toolOverlay.postMessage({ what: 'removeCSS', css: previewCSS });
|
||||
previewCSS = '';
|
||||
}
|
||||
if ( state === false ) { return; }
|
||||
const selector = qs$('textarea').value;
|
||||
if ( isValidSelector(selector) === false ) { return; }
|
||||
previewCSS = `${selector}{display:none!important;}`;
|
||||
toolOverlay.postMessage({ what: 'insertCSS', css: previewCSS });
|
||||
const selector = state && validateSelector(qs$('textarea').value) || '';
|
||||
return toolOverlay.postMessage({ what: 'previewSelector', selector });
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
async function onCreateClicked() {
|
||||
const selector = qs$('textarea').value;
|
||||
if ( isValidSelector(selector) === false ) { return; }
|
||||
await toolOverlay.postMessage({ what: 'uninjectCustomFilters' }).then(( ) =>
|
||||
toolOverlay.sendMessage({
|
||||
what: 'addCustomFilter',
|
||||
hostname: toolOverlay.url.hostname,
|
||||
selector,
|
||||
})
|
||||
).then(( ) =>
|
||||
toolOverlay.postMessage({ what: 'injectCustomFilters' })
|
||||
);
|
||||
const selector = validateSelector(qs$('textarea').value);
|
||||
if ( selector === undefined ) { return; }
|
||||
await toolOverlay.postMessage({ what: 'terminateCustomFilters' });
|
||||
await toolOverlay.sendMessage({
|
||||
what: 'addCustomFilter',
|
||||
hostname: toolOverlay.url.hostname,
|
||||
selector,
|
||||
});
|
||||
await toolOverlay.postMessage({ what: 'startCustomFilters' });
|
||||
qs$('textarea').value = '';
|
||||
dom.cl.remove(dom.root, 'preview');
|
||||
quitPicker();
|
||||
|
|
@ -329,10 +320,10 @@ function showDialog(msg) {
|
|||
/******************************************************************************/
|
||||
|
||||
function highlightCandidate() {
|
||||
const selector = qs$('textarea').value;
|
||||
if ( isValidSelector(selector) === false ) {
|
||||
const selector = validateSelector(qs$('textarea').value);
|
||||
if ( selector === undefined ) {
|
||||
toolOverlay.postMessage({ what: 'unhighlight' });
|
||||
updateElementCount({ count: 0, error: isValidSelector.error });
|
||||
updateElementCount({ count: 0, error: validateSelector.error });
|
||||
return;
|
||||
}
|
||||
toolOverlay.postMessage({
|
||||
|
|
@ -366,11 +357,7 @@ function pausePicker() {
|
|||
function unpausePicker() {
|
||||
dom.cl.remove(dom.root, 'paused', 'preview');
|
||||
dom.cl.add(dom.root, 'minimized');
|
||||
updatePreview();
|
||||
toolOverlay.postMessage({
|
||||
what: 'togglePreview',
|
||||
state: false,
|
||||
});
|
||||
updatePreview(false);
|
||||
toolOverlay.highlightElementUnderMouse(true);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -265,7 +265,11 @@ dom.on('#gotoZapper', 'click', ( ) => {
|
|||
dom.on('#gotoPicker', 'click', ( ) => {
|
||||
if ( browser.scripting === undefined ) { return; }
|
||||
browser.scripting.executeScript({
|
||||
files: [ '/js/scripting/tool-overlay.js', '/js/scripting/picker.js' ],
|
||||
files: [
|
||||
'/js/scripting/css-procedural-api.js',
|
||||
'/js/scripting/tool-overlay.js',
|
||||
'/js/scripting/picker.js',
|
||||
],
|
||||
target: { tabId: currentTab.id },
|
||||
});
|
||||
self.close();
|
||||
|
|
@ -276,7 +280,10 @@ dom.on('#gotoPicker', 'click', ( ) => {
|
|||
dom.on('#gotoUnpicker', 'click', ( ) => {
|
||||
if ( browser.scripting === undefined ) { return; }
|
||||
browser.scripting.executeScript({
|
||||
files: [ '/js/scripting/tool-overlay.js', '/js/scripting/unpicker.js' ],
|
||||
files: [
|
||||
'/js/scripting/tool-overlay.js',
|
||||
'/js/scripting/unpicker.js',
|
||||
],
|
||||
target: { tabId: currentTab.id },
|
||||
});
|
||||
self.close();
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@
|
|||
// Isolate from global scope
|
||||
(function uBOL_cssProceduralAPI() {
|
||||
|
||||
if ( self.cssProceduralAPI !== undefined ) {
|
||||
if ( self.cssProceduralAPI instanceof Promise === false ) { return; }
|
||||
if ( self.ProceduralFiltererAPI !== undefined ) {
|
||||
if ( self.ProceduralFiltererAPI instanceof Promise === false ) { return; }
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
@ -55,11 +55,21 @@ const regexFromString = (s, exact = false) => {
|
|||
return new RegExp(exact ? `^${reStr}$` : reStr);
|
||||
};
|
||||
|
||||
const randomToken = ( ) => {
|
||||
const n = Math.random();
|
||||
return String.fromCharCode(n * 25 + 97) +
|
||||
Math.floor(
|
||||
(0.25 + n * 0.75) * Number.MAX_SAFE_INTEGER
|
||||
).toString(36).slice(-8);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// 'P' stands for 'Procedural'
|
||||
|
||||
class PSelectorTask {
|
||||
destructor() {
|
||||
}
|
||||
begin() {
|
||||
}
|
||||
end() {
|
||||
|
|
@ -69,7 +79,7 @@ class PSelectorTask {
|
|||
/******************************************************************************/
|
||||
|
||||
class PSelectorVoidTask extends PSelectorTask {
|
||||
constructor(task) {
|
||||
constructor(filterer, task) {
|
||||
super();
|
||||
console.info(`uBO: :${task[0]}() operator does not exist`);
|
||||
}
|
||||
|
|
@ -80,7 +90,7 @@ class PSelectorVoidTask extends PSelectorTask {
|
|||
/******************************************************************************/
|
||||
|
||||
class PSelectorHasTextTask extends PSelectorTask {
|
||||
constructor(task) {
|
||||
constructor(filterer, task) {
|
||||
super();
|
||||
this.needle = regexFromString(task[1]);
|
||||
}
|
||||
|
|
@ -94,26 +104,26 @@ class PSelectorHasTextTask extends PSelectorTask {
|
|||
/******************************************************************************/
|
||||
|
||||
class PSelectorIfTask extends PSelectorTask {
|
||||
constructor(task) {
|
||||
constructor(filterer, task) {
|
||||
super();
|
||||
this.pselector = new PSelector(task[1]);
|
||||
this.pselector = new PSelector(filterer, task[1]);
|
||||
}
|
||||
transpose(node, output) {
|
||||
if ( this.pselector.test(node) === this.target ) {
|
||||
output.push(node);
|
||||
}
|
||||
}
|
||||
target = true;
|
||||
}
|
||||
PSelectorIfTask.prototype.target = true;
|
||||
|
||||
class PSelectorIfNotTask extends PSelectorIfTask {
|
||||
target = false;
|
||||
}
|
||||
PSelectorIfNotTask.prototype.target = false;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
class PSelectorMatchesAttrTask extends PSelectorTask {
|
||||
constructor(task) {
|
||||
constructor(filterer, task) {
|
||||
super();
|
||||
this.reAttr = regexFromString(task[1].attr, true);
|
||||
this.reValue = regexFromString(task[1].value, true);
|
||||
|
|
@ -132,7 +142,7 @@ class PSelectorMatchesAttrTask extends PSelectorTask {
|
|||
/******************************************************************************/
|
||||
|
||||
class PSelectorMatchesCSSTask extends PSelectorTask {
|
||||
constructor(task) {
|
||||
constructor(filterer, task) {
|
||||
super();
|
||||
this.name = task[1].name;
|
||||
this.pseudo = task[1].pseudo ? `::${task[1].pseudo}` : null;
|
||||
|
|
@ -150,15 +160,15 @@ class PSelectorMatchesCSSTask extends PSelectorTask {
|
|||
}
|
||||
}
|
||||
class PSelectorMatchesCSSAfterTask extends PSelectorMatchesCSSTask {
|
||||
constructor(task) {
|
||||
super(task);
|
||||
constructor(filterer, task) {
|
||||
super(filterer, task);
|
||||
this.pseudo = '::after';
|
||||
}
|
||||
}
|
||||
|
||||
class PSelectorMatchesCSSBeforeTask extends PSelectorMatchesCSSTask {
|
||||
constructor(task) {
|
||||
super(task);
|
||||
constructor(filterer, task) {
|
||||
super(filterer, task);
|
||||
this.pseudo = '::before';
|
||||
}
|
||||
}
|
||||
|
|
@ -166,26 +176,32 @@ class PSelectorMatchesCSSBeforeTask extends PSelectorMatchesCSSTask {
|
|||
/******************************************************************************/
|
||||
|
||||
class PSelectorMatchesMediaTask extends PSelectorTask {
|
||||
constructor(task) {
|
||||
constructor(filterer, task) {
|
||||
super();
|
||||
this.filterer = filterer;
|
||||
this.mql = window.matchMedia(task[1]);
|
||||
if ( this.mql.media === 'not all' ) { return; }
|
||||
this.mql.addEventListener('change', ( ) => {
|
||||
const { proceduralFilterer } = self.cssProceduralAPI;
|
||||
if ( proceduralFilterer instanceof Object === false ) { return; }
|
||||
proceduralFilterer.uBOL_DOMChanged();
|
||||
});
|
||||
this.boundHandler = this.handler.bind(this);
|
||||
this.mql.addEventListener('change', this.boundHandler);
|
||||
}
|
||||
destructor() {
|
||||
super.destructor();
|
||||
this.mql.removeEventListener('change', this.boundHandler);
|
||||
}
|
||||
transpose(node, output) {
|
||||
if ( this.mql.matches === false ) { return; }
|
||||
output.push(node);
|
||||
}
|
||||
handler() {
|
||||
if ( this.filterer instanceof Object === false ) { return; }
|
||||
this.filterer.uBOL_DOMChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
class PSelectorMatchesPathTask extends PSelectorTask {
|
||||
constructor(task) {
|
||||
constructor(filterer, task) {
|
||||
super();
|
||||
this.needle = regexFromString(
|
||||
task[1].replace(/\P{ASCII}/gu, s => encodeURIComponent(s))
|
||||
|
|
@ -201,7 +217,7 @@ class PSelectorMatchesPathTask extends PSelectorTask {
|
|||
/******************************************************************************/
|
||||
|
||||
class PSelectorMatchesPropTask extends PSelectorTask {
|
||||
constructor(task) {
|
||||
constructor(filterer, task) {
|
||||
super();
|
||||
this.props = task[1].attr.split('.');
|
||||
this.reValue = task[1].value !== ''
|
||||
|
|
@ -227,7 +243,7 @@ class PSelectorMatchesPropTask extends PSelectorTask {
|
|||
/******************************************************************************/
|
||||
|
||||
class PSelectorMinTextLengthTask extends PSelectorTask {
|
||||
constructor(task) {
|
||||
constructor(filterer, task) {
|
||||
super();
|
||||
this.min = task[1];
|
||||
}
|
||||
|
|
@ -298,7 +314,7 @@ class PSelectorOthersTask extends PSelectorTask {
|
|||
/******************************************************************************/
|
||||
|
||||
class PSelectorShadowTask extends PSelectorTask {
|
||||
constructor(task) {
|
||||
constructor(filterer, task) {
|
||||
super();
|
||||
this.selector = task[1];
|
||||
}
|
||||
|
|
@ -332,7 +348,7 @@ class PSelectorShadowTask extends PSelectorTask {
|
|||
// https://github.com/AdguardTeam/ExtendedCss/issues/31#issuecomment-302391277
|
||||
// Prepend `:scope ` if needed.
|
||||
class PSelectorSpathTask extends PSelectorTask {
|
||||
constructor(task) {
|
||||
constructor(filterer, task) {
|
||||
super();
|
||||
this.spath = task[1];
|
||||
this.nth = /^(?:\s*[+~]|:)/.test(this.spath);
|
||||
|
|
@ -368,7 +384,7 @@ class PSelectorSpathTask extends PSelectorTask {
|
|||
/******************************************************************************/
|
||||
|
||||
class PSelectorUpwardTask extends PSelectorTask {
|
||||
constructor(task) {
|
||||
constructor(filterer, task) {
|
||||
super();
|
||||
const arg = task[1];
|
||||
if ( typeof arg === 'number' ) {
|
||||
|
|
@ -394,15 +410,16 @@ class PSelectorUpwardTask extends PSelectorTask {
|
|||
}
|
||||
output.push(node);
|
||||
}
|
||||
i = 0;
|
||||
s = '';
|
||||
}
|
||||
PSelectorUpwardTask.prototype.i = 0;
|
||||
PSelectorUpwardTask.prototype.s = '';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
class PSelectorWatchAttrs extends PSelectorTask {
|
||||
constructor(task) {
|
||||
constructor(filterer, task) {
|
||||
super();
|
||||
this.filterer = filterer;
|
||||
this.observer = null;
|
||||
this.observed = new WeakSet();
|
||||
this.observerOptions = {
|
||||
|
|
@ -414,17 +431,22 @@ class PSelectorWatchAttrs extends PSelectorTask {
|
|||
this.observerOptions.attributeFilter = task[1];
|
||||
}
|
||||
}
|
||||
// TODO: Is it worth trying to re-apply only the current selector?
|
||||
handler() {
|
||||
const { proceduralFilterer } = self.cssProceduralAPI;
|
||||
if ( proceduralFilterer instanceof Object === false ) { return; }
|
||||
proceduralFilterer.uBOL_DOMChanged();
|
||||
destructor() {
|
||||
super.destructor();
|
||||
if ( this.observer ) {
|
||||
this.observer.takeRecords();
|
||||
this.observer.disconnect();
|
||||
this.observer = null;
|
||||
}
|
||||
}
|
||||
transpose(node, output) {
|
||||
output.push(node);
|
||||
if ( this.filterer instanceof Object === false ) { return; }
|
||||
if ( this.observed.has(node) ) { return; }
|
||||
if ( this.observer === null ) {
|
||||
this.observer = new MutationObserver(this.handler);
|
||||
this.observer = new MutationObserver(( ) => {
|
||||
this.filterer.uBOL_DOMChanged();
|
||||
});
|
||||
}
|
||||
this.observer.observe(node, this.observerOptions);
|
||||
this.observed.add(node);
|
||||
|
|
@ -434,7 +456,7 @@ class PSelectorWatchAttrs extends PSelectorTask {
|
|||
/******************************************************************************/
|
||||
|
||||
class PSelectorXpathTask extends PSelectorTask {
|
||||
constructor(task) {
|
||||
constructor(filterer, task) {
|
||||
super();
|
||||
this.xpe = document.createExpression(task[1], null);
|
||||
this.xpr = null;
|
||||
|
|
@ -458,17 +480,22 @@ class PSelectorXpathTask extends PSelectorTask {
|
|||
/******************************************************************************/
|
||||
|
||||
class PSelector {
|
||||
constructor(o) {
|
||||
constructor(filterer, o) {
|
||||
this.selector = o.selector;
|
||||
this.tasks = [];
|
||||
const tasks = [];
|
||||
if ( Array.isArray(o.tasks) === false ) { return; }
|
||||
for ( const task of o.tasks ) {
|
||||
const ctor = this.operatorToTaskMap.get(task[0]) || PSelectorVoidTask;
|
||||
tasks.push(new ctor(task));
|
||||
const ctor = PSelector.operatorToTaskMap.get(task[0]) || PSelectorVoidTask;
|
||||
tasks.push(new ctor(filterer, task));
|
||||
}
|
||||
this.tasks = tasks;
|
||||
}
|
||||
destructor() {
|
||||
for ( const task of this.tasks ) {
|
||||
task.destructor();
|
||||
}
|
||||
}
|
||||
prime(input) {
|
||||
const root = input || document;
|
||||
if ( this.selector === '' ) { return [ root ]; }
|
||||
|
|
@ -514,34 +541,34 @@ class PSelector {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
static operatorToTaskMap = new Map([
|
||||
[ 'has', PSelectorIfTask ],
|
||||
[ 'has-text', PSelectorHasTextTask ],
|
||||
[ 'if', PSelectorIfTask ],
|
||||
[ 'if-not', PSelectorIfNotTask ],
|
||||
[ 'matches-attr', PSelectorMatchesAttrTask ],
|
||||
[ 'matches-css', PSelectorMatchesCSSTask ],
|
||||
[ 'matches-css-after', PSelectorMatchesCSSAfterTask ],
|
||||
[ 'matches-css-before', PSelectorMatchesCSSBeforeTask ],
|
||||
[ 'matches-media', PSelectorMatchesMediaTask ],
|
||||
[ 'matches-path', PSelectorMatchesPathTask ],
|
||||
[ 'matches-prop', PSelectorMatchesPropTask ],
|
||||
[ 'min-text-length', PSelectorMinTextLengthTask ],
|
||||
[ 'not', PSelectorIfNotTask ],
|
||||
[ 'others', PSelectorOthersTask ],
|
||||
[ 'shadow', PSelectorShadowTask ],
|
||||
[ 'spath', PSelectorSpathTask ],
|
||||
[ 'upward', PSelectorUpwardTask ],
|
||||
[ 'watch-attr', PSelectorWatchAttrs ],
|
||||
[ 'xpath', PSelectorXpathTask ],
|
||||
]);
|
||||
}
|
||||
PSelector.prototype.operatorToTaskMap = new Map([
|
||||
[ 'has', PSelectorIfTask ],
|
||||
[ 'has-text', PSelectorHasTextTask ],
|
||||
[ 'if', PSelectorIfTask ],
|
||||
[ 'if-not', PSelectorIfNotTask ],
|
||||
[ 'matches-attr', PSelectorMatchesAttrTask ],
|
||||
[ 'matches-css', PSelectorMatchesCSSTask ],
|
||||
[ 'matches-css-after', PSelectorMatchesCSSAfterTask ],
|
||||
[ 'matches-css-before', PSelectorMatchesCSSBeforeTask ],
|
||||
[ 'matches-media', PSelectorMatchesMediaTask ],
|
||||
[ 'matches-path', PSelectorMatchesPathTask ],
|
||||
[ 'matches-prop', PSelectorMatchesPropTask ],
|
||||
[ 'min-text-length', PSelectorMinTextLengthTask ],
|
||||
[ 'not', PSelectorIfNotTask ],
|
||||
[ 'others', PSelectorOthersTask ],
|
||||
[ 'shadow', PSelectorShadowTask ],
|
||||
[ 'spath', PSelectorSpathTask ],
|
||||
[ 'upward', PSelectorUpwardTask ],
|
||||
[ 'watch-attr', PSelectorWatchAttrs ],
|
||||
[ 'xpath', PSelectorXpathTask ],
|
||||
]);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
class PSelectorRoot extends PSelector {
|
||||
constructor(o) {
|
||||
super(o);
|
||||
constructor(filterer, o) {
|
||||
super(filterer, o);
|
||||
this.budget = 200; // I arbitrary picked a 1/5 second
|
||||
this.raw = o.raw;
|
||||
this.cost = 0;
|
||||
|
|
@ -569,16 +596,39 @@ class PSelectorRoot extends PSelector {
|
|||
class ProceduralFilterer {
|
||||
constructor() {
|
||||
this.selectors = [];
|
||||
this.masterToken = this.randomToken();
|
||||
this.styleTokenMap = new Map();
|
||||
this.styledNodes = new Set();
|
||||
this.timer = undefined;
|
||||
this.hideStyle = 'display:none!important;';
|
||||
}
|
||||
|
||||
async reset() {
|
||||
if ( this.timer ) {
|
||||
self.cancelAnimationFrame(this.timer);
|
||||
this.timer = undefined;
|
||||
}
|
||||
for ( const pselector of this.selectors.values() ) {
|
||||
pselector.destructor();
|
||||
}
|
||||
this.selectors.length = 0;
|
||||
const promises = [];
|
||||
for ( const [ style, token ] of this.styleTokenMap ) {
|
||||
for ( const elem of this.styledNodes ) {
|
||||
elem.removeAttribute(token);
|
||||
}
|
||||
const css = `[${token}]\n{${style}}\n`;
|
||||
promises.push(
|
||||
chrome.runtime.sendMessage({ what: 'removeCSS', css }).catch(( ) => { })
|
||||
);
|
||||
}
|
||||
this.styleTokenMap.clear();
|
||||
this.styledNodes.clear();
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
addSelectors(selectors) {
|
||||
for ( const selector of selectors ) {
|
||||
const pselector = new PSelectorRoot(selector);
|
||||
const pselector = new PSelectorRoot(this, selector);
|
||||
this.primeProceduralSelector(pselector);
|
||||
this.selectors.push(pselector);
|
||||
}
|
||||
|
|
@ -636,9 +686,9 @@ class ProceduralFilterer {
|
|||
if ( style === undefined ) { return; }
|
||||
let styleToken = this.styleTokenMap.get(style);
|
||||
if ( styleToken !== undefined ) { return styleToken; }
|
||||
styleToken = this.randomToken();
|
||||
styleToken = randomToken();
|
||||
this.styleTokenMap.set(style, styleToken);
|
||||
uBOL_injectCSS(`[${this.masterToken}][${styleToken}]\n{${style}}\n`);
|
||||
uBOL_injectCSS(`[${styleToken}]\n{${style}}\n`);
|
||||
return styleToken;
|
||||
}
|
||||
|
||||
|
|
@ -653,7 +703,6 @@ class ProceduralFilterer {
|
|||
arg === '' ? this.hideStyle : arg
|
||||
);
|
||||
for ( const node of nodes ) {
|
||||
node.setAttribute(this.masterToken, '');
|
||||
node.setAttribute(styleToken, '');
|
||||
this.styledNodes.add(node);
|
||||
}
|
||||
|
|
@ -692,24 +741,16 @@ class ProceduralFilterer {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: Current assumption is one style per hit element. Could be an
|
||||
// issue if an element has multiple styling and one styling is
|
||||
// brought back. Possibly too rare to care about this for now.
|
||||
unprocessNodes(nodes) {
|
||||
const tokens = Array.from(this.styleTokenMap.values());
|
||||
for ( const node of nodes ) {
|
||||
if ( this.styledNodes.has(node) ) { continue; }
|
||||
node.removeAttribute(this.masterToken);
|
||||
for ( const token of tokens ) {
|
||||
node.removeAttribute(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
randomToken() {
|
||||
const n = Math.random();
|
||||
return String.fromCharCode(n * 25 + 97) +
|
||||
Math.floor(
|
||||
(0.25 + n * 0.75) * Number.MAX_SAFE_INTEGER
|
||||
).toString(36).slice(-8);
|
||||
}
|
||||
|
||||
uBOL_DOMChanged() {
|
||||
if ( this.timer !== undefined ) { return; }
|
||||
this.timer = self.requestAnimationFrame(( ) => {
|
||||
|
|
@ -721,9 +762,24 @@ class ProceduralFilterer {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
self.cssProceduralAPI = {
|
||||
proceduralFilterer: null,
|
||||
domObserver: null,
|
||||
self.ProceduralFiltererAPI = class {
|
||||
constructor() {
|
||||
this.proceduralFilterer = null;
|
||||
this.domObserver = null;
|
||||
}
|
||||
|
||||
async reset() {
|
||||
if ( this.domObserver ) {
|
||||
this.domObserver.takeRecords();
|
||||
this.domObserver.disconnect();
|
||||
this.domObserver = null;
|
||||
}
|
||||
if ( this.proceduralFilterer ) {
|
||||
await this.proceduralFilterer.reset();
|
||||
this.proceduralFilterer = null;
|
||||
}
|
||||
}
|
||||
|
||||
addSelectors(selectors) {
|
||||
if ( this.proceduralFilterer === null ) {
|
||||
this.proceduralFilterer = new ProceduralFilterer();
|
||||
|
|
@ -732,14 +788,18 @@ self.cssProceduralAPI = {
|
|||
this.domObserver = new MutationObserver(mutations => {
|
||||
this.onDOMChanged(mutations);
|
||||
});
|
||||
this.domObserver.observe(document, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
this.domObserver.observe(document, { childList: true, subtree: true });
|
||||
}
|
||||
this.proceduralFilterer.addSelectors(selectors);
|
||||
this.proceduralFilterer.uBOL_commit();
|
||||
},
|
||||
}
|
||||
|
||||
qsa(selector) {
|
||||
const o = JSON.parse(selector);
|
||||
const pselector = new PSelectorRoot(null, o);
|
||||
return pselector.exec();
|
||||
}
|
||||
|
||||
onDOMChanged(mutations) {
|
||||
for ( const mutation of mutations ) {
|
||||
for ( const added of mutation.addedNodes ) {
|
||||
|
|
@ -751,13 +811,11 @@ self.cssProceduralAPI = {
|
|||
return this.proceduralFilterer.uBOL_DOMChanged();
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
void 0;
|
||||
|
|
|
|||
|
|
@ -112,17 +112,20 @@ if ( declaratives.length !== 0 ) {
|
|||
const procedurals = exceptedSelectors.filter(a => a.cssable === undefined);
|
||||
if ( procedurals.length !== 0 ) {
|
||||
const addSelectors = selectors => {
|
||||
if ( self.cssProceduralAPI instanceof Object === false ) { return; }
|
||||
self.cssProceduralAPI.addSelectors(selectors);
|
||||
if ( self.listsProceduralFiltererAPI instanceof Object === false ) { return; }
|
||||
self.listsProceduralFiltererAPI.addSelectors(selectors);
|
||||
};
|
||||
if ( self.cssProceduralAPI === undefined ) {
|
||||
self.cssProceduralAPI = chrome.runtime.sendMessage({
|
||||
if ( self.ProceduralFiltererAPI === undefined ) {
|
||||
self.ProceduralFiltererAPI = chrome.runtime.sendMessage({
|
||||
what: 'injectCSSProceduralAPI'
|
||||
}).catch(( ) => {
|
||||
});
|
||||
}
|
||||
if ( self.cssProceduralAPI instanceof Promise ) {
|
||||
self.cssProceduralAPI.then(( ) => { addSelectors(procedurals); });
|
||||
if ( self.ProceduralFiltererAPI instanceof Promise ) {
|
||||
self.ProceduralFiltererAPI.then(( ) => {
|
||||
self.listsProceduralFiltererAPI = new self.ProceduralFiltererAPI();
|
||||
addSelectors(procedurals);
|
||||
});
|
||||
} else {
|
||||
addSelectors(procedurals);
|
||||
}
|
||||
|
|
|
|||
45
platform/mv3/extension/js/scripting/css-user-terminate.js
Normal file
45
platform/mv3/extension/js/scripting/css-user-terminate.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/*******************************************************************************
|
||||
|
||||
uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
|
||||
Copyright (C) 2019-present Raymond Hill
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
|
||||
Home: https://github.com/gorhill/uBlock
|
||||
*/
|
||||
|
||||
(function uBOL_cssUserTerminate() {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const plainSelectors = self.customFilters?.plainSelectors;
|
||||
if ( plainSelectors ) {
|
||||
chrome.runtime.sendMessage({
|
||||
what: 'removeCSS',
|
||||
css: `${plainSelectors.join(',\n')}{display:none!important;}`,
|
||||
}).catch(( ) => {
|
||||
});
|
||||
}
|
||||
|
||||
if ( self.customProceduralFiltererAPI instanceof Object ) {
|
||||
self.customProceduralFiltererAPI.reset();
|
||||
}
|
||||
|
||||
self.customFilters = undefined;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
})();
|
||||
|
||||
void 0;
|
||||
|
|
@ -24,12 +24,23 @@
|
|||
/******************************************************************************/
|
||||
|
||||
const docURL = new URL(document.baseURI);
|
||||
chrome.runtime.sendMessage({
|
||||
const details = await chrome.runtime.sendMessage({
|
||||
what: 'injectCustomFilters',
|
||||
hostname: docURL.hostname,
|
||||
}).catch(( ) => {
|
||||
});
|
||||
|
||||
if ( details?.proceduralSelectors?.length ) {
|
||||
if ( self.ProceduralFiltererAPI ) {
|
||||
self.customProceduralFiltererAPI = new self.ProceduralFiltererAPI();
|
||||
self.customProceduralFiltererAPI.addSelectors(
|
||||
details.proceduralSelectors.map(a => JSON.parse(a))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
self.customFilters = details;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a comprehensive, efficient content blocker
|
||||
uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
|
||||
Copyright (C) 2025-present Raymond Hill
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
|
|
@ -237,22 +237,55 @@ const excludedSelectors = [
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
async function previewSelector(selector) {
|
||||
if ( selector === previewedSelector ) { return; }
|
||||
if ( previewedSelector !== '' ) {
|
||||
if ( previewedSelector.startsWith('{') ) {
|
||||
if ( self.pickerProceduralFilteringAPI ) {
|
||||
await self.pickerProceduralFilteringAPI.reset();
|
||||
}
|
||||
}
|
||||
if ( previewedCSS !== '' ) {
|
||||
await ubolOverlay.sendMessage({ what: 'removeCSS', css: previewedCSS });
|
||||
previewedCSS = '';
|
||||
}
|
||||
}
|
||||
previewedSelector = selector || '';
|
||||
if ( selector === '' ) { return; }
|
||||
if ( selector.startsWith('{') ) {
|
||||
if ( self.ProceduralFiltererAPI === undefined ) { return; }
|
||||
if ( self.pickerProceduralFilteringAPI === undefined ) {
|
||||
self.pickerProceduralFilteringAPI = new self.ProceduralFiltererAPI();
|
||||
}
|
||||
self.pickerProceduralFilteringAPI.addSelectors([ JSON.parse(selector) ]);
|
||||
return;
|
||||
}
|
||||
previewedCSS = `${selector}{display:none!important;}`;
|
||||
await ubolOverlay.sendMessage({ what: 'insertCSS', css: previewedCSS });
|
||||
}
|
||||
|
||||
let previewedSelector = '';
|
||||
let previewedCSS = '';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const previewProceduralFiltererAPI = new self.ProceduralFiltererAPI();
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
function onMessage(msg) {
|
||||
switch ( msg.what ) {
|
||||
case 'injectCustomFilters':
|
||||
return ubolOverlay.sendMessage({ what: 'injectCustomFilters',
|
||||
hostname: ubolOverlay.url.hostname,
|
||||
});
|
||||
case 'uninjectCustomFilters':
|
||||
return ubolOverlay.sendMessage({ what: 'uninjectCustomFilters',
|
||||
hostname: ubolOverlay.url.hostname,
|
||||
});
|
||||
case 'quitTool':
|
||||
previewProceduralFiltererAPI.reset();
|
||||
break;
|
||||
case 'startCustomFilters':
|
||||
return ubolOverlay.sendMessage({ what: 'startCustomFilters' });
|
||||
case 'terminateCustomFilters':
|
||||
return ubolOverlay.sendMessage({ what: 'terminateCustomFilters' });
|
||||
case 'candidatesAtPoint':
|
||||
return candidatesAtPoint(msg.mx, msg.my, msg.broad);
|
||||
case 'insertCSS':
|
||||
return ubolOverlay.sendMessage(msg);
|
||||
case 'removeCSS':
|
||||
return ubolOverlay.sendMessage(msg);
|
||||
case 'previewSelector':
|
||||
return previewSelector(msg.selector);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a comprehensive, efficient content blocker
|
||||
uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
|
||||
Copyright (C) 2025-present Raymond Hill
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
|
|
@ -254,14 +254,20 @@ self.ubolOverlay = {
|
|||
},
|
||||
|
||||
qsa(node, selector) {
|
||||
if ( node !== null ) {
|
||||
try {
|
||||
const elems = node.querySelectorAll(selector);
|
||||
this.qsa.error = undefined;
|
||||
return elems;
|
||||
} catch (reason) {
|
||||
this.qsa.error = `${reason}`;
|
||||
if ( node === null ) { return []; }
|
||||
if ( selector.startsWith('{') ) {
|
||||
if ( this.proceduralFiltererAPI === undefined ) {
|
||||
if ( self.ProceduralFiltererAPI === undefined ) { return []; }
|
||||
this.proceduralFiltererAPI = new self.ProceduralFiltererAPI();
|
||||
}
|
||||
return this.proceduralFiltererAPI.qsa(selector);
|
||||
}
|
||||
try {
|
||||
const elems = node.querySelectorAll(selector);
|
||||
this.qsa.error = undefined;
|
||||
return elems;
|
||||
} catch (reason) {
|
||||
this.qsa.error = `${reason}`;
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a comprehensive, efficient content blocker
|
||||
uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
|
||||
Copyright (C) 2025-present Raymond Hill
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
|
|
@ -31,14 +31,10 @@ if ( ubolOverlay.file === '/unpicker-ui.html' ) { return; }
|
|||
|
||||
function onMessage(msg) {
|
||||
switch ( msg.what ) {
|
||||
case 'injectCustomFilters':
|
||||
return ubolOverlay.sendMessage({ what: 'injectCustomFilters',
|
||||
hostname: ubolOverlay.url.hostname,
|
||||
});
|
||||
case 'uninjectCustomFilters':
|
||||
return ubolOverlay.sendMessage({ what: 'uninjectCustomFilters',
|
||||
hostname: ubolOverlay.url.hostname,
|
||||
});
|
||||
case 'startCustomFilters':
|
||||
return ubolOverlay.sendMessage({ what: 'startCustomFilters' });
|
||||
case 'terminateCustomFilters':
|
||||
return ubolOverlay.sendMessage({ what: 'terminateCustomFilters' });
|
||||
case 'removeCustomFilter':
|
||||
return ubolOverlay.sendMessage({ what: 'removeCustomFilter',
|
||||
hostname: ubolOverlay.url.hostname,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a comprehensive, efficient content blocker
|
||||
uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
|
||||
Copyright (C) 2025-present Raymond Hill
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a comprehensive, efficient content blocker
|
||||
uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
|
||||
Copyright (C) 2025-present Raymond Hill
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a comprehensive, efficient content blocker
|
||||
uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
|
||||
Copyright (C) 2025-present Raymond Hill
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
|
|
@ -33,8 +33,8 @@ function onMinimizeClicked() {
|
|||
|
||||
function highlight() {
|
||||
const selectors = [];
|
||||
for ( const selectorElem of qsa$('#customFilters .customFilter.on > span.selector') ) {
|
||||
selectors.push(selectorElem.textContent);
|
||||
for ( const selectorElem of qsa$('#customFilters .customFilter.on') ) {
|
||||
selectors.push(selectorElem.dataset.selector);
|
||||
}
|
||||
if ( selectors.length !== 0 ) {
|
||||
toolOverlay.postMessage({
|
||||
|
|
@ -64,7 +64,7 @@ function onFilterClicked(ev) {
|
|||
highlight();
|
||||
return;
|
||||
}
|
||||
const selector = selectorElem.textContent;
|
||||
const selector = filterElem.dataset.selector;
|
||||
const trashElem = qs$(filterElem, ':scope > span.remove');
|
||||
if ( target === trashElem ) {
|
||||
dom.cl.add(filterElem, 'removed');
|
||||
|
|
@ -111,9 +111,16 @@ function populateFilters(selectors) {
|
|||
dom.clear(container);
|
||||
const rowTemplate = qs$('template#customFilterRow');
|
||||
for ( const selector of selectors ) {
|
||||
const row = rowTemplate.content.cloneNode(true);
|
||||
qs$(row, '.customFilter > span.selector').textContent = selector;
|
||||
container.append(row);
|
||||
const fragment = rowTemplate.content.cloneNode(true);
|
||||
const row = qs$(fragment, '.customFilter');
|
||||
row.dataset.selector = selector;
|
||||
let text = selector;
|
||||
if ( selector.startsWith('{') ) {
|
||||
const o = JSON.parse(selector);
|
||||
text = o.raw;
|
||||
}
|
||||
qs$(row, '.selector').textContent = text;
|
||||
container.append(fragment);
|
||||
}
|
||||
faIconsInit(container);
|
||||
autoSelectFilter();
|
||||
|
|
@ -129,8 +136,8 @@ async function startUnpicker() {
|
|||
if ( selectors.length === 0 ) {
|
||||
return quitUnpicker();
|
||||
}
|
||||
await toolOverlay.postMessage({ what: 'terminateCustomFilters' });
|
||||
await toolOverlay.postMessage({ what: 'startTool' });
|
||||
await toolOverlay.postMessage({ what: 'uninjectCustomFilters' });
|
||||
populateFilters(selectors);
|
||||
dom.on('#minimize', 'click', onMinimizeClicked);
|
||||
dom.on('#customFilters', 'click', onFilterClicked);
|
||||
|
|
@ -140,7 +147,7 @@ async function startUnpicker() {
|
|||
/******************************************************************************/
|
||||
|
||||
async function quitUnpicker() {
|
||||
await toolOverlay.postMessage({ what: 'injectCustomFilters' });
|
||||
await toolOverlay.postMessage({ what: 'startCustomFilters' });
|
||||
toolOverlay.stop();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a comprehensive, efficient content blocker
|
||||
uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
|
||||
Copyright (C) 2025-present Raymond Hill
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
|
|
@ -33,7 +33,8 @@ function onSvgClicked(ev) {
|
|||
my: ev.clientY,
|
||||
options: {
|
||||
stay: true,
|
||||
highlight: ev.target !== toolOverlay.svgIslands,
|
||||
highlight: dom.cl.has(dom.root, 'mobile') &&
|
||||
ev.target !== toolOverlay.svgIslands,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3199,8 +3199,8 @@ export const netOptionTokenDescriptors = new Map([
|
|||
// https://github.com/uBlockOrigin/uBlock-issues/issues/89
|
||||
// Do not discard unknown pseudo-elements.
|
||||
|
||||
class ExtSelectorCompiler {
|
||||
constructor(instanceOptions) {
|
||||
export class ExtSelectorCompiler {
|
||||
constructor(instanceOptions = {}) {
|
||||
this.reParseRegexLiteral = /^\/(.+)\/([imu]+)?$/;
|
||||
|
||||
// Use a regex for most common CSS selectors known to be valid in any
|
||||
|
|
|
|||
Loading…
Reference in a new issue