mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-01 16:33:06 +01:00
Introduce experimental procedural cosmetic operator :others()
The purpose of this new procedural operator is to target all elements _outside_ than the currently selected set of elements. For any element feeding into `others()`, the resultset of the `others()` operator will include everything else except: - the descendants of a subject element - the ancestors of a subject element The resultset will contains the siblings of a subject element _except_ when those siblings are either a descendant or ancestor of another subject element. Related discussion: - https://www.reddit.com/r/uBlockOrigin/comments/slyjzp/ Though this operator is unlikely to be used in default lists, it opens the door to create specialized filter lists which purpose is some sort of "reader mode", where everything _else_ than a selected set of elements are hidden from view. Examples of usage: twitter.com##:matches-path(/^/home/) [data-testid="primaryColumn"]:others() nature.com##:matches-path(/^/articles//) :is(.c-breadcrumbs,.c-article-main-column):others() The status is currently considered experimental and support might be removed in the future if it turns out there is no sufficient usage or if unforeseen difficult issues arise implementation-wise.
This commit is contained in:
parent
9a5acbbfcd
commit
152120bd9e
@ -29,13 +29,24 @@ if (
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// TODO: Experiment/evaluate loading procedural operator code using an
|
const nonVisualElements = {
|
||||||
// on demand approach.
|
script: true,
|
||||||
|
style: true,
|
||||||
|
};
|
||||||
|
|
||||||
// 'P' stands for 'Procedural'
|
// 'P' stands for 'Procedural'
|
||||||
|
|
||||||
const PSelectorHasTextTask = class {
|
class PSelectorTask {
|
||||||
|
begin() {
|
||||||
|
}
|
||||||
|
end() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PSelectorHasTextTask extends PSelectorTask {
|
||||||
constructor(task) {
|
constructor(task) {
|
||||||
|
super();
|
||||||
let arg0 = task[1], arg1;
|
let arg0 = task[1], arg1;
|
||||||
if ( Array.isArray(task[1]) ) {
|
if ( Array.isArray(task[1]) ) {
|
||||||
arg1 = arg0[1]; arg0 = arg0[0];
|
arg1 = arg0[1]; arg0 = arg0[0];
|
||||||
@ -47,10 +58,11 @@ const PSelectorHasTextTask = class {
|
|||||||
output.push(node);
|
output.push(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const PSelectorIfTask = class {
|
class PSelectorIfTask extends PSelectorTask {
|
||||||
constructor(task) {
|
constructor(task) {
|
||||||
|
super();
|
||||||
this.pselector = new PSelector(task[1]);
|
this.pselector = new PSelector(task[1]);
|
||||||
}
|
}
|
||||||
transpose(node, output) {
|
transpose(node, output) {
|
||||||
@ -58,15 +70,16 @@ const PSelectorIfTask = class {
|
|||||||
output.push(node);
|
output.push(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
PSelectorIfTask.prototype.target = true;
|
PSelectorIfTask.prototype.target = true;
|
||||||
|
|
||||||
const PSelectorIfNotTask = class extends PSelectorIfTask {
|
class PSelectorIfNotTask extends PSelectorIfTask {
|
||||||
};
|
}
|
||||||
PSelectorIfNotTask.prototype.target = false;
|
PSelectorIfNotTask.prototype.target = false;
|
||||||
|
|
||||||
const PSelectorMatchesCSSTask = class {
|
class PSelectorMatchesCSSTask extends PSelectorTask {
|
||||||
constructor(task) {
|
constructor(task) {
|
||||||
|
super();
|
||||||
this.name = task[1].name;
|
this.name = task[1].name;
|
||||||
let arg0 = task[1].value, arg1;
|
let arg0 = task[1].value, arg1;
|
||||||
if ( Array.isArray(arg0) ) {
|
if ( Array.isArray(arg0) ) {
|
||||||
@ -80,19 +93,20 @@ const PSelectorMatchesCSSTask = class {
|
|||||||
output.push(node);
|
output.push(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
PSelectorMatchesCSSTask.prototype.pseudo = null;
|
PSelectorMatchesCSSTask.prototype.pseudo = null;
|
||||||
|
|
||||||
const PSelectorMatchesCSSAfterTask = class extends PSelectorMatchesCSSTask {
|
class PSelectorMatchesCSSAfterTask extends PSelectorMatchesCSSTask {
|
||||||
};
|
}
|
||||||
PSelectorMatchesCSSAfterTask.prototype.pseudo = ':after';
|
PSelectorMatchesCSSAfterTask.prototype.pseudo = ':after';
|
||||||
|
|
||||||
const PSelectorMatchesCSSBeforeTask = class extends PSelectorMatchesCSSTask {
|
class PSelectorMatchesCSSBeforeTask extends PSelectorMatchesCSSTask {
|
||||||
};
|
}
|
||||||
PSelectorMatchesCSSBeforeTask.prototype.pseudo = ':before';
|
PSelectorMatchesCSSBeforeTask.prototype.pseudo = ':before';
|
||||||
|
|
||||||
const PSelectorMinTextLengthTask = class {
|
class PSelectorMinTextLengthTask extends PSelectorTask {
|
||||||
constructor(task) {
|
constructor(task) {
|
||||||
|
super();
|
||||||
this.min = task[1];
|
this.min = task[1];
|
||||||
}
|
}
|
||||||
transpose(node, output) {
|
transpose(node, output) {
|
||||||
@ -100,10 +114,11 @@ const PSelectorMinTextLengthTask = class {
|
|||||||
output.push(node);
|
output.push(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const PSelectorMatchesPathTask = class {
|
class PSelectorMatchesPathTask extends PSelectorTask {
|
||||||
constructor(task) {
|
constructor(task) {
|
||||||
|
super();
|
||||||
let arg0 = task[1], arg1;
|
let arg0 = task[1], arg1;
|
||||||
if ( Array.isArray(task[1]) ) {
|
if ( Array.isArray(task[1]) ) {
|
||||||
arg1 = arg0[1]; arg0 = arg0[0];
|
arg1 = arg0[1]; arg0 = arg0[0];
|
||||||
@ -115,12 +130,69 @@ const PSelectorMatchesPathTask = class {
|
|||||||
output.push(node);
|
output.push(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
class PSelectorOthersTask extends PSelectorTask {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.targets = new Set();
|
||||||
|
}
|
||||||
|
begin() {
|
||||||
|
this.targets.clear();
|
||||||
|
}
|
||||||
|
end(output) {
|
||||||
|
const toKeep = new Set(this.targets);
|
||||||
|
const toDiscard = new Set();
|
||||||
|
const body = document.body;
|
||||||
|
let discard = null;
|
||||||
|
for ( let keep of this.targets ) {
|
||||||
|
while ( keep !== null && keep !== body ) {
|
||||||
|
toKeep.add(keep);
|
||||||
|
toDiscard.delete(keep);
|
||||||
|
discard = keep.previousElementSibling;
|
||||||
|
while ( discard !== null ) {
|
||||||
|
if (
|
||||||
|
nonVisualElements[discard.localName] !== true &&
|
||||||
|
toKeep.has(discard) === false
|
||||||
|
) {
|
||||||
|
toDiscard.add(discard);
|
||||||
|
}
|
||||||
|
discard = discard.previousElementSibling;
|
||||||
|
}
|
||||||
|
discard = keep.nextElementSibling;
|
||||||
|
while ( discard !== null ) {
|
||||||
|
if (
|
||||||
|
nonVisualElements[discard.localName] !== true &&
|
||||||
|
toKeep.has(discard) === false
|
||||||
|
) {
|
||||||
|
toDiscard.add(discard);
|
||||||
|
}
|
||||||
|
discard = discard.nextElementSibling;
|
||||||
|
}
|
||||||
|
keep = keep.parentElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ( discard of toDiscard ) {
|
||||||
|
output.push(discard);
|
||||||
|
}
|
||||||
|
this.targets.clear();
|
||||||
|
}
|
||||||
|
transpose(candidate) {
|
||||||
|
for ( const target of this.targets ) {
|
||||||
|
if ( target.contains(candidate) ) { return; }
|
||||||
|
if ( candidate.contains(target) ) {
|
||||||
|
this.targets.delete(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.targets.add(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// https://github.com/AdguardTeam/ExtendedCss/issues/31#issuecomment-302391277
|
// https://github.com/AdguardTeam/ExtendedCss/issues/31#issuecomment-302391277
|
||||||
// Prepend `:scope ` if needed.
|
// Prepend `:scope ` if needed.
|
||||||
const PSelectorSpathTask = class {
|
class PSelectorSpathTask extends PSelectorTask {
|
||||||
constructor(task) {
|
constructor(task) {
|
||||||
|
super();
|
||||||
this.spath = task[1];
|
this.spath = task[1];
|
||||||
this.nth = /^(?:\s*[+~]|:)/.test(this.spath);
|
this.nth = /^(?:\s*[+~]|:)/.test(this.spath);
|
||||||
if ( this.nth ) { return; }
|
if ( this.nth ) { return; }
|
||||||
@ -151,10 +223,11 @@ const PSelectorSpathTask = class {
|
|||||||
output.push(node);
|
output.push(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const PSelectorUpwardTask = class {
|
class PSelectorUpwardTask extends PSelectorTask {
|
||||||
constructor(task) {
|
constructor(task) {
|
||||||
|
super();
|
||||||
const arg = task[1];
|
const arg = task[1];
|
||||||
if ( typeof arg === 'number' ) {
|
if ( typeof arg === 'number' ) {
|
||||||
this.i = arg;
|
this.i = arg;
|
||||||
@ -179,12 +252,13 @@ const PSelectorUpwardTask = class {
|
|||||||
}
|
}
|
||||||
output.push(node);
|
output.push(node);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
PSelectorUpwardTask.prototype.i = 0;
|
PSelectorUpwardTask.prototype.i = 0;
|
||||||
PSelectorUpwardTask.prototype.s = '';
|
PSelectorUpwardTask.prototype.s = '';
|
||||||
|
|
||||||
const PSelectorWatchAttrs = class {
|
class PSelectorWatchAttrs extends PSelectorTask {
|
||||||
constructor(task) {
|
constructor(task) {
|
||||||
|
super();
|
||||||
this.observer = null;
|
this.observer = null;
|
||||||
this.observed = new WeakSet();
|
this.observed = new WeakSet();
|
||||||
this.observerOptions = {
|
this.observerOptions = {
|
||||||
@ -213,10 +287,11 @@ const PSelectorWatchAttrs = class {
|
|||||||
this.observer.observe(node, this.observerOptions);
|
this.observer.observe(node, this.observerOptions);
|
||||||
this.observed.add(node);
|
this.observed.add(node);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const PSelectorXpathTask = class {
|
class PSelectorXpathTask extends PSelectorTask {
|
||||||
constructor(task) {
|
constructor(task) {
|
||||||
|
super();
|
||||||
this.xpe = document.createExpression(task[1], null);
|
this.xpe = document.createExpression(task[1], null);
|
||||||
this.xpr = null;
|
this.xpr = null;
|
||||||
}
|
}
|
||||||
@ -234,9 +309,9 @@ const PSelectorXpathTask = class {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const PSelector = class {
|
class PSelector {
|
||||||
constructor(o) {
|
constructor(o) {
|
||||||
if ( PSelector.prototype.operatorToTaskMap === undefined ) {
|
if ( PSelector.prototype.operatorToTaskMap === undefined ) {
|
||||||
PSelector.prototype.operatorToTaskMap = new Map([
|
PSelector.prototype.operatorToTaskMap = new Map([
|
||||||
@ -251,6 +326,7 @@ const PSelector = class {
|
|||||||
[ ':min-text-length', PSelectorMinTextLengthTask ],
|
[ ':min-text-length', PSelectorMinTextLengthTask ],
|
||||||
[ ':not', PSelectorIfNotTask ],
|
[ ':not', PSelectorIfNotTask ],
|
||||||
[ ':nth-ancestor', PSelectorUpwardTask ],
|
[ ':nth-ancestor', PSelectorUpwardTask ],
|
||||||
|
[ ':others', PSelectorOthersTask ],
|
||||||
[ ':spath', PSelectorSpathTask ],
|
[ ':spath', PSelectorSpathTask ],
|
||||||
[ ':upward', PSelectorUpwardTask ],
|
[ ':upward', PSelectorUpwardTask ],
|
||||||
[ ':watch-attr', PSelectorWatchAttrs ],
|
[ ':watch-attr', PSelectorWatchAttrs ],
|
||||||
@ -258,15 +334,18 @@ const PSelector = class {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
this.raw = o.raw;
|
this.raw = o.raw;
|
||||||
this.selector = o.selector;
|
this.selector = ':root > :root';
|
||||||
this.tasks = [];
|
this.tasks = [];
|
||||||
const tasks = o.tasks;
|
const tasks = [];
|
||||||
if ( Array.isArray(tasks) === false ) { return; }
|
if ( Array.isArray(o.tasks) === false ) { return; }
|
||||||
for ( const task of tasks ) {
|
for ( const task of o.tasks ) {
|
||||||
this.tasks.push(
|
const ctor = this.operatorToTaskMap.get(task[0]);
|
||||||
new (this.operatorToTaskMap.get(task[0]))(task)
|
if ( ctor === undefined ) { return; }
|
||||||
);
|
tasks.push(new ctor(task));
|
||||||
}
|
}
|
||||||
|
// Initialize only after all tasks have been successfully instantiated
|
||||||
|
this.selector = o.selector;
|
||||||
|
this.tasks = tasks;
|
||||||
}
|
}
|
||||||
prime(input) {
|
prime(input) {
|
||||||
const root = input || document;
|
const root = input || document;
|
||||||
@ -278,9 +357,11 @@ const PSelector = class {
|
|||||||
for ( const task of this.tasks ) {
|
for ( const task of this.tasks ) {
|
||||||
if ( nodes.length === 0 ) { break; }
|
if ( nodes.length === 0 ) { break; }
|
||||||
const transposed = [];
|
const transposed = [];
|
||||||
|
task.begin();
|
||||||
for ( const node of nodes ) {
|
for ( const node of nodes ) {
|
||||||
task.transpose(node, transposed);
|
task.transpose(node, transposed);
|
||||||
}
|
}
|
||||||
|
task.end(transposed);
|
||||||
nodes = transposed;
|
nodes = transposed;
|
||||||
}
|
}
|
||||||
return nodes;
|
return nodes;
|
||||||
@ -291,9 +372,11 @@ const PSelector = class {
|
|||||||
let output = [ node ];
|
let output = [ node ];
|
||||||
for ( const task of this.tasks ) {
|
for ( const task of this.tasks ) {
|
||||||
const transposed = [];
|
const transposed = [];
|
||||||
|
task.begin();
|
||||||
for ( const node of output ) {
|
for ( const node of output ) {
|
||||||
task.transpose(node, transposed);
|
task.transpose(node, transposed);
|
||||||
}
|
}
|
||||||
|
task.end(transposed);
|
||||||
output = transposed;
|
output = transposed;
|
||||||
if ( output.length === 0 ) { break; }
|
if ( output.length === 0 ) { break; }
|
||||||
}
|
}
|
||||||
@ -301,10 +384,10 @@ const PSelector = class {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
PSelector.prototype.operatorToTaskMap = undefined;
|
PSelector.prototype.operatorToTaskMap = undefined;
|
||||||
|
|
||||||
const PSelectorRoot = class extends PSelector {
|
class PSelectorRoot extends PSelector {
|
||||||
constructor(o, styleToken) {
|
constructor(o, styleToken) {
|
||||||
super(o);
|
super(o);
|
||||||
this.budget = 200; // I arbitrary picked a 1/5 second
|
this.budget = 200; // I arbitrary picked a 1/5 second
|
||||||
@ -313,10 +396,10 @@ const PSelectorRoot = class extends PSelector {
|
|||||||
this.lastAllowanceTime = 0;
|
this.lastAllowanceTime = 0;
|
||||||
this.styleToken = styleToken;
|
this.styleToken = styleToken;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
PSelectorRoot.prototype.hit = false;
|
PSelectorRoot.prototype.hit = false;
|
||||||
|
|
||||||
const ProceduralFilterer = class {
|
class ProceduralFilterer {
|
||||||
constructor(domFilterer) {
|
constructor(domFilterer) {
|
||||||
this.domFilterer = domFilterer;
|
this.domFilterer = domFilterer;
|
||||||
this.domIsReady = false;
|
this.domIsReady = false;
|
||||||
@ -457,7 +540,7 @@ const ProceduralFilterer = class {
|
|||||||
removedNodes;
|
removedNodes;
|
||||||
this.domFilterer.commit();
|
this.domFilterer.commit();
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
vAPI.DOMProceduralFilterer = ProceduralFilterer;
|
vAPI.DOMProceduralFilterer = ProceduralFilterer;
|
||||||
|
|
||||||
|
@ -1575,7 +1575,7 @@ Parser.prototype.SelectorCompiler = class {
|
|||||||
if ( this.querySelectable(s) ) { return s; }
|
if ( this.querySelectable(s) ) { return s; }
|
||||||
}
|
}
|
||||||
|
|
||||||
compileRemoveSelector(s) {
|
compileNoArgument(s) {
|
||||||
if ( s === '' ) { return s; }
|
if ( s === '' ) { return s; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1683,6 +1683,7 @@ Parser.prototype.SelectorCompiler = class {
|
|||||||
raw.push(task[1]);
|
raw.push(task[1]);
|
||||||
break;
|
break;
|
||||||
case ':min-text-length':
|
case ':min-text-length':
|
||||||
|
case ':others':
|
||||||
case ':upward':
|
case ':upward':
|
||||||
case ':watch-attr':
|
case ':watch-attr':
|
||||||
case ':xpath':
|
case ':xpath':
|
||||||
@ -1860,8 +1861,10 @@ Parser.prototype.SelectorCompiler = class {
|
|||||||
return this.compileInteger(args);
|
return this.compileInteger(args);
|
||||||
case ':not':
|
case ':not':
|
||||||
return this.compileNotSelector(args);
|
return this.compileNotSelector(args);
|
||||||
|
case ':others':
|
||||||
|
return this.compileNoArgument(args);
|
||||||
case ':remove':
|
case ':remove':
|
||||||
return this.compileRemoveSelector(args);
|
return this.compileNoArgument(args);
|
||||||
case ':spath':
|
case ':spath':
|
||||||
return this.compileSpathExpression(args);
|
return this.compileSpathExpression(args);
|
||||||
case ':style':
|
case ':style':
|
||||||
@ -1878,6 +1881,9 @@ Parser.prototype.SelectorCompiler = class {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// bit 0: can be used as auto-completion hint
|
||||||
|
// bit 1: can not be used in HTML filtering
|
||||||
|
//
|
||||||
Parser.prototype.proceduralOperatorTokens = new Map([
|
Parser.prototype.proceduralOperatorTokens = new Map([
|
||||||
[ '-abp-contains', 0b00 ],
|
[ '-abp-contains', 0b00 ],
|
||||||
[ '-abp-has', 0b00, ],
|
[ '-abp-has', 0b00, ],
|
||||||
@ -1893,6 +1899,7 @@ Parser.prototype.proceduralOperatorTokens = new Map([
|
|||||||
[ 'min-text-length', 0b01 ],
|
[ 'min-text-length', 0b01 ],
|
||||||
[ 'not', 0b01 ],
|
[ 'not', 0b01 ],
|
||||||
[ 'nth-ancestor', 0b00 ],
|
[ 'nth-ancestor', 0b00 ],
|
||||||
|
[ 'others', 0b01 ],
|
||||||
[ 'remove', 0b11 ],
|
[ 'remove', 0b11 ],
|
||||||
[ 'style', 0b11 ],
|
[ 'style', 0b11 ],
|
||||||
[ 'upward', 0b01 ],
|
[ 'upward', 0b01 ],
|
||||||
|
Loading…
Reference in New Issue
Block a user