1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-11-17 16:02:33 +01:00
uBlock/src/js/scriptlets/cosmetic-logger.js

387 lines
12 KiB
JavaScript
Raw Normal View History

/*******************************************************************************
2016-03-06 16:51:06 +01:00
uBlock Origin - a browser extension to block requests.
2018-07-22 21:33:35 +02:00
Copyright (C) 2015-present Raymond Hill
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
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uBlock
*/
'use strict';
/******************************************************************************/
(( ) => {
// >>>>>>>> start of private namespace
/******************************************************************************/
2017-10-21 19:43:46 +02:00
if (
typeof vAPI !== 'object' ||
vAPI.domWatcher instanceof Object === false
) {
return;
}
const reHasCSSCombinators = /[ >+~]/;
const simpleDeclarativeSet = new Set();
let simpleDeclarativeStr;
const complexDeclarativeSet = new Set();
let complexDeclarativeStr;
const declarativeStyleDict = new Map();
let declarativeStyleStr;
const proceduralDict = new Map();
const exceptionDict = new Map();
let exceptionStr;
const proceduralExceptionDict = new Map();
const nodesToProcess = new Set();
const loggedSelectors = new Set();
2018-07-22 21:33:35 +02:00
/******************************************************************************/
const rePseudoElements = /:(?::?after|:?before|:[a-z-]+)$/;
const safeMatchSelector = function(selector, context) {
const safeSelector = rePseudoElements.test(selector)
? selector.replace(rePseudoElements, '')
: selector;
return context.matches(safeSelector);
};
const safeQuerySelector = function(selector, context = document) {
const safeSelector = rePseudoElements.test(selector)
? selector.replace(rePseudoElements, '')
: selector;
return context.querySelector(safeSelector);
};
const safeGroupSelectors = function(selectors) {
const arr = Array.isArray(selectors)
? selectors
: Array.from(selectors);
return arr.map(s => {
return rePseudoElements.test(s)
? s.replace(rePseudoElements, '')
: s;
}).join(',\n');
};
/******************************************************************************/
const processDeclarativeSimple = function(node, out) {
2018-07-22 21:33:35 +02:00
if ( simpleDeclarativeSet.size === 0 ) { return; }
if ( simpleDeclarativeStr === undefined ) {
simpleDeclarativeStr = safeGroupSelectors(simpleDeclarativeSet);
2017-10-21 19:43:46 +02:00
}
if (
2018-07-22 21:33:35 +02:00
(node === document || node.matches(simpleDeclarativeStr) === false) &&
(node.querySelector(simpleDeclarativeStr) === null)
) {
2017-10-21 19:43:46 +02:00
return;
}
for ( const selector of simpleDeclarativeSet ) {
2017-10-21 19:43:46 +02:00
if (
(node === document || safeMatchSelector(selector, node) === false) &&
(safeQuerySelector(selector, node) === null)
2017-10-21 19:43:46 +02:00
) {
continue;
2017-10-21 19:43:46 +02:00
}
out.push(`##${selector}`);
simpleDeclarativeSet.delete(selector);
simpleDeclarativeStr = undefined;
loggedSelectors.add(selector);
}
};
2018-07-22 21:33:35 +02:00
/******************************************************************************/
const processDeclarativeComplex = function(out) {
2018-07-22 21:33:35 +02:00
if ( complexDeclarativeSet.size === 0 ) { return; }
if ( complexDeclarativeStr === undefined ) {
complexDeclarativeStr = safeGroupSelectors(complexDeclarativeSet);
2017-10-21 19:43:46 +02:00
}
2018-07-22 21:33:35 +02:00
if ( document.querySelector(complexDeclarativeStr) === null ) { return; }
for ( const selector of complexDeclarativeSet ) {
if ( safeQuerySelector(selector) === null ) { continue; }
out.push(`##${selector}`);
2018-07-22 21:33:35 +02:00
complexDeclarativeSet.delete(selector);
complexDeclarativeStr = undefined;
loggedSelectors.add(selector);
2017-10-21 19:43:46 +02:00
}
};
2018-07-22 21:33:35 +02:00
/******************************************************************************/
const processDeclarativeStyle = function(out) {
if ( declarativeStyleDict.size === 0 ) { return; }
if ( declarativeStyleStr === undefined ) {
declarativeStyleStr = safeGroupSelectors(declarativeStyleDict.keys());
}
if ( document.querySelector(declarativeStyleStr) === null ) { return; }
for ( const selector of declarativeStyleDict.keys() ) {
if ( safeQuerySelector(selector) === null ) { continue; }
for ( const style of declarativeStyleDict.get(selector) ) {
const raw = `##${selector}:style(${style})`;
out.push(raw);
loggedSelectors.add(raw);
}
declarativeStyleDict.delete(selector);
declarativeStyleStr = undefined;
}
};
/******************************************************************************/
const processProcedural = function(out) {
2018-07-22 21:33:35 +02:00
if ( proceduralDict.size === 0 ) { return; }
for ( const [ raw, pselector ] of proceduralDict ) {
if ( pselector.hit === false ) { continue; }
out.push(`##${raw}`);
proceduralDict.delete(raw);
2017-10-21 19:43:46 +02:00
}
};
2018-07-22 21:33:35 +02:00
/******************************************************************************/
const processExceptions = function(out) {
if ( exceptionDict.size === 0 ) { return; }
if ( exceptionStr === undefined ) {
exceptionStr = safeGroupSelectors(exceptionDict.keys());
}
if ( document.querySelector(exceptionStr) === null ) { return; }
for ( const [ selector, raw ] of exceptionDict ) {
if ( safeQuerySelector(selector) === null ) { continue; }
out.push(`#@#${raw}`);
exceptionDict.delete(selector);
exceptionStr = undefined;
loggedSelectors.add(raw);
}
};
/******************************************************************************/
const processProceduralExceptions = function(out) {
if ( proceduralExceptionDict.size === 0 ) { return; }
for ( const exception of proceduralExceptionDict.values() ) {
if ( exception.test() === false ) { continue; }
out.push(`#@#${exception.raw}`);
proceduralExceptionDict.delete(exception.raw);
}
};
/******************************************************************************/
const processTimer = new vAPI.SafeAnimationFrame(( ) => {
2018-07-22 21:33:35 +02:00
//console.time('dom logger/scanning for matches');
processTimer.clear();
if ( nodesToProcess.size === 0 ) { return; }
if ( nodesToProcess.size !== 1 && nodesToProcess.has(document) ) {
nodesToProcess.clear();
nodesToProcess.add(document);
}
const toLog = [];
if ( simpleDeclarativeSet.size !== 0 ) {
for ( const node of nodesToProcess ) {
2018-07-22 21:33:35 +02:00
processDeclarativeSimple(node, toLog);
}
}
processDeclarativeComplex(toLog);
processDeclarativeStyle(toLog);
processProcedural(toLog);
processExceptions(toLog);
processProceduralExceptions(toLog);
nodesToProcess.clear();
2018-07-22 21:33:35 +02:00
if ( toLog.length === 0 ) { return; }
const location = vAPI.effectiveSelf.location;
vAPI.messaging.send('scriptlets', {
what: 'logCosmeticFilteringData',
frameURL: location.href,
frameHostname: location.hostname,
matchedSelectors: toLog,
});
2018-07-22 21:33:35 +02:00
//console.timeEnd('dom logger/scanning for matches');
});
2018-07-22 21:33:35 +02:00
/******************************************************************************/
const attributeObserver = new MutationObserver(mutations => {
if ( nodesToProcess.has(document) ) { return; }
for ( const mutation of mutations ) {
const node = mutation.target;
if ( node.nodeType !== 1 ) { continue; }
nodesToProcess.add(node);
2017-10-21 19:43:46 +02:00
}
if ( nodesToProcess.size !== 0 ) {
2018-07-22 21:33:35 +02:00
processTimer.start(100);
2017-10-21 19:43:46 +02:00
}
});
2018-07-22 21:33:35 +02:00
/******************************************************************************/
const handlers = {
2017-11-14 21:03:20 +01:00
onFiltersetChanged: function(changes) {
//console.time('dom logger/filterset changed');
for ( const entry of (changes.declarative || []) ) {
2018-07-22 21:33:35 +02:00
for ( let selector of entry[0].split(',\n') ) {
if ( entry[1] !== 'display:none!important;' ) {
declarativeStyleStr = undefined;
const styles = declarativeStyleDict.get(selector);
if ( styles === undefined ) {
declarativeStyleDict.set(selector, [ entry[1] ]);
continue;
}
styles.push(entry[1]);
continue;
}
if ( loggedSelectors.has(selector) ) { continue; }
if ( reHasCSSCombinators.test(selector) ) {
complexDeclarativeSet.add(selector);
complexDeclarativeStr = undefined;
} else {
simpleDeclarativeSet.add(selector);
simpleDeclarativeStr = undefined;
2017-10-21 19:43:46 +02:00
}
}
2017-11-14 21:03:20 +01:00
}
2018-07-22 21:33:35 +02:00
if (
Array.isArray(changes.procedural) &&
changes.procedural.length !== 0
) {
for ( const selector of changes.procedural ) {
2018-07-22 21:33:35 +02:00
proceduralDict.set(selector.raw, selector);
2017-10-21 19:43:46 +02:00
}
}
if ( Array.isArray(changes.exceptions) ) {
for ( const selector of changes.exceptions ) {
if ( loggedSelectors.has(selector) ) { continue; }
if ( selector.charCodeAt(0) !== 0x7B /* '{' */ ) {
exceptionDict.set(selector, selector);
continue;
}
const details = JSON.parse(selector);
if (
details.action !== undefined &&
details.tasks === undefined &&
details.action[0] === ':style'
) {
exceptionDict.set(details.selector, details.raw);
continue;
}
proceduralExceptionDict.set(
details.raw,
vAPI.domFilterer.createProceduralFilter(details)
);
}
exceptionStr = undefined;
2017-10-21 19:43:46 +02:00
}
nodesToProcess.clear();
nodesToProcess.add(document);
processTimer.start(1);
//console.timeEnd('dom logger/filterset changed');
2017-10-21 19:43:46 +02:00
},
onDOMCreated: function() {
if ( vAPI.domFilterer instanceof Object === false ) {
return shutdown();
}
2017-11-14 21:03:20 +01:00
handlers.onFiltersetChanged(vAPI.domFilterer.getAllSelectors());
2017-10-21 19:43:46 +02:00
vAPI.domFilterer.addListener(handlers);
2018-07-22 21:33:35 +02:00
attributeObserver.observe(document.body, {
attributes: true,
subtree: true
});
2017-10-21 19:43:46 +02:00
},
onDOMChanged: function(addedNodes) {
if ( nodesToProcess.has(document) ) { return; }
for ( const node of addedNodes ) {
if ( node.parentNode === null ) { continue; }
nodesToProcess.add(node);
}
if ( nodesToProcess.size !== 0 ) {
2018-07-22 21:33:35 +02:00
processTimer.start(100);
2017-10-21 19:43:46 +02:00
}
}
2017-10-21 19:43:46 +02:00
};
2017-10-21 19:43:46 +02:00
/******************************************************************************/
const shutdown = function() {
processTimer.clear();
attributeObserver.disconnect();
if ( typeof vAPI !== 'object' ) { return; }
if ( vAPI.domFilterer instanceof Object ) {
vAPI.domFilterer.removeListener(handlers);
}
if ( vAPI.domWatcher instanceof Object ) {
vAPI.domWatcher.removeListener(handlers);
}
if ( vAPI.broadcastListener instanceof Object ) {
vAPI.broadcastListener.remove(broadcastListener);
}
};
/******************************************************************************/
const broadcastListener = msg => {
if ( msg.what === 'loggerDisabled' ) {
shutdown();
}
};
/******************************************************************************/
vAPI.messaging.extend().then(extended => {
if ( extended !== true ) {
return shutdown();
}
vAPI.broadcastListener.add(broadcastListener);
});
2017-10-21 19:43:46 +02:00
vAPI.domWatcher.addListener(handlers);
/******************************************************************************/
// <<<<<<<< end of private namespace
})();
/*******************************************************************************
DO NOT:
- Remove the following code
- Add code beyond the following code
Reason:
- https://github.com/gorhill/uBlock/pull/3721
- uBO never uses the return value from injected content scripts
**/
void 0;