1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-10-04 16:47:15 +02:00

Add ability to report exception cosmetic filters in the logger

Related issue:
- https://github.com/gorhill/uBlock/issues/127

Additionally, the extended exception filters in the
logger will be rendered with a line-through to more
easily distinguish them from non-exception ones.

Also, opportunistically converted revisited code to
ES6 syntax.
This commit is contained in:
Raymond Hill 2019-05-16 13:44:49 -04:00
parent 2114c857a4
commit 3573b6b32c
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
10 changed files with 400 additions and 329 deletions

View File

@ -30,7 +30,7 @@
if ( typeof vAPI === 'object' ) { if ( typeof vAPI === 'object' ) {
vAPI.supportsUserStylesheets = vAPI.supportsUserStylesheets =
/\bChrom(?:e|ium)\/(?:6[6789]|[789]|1\d\d)/.test(navigator.userAgent); /\bChrom(?:e|ium)\/(?:5\d|6[012345])\b/.test(navigator.userAgent) === false;
} }

View File

@ -40,10 +40,10 @@ vAPI.userStylesheet = {
inject: function() { inject: function() {
this.style = document.createElement('style'); this.style = document.createElement('style');
this.style.disabled = this.disabled; this.style.disabled = this.disabled;
var parent = document.head || document.documentElement; const parent = document.head || document.documentElement;
if ( parent === null ) { return; } if ( parent === null ) { return; }
parent.appendChild(this.style); parent.appendChild(this.style);
var observer = new MutationObserver(function() { const observer = new MutationObserver(function() {
if ( this.style === null ) { return; } if ( this.style === null ) { return; }
if ( this.style.sheet !== null ) { return; } if ( this.style.sheet !== null ) { return; }
this.styleFixCount += 1; this.styleFixCount += 1;
@ -58,31 +58,31 @@ vAPI.userStylesheet = {
add: function(cssText) { add: function(cssText) {
if ( cssText === '' || this.css.has(cssText) ) { return; } if ( cssText === '' || this.css.has(cssText) ) { return; }
if ( this.style === null ) { this.inject(); } if ( this.style === null ) { this.inject(); }
var sheet = this.style.sheet; const sheet = this.style.sheet;
if ( !sheet ) { return; } if ( !sheet ) { return; }
var i = sheet.cssRules.length; const i = sheet.cssRules.length;
sheet.insertRule(cssText, i); sheet.insertRule(cssText, i);
this.css.set(cssText, sheet.cssRules[i]); this.css.set(cssText, sheet.cssRules[i]);
}, },
remove: function(cssText) { remove: function(cssText) {
if ( cssText === '' ) { return; } if ( cssText === '' ) { return; }
var cssRule = this.css.get(cssText); const cssRule = this.css.get(cssText);
if ( cssRule === undefined ) { return; } if ( cssRule === undefined ) { return; }
this.css.delete(cssText); this.css.delete(cssText);
if ( this.style === null ) { return; } if ( this.style === null ) { return; }
var sheet = this.style.sheet; const sheet = this.style.sheet;
if ( !sheet ) { return; } if ( !sheet ) { return; }
var rules = sheet.cssRules, const rules = sheet.cssRules;
i = rules.length; let i = rules.length;
while ( i-- ) { while ( i-- ) {
if ( rules[i] !== cssRule ) { continue; } if ( rules[i] !== cssRule ) { continue; }
sheet.deleteRule(i); sheet.deleteRule(i);
break; break;
} }
if ( rules.length !== 0 ) { return; } if ( rules.length !== 0 ) { return; }
var style = this.style; const style = this.style;
this.style = null; this.style = null;
var parent = style.parentNode; const parent = style.parentNode;
if ( parent !== null ) { if ( parent !== null ) {
parent.removeChild(style); parent.removeChild(style);
} }
@ -99,55 +99,54 @@ vAPI.userStylesheet = {
/******************************************************************************/ /******************************************************************************/
vAPI.DOMFilterer = function() { vAPI.DOMFilterer = class {
this.commitTimer = new vAPI.SafeAnimationFrame(this.commitNow.bind(this)); constructor() {
this.domIsReady = document.readyState !== 'loading'; this.commitTimer = new vAPI.SafeAnimationFrame(this.commitNow.bind(this));
this.listeners = []; this.domIsReady = document.readyState !== 'loading';
this.excludedNodeSet = new WeakSet(); this.listeners = [];
this.addedNodes = new Set(); this.excludedNodeSet = new WeakSet();
this.removedNodes = false; this.addedNodes = new Set();
this.removedNodes = false;
this.specificSimpleHide = new Set(); this.specificSimpleHide = new Set();
this.specificSimpleHideAggregated = undefined; this.specificSimpleHideAggregated = undefined;
this.addedSpecificSimpleHide = []; this.addedSpecificSimpleHide = [];
this.specificComplexHide = new Set(); this.specificComplexHide = new Set();
this.specificComplexHideAggregated = undefined; this.specificComplexHideAggregated = undefined;
this.addedSpecificComplexHide = []; this.addedSpecificComplexHide = [];
this.specificOthers = []; this.specificOthers = [];
this.genericSimpleHide = new Set(); this.genericSimpleHide = new Set();
this.genericComplexHide = new Set(); this.genericComplexHide = new Set();
this.exceptedCSSRules = [];
this.hideNodeExpando = undefined; this.hideNodeExpando = undefined;
this.hideNodeBatchProcessTimer = undefined; this.hideNodeBatchProcessTimer = undefined;
this.hiddenNodeObserver = undefined; this.hiddenNodeObserver = undefined;
this.hiddenNodesetToProcess = new Set(); this.hiddenNodesetToProcess = new Set();
this.hiddenNodeset = new WeakSet(); this.hiddenNodeset = new WeakSet();
if ( vAPI.domWatcher instanceof Object ) { if ( vAPI.domWatcher instanceof Object ) {
vAPI.domWatcher.addListener(this); vAPI.domWatcher.addListener(this);
}
// https://www.w3.org/community/webed/wiki/CSS/Selectors#Combinators
this.reCSSCombinators = /[ >+~]/;
} }
};
vAPI.DOMFilterer.prototype = { commitNow() {
// https://www.w3.org/community/webed/wiki/CSS/Selectors#Combinators
reCSSCombinators: /[ >+~]/,
commitNow: function() {
this.commitTimer.clear(); this.commitTimer.clear();
if ( this.domIsReady !== true || vAPI.userStylesheet.disabled ) { if ( this.domIsReady !== true || vAPI.userStylesheet.disabled ) {
return; return;
} }
var nodes, node;
// Filterset changed. // Filterset changed.
if ( this.addedSpecificSimpleHide.length !== 0 ) { if ( this.addedSpecificSimpleHide.length !== 0 ) {
//console.time('specific simple filterset changed'); //console.time('specific simple filterset changed');
//console.log('added %d specific simple selectors', this.addedSpecificSimpleHide.length); //console.log('added %d specific simple selectors', this.addedSpecificSimpleHide.length);
nodes = document.querySelectorAll(this.addedSpecificSimpleHide.join(',')); const nodes = document.querySelectorAll(this.addedSpecificSimpleHide.join(','));
for ( node of nodes ) { for ( const node of nodes ) {
this.hideNode(node); this.hideNode(node);
} }
this.addedSpecificSimpleHide = []; this.addedSpecificSimpleHide = [];
@ -158,8 +157,8 @@ vAPI.DOMFilterer.prototype = {
if ( this.addedSpecificComplexHide.length !== 0 ) { if ( this.addedSpecificComplexHide.length !== 0 ) {
//console.time('specific complex filterset changed'); //console.time('specific complex filterset changed');
//console.log('added %d specific complex selectors', this.addedSpecificComplexHide.length); //console.log('added %d specific complex selectors', this.addedSpecificComplexHide.length);
nodes = document.querySelectorAll(this.addedSpecificComplexHide.join(',')); const nodes = document.querySelectorAll(this.addedSpecificComplexHide.join(','));
for ( node of nodes ) { for ( const node of nodes ) {
this.hideNode(node); this.hideNode(node);
} }
this.addedSpecificComplexHide = []; this.addedSpecificComplexHide = [];
@ -169,8 +168,8 @@ vAPI.DOMFilterer.prototype = {
// DOM layout changed. // DOM layout changed.
var domNodesAdded = this.addedNodes.size !== 0, const domNodesAdded = this.addedNodes.size !== 0;
domLayoutChanged = domNodesAdded || this.removedNodes; const domLayoutChanged = domNodesAdded || this.removedNodes;
if ( domNodesAdded === false || domLayoutChanged === false ) { if ( domNodesAdded === false || domLayoutChanged === false ) {
return; return;
@ -184,12 +183,12 @@ vAPI.DOMFilterer.prototype = {
this.specificSimpleHideAggregated = this.specificSimpleHideAggregated =
Array.from(this.specificSimpleHide).join(',\n'); Array.from(this.specificSimpleHide).join(',\n');
} }
for ( node of this.addedNodes ) { for ( const node of this.addedNodes ) {
if ( node[vAPI.matchesProp](this.specificSimpleHideAggregated) ) { if ( node[vAPI.matchesProp](this.specificSimpleHideAggregated) ) {
this.hideNode(node); this.hideNode(node);
} }
nodes = node.querySelectorAll(this.specificSimpleHideAggregated); const nodes = node.querySelectorAll(this.specificSimpleHideAggregated);
for ( node of nodes ) { for ( const node of nodes ) {
this.hideNode(node); this.hideNode(node);
} }
} }
@ -202,8 +201,8 @@ vAPI.DOMFilterer.prototype = {
this.specificComplexHideAggregated = this.specificComplexHideAggregated =
Array.from(this.specificComplexHide).join(',\n'); Array.from(this.specificComplexHide).join(',\n');
} }
nodes = document.querySelectorAll(this.specificComplexHideAggregated); const nodes = document.querySelectorAll(this.specificComplexHideAggregated);
for ( node of nodes ) { for ( const node of nodes ) {
this.hideNode(node); this.hideNode(node);
} }
//console.timeEnd('dom layout changed/specific complex selectors'); //console.timeEnd('dom layout changed/specific complex selectors');
@ -211,23 +210,23 @@ vAPI.DOMFilterer.prototype = {
this.addedNodes.clear(); this.addedNodes.clear();
this.removedNodes = false; this.removedNodes = false;
}, }
commit: function(now) { commit(now) {
if ( now ) { if ( now ) {
this.commitTimer.clear(); this.commitTimer.clear();
this.commitNow(); this.commitNow();
} else { } else {
this.commitTimer.start(); this.commitTimer.start();
} }
}, }
addCSSRule: function(selectors, declarations, details) { addCSSRule(selectors, declarations, details) {
if ( selectors === undefined ) { return; } if ( selectors === undefined ) { return; }
if ( details === undefined ) { details = {}; } if ( details === undefined ) { details = {}; }
var selectorsStr = Array.isArray(selectors) ? const selectorsStr = Array.isArray(selectors) ?
selectors.join(',\n') : selectors.join(',\n') :
selectors; selectors;
if ( selectorsStr.length === 0 ) { return; } if ( selectorsStr.length === 0 ) { return; }
@ -251,10 +250,9 @@ vAPI.DOMFilterer.prototype = {
// Do not strongly enforce internal CSS rules. // Do not strongly enforce internal CSS rules.
if ( details.internal ) { return; } if ( details.internal ) { return; }
var isGeneric= details.lazy === true, const isGeneric= details.lazy === true;
isSimple = details.type === 'simple', const isSimple = details.type === 'simple';
isComplex = details.type === 'complex', const isComplex = details.type === 'complex';
selector;
if ( isGeneric ) { if ( isGeneric ) {
if ( isSimple ) { if ( isSimple ) {
@ -267,12 +265,12 @@ vAPI.DOMFilterer.prototype = {
} }
} }
var selectorsArr = Array.isArray(selectors) ? const selectorsArr = Array.isArray(selectors) ?
selectors : selectors :
selectors.split(',\n'); selectors.split(',\n');
if ( isGeneric ) { if ( isGeneric ) {
for ( selector of selectorsArr ) { for ( const selector of selectorsArr ) {
if ( this.reCSSCombinators.test(selector) ) { if ( this.reCSSCombinators.test(selector) ) {
this.genericComplexHide.add(selector); this.genericComplexHide.add(selector);
} else { } else {
@ -283,7 +281,7 @@ vAPI.DOMFilterer.prototype = {
} }
// Specific cosmetic filters. // Specific cosmetic filters.
for ( selector of selectorsArr ) { for ( const selector of selectorsArr ) {
if ( if (
isComplex || isComplex ||
isSimple === false && this.reCSSCombinators.test(selector) isSimple === false && this.reCSSCombinators.test(selector)
@ -297,44 +295,51 @@ vAPI.DOMFilterer.prototype = {
this.addedSpecificSimpleHide.push(selector); this.addedSpecificSimpleHide.push(selector);
} }
} }
}, }
onDOMCreated: function() { exceptCSSRules(exceptions) {
if ( exceptions.length === 0 ) { return; }
this.exceptedCSSRules.push(...exceptions);
if ( this.hasListeners() ) {
this.triggerListeners({ exceptions });
}
}
onDOMCreated() {
this.domIsReady = true; this.domIsReady = true;
this.addedNodes.clear(); this.addedNodes.clear();
this.removedNodes = false; this.removedNodes = false;
this.commit(); this.commit();
}, }
onDOMChanged: function(addedNodes, removedNodes) { onDOMChanged(addedNodes, removedNodes) {
for ( var node of addedNodes ) { for ( const node of addedNodes ) {
this.addedNodes.add(node); this.addedNodes.add(node);
} }
this.removedNodes = this.removedNodes || removedNodes; this.removedNodes = this.removedNodes || removedNodes;
this.commit(); this.commit();
}, }
addListener: function(listener) { addListener(listener) {
if ( this.listeners.indexOf(listener) !== -1 ) { return; } if ( this.listeners.indexOf(listener) !== -1 ) { return; }
this.listeners.push(listener); this.listeners.push(listener);
}, }
removeListener: function(listener) { removeListener(listener) {
var pos = this.listeners.indexOf(listener); const pos = this.listeners.indexOf(listener);
if ( pos === -1 ) { return; } if ( pos === -1 ) { return; }
this.listeners.splice(pos, 1); this.listeners.splice(pos, 1);
}, }
hasListeners: function() { hasListeners() {
return this.listeners.length !== 0; return this.listeners.length !== 0;
}, }
triggerListeners: function(changes) { triggerListeners(changes) {
var i = this.listeners.length; for ( const listener of this.listeners ) {
while ( i-- ) { listener.onFiltersetChanged(changes);
this.listeners[i].onFiltersetChanged(changes);
} }
}, }
// https://jsperf.com/clientheight-and-clientwidth-vs-getcomputedstyle // https://jsperf.com/clientheight-and-clientwidth-vs-getcomputedstyle
// Avoid getComputedStyle(), detecting whether a node is visible can be // Avoid getComputedStyle(), detecting whether a node is visible can be
@ -351,10 +356,10 @@ vAPI.DOMFilterer.prototype = {
// However, toggling off/on cosmetic filtering repeatedly is not // However, toggling off/on cosmetic filtering repeatedly is not
// a real use case, but this shows this will help performance // a real use case, but this shows this will help performance
// on sites which try to use inline styles to bypass blockers. // on sites which try to use inline styles to bypass blockers.
hideNodeBatchProcess: function() { hideNodeBatchProcess() {
this.hideNodeBatchProcessTimer.clear(); this.hideNodeBatchProcessTimer.clear();
var expando = this.hideNodeExpando; const expando = this.hideNodeExpando;
for ( var node of this.hiddenNodesetToProcess ) { for ( const node of this.hiddenNodesetToProcess ) {
if ( if (
this.hiddenNodeset.has(node) === false || this.hiddenNodeset.has(node) === false ||
node[expando] === undefined || node[expando] === undefined ||
@ -362,7 +367,7 @@ vAPI.DOMFilterer.prototype = {
) { ) {
continue; continue;
} }
var attr = node.getAttribute('style'); let attr = node.getAttribute('style');
if ( attr === null ) { if ( attr === null ) {
attr = ''; attr = '';
} else if ( } else if (
@ -374,24 +379,18 @@ vAPI.DOMFilterer.prototype = {
node.setAttribute('style', attr + 'display:none!important;'); node.setAttribute('style', attr + 'display:none!important;');
} }
this.hiddenNodesetToProcess.clear(); this.hiddenNodesetToProcess.clear();
}, }
hideNodeObserverHandler: function(mutations) { hideNodeObserverHandler(mutations) {
if ( vAPI.userStylesheet.disabled ) { return; } if ( vAPI.userStylesheet.disabled ) { return; }
var i = mutations.length, const stagedNodes = this.hiddenNodesetToProcess;
stagedNodes = this.hiddenNodesetToProcess; for ( const mutation of mutations ) {
while ( i-- ) { stagedNodes.add(mutation.target);
stagedNodes.add(mutations[i].target);
} }
this.hideNodeBatchProcessTimer.start(); this.hideNodeBatchProcessTimer.start();
}, }
hiddenNodeObserverOptions: { hideNodeInit() {
attributes: true,
attributeFilter: [ 'style' ]
},
hideNodeInit: function() {
this.hideNodeExpando = vAPI.randomToken(); this.hideNodeExpando = vAPI.randomToken();
this.hideNodeBatchProcessTimer = this.hideNodeBatchProcessTimer =
new vAPI.SafeAnimationFrame(this.hideNodeBatchProcess.bind(this)); new vAPI.SafeAnimationFrame(this.hideNodeBatchProcess.bind(this));
@ -400,21 +399,21 @@ vAPI.DOMFilterer.prototype = {
if ( this.hideNodeStyleSheetInjected === false ) { if ( this.hideNodeStyleSheetInjected === false ) {
this.hideNodeStyleSheetInjected = true; this.hideNodeStyleSheetInjected = true;
vAPI.userStylesheet.add( vAPI.userStylesheet.add(
'[' + this.hideNodeAttr + ']\n{display:none!important;}' `[${this.hideNodeAttr}]\n{display:none!important;}`
); );
} }
}, }
excludeNode: function(node) { excludeNode(node) {
this.excludedNodeSet.add(node); this.excludedNodeSet.add(node);
this.unhideNode(node); this.unhideNode(node);
}, }
unexcludeNode: function(node) { unexcludeNode(node) {
this.excludedNodeSet.delete(node); this.excludedNodeSet.delete(node);
}, }
hideNode: function(node) { hideNode(node) {
if ( this.excludedNodeSet.has(node) ) { return; } if ( this.excludedNodeSet.has(node) ) { return; }
if ( this.hideNodeAttr === undefined ) { return; } if ( this.hideNodeAttr === undefined ) { return; }
if ( this.hiddenNodeset.has(node) ) { return; } if ( this.hiddenNodeset.has(node) ) { return; }
@ -430,15 +429,15 @@ vAPI.DOMFilterer.prototype = {
this.hiddenNodesetToProcess.add(node); this.hiddenNodesetToProcess.add(node);
this.hideNodeBatchProcessTimer.start(); this.hideNodeBatchProcessTimer.start();
this.hiddenNodeObserver.observe(node, this.hiddenNodeObserverOptions); this.hiddenNodeObserver.observe(node, this.hiddenNodeObserverOptions);
}, }
unhideNode: function(node) { unhideNode(node) {
if ( this.hiddenNodeset.has(node) === false ) { return; } if ( this.hiddenNodeset.has(node) === false ) { return; }
node.hidden = false; node.hidden = false;
node.removeAttribute(this.hideNodeAttr); node.removeAttribute(this.hideNodeAttr);
this.hiddenNodesetToProcess.delete(node); this.hiddenNodesetToProcess.delete(node);
if ( this.hideNodeExpando === undefined ) { return; } if ( this.hideNodeExpando === undefined ) { return; }
var attr = node[this.hideNodeExpando]; const attr = node[this.hideNodeExpando];
if ( attr === false ) { if ( attr === false ) {
node.removeAttribute('style'); node.removeAttribute('style');
} else if ( typeof attr === 'string' ) { } else if ( typeof attr === 'string' ) {
@ -446,28 +445,28 @@ vAPI.DOMFilterer.prototype = {
} }
node[this.hideNodeExpando] = undefined; node[this.hideNodeExpando] = undefined;
this.hiddenNodeset.delete(node); this.hiddenNodeset.delete(node);
}, }
showNode: function(node) { showNode(node) {
node.hidden = false; node.hidden = false;
var attr = node[this.hideNodeExpando]; const attr = node[this.hideNodeExpando];
if ( attr === false ) { if ( attr === false ) {
node.removeAttribute('style'); node.removeAttribute('style');
} else if ( typeof attr === 'string' ) { } else if ( typeof attr === 'string' ) {
node.setAttribute('style', attr); node.setAttribute('style', attr);
} }
}, }
unshowNode: function(node) { unshowNode(node) {
node.hidden = true; node.hidden = true;
this.hiddenNodesetToProcess.add(node); this.hiddenNodesetToProcess.add(node);
}, }
toggle: function(state, callback) { toggle(state, callback) {
vAPI.userStylesheet.toggle(state); vAPI.userStylesheet.toggle(state);
var disabled = vAPI.userStylesheet.disabled, const disabled = vAPI.userStylesheet.disabled;
nodes = document.querySelectorAll('[' + this.hideNodeAttr + ']'); const nodes = document.querySelectorAll(`[${this.hideNodeAttr}]`);
for ( var node of nodes ) { for ( const node of nodes ) {
if ( disabled ) { if ( disabled ) {
this.showNode(node); this.showNode(node);
} else { } else {
@ -480,11 +479,12 @@ vAPI.DOMFilterer.prototype = {
if ( typeof callback === 'function' ) { if ( typeof callback === 'function' ) {
callback(); callback();
} }
}, }
getAllSelectors_: function(all) { getAllSelectors_(all) {
var out = { const out = {
declarative: [] declarative: [],
exceptions: this.exceptedCSSRules,
}; };
if ( this.specificSimpleHide.size !== 0 ) { if ( this.specificSimpleHide.size !== 0 ) {
out.declarative.push([ out.declarative.push([
@ -516,28 +516,30 @@ vAPI.DOMFilterer.prototype = {
'display:none!important;' 'display:none!important;'
]); ]);
} }
for ( var entry of this.specificOthers ) { for ( const entry of this.specificOthers ) {
out.declarative.push([ entry.selectors, entry.declarations ]); out.declarative.push([ entry.selectors, entry.declarations ]);
} }
return out; return out;
}, }
getFilteredElementCount: function() { getFilteredElementCount() {
var details = this.getAllSelectors_(true); const details = this.getAllSelectors_(true);
if ( Array.isArray(details.declarative) === false ) { return 0; } if ( Array.isArray(details.declarative) === false ) { return 0; }
var selectors = details.declarative.reduce(function(acc, entry) { const selectors = details.declarative.map(entry => entry[0]);
acc.push(entry[0]);
return acc;
}, []);
if ( selectors.length === 0 ) { return 0; } if ( selectors.length === 0 ) { return 0; }
return document.querySelectorAll(selectors.join(',\n')).length; return document.querySelectorAll(selectors.join(',\n')).length;
}, }
getAllSelectors: function() { getAllSelectors() {
return this.getAllSelectors_(false); return this.getAllSelectors_(false);
} }
}; };
vAPI.DOMFilterer.prototype.hiddenNodeObserverOptions = {
attributes: true,
attributeFilter: [ 'style' ]
};
/******************************************************************************/ /******************************************************************************/
/******************************************************************************/ /******************************************************************************/

View File

@ -57,31 +57,31 @@ vAPI.userStylesheet = {
/******************************************************************************/ /******************************************************************************/
vAPI.DOMFilterer = function() { vAPI.DOMFilterer = class {
this.commitTimer = new vAPI.SafeAnimationFrame(this.commitNow.bind(this)); constructor() {
this.domIsReady = document.readyState !== 'loading'; this.commitTimer = new vAPI.SafeAnimationFrame(( ) => this.commitNow);
this.disabled = false; this.domIsReady = document.readyState !== 'loading';
this.listeners = []; this.disabled = false;
this.filterset = new Set(); this.listeners = [];
this.excludedNodeSet = new WeakSet(); this.filterset = new Set();
this.addedCSSRules = new Set(); this.excludedNodeSet = new WeakSet();
this.addedCSSRules = new Set();
this.exceptedCSSRules = [];
this.reOnlySelectors = /\n\{[^\n]+/g;
// https://github.com/uBlockOrigin/uBlock-issues/issues/167 // https://github.com/uBlockOrigin/uBlock-issues/issues/167
// By the time the DOMContentLoaded is fired, the content script might // By the time the DOMContentLoaded is fired, the content script might
// have been disconnected from the background page. Unclear why this // have been disconnected from the background page. Unclear why this
// would happen, so far seems to be a Chromium-specific behavior at // would happen, so far seems to be a Chromium-specific behavior at
// launch time. // launch time.
if ( this.domIsReady !== true ) { if ( this.domIsReady !== true ) {
document.addEventListener('DOMContentLoaded', ( ) => { document.addEventListener('DOMContentLoaded', ( ) => {
if ( vAPI instanceof Object === false ) { return; } if ( vAPI instanceof Object === false ) { return; }
this.domIsReady = true; this.domIsReady = true;
this.commit(); this.commit();
}); });
}
} }
};
vAPI.DOMFilterer.prototype = {
reOnlySelectors: /\n\{[^\n]+/g,
// Here we will deal with: // Here we will deal with:
// - Injecting low priority user styles; // - Injecting low priority user styles;
@ -92,11 +92,11 @@ vAPI.DOMFilterer.prototype = {
// process. Another approach would be to have vAPI.SafeAnimationFrame // process. Another approach would be to have vAPI.SafeAnimationFrame
// register a shutdown job: to evaluate. For now I will keep the fix // register a shutdown job: to evaluate. For now I will keep the fix
// trivial. // trivial.
commitNow: function() { commitNow() {
this.commitTimer.clear(); this.commitTimer.clear();
if ( vAPI instanceof Object === false ) { return; } if ( vAPI instanceof Object === false ) { return; }
let userStylesheet = vAPI.userStylesheet; const userStylesheet = vAPI.userStylesheet;
for ( let entry of this.addedCSSRules ) { for ( const entry of this.addedCSSRules ) {
if ( if (
this.disabled === false && this.disabled === false &&
entry.lazy && entry.lazy &&
@ -109,25 +109,25 @@ vAPI.DOMFilterer.prototype = {
} }
this.addedCSSRules.clear(); this.addedCSSRules.clear();
userStylesheet.apply(); userStylesheet.apply();
}, }
commit: function(commitNow) { commit(commitNow) {
if ( commitNow ) { if ( commitNow ) {
this.commitTimer.clear(); this.commitTimer.clear();
this.commitNow(); this.commitNow();
} else { } else {
this.commitTimer.start(); this.commitTimer.start();
} }
}, }
addCSSRule: function(selectors, declarations, details) { addCSSRule(selectors, declarations, details) {
if ( selectors === undefined ) { return; } if ( selectors === undefined ) { return; }
var selectorsStr = Array.isArray(selectors) const selectorsStr = Array.isArray(selectors)
? selectors.join(',\n') ? selectors.join(',\n')
: selectors; : selectors;
if ( selectorsStr.length === 0 ) { return; } if ( selectorsStr.length === 0 ) { return; }
if ( details === undefined ) { details = {}; } if ( details === undefined ) { details = {}; }
var entry = { const entry = {
selectors: selectorsStr, selectors: selectorsStr,
declarations, declarations,
lazy: details.lazy === true, lazy: details.lazy === true,
@ -148,64 +148,71 @@ vAPI.DOMFilterer.prototype = {
declarative: [ [ selectorsStr, declarations ] ] declarative: [ [ selectorsStr, declarations ] ]
}); });
} }
}, }
addListener: function(listener) { exceptCSSRules(exceptions) {
if ( exceptions.length === 0 ) { return; }
this.exceptedCSSRules.push(...exceptions);
if ( this.hasListeners() ) {
this.triggerListeners({ exceptions });
}
}
addListener(listener) {
if ( this.listeners.indexOf(listener) !== -1 ) { return; } if ( this.listeners.indexOf(listener) !== -1 ) { return; }
this.listeners.push(listener); this.listeners.push(listener);
}, }
removeListener: function(listener) { removeListener(listener) {
var pos = this.listeners.indexOf(listener); const pos = this.listeners.indexOf(listener);
if ( pos === -1 ) { return; } if ( pos === -1 ) { return; }
this.listeners.splice(pos, 1); this.listeners.splice(pos, 1);
}, }
hasListeners: function() { hasListeners() {
return this.listeners.length !== 0; return this.listeners.length !== 0;
}, }
triggerListeners: function(changes) { triggerListeners(changes) {
var i = this.listeners.length; for ( const listener of this.listeners ) {
while ( i-- ) { listener.onFiltersetChanged(changes);
this.listeners[i].onFiltersetChanged(changes);
} }
}, }
excludeNode: function(node) { excludeNode(node) {
this.excludedNodeSet.add(node); this.excludedNodeSet.add(node);
this.unhideNode(node); this.unhideNode(node);
}, }
unexcludeNode: function(node) { unexcludeNode(node) {
this.excludedNodeSet.delete(node); this.excludedNodeSet.delete(node);
}, }
hideNode: function(node) { hideNode(node) {
if ( this.excludedNodeSet.has(node) ) { return; } if ( this.excludedNodeSet.has(node) ) { return; }
if ( this.hideNodeAttr === undefined ) { return; } if ( this.hideNodeAttr === undefined ) { return; }
node.setAttribute(this.hideNodeAttr, ''); node.setAttribute(this.hideNodeAttr, '');
if ( this.hideNodeStyleSheetInjected === false ) { if ( this.hideNodeStyleSheetInjected === false ) {
this.hideNodeStyleSheetInjected = true; this.hideNodeStyleSheetInjected = true;
this.addCSSRule( this.addCSSRule(
'[' + this.hideNodeAttr + ']', `[${this.hideNodeAttr}]`,
'display:none!important;' 'display:none!important;'
); );
} }
}, }
unhideNode: function(node) { unhideNode(node) {
if ( this.hideNodeAttr === undefined ) { return; } if ( this.hideNodeAttr === undefined ) { return; }
node.removeAttribute(this.hideNodeAttr); node.removeAttribute(this.hideNodeAttr);
}, }
toggle: function(state, callback) { toggle(state, callback) {
if ( state === undefined ) { state = this.disabled; } if ( state === undefined ) { state = this.disabled; }
if ( state !== this.disabled ) { return; } if ( state !== this.disabled ) { return; }
this.disabled = !state; this.disabled = !state;
var userStylesheet = vAPI.userStylesheet; const userStylesheet = vAPI.userStylesheet;
for ( var entry of this.filterset ) { for ( const entry of this.filterset ) {
var rule = entry.selectors + '\n{' + entry.declarations + '}'; const rule = `${entry.selectors}\n{${entry.declarations}}`;
if ( this.disabled ) { if ( this.disabled ) {
userStylesheet.remove(rule); userStylesheet.remove(rule);
} else { } else {
@ -213,38 +220,35 @@ vAPI.DOMFilterer.prototype = {
} }
} }
userStylesheet.apply(callback); userStylesheet.apply(callback);
}, }
getAllSelectors_: function(all) { getAllSelectors_(all) {
var out = { const out = {
declarative: [] declarative: [],
exceptions: this.exceptedCSSRules,
}; };
var selectors; for ( const entry of this.filterset ) {
for ( var entry of this.filterset ) { let selectors = entry.selectors;
selectors = entry.selectors;
if ( all !== true && this.hideNodeAttr !== undefined ) { if ( all !== true && this.hideNodeAttr !== undefined ) {
selectors = selectors selectors = selectors
.replace('[' + this.hideNodeAttr + ']', '') .replace(`[${this.hideNodeAttr}]`, '')
.replace(/^,\n|,\n$/gm, ''); .replace(/^,\n|,\n$/gm, '');
if ( selectors === '' ) { continue; } if ( selectors === '' ) { continue; }
} }
out.declarative.push([ selectors, entry.declarations ]); out.declarative.push([ selectors, entry.declarations ]);
} }
return out; return out;
}, }
getFilteredElementCount: function() { getFilteredElementCount() {
let details = this.getAllSelectors_(true); const details = this.getAllSelectors_(true);
if ( Array.isArray(details.declarative) === false ) { return 0; } if ( Array.isArray(details.declarative) === false ) { return 0; }
let selectors = details.declarative.reduce(function(acc, entry) { const selectors = details.declarative.map(entry => entry[0]);
acc.push(entry[0]);
return acc;
}, []);
if ( selectors.length === 0 ) { return 0; } if ( selectors.length === 0 ) { return 0; }
return document.querySelectorAll(selectors.join(',\n')).length; return document.querySelectorAll(selectors.join(',\n')).length;
}, }
getAllSelectors: function() { getAllSelectors() {
return this.getAllSelectors_(false); return this.getAllSelectors_(false);
} }
}; };

View File

@ -265,11 +265,11 @@ body.colorBlind #netFilteringDialog > .panes > .details > div[data-status="2"] {
#vwRenderer .logEntry > div[data-tabid="-1"] { #vwRenderer .logEntry > div[data-tabid="-1"] {
text-shadow: 0 0.2em 0.4em #aaa; text-shadow: 0 0.2em 0.4em #aaa;
} }
#vwRenderer .logEntry > div.cosmetic, #vwRenderer .logEntry > div.cosmeticRealm,
#vwRenderer .logEntry > div.redirect { #vwRenderer .logEntry > div.redirect {
background-color: rgba(255, 255, 0, 0.1); background-color: rgba(255, 255, 0, 0.1);
} }
body.colorBlind #vwRenderer .logEntry > div.cosmetic, body.colorBlind #vwRenderer .logEntry > div.cosmeticRealm,
body.colorBlind #vwRenderer .logEntry > div.redirect { body.colorBlind #vwRenderer .logEntry > div.redirect {
background-color: rgba(0, 19, 110, 0.1); background-color: rgba(0, 19, 110, 0.1);
} }
@ -326,6 +326,9 @@ body[dir="rtl"] #vwRenderer .logEntry > div > span:first-child {
#vwRenderer .logEntry > div.messageRealm[data-type="tabLoad"] > span:nth-of-type(2) { #vwRenderer .logEntry > div.messageRealm[data-type="tabLoad"] > span:nth-of-type(2) {
text-align: center; text-align: center;
} }
#vwRenderer .logEntry > div.cosmeticRealm.isException > span:nth-of-type(2) {
text-decoration: line-through;
}
#vwRenderer .logEntry > div > span:nth-of-type(3) { #vwRenderer .logEntry > div > span:nth-of-type(3) {
font: 12px monospace; font: 12px monospace;
padding-left: 0.3em; padding-left: 0.3em;

View File

@ -825,66 +825,60 @@ vAPI.DOMFilterer = (function() {
} }
}; };
const DOMFiltererBase = vAPI.DOMFilterer; const DOMFilterer = class extends vAPI.DOMFilterer {
constructor() {
super();
this.exceptions = [];
this.proceduralFilterer = new DOMProceduralFilterer(this);
this.hideNodeAttr = undefined;
this.hideNodeStyleSheetInjected = false;
if ( vAPI.domWatcher instanceof Object ) {
vAPI.domWatcher.addListener(this);
}
}
const domFilterer = function() { commitNow() {
DOMFiltererBase.call(this); super.commitNow();
this.exceptions = []; this.proceduralFilterer.commitNow();
this.proceduralFilterer = new DOMProceduralFilterer(this); }
this.hideNodeAttr = undefined;
this.hideNodeStyleSheetInjected = false;
// May or may not exist: cache locally since this may be called often. addProceduralSelectors(aa) {
this.baseOnDOMChanged = DOMFiltererBase.prototype.onDOMChanged; this.proceduralFilterer.addProceduralSelectors(aa);
}
if ( vAPI.domWatcher instanceof Object ) { createProceduralFilter(o) {
vAPI.domWatcher.addListener(this); return this.proceduralFilterer.createProceduralFilter(o);
}
getAllSelectors() {
const out = super.getAllSelectors();
out.procedural = Array.from(this.proceduralFilterer.selectors.values());
return out;
}
getAllExceptionSelectors() {
return this.exceptions.join(',\n');
}
onDOMCreated() {
if ( super.onDOMCreated instanceof Function ) {
super.onDOMCreated();
}
this.proceduralFilterer.onDOMCreated();
}
onDOMChanged() {
if ( super.onDOMChanged instanceof Function ) {
super.onDOMChanged(arguments);
}
this.proceduralFilterer.onDOMChanged.apply(
this.proceduralFilterer,
arguments
);
} }
}; };
domFilterer.prototype = Object.create(DOMFiltererBase.prototype);
domFilterer.prototype.constructor = domFilterer;
domFilterer.prototype.commitNow = function() { return DOMFilterer;
DOMFiltererBase.prototype.commitNow.call(this);
this.proceduralFilterer.commitNow();
};
domFilterer.prototype.addProceduralSelectors = function(aa) {
this.proceduralFilterer.addProceduralSelectors(aa);
};
domFilterer.prototype.createProceduralFilter = function(o) {
return this.proceduralFilterer.createProceduralFilter(o);
};
domFilterer.prototype.getAllSelectors = function() {
const out = DOMFiltererBase.prototype.getAllSelectors.call(this);
out.procedural = Array.from(this.proceduralFilterer.selectors.values());
return out;
};
domFilterer.prototype.getAllExceptionSelectors = function() {
return this.exceptions.join(',\n');
};
domFilterer.prototype.onDOMCreated = function() {
if ( DOMFiltererBase.prototype.onDOMCreated !== undefined ) {
DOMFiltererBase.prototype.onDOMCreated.call(this);
}
this.proceduralFilterer.onDOMCreated();
};
domFilterer.prototype.onDOMChanged = function() {
if ( this.baseOnDOMChanged !== undefined ) {
this.baseOnDOMChanged.apply(this, arguments);
}
this.proceduralFilterer.onDOMChanged.apply(
this.proceduralFilterer,
arguments
);
};
return domFilterer;
})(); })();
vAPI.domFilterer = new vAPI.DOMFilterer(); vAPI.domFilterer = new vAPI.DOMFilterer();
@ -1340,6 +1334,10 @@ vAPI.domSurveyor = (function() {
); );
mustCommit = true; mustCommit = true;
} }
selectors = result.excepted;
if ( Array.isArray(selectors) && selectors.length !== 0 ) {
domFilterer.exceptCSSRules(selectors);
}
} }
if ( pendingNodes.stopped === false ) { if ( pendingNodes.stopped === false ) {
@ -1524,6 +1522,7 @@ vAPI.bootstrap = (function() {
{ injected: true } { injected: true }
); );
domFilterer.addProceduralSelectors(cfeDetails.proceduralFilters); domFilterer.addProceduralSelectors(cfeDetails.proceduralFilters);
domFilterer.exceptCSSRules(cfeDetails.exceptedFilters);
} }
if ( cfeDetails.networkFilters.length !== 0 ) { if ( cfeDetails.networkFilters.length !== 0 ) {

View File

@ -843,22 +843,22 @@ FilterContainer.prototype.retrieveGenericSelectors = function(request) {
//console.time('cosmeticFilteringEngine.retrieveGenericSelectors'); //console.time('cosmeticFilteringEngine.retrieveGenericSelectors');
let simpleSelectors = this.setRegister0, const simpleSelectors = this.setRegister0;
complexSelectors = this.setRegister1; const complexSelectors = this.setRegister1;
let cacheEntry = this.selectorCache.get(request.hostname), const cacheEntry = this.selectorCache.get(request.hostname);
previousHits = cacheEntry && cacheEntry.cosmetic || this.setRegister2; const previousHits = cacheEntry && cacheEntry.cosmetic || this.setRegister2;
for ( let type in this.lowlyGeneric ) { for ( const type in this.lowlyGeneric ) {
let entry = this.lowlyGeneric[type]; const entry = this.lowlyGeneric[type];
let selectors = request[entry.canonical]; const selectors = request[entry.canonical];
if ( Array.isArray(selectors) === false ) { continue; } if ( Array.isArray(selectors) === false ) { continue; }
for ( let selector of selectors ) { for ( let selector of selectors ) {
if ( entry.simple.has(selector) === false ) { continue; } if ( entry.simple.has(selector) === false ) { continue; }
let bucket = entry.complex.get(selector); const bucket = entry.complex.get(selector);
if ( bucket !== undefined ) { if ( bucket !== undefined ) {
if ( Array.isArray(bucket) ) { if ( Array.isArray(bucket) ) {
for ( selector of bucket ) { for ( const selector of bucket ) {
if ( previousHits.has(selector) === false ) { if ( previousHits.has(selector) === false ) {
complexSelectors.add(selector); complexSelectors.add(selector);
} }
@ -877,26 +877,39 @@ FilterContainer.prototype.retrieveGenericSelectors = function(request) {
// Apply exceptions: it is the responsibility of the caller to provide // Apply exceptions: it is the responsibility of the caller to provide
// the exceptions to be applied. // the exceptions to be applied.
const excepted = [];
if ( Array.isArray(request.exceptions) ) { if ( Array.isArray(request.exceptions) ) {
for ( let exception of request.exceptions ) { for ( const exception of request.exceptions ) {
simpleSelectors.delete(exception); if (
complexSelectors.delete(exception); simpleSelectors.delete(exception) ||
complexSelectors.delete(exception)
) {
excepted.push(exception);
}
} }
} }
if ( simpleSelectors.size === 0 && complexSelectors.size === 0 ) { if (
simpleSelectors.size === 0 &&
complexSelectors.size === 0 &&
excepted.length === 0
) {
return; return;
} }
let out = { const out = {
simple: Array.from(simpleSelectors), simple: Array.from(simpleSelectors),
complex: Array.from(complexSelectors), complex: Array.from(complexSelectors),
injected: '' injected: '',
excepted,
}; };
// Cache and inject (if user stylesheets supported) looked-up low generic // Cache and inject (if user stylesheets supported) looked-up low generic
// cosmetic filters. // cosmetic filters.
if ( typeof request.hostname === 'string' && request.hostname !== '' ) { if (
(typeof request.hostname === 'string' && request.hostname !== '') &&
(out.simple.length !== 0 || out.complex.length !== 0)
) {
this.addToSelectorCache({ this.addToSelectorCache({
cost: request.surveyCost || 0, cost: request.surveyCost || 0,
hostname: request.hostname, hostname: request.hostname,
@ -913,7 +926,7 @@ FilterContainer.prototype.retrieveGenericSelectors = function(request) {
request.tabId !== undefined && request.tabId !== undefined &&
request.frameId !== undefined request.frameId !== undefined
) { ) {
let injected = []; const injected = [];
if ( out.simple.length !== 0 ) { if ( out.simple.length !== 0 ) {
injected.push(out.simple.join(',\n')); injected.push(out.simple.join(',\n'));
out.simple = []; out.simple = [];
@ -964,6 +977,7 @@ FilterContainer.prototype.retrieveSpecificSelectors = function(
domain: request.domain, domain: request.domain,
declarativeFilters: [], declarativeFilters: [],
exceptionFilters: [], exceptionFilters: [],
exceptedFilters: [],
hideNodeAttr: this.randomAlphaToken(), hideNodeAttr: this.randomAlphaToken(),
hideNodeStyleSheetInjected: false, hideNodeStyleSheetInjected: false,
highGenericHideSimple: '', highGenericHideSimple: '',
@ -1008,8 +1022,13 @@ FilterContainer.prototype.retrieveSpecificSelectors = function(
if ( exceptionSet.size !== 0 ) { if ( exceptionSet.size !== 0 ) {
out.exceptionFilters = Array.from(exceptionSet); out.exceptionFilters = Array.from(exceptionSet);
for ( const exception of exceptionSet ) { for ( const exception of exceptionSet ) {
specificSet.delete(exception); if (
proceduralSet.delete(exception); specificSet.delete(exception) ||
proceduralSet.delete(exception)
) {
out.exceptedFilters.push(exception);
}
} }
} }
@ -1034,7 +1053,7 @@ FilterContainer.prototype.retrieveSpecificSelectors = function(
const entry = this.highlyGeneric[type]; const entry = this.highlyGeneric[type];
let str = entry.mru.lookup(exceptionHash); let str = entry.mru.lookup(exceptionHash);
if ( str === undefined ) { if ( str === undefined ) {
str = { s: entry.str }; str = { s: entry.str, excepted: [] };
let genericSet = entry.dict; let genericSet = entry.dict;
let hit = false; let hit = false;
for ( const exception of exceptionSet ) { for ( const exception of exceptionSet ) {
@ -1042,14 +1061,20 @@ FilterContainer.prototype.retrieveSpecificSelectors = function(
} }
if ( hit ) { if ( hit ) {
genericSet = new Set(entry.dict); genericSet = new Set(entry.dict);
for ( let exception of exceptionSet ) { for ( const exception of exceptionSet ) {
genericSet.delete(exception); if ( genericSet.delete(exception) ) {
str.excepted.push(exception);
}
} }
str.s = Array.from(genericSet).join(',\n'); str.s = Array.from(genericSet).join(',\n');
} }
entry.mru.add(exceptionHash, str); entry.mru.add(exceptionHash, str);
} }
out[entry.canonical] = str.s; out[entry.canonical] = str.s;
if ( str.excepted.length !== 0 ) {
out.exceptedFilters.push(...str.excepted);
}
} }
} }

View File

@ -622,13 +622,13 @@ const viewPort = (function() {
if ( filter !== undefined ) { if ( filter !== undefined ) {
if ( typeof filter.source === 'string' ) { if ( typeof filter.source === 'string' ) {
filteringType = filter.source; filteringType = filter.source;
divcl.add(filteringType);
} }
if ( filteringType === 'static' ) { if ( filteringType === 'static' ) {
divcl.add('canLookup'); divcl.add('canLookup');
div.setAttribute('data-filter', filter.compiled); div.setAttribute('data-filter', filter.compiled);
} else if ( filteringType === 'cosmetic' ) { } else if ( filteringType === 'cosmetic' ) {
divcl.add('canLookup'); divcl.add('canLookup');
divcl.toggle('isException', filter.raw.startsWith('#@#'));
} }
} }
span = div.children[1]; span = div.children[1];
@ -1503,7 +1503,7 @@ const reloadTab = function(ev) {
text = trch[1].textContent; text = trch[1].textContent;
if ( if (
(text !== '') && (text !== '') &&
(trcl.contains('cosmetic') || trcl.contains('static')) (trcl.contains('cosmeticRealm') || trcl.contains('networkRealm'))
) { ) {
rows[0].children[1].textContent = text; rows[0].children[1].textContent = text;
} else { } else {

View File

@ -1352,7 +1352,7 @@ const logCosmeticFilters = function(tabId, details) {
.setDocOriginFromURL(details.frameURL) .setDocOriginFromURL(details.frameURL)
.setFilter(filter); .setFilter(filter);
for ( const selector of details.matchedSelectors.sort() ) { for ( const selector of details.matchedSelectors.sort() ) {
filter.raw = '##' + selector; filter.raw = selector;
fctxt.toLogger(); fctxt.toLogger();
} }
}; };

View File

@ -186,6 +186,7 @@ const fromCosmeticFilter = function(details) {
// Lowly generic cosmetic filters // Lowly generic cosmetic filters
case 0: // simple id-based case 0: // simple id-based
if ( if (
exception === false &&
fargs[1] === selector.slice(1) && fargs[1] === selector.slice(1) &&
selector.charAt(0) === '#' selector.charAt(0) === '#'
) { ) {
@ -194,6 +195,7 @@ const fromCosmeticFilter = function(details) {
break; break;
case 2: // simple class-based case 2: // simple class-based
if ( if (
exception === false &&
fargs[1] === selector.slice(1) && fargs[1] === selector.slice(1) &&
selector.charAt(0) === '.' selector.charAt(0) === '.'
) { ) {
@ -202,7 +204,7 @@ const fromCosmeticFilter = function(details) {
break; break;
case 1: // complex id-based case 1: // complex id-based
case 3: // complex class-based case 3: // complex class-based
if ( fargs[2] === selector ) { if ( exception === false && fargs[2] === selector ) {
found = prefix + selector; found = prefix + selector;
} }
break; break;

View File

@ -35,30 +35,34 @@ if (
return; return;
} }
let reHasCSSCombinators = /[ >+~]/, const reHasCSSCombinators = /[ >+~]/;
reHasPseudoClass = /:+(?:after|before)$/, const reHasPseudoClass = /:+(?:after|before)$/;
sanitizedSelectors = new Map(), const sanitizedSelectors = new Map();
simpleDeclarativeSet = new Set(), const simpleDeclarativeSet = new Set();
simpleDeclarativeStr, let simpleDeclarativeStr;
complexDeclarativeSet = new Set(), const complexDeclarativeSet = new Set();
complexDeclarativeStr, let complexDeclarativeStr;
proceduralDict = new Map(), const proceduralDict = new Map();
nodesToProcess = new Set(), const exceptionSet = new Set();
shouldProcessDeclarativeComplex = false, let exceptionStr;
shouldProcessProcedural = false, const nodesToProcess = new Set();
loggedSelectors = new Set(); let shouldProcessDeclarativeComplex = false;
let shouldProcessProcedural = false;
let shouldProcessExceptions = false;
const loggedSelectors = new Set();
/******************************************************************************/ /******************************************************************************/
let shouldProcess = function() { const shouldProcess = function() {
return nodesToProcess.size !== 0 || return nodesToProcess.size !== 0 ||
shouldProcessDeclarativeComplex || shouldProcessDeclarativeComplex ||
shouldProcessProcedural; shouldProcessProcedural ||
shouldProcessExceptions;
}; };
/******************************************************************************/ /******************************************************************************/
let processDeclarativeSimple = function(node, out) { const processDeclarativeSimple = function(node, out) {
if ( simpleDeclarativeSet.size === 0 ) { return; } if ( simpleDeclarativeSet.size === 0 ) { return; }
if ( simpleDeclarativeStr === undefined ) { if ( simpleDeclarativeStr === undefined ) {
simpleDeclarativeStr = Array.from(simpleDeclarativeSet).join(',\n'); simpleDeclarativeStr = Array.from(simpleDeclarativeSet).join(',\n');
@ -69,14 +73,14 @@ let processDeclarativeSimple = function(node, out) {
) { ) {
return; return;
} }
for ( let selector of simpleDeclarativeSet ) { for ( const selector of simpleDeclarativeSet ) {
if ( if (
(node === document || node.matches(selector) === false) && (node === document || node.matches(selector) === false) &&
(node.querySelector(selector) === null) (node.querySelector(selector) === null)
) { ) {
continue; continue;
} }
out.push(sanitizedSelectors.get(selector) || selector); out.push(`##${sanitizedSelectors.get(selector) || selector}`);
simpleDeclarativeSet.delete(selector); simpleDeclarativeSet.delete(selector);
simpleDeclarativeStr = undefined; simpleDeclarativeStr = undefined;
loggedSelectors.add(selector); loggedSelectors.add(selector);
@ -86,15 +90,15 @@ let processDeclarativeSimple = function(node, out) {
/******************************************************************************/ /******************************************************************************/
let processDeclarativeComplex = function(out) { const processDeclarativeComplex = function(out) {
if ( complexDeclarativeSet.size === 0 ) { return; } if ( complexDeclarativeSet.size === 0 ) { return; }
if ( complexDeclarativeStr === undefined ) { if ( complexDeclarativeStr === undefined ) {
complexDeclarativeStr = Array.from(complexDeclarativeSet).join(',\n'); complexDeclarativeStr = Array.from(complexDeclarativeSet).join(',\n');
} }
if ( document.querySelector(complexDeclarativeStr) === null ) { return; } if ( document.querySelector(complexDeclarativeStr) === null ) { return; }
for ( let selector of complexDeclarativeSet ) { for ( const selector of complexDeclarativeSet ) {
if ( document.querySelector(selector) === null ) { continue; } if ( document.querySelector(selector) === null ) { continue; }
out.push(sanitizedSelectors.get(selector) || selector); out.push(`##${sanitizedSelectors.get(selector) || selector}`);
complexDeclarativeSet.delete(selector); complexDeclarativeSet.delete(selector);
complexDeclarativeStr = undefined; complexDeclarativeStr = undefined;
loggedSelectors.add(selector); loggedSelectors.add(selector);
@ -104,11 +108,11 @@ let processDeclarativeComplex = function(out) {
/******************************************************************************/ /******************************************************************************/
let processProcedural = function(out) { const processProcedural = function(out) {
if ( proceduralDict.size === 0 ) { return; } if ( proceduralDict.size === 0 ) { return; }
for ( let entry of proceduralDict ) { for ( const entry of proceduralDict ) {
if ( entry[1].test() === false ) { continue; } if ( entry[1].test() === false ) { continue; }
out.push(entry[1].raw); out.push(`##${entry[1].raw}`);
proceduralDict.delete(entry[0]); proceduralDict.delete(entry[0]);
if ( proceduralDict.size === 0 ) { break; } if ( proceduralDict.size === 0 ) { break; }
} }
@ -116,16 +120,33 @@ let processProcedural = function(out) {
/******************************************************************************/ /******************************************************************************/
let processTimer = new vAPI.SafeAnimationFrame(() => { const processExceptions = function(out) {
if ( exceptionSet.size === 0 ) { return; }
if ( exceptionStr === undefined ) {
exceptionStr = Array.from(exceptionSet).join(',\n');
}
if ( document.querySelector(exceptionStr) === null ) { return; }
for ( const selector of exceptionSet ) {
if ( document.querySelector(selector) === null ) { continue; }
out.push(`#@#${selector}`);
exceptionSet.delete(selector);
exceptionStr = undefined;
loggedSelectors.add(selector);
}
};
/******************************************************************************/
const processTimer = new vAPI.SafeAnimationFrame(() => {
//console.time('dom logger/scanning for matches'); //console.time('dom logger/scanning for matches');
processTimer.clear(); processTimer.clear();
let toLog = []; const toLog = [];
if ( nodesToProcess.size !== 0 && simpleDeclarativeSet.size !== 0 ) { if ( nodesToProcess.size !== 0 && simpleDeclarativeSet.size !== 0 ) {
if ( nodesToProcess.size !== 1 && nodesToProcess.has(document) ) { if ( nodesToProcess.size !== 1 && nodesToProcess.has(document) ) {
nodesToProcess.clear(); nodesToProcess.clear();
nodesToProcess.add(document); nodesToProcess.add(document);
} }
for ( let node of nodesToProcess ) { for ( const node of nodesToProcess ) {
processDeclarativeSimple(node, toLog); processDeclarativeSimple(node, toLog);
} }
nodesToProcess.clear(); nodesToProcess.clear();
@ -138,6 +159,10 @@ let processTimer = new vAPI.SafeAnimationFrame(() => {
processProcedural(toLog); processProcedural(toLog);
shouldProcessProcedural = false; shouldProcessProcedural = false;
} }
if ( shouldProcessExceptions ) {
processExceptions(toLog);
shouldProcessExceptions = false;
}
if ( toLog.length === 0 ) { return; } if ( toLog.length === 0 ) { return; }
vAPI.messaging.send( vAPI.messaging.send(
'scriptlets', 'scriptlets',
@ -145,7 +170,7 @@ let processTimer = new vAPI.SafeAnimationFrame(() => {
what: 'logCosmeticFilteringData', what: 'logCosmeticFilteringData',
frameURL: window.location.href, frameURL: window.location.href,
frameHostname: window.location.hostname, frameHostname: window.location.hostname,
matchedSelectors: toLog matchedSelectors: toLog,
} }
); );
//console.timeEnd('dom logger/scanning for matches'); //console.timeEnd('dom logger/scanning for matches');
@ -153,10 +178,10 @@ let processTimer = new vAPI.SafeAnimationFrame(() => {
/******************************************************************************/ /******************************************************************************/
let attributeObserver = new MutationObserver(mutations => { const attributeObserver = new MutationObserver(mutations => {
if ( simpleDeclarativeSet.size !== 0 ) { if ( simpleDeclarativeSet.size !== 0 ) {
for ( let mutation of mutations ) { for ( const mutation of mutations ) {
let node = mutation.target; const node = mutation.target;
if ( node.nodeType !== 1 ) { continue; } if ( node.nodeType !== 1 ) { continue; }
nodesToProcess.add(node); nodesToProcess.add(node);
} }
@ -174,20 +199,20 @@ let attributeObserver = new MutationObserver(mutations => {
/******************************************************************************/ /******************************************************************************/
let handlers = { const handlers = {
onFiltersetChanged: function(changes) { onFiltersetChanged: function(changes) {
//console.time('dom logger/filterset changed'); //console.time('dom logger/filterset changed');
let simpleSizeBefore = simpleDeclarativeSet.size, const simpleSizeBefore = simpleDeclarativeSet.size,
complexSizeBefore = complexDeclarativeSet.size, complexSizeBefore = complexDeclarativeSet.size,
logNow = []; logNow = [];
for ( let entry of (changes.declarative || []) ) { for ( const entry of (changes.declarative || []) ) {
for ( let selector of entry[0].split(',\n') ) { for ( let selector of entry[0].split(',\n') ) {
if ( entry[1] !== 'display:none!important;' ) { if ( entry[1] !== 'display:none!important;' ) {
logNow.push(selector + ':style(' + entry[1] + ')'); logNow.push(`##${selector}:style(${entry[1]})`);
continue; continue;
} }
if ( reHasPseudoClass.test(selector) ) { if ( reHasPseudoClass.test(selector) ) {
let sanitized = selector.replace(reHasPseudoClass, ''); const sanitized = selector.replace(reHasPseudoClass, '');
sanitizedSelectors.set(sanitized, selector); sanitizedSelectors.set(sanitized, selector);
selector = sanitized; selector = sanitized;
} }
@ -222,11 +247,19 @@ let handlers = {
Array.isArray(changes.procedural) && Array.isArray(changes.procedural) &&
changes.procedural.length !== 0 changes.procedural.length !== 0
) { ) {
for ( let selector of changes.procedural ) { for ( const selector of changes.procedural ) {
proceduralDict.set(selector.raw, selector); proceduralDict.set(selector.raw, selector);
} }
shouldProcessProcedural = true; shouldProcessProcedural = true;
} }
if ( Array.isArray(changes.exceptions) ) {
for ( const selector of changes.exceptions ) {
if ( loggedSelectors.has(selector) ) { continue; }
exceptionSet.add(selector);
}
exceptionStr = undefined;
shouldProcessExceptions = true;
}
if ( shouldProcess() ) { if ( shouldProcess() ) {
processTimer.start(1); processTimer.start(1);
} }
@ -246,7 +279,7 @@ let handlers = {
// This is to guard against runaway job queue. I suspect this could // This is to guard against runaway job queue. I suspect this could
// occur on slower devices. // occur on slower devices.
if ( simpleDeclarativeSet.size !== 0 ) { if ( simpleDeclarativeSet.size !== 0 ) {
for ( let node of addedNodes ) { for ( const node of addedNodes ) {
if ( node.parentNode === null ) { continue; } if ( node.parentNode === null ) { continue; }
nodesToProcess.add(node); nodesToProcess.add(node);
} }
@ -257,6 +290,9 @@ let handlers = {
if ( proceduralDict.size !== 0 ) { if ( proceduralDict.size !== 0 ) {
shouldProcessProcedural = true; shouldProcessProcedural = true;
} }
if ( exceptionSet.size !== 0 ) {
shouldProcessExceptions = true;
}
if ( shouldProcess() ) { if ( shouldProcess() ) {
processTimer.start(100); processTimer.start(100);
} }
@ -265,7 +301,7 @@ let handlers = {
/******************************************************************************/ /******************************************************************************/
let onMessage = function(msg) { const onMessage = function(msg) {
if ( msg.what === 'loggerDisabled' ) { if ( msg.what === 'loggerDisabled' ) {
processTimer.clear(); processTimer.clear();
attributeObserver.disconnect(); attributeObserver.disconnect();