mirror of https://github.com/gorhill/uBlock.git
Support CSS selectors mixed w/ operators in procedural cosmetic filters
Related issue: - https://github.com/gorhill/uBlock/issues/3683 This commit further increases uBO's procedural cosmetic filters Adguard's cosmetic filter syntax -- specifically those procedural cosmetic filters where plain CSS selectors appeared following a procedural oeprator (this was rejected as invalid by uBO). Also, experimental support for `:watch-attrs` procedural operator, as discussed in <https://github.com/uBlockOrigin/uBlock-issues/issues/341#issuecomment-449765525>. Support may be dropped before next release depending on whether a better solution is suggested. Additionally, the usual opportunistic refactoring toward ES6 syntax.
This commit is contained in:
parent
e4cec5a15e
commit
8a88e9d931
|
@ -1,7 +1,7 @@
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
|
|
||||||
uBlock Origin - a browser extension to block requests.
|
uBlock Origin - a browser extension to block requests.
|
||||||
Copyright (C) 2014-2018 Raymond Hill
|
Copyright (C) 2014-present Raymond Hill
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -105,7 +105,8 @@
|
||||||
// https://github.com/chrisaljoudi/uBlock/issues/456
|
// https://github.com/chrisaljoudi/uBlock/issues/456
|
||||||
// https://github.com/gorhill/uBlock/issues/2029
|
// https://github.com/gorhill/uBlock/issues/2029
|
||||||
|
|
||||||
if ( typeof vAPI === 'object' && !vAPI.contentScript ) { // >>>>>>>> start of HUGE-IF-BLOCK
|
// >>>>>>>> start of HUGE-IF-BLOCK
|
||||||
|
if ( typeof vAPI === 'object' && !vAPI.contentScript ) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -178,28 +179,28 @@ vAPI.SafeAnimationFrame.prototype = {
|
||||||
|
|
||||||
vAPI.domWatcher = (function() {
|
vAPI.domWatcher = (function() {
|
||||||
|
|
||||||
var addedNodeLists = [],
|
const addedNodeLists = [];
|
||||||
addedNodes = [],
|
const removedNodeLists = [];
|
||||||
domIsReady = false,
|
const addedNodes = [];
|
||||||
|
const ignoreTags = new Set([ 'br', 'head', 'link', 'meta', 'script', 'style' ]);
|
||||||
|
const listeners = [];
|
||||||
|
|
||||||
|
let domIsReady = false,
|
||||||
domLayoutObserver,
|
domLayoutObserver,
|
||||||
ignoreTags = new Set([ 'br', 'head', 'link', 'meta', 'script', 'style' ]),
|
|
||||||
listeners = [],
|
|
||||||
listenerIterator = [], listenerIteratorDirty = false,
|
listenerIterator = [], listenerIteratorDirty = false,
|
||||||
removedNodeLists = [],
|
|
||||||
removedNodes = false,
|
removedNodes = false,
|
||||||
safeObserverHandlerTimer;
|
safeObserverHandlerTimer;
|
||||||
|
|
||||||
var safeObserverHandler = function() {
|
const safeObserverHandler = function() {
|
||||||
//console.time('dom watcher/safe observer handler');
|
//console.time('dom watcher/safe observer handler');
|
||||||
safeObserverHandlerTimer.clear();
|
safeObserverHandlerTimer.clear();
|
||||||
var i = addedNodeLists.length,
|
let i = addedNodeLists.length,
|
||||||
j = addedNodes.length,
|
j = addedNodes.length;
|
||||||
nodeList, iNode, node;
|
|
||||||
while ( i-- ) {
|
while ( i-- ) {
|
||||||
nodeList = addedNodeLists[i];
|
const nodeList = addedNodeLists[i];
|
||||||
iNode = nodeList.length;
|
let iNode = nodeList.length;
|
||||||
while ( iNode-- ) {
|
while ( iNode-- ) {
|
||||||
node = nodeList[iNode];
|
const node = nodeList[iNode];
|
||||||
if ( node.nodeType !== 1 ) { continue; }
|
if ( node.nodeType !== 1 ) { continue; }
|
||||||
if ( ignoreTags.has(node.localName) ) { continue; }
|
if ( ignoreTags.has(node.localName) ) { continue; }
|
||||||
if ( node.parentElement === null ) { continue; }
|
if ( node.parentElement === null ) { continue; }
|
||||||
|
@ -209,8 +210,8 @@ vAPI.domWatcher = (function() {
|
||||||
addedNodeLists.length = 0;
|
addedNodeLists.length = 0;
|
||||||
i = removedNodeLists.length;
|
i = removedNodeLists.length;
|
||||||
while ( i-- && removedNodes === false ) {
|
while ( i-- && removedNodes === false ) {
|
||||||
nodeList = removedNodeLists[i];
|
const nodeList = removedNodeLists[i];
|
||||||
iNode = nodeList.length;
|
let iNode = nodeList.length;
|
||||||
while ( iNode-- ) {
|
while ( iNode-- ) {
|
||||||
if ( nodeList[iNode].nodeType !== 1 ) { continue; }
|
if ( nodeList[iNode].nodeType !== 1 ) { continue; }
|
||||||
removedNodes = true;
|
removedNodes = true;
|
||||||
|
@ -220,7 +221,7 @@ vAPI.domWatcher = (function() {
|
||||||
removedNodeLists.length = 0;
|
removedNodeLists.length = 0;
|
||||||
//console.timeEnd('dom watcher/safe observer handler');
|
//console.timeEnd('dom watcher/safe observer handler');
|
||||||
if ( addedNodes.length === 0 && removedNodes === false ) { return; }
|
if ( addedNodes.length === 0 && removedNodes === false ) { return; }
|
||||||
for ( var listener of getListenerIterator() ) {
|
for ( const listener of getListenerIterator() ) {
|
||||||
listener.onDOMChanged(addedNodes, removedNodes);
|
listener.onDOMChanged(addedNodes, removedNodes);
|
||||||
}
|
}
|
||||||
addedNodes.length = 0;
|
addedNodes.length = 0;
|
||||||
|
@ -229,17 +230,15 @@ vAPI.domWatcher = (function() {
|
||||||
|
|
||||||
// https://github.com/chrisaljoudi/uBlock/issues/205
|
// https://github.com/chrisaljoudi/uBlock/issues/205
|
||||||
// Do not handle added node directly from within mutation observer.
|
// Do not handle added node directly from within mutation observer.
|
||||||
var observerHandler = function(mutations) {
|
const observerHandler = function(mutations) {
|
||||||
//console.time('dom watcher/observer handler');
|
//console.time('dom watcher/observer handler');
|
||||||
var nodeList, mutation,
|
let i = mutations.length;
|
||||||
i = mutations.length;
|
|
||||||
while ( i-- ) {
|
while ( i-- ) {
|
||||||
mutation = mutations[i];
|
const mutation = mutations[i];
|
||||||
nodeList = mutation.addedNodes;
|
let nodeList = mutation.addedNodes;
|
||||||
if ( nodeList.length !== 0 ) {
|
if ( nodeList.length !== 0 ) {
|
||||||
addedNodeLists.push(nodeList);
|
addedNodeLists.push(nodeList);
|
||||||
}
|
}
|
||||||
if ( removedNodes ) { continue; }
|
|
||||||
nodeList = mutation.removedNodes;
|
nodeList = mutation.removedNodes;
|
||||||
if ( nodeList.length !== 0 ) {
|
if ( nodeList.length !== 0 ) {
|
||||||
removedNodeLists.push(nodeList);
|
removedNodeLists.push(nodeList);
|
||||||
|
@ -253,7 +252,7 @@ vAPI.domWatcher = (function() {
|
||||||
//console.timeEnd('dom watcher/observer handler');
|
//console.timeEnd('dom watcher/observer handler');
|
||||||
};
|
};
|
||||||
|
|
||||||
var startMutationObserver = function() {
|
const startMutationObserver = function() {
|
||||||
if ( domLayoutObserver !== undefined || !domIsReady ) { return; }
|
if ( domLayoutObserver !== undefined || !domIsReady ) { return; }
|
||||||
domLayoutObserver = new MutationObserver(observerHandler);
|
domLayoutObserver = new MutationObserver(observerHandler);
|
||||||
domLayoutObserver.observe(document.documentElement, {
|
domLayoutObserver.observe(document.documentElement, {
|
||||||
|
@ -266,13 +265,13 @@ vAPI.domWatcher = (function() {
|
||||||
vAPI.shutdown.add(cleanup);
|
vAPI.shutdown.add(cleanup);
|
||||||
};
|
};
|
||||||
|
|
||||||
var stopMutationObserver = function() {
|
const stopMutationObserver = function() {
|
||||||
if ( domLayoutObserver === undefined ) { return; }
|
if ( domLayoutObserver === undefined ) { return; }
|
||||||
cleanup();
|
cleanup();
|
||||||
vAPI.shutdown.remove(cleanup);
|
vAPI.shutdown.remove(cleanup);
|
||||||
};
|
};
|
||||||
|
|
||||||
var getListenerIterator = function() {
|
const getListenerIterator = function() {
|
||||||
if ( listenerIteratorDirty ) {
|
if ( listenerIteratorDirty ) {
|
||||||
listenerIterator = listeners.slice();
|
listenerIterator = listeners.slice();
|
||||||
listenerIteratorDirty = false;
|
listenerIteratorDirty = false;
|
||||||
|
@ -280,7 +279,7 @@ vAPI.domWatcher = (function() {
|
||||||
return listenerIterator;
|
return listenerIterator;
|
||||||
};
|
};
|
||||||
|
|
||||||
var addListener = function(listener) {
|
const addListener = function(listener) {
|
||||||
if ( listeners.indexOf(listener) !== -1 ) { return; }
|
if ( listeners.indexOf(listener) !== -1 ) { return; }
|
||||||
listeners.push(listener);
|
listeners.push(listener);
|
||||||
listenerIteratorDirty = true;
|
listenerIteratorDirty = true;
|
||||||
|
@ -289,8 +288,8 @@ vAPI.domWatcher = (function() {
|
||||||
startMutationObserver();
|
startMutationObserver();
|
||||||
};
|
};
|
||||||
|
|
||||||
var removeListener = function(listener) {
|
const removeListener = function(listener) {
|
||||||
var pos = listeners.indexOf(listener);
|
const pos = listeners.indexOf(listener);
|
||||||
if ( pos === -1 ) { return; }
|
if ( pos === -1 ) { return; }
|
||||||
listeners.splice(pos, 1);
|
listeners.splice(pos, 1);
|
||||||
listenerIteratorDirty = true;
|
listenerIteratorDirty = true;
|
||||||
|
@ -299,7 +298,7 @@ vAPI.domWatcher = (function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var cleanup = function() {
|
const cleanup = function() {
|
||||||
if ( domLayoutObserver !== undefined ) {
|
if ( domLayoutObserver !== undefined ) {
|
||||||
domLayoutObserver.disconnect();
|
domLayoutObserver.disconnect();
|
||||||
domLayoutObserver = null;
|
domLayoutObserver = null;
|
||||||
|
@ -310,19 +309,15 @@ vAPI.domWatcher = (function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var start = function() {
|
const start = function() {
|
||||||
domIsReady = true;
|
domIsReady = true;
|
||||||
for ( var listener of getListenerIterator() ) {
|
for ( const listener of getListenerIterator() ) {
|
||||||
listener.onDOMCreated();
|
listener.onDOMCreated();
|
||||||
}
|
}
|
||||||
startMutationObserver();
|
startMutationObserver();
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return { start, addListener, removeListener };
|
||||||
start: start,
|
|
||||||
addListener: addListener,
|
|
||||||
removeListener: removeListener
|
|
||||||
};
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -330,7 +325,7 @@ vAPI.domWatcher = (function() {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
vAPI.matchesProp = (function() {
|
vAPI.matchesProp = (function() {
|
||||||
var docElem = document.documentElement;
|
const docElem = document.documentElement;
|
||||||
if ( typeof docElem.matches !== 'function' ) {
|
if ( typeof docElem.matches !== 'function' ) {
|
||||||
if ( typeof docElem.mozMatchesSelector === 'function' ) {
|
if ( typeof docElem.mozMatchesSelector === 'function' ) {
|
||||||
return 'mozMatchesSelector';
|
return 'mozMatchesSelector';
|
||||||
|
@ -454,6 +449,63 @@ vAPI.DOMFilterer = (function() {
|
||||||
PSelectorMatchesCSSBeforeTask.prototype = Object.create(PSelectorMatchesCSSTask.prototype);
|
PSelectorMatchesCSSBeforeTask.prototype = Object.create(PSelectorMatchesCSSTask.prototype);
|
||||||
PSelectorMatchesCSSBeforeTask.prototype.constructor = PSelectorMatchesCSSBeforeTask;
|
PSelectorMatchesCSSBeforeTask.prototype.constructor = PSelectorMatchesCSSBeforeTask;
|
||||||
|
|
||||||
|
const PSelectorSpathTask = function(task) {
|
||||||
|
this.spath = task[1];
|
||||||
|
};
|
||||||
|
PSelectorSpathTask.prototype.exec = function(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
|
const PSelectorWatchAttrs = function(task) {
|
||||||
|
this.observer = null;
|
||||||
|
this.observed = new WeakSet();
|
||||||
|
this.observerOptions = {
|
||||||
|
attributes: true,
|
||||||
|
subtree: true,
|
||||||
|
};
|
||||||
|
const attrs = task[1];
|
||||||
|
if ( Array.isArray(attrs) && attrs.length !== 0 ) {
|
||||||
|
this.observerOptions.attributeFilter = task[1];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// TODO: Is it worth trying to re-apply only the current selector?
|
||||||
|
PSelectorWatchAttrs.prototype.handler = function() {
|
||||||
|
const filterer =
|
||||||
|
vAPI.domFilterer && vAPI.domFilterer.proceduralFilterer;
|
||||||
|
if ( filterer instanceof Object ) {
|
||||||
|
filterer.onDOMChanged([ null ]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PSelectorWatchAttrs.prototype.exec = function(input) {
|
||||||
|
if ( input.length === 0 ) { return input; }
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
const PSelectorXpathTask = function(task) {
|
const PSelectorXpathTask = function(task) {
|
||||||
this.xpe = document.createExpression(task[1], null);
|
this.xpe = document.createExpression(task[1], null);
|
||||||
this.xpr = null;
|
this.xpr = null;
|
||||||
|
@ -488,7 +540,9 @@ vAPI.DOMFilterer = (function() {
|
||||||
[ ':matches-css-after', PSelectorMatchesCSSAfterTask ],
|
[ ':matches-css-after', PSelectorMatchesCSSAfterTask ],
|
||||||
[ ':matches-css-before', PSelectorMatchesCSSBeforeTask ],
|
[ ':matches-css-before', PSelectorMatchesCSSBeforeTask ],
|
||||||
[ ':not', PSelectorIfNotTask ],
|
[ ':not', PSelectorIfNotTask ],
|
||||||
[ ':xpath', PSelectorXpathTask ]
|
[ ':spath', PSelectorSpathTask ],
|
||||||
|
[ ':watch-attrs', PSelectorWatchAttrs ],
|
||||||
|
[ ':xpath', PSelectorXpathTask ],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
this.budget = 200; // I arbitrary picked a 1/5 second
|
this.budget = 200; // I arbitrary picked a 1/5 second
|
||||||
|
@ -618,10 +672,9 @@ vAPI.DOMFilterer = (function() {
|
||||||
pselector.budget = -0x7FFFFFFF;
|
pselector.budget = -0x7FFFFFFF;
|
||||||
}
|
}
|
||||||
t0 = t1;
|
t0 = t1;
|
||||||
let i = nodes.length;
|
for ( const node of nodes ) {
|
||||||
while ( i-- ) {
|
this.domFilterer.hideNode(node);
|
||||||
this.domFilterer.hideNode(nodes[i]);
|
this.hiddenNodes.add(node);
|
||||||
this.hiddenNodes.add(nodes[i]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -720,33 +773,34 @@ vAPI.domFilterer = new vAPI.DOMFilterer();
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
vAPI.domCollapser = (function() {
|
vAPI.domCollapser = (function() {
|
||||||
var resquestIdGenerator = 1,
|
const messaging = vAPI.messaging;
|
||||||
processTimer,
|
const toProcess = [];
|
||||||
toProcess = [],
|
const toFilter = [];
|
||||||
toFilter = [],
|
const toCollapse = new Map();
|
||||||
toCollapse = new Map(),
|
const src1stProps = {
|
||||||
cachedBlockedSet,
|
embed: 'src',
|
||||||
cachedBlockedSetHash,
|
iframe: 'src',
|
||||||
cachedBlockedSetTimer;
|
img: 'src',
|
||||||
var src1stProps = {
|
object: 'data'
|
||||||
'embed': 'src',
|
|
||||||
'iframe': 'src',
|
|
||||||
'img': 'src',
|
|
||||||
'object': 'data'
|
|
||||||
};
|
};
|
||||||
var src2ndProps = {
|
const src2ndProps = {
|
||||||
'img': 'srcset'
|
img: 'srcset'
|
||||||
};
|
};
|
||||||
var tagToTypeMap = {
|
const tagToTypeMap = {
|
||||||
embed: 'object',
|
embed: 'object',
|
||||||
iframe: 'sub_frame',
|
iframe: 'sub_frame',
|
||||||
img: 'image',
|
img: 'image',
|
||||||
object: 'object'
|
object: 'object'
|
||||||
};
|
};
|
||||||
var netSelectorCacheCount = 0,
|
|
||||||
messaging = vAPI.messaging;
|
|
||||||
|
|
||||||
var cachedBlockedSetClear = function() {
|
let resquestIdGenerator = 1,
|
||||||
|
processTimer,
|
||||||
|
cachedBlockedSet,
|
||||||
|
cachedBlockedSetHash,
|
||||||
|
cachedBlockedSetTimer,
|
||||||
|
netSelectorCacheCount = 0;
|
||||||
|
|
||||||
|
const cachedBlockedSetClear = function() {
|
||||||
cachedBlockedSet =
|
cachedBlockedSet =
|
||||||
cachedBlockedSetHash =
|
cachedBlockedSetHash =
|
||||||
cachedBlockedSetTimer = undefined;
|
cachedBlockedSetTimer = undefined;
|
||||||
|
@ -754,13 +808,13 @@ vAPI.domCollapser = (function() {
|
||||||
|
|
||||||
// https://github.com/chrisaljoudi/uBlock/issues/174
|
// https://github.com/chrisaljoudi/uBlock/issues/174
|
||||||
// Do not remove fragment from src URL
|
// Do not remove fragment from src URL
|
||||||
var onProcessed = function(response) {
|
const onProcessed = function(response) {
|
||||||
if ( !response ) { // This happens if uBO is disabled or restarted.
|
if ( !response ) { // This happens if uBO is disabled or restarted.
|
||||||
toCollapse.clear();
|
toCollapse.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var targets = toCollapse.get(response.id);
|
const targets = toCollapse.get(response.id);
|
||||||
if ( targets === undefined ) { return; }
|
if ( targets === undefined ) { return; }
|
||||||
toCollapse.delete(response.id);
|
toCollapse.delete(response.id);
|
||||||
if ( cachedBlockedSetHash !== response.hash ) {
|
if ( cachedBlockedSetHash !== response.hash ) {
|
||||||
|
@ -774,16 +828,15 @@ vAPI.domCollapser = (function() {
|
||||||
if ( cachedBlockedSet === undefined || cachedBlockedSet.size === 0 ) {
|
if ( cachedBlockedSet === undefined || cachedBlockedSet.size === 0 ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var selectors = [],
|
const selectors = [];
|
||||||
iframeLoadEventPatch = vAPI.iframeLoadEventPatch,
|
const iframeLoadEventPatch = vAPI.iframeLoadEventPatch;
|
||||||
netSelectorCacheCountMax = response.netSelectorCacheCountMax,
|
let netSelectorCacheCountMax = response.netSelectorCacheCountMax;
|
||||||
tag, prop, src, value;
|
|
||||||
|
|
||||||
for ( var target of targets ) {
|
for ( const target of targets ) {
|
||||||
tag = target.localName;
|
const tag = target.localName;
|
||||||
prop = src1stProps[tag];
|
let prop = src1stProps[tag];
|
||||||
if ( prop === undefined ) { continue; }
|
if ( prop === undefined ) { continue; }
|
||||||
src = target[prop];
|
let src = target[prop];
|
||||||
if ( typeof src !== 'string' || src.length === 0 ) {
|
if ( typeof src !== 'string' || src.length === 0 ) {
|
||||||
prop = src2ndProps[tag];
|
prop = src2ndProps[tag];
|
||||||
if ( prop === undefined ) { continue; }
|
if ( prop === undefined ) { continue; }
|
||||||
|
@ -799,12 +852,12 @@ vAPI.domCollapser = (function() {
|
||||||
target.hidden = true;
|
target.hidden = true;
|
||||||
// https://github.com/chrisaljoudi/uBlock/issues/1048
|
// https://github.com/chrisaljoudi/uBlock/issues/1048
|
||||||
// Use attribute to construct CSS rule
|
// Use attribute to construct CSS rule
|
||||||
if (
|
if ( netSelectorCacheCount <= netSelectorCacheCountMax ) {
|
||||||
netSelectorCacheCount <= netSelectorCacheCountMax &&
|
const value = target.getAttribute(prop);
|
||||||
(value = target.getAttribute(prop))
|
if ( value ) {
|
||||||
) {
|
selectors.push(tag + '[' + prop + '="' + value + '"]');
|
||||||
selectors.push(tag + '[' + prop + '="' + value + '"]');
|
netSelectorCacheCount += 1;
|
||||||
netSelectorCacheCount += 1;
|
}
|
||||||
}
|
}
|
||||||
if ( iframeLoadEventPatch !== undefined ) {
|
if ( iframeLoadEventPatch !== undefined ) {
|
||||||
iframeLoadEventPatch(target);
|
iframeLoadEventPatch(target);
|
||||||
|
@ -824,10 +877,10 @@ vAPI.domCollapser = (function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var send = function() {
|
const send = function() {
|
||||||
processTimer = undefined;
|
processTimer = undefined;
|
||||||
toCollapse.set(resquestIdGenerator, toProcess);
|
toCollapse.set(resquestIdGenerator, toProcess);
|
||||||
var msg = {
|
const msg = {
|
||||||
what: 'getCollapsibleBlockedRequests',
|
what: 'getCollapsibleBlockedRequests',
|
||||||
id: resquestIdGenerator,
|
id: resquestIdGenerator,
|
||||||
frameURL: window.location.href,
|
frameURL: window.location.href,
|
||||||
|
@ -835,12 +888,12 @@ vAPI.domCollapser = (function() {
|
||||||
hash: cachedBlockedSetHash
|
hash: cachedBlockedSetHash
|
||||||
};
|
};
|
||||||
messaging.send('contentscript', msg, onProcessed);
|
messaging.send('contentscript', msg, onProcessed);
|
||||||
toProcess = [];
|
toProcess.length = 0;
|
||||||
toFilter = [];
|
toFilter.length = 0;
|
||||||
resquestIdGenerator += 1;
|
resquestIdGenerator += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
var process = function(delay) {
|
const process = function(delay) {
|
||||||
if ( toProcess.length === 0 ) { return; }
|
if ( toProcess.length === 0 ) { return; }
|
||||||
if ( delay === 0 ) {
|
if ( delay === 0 ) {
|
||||||
if ( processTimer !== undefined ) {
|
if ( processTimer !== undefined ) {
|
||||||
|
@ -852,26 +905,24 @@ vAPI.domCollapser = (function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var add = function(target) {
|
const add = function(target) {
|
||||||
toProcess[toProcess.length] = target;
|
toProcess[toProcess.length] = target;
|
||||||
};
|
};
|
||||||
|
|
||||||
var addMany = function(targets) {
|
const addMany = function(targets) {
|
||||||
var i = targets.length;
|
for ( const target of targets ) {
|
||||||
while ( i-- ) {
|
add(target);
|
||||||
add(targets[i]);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var iframeSourceModified = function(mutations) {
|
const iframeSourceModified = function(mutations) {
|
||||||
var i = mutations.length;
|
for ( const mutation of mutations ) {
|
||||||
while ( i-- ) {
|
addIFrame(mutation.target, true);
|
||||||
addIFrame(mutations[i].target, true);
|
|
||||||
}
|
}
|
||||||
process();
|
process();
|
||||||
};
|
};
|
||||||
var iframeSourceObserver = new MutationObserver(iframeSourceModified);
|
const iframeSourceObserver = new MutationObserver(iframeSourceModified);
|
||||||
var iframeSourceObserverOptions = {
|
const iframeSourceObserverOptions = {
|
||||||
attributes: true,
|
attributes: true,
|
||||||
attributeFilter: [ 'src' ]
|
attributeFilter: [ 'src' ]
|
||||||
};
|
};
|
||||||
|
@ -880,7 +931,7 @@ vAPI.domCollapser = (function() {
|
||||||
// document, from within `bootstrapPhase1`, and which scriptlets are
|
// document, from within `bootstrapPhase1`, and which scriptlets are
|
||||||
// selectively looked-up from:
|
// selectively looked-up from:
|
||||||
// https://github.com/uBlockOrigin/uAssets/blob/master/filters/resources.txt
|
// https://github.com/uBlockOrigin/uAssets/blob/master/filters/resources.txt
|
||||||
var primeLocalIFrame = function(iframe) {
|
const primeLocalIFrame = function(iframe) {
|
||||||
if ( vAPI.injectedScripts ) {
|
if ( vAPI.injectedScripts ) {
|
||||||
vAPI.injectScriptlet(iframe.contentDocument, vAPI.injectedScripts);
|
vAPI.injectScriptlet(iframe.contentDocument, vAPI.injectedScripts);
|
||||||
}
|
}
|
||||||
|
@ -888,11 +939,11 @@ vAPI.domCollapser = (function() {
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/162
|
// https://github.com/gorhill/uBlock/issues/162
|
||||||
// Be prepared to deal with possible change of src attribute.
|
// Be prepared to deal with possible change of src attribute.
|
||||||
var addIFrame = function(iframe, dontObserve) {
|
const addIFrame = function(iframe, dontObserve) {
|
||||||
if ( dontObserve !== true ) {
|
if ( dontObserve !== true ) {
|
||||||
iframeSourceObserver.observe(iframe, iframeSourceObserverOptions);
|
iframeSourceObserver.observe(iframe, iframeSourceObserverOptions);
|
||||||
}
|
}
|
||||||
var src = iframe.src;
|
const src = iframe.src;
|
||||||
if ( src === '' || typeof src !== 'string' ) {
|
if ( src === '' || typeof src !== 'string' ) {
|
||||||
primeLocalIFrame(iframe);
|
primeLocalIFrame(iframe);
|
||||||
return;
|
return;
|
||||||
|
@ -905,21 +956,20 @@ vAPI.domCollapser = (function() {
|
||||||
add(iframe);
|
add(iframe);
|
||||||
};
|
};
|
||||||
|
|
||||||
var addIFrames = function(iframes) {
|
const addIFrames = function(iframes) {
|
||||||
var i = iframes.length;
|
for ( const iframe of iframes ) {
|
||||||
while ( i-- ) {
|
addIFrame(iframe);
|
||||||
addIFrame(iframes[i]);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var onResourceFailed = function(ev) {
|
const onResourceFailed = function(ev) {
|
||||||
if ( tagToTypeMap[ev.target.localName] !== undefined ) {
|
if ( tagToTypeMap[ev.target.localName] !== undefined ) {
|
||||||
add(ev.target);
|
add(ev.target);
|
||||||
process();
|
process();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var domWatcherInterface = {
|
const domWatcherInterface = {
|
||||||
onDOMCreated: function() {
|
onDOMCreated: function() {
|
||||||
if ( vAPI instanceof Object === false ) { return; }
|
if ( vAPI instanceof Object === false ) { return; }
|
||||||
if ( vAPI.domCollapser instanceof Object === false ) {
|
if ( vAPI.domCollapser instanceof Object === false ) {
|
||||||
|
@ -935,10 +985,9 @@ vAPI.domCollapser = (function() {
|
||||||
// https://github.com/chrisaljoudi/uBlock/issues/7
|
// https://github.com/chrisaljoudi/uBlock/issues/7
|
||||||
// Preferring getElementsByTagName over querySelectorAll:
|
// Preferring getElementsByTagName over querySelectorAll:
|
||||||
// http://jsperf.com/queryselectorall-vs-getelementsbytagname/145
|
// http://jsperf.com/queryselectorall-vs-getelementsbytagname/145
|
||||||
var elems = document.images || document.getElementsByTagName('img'),
|
const elems = document.images ||
|
||||||
i = elems.length, elem;
|
document.getElementsByTagName('img');
|
||||||
while ( i-- ) {
|
for ( const elem of elems ) {
|
||||||
elem = elems[i];
|
|
||||||
if ( elem.complete ) {
|
if ( elem.complete ) {
|
||||||
add(elem);
|
add(elem);
|
||||||
}
|
}
|
||||||
|
@ -958,15 +1007,13 @@ vAPI.domCollapser = (function() {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onDOMChanged: function(addedNodes) {
|
onDOMChanged: function(addedNodes) {
|
||||||
var ni = addedNodes.length;
|
if ( addedNodes.length === 0 ) { return; }
|
||||||
if ( ni === 0 ) { return; }
|
for ( const node of addedNodes ) {
|
||||||
for ( var i = 0, node; i < ni; i++ ) {
|
|
||||||
node = addedNodes[i];
|
|
||||||
if ( node.localName === 'iframe' ) {
|
if ( node.localName === 'iframe' ) {
|
||||||
addIFrame(node);
|
addIFrame(node);
|
||||||
}
|
}
|
||||||
if ( node.childElementCount === 0 ) { continue; }
|
if ( node.childElementCount === 0 ) { continue; }
|
||||||
var iframes = node.getElementsByTagName('iframe');
|
const iframes = node.getElementsByTagName('iframe');
|
||||||
if ( iframes.length !== 0 ) {
|
if ( iframes.length !== 0 ) {
|
||||||
addIFrames(iframes);
|
addIFrames(iframes);
|
||||||
}
|
}
|
||||||
|
@ -979,13 +1026,7 @@ vAPI.domCollapser = (function() {
|
||||||
vAPI.domWatcher.addListener(domWatcherInterface);
|
vAPI.domWatcher.addListener(domWatcherInterface);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return { add, addMany, addIFrame, addIFrames, process };
|
||||||
add: add,
|
|
||||||
addMany: addMany,
|
|
||||||
addIFrame: addIFrame,
|
|
||||||
addIFrames: addIFrames,
|
|
||||||
process: process
|
|
||||||
};
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -993,13 +1034,14 @@ vAPI.domCollapser = (function() {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
vAPI.domSurveyor = (function() {
|
vAPI.domSurveyor = (function() {
|
||||||
var messaging = vAPI.messaging,
|
const messaging = vAPI.messaging;
|
||||||
domFilterer,
|
const queriedIds = new Set();
|
||||||
|
const queriedClasses = new Set();
|
||||||
|
const pendingIdNodes = { nodes: [], added: [] };
|
||||||
|
const pendingClassNodes = { nodes: [], added: [] };
|
||||||
|
|
||||||
|
let domFilterer,
|
||||||
hostname = '',
|
hostname = '',
|
||||||
queriedIds = new Set(),
|
|
||||||
queriedClasses = new Set(),
|
|
||||||
pendingIdNodes = { nodes: [], added: [] },
|
|
||||||
pendingClassNodes = { nodes: [], added: [] },
|
|
||||||
surveyCost = 0;
|
surveyCost = 0;
|
||||||
|
|
||||||
// This is to shutdown the surveyor if result of surveying keeps being
|
// This is to shutdown the surveyor if result of surveying keeps being
|
||||||
|
@ -1007,17 +1049,17 @@ vAPI.domSurveyor = (function() {
|
||||||
// picked 5 minutes before the surveyor is allowed to shutdown. I also
|
// picked 5 minutes before the surveyor is allowed to shutdown. I also
|
||||||
// arbitrarily picked 256 misses before the surveyor is allowed to
|
// arbitrarily picked 256 misses before the surveyor is allowed to
|
||||||
// shutdown.
|
// shutdown.
|
||||||
var canShutdownAfter = Date.now() + 300000,
|
let canShutdownAfter = Date.now() + 300000,
|
||||||
surveyingMissCount = 0;
|
surveyingMissCount = 0;
|
||||||
|
|
||||||
// Handle main process' response.
|
// Handle main process' response.
|
||||||
|
|
||||||
var surveyPhase3 = function(response) {
|
const surveyPhase3 = function(response) {
|
||||||
var result = response && response.result,
|
const result = response && response.result;
|
||||||
mustCommit = false;
|
let mustCommit = false;
|
||||||
|
|
||||||
if ( result ) {
|
if ( result ) {
|
||||||
var selectors = result.simple;
|
let selectors = result.simple;
|
||||||
if ( Array.isArray(selectors) && selectors.length !== 0 ) {
|
if ( Array.isArray(selectors) && selectors.length !== 0 ) {
|
||||||
domFilterer.addCSSRule(
|
domFilterer.addCSSRule(
|
||||||
selectors,
|
selectors,
|
||||||
|
@ -1068,19 +1110,14 @@ vAPI.domSurveyor = (function() {
|
||||||
vAPI.domSurveyor = null;
|
vAPI.domSurveyor = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
var surveyTimer = new vAPI.SafeAnimationFrame(function() {
|
|
||||||
surveyPhase1();
|
|
||||||
});
|
|
||||||
|
|
||||||
// The purpose of "chunkification" is to ensure the surveyor won't unduly
|
// The purpose of "chunkification" is to ensure the surveyor won't unduly
|
||||||
// block the main event loop.
|
// block the main event loop.
|
||||||
|
|
||||||
var hasChunk = function(pending) {
|
const hasChunk = function(pending) {
|
||||||
return pending.nodes.length !== 0 ||
|
return pending.nodes.length !== 0 || pending.added.length !== 0;
|
||||||
pending.added.length !== 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var addChunk = function(pending, added) {
|
const addChunk = function(pending, added) {
|
||||||
if ( added.length === 0 ) { return; }
|
if ( added.length === 0 ) { return; }
|
||||||
if (
|
if (
|
||||||
Array.isArray(added) === false ||
|
Array.isArray(added) === false ||
|
||||||
|
@ -1094,8 +1131,8 @@ vAPI.domSurveyor = (function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var nextChunk = function(pending) {
|
const nextChunk = function(pending) {
|
||||||
var added = pending.added.length !== 0 ? pending.added.shift() : [],
|
let added = pending.added.length !== 0 ? pending.added.shift() : [],
|
||||||
nodes;
|
nodes;
|
||||||
if ( pending.nodes.length === 0 ) {
|
if ( pending.nodes.length === 0 ) {
|
||||||
if ( added.length <= 1000 ) { return added; }
|
if ( added.length <= 1000 ) { return added; }
|
||||||
|
@ -1121,39 +1158,39 @@ vAPI.domSurveyor = (function() {
|
||||||
// Extract all classes/ids: these will be passed to the cosmetic
|
// Extract all classes/ids: these will be passed to the cosmetic
|
||||||
// filtering engine, and in return we will obtain only the relevant
|
// filtering engine, and in return we will obtain only the relevant
|
||||||
// CSS selectors.
|
// CSS selectors.
|
||||||
|
const reWhitespace = /\s/;
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/672
|
// https://github.com/gorhill/uBlock/issues/672
|
||||||
// http://www.w3.org/TR/2014/REC-html5-20141028/infrastructure.html#space-separated-tokens
|
// http://www.w3.org/TR/2014/REC-html5-20141028/infrastructure.html#space-separated-tokens
|
||||||
// http://jsperf.com/enumerate-classes/6
|
// http://jsperf.com/enumerate-classes/6
|
||||||
|
|
||||||
var surveyPhase1 = function() {
|
const surveyPhase1 = function() {
|
||||||
//console.time('dom surveyor/surveying');
|
//console.time('dom surveyor/surveying');
|
||||||
surveyTimer.clear();
|
surveyTimer.clear();
|
||||||
var t0 = window.performance.now();
|
const t0 = window.performance.now();
|
||||||
var rews = reWhitespace,
|
const rews = reWhitespace;
|
||||||
qq, iout, nodes, i, node, v, vv, j;
|
const ids = [];
|
||||||
var ids = [];
|
let iout = 0;
|
||||||
iout = 0;
|
let qq = queriedIds;
|
||||||
qq = queriedIds;
|
let nodes = nextChunk(pendingIdNodes);
|
||||||
nodes = nextChunk(pendingIdNodes);
|
let i = nodes.length;
|
||||||
i = nodes.length;
|
|
||||||
while ( i-- ) {
|
while ( i-- ) {
|
||||||
node = nodes[i];
|
const node = nodes[i];
|
||||||
v = node.id;
|
let v = node.id;
|
||||||
if ( typeof v !== 'string' ) { continue; }
|
if ( typeof v !== 'string' ) { continue; }
|
||||||
v = v.trim();
|
v = v.trim();
|
||||||
if ( qq.has(v) === false && v.length !== 0 ) {
|
if ( qq.has(v) === false && v.length !== 0 ) {
|
||||||
ids[iout++] = v; qq.add(v);
|
ids[iout++] = v; qq.add(v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var classes = [];
|
const classes = [];
|
||||||
iout = 0;
|
iout = 0;
|
||||||
qq = queriedClasses;
|
qq = queriedClasses;
|
||||||
nodes = nextChunk(pendingClassNodes);
|
nodes = nextChunk(pendingClassNodes);
|
||||||
i = nodes.length;
|
i = nodes.length;
|
||||||
while ( i-- ) {
|
while ( i-- ) {
|
||||||
node = nodes[i];
|
const node = nodes[i];
|
||||||
vv = node.className;
|
let vv = node.className;
|
||||||
if ( typeof vv !== 'string' ) { continue; }
|
if ( typeof vv !== 'string' ) { continue; }
|
||||||
if ( rews.test(vv) === false ) {
|
if ( rews.test(vv) === false ) {
|
||||||
if ( qq.has(vv) === false && vv.length !== 0 ) {
|
if ( qq.has(vv) === false && vv.length !== 0 ) {
|
||||||
|
@ -1161,9 +1198,9 @@ vAPI.domSurveyor = (function() {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
vv = node.classList;
|
vv = node.classList;
|
||||||
j = vv.length;
|
let j = vv.length;
|
||||||
while ( j-- ) {
|
while ( j-- ) {
|
||||||
v = vv[j];
|
let v = vv[j];
|
||||||
if ( qq.has(v) === false ) {
|
if ( qq.has(v) === false ) {
|
||||||
classes[iout++] = v; qq.add(v);
|
classes[iout++] = v; qq.add(v);
|
||||||
}
|
}
|
||||||
|
@ -1190,9 +1227,10 @@ vAPI.domSurveyor = (function() {
|
||||||
}
|
}
|
||||||
//console.timeEnd('dom surveyor/surveying');
|
//console.timeEnd('dom surveyor/surveying');
|
||||||
};
|
};
|
||||||
var reWhitespace = /\s/;
|
|
||||||
|
|
||||||
var domWatcherInterface = {
|
const surveyTimer = new vAPI.SafeAnimationFrame(surveyPhase1);
|
||||||
|
|
||||||
|
const domWatcherInterface = {
|
||||||
onDOMCreated: function() {
|
onDOMCreated: function() {
|
||||||
if (
|
if (
|
||||||
vAPI instanceof Object === false ||
|
vAPI instanceof Object === false ||
|
||||||
|
@ -1217,17 +1255,18 @@ vAPI.domSurveyor = (function() {
|
||||||
onDOMChanged: function(addedNodes) {
|
onDOMChanged: function(addedNodes) {
|
||||||
if ( addedNodes.length === 0 ) { return; }
|
if ( addedNodes.length === 0 ) { return; }
|
||||||
//console.time('dom surveyor/dom layout changed');
|
//console.time('dom surveyor/dom layout changed');
|
||||||
var idNodes = [], iid = 0,
|
const idNodes = [];
|
||||||
classNodes = [], iclass = 0;
|
let iid = 0;
|
||||||
var i = addedNodes.length,
|
const classNodes = [];
|
||||||
node, nodeList, j;
|
let iclass = 0;
|
||||||
|
let i = addedNodes.length;
|
||||||
while ( i-- ) {
|
while ( i-- ) {
|
||||||
node = addedNodes[i];
|
const node = addedNodes[i];
|
||||||
idNodes[iid++] = node;
|
idNodes[iid++] = node;
|
||||||
classNodes[iclass++] = node;
|
classNodes[iclass++] = node;
|
||||||
if ( node.childElementCount === 0 ) { continue; }
|
if ( node.childElementCount === 0 ) { continue; }
|
||||||
nodeList = node.querySelectorAll('[id]');
|
let nodeList = node.querySelectorAll('[id]');
|
||||||
j = nodeList.length;
|
let j = nodeList.length;
|
||||||
while ( j-- ) {
|
while ( j-- ) {
|
||||||
idNodes[iid++] = nodeList[j];
|
idNodes[iid++] = nodeList[j];
|
||||||
}
|
}
|
||||||
|
@ -1246,15 +1285,13 @@ vAPI.domSurveyor = (function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var start = function(details) {
|
const start = function(details) {
|
||||||
if ( vAPI.domWatcher instanceof Object === false ) { return; }
|
if ( vAPI.domWatcher instanceof Object === false ) { return; }
|
||||||
hostname = details.hostname;
|
hostname = details.hostname;
|
||||||
vAPI.domWatcher.addListener(domWatcherInterface);
|
vAPI.domWatcher.addListener(domWatcherInterface);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return { start };
|
||||||
start: start
|
|
||||||
};
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -1266,18 +1303,11 @@ vAPI.domSurveyor = (function() {
|
||||||
|
|
||||||
(function bootstrap() {
|
(function bootstrap() {
|
||||||
|
|
||||||
var bootstrapPhase2 = function(ev) {
|
const bootstrapPhase2 = function() {
|
||||||
// This can happen on Firefox. For instance:
|
// This can happen on Firefox. For instance:
|
||||||
// https://github.com/gorhill/uBlock/issues/1893
|
// https://github.com/gorhill/uBlock/issues/1893
|
||||||
if ( window.location === null ) { return; }
|
if ( window.location === null ) { return; }
|
||||||
|
if ( vAPI instanceof Object === false ) { return; }
|
||||||
if ( ev ) {
|
|
||||||
document.removeEventListener('DOMContentLoaded', bootstrapPhase2);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( vAPI instanceof Object === false ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
vAPI.messaging.send(
|
vAPI.messaging.send(
|
||||||
'contentscript',
|
'contentscript',
|
||||||
|
@ -1303,8 +1333,8 @@ vAPI.domSurveyor = (function() {
|
||||||
// as nuisance popups.
|
// as nuisance popups.
|
||||||
// Ref.: https://developer.mozilla.org/en-US/docs/Web/Events/contextmenu
|
// Ref.: https://developer.mozilla.org/en-US/docs/Web/Events/contextmenu
|
||||||
|
|
||||||
var onMouseClick = function(ev) {
|
const onMouseClick = function(ev) {
|
||||||
var elem = ev.target;
|
let elem = ev.target;
|
||||||
while ( elem !== null && elem.localName !== 'a' ) {
|
while ( elem !== null && elem.localName !== 'a' ) {
|
||||||
elem = elem.parentElement;
|
elem = elem.parentElement;
|
||||||
}
|
}
|
||||||
|
@ -1327,9 +1357,9 @@ vAPI.domSurveyor = (function() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var bootstrapPhase1 = function(response) {
|
const bootstrapPhase1 = function(response) {
|
||||||
// cosmetic filtering engine aka 'cfe'
|
// cosmetic filtering engine aka 'cfe'
|
||||||
var cfeDetails = response && response.specificCosmeticFilters;
|
const cfeDetails = response && response.specificCosmeticFilters;
|
||||||
if ( !cfeDetails || !cfeDetails.ready ) {
|
if ( !cfeDetails || !cfeDetails.ready ) {
|
||||||
vAPI.domWatcher = vAPI.domCollapser = vAPI.domFilterer =
|
vAPI.domWatcher = vAPI.domCollapser = vAPI.domFilterer =
|
||||||
vAPI.domSurveyor = vAPI.domIsLoaded = null;
|
vAPI.domSurveyor = vAPI.domIsLoaded = null;
|
||||||
|
@ -1340,7 +1370,7 @@ vAPI.domSurveyor = (function() {
|
||||||
vAPI.domFilterer = null;
|
vAPI.domFilterer = null;
|
||||||
vAPI.domSurveyor = null;
|
vAPI.domSurveyor = null;
|
||||||
} else {
|
} else {
|
||||||
var domFilterer = vAPI.domFilterer;
|
const domFilterer = vAPI.domFilterer;
|
||||||
if ( response.noGenericCosmeticFiltering || cfeDetails.noDOMSurveying ) {
|
if ( response.noGenericCosmeticFiltering || cfeDetails.noDOMSurveying ) {
|
||||||
vAPI.domSurveyor = null;
|
vAPI.domSurveyor = null;
|
||||||
}
|
}
|
||||||
|
@ -1398,7 +1428,11 @@ vAPI.domSurveyor = (function() {
|
||||||
) {
|
) {
|
||||||
bootstrapPhase2();
|
bootstrapPhase2();
|
||||||
} else {
|
} else {
|
||||||
document.addEventListener('DOMContentLoaded', bootstrapPhase2);
|
document.addEventListener(
|
||||||
|
'DOMContentLoaded',
|
||||||
|
bootstrapPhase2,
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1419,4 +1453,5 @@ vAPI.domSurveyor = (function() {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
} // <<<<<<<< end of HUGE-IF-BLOCK
|
}
|
||||||
|
// <<<<<<<< end of HUGE-IF-BLOCK
|
||||||
|
|
|
@ -61,7 +61,7 @@ let filterFromCompiledData = function(args) {
|
||||||
|
|
||||||
// One hostname => one selector
|
// One hostname => one selector
|
||||||
|
|
||||||
let FilterOneOne = function(hostname, selector) {
|
const FilterOneOne = function(hostname, selector) {
|
||||||
this.hostname = hostname;
|
this.hostname = hostname;
|
||||||
this.selector = selector;
|
this.selector = selector;
|
||||||
};
|
};
|
||||||
|
@ -87,7 +87,7 @@ FilterOneOne.prototype = {
|
||||||
|
|
||||||
retrieve: function(target, out) {
|
retrieve: function(target, out) {
|
||||||
if ( target.endsWith(this.hostname) === false ) { return; }
|
if ( target.endsWith(this.hostname) === false ) { return; }
|
||||||
let i = target.length - this.hostname.length;
|
const i = target.length - this.hostname.length;
|
||||||
if ( i !== 0 && target.charCodeAt(i-1) !== 0x2E /* '.' */ ) { return; }
|
if ( i !== 0 && target.charCodeAt(i-1) !== 0x2E /* '.' */ ) { return; }
|
||||||
out.add(this.selector);
|
out.add(this.selector);
|
||||||
},
|
},
|
||||||
|
@ -107,7 +107,7 @@ registerFilterClass(FilterOneOne);
|
||||||
|
|
||||||
// One hostname => many selectors
|
// One hostname => many selectors
|
||||||
|
|
||||||
let FilterOneMany = function(hostname, selectors) {
|
const FilterOneMany = function(hostname, selectors) {
|
||||||
this.hostname = hostname;
|
this.hostname = hostname;
|
||||||
this.selectors = selectors;
|
this.selectors = selectors;
|
||||||
};
|
};
|
||||||
|
@ -131,7 +131,7 @@ FilterOneMany.prototype = {
|
||||||
|
|
||||||
retrieve: function(target, out) {
|
retrieve: function(target, out) {
|
||||||
if ( target.endsWith(this.hostname) === false ) { return; }
|
if ( target.endsWith(this.hostname) === false ) { return; }
|
||||||
let i = target.length - this.hostname.length;
|
const i = target.length - this.hostname.length;
|
||||||
if ( i !== 0 && target.charCodeAt(i-1) !== 0x2E /* '.' */ ) { return; }
|
if ( i !== 0 && target.charCodeAt(i-1) !== 0x2E /* '.' */ ) { return; }
|
||||||
for ( let selector of this.selectors ) {
|
for ( let selector of this.selectors ) {
|
||||||
out.add(selector);
|
out.add(selector);
|
||||||
|
@ -161,7 +161,7 @@ FilterManyAny.prototype = {
|
||||||
fid: 10,
|
fid: 10,
|
||||||
|
|
||||||
add: function(hostname, selector) {
|
add: function(hostname, selector) {
|
||||||
let selectors = this.entries.get(hostname);
|
const selectors = this.entries.get(hostname);
|
||||||
if ( selectors === undefined ) {
|
if ( selectors === undefined ) {
|
||||||
this.entries.set(hostname, selector);
|
this.entries.set(hostname, selector);
|
||||||
} else if ( typeof selectors === 'string' ) {
|
} else if ( typeof selectors === 'string' ) {
|
||||||
|
@ -172,19 +172,19 @@ FilterManyAny.prototype = {
|
||||||
},
|
},
|
||||||
|
|
||||||
retrieve: function(target, out) {
|
retrieve: function(target, out) {
|
||||||
for ( let entry of this.entries ) {
|
for ( const entry of this.entries ) {
|
||||||
let hostname = entry[0];
|
const hostname = entry[0];
|
||||||
if ( target.endsWith(hostname) === false ) { continue; }
|
if ( target.endsWith(hostname) === false ) { continue; }
|
||||||
let i = target.length - hostname.length;
|
const i = target.length - hostname.length;
|
||||||
if ( i !== 0 && target.charCodeAt(i-1) !== 0x2E /* '.' */ ) {
|
if ( i !== 0 && target.charCodeAt(i-1) !== 0x2E /* '.' */ ) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let selectors = entry[1];
|
const selectors = entry[1];
|
||||||
if ( typeof selectors === 'string' ) {
|
if ( typeof selectors === 'string' ) {
|
||||||
out.add(selectors);
|
out.add(selectors);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for ( let selector of selectors ) {
|
for ( const selector of selectors ) {
|
||||||
out.add(selector);
|
out.add(selector);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -523,8 +523,8 @@ FilterContainer.prototype.compile = function(parsed, writer) {
|
||||||
// 1000 = cosmetic filtering
|
// 1000 = cosmetic filtering
|
||||||
writer.select(1000);
|
writer.select(1000);
|
||||||
|
|
||||||
let hostnames = parsed.hostnames,
|
const hostnames = parsed.hostnames;
|
||||||
i = hostnames.length;
|
let i = hostnames.length;
|
||||||
if ( i === 0 ) {
|
if ( i === 0 ) {
|
||||||
this.compileGenericSelector(parsed, writer);
|
this.compileGenericSelector(parsed, writer);
|
||||||
return true;
|
return true;
|
||||||
|
@ -535,7 +535,7 @@ FilterContainer.prototype.compile = function(parsed, writer) {
|
||||||
// of same filter OR globally if there is no non-negated hostnames.
|
// of same filter OR globally if there is no non-negated hostnames.
|
||||||
let applyGlobally = true;
|
let applyGlobally = true;
|
||||||
while ( i-- ) {
|
while ( i-- ) {
|
||||||
let hostname = hostnames[i];
|
const hostname = hostnames[i];
|
||||||
if ( hostname.startsWith('~') === false ) {
|
if ( hostname.startsWith('~') === false ) {
|
||||||
applyGlobally = false;
|
applyGlobally = false;
|
||||||
}
|
}
|
||||||
|
@ -599,7 +599,7 @@ FilterContainer.prototype.compileGenericHideSelector = function(
|
||||||
if ( compiled === undefined || compiled !== selector ) {
|
if ( compiled === undefined || compiled !== selector ) {
|
||||||
const who = writer.properties.get('assetKey') || '?';
|
const who = writer.properties.get('assetKey') || '?';
|
||||||
µb.logger.writeOne({
|
µb.logger.writeOne({
|
||||||
error: `Invalid generic cosmetic filter in ${who} : ##${selector}`
|
error: `Invalid generic cosmetic filter in ${who}: ##${selector}`
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,6 +167,7 @@
|
||||||
'matches-css-after',
|
'matches-css-after',
|
||||||
'matches-css-before',
|
'matches-css-before',
|
||||||
'not',
|
'not',
|
||||||
|
'watch-attrs',
|
||||||
'xpath'
|
'xpath'
|
||||||
].join('|'),
|
].join('|'),
|
||||||
')\\('
|
')\\('
|
||||||
|
@ -235,6 +236,23 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const compileSpathExpression = function(s) {
|
||||||
|
if ( isValidCSSSelector('*' + s) ) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const compileAttrList = function(s) {
|
||||||
|
const attrs = s.split('\s*,\s*');
|
||||||
|
const out = [];
|
||||||
|
for ( const attr of attrs ) {
|
||||||
|
if ( attr !== '' ) {
|
||||||
|
out.push(attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
const compileXpathExpression = function(s) {
|
const compileXpathExpression = function(s) {
|
||||||
try {
|
try {
|
||||||
document.createExpression(s, null);
|
document.createExpression(s, null);
|
||||||
|
@ -260,6 +278,8 @@
|
||||||
[ ':matches-css-after', compileCSSDeclaration ],
|
[ ':matches-css-after', compileCSSDeclaration ],
|
||||||
[ ':matches-css-before', compileCSSDeclaration ],
|
[ ':matches-css-before', compileCSSDeclaration ],
|
||||||
[ ':not', compileNotSelector ],
|
[ ':not', compileNotSelector ],
|
||||||
|
[ ':spath', compileSpathExpression ],
|
||||||
|
[ ':watch-attrs', compileAttrList ],
|
||||||
[ ':xpath', compileXpathExpression ]
|
[ ':xpath', compileXpathExpression ]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -277,10 +297,11 @@
|
||||||
}
|
}
|
||||||
const raw = [ compiled.selector ];
|
const raw = [ compiled.selector ];
|
||||||
let value;
|
let value;
|
||||||
for ( let task of tasks ) {
|
for ( const task of tasks ) {
|
||||||
switch ( task[0] ) {
|
switch ( task[0] ) {
|
||||||
case ':xpath':
|
case ':has':
|
||||||
raw.push(task[0], '(', task[1], ')');
|
case ':if':
|
||||||
|
raw.push(':has', '(', decompile(task[1]), ')');
|
||||||
break;
|
break;
|
||||||
case ':has-text':
|
case ':has-text':
|
||||||
if ( Array.isArray(task[1]) ) {
|
if ( Array.isArray(task[1]) ) {
|
||||||
|
@ -306,14 +327,15 @@
|
||||||
}
|
}
|
||||||
raw.push(task[0], '(', task[1].name, ': ', value, ')');
|
raw.push(task[0], '(', task[1].name, ': ', value, ')');
|
||||||
break;
|
break;
|
||||||
case ':has':
|
|
||||||
case ':if':
|
|
||||||
raw.push(':has', '(', decompile(task[1]), ')');
|
|
||||||
break;
|
|
||||||
case ':if-not':
|
|
||||||
case ':not':
|
case ':not':
|
||||||
|
case ':if-not':
|
||||||
raw.push(':not', '(', decompile(task[1]), ')');
|
raw.push(':not', '(', decompile(task[1]), ')');
|
||||||
break;
|
break;
|
||||||
|
case ':spath':
|
||||||
|
case ':watch-attrs':
|
||||||
|
case ':xpath':
|
||||||
|
raw.push(task[0], '(', task[1], ')');
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return raw.join('');
|
return raw.join('');
|
||||||
|
@ -364,13 +386,7 @@
|
||||||
// then consider it to be part of the prefix. If there is
|
// then consider it to be part of the prefix. If there is
|
||||||
// at least one task present, then we fail, as we do not
|
// at least one task present, then we fail, as we do not
|
||||||
// support suffix CSS selectors.
|
// support suffix CSS selectors.
|
||||||
// TODO: AdGuard does support suffix CSS selectors, so
|
if ( isValidCSSSelector(raw.slice(opNameBeg, i)) ) { continue; }
|
||||||
// supporting this would increase compatibility with
|
|
||||||
// AdGuard filter lists.
|
|
||||||
if ( isValidCSSSelector(raw.slice(opNameBeg, i)) ) {
|
|
||||||
if ( opPrefixBeg !== 0 ) { return; }
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Extract and remember operator details.
|
// Extract and remember operator details.
|
||||||
let operator = raw.slice(opNameBeg, opNameEnd);
|
let operator = raw.slice(opNameBeg, opNameEnd);
|
||||||
operator = normalizedOperators.get(operator) || operator;
|
operator = normalizedOperators.get(operator) || operator;
|
||||||
|
@ -380,7 +396,11 @@
|
||||||
if ( opPrefixBeg === 0 ) {
|
if ( opPrefixBeg === 0 ) {
|
||||||
prefix = raw.slice(0, opNameBeg);
|
prefix = raw.slice(0, opNameBeg);
|
||||||
} else if ( opNameBeg !== opPrefixBeg ) {
|
} else if ( opNameBeg !== opPrefixBeg ) {
|
||||||
return;
|
const spath = compileSpathExpression(
|
||||||
|
raw.slice(opPrefixBeg, opNameBeg)
|
||||||
|
);
|
||||||
|
if ( spath === undefined ) { return; }
|
||||||
|
tasks.push([ ':spath', spath ]);
|
||||||
}
|
}
|
||||||
tasks.push([ operator, args ]);
|
tasks.push([ operator, args ]);
|
||||||
opPrefixBeg = i;
|
opPrefixBeg = i;
|
||||||
|
@ -392,7 +412,9 @@
|
||||||
prefix = raw;
|
prefix = raw;
|
||||||
tasks = undefined;
|
tasks = undefined;
|
||||||
} else if ( opPrefixBeg < n ) {
|
} else if ( opPrefixBeg < n ) {
|
||||||
return;
|
const spath = compileSpathExpression(raw.slice(opPrefixBeg));
|
||||||
|
if ( spath === undefined ) { return; }
|
||||||
|
tasks.push([ ':spath', spath ]);
|
||||||
}
|
}
|
||||||
// https://github.com/NanoAdblocker/NanoCore/issues/1#issuecomment-354394894
|
// https://github.com/NanoAdblocker/NanoCore/issues/1#issuecomment-354394894
|
||||||
if ( prefix !== '' ) {
|
if ( prefix !== '' ) {
|
||||||
|
@ -407,7 +429,7 @@
|
||||||
return lastProceduralSelectorCompiled;
|
return lastProceduralSelectorCompiled;
|
||||||
}
|
}
|
||||||
lastProceduralSelector = raw;
|
lastProceduralSelector = raw;
|
||||||
var compiled = compile(raw);
|
let compiled = compile(raw);
|
||||||
if ( compiled !== undefined ) {
|
if ( compiled !== undefined ) {
|
||||||
compiled.raw = decompile(compiled);
|
compiled.raw = decompile(compiled);
|
||||||
compiled = JSON.stringify(compiled);
|
compiled = JSON.stringify(compiled);
|
||||||
|
|
Loading…
Reference in New Issue