diff options
Diffstat (limited to 'data/extensions/uBlock0@raymondhill.net/js/scriptlets/cosmetic-logger.js')
-rw-r--r-- | data/extensions/uBlock0@raymondhill.net/js/scriptlets/cosmetic-logger.js | 361 |
1 files changed, 361 insertions, 0 deletions
diff --git a/data/extensions/uBlock0@raymondhill.net/js/scriptlets/cosmetic-logger.js b/data/extensions/uBlock0@raymondhill.net/js/scriptlets/cosmetic-logger.js new file mode 100644 index 0000000..912c18e --- /dev/null +++ b/data/extensions/uBlock0@raymondhill.net/js/scriptlets/cosmetic-logger.js @@ -0,0 +1,361 @@ +/******************************************************************************* + + uBlock Origin - a comprehensive, efficient content blocker + 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 +*/ + +/******************************************************************************/ + +(( ) => { +// >>>>>>>> start of private namespace + +/******************************************************************************/ + +if ( typeof vAPI !== 'object' ) { return; } +if ( vAPI.domWatcher instanceof Object === false ) { return; } + +const reHasCSSCombinators = /[ >+~]/; +const simpleDeclarativeSet = new Set(); +let simpleDeclarativeStr; +const complexDeclarativeSet = new Set(); +let complexDeclarativeStr; +const proceduralDict = new Map(); +const exceptionDict = new Map(); +let exceptionStr; +const proceduralExceptionDict = new Map(); +const nodesToProcess = new Set(); +const loggedSelectors = new Set(); + +/******************************************************************************/ + +const rePseudoElements = /:(?::?after|:?before|:[a-z-]+)$/; + +function hasSelector(selector, context = document) { + try { + return context.querySelector(selector) !== null; + } + catch { + } + return false; +} + +function safeMatchSelector(selector, context) { + const safeSelector = rePseudoElements.test(selector) + ? selector.replace(rePseudoElements, '') + : selector; + try { + return context.matches(safeSelector); + } + catch { + } + return false; +} + +function safeQuerySelector(selector, context = document) { + const safeSelector = rePseudoElements.test(selector) + ? selector.replace(rePseudoElements, '') + : selector; + try { + return context.querySelector(safeSelector); + } + catch { + } + return null; +} + +function safeGroupSelectors(selectors) { + const arr = Array.isArray(selectors) + ? selectors + : Array.from(selectors); + return arr.map(s => { + return rePseudoElements.test(s) + ? s.replace(rePseudoElements, '') + : s; + }).join(',\n'); +} + +/******************************************************************************/ + +function processDeclarativeSimple(node, out) { + if ( simpleDeclarativeSet.size === 0 ) { return; } + if ( simpleDeclarativeStr === undefined ) { + simpleDeclarativeStr = safeGroupSelectors(simpleDeclarativeSet); + } + if ( + (node === document || node.matches(simpleDeclarativeStr) === false) && + (hasSelector(simpleDeclarativeStr, node) === false) + ) { + return; + } + for ( const selector of simpleDeclarativeSet ) { + if ( + (node === document || safeMatchSelector(selector, node) === false) && + (safeQuerySelector(selector, node) === null) + ) { + continue; + } + out.push(`##${selector}`); + simpleDeclarativeSet.delete(selector); + simpleDeclarativeStr = undefined; + loggedSelectors.add(selector); + } +} + +/******************************************************************************/ + +function processDeclarativeComplex(out) { + if ( complexDeclarativeSet.size === 0 ) { return; } + if ( complexDeclarativeStr === undefined ) { + complexDeclarativeStr = safeGroupSelectors(complexDeclarativeSet); + } + if ( hasSelector(complexDeclarativeStr) === false ) { return; } + for ( const selector of complexDeclarativeSet ) { + if ( safeQuerySelector(selector) === null ) { continue; } + out.push(`##${selector}`); + complexDeclarativeSet.delete(selector); + complexDeclarativeStr = undefined; + loggedSelectors.add(selector); + } +} + +/******************************************************************************/ + +function processProcedural(out) { + if ( proceduralDict.size === 0 ) { return; } + for ( const [ raw, pselector ] of proceduralDict ) { + if ( pselector.converted ) { + if ( safeQuerySelector(pselector.selector) === null ) { continue; } + } else if ( pselector.hit === false && pselector.exec().length === 0 ) { + continue; + } + out.push(`##${raw}`); + proceduralDict.delete(raw); + } +} + +/******************************************************************************/ + +function processExceptions(out) { + if ( exceptionDict.size === 0 ) { return; } + if ( exceptionStr === undefined ) { + exceptionStr = safeGroupSelectors(exceptionDict.keys()); + } + if ( hasSelector(exceptionStr) === false ) { return; } + for ( const [ selector, raw ] of exceptionDict ) { + if ( safeQuerySelector(selector) === null ) { continue; } + out.push(`#@#${raw}`); + exceptionDict.delete(selector); + exceptionStr = undefined; + loggedSelectors.add(raw); + } +} + +/******************************************************************************/ + +function processProceduralExceptions(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(( ) => { + //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 ) { + processDeclarativeSimple(node, toLog); + } + } + + processDeclarativeComplex(toLog); + processProcedural(toLog); + processExceptions(toLog); + processProceduralExceptions(toLog); + + nodesToProcess.clear(); + + if ( toLog.length === 0 ) { return; } + + const location = vAPI.effectiveSelf.location; + + vAPI.messaging.send('scriptlets', { + what: 'logCosmeticFilteringData', + frameURL: location.href, + frameHostname: location.hostname, + matchedSelectors: toLog, + }); + //console.timeEnd('dom logger/scanning for matches'); +}); + +/******************************************************************************/ + +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); + } + if ( nodesToProcess.size !== 0 ) { + processTimer.start(100); + } +}); + +/******************************************************************************/ + +const handlers = { + onFiltersetChanged: function(changes) { + //console.time('dom logger/filterset changed'); + for ( const block of (changes.declarative || []) ) { + for ( const selector of block.split(',\n') ) { + if ( loggedSelectors.has(selector) ) { continue; } + if ( reHasCSSCombinators.test(selector) ) { + complexDeclarativeSet.add(selector); + complexDeclarativeStr = undefined; + } else { + simpleDeclarativeSet.add(selector); + simpleDeclarativeStr = undefined; + } + } + } + if ( + Array.isArray(changes.procedural) && + changes.procedural.length !== 0 + ) { + for ( const selector of changes.procedural ) { + proceduralDict.set(selector.raw, selector); + } + } + 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; + } + nodesToProcess.clear(); + nodesToProcess.add(document); + processTimer.start(1); + //console.timeEnd('dom logger/filterset changed'); + }, + + onDOMCreated: function() { + if ( vAPI.domFilterer instanceof Object === false ) { + return shutdown(); + } + handlers.onFiltersetChanged(vAPI.domFilterer.getAllSelectors()); + vAPI.domFilterer.addListener(handlers); + attributeObserver.observe(document.body, { + attributes: true, + subtree: true + }); + }, + + 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 ) { + processTimer.start(100); + } + } +}; + +vAPI.domWatcher.addListener(handlers); + +/******************************************************************************/ + +const broadcastHandler = msg => { + if ( msg.what === 'loggerDisabled' ) { + shutdown(); + } +}; + +browser.runtime.onMessage.addListener(broadcastHandler); + +/******************************************************************************/ + +function shutdown() { + browser.runtime.onMessage.removeListener(broadcastHandler); + 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); + } +} + +/******************************************************************************/ + +// <<<<<<<< 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; + |