Replace exec with transpose in procedural filters

The purpose is to avoid having to iterate through
all input nodes at each operator implementation
level. The `transpose` method deals with only one
input node, and the iteration is performed by the
main procedural filtering entry points.

Additionally:
- Add `:spath` to HTML filtering
- Rename `:watch-attrs` to `:watch-attr`
  - `:watch=attrs` is deprecated and will be kept around
    until it is safe to remove it completely
This commit is contained in:
Raymond Hill 2019-06-23 08:05:53 -04:00
parent 4956a166d3
commit 41685f4cce
No known key found for this signature in database
GPG key ID: 25E1490B761470C2
3 changed files with 140 additions and 169 deletions

View file

@ -469,14 +469,10 @@ vAPI.DOMFilterer = (function() {
} }
this.needle = new RegExp(arg0, arg1); this.needle = new RegExp(arg0, arg1);
} }
exec(input) { transpose(node, output) {
const output = []; if ( this.needle.test(node.textContent) ) {
for ( const node of input ) { output.push(node);
if ( this.needle.test(node.textContent) ) {
output.push(node);
}
} }
return output;
} }
}; };
@ -484,24 +480,17 @@ vAPI.DOMFilterer = (function() {
constructor(task) { constructor(task) {
this.pselector = new PSelector(task[1]); this.pselector = new PSelector(task[1]);
} }
exec(input) { transpose(node, output) {
const output = []; if ( this.pselector.test(node) === this.target ) {
for ( const node of input ) { output.push(node);
if ( this.pselector.test(node) === this.target ) {
output.push(node);
}
} }
return output;
} }
}; };
PSelectorIfTask.prototype.target = true; PSelectorIfTask.prototype.target = true;
const PSelectorIfNotTask = class extends PSelectorIfTask { const PSelectorIfNotTask = class extends PSelectorIfTask {
constructor(task) {
super(task);
this.target = false;
}
}; };
PSelectorIfNotTask.prototype.target = false;
const PSelectorMatchesCSSTask = class { const PSelectorMatchesCSSTask = class {
constructor(task) { constructor(task) {
@ -512,46 +501,31 @@ vAPI.DOMFilterer = (function() {
} }
this.value = new RegExp(arg0, arg1); this.value = new RegExp(arg0, arg1);
} }
exec(input) { transpose(node, output) {
const output = []; const style = window.getComputedStyle(node, this.pseudo);
for ( const node of input ) { if ( style !== null && this.value.test(style[this.name]) ) {
const style = window.getComputedStyle(node, this.pseudo); output.push(node);
if ( style === null ) { return null; } /* FF */
if ( this.value.test(style[this.name]) ) {
output.push(node);
}
} }
return output;
} }
}; };
PSelectorMatchesCSSTask.prototype.pseudo = null; PSelectorMatchesCSSTask.prototype.pseudo = null;
const PSelectorMatchesCSSAfterTask = class extends PSelectorMatchesCSSTask { const PSelectorMatchesCSSAfterTask = class extends PSelectorMatchesCSSTask {
constructor(task) {
super(task);
this.pseudo = ':after';
}
}; };
PSelectorMatchesCSSAfterTask.prototype.pseudo = ':after';
const PSelectorMatchesCSSBeforeTask = class extends PSelectorMatchesCSSTask { const PSelectorMatchesCSSBeforeTask = class extends PSelectorMatchesCSSTask {
constructor(task) {
super(task);
this.pseudo = ':before';
}
}; };
PSelectorMatchesCSSBeforeTask.prototype.pseudo = ':before';
const PSelectorMinTextLengthTask = class { const PSelectorMinTextLengthTask = class {
constructor(task) { constructor(task) {
this.min = task[1]; this.min = task[1];
} }
exec(input) { transpose(node, output) {
const output = []; if ( node.textContent.length >= this.min ) {
for ( const node of input ) { output.push(node);
if ( node.textContent.length >= this.min ) {
output.push(node);
}
} }
return output;
} }
}; };
@ -559,20 +533,15 @@ vAPI.DOMFilterer = (function() {
constructor(task) { constructor(task) {
this.nth = task[1]; this.nth = task[1];
} }
exec(input) { transpose(node, output) {
const output = []; let nth = this.nth;
for ( let node of input ) { for (;;) {
let nth = this.nth; node = node.parentElement;
for (;;) { if ( node === null ) { return; }
node = node.parentElement; nth -= 1;
if ( node === null ) { break; } if ( nth === 0 ) { break; }
nth -= 1;
if ( nth !== 0 ) { continue; }
output.push(node);
break;
}
} }
return output; output.push(node);
} }
}; };
@ -580,25 +549,21 @@ vAPI.DOMFilterer = (function() {
constructor(task) { constructor(task) {
this.spath = task[1]; this.spath = task[1];
} }
exec(input) { transpose(node, output) {
const output = []; const parent = node.parentElement;
for ( let node of input ) { if ( parent === null ) { return; }
const parent = node.parentElement; let pos = 1;
if ( parent === null ) { continue; } for (;;) {
let pos = 1; node = node.previousElementSibling;
for (;;) { if ( node === null ) { break; }
node = node.previousElementSibling; pos += 1;
if ( node === null ) { break; } }
pos += 1; const nodes = parent.querySelectorAll(
} `:scope > :nth-child(${pos})${this.spath}`
const nodes = parent.querySelectorAll( );
':scope > :nth-child(' + pos + ')' + this.spath for ( const node of nodes ) {
); output.push(node);
for ( const node of nodes ) {
output.push(node);
}
} }
return output;
} }
}; };
@ -623,17 +588,14 @@ vAPI.DOMFilterer = (function() {
filterer.onDOMChanged([ null ]); filterer.onDOMChanged([ null ]);
} }
} }
exec(input) { transpose(node, output) {
if ( input.length === 0 ) { return input; } output.push(node);
if ( this.observed.has(node) ) { return; }
if ( this.observer === null ) { if ( this.observer === null ) {
this.observer = new MutationObserver(this.handler); this.observer = new MutationObserver(this.handler);
} }
for ( const node of input ) { this.observer.observe(node, this.observerOptions);
if ( this.observed.has(node) ) { continue; } this.observed.add(node);
this.observer.observe(node, this.observerOptions);
this.observed.add(node);
}
return input;
} }
}; };
@ -642,23 +604,19 @@ vAPI.DOMFilterer = (function() {
this.xpe = document.createExpression(task[1], null); this.xpe = document.createExpression(task[1], null);
this.xpr = null; this.xpr = null;
} }
exec(input) { transpose(node, output) {
const output = []; this.xpr = this.xpe.evaluate(
for ( const node of input ) { node,
this.xpr = this.xpe.evaluate( XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
node, this.xpr
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, );
this.xpr let j = this.xpr.snapshotLength;
); while ( j-- ) {
let j = this.xpr.snapshotLength; const node = this.xpr.snapshotItem(j);
while ( j-- ) { if ( node.nodeType === 1 ) {
const node = this.xpr.snapshotItem(j); output.push(node);
if ( node.nodeType === 1 ) {
output.push(node);
}
} }
} }
return output;
} }
}; };
@ -677,7 +635,7 @@ vAPI.DOMFilterer = (function() {
[ ':not', PSelectorIfNotTask ], [ ':not', PSelectorIfNotTask ],
[ ':nth-ancestor', PSelectorNthAncestorTask ], [ ':nth-ancestor', PSelectorNthAncestorTask ],
[ ':spath', PSelectorSpathTask ], [ ':spath', PSelectorSpathTask ],
[ ':watch-attrs', PSelectorWatchAttrs ], [ ':watch-attr', PSelectorWatchAttrs ],
[ ':xpath', PSelectorXpathTask ], [ ':xpath', PSelectorXpathTask ],
]); ]);
} }
@ -704,21 +662,27 @@ vAPI.DOMFilterer = (function() {
let nodes = this.prime(input); let nodes = this.prime(input);
for ( const task of this.tasks ) { for ( const task of this.tasks ) {
if ( nodes.length === 0 ) { break; } if ( nodes.length === 0 ) { break; }
nodes = task.exec(nodes); const transposed = [];
for ( const node of nodes ) {
task.transpose(node, transposed);
}
nodes = transposed;
} }
return nodes; return nodes;
} }
test(input) { test(input) {
const nodes = this.prime(input); const nodes = this.prime(input);
const AA = [ null ];
for ( const node of nodes ) { for ( const node of nodes ) {
AA[0] = node; let output = [ node ];
let aa = AA;
for ( const task of this.tasks ) { for ( const task of this.tasks ) {
aa = task.exec(aa); const transposed = [];
if ( aa.length === 0 ) { break; } for ( const node of output ) {
task.transpose(node, transposed);
}
output = transposed;
if ( output.length === 0 ) { break; }
} }
if ( aa.length !== 0 ) { return true; } if ( output.length !== 0 ) { return true; }
} }
return false; return false;
} }

View file

@ -50,14 +50,10 @@
} }
this.needle = new RegExp(arg0, arg1); this.needle = new RegExp(arg0, arg1);
} }
exec(input) { transpose(node, output) {
const output = []; if ( this.needle.test(node.textContent) ) {
for ( const node of input ) { output.push(node);
if ( this.needle.test(node.textContent) ) {
output.push(node);
}
} }
return output;
} }
}; };
@ -65,14 +61,10 @@
constructor(task) { constructor(task) {
this.pselector = new PSelector(task[1]); this.pselector = new PSelector(task[1]);
} }
exec(input) { transpose(node, output) {
const output = []; if ( this.pselector.test(node) === this.target ) {
for ( const node of input ) { output.push(node);
if ( this.pselector.test(node) === this.target ) {
output.push(node);
}
} }
return output;
} }
get invalid() { get invalid() {
return this.pselector.invalid; return this.pselector.invalid;
@ -81,24 +73,17 @@
PSelectorIfTask.prototype.target = true; PSelectorIfTask.prototype.target = true;
const PSelectorIfNotTask = class extends PSelectorIfTask { const PSelectorIfNotTask = class extends PSelectorIfTask {
constructor(task) {
super(task);
this.target = false;
}
}; };
PSelectorIfNotTask.prototype.target = false;
const PSelectorMinTextLengthTask = class { const PSelectorMinTextLengthTask = class {
constructor(task) { constructor(task) {
this.min = task[1]; this.min = task[1];
} }
exec(input) { transpose(node, output) {
const output = []; if ( node.textContent.length >= this.min ) {
for ( const node of input ) { output.push(node);
if ( node.textContent.length >= this.min ) {
output.push(node);
}
} }
return output;
} }
}; };
@ -106,20 +91,37 @@
constructor(task) { constructor(task) {
this.nth = task[1]; this.nth = task[1];
} }
exec(input) { transpose(node, output) {
const output = []; let nth = this.nth;
for ( let node of input ) { for (;;) {
let nth = this.nth; node = node.parentElement;
for (;;) { if ( node === null ) { return; }
node = node.parentElement; nth -= 1;
if ( node === null ) { break; } if ( nth === 0 ) { break; }
nth -= 1; }
if ( nth !== 0 ) { continue; } output.push(node);
output.push(node); }
break; };
}
const PSelectorSpathTask = class {
constructor(task) {
this.spath = task[1];
}
transpose(node, output) {
const parent = node.parentElement;
if ( parent === null ) { return; }
let pos = 1;
for (;;) {
node = node.previousElementSibling;
if ( node === null ) { break; }
pos += 1;
}
const nodes = parent.querySelectorAll(
`:scope > :nth-child(${pos})${this.spath}`
);
for ( const node of nodes ) {
output.push(node);
} }
return output;
} }
}; };
@ -127,25 +129,21 @@
constructor(task) { constructor(task) {
this.xpe = task[1]; this.xpe = task[1];
} }
exec(input) { transpose(node, output) {
const output = []; const xpr = docRegister.evaluate(
const xpe = docRegister.createExpression(this.xpe, null); this.xpe,
let xpr = null; node,
for ( const node of input ) { null,
xpr = xpe.evaluate( XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
node, null
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, );
xpr let j = xpr.snapshotLength;
); while ( j-- ) {
let j = xpr.snapshotLength; const node = xpr.snapshotItem(j);
while ( j-- ) { if ( node.nodeType === 1 ) {
const node = xpr.snapshotItem(j); output.push(node);
if ( node.nodeType === 1 ) {
output.push(node);
}
} }
} }
return output;
} }
}; };
@ -181,22 +179,28 @@
let nodes = this.prime(input); let nodes = this.prime(input);
for ( const task of this.tasks ) { for ( const task of this.tasks ) {
if ( nodes.length === 0 ) { break; } if ( nodes.length === 0 ) { break; }
nodes = task.exec(nodes); const transposed = [];
for ( const node of nodes ) {
task.transpose(node, transposed);
}
nodes = transposed;
} }
return nodes; return nodes;
} }
test(input) { test(input) {
if ( this.invalid ) { return false; } if ( this.invalid ) { return false; }
const nodes = this.prime(input); const nodes = this.prime(input);
const AA = [ null ];
for ( const node of nodes ) { for ( const node of nodes ) {
AA[0] = node; let output = [ node ];
let aa = AA;
for ( const task of this.tasks ) { for ( const task of this.tasks ) {
aa = task.exec(aa); const transposed = [];
if ( aa.length === 0 ) { break; } for ( const node of output ) {
task.transpose(node, transposed);
}
output = transposed;
if ( output.length === 0 ) { break; }
} }
if ( aa.length !== 0 ) { return true; } if ( output.length !== 0 ) { return true; }
} }
return false; return false;
} }
@ -209,7 +213,8 @@
[ ':min-text-length', PSelectorMinTextLengthTask ], [ ':min-text-length', PSelectorMinTextLengthTask ],
[ ':not', PSelectorIfNotTask ], [ ':not', PSelectorIfNotTask ],
[ ':nth-ancestor', PSelectorNthAncestorTask ], [ ':nth-ancestor', PSelectorNthAncestorTask ],
[ ':xpath', PSelectorXpathTask ] [ ':spath', PSelectorSpathTask ],
[ ':xpath', PSelectorXpathTask ],
]); ]);
PSelector.prototype.invalid = false; PSelector.prototype.invalid = false;

View file

@ -169,6 +169,7 @@
'min-text-length', 'min-text-length',
'not', 'not',
'nth-ancestor', 'nth-ancestor',
'watch-attr',
'watch-attrs', 'watch-attrs',
'xpath' 'xpath'
].join('|'), ].join('|'),
@ -285,6 +286,7 @@
[ ':-abp-contains', ':has-text' ], [ ':-abp-contains', ':has-text' ],
[ ':-abp-has', ':has' ], [ ':-abp-has', ':has' ],
[ ':contains', ':has-text' ], [ ':contains', ':has-text' ],
[ ':watch-attrs', ':watch-attr' ],
]); ]);
const compileArgument = new Map([ const compileArgument = new Map([
@ -299,7 +301,7 @@
[ ':not', compileNotSelector ], [ ':not', compileNotSelector ],
[ ':nth-ancestor', compileNthAncestorSelector ], [ ':nth-ancestor', compileNthAncestorSelector ],
[ ':spath', compileSpathExpression ], [ ':spath', compileSpathExpression ],
[ ':watch-attrs', compileAttrList ], [ ':watch-attr', compileAttrList ],
[ ':xpath', compileXpathExpression ] [ ':xpath', compileXpathExpression ]
]); ]);
@ -356,7 +358,7 @@
break; break;
case ':min-text-length': case ':min-text-length':
case ':nth-ancestor': case ':nth-ancestor':
case ':watch-attrs': case ':watch-attr':
case ':xpath': case ':xpath':
raw.push(`${task[0]}(${task[1]})`); raw.push(`${task[0]}(${task[1]})`);
break; break;