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:
- 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
  • Loading branch information
gorhill committed Jun 23, 2019
1 parent 4956a16 commit 41685f4
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 169 deletions.
172 changes: 68 additions & 104 deletions src/js/contentscript.js
Original file line number Diff line number Diff line change
Expand Up @@ -469,39 +469,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 @@ -512,93 +501,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 @@ -623,17 +588,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 @@ -642,23 +604,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 @@ -677,7 +635,7 @@ vAPI.DOMFilterer = (function() {
[ ':not', PSelectorIfNotTask ],
[ ':nth-ancestor', PSelectorNthAncestorTask ],
[ ':spath', PSelectorSpathTask ],
[ ':watch-attrs', PSelectorWatchAttrs ],
[ ':watch-attr', PSelectorWatchAttrs ],
[ ':xpath', PSelectorXpathTask ],
]);
}
Expand All @@ -704,21 +662,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
Loading

0 comments on commit 41685f4

Please sign in to comment.