Skip to content

Commit

Permalink
Replace exec with transpose in procedural filters
Browse files Browse the repository at this point in the history
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:
- Rename `:watch-attrs` to `:watch-attr`
  - `:watch=attrs` is deprecated and will be kept around
    until it is safe to remove it completely
  • Loading branch information
gorhill authored and hawkeye116477 committed Jun 29, 2020
1 parent 8123d49 commit 279fe27
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 106 deletions.
172 changes: 68 additions & 104 deletions src/js/contentscript.js
Original file line number Diff line number Diff line change
Expand Up @@ -377,39 +377,28 @@ vAPI.DOMFilterer = (function() {
}
this.needle = new RegExp(arg0, arg1);
}
exec(input) {
const output = [];
for ( const node of input ) {
if ( this.needle.test(node.textContent) ) {
output.push(node);
}
transpose(node, output) {
if ( this.needle.test(node.textContent) ) {
output.push(node);
}
return output;
}
};

const PSelectorIfTask = class {
constructor(task) {
this.pselector = new PSelector(task[1]);
}
exec(input) {
const output = [];
for ( const node of input ) {
if ( this.pselector.test(node) === this.target ) {
output.push(node);
}
transpose(node, output) {
if ( this.pselector.test(node) === this.target ) {
output.push(node);
}
return output;
}
};
PSelectorIfTask.prototype.target = true;

const PSelectorIfNotTask = class extends PSelectorIfTask {
constructor(task) {
super(task);
this.target = false;
}
};
PSelectorIfNotTask.prototype.target = false;

const PSelectorMatchesCSSTask = class {
constructor(task) {
Expand All @@ -420,93 +409,69 @@ vAPI.DOMFilterer = (function() {
}
this.value = new RegExp(arg0, arg1);
}
exec(input) {
const output = [];
for ( const node of input ) {
const style = window.getComputedStyle(node, this.pseudo);
if ( style === null ) { return null; } /* FF */
if ( this.value.test(style[this.name]) ) {
output.push(node);
}
transpose(node, output) {
const style = window.getComputedStyle(node, this.pseudo);
if ( style !== null && this.value.test(style[this.name]) ) {
output.push(node);
}
return output;
}
};
PSelectorMatchesCSSTask.prototype.pseudo = null;

const PSelectorMatchesCSSAfterTask = class extends PSelectorMatchesCSSTask {
constructor(task) {
super(task);
this.pseudo = ':after';
}
};
PSelectorMatchesCSSAfterTask.prototype.pseudo = ':after';

const PSelectorMatchesCSSBeforeTask = class extends PSelectorMatchesCSSTask {
constructor(task) {
super(task);
this.pseudo = ':before';
}
};
PSelectorMatchesCSSBeforeTask.prototype.pseudo = ':before';

const PSelectorMinTextLengthTask = class {
constructor(task) {
this.min = task[1];
}
exec(input) {
const output = [];
for ( const node of input ) {
if ( node.textContent.length >= this.min ) {
output.push(node);
}
transpose(node, output) {
if ( node.textContent.length >= this.min ) {
output.push(node);
}
return output;
}
};

const PSelectorNthAncestorTask = class {
constructor(task) {
this.nth = task[1];
}
exec(input) {
const output = [];
for ( let node of input ) {
let nth = this.nth;
for (;;) {
node = node.parentElement;
if ( node === null ) { break; }
nth -= 1;
if ( nth !== 0 ) { continue; }
output.push(node);
break;
}
transpose(node, output) {
let nth = this.nth;
for (;;) {
node = node.parentElement;
if ( node === null ) { return; }
nth -= 1;
if ( nth === 0 ) { break; }
}
return output;
output.push(node);
}
};

const PSelectorSpathTask = class {
constructor(task) {
this.spath = task[1];
}
exec(input) {
const output = [];
for ( let node of input ) {
const parent = node.parentElement;
if ( parent === null ) { continue; }
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);
}
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;
}
};

Expand All @@ -531,17 +496,14 @@ vAPI.DOMFilterer = (function() {
filterer.onDOMChanged([ null ]);
}
}
exec(input) {
if ( input.length === 0 ) { return input; }
transpose(node, output) {
output.push(node);
if ( this.observed.has(node) ) { return; }
if ( this.observer === null ) {
this.observer = new MutationObserver(this.handler);
}
for ( const node of input ) {
if ( this.observed.has(node) ) { continue; }
this.observer.observe(node, this.observerOptions);
this.observed.add(node);
}
return input;
this.observer.observe(node, this.observerOptions);
this.observed.add(node);
}
};

Expand All @@ -550,23 +512,19 @@ vAPI.DOMFilterer = (function() {
this.xpe = document.createExpression(task[1], null);
this.xpr = null;
}
exec(input) {
const output = [];
for ( const node of input ) {
this.xpr = this.xpe.evaluate(
node,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
this.xpr
);
let j = this.xpr.snapshotLength;
while ( j-- ) {
const node = this.xpr.snapshotItem(j);
if ( node.nodeType === 1 ) {
output.push(node);
}
transpose(node, output) {
this.xpr = this.xpe.evaluate(
node,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
this.xpr
);
let j = this.xpr.snapshotLength;
while ( j-- ) {
const node = this.xpr.snapshotItem(j);
if ( node.nodeType === 1 ) {
output.push(node);
}
}
return output;
}
};

Expand All @@ -585,7 +543,7 @@ vAPI.DOMFilterer = (function() {
[ ':not', PSelectorIfNotTask ],
[ ':nth-ancestor', PSelectorNthAncestorTask ],
[ ':spath', PSelectorSpathTask ],
[ ':watch-attrs', PSelectorWatchAttrs ],
[ ':watch-attr', PSelectorWatchAttrs ],
[ ':xpath', PSelectorXpathTask ],
]);
}
Expand All @@ -612,21 +570,27 @@ vAPI.DOMFilterer = (function() {
let nodes = this.prime(input);
for ( const task of this.tasks ) {
if ( nodes.length === 0 ) { break; }
nodes = task.exec(nodes);
const transposed = [];
for ( const node of nodes ) {
task.transpose(node, transposed);
}
nodes = transposed;
}
return nodes;
}
test(input) {
const nodes = this.prime(input);
const AA = [ null ];
for ( const node of nodes ) {
AA[0] = node;
let aa = AA;
let output = [ node ];
for ( const task of this.tasks ) {
aa = task.exec(aa);
if ( aa.length === 0 ) { break; }
const transposed = [];
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;
}
Expand Down
6 changes: 4 additions & 2 deletions src/js/static-ext-filtering.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@
'min-text-length',
'not',
'nth-ancestor',
'watch-attr',
'watch-attrs',
'xpath'
].join('|'),
Expand Down Expand Up @@ -281,6 +282,7 @@
[ ':-abp-contains', ':has-text' ],
[ ':-abp-has', ':has' ],
[ ':contains', ':has-text' ],
[ ':watch-attrs', ':watch-attr' ],
]);

const compileArgument = new Map([
Expand All @@ -295,7 +297,7 @@
[ ':not', compileNotSelector ],
[ ':nth-ancestor', compileNthAncestorSelector ],
[ ':spath', compileSpathExpression ],
[ ':watch-attrs', compileAttrList ],
[ ':watch-attr', compileAttrList ],
[ ':xpath', compileXpathExpression ]
]);

Expand Down Expand Up @@ -352,7 +354,7 @@
break;
case ':min-text-length':
case ':nth-ancestor':
case ':watch-attrs':
case ':watch-attr':
case ':xpath':
raw.push(`${task[0]}(${task[1]})`);
break;
Expand Down

0 comments on commit 279fe27

Please sign in to comment.