From 41685f4cce084f3f89e9cdd8fc1cde5b57862958 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 23 Jun 2019 08:05:53 -0400 Subject: [PATCH] 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 --- src/js/contentscript.js | 172 +++++++++++++-------------------- src/js/html-filtering.js | 131 +++++++++++++------------ src/js/static-ext-filtering.js | 6 +- 3 files changed, 140 insertions(+), 169 deletions(-) diff --git a/src/js/contentscript.js b/src/js/contentscript.js index 05f5446a8..f7aa657cb 100644 --- a/src/js/contentscript.js +++ b/src/js/contentscript.js @@ -469,14 +469,10 @@ 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; } }; @@ -484,24 +480,17 @@ vAPI.DOMFilterer = (function() { 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) { @@ -512,46 +501,31 @@ 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; } }; @@ -559,20 +533,15 @@ vAPI.DOMFilterer = (function() { 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); } }; @@ -580,25 +549,21 @@ vAPI.DOMFilterer = (function() { 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; } }; @@ -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); } }; @@ -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; } }; @@ -677,7 +635,7 @@ vAPI.DOMFilterer = (function() { [ ':not', PSelectorIfNotTask ], [ ':nth-ancestor', PSelectorNthAncestorTask ], [ ':spath', PSelectorSpathTask ], - [ ':watch-attrs', PSelectorWatchAttrs ], + [ ':watch-attr', PSelectorWatchAttrs ], [ ':xpath', PSelectorXpathTask ], ]); } @@ -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; } diff --git a/src/js/html-filtering.js b/src/js/html-filtering.js index e79fd401c..5e2db800d 100644 --- a/src/js/html-filtering.js +++ b/src/js/html-filtering.js @@ -50,14 +50,10 @@ } 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; } }; @@ -65,14 +61,10 @@ 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; } get invalid() { return this.pselector.invalid; @@ -81,24 +73,17 @@ PSelectorIfTask.prototype.target = true; const PSelectorIfNotTask = class extends PSelectorIfTask { - constructor(task) { - super(task); - this.target = false; - } }; + PSelectorIfNotTask.prototype.target = false; 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; } }; @@ -106,20 +91,37 @@ 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; } + } + output.push(node); + } + }; + + 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) { this.xpe = task[1]; } - exec(input) { - const output = []; - const xpe = docRegister.createExpression(this.xpe, null); - let xpr = null; - for ( const node of input ) { - xpr = xpe.evaluate( - node, - XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, - xpr - ); - let j = xpr.snapshotLength; - while ( j-- ) { - const node = xpr.snapshotItem(j); - if ( node.nodeType === 1 ) { - output.push(node); - } + transpose(node, output) { + const xpr = docRegister.evaluate( + this.xpe, + node, + null, + XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, + null + ); + let j = xpr.snapshotLength; + while ( j-- ) { + const node = xpr.snapshotItem(j); + if ( node.nodeType === 1 ) { + output.push(node); } } - return output; } }; @@ -181,22 +179,28 @@ 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) { if ( this.invalid ) { return false; } 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; } @@ -209,7 +213,8 @@ [ ':min-text-length', PSelectorMinTextLengthTask ], [ ':not', PSelectorIfNotTask ], [ ':nth-ancestor', PSelectorNthAncestorTask ], - [ ':xpath', PSelectorXpathTask ] + [ ':spath', PSelectorSpathTask ], + [ ':xpath', PSelectorXpathTask ], ]); PSelector.prototype.invalid = false; diff --git a/src/js/static-ext-filtering.js b/src/js/static-ext-filtering.js index b9d4c873e..e8a795d9d 100644 --- a/src/js/static-ext-filtering.js +++ b/src/js/static-ext-filtering.js @@ -169,6 +169,7 @@ 'min-text-length', 'not', 'nth-ancestor', + 'watch-attr', 'watch-attrs', 'xpath' ].join('|'), @@ -285,6 +286,7 @@ [ ':-abp-contains', ':has-text' ], [ ':-abp-has', ':has' ], [ ':contains', ':has-text' ], + [ ':watch-attrs', ':watch-attr' ], ]); const compileArgument = new Map([ @@ -299,7 +301,7 @@ [ ':not', compileNotSelector ], [ ':nth-ancestor', compileNthAncestorSelector ], [ ':spath', compileSpathExpression ], - [ ':watch-attrs', compileAttrList ], + [ ':watch-attr', compileAttrList ], [ ':xpath', compileXpathExpression ] ]); @@ -356,7 +358,7 @@ break; case ':min-text-length': case ':nth-ancestor': - case ':watch-attrs': + case ':watch-attr': case ':xpath': raw.push(`${task[0]}(${task[1]})`); break;