Add support for `nth-ancestor` operator in HTML filtering

Also opportunitisically converted some code to
ES6's `class`.
This commit is contained in:
Raymond Hill 2019-05-11 13:21:23 -04:00
parent d42d86dd12
commit 8a7e704080
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
1 changed files with 134 additions and 109 deletions

View File

@ -42,135 +42,160 @@
} }
}; };
const PSelectorHasTextTask = function(task) { const PSelectorHasTextTask = class {
let arg0 = task[1], arg1; constructor(task) {
if ( Array.isArray(task[1]) ) { let arg0 = task[1], arg1;
arg1 = arg0[1]; arg0 = arg0[0]; if ( Array.isArray(task[1]) ) {
} arg1 = arg0[1]; arg0 = arg0[0];
this.needle = new RegExp(arg0, arg1);
};
PSelectorHasTextTask.prototype.exec = function(input) {
const output = [];
for ( const node of input ) {
if ( this.needle.test(node.textContent) ) {
output.push(node);
} }
this.needle = new RegExp(arg0, arg1);
} }
return output; exec(input) {
}; const output = [];
for ( const node of input ) {
const PSelectorIfTask = function(task) { if ( this.needle.test(node.textContent) ) {
this.pselector = new PSelector(task[1]);
};
PSelectorIfTask.prototype.target = true;
Object.defineProperty(PSelectorIfTask.prototype, 'invalid', {
get: function() {
return this.pselector.invalid;
}
});
PSelectorIfTask.prototype.exec = function(input) {
const output = [];
for ( const node of input ) {
if ( this.pselector.test(node) === this.target ) {
output.push(node);
}
}
return output;
};
const PSelectorIfNotTask = function(task) {
PSelectorIfTask.call(this, task);
this.target = false;
};
PSelectorIfNotTask.prototype = Object.create(PSelectorIfTask.prototype);
PSelectorIfNotTask.prototype.constructor = PSelectorIfNotTask;
const PSelectorXpathTask = function(task) {
this.xpe = task[1];
};
PSelectorXpathTask.prototype.exec = function(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); output.push(node);
} }
} }
return output;
} }
return output;
}; };
const PSelector = function(o) { const PSelectorIfTask = class {
if ( PSelector.prototype.operatorToTaskMap === undefined ) { constructor(task) {
PSelector.prototype.operatorToTaskMap = new Map([ this.pselector = new PSelector(task[1]);
[ ':has', PSelectorIfTask ],
[ ':has-text', PSelectorHasTextTask ],
[ ':if', PSelectorIfTask ],
[ ':if-not', PSelectorIfNotTask ],
[ ':not', PSelectorIfNotTask ],
[ ':xpath', PSelectorXpathTask ]
]);
} }
this.raw = o.raw; exec(input) {
this.selector = o.selector; const output = [];
this.tasks = []; for ( const node of input ) {
if ( !o.tasks ) { return; } if ( this.pselector.test(node) === this.target ) {
for ( const task of o.tasks ) { output.push(node);
const ctor = this.operatorToTaskMap.get(task[0]); }
if ( ctor === undefined ) {
this.invalid = true;
break;
} }
const pselector = new ctor(task); return output;
if ( pselector instanceof PSelectorIfTask && pselector.invalid ) { }
this.invalid = true; get invalid() {
break; return this.pselector.invalid;
}
};
PSelectorIfTask.prototype.target = true;
const PSelectorIfNotTask = class extends PSelectorIfTask {
constructor(task) {
super.call(task);
this.target = false;
}
};
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;
}
} }
this.tasks.push(pselector); return output;
} }
}; };
PSelector.prototype.operatorToTaskMap = undefined;
PSelector.prototype.invalid = false; const PSelectorXpathTask = class {
PSelector.prototype.prime = function(input) { constructor(task) {
const root = input || docRegister; this.xpe = task[1];
if ( this.selector !== '' ) {
return root.querySelectorAll(this.selector);
} }
return [ root ]; exec(input) {
}; const output = [];
PSelector.prototype.exec = function(input) { const xpe = docRegister.createExpression(this.xpe, null);
if ( this.invalid ) { return []; } let xpr = null;
let nodes = this.prime(input); for ( const node of input ) {
for ( const task of this.tasks ) { xpr = xpe.evaluate(
if ( nodes.length === 0 ) { break; } node,
nodes = task.exec(nodes); XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
xpr
);
let j = xpr.snapshotLength;
while ( j-- ) {
const node = xpr.snapshotItem(j);
if ( node.nodeType === 1 ) {
output.push(node);
}
}
}
return output;
} }
return nodes;
}; };
PSelector.prototype.test = function(input) {
if ( this.invalid ) { return false; } const PSelector = class {
const nodes = this.prime(input); constructor(o) {
const AA = [ null ]; this.raw = o.raw;
for ( const node of nodes ) { this.selector = o.selector;
AA[0] = node; this.tasks = [];
let aa = AA; if ( !o.tasks ) { return; }
for ( const task of o.tasks ) {
const ctor = this.operatorToTaskMap.get(task[0]);
if ( ctor === undefined ) {
this.invalid = true;
break;
}
const pselector = new ctor(task);
if ( pselector instanceof PSelectorIfTask && pselector.invalid ) {
this.invalid = true;
break;
}
this.tasks.push(pselector);
}
}
prime(input) {
const root = input || docRegister;
if ( this.selector !== '' ) {
return root.querySelectorAll(this.selector);
}
return [ root ];
}
exec(input) {
if ( this.invalid ) { return []; }
let nodes = this.prime(input);
for ( const task of this.tasks ) { for ( const task of this.tasks ) {
aa = task.exec(aa); if ( nodes.length === 0 ) { break; }
if ( aa.length === 0 ) { break; } nodes = task.exec(nodes);
} }
if ( aa.length !== 0 ) { return true; } 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;
for ( const task of this.tasks ) {
aa = task.exec(aa);
if ( aa.length === 0 ) { break; }
}
if ( aa.length !== 0 ) { return true; }
}
return false;
} }
return false;
}; };
PSelector.prototype.operatorToTaskMap = new Map([
[ ':has', PSelectorIfTask ],
[ ':has-text', PSelectorHasTextTask ],
[ ':if', PSelectorIfTask ],
[ ':if-not', PSelectorIfNotTask ],
[ ':not', PSelectorIfNotTask ],
[ ':nth-ancestor', PSelectorNthAncestorTask ],
[ ':xpath', PSelectorXpathTask ]
]);
PSelector.prototype.invalid = false;
const logOne = function(details, exception, selector) { const logOne = function(details, exception, selector) {
µBlock.filteringContext µBlock.filteringContext