mirror of
https://github.com/gorhill/uBlock.git
synced 2024-09-18 08:52:26 +02:00
add chainable and recursive cosmetic procedural filters
This commit is contained in:
parent
2f01fcda54
commit
73a69711f2
@ -63,7 +63,10 @@ section > div:first-child {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
section > div > textarea {
|
section.invalidFilter > div:first-child {
|
||||||
|
border-color: red;
|
||||||
|
}
|
||||||
|
section > div:first-child > textarea {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border: none;
|
border: none;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@ -75,10 +78,7 @@ section > div > textarea {
|
|||||||
resize: none;
|
resize: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
section > div > textarea.invalidFilter {
|
section > div:first-child > textarea + div {
|
||||||
background-color: #fee;
|
|
||||||
}
|
|
||||||
section > div > textarea + div {
|
|
||||||
background-color: #aaa;
|
background-color: #aaa;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
color: white;
|
color: white;
|
||||||
@ -86,6 +86,9 @@ section > div > textarea + div {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
section.invalidFilter > div:first-child > textarea + div {
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
section > div:first-child + div {
|
section > div:first-child + div {
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
margin: 2px 0;
|
margin: 2px 0;
|
||||||
|
@ -137,19 +137,9 @@ vAPI.domFilterer = (function() {
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var jobQueue = [
|
|
||||||
{ t: 'css-hide', _0: [] }, // to inject in style tag
|
|
||||||
{ t: 'css-style', _0: [] }, // to inject in style tag
|
|
||||||
{ t: 'css-ssel', _0: [] }, // to manually hide (incremental)
|
|
||||||
{ t: 'css-csel', _0: [] } // to manually hide (not incremental)
|
|
||||||
];
|
|
||||||
|
|
||||||
var reParserEx = /:(?:has|matches-css|matches-css-before|matches-css-after|style|xpath)\(.+?\)$/;
|
|
||||||
|
|
||||||
var allExceptions = createSet(),
|
var allExceptions = createSet(),
|
||||||
allSelectors = createSet(),
|
allSelectors = createSet(),
|
||||||
stagedNodes = [],
|
stagedNodes = [];
|
||||||
matchesProp = vAPI.matchesProp;
|
|
||||||
|
|
||||||
// Complex selectors, due to their nature may need to be "de-committed". A
|
// Complex selectors, due to their nature may need to be "de-committed". A
|
||||||
// Set() is used to implement this functionality.
|
// Set() is used to implement this functionality.
|
||||||
@ -308,100 +298,179 @@ var platformHideNode = vAPI.hideNode,
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var runSimpleSelectorJob = function(job, root, fn) {
|
// 'P' stands for 'Procedural'
|
||||||
if ( job._1 === undefined ) {
|
|
||||||
job._1 = job._0.join(cssNotHiddenId + ',');
|
|
||||||
}
|
|
||||||
if ( root[matchesProp](job._1) ) {
|
|
||||||
fn(root);
|
|
||||||
}
|
|
||||||
var nodes = root.querySelectorAll(job._1),
|
|
||||||
i = nodes.length;
|
|
||||||
while ( i-- ) {
|
|
||||||
fn(nodes[i], job);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var runComplexSelectorJob = function(job, fn) {
|
var PSelectorHasTask = function(task) {
|
||||||
if ( job._1 === undefined ) {
|
this.selector = task[1];
|
||||||
job._1 = job._0.join(',');
|
|
||||||
}
|
|
||||||
var nodes = document.querySelectorAll(job._1),
|
|
||||||
i = nodes.length;
|
|
||||||
while ( i-- ) {
|
|
||||||
fn(nodes[i], job);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
PSelectorHasTask.prototype.exec = function(input) {
|
||||||
var runHasJob = function(job, fn) {
|
var output = [];
|
||||||
var nodes = document.querySelectorAll(job._0),
|
for ( var i = 0, n = input.length; i < n; i++ ) {
|
||||||
i = nodes.length, node;
|
if ( input[i].querySelector(this.selector) !== null ) {
|
||||||
while ( i-- ) {
|
output.push(input[i]);
|
||||||
node = nodes[i];
|
|
||||||
if ( node.querySelector(job._1) !== null ) {
|
|
||||||
fn(node, job);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return output;
|
||||||
};
|
};
|
||||||
|
|
||||||
// '/' = ascii 0x2F */
|
var PSelectorHasTextTask = function(task) {
|
||||||
|
this.needle = new RegExp(task[1]);
|
||||||
var parseMatchesCSSJob = function(raw) {
|
|
||||||
var prop = raw.trim();
|
|
||||||
if ( prop === '' ) { return null; }
|
|
||||||
var pos = prop.indexOf(':'),
|
|
||||||
v = pos !== -1 ? prop.slice(pos + 1).trim() : '',
|
|
||||||
vlen = v.length;
|
|
||||||
if (
|
|
||||||
vlen > 1 &&
|
|
||||||
v.charCodeAt(0) === 0x2F &&
|
|
||||||
v.charCodeAt(vlen-1) === 0x2F
|
|
||||||
) {
|
|
||||||
try { v = new RegExp(v.slice(1, -1)); } catch(ex) { return null; }
|
|
||||||
}
|
|
||||||
return { k: prop.slice(0, pos).trim(), v: v };
|
|
||||||
};
|
};
|
||||||
|
PSelectorHasTextTask.prototype.exec = function(input) {
|
||||||
var runMatchesCSSJob = function(job, fn) {
|
var output = [];
|
||||||
var nodes = document.querySelectorAll(job._0),
|
for ( var i = 0, n = input.length; i < n; i++ ) {
|
||||||
i = nodes.length;
|
if ( this.needle.test(input[i].textContent) ) {
|
||||||
if ( i === 0 ) { return; }
|
output.push(input[i]);
|
||||||
if ( typeof job._1 === 'string' ) {
|
|
||||||
job._1 = parseMatchesCSSJob(job._1);
|
|
||||||
}
|
|
||||||
if ( job._1 === null ) { return; }
|
|
||||||
var k = job._1.k,
|
|
||||||
v = job._1.v,
|
|
||||||
node, style, match;
|
|
||||||
while ( i-- ) {
|
|
||||||
node = nodes[i];
|
|
||||||
style = window.getComputedStyle(node, job._2);
|
|
||||||
if ( style === null ) { continue; } /* FF */
|
|
||||||
if ( v instanceof RegExp ) {
|
|
||||||
match = v.test(style[k]);
|
|
||||||
} else {
|
|
||||||
match = style[k] === v;
|
|
||||||
}
|
|
||||||
if ( match ) {
|
|
||||||
fn(node, job);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return output;
|
||||||
};
|
};
|
||||||
|
|
||||||
var runXpathJob = function(job, fn) {
|
var PSelectorIfTask = function(task) {
|
||||||
if ( job._1 === undefined ) {
|
this.pselector = new PSelector(task[1]);
|
||||||
job._1 = document.createExpression(job._0, null);
|
};
|
||||||
|
PSelectorIfTask.prototype.target = true;
|
||||||
|
PSelectorIfTask.prototype.exec = function(input) {
|
||||||
|
var output = [];
|
||||||
|
for ( var i = 0, n = input.length; i < n; i++ ) {
|
||||||
|
if ( this.pselector.test(input[i]) === this.target ) {
|
||||||
|
output.push(input[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var xpr = job._2 = job._1.evaluate(
|
return output;
|
||||||
document,
|
};
|
||||||
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
|
|
||||||
job._2 || null
|
var PSelectorIfNotTask = function(task) {
|
||||||
);
|
PSelectorIfTask.call(this, task);
|
||||||
var i = xpr.snapshotLength, node;
|
this.target = false;
|
||||||
|
};
|
||||||
|
PSelectorIfNotTask.prototype = Object.create(PSelectorIfTask.prototype);
|
||||||
|
PSelectorIfNotTask.prototype.constructor = PSelectorIfNotTask;
|
||||||
|
|
||||||
|
var PSelectorMatchesCSSTask = function(task) {
|
||||||
|
this.name = task[1].name;
|
||||||
|
this.value = new RegExp(task[1].value);
|
||||||
|
};
|
||||||
|
PSelectorMatchesCSSTask.prototype.pseudo = null;
|
||||||
|
PSelectorMatchesCSSTask.prototype.exec = function(input) {
|
||||||
|
var output = [], style;
|
||||||
|
for ( var i = 0, n = input.length; i < n; i++ ) {
|
||||||
|
style = window.getComputedStyle(input[i], this.pseudo);
|
||||||
|
if ( style === null ) { return null; } /* FF */
|
||||||
|
if ( this.value.test(style[this.name]) ) {
|
||||||
|
output.push(input[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
|
var PSelectorMatchesCSSAfterTask = function(task) {
|
||||||
|
PSelectorMatchesCSSTask.call(this, task);
|
||||||
|
this.pseudo = ':after';
|
||||||
|
};
|
||||||
|
PSelectorMatchesCSSAfterTask.prototype = Object.create(PSelectorMatchesCSSTask.prototype);
|
||||||
|
PSelectorMatchesCSSAfterTask.prototype.constructor = PSelectorMatchesCSSAfterTask;
|
||||||
|
|
||||||
|
var PSelectorMatchesCSSBeforeTask = function(task) {
|
||||||
|
PSelectorMatchesCSSTask.call(this, task);
|
||||||
|
this.pseudo = ':before';
|
||||||
|
};
|
||||||
|
PSelectorMatchesCSSBeforeTask.prototype = Object.create(PSelectorMatchesCSSTask.prototype);
|
||||||
|
PSelectorMatchesCSSBeforeTask.prototype.constructor = PSelectorMatchesCSSBeforeTask;
|
||||||
|
|
||||||
|
var PSelectorXpathTask = function(task) {
|
||||||
|
this.xpe = document.createExpression(task[1], null);
|
||||||
|
this.xpr = null;
|
||||||
|
};
|
||||||
|
PSelectorXpathTask.prototype.exec = function(input) {
|
||||||
|
var output = [], j, node;
|
||||||
|
for ( var i = 0, n = input.length; i < n; i++ ) {
|
||||||
|
this.xpr = this.xpe.evaluate(
|
||||||
|
input[i],
|
||||||
|
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
|
||||||
|
this.xpr
|
||||||
|
);
|
||||||
|
j = this.xpr.snapshotLength;
|
||||||
|
while ( j-- ) {
|
||||||
|
node = this.xpr.snapshotItem(j);
|
||||||
|
if ( node.nodeType === 1 ) {
|
||||||
|
output.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
|
var PSelector = function(o) {
|
||||||
|
if ( PSelector.prototype.operatorToTaskMap === undefined ) {
|
||||||
|
PSelector.prototype.operatorToTaskMap = new Map([
|
||||||
|
[ ':has', PSelectorHasTask ],
|
||||||
|
[ ':has-text', PSelectorHasTextTask ],
|
||||||
|
[ ':if', PSelectorIfTask ],
|
||||||
|
[ ':if-not', PSelectorIfNotTask ],
|
||||||
|
[ ':matches-css', PSelectorMatchesCSSTask ],
|
||||||
|
[ ':matches-css-after', PSelectorMatchesCSSAfterTask ],
|
||||||
|
[ ':matches-css-before', PSelectorMatchesCSSBeforeTask ],
|
||||||
|
[ ':xpath', PSelectorXpathTask ]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
this.raw = o.raw;
|
||||||
|
this.selector = o.selector;
|
||||||
|
this.tasks = [];
|
||||||
|
var tasks = o.tasks, task, ctor;
|
||||||
|
for ( var i = 0; i < tasks.length; i++ ) {
|
||||||
|
task = tasks[i];
|
||||||
|
ctor = this.operatorToTaskMap.get(task[0]);
|
||||||
|
this.tasks.push(new ctor(task));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PSelector.prototype.operatorToTaskMap = undefined;
|
||||||
|
PSelector.prototype.prime = function(input) {
|
||||||
|
var root = input || document;
|
||||||
|
if ( this.selector !== '' ) {
|
||||||
|
return root.querySelectorAll(this.selector);
|
||||||
|
}
|
||||||
|
return [ root ];
|
||||||
|
};
|
||||||
|
PSelector.prototype.exec = function(input) {
|
||||||
|
//var t0 = window.performance.now();
|
||||||
|
var tasks = this.tasks, nodes = this.prime(input);
|
||||||
|
for ( var i = 0, n = tasks.length; i < n && nodes.length !== 0; i++ ) {
|
||||||
|
nodes = tasks[i].exec(nodes);
|
||||||
|
}
|
||||||
|
//console.log('%s: %s ms', this.raw, (window.performance.now() - t0).toFixed(2));
|
||||||
|
return nodes;
|
||||||
|
};
|
||||||
|
PSelector.prototype.test = function(input) {
|
||||||
|
//var t0 = window.performance.now();
|
||||||
|
var tasks = this.tasks, nodes = this.prime(input), aa0 = [ null ], aa;
|
||||||
|
for ( var i = 0, ni = nodes.length; i < ni; i++ ) {
|
||||||
|
aa0[0] = nodes[i]; aa = aa0;
|
||||||
|
for ( var j = 0, nj = tasks.length; j < nj && aa.length !== 0; j++ ) {
|
||||||
|
aa = tasks[i].exec(aa);
|
||||||
|
}
|
||||||
|
if ( aa.length !== 0 ) { return true; }
|
||||||
|
}
|
||||||
|
//console.log('%s: %s ms', this.raw, (window.performance.now() - t0).toFixed(2));
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
var PSelectors = function() {
|
||||||
|
this.entries = [];
|
||||||
|
};
|
||||||
|
PSelectors.prototype.add = function(o) {
|
||||||
|
this.entries.push(new PSelector(o));
|
||||||
|
};
|
||||||
|
PSelectors.prototype.forEachNode = function(callback) {
|
||||||
|
var pfilters = this.entries,
|
||||||
|
i = pfilters.length,
|
||||||
|
pfilter, nodes, j;
|
||||||
while ( i-- ) {
|
while ( i-- ) {
|
||||||
node = xpr.snapshotItem(i);
|
pfilter = pfilters[i];
|
||||||
if ( node.nodeType === 1 ) {
|
nodes = pfilter.exec();
|
||||||
fn(node, job);
|
j = nodes.length;
|
||||||
|
while ( j-- ) {
|
||||||
|
callback(nodes[j], pfilter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -418,14 +487,52 @@ var domFilterer = {
|
|||||||
hiddenNodeCount: 0,
|
hiddenNodeCount: 0,
|
||||||
hiddenNodeEnforcer: false,
|
hiddenNodeEnforcer: false,
|
||||||
loggerEnabled: undefined,
|
loggerEnabled: undefined,
|
||||||
styleTags: [],
|
|
||||||
|
|
||||||
jobQueue: jobQueue,
|
newHideSelectorBuffer: [], // Hide style filter buffer
|
||||||
// Stock jobs.
|
newStyleRuleBuffer: [], // Non-hide style filter buffer
|
||||||
job0: jobQueue[0],
|
simpleHideSelectors: { // Hiding filters: simple selectors
|
||||||
job1: jobQueue[1],
|
entries: [],
|
||||||
job2: jobQueue[2],
|
matchesProp: vAPI.matchesProp,
|
||||||
job3: jobQueue[3],
|
selector: undefined,
|
||||||
|
add: function(selector) {
|
||||||
|
this.entries.push(selector);
|
||||||
|
this.selector = undefined;
|
||||||
|
},
|
||||||
|
forEachNodeOfSelector: function(/*callback, root, extra*/) {
|
||||||
|
},
|
||||||
|
forEachNode: function(callback, root, extra) {
|
||||||
|
if ( this.selector === undefined ) {
|
||||||
|
this.selector = this.entries.join(extra + ',') + extra;
|
||||||
|
}
|
||||||
|
if ( root[this.matchesProp](this.selector) ) {
|
||||||
|
callback(root);
|
||||||
|
}
|
||||||
|
var nodes = root.querySelectorAll(this.selector),
|
||||||
|
i = nodes.length;
|
||||||
|
while ( i-- ) {
|
||||||
|
callback(nodes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
complexHideSelectors: { // Hiding filters: complex selectors
|
||||||
|
entries: [],
|
||||||
|
selector: undefined,
|
||||||
|
add: function(selector) {
|
||||||
|
this.entries.push(selector);
|
||||||
|
this.selector = undefined;
|
||||||
|
},
|
||||||
|
forEachNode: function(callback) {
|
||||||
|
if ( this.selector === undefined ) {
|
||||||
|
this.selector = this.entries.join(',');
|
||||||
|
}
|
||||||
|
var nodes = document.querySelectorAll(this.selector),
|
||||||
|
i = nodes.length;
|
||||||
|
while ( i-- ) {
|
||||||
|
callback(nodes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
proceduralSelectors: new PSelectors(), // Hiding filters: procedural
|
||||||
|
|
||||||
addExceptions: function(aa) {
|
addExceptions: function(aa) {
|
||||||
for ( var i = 0, n = aa.length; i < n; i++ ) {
|
for ( var i = 0, n = aa.length; i < n; i++ ) {
|
||||||
@ -433,58 +540,28 @@ var domFilterer = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Job:
|
addSelector: function(selector) {
|
||||||
// Stock jobs in job queue:
|
if ( allSelectors.has(selector) || allExceptions.has(selector) ) {
|
||||||
// 0 = css rules/css declaration to remove visibility
|
|
||||||
// 1 = css rules/any css declaration
|
|
||||||
// 2 = simple css selectors/hide
|
|
||||||
// 3 = complex css selectors/hide
|
|
||||||
// Custom jobs:
|
|
||||||
// matches-css/hide
|
|
||||||
// has/hide
|
|
||||||
// xpath/hide
|
|
||||||
|
|
||||||
addSelector: function(s) {
|
|
||||||
if ( allSelectors.has(s) || allExceptions.has(s) ) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
allSelectors.add(s);
|
allSelectors.add(selector);
|
||||||
var sel0 = s, sel1 = '';
|
if ( selector.charCodeAt(0) !== 0x7B /* '{' */ ) {
|
||||||
if ( s.charCodeAt(s.length - 1) === 0x29 ) {
|
this.newHideSelectorBuffer.push(selector);
|
||||||
var parts = reParserEx.exec(s);
|
if ( selector.indexOf(' ') === -1 ) {
|
||||||
if ( parts !== null ) {
|
this.simpleHideSelectors.add(selector);
|
||||||
sel1 = parts[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ( sel1 === '' ) {
|
|
||||||
this.job0._0.push(sel0);
|
|
||||||
if ( sel0.indexOf(' ') === -1 ) {
|
|
||||||
this.job2._0.push(sel0);
|
|
||||||
this.job2._1 = undefined;
|
|
||||||
} else {
|
} else {
|
||||||
this.job3._0.push(sel0);
|
this.complexHideSelectors.add(selector);
|
||||||
this.job3._1 = undefined;
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sel0 = sel0.slice(0, sel0.length - sel1.length);
|
var o = JSON.parse(selector);
|
||||||
if ( sel1.lastIndexOf(':has', 0) === 0 ) {
|
if ( o.style ) {
|
||||||
this.jobQueue.push({ t: 'has-hide', raw: s, _0: sel0, _1: sel1.slice(5, -1) });
|
this.newStyleRuleBuffer.push(o.parts.join(' '));
|
||||||
} else if ( sel1.lastIndexOf(':matches-css', 0) === 0 ) {
|
return;
|
||||||
if ( sel1.lastIndexOf(':matches-css-before', 0) === 0 ) {
|
}
|
||||||
this.jobQueue.push({ t: 'matches-css-hide', raw: s, _0: sel0, _1: sel1.slice(20, -1), _2: ':before' });
|
if ( o.procedural ) {
|
||||||
} else if ( sel1.lastIndexOf(':matches-css-after', 0) === 0 ) {
|
this.proceduralSelectors.add(o);
|
||||||
this.jobQueue.push({ t: 'matches-css-hide', raw: s, _0: sel0, _1: sel1.slice(19, -1), _2: ':after' });
|
|
||||||
} else {
|
|
||||||
this.jobQueue.push({ t: 'matches-css-hide', raw: s, _0: sel0, _1: sel1.slice(13, -1), _2: null });
|
|
||||||
}
|
|
||||||
} else if ( sel1.lastIndexOf(':style', 0) === 0 ) {
|
|
||||||
this.job1._0.push(sel0 + ' { ' + sel1.slice(7, -1) + ' }');
|
|
||||||
this.job1._1 = undefined;
|
|
||||||
} else if ( sel1.lastIndexOf(':xpath', 0) === 0 ) {
|
|
||||||
this.jobQueue.push({ t: 'xpath-hide', raw: s, _0: sel1.slice(7, -1) });
|
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
addSelectors: function(aa) {
|
addSelectors: function(aa) {
|
||||||
@ -497,27 +574,27 @@ var domFilterer = {
|
|||||||
this.commitTimer.clear();
|
this.commitTimer.clear();
|
||||||
|
|
||||||
var beforeHiddenNodeCount = this.hiddenNodeCount,
|
var beforeHiddenNodeCount = this.hiddenNodeCount,
|
||||||
styleText = '', i, n;
|
styleText = '', i;
|
||||||
|
|
||||||
// Stock job 0 = css rules/hide
|
// CSS rules/hide
|
||||||
if ( this.job0._0.length ) {
|
if ( this.newHideSelectorBuffer.length ) {
|
||||||
styleText = '\n:root ' + this.job0._0.join(',\n:root ') + '\n{ display: none !important; }';
|
styleText = '\n:root ' + this.newHideSelectorBuffer.join(',\n:root ') + '\n{ display: none !important; }';
|
||||||
this.job0._0.length = 0;
|
this.newHideSelectorBuffer.length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stock job 1 = css rules/any css declaration
|
// CSS rules/any css declaration
|
||||||
if ( this.job1._0.length ) {
|
if ( this.newStyleRuleBuffer.length ) {
|
||||||
styleText += '\n' + this.job1._0.join('\n');
|
styleText += '\n' + this.newStyleRuleBuffer.join('\n');
|
||||||
this.job1._0.length = 0;
|
this.newStyleRuleBuffer.length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple selectors: incremental.
|
// Simple selectors: incremental.
|
||||||
|
|
||||||
// Stock job 2 = simple css selectors/hide
|
// Simple css selectors/hide
|
||||||
if ( this.job2._0.length ) {
|
if ( this.simpleHideSelectors.entries.length ) {
|
||||||
i = stagedNodes.length;
|
i = stagedNodes.length;
|
||||||
while ( i-- ) {
|
while ( i-- ) {
|
||||||
runSimpleSelectorJob(this.job2, stagedNodes[i], hideNode);
|
this.simpleHideSelectors.forEachNode(hideNode, stagedNodes[i], cssNotHiddenId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stagedNodes = [];
|
stagedNodes = [];
|
||||||
@ -526,17 +603,16 @@ var domFilterer = {
|
|||||||
complexSelectorsOldResultSet = complexSelectorsCurrentResultSet;
|
complexSelectorsOldResultSet = complexSelectorsCurrentResultSet;
|
||||||
complexSelectorsCurrentResultSet = createSet('object');
|
complexSelectorsCurrentResultSet = createSet('object');
|
||||||
|
|
||||||
// Stock job 3 = complex css selectors/hide
|
// Complex css selectors/hide
|
||||||
// The handling of these can be considered optional, since they are
|
// The handling of these can be considered optional, since they are
|
||||||
// also applied declaratively using a style tag.
|
// also applied declaratively using a style tag.
|
||||||
if ( this.job3._0.length ) {
|
if ( this.complexHideSelectors.entries.length ) {
|
||||||
runComplexSelectorJob(this.job3, complexHideNode);
|
this.complexHideSelectors.forEachNode(complexHideNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom jobs. No optional since they can't be applied in a
|
// Procedural cosmetic filters
|
||||||
// declarative way.
|
if ( this.proceduralSelectors.entries.length ) {
|
||||||
for ( i = 4, n = this.jobQueue.length; i < n; i++ ) {
|
this.proceduralSelectors.forEachNode(complexHideNode);
|
||||||
this.runJob(this.jobQueue[i], complexHideNode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/1912
|
// https://github.com/gorhill/uBlock/issues/1912
|
||||||
@ -595,6 +671,10 @@ var domFilterer = {
|
|||||||
this.commitTimer.start();
|
this.commitTimer.start();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
createProceduralFilter: function(o) {
|
||||||
|
return new PSelector(o);
|
||||||
|
},
|
||||||
|
|
||||||
getExcludeId: function() {
|
getExcludeId: function() {
|
||||||
if ( this.excludeId === undefined ) {
|
if ( this.excludeId === undefined ) {
|
||||||
this.excludeId = vAPI.randomToken();
|
this.excludeId = vAPI.randomToken();
|
||||||
@ -616,20 +696,6 @@ var domFilterer = {
|
|||||||
this.commitTimer = new vAPI.SafeAnimationFrame(this.commit_.bind(this));
|
this.commitTimer = new vAPI.SafeAnimationFrame(this.commit_.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
runJob: function(job, fn) {
|
|
||||||
switch ( job.t ) {
|
|
||||||
case 'has-hide':
|
|
||||||
runHasJob(job, fn);
|
|
||||||
break;
|
|
||||||
case 'matches-css-hide':
|
|
||||||
runMatchesCSSJob(job, fn);
|
|
||||||
break;
|
|
||||||
case 'xpath-hide':
|
|
||||||
runXpathJob(job, fn);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
showNode: function(node) {
|
showNode: function(node) {
|
||||||
node.hidden = false;
|
node.hidden = false;
|
||||||
platformUnhideNode(node);
|
platformUnhideNode(node);
|
||||||
@ -1248,14 +1314,14 @@ vAPI.domSurveyor = (function() {
|
|||||||
|
|
||||||
// Need to do this before committing DOM filterer, as needed info
|
// Need to do this before committing DOM filterer, as needed info
|
||||||
// will no longer be there after commit.
|
// will no longer be there after commit.
|
||||||
if ( firstSurvey || domFilterer.job0._0.length ) {
|
if ( firstSurvey || domFilterer.newHideSelectorBuffer.length ) {
|
||||||
messaging.send(
|
messaging.send(
|
||||||
'contentscript',
|
'contentscript',
|
||||||
{
|
{
|
||||||
what: 'cosmeticFiltersInjected',
|
what: 'cosmeticFiltersInjected',
|
||||||
type: 'cosmetic',
|
type: 'cosmetic',
|
||||||
hostname: window.location.hostname,
|
hostname: window.location.hostname,
|
||||||
selectors: domFilterer.job0._0,
|
selectors: domFilterer.newHideSelectorBuffer,
|
||||||
first: firstSurvey,
|
first: firstSurvey,
|
||||||
cost: surveyCost
|
cost: surveyCost
|
||||||
}
|
}
|
||||||
@ -1263,7 +1329,7 @@ vAPI.domSurveyor = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown surveyor if too many consecutive empty resultsets.
|
// Shutdown surveyor if too many consecutive empty resultsets.
|
||||||
if ( domFilterer.job0._0.length === 0 ) {
|
if ( domFilterer.newHideSelectorBuffer.length === 0 ) {
|
||||||
cosmeticSurveyingMissCount += 1;
|
cosmeticSurveyingMissCount += 1;
|
||||||
} else {
|
} else {
|
||||||
cosmeticSurveyingMissCount = 0;
|
cosmeticSurveyingMissCount = 0;
|
||||||
|
@ -39,6 +39,35 @@ var µb = µBlock;
|
|||||||
var encode = JSON.stringify;
|
var encode = JSON.stringify;
|
||||||
var decode = JSON.parse;
|
var decode = JSON.parse;
|
||||||
|
|
||||||
|
var isValidCSSSelector = (function() {
|
||||||
|
var div = document.createElement('div'),
|
||||||
|
matchesFn;
|
||||||
|
// Keep in mind:
|
||||||
|
// https://github.com/gorhill/uBlock/issues/693
|
||||||
|
// https://github.com/gorhill/uBlock/issues/1955
|
||||||
|
if ( div.matches instanceof Function ) {
|
||||||
|
matchesFn = div.matches.bind(div);
|
||||||
|
} else if ( div.mozMatchesSelector instanceof Function ) {
|
||||||
|
matchesFn = div.mozMatchesSelector.bind(div);
|
||||||
|
} else if ( div.webkitMatchesSelector instanceof Function ) {
|
||||||
|
matchesFn = div.webkitMatchesSelector.bind(div);
|
||||||
|
} else if ( div.msMatchesSelector instanceof Function ) {
|
||||||
|
matchesFn = div.msMatchesSelector.bind(div);
|
||||||
|
} else {
|
||||||
|
matchesFn = div.querySelector.bind(div);
|
||||||
|
}
|
||||||
|
return function(s) {
|
||||||
|
try {
|
||||||
|
matchesFn(s + ', ' + s + ':not(#foo)');
|
||||||
|
} catch (ex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
var reIsRegexLiteral = /^\/.+\/$/;
|
||||||
|
|
||||||
var isBadRegex = function(s) {
|
var isBadRegex = function(s) {
|
||||||
try {
|
try {
|
||||||
void new RegExp(s);
|
void new RegExp(s);
|
||||||
@ -218,7 +247,7 @@ var FilterParser = function() {
|
|||||||
this.hostnames = [];
|
this.hostnames = [];
|
||||||
this.invalid = false;
|
this.invalid = false;
|
||||||
this.cosmetic = true;
|
this.cosmetic = true;
|
||||||
this.reNeedHostname = /^(?:script:contains|script:inject|.+?:has|.+?:matches-css(?:-before|-after)?|:xpath)\(.+?\)$/;
|
this.reNeedHostname = /^(?:script:contains|script:inject|.+?:has|.+?:has-text|.+?:if|.+?:if-not|.+?:matches-css(?:-before|-after)?|.*?:xpath)\(.+\)$/;
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
@ -331,7 +360,12 @@ FilterParser.prototype.parse = function(raw) {
|
|||||||
// ##script:contains(...)
|
// ##script:contains(...)
|
||||||
// ##script:inject(...)
|
// ##script:inject(...)
|
||||||
// ##.foo:has(...)
|
// ##.foo:has(...)
|
||||||
|
// ##.foo:has-text(...)
|
||||||
|
// ##.foo:if(...)
|
||||||
|
// ##.foo:if-not(...)
|
||||||
// ##.foo:matches-css(...)
|
// ##.foo:matches-css(...)
|
||||||
|
// ##.foo:matches-css-after(...)
|
||||||
|
// ##.foo:matches-css-before(...)
|
||||||
// ##:xpath(...)
|
// ##:xpath(...)
|
||||||
if (
|
if (
|
||||||
this.hostnames.length === 0 &&
|
this.hostnames.length === 0 &&
|
||||||
@ -698,91 +732,178 @@ FilterContainer.prototype.freeze = function() {
|
|||||||
// implemented (if ever). Unlikely, see:
|
// implemented (if ever). Unlikely, see:
|
||||||
// https://github.com/gorhill/uBlock/issues/1752
|
// https://github.com/gorhill/uBlock/issues/1752
|
||||||
|
|
||||||
FilterContainer.prototype.isValidSelector = (function() {
|
FilterContainer.prototype.compileSelector = (function() {
|
||||||
var div = document.createElement('div');
|
var reStyleSelector = /^(.+?):style\((.+?)\)$/,
|
||||||
var matchesProp = (function() {
|
|
||||||
if ( typeof div.matches === 'function' ) {
|
|
||||||
return 'matches';
|
|
||||||
}
|
|
||||||
if ( typeof div.mozMatchesSelector === 'function' ) {
|
|
||||||
return 'mozMatchesSelector';
|
|
||||||
}
|
|
||||||
if ( typeof div.webkitMatchesSelector === 'function' ) {
|
|
||||||
return 'webkitMatchesSelector';
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
})();
|
|
||||||
// Not all browsers support `Element.matches`:
|
|
||||||
// http://caniuse.com/#feat=matchesselector
|
|
||||||
if ( matchesProp === '' ) {
|
|
||||||
return function() {
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var reHasSelector = /^(.+?):has\((.+?)\)$/,
|
|
||||||
reMatchesCSSSelector = /^(.+?):matches-css(?:-before|-after)?\((.+?)\)$/,
|
|
||||||
reXpathSelector = /^:xpath\((.+?)\)$/,
|
|
||||||
reStyleSelector = /^(.+?):style\((.+?)\)$/,
|
|
||||||
reStyleBad = /url\([^)]+\)/,
|
reStyleBad = /url\([^)]+\)/,
|
||||||
reScriptSelector = /^script:(contains|inject)\((.+)\)$/;
|
reScriptSelector = /^script:(contains|inject)\((.+)\)$/;
|
||||||
|
|
||||||
// Keep in mind:
|
return function(raw) {
|
||||||
// https://github.com/gorhill/uBlock/issues/693
|
if ( isValidCSSSelector(raw) && raw.indexOf('[-abp-properties=') === -1 ) {
|
||||||
// https://github.com/gorhill/uBlock/issues/1955
|
return raw;
|
||||||
var isValidCSSSelector = function(s) {
|
|
||||||
try {
|
|
||||||
div[matchesProp](s + ', ' + s + ':not(#foo)');
|
|
||||||
} catch (ex) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
return function(s) {
|
// We rarely reach this point.
|
||||||
if ( isValidCSSSelector(s) && s.indexOf('[-abp-properties=') === -1 ) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// We reach this point very rarely.
|
|
||||||
var matches;
|
var matches;
|
||||||
|
|
||||||
// Future `:has`-based filter? If so, validate both parts of the whole
|
|
||||||
// selector.
|
|
||||||
matches = reHasSelector.exec(s);
|
|
||||||
if ( matches !== null ) {
|
|
||||||
return isValidCSSSelector(matches[1]) && isValidCSSSelector(matches[2]);
|
|
||||||
}
|
|
||||||
// Custom `:matches-css`-based filter?
|
|
||||||
matches = reMatchesCSSSelector.exec(s);
|
|
||||||
if ( matches !== null ) {
|
|
||||||
return isValidCSSSelector(matches[1]);
|
|
||||||
}
|
|
||||||
// Custom `:xpath`-based filter?
|
|
||||||
matches = reXpathSelector.exec(s);
|
|
||||||
if ( matches !== null ) {
|
|
||||||
try {
|
|
||||||
return document.createExpression(matches[1], null) instanceof XPathExpression;
|
|
||||||
} catch (e) {
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// `:style` selector?
|
// `:style` selector?
|
||||||
matches = reStyleSelector.exec(s);
|
if ( (matches = reStyleSelector.exec(raw)) !== null ) {
|
||||||
if ( matches !== null ) {
|
if ( isValidCSSSelector(matches[1]) && reStyleBad.test(matches[2]) === false ) {
|
||||||
return isValidCSSSelector(matches[1]) && reStyleBad.test(matches[2]) === false;
|
return JSON.stringify({
|
||||||
}
|
style: true,
|
||||||
// Special `script:` filter?
|
raw: raw,
|
||||||
matches = reScriptSelector.exec(s);
|
parts: [ matches[1], '{' + matches[2] + '}' ]
|
||||||
if ( matches !== null ) {
|
});
|
||||||
if ( matches[1] === 'inject' ) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return matches[2].startsWith('/') === false ||
|
return;
|
||||||
matches[2].endsWith('/') === false ||
|
|
||||||
isBadRegex(matches[2].slice(1, -1)) === false;
|
|
||||||
}
|
}
|
||||||
µb.logger.writeOne('', 'error', 'Cosmetic filtering – invalid filter: ' + s);
|
|
||||||
return false;
|
// `script:` filter?
|
||||||
|
if ( (matches = reScriptSelector.exec(raw)) !== null ) {
|
||||||
|
// :inject
|
||||||
|
if ( matches[1] === 'inject' ) {
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
// :contains
|
||||||
|
if ( reIsRegexLiteral.test(matches[2]) === false || isBadRegex(matches[2].slice(1, -1)) === false ) {
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Procedural selector?
|
||||||
|
var compiled;
|
||||||
|
if ( (compiled = this.compileProceduralSelector(raw)) ) {
|
||||||
|
return compiled;
|
||||||
|
}
|
||||||
|
|
||||||
|
µb.logger.writeOne('', 'error', 'Cosmetic filtering – invalid filter: ' + raw);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
FilterContainer.prototype.compileProceduralSelector = (function() {
|
||||||
|
var reParserEx = /(:(?:has|has-text|if|if-not|matches-css|matches-css-after|matches-css-before|xpath))\(.+\)$/,
|
||||||
|
reFirstParentheses = /^\(*/,
|
||||||
|
reLastParentheses = /\)*$/,
|
||||||
|
reEscapeRegex = /[.*+?^${}()|[\]\\]/g;
|
||||||
|
|
||||||
|
var lastProceduralSelector = '',
|
||||||
|
lastProceduralSelectorCompiled;
|
||||||
|
|
||||||
|
var compileCSSSelector = function(s) {
|
||||||
|
if ( isValidCSSSelector(s) ) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var compileText = function(s) {
|
||||||
|
if ( reIsRegexLiteral.test(s) ) {
|
||||||
|
s = s.slice(1, -1);
|
||||||
|
if ( isBadRegex(s) ) { return; }
|
||||||
|
} else {
|
||||||
|
s = s.replace(reEscapeRegex, '\\$&');
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|
||||||
|
var compileCSSDeclaration = function(s) {
|
||||||
|
var name, value,
|
||||||
|
pos = s.indexOf(':');
|
||||||
|
if ( pos === -1 ) { return; }
|
||||||
|
name = s.slice(0, pos).trim();
|
||||||
|
value = s.slice(pos + 1).trim();
|
||||||
|
if ( reIsRegexLiteral.test(value) ) {
|
||||||
|
value = value.slice(1, -1);
|
||||||
|
if ( isBadRegex(value) ) { return; }
|
||||||
|
} else {
|
||||||
|
value = value.replace(reEscapeRegex, '\\$&');
|
||||||
|
}
|
||||||
|
return { name: name, value: value };
|
||||||
|
};
|
||||||
|
|
||||||
|
var compileConditionalSelector = function(s) {
|
||||||
|
return compile(s);
|
||||||
|
};
|
||||||
|
|
||||||
|
var compileXpathExpression = function(s) {
|
||||||
|
var dummy;
|
||||||
|
try {
|
||||||
|
dummy = document.createExpression(s, null) instanceof XPathExpression;
|
||||||
|
} catch (e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|
||||||
|
var compileArgument = new Map([
|
||||||
|
[ ':has', compileCSSSelector ],
|
||||||
|
[ ':has-text', compileText ],
|
||||||
|
[ ':if', compileConditionalSelector ],
|
||||||
|
[ ':if-not', compileConditionalSelector ],
|
||||||
|
[ ':matches-css', compileCSSDeclaration ],
|
||||||
|
[ ':matches-css-after', compileCSSDeclaration ],
|
||||||
|
[ ':matches-css-before', compileCSSDeclaration ],
|
||||||
|
[ ':xpath', compileXpathExpression ]
|
||||||
|
]);
|
||||||
|
|
||||||
|
var compile = function(raw) {
|
||||||
|
var matches = reParserEx.exec(raw);
|
||||||
|
if ( matches === null ) { return; }
|
||||||
|
var tasks = [],
|
||||||
|
firstOperand = raw.slice(0, matches.index),
|
||||||
|
currentOperator = matches[1],
|
||||||
|
selector = raw.slice(matches.index + currentOperator.length),
|
||||||
|
currentArgument = '', nextOperand, nextOperator,
|
||||||
|
depth = 0, opening, closing;
|
||||||
|
if ( firstOperand !== '' && isValidCSSSelector(firstOperand) === false ) { return; }
|
||||||
|
for (;;) {
|
||||||
|
matches = reParserEx.exec(selector);
|
||||||
|
if ( matches !== null ) {
|
||||||
|
nextOperand = selector.slice(0, matches.index);
|
||||||
|
nextOperator = matches[1];
|
||||||
|
} else {
|
||||||
|
nextOperand = selector;
|
||||||
|
nextOperator = '';
|
||||||
|
}
|
||||||
|
opening = reFirstParentheses.exec(nextOperand)[0].length;
|
||||||
|
closing = reLastParentheses.exec(nextOperand)[0].length;
|
||||||
|
if ( opening > closing ) {
|
||||||
|
if ( depth === 0 ) { currentArgument = ''; }
|
||||||
|
depth += 1;
|
||||||
|
} else if ( closing > opening && depth > 0 ) {
|
||||||
|
depth -= 1;
|
||||||
|
if ( depth === 0 ) { nextOperand = currentArgument + nextOperand; }
|
||||||
|
}
|
||||||
|
if ( depth !== 0 ) {
|
||||||
|
currentArgument += nextOperand + nextOperator;
|
||||||
|
} else {
|
||||||
|
currentArgument = compileArgument.get(currentOperator)(nextOperand.slice(1, -1));
|
||||||
|
if ( currentArgument === undefined ) { return; }
|
||||||
|
tasks.push([ currentOperator, currentArgument ]);
|
||||||
|
currentOperator = nextOperator;
|
||||||
|
}
|
||||||
|
if ( nextOperator === '' ) { break; }
|
||||||
|
selector = selector.slice(matches.index + nextOperator.length);
|
||||||
|
}
|
||||||
|
if ( tasks.length === 0 || depth !== 0 ) { return; }
|
||||||
|
return { selector: firstOperand, tasks: tasks };
|
||||||
|
};
|
||||||
|
|
||||||
|
return function(raw) {
|
||||||
|
if ( raw === lastProceduralSelector ) {
|
||||||
|
return lastProceduralSelectorCompiled;
|
||||||
|
}
|
||||||
|
lastProceduralSelector = raw;
|
||||||
|
var compiled = compile(raw);
|
||||||
|
if ( compiled !== undefined ) {
|
||||||
|
compiled.procedural = true;
|
||||||
|
compiled.raw = raw;
|
||||||
|
compiled = JSON.stringify(compiled);
|
||||||
|
}
|
||||||
|
lastProceduralSelectorCompiled = compiled;
|
||||||
|
return compiled;
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@ -843,7 +964,7 @@ FilterContainer.prototype.compile = function(s, out) {
|
|||||||
// still the most common, and can easily be tested using a plain regex.
|
// still the most common, and can easily be tested using a plain regex.
|
||||||
if (
|
if (
|
||||||
this.reClassOrIdSelector.test(parsed.suffix) === false &&
|
this.reClassOrIdSelector.test(parsed.suffix) === false &&
|
||||||
this.isValidSelector(parsed.suffix) === false
|
this.compileSelector(parsed.suffix) === undefined
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -895,15 +1016,15 @@ FilterContainer.prototype.compileGenericHideSelector = function(parsed, out) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Composite CSS rule.
|
// Composite CSS rule.
|
||||||
if ( this.isValidSelector(selector) ) {
|
if ( this.compileSelector(selector) ) {
|
||||||
out.push('c\vlg+\v' + key + '\v' + selector);
|
out.push('c\vlg+\v' + key + '\v' + selector);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( this.isValidSelector(selector) !== true ) {
|
var compiled = this.compileSelector(selector);
|
||||||
return;
|
if ( compiled === undefined ) { return; }
|
||||||
}
|
// TODO: Detect and error on procedural cosmetic filters.
|
||||||
|
|
||||||
// ["title"] and ["alt"] will go in high-low generic bin.
|
// ["title"] and ["alt"] will go in high-low generic bin.
|
||||||
if ( this.reHighLow.test(selector) ) {
|
if ( this.reHighLow.test(selector) ) {
|
||||||
@ -948,10 +1069,6 @@ FilterContainer.prototype.compileGenericHideSelector = function(parsed, out) {
|
|||||||
FilterContainer.prototype.compileGenericUnhideSelector = function(parsed, out) {
|
FilterContainer.prototype.compileGenericUnhideSelector = function(parsed, out) {
|
||||||
var selector = parsed.suffix;
|
var selector = parsed.suffix;
|
||||||
|
|
||||||
if ( this.isValidSelector(selector) !== true ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// script:contains(...)
|
// script:contains(...)
|
||||||
// script:inject(...)
|
// script:inject(...)
|
||||||
if ( this.reScriptSelector.test(selector) ) {
|
if ( this.reScriptSelector.test(selector) ) {
|
||||||
@ -959,10 +1076,14 @@ FilterContainer.prototype.compileGenericUnhideSelector = function(parsed, out) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Procedural cosmetic filters are acceptable as generic exception filters.
|
||||||
|
var compiled = this.compileSelector(selector);
|
||||||
|
if ( compiled === undefined ) { return; }
|
||||||
|
|
||||||
// https://github.com/chrisaljoudi/uBlock/issues/497
|
// https://github.com/chrisaljoudi/uBlock/issues/497
|
||||||
// All generic exception filters are put in the same bucket: they are
|
// All generic exception filters are put in the same bucket: they are
|
||||||
// expected to be very rare.
|
// expected to be very rare.
|
||||||
out.push('c\vg1\v' + selector);
|
out.push('c\vg1\v' + compiled);
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
@ -980,20 +1101,24 @@ FilterContainer.prototype.compileHostnameSelector = function(hostname, parsed, o
|
|||||||
hostname = this.punycode.toASCII(hostname);
|
hostname = this.punycode.toASCII(hostname);
|
||||||
}
|
}
|
||||||
|
|
||||||
var domain = this.µburi.domainFromHostname(hostname),
|
var selector = parsed.suffix,
|
||||||
|
domain = this.µburi.domainFromHostname(hostname),
|
||||||
hash;
|
hash;
|
||||||
|
|
||||||
// script:contains(...)
|
// script:contains(...)
|
||||||
// script:inject(...)
|
// script:inject(...)
|
||||||
if ( this.reScriptSelector.test(parsed.suffix) ) {
|
if ( this.reScriptSelector.test(selector) ) {
|
||||||
hash = domain !== '' ? domain : this.noDomainHash;
|
hash = domain !== '' ? domain : this.noDomainHash;
|
||||||
if ( unhide ) {
|
if ( unhide ) {
|
||||||
hash = '!' + hash;
|
hash = '!' + hash;
|
||||||
}
|
}
|
||||||
out.push('c\vjs\v' + hash + '\v' + hostname + '\v' + parsed.suffix);
|
out.push('c\vjs\v' + hash + '\v' + hostname + '\v' + selector);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var compiled = this.compileSelector(selector);
|
||||||
|
if ( compiled === undefined ) { return; }
|
||||||
|
|
||||||
// https://github.com/chrisaljoudi/uBlock/issues/188
|
// https://github.com/chrisaljoudi/uBlock/issues/188
|
||||||
// If not a real domain as per PSL, assign a synthetic one
|
// If not a real domain as per PSL, assign a synthetic one
|
||||||
if ( hostname.endsWith('.*') === false ) {
|
if ( hostname.endsWith('.*') === false ) {
|
||||||
@ -1005,7 +1130,7 @@ FilterContainer.prototype.compileHostnameSelector = function(hostname, parsed, o
|
|||||||
hash = '!' + hash;
|
hash = '!' + hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
out.push('c\vh\v' + hash + '\v' + hostname + '\v' + parsed.suffix);
|
out.push('c\vh\v' + hash + '\v' + hostname + '\v' + compiled);
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -100,6 +100,10 @@ var onMessage = function(request, sender, callback) {
|
|||||||
µb.mouseURL = request.url;
|
µb.mouseURL = request.url;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'compileCosmeticFilterSelector':
|
||||||
|
response = µb.cosmeticFilteringEngine.compileSelector(request.selector);
|
||||||
|
break;
|
||||||
|
|
||||||
case 'cosmeticFiltersInjected':
|
case 'cosmeticFiltersInjected':
|
||||||
µb.cosmeticFilteringEngine.addToSelectorCache(request);
|
µb.cosmeticFilteringEngine.addToSelectorCache(request);
|
||||||
/* falls through */
|
/* falls through */
|
||||||
|
@ -134,14 +134,23 @@ var fromCosmeticFilter = function(details) {
|
|||||||
}
|
}
|
||||||
candidates[details.rawFilter] = new RegExp(reStr.join('\\v') + '(?:\\n|$)');
|
candidates[details.rawFilter] = new RegExp(reStr.join('\\v') + '(?:\\n|$)');
|
||||||
|
|
||||||
|
// Procedural filters, which are pre-compiled, make thing sort of
|
||||||
|
// complicated. We are going to also search for one portion of the
|
||||||
|
// compiled form of a filter.
|
||||||
|
var filterEx = '(' +
|
||||||
|
reEscape(filter) +
|
||||||
|
'|[^\\v]+' +
|
||||||
|
reEscape(JSON.stringify({ raw: filter }).slice(1,-1)) +
|
||||||
|
'[^\\v]+)';
|
||||||
|
|
||||||
// Second step: find hostname-based versions.
|
// Second step: find hostname-based versions.
|
||||||
// Reference: FilterContainer.compileHostnameSelector().
|
// Reference: FilterContainer.compileHostnameSelector().
|
||||||
var pos;
|
var pos,
|
||||||
var hostname = details.hostname;
|
hostname = details.hostname;
|
||||||
if ( hostname !== '' ) {
|
if ( hostname !== '' ) {
|
||||||
for ( ;; ) {
|
for ( ;; ) {
|
||||||
candidates[hostname + '##' + filter] = new RegExp(
|
candidates[hostname + '##' + filter] = new RegExp(
|
||||||
['c', 'h', '[^\\v]+', reEscape(hostname), reEscape(filter)].join('\\v') +
|
['c', 'h', '[^\\v]+', reEscape(hostname), filterEx].join('\\v') +
|
||||||
'(?:\\n|$)'
|
'(?:\\n|$)'
|
||||||
);
|
);
|
||||||
pos = hostname.indexOf('.');
|
pos = hostname.indexOf('.');
|
||||||
@ -159,7 +168,7 @@ var fromCosmeticFilter = function(details) {
|
|||||||
if ( pos !== -1 ) {
|
if ( pos !== -1 ) {
|
||||||
var entity = domain.slice(0, pos) + '.*';
|
var entity = domain.slice(0, pos) + '.*';
|
||||||
candidates[entity + '##' + filter] = new RegExp(
|
candidates[entity + '##' + filter] = new RegExp(
|
||||||
['c', 'h', '[^\\v]+', reEscape(entity), reEscape(filter)].join('\\v') +
|
['c', 'h', '[^\\v]+', reEscape(entity), filterEx].join('\\v') +
|
||||||
'(?:\\n|$)'
|
'(?:\\n|$)'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -31,38 +31,36 @@ if ( typeof vAPI !== 'object' || !vAPI.domFilterer ) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var df = vAPI.domFilterer,
|
var loggedSelectors = vAPI.loggedSelectors || {},
|
||||||
loggedSelectors = vAPI.loggedSelectors || {},
|
matchedSelectors = [];
|
||||||
matchedSelectors = [],
|
|
||||||
selectors, i, selector;
|
|
||||||
|
|
||||||
|
|
||||||
// CSS selectors.
|
var evaluateSelector = function(selector) {
|
||||||
selectors = df.jobQueue[2]._0.concat(df.jobQueue[3]._0);
|
if (
|
||||||
i = selectors.length;
|
loggedSelectors.hasOwnProperty(selector) === false &&
|
||||||
while ( i-- ) {
|
document.querySelector(selector) !== null
|
||||||
selector = selectors[i];
|
) {
|
||||||
if ( loggedSelectors.hasOwnProperty(selector) ) {
|
loggedSelectors[selector] = true;
|
||||||
continue;
|
matchedSelectors.push(selector);
|
||||||
}
|
}
|
||||||
if ( document.querySelector(selector) === null ) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
loggedSelectors[selector] = true;
|
|
||||||
matchedSelectors.push(selector);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Non-CSS selectors.
|
|
||||||
var logHit = function(node, job) {
|
|
||||||
if ( !job.raw || loggedSelectors.hasOwnProperty(job.raw) ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
loggedSelectors[job.raw] = true;
|
|
||||||
matchedSelectors.push(job.raw);
|
|
||||||
};
|
};
|
||||||
for ( i = 4; i < df.jobQueue.length; i++ ) {
|
|
||||||
df.runJob(df.jobQueue[i], logHit);
|
// Simple CSS selector-based cosmetic filters.
|
||||||
}
|
vAPI.domFilterer.simpleHideSelectors.entries.forEach(evaluateSelector);
|
||||||
|
|
||||||
|
// Complex CSS selector-based cosmetic filters.
|
||||||
|
vAPI.domFilterer.complexHideSelectors.entries.forEach(evaluateSelector);
|
||||||
|
|
||||||
|
// Procedural cosmetic filters.
|
||||||
|
vAPI.domFilterer.proceduralSelectors.entries.forEach(function(pfilter) {
|
||||||
|
if (
|
||||||
|
loggedSelectors.hasOwnProperty(pfilter.raw) === false &&
|
||||||
|
pfilter.exec().length !== 0
|
||||||
|
) {
|
||||||
|
loggedSelectors[pfilter.raw] = true;
|
||||||
|
matchedSelectors.push(pfilter.raw);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
vAPI.loggedSelectors = loggedSelectors;
|
vAPI.loggedSelectors = loggedSelectors;
|
||||||
|
|
||||||
|
@ -693,7 +693,7 @@ var cosmeticFilterMapper = (function() {
|
|||||||
i, j;
|
i, j;
|
||||||
|
|
||||||
// CSS-based selectors: simple one.
|
// CSS-based selectors: simple one.
|
||||||
selectors = vAPI.domFilterer.job2._0;
|
selectors = vAPI.domFilterer.simpleHideSelectors.entries;
|
||||||
i = selectors.length;
|
i = selectors.length;
|
||||||
while ( i-- ) {
|
while ( i-- ) {
|
||||||
selector = selectors[i];
|
selector = selectors[i];
|
||||||
@ -709,9 +709,9 @@ var cosmeticFilterMapper = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CSS-based selectors: complex one (must query from doc root).
|
// CSS-based selectors: complex one (must query from doc root).
|
||||||
selectors = vAPI.domFilterer.job3._0;
|
selectors = vAPI.domFilterer.complexHideSelectors.entries;
|
||||||
i = selectors.length;
|
i = selectors.length;
|
||||||
while ( i-- ) {
|
while ( i-- ) {
|
||||||
selector = selectors[i];
|
selector = selectors[i];
|
||||||
@ -726,14 +726,12 @@ var cosmeticFilterMapper = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Non-CSS selectors.
|
// Non-CSS selectors.
|
||||||
var runJobCallback = function(node, job) {
|
var runJobCallback = function(node, pfilter) {
|
||||||
if ( filterMap.has(node) === false ) {
|
if ( filterMap.has(node) === false ) {
|
||||||
filterMap.set(node, job.raw);
|
filterMap.set(node, pfilter.raw);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
for ( i = 4; i < vAPI.domFilterer.jobQueue.length; i++ ) {
|
vAPI.domFilterer.proceduralSelectors.forEachNode(runJobCallback);
|
||||||
vAPI.domFilterer.runJob(vAPI.domFilterer.jobQueue[i], runJobCallback);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var incremental = function(rootNode) {
|
var incremental = function(rootNode) {
|
||||||
|
@ -177,6 +177,14 @@ var safeQuerySelectorAll = function(node, selector) {
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
var rawFilterFromTextarea = function() {
|
||||||
|
var s = taCandidate.value,
|
||||||
|
pos = s.indexOf('\n');
|
||||||
|
return pos === -1 ? s.trim() : s.slice(0, pos).trim();
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
var getElementBoundingClientRect = function(elem) {
|
var getElementBoundingClientRect = function(elem) {
|
||||||
var rect = typeof elem.getBoundingClientRect === 'function' ?
|
var rect = typeof elem.getBoundingClientRect === 'function' ?
|
||||||
elem.getBoundingClientRect() :
|
elem.getBoundingClientRect() :
|
||||||
@ -635,7 +643,9 @@ var filtersFrom = function(x, y) {
|
|||||||
filterToDOMInterface.set
|
filterToDOMInterface.set
|
||||||
@desc Look-up all the HTML elements matching the filter passed in
|
@desc Look-up all the HTML elements matching the filter passed in
|
||||||
argument.
|
argument.
|
||||||
@param string, a cosmetic of network filter.
|
@param string, a cosmetic or network filter.
|
||||||
|
@param function, called once all items matching the filter have been
|
||||||
|
collected.
|
||||||
@return array, or undefined if the filter is invalid.
|
@return array, or undefined if the filter is invalid.
|
||||||
|
|
||||||
filterToDOMInterface.preview
|
filterToDOMInterface.preview
|
||||||
@ -733,16 +743,15 @@ var filterToDOMInterface = (function() {
|
|||||||
// ways to compose a valid href to the same effective URL. One idea is to
|
// ways to compose a valid href to the same effective URL. One idea is to
|
||||||
// normalize all a[href] on the page, but for now I will wait and see, as I
|
// normalize all a[href] on the page, but for now I will wait and see, as I
|
||||||
// prefer to refrain from tampering with the page content if I can avoid it.
|
// prefer to refrain from tampering with the page content if I can avoid it.
|
||||||
var fromCosmeticFilter = function(filter) {
|
var fromPlainCosmeticFilter = function(filter) {
|
||||||
var elems;
|
var elems;
|
||||||
try {
|
try {
|
||||||
elems = document.querySelectorAll(filter);
|
elems = document.querySelectorAll(filter);
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
return fromProceduralCosmeticFilter(filter);
|
return;
|
||||||
}
|
}
|
||||||
var out = [],
|
var out = [], iElem = elems.length;
|
||||||
iElem = elems.length;
|
|
||||||
while ( iElem-- ) {
|
while ( iElem-- ) {
|
||||||
out.push({ type: 'cosmetic', elem: elems[iElem]});
|
out.push({ type: 'cosmetic', elem: elems[iElem]});
|
||||||
}
|
}
|
||||||
@ -751,108 +760,27 @@ var filterToDOMInterface = (function() {
|
|||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/1772
|
// https://github.com/gorhill/uBlock/issues/1772
|
||||||
// Handle procedural cosmetic filters.
|
// Handle procedural cosmetic filters.
|
||||||
var fromProceduralCosmeticFilter = function(filter) {
|
var fromCompiledCosmeticFilter = function(raw) {
|
||||||
if ( filter.charCodeAt(filter.length - 1) === 0x29 /* ')' */ ) {
|
if ( typeof raw !== 'string' ) { return; }
|
||||||
var parts = reProceduralCosmeticFilter.exec(filter);
|
var o;
|
||||||
if (
|
try {
|
||||||
parts !== null &&
|
o = JSON.parse(raw);
|
||||||
proceduralCosmeticFilterFunctions.hasOwnProperty(parts[2])
|
} catch(ex) {
|
||||||
) {
|
return;
|
||||||
return proceduralCosmeticFilterFunctions[parts[2]](
|
|
||||||
parts[1].trim(),
|
|
||||||
parts[3].trim()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
var elems;
|
||||||
|
if ( o.style ) {
|
||||||
var reProceduralCosmeticFilter = /^(.*?):(matches-css|has|style|xpath)\((.+?)\)$/;
|
elems = document.querySelectorAll(o.parts[0]);
|
||||||
|
lastAction = o.parts.join(' ');
|
||||||
// Collection of handlers for procedural cosmetic filters.
|
} else if ( o.procedural ) {
|
||||||
var proceduralCosmeticFilterFunctions = {
|
elems = vAPI.domFilterer.createProceduralFilter(o).exec();
|
||||||
'has': function(selector, arg) {
|
|
||||||
if ( selector === '' ) { return; }
|
|
||||||
var elems;
|
|
||||||
try {
|
|
||||||
elems = document.querySelectorAll(selector);
|
|
||||||
document.querySelector(arg);
|
|
||||||
} catch(ex) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var out = [], elem;
|
|
||||||
for ( var i = 0, n = elems.length; i < n; i++ ) {
|
|
||||||
elem = elems[i];
|
|
||||||
if ( elem.querySelector(arg) ) {
|
|
||||||
out.push({ type: 'cosmetic', elem: elem });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
},
|
|
||||||
'matches-css': function(selector, arg) {
|
|
||||||
if ( selector === '' ) { return; }
|
|
||||||
var elems;
|
|
||||||
try {
|
|
||||||
elems = document.querySelectorAll(selector);
|
|
||||||
} catch(ex) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var out = [], elem, style,
|
|
||||||
pos = arg.indexOf(':');
|
|
||||||
if ( pos === -1 ) { return; }
|
|
||||||
var prop = arg.slice(0, pos).trim(),
|
|
||||||
reText = arg.slice(pos + 1).trim();
|
|
||||||
if ( reText === '' ) { return; }
|
|
||||||
var re = reText !== '*' ?
|
|
||||||
new RegExp('^' + reText.replace(/[.+?${}()|[\]\\^]/g, '\\$&').replace(/\*+/g, '.*?') + '$') :
|
|
||||||
/./;
|
|
||||||
for ( var i = 0, n = elems.length; i < n; i++ ) {
|
|
||||||
elem = elems[i];
|
|
||||||
style = window.getComputedStyle(elem, null);
|
|
||||||
if ( re.test(style[prop]) ) {
|
|
||||||
out.push({ type: 'cosmetic', elem: elem });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
},
|
|
||||||
'style': function(selector, arg) {
|
|
||||||
if ( selector === '' || arg === '' ) { return; }
|
|
||||||
var elems;
|
|
||||||
try {
|
|
||||||
elems = document.querySelectorAll(selector);
|
|
||||||
} catch(ex) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var out = [];
|
|
||||||
for ( var i = 0, n = elems.length; i < n; i++ ) {
|
|
||||||
out.push({ type: 'cosmetic', elem: elems[i] });
|
|
||||||
}
|
|
||||||
lastAction = selector + ' { ' + arg + ' }';
|
|
||||||
return out;
|
|
||||||
},
|
|
||||||
'xpath': function(selector, arg) {
|
|
||||||
if ( selector !== '' ) { return []; }
|
|
||||||
var result;
|
|
||||||
try {
|
|
||||||
result = document.evaluate(
|
|
||||||
arg,
|
|
||||||
document,
|
|
||||||
null,
|
|
||||||
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
} catch(ex) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ( result === undefined ) { return []; }
|
|
||||||
var out = [], elem, i = result.snapshotLength;
|
|
||||||
while ( i-- ) {
|
|
||||||
elem = result.snapshotItem(i);
|
|
||||||
if ( elem.nodeType === 1 ) {
|
|
||||||
out.push({ type: 'cosmetic', elem: elem });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
}
|
||||||
|
if ( !elems ) { return; }
|
||||||
|
var out = [];
|
||||||
|
for ( var i = 0, n = elems.length; i < n; i++ ) {
|
||||||
|
out.push({ type: 'cosmetic', elem: elems[i] });
|
||||||
|
}
|
||||||
|
return out;
|
||||||
};
|
};
|
||||||
|
|
||||||
var lastFilter,
|
var lastFilter,
|
||||||
@ -862,26 +790,44 @@ var filterToDOMInterface = (function() {
|
|||||||
applied = false,
|
applied = false,
|
||||||
previewing = false;
|
previewing = false;
|
||||||
|
|
||||||
var queryAll = function(filter) {
|
var queryAll = function(filter, callback) {
|
||||||
filter = filter.trim();
|
filter = filter.trim();
|
||||||
if ( filter === lastFilter ) {
|
if ( filter === lastFilter ) {
|
||||||
return lastResultset;
|
callback(lastResultset);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
unapply();
|
unapply();
|
||||||
if ( filter === '' ) {
|
if ( filter === '' ) {
|
||||||
lastFilter = '';
|
lastFilter = '';
|
||||||
lastResultset = [];
|
lastResultset = [];
|
||||||
} else {
|
callback(lastResultset);
|
||||||
lastFilter = filter;
|
return;
|
||||||
lastAction = undefined;
|
|
||||||
lastResultset = filter.lastIndexOf('##', 0) === 0 ?
|
|
||||||
fromCosmeticFilter(filter.slice(2)) :
|
|
||||||
fromNetworkFilter(filter);
|
|
||||||
if ( previewing ) {
|
|
||||||
apply(filter);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return lastResultset;
|
lastFilter = filter;
|
||||||
|
lastAction = undefined;
|
||||||
|
if ( filter.lastIndexOf('##', 0) === -1 ) {
|
||||||
|
lastResultset = fromNetworkFilter(filter);
|
||||||
|
if ( previewing ) { apply(); }
|
||||||
|
callback(lastResultset);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var selector = filter.slice(2);
|
||||||
|
lastResultset = fromPlainCosmeticFilter(selector);
|
||||||
|
if ( lastResultset ) {
|
||||||
|
if ( previewing ) { apply(); }
|
||||||
|
callback(lastResultset);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Procedural cosmetic filter
|
||||||
|
vAPI.messaging.send(
|
||||||
|
'elementPicker',
|
||||||
|
{ what: 'compileCosmeticFilterSelector', selector: selector },
|
||||||
|
function(response) {
|
||||||
|
lastResultset = fromCompiledCosmeticFilter(response);
|
||||||
|
if ( previewing ) { apply(); }
|
||||||
|
callback(lastResultset);
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
var applyHide = function() {
|
var applyHide = function() {
|
||||||
@ -983,9 +929,11 @@ var filterToDOMInterface = (function() {
|
|||||||
var preview = function(filter) {
|
var preview = function(filter) {
|
||||||
previewing = filter !== false;
|
previewing = filter !== false;
|
||||||
if ( previewing ) {
|
if ( previewing ) {
|
||||||
if ( queryAll(filter) !== undefined ) {
|
queryAll(filter, function(items) {
|
||||||
apply();
|
if ( items !== undefined ) {
|
||||||
}
|
apply();
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
unapply();
|
unapply();
|
||||||
}
|
}
|
||||||
@ -999,70 +947,75 @@ var filterToDOMInterface = (function() {
|
|||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// https://www.youtube.com/watch?v=nuUXJ6RfIik
|
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var userFilterFromCandidate = function() {
|
var userFilterFromCandidate = function(callback) {
|
||||||
var v = taCandidate.value;
|
var v = rawFilterFromTextarea();
|
||||||
var items = filterToDOMInterface.set(v);
|
filterToDOMInterface.set(v, function(items) {
|
||||||
if ( !items || items.length === 0 ) {
|
if ( !items || items.length === 0 ) {
|
||||||
return false;
|
callback();
|
||||||
}
|
return;
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/738
|
|
||||||
// Trim dots.
|
|
||||||
var hostname = window.location.hostname;
|
|
||||||
if ( hostname.slice(-1) === '.' ) {
|
|
||||||
hostname = hostname.slice(0, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cosmetic filter?
|
|
||||||
if ( v.lastIndexOf('##', 0) === 0 ) {
|
|
||||||
return hostname + v;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assume net filter
|
|
||||||
var opts = [];
|
|
||||||
|
|
||||||
// If no domain included in filter, we need domain option
|
|
||||||
if ( v.lastIndexOf('||', 0) === -1 ) {
|
|
||||||
opts.push('domain=' + hostname);
|
|
||||||
}
|
|
||||||
|
|
||||||
var item = items[0];
|
|
||||||
if ( item.opts ) {
|
|
||||||
opts.push(item.opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( opts.length ) {
|
|
||||||
v += '$' + opts.join(',');
|
|
||||||
}
|
|
||||||
|
|
||||||
return v;
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
var onCandidateChanged = function() {
|
|
||||||
var elems = [],
|
|
||||||
items = filterToDOMInterface.set(taCandidate.value),
|
|
||||||
valid = items !== undefined;
|
|
||||||
if ( valid ) {
|
|
||||||
for ( var i = 0; i < items.length; i++ ) {
|
|
||||||
elems.push(items[i].elem);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
pickerBody.querySelector('body section textarea + div').textContent = valid ?
|
// https://github.com/gorhill/uBlock/issues/738
|
||||||
items.length.toLocaleString() :
|
// Trim dots.
|
||||||
'0';
|
var hostname = window.location.hostname;
|
||||||
taCandidate.classList.toggle('invalidFilter', !valid);
|
if ( hostname.slice(-1) === '.' ) {
|
||||||
dialog.querySelector('#create').disabled = elems.length === 0;
|
hostname = hostname.slice(0, -1);
|
||||||
highlightElements(elems, true);
|
}
|
||||||
|
|
||||||
|
// Cosmetic filter?
|
||||||
|
if ( v.lastIndexOf('##', 0) === 0 ) {
|
||||||
|
callback(hostname + v);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume net filter
|
||||||
|
var opts = [];
|
||||||
|
|
||||||
|
// If no domain included in filter, we need domain option
|
||||||
|
if ( v.lastIndexOf('||', 0) === -1 ) {
|
||||||
|
opts.push('domain=' + hostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
var item = items[0];
|
||||||
|
if ( item.opts ) {
|
||||||
|
opts.push(item.opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( opts.length ) {
|
||||||
|
v += '$' + opts.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(v);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
var onCandidateChanged = (function() {
|
||||||
|
var process = function(items) {
|
||||||
|
var elems = [], valid = items !== undefined;
|
||||||
|
if ( valid ) {
|
||||||
|
for ( var i = 0; i < items.length; i++ ) {
|
||||||
|
elems.push(items[i].elem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pickerBody.querySelector('body section textarea + div').textContent = valid ?
|
||||||
|
items.length.toLocaleString() :
|
||||||
|
'ε';
|
||||||
|
dialog.querySelector('section').classList.toggle('invalidFilter', !valid);
|
||||||
|
dialog.querySelector('#create').disabled = elems.length === 0;
|
||||||
|
highlightElements(elems, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return function() {
|
||||||
|
filterToDOMInterface.set(rawFilterFromTextarea(), process);
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
var candidateFromFilterChoice = function(filterChoice) {
|
var candidateFromFilterChoice = function(filterChoice) {
|
||||||
var slot = filterChoice.slot;
|
var slot = filterChoice.slot;
|
||||||
var filters = filterChoice.filters;
|
var filters = filterChoice.filters;
|
||||||
@ -1132,8 +1085,8 @@ var onDialogClicked = function(ev) {
|
|||||||
// We have to exit from preview mode: this guarantees matching elements
|
// We have to exit from preview mode: this guarantees matching elements
|
||||||
// will be found for the candidate filter.
|
// will be found for the candidate filter.
|
||||||
filterToDOMInterface.preview(false);
|
filterToDOMInterface.preview(false);
|
||||||
var filter = userFilterFromCandidate();
|
userFilterFromCandidate(function(filter) {
|
||||||
if ( filter ) {
|
if ( !filter ) { return; }
|
||||||
var d = new Date();
|
var d = new Date();
|
||||||
vAPI.messaging.send(
|
vAPI.messaging.send(
|
||||||
'elementPicker',
|
'elementPicker',
|
||||||
@ -1143,9 +1096,9 @@ var onDialogClicked = function(ev) {
|
|||||||
pageDomain: window.location.hostname
|
pageDomain: window.location.hostname
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
filterToDOMInterface.preview(taCandidate.value);
|
filterToDOMInterface.preview(rawFilterFromTextarea());
|
||||||
stopPicker();
|
stopPicker();
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
else if ( ev.target.id === 'pick' ) {
|
else if ( ev.target.id === 'pick' ) {
|
||||||
@ -1161,7 +1114,7 @@ var onDialogClicked = function(ev) {
|
|||||||
if ( filterToDOMInterface.previewing() ) {
|
if ( filterToDOMInterface.previewing() ) {
|
||||||
filterToDOMInterface.preview(false);
|
filterToDOMInterface.preview(false);
|
||||||
} else {
|
} else {
|
||||||
filterToDOMInterface.preview(taCandidate.value);
|
filterToDOMInterface.preview(rawFilterFromTextarea());
|
||||||
}
|
}
|
||||||
highlightElements(targetElements, true);
|
highlightElements(targetElements, true);
|
||||||
}
|
}
|
||||||
@ -1300,6 +1253,7 @@ var onKeyPressed = function(ev) {
|
|||||||
if ( ev.which === 27 ) {
|
if ( ev.which === 27 ) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
filterToDOMInterface.preview(false);
|
||||||
stopPicker();
|
stopPicker();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user