diff --git a/assets/resources/scriptlets.js b/assets/resources/scriptlets.js index c87e5d945b625..8cd1aa2e9ad8e 100644 --- a/assets/resources/scriptlets.js +++ b/assets/resources/scriptlets.js @@ -42,8 +42,8 @@ builtinScriptlets.push({ fn: safeSelf, }); function safeSelf() { - if ( scriptletGlobals.has('safeSelf') ) { - return scriptletGlobals.get('safeSelf'); + if ( scriptletGlobals.safeSelf ) { + return scriptletGlobals.safeSelf; } const self = globalThis; const safe = { @@ -73,11 +73,22 @@ function safeSelf() { 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), 'log': console.log.bind(console), + // Properties + logLevel: 0, + // Methods + makeLogPrefix(...args) { + return this.sendToLogger && `[${args.join(' \u205D ')}]` || ''; + }, uboLog(...args) { - if ( scriptletGlobals.has('canDebug') === false ) { return; } - if ( args.length === 0 ) { return; } - if ( `${args[0]}` === '' ) { return; } - this.log('[uBO]', ...args); + if ( this.sendToLogger === undefined ) { return; } + if ( args === undefined || args[0] === '' ) { return; } + return this.sendToLogger('info', ...args); + + }, + uboErr(...args) { + if ( this.sendToLogger === undefined ) { return; } + if ( args === undefined || args[0] === '' ) { return; } + return this.sendToLogger('error', ...args); }, escapeRegexChars(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); @@ -145,7 +156,25 @@ function safeSelf() { return this.Object_fromEntries(entries); }, }; - scriptletGlobals.set('safeSelf', safe); + if ( scriptletGlobals.bcSecret !== undefined ) { + const bc = new self.BroadcastChannel(scriptletGlobals.bcSecret); + safe.logLevel = 1; + safe.sendToLogger = (type, ...args) => { + if ( args.length === 0 ) { return; } + const text = `[${document.location.hostname || document.location.href}]${args.join(' ')}`; + bc.postMessage({ what: 'messageToLogger', type, text }); + }; + bc.onmessage = ev => { + const msg = ev.data; + if ( msg instanceof Object === false ) { return; } + switch ( msg.what ) { + case 'setScriptletLogLevel': + safe.logLevel = msg.level; + break; + } + }; + } + scriptletGlobals.safeSelf = safe; return safe; } @@ -181,18 +210,7 @@ builtinScriptlets.push({ }); function shouldDebug(details) { if ( details instanceof Object === false ) { return false; } - return scriptletGlobals.has('canDebug') && details.debug; -} - -/******************************************************************************/ - -builtinScriptlets.push({ - name: 'should-log.fn', - fn: shouldLog, -}); -function shouldLog(details) { - if ( details instanceof Object === false ) { return false; } - return scriptletGlobals.has('canDebug') && details.log; + return scriptletGlobals.canDebug && details.debug; } /******************************************************************************/ @@ -297,12 +315,12 @@ function generateContentFn(directive) { return Promise.resolve(randomize(len | 0)); } } - if ( directive.startsWith('war:') && scriptletGlobals.has('warOrigin') ) { + if ( directive.startsWith('war:') && scriptletGlobals.warOrigin ) { return new Promise(resolve => { - const warOrigin = scriptletGlobals.get('warOrigin'); + const warOrigin = scriptletGlobals.warOrigin; const warName = directive.slice(4); const fullpath = [ warOrigin, '/', warName ]; - const warSecret = scriptletGlobals.get('warSecret'); + const warSecret = scriptletGlobals.warSecret; if ( warSecret !== undefined ) { fullpath.push('?secret=', warSecret); } @@ -327,7 +345,6 @@ builtinScriptlets.push({ 'get-exception-token.fn', 'safe-self.fn', 'should-debug.fn', - 'should-log.fn', ], }); // Issues to mind before changing anything: @@ -340,6 +357,7 @@ function abortCurrentScriptCore( if ( typeof target !== 'string' ) { return; } if ( target === '' ) { return; } const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix('abort-current-script', target, needle, context); const reNeedle = safe.patternToRegex(needle); const reContext = safe.patternToRegex(context); const extraArgs = safe.getExtraArgs(Array.from(arguments), 3); @@ -363,7 +381,6 @@ function abortCurrentScriptCore( value = owner[prop]; desc = undefined; } - const log = shouldLog(extraArgs); const debug = shouldDebug(extraArgs); const exceptionToken = getExceptionToken(); const scriptTexts = new WeakMap(); @@ -396,14 +413,19 @@ function abortCurrentScriptCore( if ( debug === 'nomatch' || debug === 'all' ) { debugger; } // jshint ignore: line return; } - if ( log && e.src !== '' ) { safe.uboLog(`matched src: ${e.src}`); } + if ( safe.logLevel > 1 && e.src !== '' ) { + safe.uboLevel(logPrefix, `Matched src\n${e.src}`); + } const scriptText = getScriptText(e); if ( reNeedle.test(scriptText) === false ) { if ( debug === 'nomatch' || debug === 'all' ) { debugger; } // jshint ignore: line return; } - if ( log ) { safe.uboLog(`matched script text: ${scriptText}`); } + if ( safe.logLevel > 1 ) { + safe.uboLevel(logPrefix, `Matched text\n${scriptText}`); + } if ( debug === 'match' || debug === 'all' ) { debugger; } // jshint ignore: line + safe.uboLog(logPrefix, 'Aborted'); throw new ReferenceError(exceptionToken); }; if ( debug === 'install' ) { debugger; } // jshint ignore: line @@ -425,7 +447,7 @@ function abortCurrentScriptCore( } }); } catch(ex) { - if ( log ) { safe.uboLog(ex); } + safe.uboErr(logPrefix, `Error: ${ex}`); } } @@ -447,6 +469,7 @@ function setConstantCore( ) { if ( chain === '' ) { return; } const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix('set-constant', chain, cValue); const extraArgs = safe.getExtraArgs(Array.from(arguments), 3); function setConstant(chain, cValue) { const trappedProp = (( ) => { @@ -533,6 +556,9 @@ function setConstantCore( (v !== undefined && v !== null) && (cValue !== undefined && cValue !== null) && (typeof v !== typeof cValue); + if ( aborted ) { + safe.uboLog(logPrefix, `Aborted because value set to ${v}`); + } return aborted; }; // https://github.com/uBlockOrigin/uBlock-issues/issues/156 @@ -580,9 +606,11 @@ function setConstantCore( return true; }, getter: function() { - return document.currentScript === thisScript - ? this.v - : cValue; + if ( document.currentScript === thisScript ) { + return this.v; + } + safe.uboLog(logPrefix, 'Property read'); + return cValue; }, setter: function(a) { if ( mustAbort(a) === false ) { return; } @@ -638,18 +666,18 @@ function replaceNodeTextFn( replacement = '' ) { const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix('replace-node-text.fn', ...Array.from(arguments)); const reNodeName = safe.patternToRegex(nodeName, 'i', true); const rePattern = safe.patternToRegex(pattern, 'gms'); const extraArgs = safe.getExtraArgs(Array.from(arguments), 3); - const shouldLog = scriptletGlobals.has('canDebug') && extraArgs.log || 0; const reCondition = safe.patternToRegex(extraArgs.condition || '', 'ms'); const stop = (takeRecord = true) => { if ( takeRecord ) { handleMutations(observer.takeRecords()); } observer.disconnect(); - if ( shouldLog !== 0 ) { - safe.uboLog(`replace-node-text-core.fn: quitting "${pattern}" => "${replacement}"`); + if ( safe.logLevel > 1 ) { + safe.uboLog(logPrefix, 'Quitting'); } }; let sedCount = extraArgs.sedCount || 0; @@ -664,10 +692,7 @@ function replaceNodeTextFn( ? before.replace(rePattern, replacement) : replacement; node.textContent = after; - if ( shouldLog !== 0 ) { - safe.uboLog('replace-node-text.fn before:\n', before); - safe.uboLog('replace-node-text.fn after:\n', after); - } + safe.uboLog(logPrefix, `Replace result:\n${after.trim()}`); return sedCount === 0 || (sedCount -= 1) !== 0; }; const handleMutations = mutations => { @@ -695,9 +720,7 @@ function replaceNodeTextFn( if ( handleNode(node) ) { continue; } stop(); break; } - if ( shouldLog !== 0 ) { - safe.uboLog(`replace-node-text-core.fn ${count} nodes present before installing mutation observer`); - } + safe.uboLog(logPrefix, `${count} nodes present before installing mutation observer`); } if ( extraArgs.stay ) { return; } runAt(( ) => { @@ -718,8 +741,6 @@ builtinScriptlets.push({ dependencies: [ 'matches-stack-trace.fn', 'object-find-owner.fn', - 'safe-self.fn', - 'should-log.fn', ], }); // When no "prune paths" argument is provided, the scriptlet is @@ -736,15 +757,12 @@ function objectPruneFn( extraArgs = {} ) { if ( typeof rawPrunePaths !== 'string' ) { return; } - const safe = safeSelf(); const prunePaths = rawPrunePaths !== '' ? rawPrunePaths.split(/ +/) : []; const needlePaths = prunePaths.length !== 0 && rawNeedlePaths !== '' ? rawNeedlePaths.split(/ +/) : []; - const logLevel = shouldLog({ log: rawPrunePaths === '' || extraArgs.log }); - const reLogNeedle = safe.patternToRegex(logLevel === true ? rawNeedlePaths : ''); if ( stackNeedleDetails.matchAll !== true ) { if ( matchesStackTrace(stackNeedleDetails, extraArgs.logstack) === false ) { return; @@ -759,14 +777,6 @@ function objectPruneFn( } return true; }; - objectPruneFn.logJson = (json, msg, reNeedle) => { - if ( reNeedle.test(json) === false ) { return; } - safeSelf().uboLog(`objectPrune()`, msg, location.hostname, json); - }; - } - const jsonBefore = logLevel ? safe.JSON_stringify(obj, null, 2) : ''; - if ( logLevel === true || logLevel === 'all' ) { - objectPruneFn.logJson(jsonBefore, `prune:"${rawPrunePaths}" log:"${logLevel}"`, reLogNeedle); } if ( prunePaths.length === 0 ) { return; } let outcome = 'nomatch'; @@ -777,9 +787,6 @@ function objectPruneFn( } } } - if ( logLevel === outcome ) { - objectPruneFn.logJson(jsonBefore, `prune:"${rawPrunePaths}" log:"${logLevel}"`, reLogNeedle); - } if ( outcome === 'match' ) { return obj; } } @@ -931,9 +938,12 @@ function setCookieFn( } catch(_) { } - if ( options.reload && getCookieFn(name) === value ) { + const done = getCookieFn(name) === value; + if ( done && options.reload ) { window.location.reload(); } + + return done; } /******************************************************************************/ @@ -1140,7 +1150,6 @@ builtinScriptlets.push({ 'object-prune.fn', 'parse-properties-to-match.fn', 'safe-self.fn', - 'should-log.fn', ], }); function jsonPruneFetchResponseFn( @@ -1148,23 +1157,22 @@ function jsonPruneFetchResponseFn( rawNeedlePaths = '' ) { const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix('json-prune-fetch-response', rawPrunePaths, rawNeedlePaths); const extraArgs = safe.getExtraArgs(Array.from(arguments), 2); - const logLevel = shouldLog({ log: rawPrunePaths === '' || extraArgs.log, }); - const log = logLevel ? ((...args) => { safe.uboLog(...args); }) : (( ) => { }); const propNeedles = parsePropertiesToMatch(extraArgs.propsToMatch, 'url'); const stackNeedle = safe.initPattern(extraArgs.stackToMatch || '', { canNegate: true }); const applyHandler = function(target, thisArg, args) { const fetchPromise = Reflect.apply(target, thisArg, args); - if ( logLevel === true ) { - log('json-prune-fetch-response:', JSON.stringify(Array.from(args)).slice(1,-1)); - } if ( rawPrunePaths === '' ) { return fetchPromise; } let outcome = 'match'; if ( propNeedles.size !== 0 ) { const objs = [ args[0] instanceof Object ? args[0] : { url: args[0] } ]; if ( objs[0] instanceof Request ) { - try { objs[0] = safe.Request_clone.call(objs[0]); } - catch(ex) { log(ex); } + try { + objs[0] = safe.Request_clone.call(objs[0]); + } catch(ex) { + safe.uboErr(logPrefix, 'Error:', ex); + } } if ( args[1] instanceof Object ) { objs.push(args[1]); @@ -1172,15 +1180,11 @@ function jsonPruneFetchResponseFn( if ( matchObjectProperties(propNeedles, ...objs) === false ) { outcome = 'nomatch'; } - if ( outcome === logLevel || logLevel === 'all' ) { - log( - `json-prune-fetch-response (${outcome})`, - `\n\tfetchPropsToMatch: ${JSON.stringify(Array.from(propNeedles)).slice(1,-1)}`, - '\n\tprops:', ...objs, - ); - } } if ( outcome === 'nomatch' ) { return fetchPromise; } + if ( safe.logLevel > 1 ) { + safe.uboLog(logPrefix, `Matched optional "propsToMatch"\n${extraArgs.propsToMatch}`); + } return fetchPromise.then(responseBefore => { const response = responseBefore.clone(); return response.json().then(objBefore => { @@ -1193,6 +1197,7 @@ function jsonPruneFetchResponseFn( extraArgs ); if ( typeof objAfter !== 'object' ) { return responseBefore; } + safe.uboLog(logPrefix, 'Pruned'); const responseAfter = Response.json(objAfter, { status: responseBefore.status, statusText: responseBefore.statusText, @@ -1206,11 +1211,11 @@ function jsonPruneFetchResponseFn( }); return responseAfter; }).catch(reason => { - log('json-prune-fetch-response:', reason); + safe.uboErr(logPrefix, 'Error:', reason); return responseBefore; }); }).catch(reason => { - log('json-prune-fetch-response:', reason); + safe.uboErr(logPrefix, 'Error:', reason); return fetchPromise; }); }; @@ -1228,7 +1233,6 @@ builtinScriptlets.push({ 'match-object-properties.fn', 'parse-properties-to-match.fn', 'safe-self.fn', - 'should-log.fn', ], }); function replaceFetchResponseFn( @@ -1239,27 +1243,24 @@ function replaceFetchResponseFn( ) { if ( trusted !== true ) { return; } const safe = safeSelf(); - const extraArgs = safe.getExtraArgs(Array.from(arguments), 4); - const logLevel = shouldLog({ - log: pattern === '' || extraArgs.log, - }); - const log = logLevel ? ((...args) => { safe.uboLog(...args); }) : (( ) => { }); + const logPrefix = safe.makeLogPrefix('replace-fetch-response', pattern, replacement, propsToMatch); if ( pattern === '*' ) { pattern = '.*'; } const rePattern = safe.patternToRegex(pattern); const propNeedles = parsePropertiesToMatch(propsToMatch, 'url'); self.fetch = new Proxy(self.fetch, { apply: function(target, thisArg, args) { - if ( logLevel === true ) { - log('replace-fetch-response:', JSON.stringify(Array.from(args)).slice(1,-1)); - } const fetchPromise = Reflect.apply(target, thisArg, args); if ( pattern === '' ) { return fetchPromise; } let outcome = 'match'; if ( propNeedles.size !== 0 ) { const objs = [ args[0] instanceof Object ? args[0] : { url: args[0] } ]; if ( objs[0] instanceof Request ) { - try { objs[0] = safe.Request_clone.call(objs[0]); } - catch(ex) { log(ex); } + try { + objs[0] = safe.Request_clone.call(objs[0]); + } + catch(ex) { + safe.uboErr(logPrefix, ex); + } } if ( args[1] instanceof Object ) { objs.push(args[1]); @@ -1267,28 +1268,18 @@ function replaceFetchResponseFn( if ( matchObjectProperties(propNeedles, ...objs) === false ) { outcome = 'nomatch'; } - if ( outcome === logLevel || logLevel === 'all' ) { - log( - `replace-fetch-response (${outcome})`, - `\n\tpropsToMatch: ${JSON.stringify(Array.from(propNeedles)).slice(1,-1)}`, - '\n\tprops:', ...args, - ); - } } if ( outcome === 'nomatch' ) { return fetchPromise; } + if ( safe.logLevel > 1 ) { + safe.uboLog(logPrefix, `Matched "propsToMatch"\n${propsToMatch}`); + } return fetchPromise.then(responseBefore => { const response = responseBefore.clone(); return response.text().then(textBefore => { const textAfter = textBefore.replace(rePattern, replacement); const outcome = textAfter !== textBefore ? 'match' : 'nomatch'; - if ( outcome === logLevel || logLevel === 'all' ) { - log( - `replace-fetch-response (${outcome})`, - `\n\tpattern: ${pattern}`, - `\n\treplacement: ${replacement}`, - ); - } if ( outcome === 'nomatch' ) { return responseBefore; } + safe.uboLog(logPrefix, 'Replaced'); const responseAfter = new Response(textAfter, { status: responseBefore.status, statusText: responseBefore.statusText, @@ -1302,11 +1293,11 @@ function replaceFetchResponseFn( }); return responseAfter; }).catch(reason => { - log('replace-fetch-response:', reason); + safe.uboErr(logPrefix, reason); return responseBefore; }); }).catch(reason => { - log('replace-fetch-response:', reason); + safe.uboErr(logPrefix, reason); return fetchPromise; }); } @@ -1353,6 +1344,7 @@ builtinScriptlets.push({ fn: abortOnPropertyRead, dependencies: [ 'get-exception-token.fn', + 'safe-self.fn', ], }); function abortOnPropertyRead( @@ -1360,8 +1352,11 @@ function abortOnPropertyRead( ) { if ( typeof chain !== 'string' ) { return; } if ( chain === '' ) { return; } + const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix('abort-on-property-read', chain); const exceptionToken = getExceptionToken(); const abort = function() { + safe.uboLog(logPrefix, 'Aborted'); throw new ReferenceError(exceptionToken); }; const makeProxy = function(owner, chain) { @@ -1409,6 +1404,7 @@ builtinScriptlets.push({ fn: abortOnPropertyWrite, dependencies: [ 'get-exception-token.fn', + 'safe-self.fn', ], }); function abortOnPropertyWrite( @@ -1416,6 +1412,8 @@ function abortOnPropertyWrite( ) { if ( typeof prop !== 'string' ) { return; } if ( prop === '' ) { return; } + const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix('abort-on-property-read', prop); const exceptionToken = getExceptionToken(); let owner = window; for (;;) { @@ -1428,6 +1426,7 @@ function abortOnPropertyWrite( delete owner[prop]; Object.defineProperty(owner, prop, { set: function() { + safe.uboLog(logPrefix, 'Aborted'); throw new ReferenceError(exceptionToken); } }); @@ -1512,7 +1511,6 @@ builtinScriptlets.push({ 'run-at.fn', 'safe-self.fn', 'should-debug.fn', - 'should-log.fn', ], }); // https://github.com/uBlockOrigin/uAssets/issues/9123#issuecomment-848255120 @@ -1521,10 +1519,10 @@ function addEventListenerDefuser( pattern = '' ) { const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix('prevent-addEventListener', type, pattern); const extraArgs = safe.getExtraArgs(Array.from(arguments), 2); const reType = safe.patternToRegex(type, undefined, true); const rePattern = safe.patternToRegex(pattern); - const log = shouldLog(extraArgs); const debug = shouldDebug(extraArgs); const targetSelector = extraArgs.elements || undefined; const shouldPrevent = (thisArg, type, handler) => { @@ -1536,8 +1534,8 @@ function addEventListenerDefuser( const matchesHandler = safe.RegExp_test.call(rePattern, handler); const matchesEither = matchesType || matchesHandler; const matchesBoth = matchesType && matchesHandler; - if ( log === 1 && matchesBoth || log === 2 && matchesEither || log === 3 ) { - safe.uboLog(`addEventListener('${type}', ${handler})`); + if ( safe.logLevel > 1 && matchesEither ) { + safe.uboLog(logPrefix, `Matched "${type}"\n${handler.trim()}`); } if ( debug === 1 && matchesBoth || debug === 2 && matchesEither ) { debugger; // jshint ignore:line @@ -1555,8 +1553,10 @@ function addEventListenerDefuser( : String(args[1]); } catch(ex) { } - if ( shouldPrevent(thisArg, type, handler) ) { return; } - return Reflect.apply(target, thisArg, args); + if ( shouldPrevent(thisArg, type, handler) !== true ) { + return Reflect.apply(target, thisArg, args); + } + safe.uboLog(logPrefix, 'Prevented'); }, get(target, prop, receiver) { if ( prop === 'toString' ) { @@ -1637,7 +1637,6 @@ builtinScriptlets.push({ 'object-prune.fn', 'parse-properties-to-match.fn', 'safe-self.fn', - 'should-log.fn', ], }); function jsonPruneXhrResponse( @@ -1645,10 +1644,9 @@ function jsonPruneXhrResponse( rawNeedlePaths = '' ) { const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix('json-prune-xhr-response', rawPrunePaths, rawNeedlePaths); const xhrInstances = new WeakMap(); const extraArgs = safe.getExtraArgs(Array.from(arguments), 2); - const logLevel = shouldLog({ log: rawPrunePaths === '' || extraArgs.log, }); - const log = logLevel ? ((...args) => { safe.uboLog(...args); }) : (( ) => { }); const propNeedles = parsePropertiesToMatch(extraArgs.propsToMatch, 'url'); const stackNeedle = safe.initPattern(extraArgs.stackToMatch || '', { canNegate: true }); self.XMLHttpRequest = class extends self.XMLHttpRequest { @@ -1660,10 +1658,10 @@ function jsonPruneXhrResponse( outcome = 'nomatch'; } } - if ( outcome === logLevel || outcome === 'all' ) { - log(`xhr.open(${method}, ${url}, ${args.join(', ')})`); - } if ( outcome === 'match' ) { + if ( safe.logLevel > 1 ) { + safe.uboLog(logPrefix, `Matched optional "propsToMatch", "${extraArgs.propsToMatch}"`); + } xhrInstances.set(this, xhrDetails); } return super.open(method, url, ...args); @@ -1688,8 +1686,10 @@ function jsonPruneXhrResponse( if ( typeof innerResponse === 'object' ) { objBefore = innerResponse; } else if ( typeof innerResponse === 'string' ) { - try { objBefore = safe.JSON_parse(innerResponse); } - catch(ex) { } + try { + objBefore = safe.JSON_parse(innerResponse); + } catch(ex) { + } } if ( typeof objBefore !== 'object' ) { return (xhrDetails.response = innerResponse); @@ -1706,6 +1706,7 @@ function jsonPruneXhrResponse( outerResponse = typeof innerResponse === 'string' ? safe.JSON_stringify(objAfter) : objAfter; + safe.uboLog(logPrefix, 'Pruned'); } else { outerResponse = innerResponse; } @@ -1884,9 +1885,9 @@ function noEvalIf( /******************************************************************************/ builtinScriptlets.push({ - name: 'no-fetch-if.js', + name: 'prevent-fetch.js', aliases: [ - 'prevent-fetch.js', + 'no-fetch-if.js', ], fn: noFetchIf, dependencies: [ @@ -1900,6 +1901,7 @@ function noFetchIf( ) { if ( typeof propsToMatch !== 'string' ) { return; } const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix('prevent-fetch', propsToMatch, responseBody); const needles = []; for ( const condition of propsToMatch.split(/\s+/) ) { if ( condition === '' ) { continue; } @@ -1914,7 +1916,6 @@ function noFetchIf( } needles.push({ key, re: safe.patternToRegex(value) }); } - const log = needles.length === 0 ? console.log.bind(console) : undefined; self.fetch = new Proxy(self.fetch, { apply: function(target, thisArg, args) { const details = args[0] instanceof self.Request @@ -1932,12 +1933,6 @@ function noFetchIf( if ( typeof v !== 'string' ) { continue; } props.set(prop, v); } - if ( log !== undefined ) { - const out = Array.from(props) - .map(a => `${a[0]}:${a[1]}`) - .join(' '); - log(`uBO: fetch(${out})`); - } proceed = needles.length === 0; for ( const { key, re } of needles ) { if ( @@ -1960,10 +1955,12 @@ function noFetchIf( responseType = desURL.origin !== document.location.origin ? 'cors' : 'basic'; - } catch(_) { + } catch(ex) { + safe.uboErr(logPrefix, `Error: ${ex}`); } } return generateContentFn(responseBody).then(text => { + safe.uboLog(logPrefix, `Prevented with response "${text}"`); const response = new Response(text, { statusText: 'OK', headers: { @@ -1995,6 +1992,7 @@ builtinScriptlets.push({ world: 'ISOLATED', dependencies: [ 'run-at.fn', + 'safe-self.fn', ], }); // https://www.reddit.com/r/uBlockOrigin/comments/q0frv0/while_reading_a_sports_article_i_was_redirected/hf7wo9v/ @@ -2002,9 +2000,12 @@ function preventRefresh( arg1 = '' ) { if ( typeof arg1 !== 'string' ) { return; } + const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix('prevent-refresh', arg1); const defuse = ( ) => { const meta = document.querySelector('meta[http-equiv="refresh" i][content]'); if ( meta === null ) { return; } + safe.uboLog(logPrefix, `Prevented "${meta.textContent}"`); const s = arg1 === '' ? meta.getAttribute('content') : arg1; @@ -2233,6 +2234,7 @@ function noSetIntervalIf( ) { if ( typeof needle !== 'string' ) { return; } const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix('prevent-setInterval', needle, delay); const needleNot = needle.charAt(0) === '!'; if ( needleNot ) { needle = needle.slice(1); } if ( delay === '' ) { delay = undefined; } @@ -2242,9 +2244,6 @@ function noSetIntervalIf( if ( delayNot ) { delay = delay.slice(1); } delay = parseInt(delay, 10); } - const log = needleNot === false && needle === '' && delay === undefined - ? console.log - : undefined; const reNeedle = safe.patternToRegex(needle); self.setInterval = new Proxy(self.setInterval, { apply: function(target, thisArg, args) { @@ -2252,19 +2251,16 @@ function noSetIntervalIf( ? String(safe.Function_toString(args[0])) : String(args[0]); const b = args[1]; - if ( log !== undefined ) { - log('uBO: setInterval("%s", %s)', a, b); - } else { - let defuse; - if ( needle !== '' ) { - defuse = reNeedle.test(a) !== needleNot; - } - if ( defuse !== false && delay !== undefined ) { - defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot; - } - if ( defuse ) { - args[0] = function(){}; - } + let defuse; + if ( needle !== '' ) { + defuse = reNeedle.test(a) !== needleNot; + } + if ( defuse !== false && delay !== undefined ) { + defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot; + } + if ( defuse ) { + args[0] = function(){}; + safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`); } return Reflect.apply(target, thisArg, args); }, @@ -2297,6 +2293,7 @@ function noSetTimeoutIf( ) { if ( typeof needle !== 'string' ) { return; } const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix('prevent-setTimeout', needle, delay); const needleNot = needle.charAt(0) === '!'; if ( needleNot ) { needle = needle.slice(1); } if ( delay === '' ) { delay = undefined; } @@ -2306,9 +2303,6 @@ function noSetTimeoutIf( if ( delayNot ) { delay = delay.slice(1); } delay = parseInt(delay, 10); } - const log = needleNot === false && needle === '' && delay === undefined - ? console.log - : undefined; const reNeedle = safe.patternToRegex(needle); self.setTimeout = new Proxy(self.setTimeout, { apply: function(target, thisArg, args) { @@ -2316,19 +2310,16 @@ function noSetTimeoutIf( ? String(safe.Function_toString(args[0])) : String(args[0]); const b = args[1]; - if ( log !== undefined ) { - log('uBO: setTimeout("%s", %s)', a, b); - } else { - let defuse; - if ( needle !== '' ) { - defuse = reNeedle.test(a) !== needleNot; - } - if ( defuse !== false && delay !== undefined ) { - defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot; - } - if ( defuse ) { - args[0] = function(){}; - } + let defuse; + if ( needle !== '' ) { + defuse = reNeedle.test(a) !== needleNot; + } + if ( defuse !== false && delay !== undefined ) { + defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot; + } + if ( defuse ) { + args[0] = function(){}; + safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`); } return Reflect.apply(target, thisArg, args); }, @@ -2429,10 +2420,11 @@ function noXhrIf( directive = '' ) { if ( typeof propsToMatch !== 'string' ) { return; } + const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix('prevent-xhr', propsToMatch, directive); const xhrInstances = new WeakMap(); const propNeedles = parsePropertiesToMatch(propsToMatch, 'url'); - const log = propNeedles.size === 0 ? console.log.bind(console) : undefined; - const warOrigin = scriptletGlobals.get('warOrigin'); + const warOrigin = scriptletGlobals.warOrigin; const headers = { 'date': '', 'content-type': '', @@ -2440,10 +2432,6 @@ function noXhrIf( }; self.XMLHttpRequest = class extends self.XMLHttpRequest { open(method, url, ...args) { - if ( log !== undefined ) { - log(`uBO: xhr.open(${method}, ${url}, ${args.join(', ')})`); - return super.open(method, url, ...args); - } xhrInstances.delete(this); if ( warOrigin !== undefined && url.startsWith(warOrigin) ) { return super.open(method, url, ...args); @@ -2526,6 +2514,7 @@ function noXhrIf( details.xhr.dispatchEvent(new Event('readystatechange')); details.xhr.dispatchEvent(new Event('load')); details.xhr.dispatchEvent(new Event('loadend')); + safe.uboLog(logPrefix, `Prevented with:\n${details.xhr.response}`); }); } getResponseHeader(headerName) { @@ -2565,7 +2554,6 @@ builtinScriptlets.push({ fn: noWindowOpenIf, dependencies: [ 'safe-self.fn', - 'should-log.fn', ], }); function noWindowOpenIf( @@ -2574,6 +2562,7 @@ function noWindowOpenIf( decoy = '' ) { const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix('no-window-open-if', pattern, delay, decoy); const targetMatchResult = pattern.startsWith('!') === false; if ( targetMatchResult === false ) { pattern = pattern.slice(1); @@ -2583,8 +2572,6 @@ function noWindowOpenIf( if ( isNaN(autoRemoveAfter) ) { autoRemoveAfter = -1; } - const extraArgs = safe.getExtraArgs(Array.from(arguments), 3); - const logLevel = shouldLog(extraArgs); const createDecoy = function(tag, urlProp, url) { const decoyElem = document.createElement(tag); decoyElem[urlProp] = url; @@ -2599,12 +2586,10 @@ function noWindowOpenIf( window.open = new Proxy(window.open, { apply: function(target, thisArg, args) { const haystack = args.join(' '); - if ( logLevel ) { - safe.uboLog('window.open:', haystack); - } if ( rePattern.test(haystack) !== targetMatchResult ) { return Reflect.apply(target, thisArg, args); } + safe.uboLog(logPrefix, 'Prevented'); if ( autoRemoveAfter < 0 ) { return null; } const decoyElem = decoy === 'obj' ? createDecoy('object', 'data', ...args) @@ -2626,14 +2611,14 @@ function noWindowOpenIf( }, }); } - if ( logLevel ) { + if ( safe.logLevel !== 0 ) { popup = new Proxy(popup, { get: function(target, prop) { - safe.uboLog('window.open / get', prop, '===', target[prop]); + safe.uboLog(logPrefix, 'window.open / get', prop, '===', target[prop]); return Reflect.get(...arguments); }, set: function(target, prop, value) { - safe.uboLog('window.open / set', prop, '=', value); + safe.uboLog(logPrefix, 'window.open / set', prop, '=', value); return Reflect.set(...arguments); }, }); @@ -2942,7 +2927,6 @@ builtinScriptlets.push({ fn: xmlPrune, dependencies: [ 'safe-self.fn', - 'should-log.fn', ], }); function xmlPrune( @@ -2953,9 +2937,9 @@ function xmlPrune( if ( typeof selector !== 'string' ) { return; } if ( selector === '' ) { return; } const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix('xml-prune', selector, selectorCheck, urlPattern); const reUrl = safe.patternToRegex(urlPattern); const extraArgs = safe.getExtraArgs(Array.from(arguments), 3); - const log = shouldLog(extraArgs) ? ((...args) => { safe.uboLog(...args); }) : (( ) => { }); const queryAll = (xmlDoc, selector) => { const isXpath = /^xpath\(.+\)$/.test(selector); if ( isXpath === false ) { @@ -2982,21 +2966,21 @@ function xmlPrune( } if ( extraArgs.logdoc ) { const serializer = new XMLSerializer(); - log(`xmlPrune: document is\n\t${serializer.serializeToString(xmlDoc)}`); + safe.uboLog(logPrefix, `Document is\n\t${serializer.serializeToString(xmlDoc)}`); } const items = queryAll(xmlDoc, selector); if ( items.length === 0 ) { return xmlDoc; } - log(`xmlPrune: removing ${items.length} items`); + safe.uboLog(logPrefix, `Removing ${items.length} items`); for ( const item of items ) { if ( item.nodeType === 1 ) { item.remove(); } else if ( item.nodeType === 2 ) { item.ownerElement.removeAttribute(item.nodeName); } - log(`xmlPrune: ${item.constructor.name}.${item.nodeName} removed`); + safe.uboLog(logPrefix, `${item.constructor.name}.${item.nodeName} removed`); } } catch(ex) { - log(ex); + safe.uboErr(logPrefix, `Error: ${ex}`); } return xmlDoc; }; @@ -3088,7 +3072,6 @@ builtinScriptlets.push({ fn: m3uPrune, dependencies: [ 'safe-self.fn', - 'should-log.fn', ], }); // https://en.wikipedia.org/wiki/M3U @@ -3098,9 +3081,8 @@ function m3uPrune( ) { if ( typeof m3uPattern !== 'string' ) { return; } const safe = safeSelf(); - const options = safe.getExtraArgs(Array.from(arguments), 2); - const logLevel = shouldLog(options); - const uboLog = logLevel ? ((...args) => safe.uboLog(...args)) : (( ) => { }); + const logPrefix = safe.makeLogPrefix('m3u-prune', m3uPattern, urlPattern); + const toLog = []; const regexFromArg = arg => { if ( arg === '' ) { return /^/; } const match = /^\/(.+)\/([gms]*)$/.exec(arg); @@ -3119,22 +3101,22 @@ function m3uPrune( if ( lines[i].startsWith('#EXT-X-CUE:TYPE="SpliceOut"') === false ) { return false; } - uboLog('m3u-prune: discarding', `\n\t${lines[i]}`); + toLog.push(`\t${lines[i]}`); lines[i] = undefined; i += 1; if ( lines[i].startsWith('#EXT-X-ASSET:CAID') ) { - uboLog(`\t${lines[i]}`); + toLog.push(`\t${lines[i]}`); lines[i] = undefined; i += 1; } if ( lines[i].startsWith('#EXT-X-SCTE35:') ) { - uboLog(`\t${lines[i]}`); + toLog.push(`\t${lines[i]}`); lines[i] = undefined; i += 1; } if ( lines[i].startsWith('#EXT-X-CUE-IN') ) { - uboLog(`\t${lines[i]}`); + toLog.push(`\t${lines[i]}`); lines[i] = undefined; i += 1; } if ( lines[i].startsWith('#EXT-X-SCTE35:') ) { - uboLog(`\t${lines[i]}`); + toLog.push(`\t${lines[i]}`); lines[i] = undefined; i += 1; } return true; @@ -3142,10 +3124,10 @@ function m3uPrune( const pruneInfBlock = (lines, i) => { if ( lines[i].startsWith('#EXTINF') === false ) { return false; } if ( reM3u.test(lines[i+1]) === false ) { return false; } - uboLog('m3u-prune: discarding', `\n\t${lines[i]}, \n\t${lines[i+1]}`); + toLog.push('Discarding', `\t${lines[i]}, \t${lines[i+1]}`); lines[i] = lines[i+1] = undefined; i += 2; if ( lines[i].startsWith('#EXT-X-DISCONTINUITY') ) { - uboLog(`\t${lines[i]}`); + toLog.push(`\t${lines[i]}`); lines[i] = undefined; i += 1; } return true; @@ -3182,9 +3164,7 @@ function m3uPrune( } text = before.trim() + '\n' + after.trim(); reM3u.lastIndex = before.length + 1; - uboLog('m3u-prune: discarding\n', - discard.split(/\n+/).map(s => `\t${s}`).join('\n') - ); + toLog.push('Discarding', ...discard.split(/\n+/).map(s => `\t${s}`)); if ( reM3u.global === false ) { break; } } return text; @@ -3209,13 +3189,18 @@ function m3uPrune( return Reflect.apply(target, thisArg, args); } return realFetch(...args).then(realResponse => - realResponse.text().then(text => - new Response(pruner(text), { + realResponse.text().then(text => { + const response = new Response(pruner(text), { status: realResponse.status, statusText: realResponse.statusText, headers: realResponse.headers, - }) - ) + }); + if ( toLog.length !== 0 ) { + toLog.unshift(logPrefix); + safe.uboLog(toLog.join('\n')); + } + return response; + }) ); } }); @@ -3233,6 +3218,10 @@ function m3uPrune( if ( textout === textin ) { return; } Object.defineProperty(thisArg, 'response', { value: textout }); Object.defineProperty(thisArg, 'responseText', { value: textout }); + if ( toLog.length !== 0 ) { + toLog.unshift(logPrefix); + safe.uboLog(toLog.join('\n')); + } }); return Reflect.apply(target, thisArg, args); } @@ -3278,6 +3267,7 @@ builtinScriptlets.push({ world: 'ISOLATED', dependencies: [ 'run-at.fn', + 'safe-self.fn', ], }); function hrefSanitizer( @@ -3286,6 +3276,8 @@ function hrefSanitizer( ) { if ( typeof selector !== 'string' ) { return; } if ( selector === '' ) { return; } + const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix('href-sanitizer', selector, source); if ( source === '' ) { source = 'text'; } const sanitizeCopycats = (href, text) => { let elems = []; @@ -3297,6 +3289,7 @@ function hrefSanitizer( for ( const elem of elems ) { elem.setAttribute('href', text); } + return elems.length; }; const validateURL = text => { if ( text === '' ) { return ''; } @@ -3345,7 +3338,8 @@ function hrefSanitizer( if ( hrefAfter === '' ) { continue; } if ( hrefAfter === href ) { continue; } elem.setAttribute('href', hrefAfter); - sanitizeCopycats(href, hrefAfter); + const count = sanitizeCopycats(href, hrefAfter); + safe.uboLog(logPrefix, `Sanitized ${count+1} links to\n${hrefAfter}`); } return true; }; @@ -3460,15 +3454,15 @@ function spoofCSS( propToValueMap.set(toCamelCase(args[i+0]), args[i+1]); } const safe = safeSelf(); - const canDebug = scriptletGlobals.has('canDebug'); + const logPrefix = safe.makeLogPrefix('spoof-css', selector, ...args); + const canDebug = scriptletGlobals.canDebug; const shouldDebug = canDebug && propToValueMap.get('debug') || 0; - const shouldLog = canDebug && propToValueMap.has('log') || 0; const spoofStyle = (prop, real) => { const normalProp = toCamelCase(prop); const shouldSpoof = propToValueMap.has(normalProp); const value = shouldSpoof ? propToValueMap.get(normalProp) : real; - if ( shouldLog === 2 || shouldSpoof && shouldLog === 1 ) { - safe.uboLog(prop, value); + if ( shouldSpoof ) { + safe.uboLog(logPrefix, `Spoofing ${prop} to ${value}`); } return value; }; @@ -3582,6 +3576,8 @@ function setCookie( path = '' ) { if ( name === '' ) { return; } + const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix('set-cookie', name, value, path); name = encodeURIComponent(name); const validValues = [ @@ -3606,7 +3602,7 @@ function setCookie( if ( n > 15 ) { return; } } - setCookieFn( + const done = setCookieFn( false, name, value, @@ -3614,6 +3610,10 @@ function setCookie( path, safeSelf().getExtraArgs(Array.from(arguments), 3) ); + + if ( done ) { + safe.uboLog(logPrefix, 'Done'); + } } // For compatibility with AdGuard @@ -3979,6 +3979,8 @@ function trustedSetCookie( ) { if ( name === '' ) { return; } + const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix('set-cookie', name, value, path); const time = new Date(); if ( value === '$now$' ) { @@ -4000,7 +4002,7 @@ function trustedSetCookie( expires = time.toUTCString(); } - setCookieFn( + const done = setCookieFn( true, name, value, @@ -4008,6 +4010,10 @@ function trustedSetCookie( path, safeSelf().getExtraArgs(Array.from(arguments), 4) ); + + if ( done ) { + safe.uboLog(logPrefix, 'Done'); + } } // For compatibility with AdGuard @@ -4098,7 +4104,6 @@ builtinScriptlets.push({ 'match-object-properties.fn', 'parse-properties-to-match.fn', 'safe-self.fn', - 'should-log.fn', ], }); function trustedReplaceXhrResponse( @@ -4107,12 +4112,8 @@ function trustedReplaceXhrResponse( propsToMatch = '' ) { const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix('trusted-replace-xhr-response', pattern, replacement, propsToMatch); const xhrInstances = new WeakMap(); - const extraArgs = safe.getExtraArgs(Array.from(arguments), 3); - const logLevel = shouldLog({ - log: pattern === '' && 'all' || extraArgs.log, - }); - const log = logLevel ? ((...args) => { safe.uboLog(...args); }) : (( ) => { }); if ( pattern === '*' ) { pattern = '.*'; } const rePattern = safe.patternToRegex(pattern); const propNeedles = parsePropertiesToMatch(propsToMatch, 'url'); @@ -4126,10 +4127,10 @@ function trustedReplaceXhrResponse( outcome = 'nomatch'; } } - if ( outcome === logLevel || outcome === 'all' ) { - log(`xhr.open(${method}, ${url}, ${args.join(', ')})`); - } if ( outcome === 'match' ) { + if ( safe.logLevel > 1 ) { + safe.uboLog(logPrefix, `Matched "propsToMatch"`); + } xhrInstances.set(outerXhr, xhrDetails); } return super.open(method, url, ...args); @@ -4155,13 +4156,8 @@ function trustedReplaceXhrResponse( } const textBefore = innerResponse; const textAfter = textBefore.replace(rePattern, replacement); - const outcome = textAfter !== textBefore ? 'match' : 'nomatch'; - if ( outcome === logLevel || logLevel === 'all' ) { - log( - `trusted-replace-xhr-response (${outcome})`, - `\n\tpattern: ${pattern}`, - `\n\treplacement: ${replacement}`, - ); + if ( textAfter !== textBefore ) { + safe.uboLog(logPrefix, 'Match'); } return (xhrDetails.response = textAfter); } @@ -4202,10 +4198,7 @@ function trustedClickElement( delay = '' ) { const safe = safeSelf(); - const extraArgs = safe.getExtraArgs(Array.from(arguments), 3); - const uboLog = extraArgs.log !== undefined - ? ((...args) => { safe.uboLog(...args); }) - : (( ) => { }); + const logPrefix = safe.makeLogPrefix('trusted-click-element', selectors, extraMatch, delay); if ( extraMatch !== '' ) { const assertions = extraMatch.split(',').map(s => { @@ -4286,12 +4279,12 @@ function trustedClickElement( const next = notFound => { if ( selectorList.length === 0 ) { - uboLog(`trusted-click-element: Completed`); + safe.uboLog(logPrefix, 'Completed'); return terminate(); } const tnow = Date.now(); if ( tnow >= tbye ) { - uboLog(`trusted-click-element: Timed out`); + safe.uboLog(logPrefix, 'Timed out'); return terminate(); } if ( notFound ) { observe(); } @@ -4300,7 +4293,7 @@ function trustedClickElement( next.timer = undefined; process(); }, delay); - uboLog(`trusted-click-element: Waiting for ${selectorList[0]}...`); + safe.uboLog(logPrefix, `Waiting for ${selectorList[0]}...`); }; next.stop = ( ) => { if ( next.timer === undefined ) { return; } @@ -4344,7 +4337,7 @@ function trustedClickElement( selectorList.unshift(selector); return next(true); } - uboLog(`trusted-click-element: Clicked ${selector}`); + safe.uboLog(logPrefix, `Clicked ${selector}`); elem.click(); tnext += clickDelay; next(); diff --git a/platform/common/vapi-background.js b/platform/common/vapi-background.js index 0d6fcdd5da7fc..54148039eb6af 100644 --- a/platform/common/vapi-background.js +++ b/platform/common/vapi-background.js @@ -87,6 +87,19 @@ vAPI.app = { }, }; +/******************************************************************************/ + +// Generate segments of random six alphanumeric characters, thus one segment +// is a value out of 36^6 = over 2x10^9 values. + +vAPI.generateSecret = (size = 1) => { + let secret = ''; + while ( size-- ) { + secret += (Math.floor(Math.random() * 2176782336) + 2176782336).toString(36).slice(1); + } + return secret; +}; + /******************************************************************************* * * https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/storage/session @@ -1165,11 +1178,6 @@ vAPI.messaging = { // Support using a new secret for every network request. { - // Generate a 6-character alphanumeric string, thus one random value out - // of 36^6 = over 2x10^9 values. - const generateSecret = ( ) => - (Math.floor(Math.random() * 2176782336) + 2176782336).toString(36).slice(1); - const root = vAPI.getURL('/'); const reSecret = /\?secret=(\w+)/; const shortSecrets = []; @@ -1207,7 +1215,7 @@ vAPI.messaging = { } } lastShortSecretTime = Date.now(); - const secret = generateSecret(); + const secret = vAPI.generateSecret(); shortSecrets.push(secret); return secret; }, @@ -1215,7 +1223,7 @@ vAPI.messaging = { if ( previous !== undefined ) { longSecrets.delete(previous); } - const secret = `${generateSecret()}${generateSecret()}${generateSecret()}`; + const secret = vAPI.generateSecret(3); longSecrets.add(secret); return secret; }, diff --git a/src/css/fa-icons.css b/src/css/fa-icons.css index f6d517d401d00..42cfa7f408a58 100644 --- a/src/css/fa-icons.css +++ b/src/css/fa-icons.css @@ -95,6 +95,7 @@ .fa-icon > .fa-icon_spinner, .fa-icon > .fa-icon_unlink, .fa-icon > .fa-icon_upload-alt, +.fa-icon > .fa-icon_volume-up, .fa-icon > .fa-icon_zoom-in, .fa-icon > .fa-icon_zoom-out { width: calc(1em * 1664 / 1792); diff --git a/src/css/logger-ui.css b/src/css/logger-ui.css index 8e5065c56b00e..fc35341cc5227 100644 --- a/src/css/logger-ui.css +++ b/src/css/logger-ui.css @@ -218,6 +218,7 @@ body[dir="rtl"] #netInspector #filterExprPicker { width: 100%; } #vwRenderer .logEntry { + background-color: var(--surface-1); display: block; left: 0; overflow: hidden; @@ -228,7 +229,11 @@ body[dir="rtl"] #netInspector #filterExprPicker { display: none; } #vwRenderer .logEntry > div { + border-bottom: 1px dotted var(--border-1); + box-sizing: border-box; + display: flex; height: 100%; + max-height: 200px; white-space: nowrap; } #vwRenderer .logEntry > div[data-status="1"], @@ -271,7 +276,7 @@ body[dir="rtl"] #netInspector #filterExprPicker { color: white; } #vwRenderer .logEntry > div[data-type="error"] { - color: var(--sf-error-ink); + color: var(--cm-negative); } #vwRenderer .logEntry > div[data-type="info"] { color: var(--sf-def-ink); @@ -283,10 +288,8 @@ body[dir="rtl"] #netInspector #filterExprPicker { opacity: 0.7; } -#vwRenderer .logEntry > div > span { - border: 1px dotted var(--border-1); - border-top: 0; - border-right: 0; +#vwRenderer .logEntry > .fields > span { + border-left: 1px dotted var(--border-1); box-sizing: border-box; display: inline-block; height: 100%; @@ -296,86 +299,74 @@ body[dir="rtl"] #netInspector #filterExprPicker { white-space: nowrap; word-break: break-all; } -#vwRenderer .logEntry > div.canDetails:hover > span { +#vwRenderer .logEntry > div:hover > span { background-color: rgba(0,0,0,0.04); } -body[dir="ltr"] #vwRenderer .logEntry > div > span:first-child { +body[dir="ltr"] #vwRenderer .logEntry > .fields > span:first-child { border-left: 0; } -body[dir="rtl"] #vwRenderer .logEntry > div > span:first-child { +body[dir="rtl"] #vwRenderer .logEntry > .fields > span:first-child { border-right: 0; } #vwRenderer .logEntry > div > span:nth-of-type(1) { } #vwRenderer .logEntry > div > span:nth-of-type(2) { } -#vwRenderer .logEntry > div > span:nth-of-type(2) { +#vwRenderer .logEntry > .fields > span:nth-of-type(2) { text-overflow: ellipsis; } -#vwRenderer .logEntry > div.messageRealm > span:nth-of-type(2) ~ span { +#vwRenderer .logEntry > .fields.messageRealm > span:nth-of-type(2) ~ span { display: none; } -.vExpanded #vwRenderer #vwContent .logEntry > div > span:nth-of-type(2) { +.vExpanded #vwRenderer #vwContent .logEntry > .fields > span:nth-of-type(2) { overflow-y: auto; white-space: pre-line; } -#vwRenderer .logEntry > div.messageRealm[data-type="tabLoad"] > span:nth-of-type(2) { +#vwRenderer .logEntry > .fields.messageRealm[data-type="tabLoad"] > span:nth-of-type(2) { text-align: center; } -#vwRenderer .logEntry > div.extendedRealm > span:nth-of-type(2) > span:first-of-type { +#vwRenderer .logEntry > .fields.extendedRealm > span:nth-of-type(2) > span:first-of-type { display: none; } -#vwRenderer .logEntry > div.extendedRealm > span:nth-of-type(2) > span:last-of-type { - pointer-events: none; - } -#vwRenderer .logEntry > div.extendedRealm.isException > span:nth-of-type(2) > span:last-of-type { +#vwRenderer .logEntry > .fields.extendedRealm.isException > span:nth-of-type(2) > span:last-of-type { text-decoration: line-through rgba(0,0,255,0.7); } -#vwRenderer .logEntry > div > span:nth-of-type(3) { +#vwRenderer .logEntry > .fields > span:nth-of-type(3) { font-family: monospace; padding-left: 0.3em; padding-right: 0.3em; text-align: center; } -#vwRenderer .logEntry > div.canDetails:hover > span:not(:nth-of-type(4)):not(:nth-of-type(8)) { - background: rgba(0, 0, 255, 0.1); - cursor: zoom-in; - } -#netInspector:not(.vExpanded) #vwRenderer .logEntry > div > span:nth-of-type(4) { - direction: rtl; +#netInspector:not(.vExpanded) #vwRenderer .logEntry > .fields > span:nth-of-type(4) { text-align: right; - unicode-bidi: plaintext; } -#vwRenderer #vwContent .logEntry > div > span:nth-of-type(4) { +#vwRenderer #vwContent .logEntry > .fields > span:nth-of-type(4) { text-overflow: ellipsis; } -.vExpanded #vwRenderer #vwContent .logEntry > div > span:nth-of-type(4) { +.vExpanded #vwRenderer #vwContent .logEntry > .fields > span:nth-of-type(4) { overflow-y: auto; text-overflow: clip; white-space: pre-line; } -#vwRenderer .logEntry > div > span:nth-of-type(5) { +#vwRenderer .logEntry > .fields > span:nth-of-type(5) { text-align: center; } -/* visual for tabless network requests */ -#vwRenderer .logEntry > div > span:nth-of-type(5) { - position: relative; - } -#vwRenderer .logEntry > div > span:nth-of-type(7) { - } -#vwRenderer #vwContent .logEntry > div > span:nth-of-type(7) { +#vwRenderer .logEntry > .fields > span:nth-of-type(3), +#vwRenderer .logEntry > .fields > span:nth-of-type(5), +#vwRenderer .logEntry > .fields > span:nth-of-type(7) { + white-space: nowrap !important; } -#vwRenderer .logEntry > div > span:nth-of-type(8) { +#vwRenderer .logEntry > .fields > span:nth-of-type(8) { position: relative; } -#vwRenderer #vwContent .logEntry > div > span:nth-of-type(8) { +#vwRenderer #vwContent .logEntry > .fields > span:nth-of-type(8) { text-overflow: ellipsis; } -.vExpanded #vwRenderer #vwContent .logEntry > div > span:nth-of-type(8) { +.vExpanded #vwRenderer #vwContent .logEntry > .fields > span:nth-of-type(8) { overflow-y: auto; white-space: pre-line; } -#vwRenderer .logEntry > div > span:nth-of-type(8) b { +#vwRenderer .logEntry > .fields > span:nth-of-type(8) b { font-weight: bold; } #vwRenderer .logEntry > div[data-status="1"] > span:nth-of-type(8) b, @@ -396,37 +387,65 @@ body[dir="rtl"] #vwRenderer .logEntry > div > span:first-child { .netFilteringDialog > .panes > .details > div[data-status="2"] b { background-color: rgb(var(--popup-cell-allow-surface-rgb) / 100%); } -#vwRenderer .logEntry > div > span:nth-of-type(8) a { +#vwRenderer .logEntry > .fields > span:nth-of-type(8) a { align-items: center; background-color: dimgray; + bottom: 0; color: white; display: none; - height: 100%; + height: min(100%, 1.5em); justify-content: center; - padding: 0 0.25em; + padding: 0.1em; opacity: 0.4; position: absolute; right: 0; text-decoration: none; - top: 0; - width: 2rem; + width: 1.5em; } -#netInspector.vExpanded #vwRenderer .logEntry > div > span:nth-of-type(8) a { +#netInspector.vExpanded #vwRenderer .logEntry > .fields > span:nth-of-type(8) a { bottom: 0px; height: unset; - padding: 0.25em; + padding: 0.2em; top: unset; } -#vwRenderer .logEntry > div > span:nth-of-type(8) a::after { +#vwRenderer .logEntry > .fields > span:nth-of-type(8) a::after { content: '\2197'; } -#vwRenderer .logEntry > div.networkRealm > span:nth-of-type(8):hover a { +#vwRenderer .logEntry > .fields.networkRealm > span:nth-of-type(8):hover a { display: inline-flex; } -#vwRenderer .logEntry > div > span:nth-of-type(8) a:hover { +#vwRenderer .logEntry > .fields > span:nth-of-type(8) a:hover { opacity: 1; } +@keyframes unrollRow { + to { + box-shadow: 0 2px 3px 0 #444; + height: auto; + max-height: 200px; + z-index: 1; + } +} +@keyframes unrollRowCell { + to { + height: auto; + overflow-y: auto; + white-space: pre-wrap; + } +} +#netInspector:not(.vExpanded) #vwRenderer .logEntry:hover { + animation-delay: 0.8s; + animation-fill-mode: forwards; + animation-name: unrollRow; + animation-timing-function: step-start; + } +#netInspector:not(.vExpanded) #vwRenderer .logEntry:hover > .fields > span { + animation-delay: 0.8s; + animation-fill-mode: forwards; + animation-name: unrollRowCell; + animation-timing-function: step-start; + } + #vwRenderer #vwBottom { background-color: #00F; height: 0; @@ -448,6 +467,7 @@ body[dir="rtl"] #vwRenderer .logEntry > div > span:first-child { max-width: 640px; min-width: min(100%, 640px); position: absolute; + z-index: 2; } #netInspector .entryTools:empty { display: none; diff --git a/src/img/fontawesome/fontawesome-defs.svg b/src/img/fontawesome/fontawesome-defs.svg index 75bc67ff93a15..e457794f6ac3b 100644 --- a/src/img/fontawesome/fontawesome-defs.svg +++ b/src/img/fontawesome/fontawesome-defs.svg @@ -73,6 +73,7 @@ License - https://github.com/FortAwesome/Font-Awesome/tree/a8386aae19e200ddb0f68 + diff --git a/src/js/background.js b/src/js/background.js index 578d8a65c8642..e8fab0513e04c 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -374,7 +374,7 @@ const µBlock = { // jshint ignore:line toLogger() { const details = { id: this.id, - tstamp: Date.now(), + tstamp: 0, realm: this.realm, method: this.getMethodName(), type: this.stype, diff --git a/src/js/benchmarks.js b/src/js/benchmarks.js index 8792f03594a41..f3fc53d9e862c 100644 --- a/src/js/benchmarks.js +++ b/src/js/benchmarks.js @@ -353,6 +353,7 @@ const loadBenchmarkDataset = (( ) => { hostname: '', tabId: 0, url: '', + nocache: true, }; let count = 0; const t0 = performance.now(); diff --git a/src/js/click2load.js b/src/js/click2load.js index 42b7525e0b973..b441d973e8845 100644 --- a/src/js/click2load.js +++ b/src/js/click2load.js @@ -49,9 +49,8 @@ document.body.addEventListener('click', ev => { what: 'clickToLoad', frameURL, }).then(ok => { - if ( ok ) { - self.location.replace(frameURL); - } + if ( ok !== true ) { return; } + self.location.replace(frameURL); }); }); diff --git a/src/js/contentscript.js b/src/js/contentscript.js index 8f3a4cfe4508a..95dbdb601778f 100644 --- a/src/js/contentscript.js +++ b/src/js/contentscript.js @@ -460,28 +460,6 @@ vAPI.SafeAnimationFrame = class { vAPI.domWatcher = { start, addListener, removeListener }; } -/******************************************************************************/ -/******************************************************************************/ -/******************************************************************************/ - -vAPI.injectScriptlet = function(doc, text) { - if ( !doc ) { return; } - let script, url; - try { - const blob = new self.Blob([ text ], { type: 'text/javascript; charset=utf-8' }); - url = self.URL.createObjectURL(blob); - script = doc.createElement('script'); - script.async = false; - script.src = url; - (doc.head || doc.documentElement || doc).appendChild(script); - } catch (ex) { - } - if ( url ) { - if ( script ) { script.remove(); } - self.URL.revokeObjectURL(url); - } -}; - /******************************************************************************/ /******************************************************************************/ /******************************************************************************* @@ -1298,7 +1276,6 @@ vAPI.DOMFilterer = class { const { noSpecificCosmeticFiltering, noGenericCosmeticFiltering, - scriptletDetails, } = response; vAPI.noSpecificCosmeticFiltering = noSpecificCosmeticFiltering; @@ -1320,14 +1297,6 @@ vAPI.DOMFilterer = class { vAPI.userStylesheet.apply(); } - if ( scriptletDetails && typeof self.uBO_scriptletsInjected !== 'string' ) { - self.uBO_scriptletsInjected = scriptletDetails.filters; - if ( scriptletDetails.mainWorld ) { - vAPI.injectScriptlet(document, scriptletDetails.mainWorld); - vAPI.injectedScripts = scriptletDetails.mainWorld; - } - } - if ( vAPI.domSurveyor ) { if ( Array.isArray(cfeDetails.genericCosmeticHashes) ) { vAPI.domSurveyor.addHashes(cfeDetails.genericCosmeticHashes); diff --git a/src/js/fa-icons.js b/src/js/fa-icons.js index 79968d0811e27..d9d658627bb87 100644 --- a/src/js/fa-icons.js +++ b/src/js/fa-icons.js @@ -78,6 +78,7 @@ export const faIconsInit = (( ) => { [ 'unlink', { viewBox: '0 0 1664 1664', path: 'm 439,1271 -256,256 q -11,9 -23,9 -12,0 -23,-9 -9,-10 -9,-23 0,-13 9,-23 l 256,-256 q 10,-9 23,-9 13,0 23,9 9,10 9,23 0,13 -9,23 z m 169,41 v 320 q 0,14 -9,23 -9,9 -23,9 -14,0 -23,-9 -9,-9 -9,-23 v -320 q 0,-14 9,-23 9,-9 23,-9 14,0 23,9 9,9 9,23 z M 384,1088 q 0,14 -9,23 -9,9 -23,9 H 32 q -14,0 -23,-9 -9,-9 -9,-23 0,-14 9,-23 9,-9 23,-9 h 320 q 14,0 23,9 9,9 9,23 z m 1264,128 q 0,120 -85,203 l -147,146 q -83,83 -203,83 -121,0 -204,-85 L 675,1228 q -21,-21 -42,-56 l 239,-18 273,274 q 27,27 68,27.5 41,0.5 68,-26.5 l 147,-146 q 28,-28 28,-67 0,-40 -28,-68 l -274,-275 18,-239 q 35,21 56,42 l 336,336 q 84,86 84,204 z M 1031,492 792,510 519,236 q -28,-28 -68,-28 -39,0 -68,27 L 236,381 q -28,28 -28,67 0,40 28,68 l 274,274 -18,240 q -35,-21 -56,-42 L 100,652 Q 16,566 16,448 16,328 101,245 L 248,99 q 83,-83 203,-83 121,0 204,85 l 334,335 q 21,21 42,56 z m 633,84 q 0,14 -9,23 -9,9 -23,9 h -320 q -14,0 -23,-9 -9,-9 -9,-23 0,-14 9,-23 9,-9 23,-9 h 320 q 14,0 23,9 9,9 9,23 z M 1120,32 v 320 q 0,14 -9,23 -9,9 -23,9 -14,0 -23,-9 -9,-9 -9,-23 V 32 q 0,-14 9,-23 9,-9 23,-9 14,0 23,9 9,9 9,23 z m 407,151 -256,256 q -11,9 -23,9 -12,0 -23,-9 -9,-10 -9,-23 0,-13 9,-23 l 256,-256 q 10,-9 23,-9 13,0 23,9 9,10 9,23 0,13 -9,23 z' } ], [ 'unlock-alt', { viewBox: '0 0 1152 1536', path: 'm 1056,768 q 40,0 68,28 28,28 28,68 v 576 q 0,40 -28,68 -28,28 -68,28 H 96 Q 56,1536 28,1508 0,1480 0,1440 V 864 q 0,-40 28,-68 28,-28 68,-28 h 32 V 448 Q 128,263 259.5,131.5 391,0 576,0 761,0 892.5,131.5 1024,263 1024,448 q 0,26 -19,45 -19,19 -45,19 h -64 q -26,0 -45,-19 -19,-19 -19,-45 0,-106 -75,-181 -75,-75 -181,-75 -106,0 -181,75 -75,75 -75,181 v 320 z' } ], [ 'upload-alt', { viewBox: '0 0 1664 1600', path: 'm 1280,1408 q 0,-26 -19,-45 -19,-19 -45,-19 -26,0 -45,19 -19,19 -19,45 0,26 19,45 19,19 45,19 26,0 45,-19 19,-19 19,-45 z m 256,0 q 0,-26 -19,-45 -19,-19 -45,-19 -26,0 -45,19 -19,19 -19,45 0,26 19,45 19,19 45,19 26,0 45,-19 19,-19 19,-45 z m 128,-224 v 320 q 0,40 -28,68 -28,28 -68,28 H 96 q -40,0 -68,-28 -28,-28 -28,-68 v -320 q 0,-40 28,-68 28,-28 68,-28 h 427 q 21,56 70.5,92 49.5,36 110.5,36 h 256 q 61,0 110.5,-36 49.5,-36 70.5,-92 h 427 q 40,0 68,28 28,28 28,68 z M 1339,536 q -17,40 -59,40 h -256 v 448 q 0,26 -19,45 -19,19 -45,19 H 704 q -26,0 -45,-19 -19,-19 -19,-45 V 576 H 384 q -42,0 -59,-40 -17,-39 14,-69 L 787,19 q 18,-19 45,-19 27,0 45,19 l 448,448 q 31,30 14,69 z' } ], + [ 'volume-up', { viewBox: '0 0 1664 1422', path: 'm 768,167 v 1088 c 0,35 -29,64 -64,64 -17,0 -33,-7 -45,-19 L 326,967 H 64 C 29,967 0,938 0,903 V 519 C 0,484 29,455 64,455 H 326 L 659,122 c 12,-12 28,-19 45,-19 35,0 64,29 64,64 z m 384,544 c 0,100 -61,197 -155,235 -8,4 -17,5 -25,5 -35,0 -64,-28 -64,-64 0,-76 116,-55 116,-176 0,-121 -116,-100 -116,-176 0,-36 29,-64 64,-64 8,0 17,1 25,5 94,37 155,135 155,235 z m 256,0 c 0,203 -122,392 -310,471 -8,3 -17,5 -25,5 -36,0 -65,-29 -65,-64 0,-28 16,-47 39,-59 27,-14 52,-26 76,-44 99,-72 157,-187 157,-309 0,-122 -58,-237 -157,-309 -24,-18 -49,-30 -76,-44 -23,-12 -39,-31 -39,-59 0,-35 29,-64 64,-64 9,0 18,2 26,5 188,79 310,268 310,471 z m 256,0 c 0,307 -183,585 -465,706 -8,3 -17,5 -26,5 -35,0 -64,-29 -64,-64 0,-29 15,-45 39,-59 14,-8 30,-13 45,-21 28,-15 56,-32 82,-51 164,-121 261,-312 261,-516 0,-204 -97,-395 -261,-516 -26,-19 -54,-36 -82,-51 -15,-8 -31,-13 -45,-21 -24,-14 -39,-30 -39,-59 0,-35 29,-64 64,-64 9,0 18,2 26,5 282,121 465,399 465,706 z' } ], [ 'zoom-in', { viewBox: '0 0 1664 1664', path: 'm 1024,672 v 64 q 0,13 -9.5,22.5 Q 1005,768 992,768 H 768 v 224 q 0,13 -9.5,22.5 -9.5,9.5 -22.5,9.5 h -64 q -13,0 -22.5,-9.5 Q 640,1005 640,992 V 768 H 416 q -13,0 -22.5,-9.5 Q 384,749 384,736 v -64 q 0,-13 9.5,-22.5 Q 403,640 416,640 H 640 V 416 q 0,-13 9.5,-22.5 Q 659,384 672,384 h 64 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 v 224 h 224 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 128,32 Q 1152,519 1020.5,387.5 889,256 704,256 519,256 387.5,387.5 256,519 256,704 256,889 387.5,1020.5 519,1152 704,1152 889,1152 1020.5,1020.5 1152,889 1152,704 Z m 512,832 q 0,53 -37.5,90.5 -37.5,37.5 -90.5,37.5 -54,0 -90,-38 L 1103,1284 Q 924,1408 704,1408 561,1408 430.5,1352.5 300,1297 205.5,1202.5 111,1108 55.5,977.5 0,847 0,704 0,561 55.5,430.5 111,300 205.5,205.5 300,111 430.5,55.5 561,0 704,0 q 143,0 273.5,55.5 130.5,55.5 225,150 94.5,94.5 150,225 55.5,130.5 55.5,273.5 0,220 -124,399 l 343,343 q 37,37 37,90 z' } ], [ 'zoom-out', { viewBox: '0 0 1664 1664', path: 'm 1024,672 v 64 q 0,13 -9.5,22.5 Q 1005,768 992,768 H 416 q -13,0 -22.5,-9.5 Q 384,749 384,736 v -64 q 0,-13 9.5,-22.5 Q 403,640 416,640 h 576 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 128,32 Q 1152,519 1020.5,387.5 889,256 704,256 519,256 387.5,387.5 256,519 256,704 256,889 387.5,1020.5 519,1152 704,1152 889,1152 1020.5,1020.5 1152,889 1152,704 Z m 512,832 q 0,53 -37.5,90.5 -37.5,37.5 -90.5,37.5 -54,0 -90,-38 L 1103,1284 Q 924,1408 704,1408 561,1408 430.5,1352.5 300,1297 205.5,1202.5 111,1108 55.5,977.5 0,847 0,704 0,561 55.5,430.5 111,300 205.5,205.5 300,111 430.5,55.5 561,0 704,0 q 143,0 273.5,55.5 130.5,55.5 225,150 94.5,94.5 150,225 55.5,130.5 55.5,273.5 0,220 -124,399 l 343,343 q 37,37 37,90 z' } ], // See /img/photon.svg diff --git a/src/js/logger-ui.js b/src/js/logger-ui.js index 668435f998807..8c08676fac56f 100644 --- a/src/js/logger-ui.js +++ b/src/js/logger-ui.js @@ -21,6 +21,7 @@ 'use strict'; +import webext from './webext.js'; import { hostnameFromURI } from './uri-utils.js'; import { i18n, i18n$ } from './i18n.js'; import { dom, qs$, qsa$ } from './dom.js'; @@ -33,7 +34,7 @@ import { dom, qs$, qsa$ } from './dom.js'; const messaging = vAPI.messaging; const logger = self.logger = { ownerId: Date.now() }; const logDate = new Date(); -const logDateTimezoneOffset = logDate.getTimezoneOffset() * 60000; +const logDateTimezoneOffset = logDate.getTimezoneOffset() * 60; const loggerEntries = []; const COLUMN_TIMESTAMP = 0; @@ -368,7 +369,7 @@ const createLogSeparator = function(details, text) { separator.textContent = ''; const textContent = []; - logDate.setTime(separator.tstamp - logDateTimezoneOffset); + logDate.setTime((separator.tstamp - logDateTimezoneOffset) * 1000); textContent.push( // cell 0 padTo2(logDate.getUTCHours()) + ':' + @@ -464,7 +465,7 @@ const parseLogEntry = function(details) { const textContent = []; // Cell 0 - logDate.setTime(details.tstamp - logDateTimezoneOffset); + logDate.setTime((details.tstamp - logDateTimezoneOffset) * 1000); textContent.push( padTo2(logDate.getUTCHours()) + ':' + padTo2(logDate.getUTCMinutes()) + ':' + @@ -474,7 +475,13 @@ const parseLogEntry = function(details) { // Cell 1 if ( details.realm === 'message' ) { textContent.push(details.text); - entry.textContent = textContent.join('\x1F'); + if ( details.type ) { + textContent.push(details.type); + } + if ( details.keywords ) { + textContent.push(...details.keywords); + } + entry.textContent = textContent.join('\x1F') + '\x1F'; return entry; } @@ -2052,12 +2059,30 @@ dom.on(document, 'keydown', ev => { } }); - dom.on( - '#netInspector', - 'click', - '.canDetails > span:not(:nth-of-type(4)):not(:nth-of-type(8))', - ev => { toggleOn(ev); } - ); + // This is to detect text selection, in which case the click won't be + // interpreted as a request to open the details of the entry. + let selectionAtMouseDown; + let selectionAtTimer; + dom.on('#netInspector', 'mousedown', '.canDetails *', ev => { + if ( ev.button !== 0 ) { return; } + if ( selectionAtMouseDown !== undefined ) { return; } + selectionAtMouseDown = document.getSelection().toString(); + }); + + dom.on('#netInspector', 'click', '.canDetails *', ev => { + if ( ev.button !== 0 ) { return; } + if ( selectionAtTimer !== undefined ) { + clearTimeout(selectionAtTimer); + } + selectionAtTimer = setTimeout(( ) => { + selectionAtTimer = undefined; + const selectionAsOfNow = document.getSelection().toString(); + const selectionHasChanged = selectionAsOfNow !== selectionAtMouseDown; + selectionAtMouseDown = undefined; + if ( selectionHasChanged && selectionAsOfNow !== '' ) { return; } + toggleOn(ev); + }, 333); + }); dom.on( '#netInspector', @@ -2149,16 +2174,12 @@ const rowFilterer = (( ) => { filters = builtinFilters.concat(userFilters); }; - const filterOne = function(logEntry) { - if ( - logEntry.dead || - selectedTabId !== 0 && - ( - logEntry.tabId === undefined || - logEntry.tabId > 0 && logEntry.tabId !== selectedTabId - ) - ) { - return false; + const filterOne = logEntry => { + if ( logEntry.dead ) { return false; } + if ( selectedTabId !== 0 ) { + if ( logEntry.tabId !== undefined && logEntry.tabId > 0 ) { + if (logEntry.tabId !== selectedTabId ) { return false; } + } } if ( masterFilterSwitch === false || filters.length === 0 ) { @@ -2303,7 +2324,7 @@ const rowJanitor = (( ) => { ? opts.maxEntryCount : 0; const obsolete = typeof opts.maxAge === 'number' - ? Date.now() - opts.maxAge * 60000 + ? Date.now() / 1000 - opts.maxAge * 60 : 0; let i = rowIndex; @@ -2686,7 +2707,7 @@ const loggerStats = (( ) => { if ( beg === 0 ) { continue; } let timeField = text.slice(0, beg); if ( options.time === 'anonymous' ) { - timeField = '+' + Math.round((entry.tstamp - t0) / 1000).toString(); + timeField = '+' + Math.round(entry.tstamp - t0).toString(); } fields.push(timeField); beg += 1; @@ -3020,6 +3041,24 @@ dom.on('#pageSelector', 'change', pageSelectorChanged); dom.on('#netInspector .vCompactToggler', 'click', toggleVCompactView); dom.on('#pause', 'click', pauseNetInspector); +dom.on('#logLevel', 'click', ev => { + const level = dom.cl.toggle(ev.currentTarget, 'active') ? 2 : 1; + webext.tabs.query({ + discarded: false, + url: [ 'http://*/*', 'https://*/*' ], + }).then(tabs => { + for ( const tab of tabs ) { + const { status } = tab; + if ( status !== 'loading' && status !== 'complete' ) { continue; } + webext.tabs.executeScript(tab.id, { + allFrames: true, + file: `/js/scriptlets/scriptlet-loglevel-${level}.js`, + matchAboutBlank: true, + }); + } + }); +}); + dom.on('#netInspector', 'copy', ev => { const selection = document.getSelection(); ev.clipboardData.setData('text/plain', diff --git a/src/js/logger.js b/src/js/logger.js index 5d1114f612ef0..2ff1d67050004 100644 --- a/src/js/logger.js +++ b/src/js/logger.js @@ -23,13 +23,14 @@ /******************************************************************************/ -import { broadcastToAll } from './broadcast.js'; +import { broadcast, broadcastToAll } from './broadcast.js'; /******************************************************************************/ let buffer = null; let lastReadTime = 0; let writePtr = 0; +let lastBoxedEntry = ''; // After 30 seconds without being read, the logger buffer will be considered // unused, and thus disabled. @@ -43,36 +44,48 @@ const janitorTimer = vAPI.defer.create(( ) => { logger.enabled = false; buffer = null; writePtr = 0; + lastBoxedEntry = ''; logger.ownerId = undefined; broadcastToAll({ what: 'loggerDisabled' }); }); -const boxEntry = function(details) { - if ( details.tstamp === undefined ) { - details.tstamp = Date.now(); - } +const boxEntry = details => { + details.tstamp = Date.now() / 1000 | 0; return JSON.stringify(details); }; +const pushOne = box => { + if ( writePtr === buffer.length ) { + buffer.push(box); + } else { + buffer[writePtr] = box; + } + writePtr += 1; +}; + const logger = { enabled: false, ownerId: undefined, - writeOne: function(details) { + writeOne(details) { if ( buffer === null ) { return; } const box = boxEntry(details); - if ( writePtr === buffer.length ) { - buffer.push(box); - } else { - buffer[writePtr] = box; + if ( box === lastBoxedEntry ) { return; } + if ( lastBoxedEntry !== '' ) { + pushOne(lastBoxedEntry); } - writePtr += 1; + lastBoxedEntry = box; }, - readAll: function(ownerId) { + readAll(ownerId) { this.ownerId = ownerId; if ( buffer === null ) { this.enabled = true; buffer = []; janitorTimer.on(logBufferObsoleteAfter); + broadcast({ what: 'loggerEnabled' }); + } + if ( lastBoxedEntry !== '' ) { + pushOne(lastBoxedEntry); + lastBoxedEntry = ''; } const out = buffer.slice(0, writePtr); writePtr = 0; diff --git a/src/js/messaging.js b/src/js/messaging.js index c5e229606d702..ec3f0f4e5ff24 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -716,15 +716,15 @@ const retrieveContentScriptParameters = async function(sender, request) { // https://github.com/uBlockOrigin/uBlock-issues/issues/688#issuecomment-748179731 // For non-network URIs, scriptlet injection is deferred to here. The // effective URL is available here in `request.url`. - if ( logger.enabled || request.needScriptlets ) { - const scriptletDetails = scriptletFilteringEngine.injectNow(request); + if ( logger.enabled ) { + const scriptletDetails = scriptletFilteringEngine.retrieve(request); if ( scriptletDetails !== undefined ) { scriptletFilteringEngine.toLogger(request, scriptletDetails); - if ( request.needScriptlets ) { - response.scriptletDetails = scriptletDetails; - } } } + if ( request.needScriptlets ) { + scriptletFilteringEngine.injectNow(request); + } // https://github.com/NanoMeow/QuickReports/issues/6#issuecomment-414516623 // Inject as early as possible to make the cosmetic logger code less @@ -796,6 +796,17 @@ const onMessage = function(request, sender, callback) { µb.maybeGoodPopup.url = request.url; break; + case 'messageToLogger': + if ( logger.enabled !== true ) { break; } + logger.writeOne({ + tabId: sender.tabId, + realm: 'message', + type: request.type || 'info', + keywords: [ 'scriptlet' ], + text: request.text, + }); + break; + case 'shouldRenderNoscriptTags': if ( pageStore === null ) { break; } const fctxt = µb.filteringContext.fromTabId(sender.tabId); diff --git a/src/js/scriptlet-filtering-core.js b/src/js/scriptlet-filtering-core.js index 021e3af305d0d..f0d9164dc7c64 100644 --- a/src/js/scriptlet-filtering-core.js +++ b/src/js/scriptlet-filtering-core.js @@ -265,10 +265,10 @@ export class ScriptletFilteringEngine { } } - const scriptletGlobals = options.scriptletGlobals || []; + const scriptletGlobals = options.scriptletGlobals || {}; if ( options.debug ) { - scriptletGlobals.push([ 'canDebug', true ]); + scriptletGlobals.canDebug = true; } return { @@ -279,7 +279,7 @@ export class ScriptletFilteringEngine { options.debugScriptlets ? 'debugger;' : ';', '', // For use by scriptlets to share local data among themselves - `const scriptletGlobals = new Map(${JSON.stringify(scriptletGlobals, null, 2)});`, + `const scriptletGlobals = ${JSON.stringify(scriptletGlobals, null, 4)}`, '', scriptletDetails.mainWorld, '', @@ -293,7 +293,7 @@ export class ScriptletFilteringEngine { options.debugScriptlets ? 'debugger;' : ';', '', // For use by scriptlets to share local data among themselves - `const scriptletGlobals = new Map(${JSON.stringify(scriptletGlobals, null, 2)});`, + `const scriptletGlobals = ${JSON.stringify(scriptletGlobals, null, 4)}`, '', scriptletDetails.isolatedWorld, '', diff --git a/src/js/scriptlet-filtering.js b/src/js/scriptlet-filtering.js index 806c870bbfe9b..02ba07bb1b368 100644 --- a/src/js/scriptlet-filtering.js +++ b/src/js/scriptlet-filtering.js @@ -44,16 +44,6 @@ import { const contentScriptRegisterer = new (class { constructor() { this.hostnameToDetails = new Map(); - if ( browser.contentScripts === undefined ) { return; } - this.bc = onBroadcast(msg => { - if ( msg.what !== 'filteringBehaviorChanged' ) { return; } - const direction = msg.direction || 0; - if ( direction > 0 ) { return; } - if ( direction >= 0 && msg.hostname ) { - return this.flush(msg.hostname); - } - this.reset(); - }); } register(hostname, code) { if ( browser.contentScripts === undefined ) { return false; } @@ -133,16 +123,15 @@ const mainWorldInjector = (( ) => { 'json-slot', ');', ]; + const jsonSlot = parts.indexOf('json-slot'); return { - parts, - jsonSlot: parts.indexOf('json-slot'), - assemble: function(hostname, scriptlets, filters) { - this.parts[this.jsonSlot] = JSON.stringify({ + assemble: function(hostname, details) { + parts[jsonSlot] = JSON.stringify({ hostname, - scriptlets, - filters, + scriptlets: details.mainWorld, + filters: details.filters, }); - return this.parts.join(''); + return parts.join(''); }, }; })(); @@ -165,24 +154,53 @@ const isolatedWorldInjector = (( ) => { 'json-slot', ');', ]; + const jsonSlot = parts.indexOf('json-slot'); return { - parts, - jsonSlot: parts.indexOf('json-slot'), - assemble: function(hostname, scriptlets) { - this.parts[this.jsonSlot] = JSON.stringify({ hostname }); - const code = this.parts.join(''); + assemble(hostname, details) { + parts[jsonSlot] = JSON.stringify({ hostname }); + const code = parts.join(''); // Manually substitute noop function with scriptlet wrapper // function, so as to not suffer instances of special // replacement characters `$`,`\` when using String.replace() // with scriptlet code. const match = /function\(\)\{\}/.exec(code); return code.slice(0, match.index) + - scriptlets + + details.isolatedWorld + code.slice(match.index + match[0].length); }, }; })(); +const onScriptletMessageInjector = (( ) => { + const parts = [ + '(', + function(name) { + if ( typeof vAPI !== 'object' ) { return; } + if ( vAPI === null ) { return; } + if ( vAPI.bcSecret ) { return; } + const bcSecret = new self.BroadcastChannel(name); + bcSecret.onmessage = ev => { + if ( self.vAPI && self.vAPI.messaging ) { + self.vAPI.messaging.send('contentscript', ev.data); + } else { + bcSecret.onmessage = null; + } + }; + vAPI.bcSecret = bcSecret; + }.toString(), + ')(', + 'bcSecret-slot', + ');', + ]; + const bcSecretSlot = parts.indexOf('bcSecret-slot'); + return { + assemble(details) { + parts[bcSecretSlot] = JSON.stringify(details.bcSecret); + return parts.join('\n'); + }, + }; +})(); + /******************************************************************************/ export class ScriptletFilteringEngineEx extends ScriptletFilteringEngine { @@ -192,10 +210,26 @@ export class ScriptletFilteringEngineEx extends ScriptletFilteringEngine { this.warSecret = undefined; this.scriptletCache = new MRUCache(32); this.isDevBuild = undefined; - onBroadcast(msg => { - if ( msg.what !== 'hiddenSettingsChanged' ) { return; } - this.scriptletCache.reset(); - this.isDevBuild = undefined; + this.bc = onBroadcast(msg => { + switch ( msg.what ) { + case 'filteringBehaviorChanged': { + const direction = msg.direction || 0; + if ( direction > 0 ) { return; } + if ( direction >= 0 && msg.hostname ) { + return contentScriptRegisterer.flush(msg.hostname); + } + contentScriptRegisterer.reset(); + break; + } + case 'hiddenSettingsChanged': + this.isDevBuild = undefined; + /* fall through */ + case 'loggerEnabled': + case 'loggerDisabled': + this.scriptletCache.reset(); + contentScriptRegisterer.reset(); + break; + } }); } @@ -243,58 +277,82 @@ export class ScriptletFilteringEngineEx extends ScriptletFilteringEngine { this.warSecret = vAPI.warSecret.long(); } + const bcSecret = vAPI.generateSecret(3); + const options = { - scriptletGlobals: [ - [ 'warOrigin', this.warOrigin ], - [ 'warSecret', this.warSecret ], - ], + scriptletGlobals: { + warOrigin: this.warOrigin, + warSecret: this.warSecret, + }, debug: this.isDevBuild, debugScriptlets: µb.hiddenSettings.debugScriptlets, }; + if ( logger.enabled ) { + options.scriptletGlobals.bcSecret = bcSecret; + } scriptletDetails = super.retrieve(request, options); - this.scriptletCache.add(hostname, scriptletDetails || null); + if ( scriptletDetails === undefined ) { + if ( request.nocache !== true ) { + this.scriptletCache.add(hostname, null); + } + return; + } - return scriptletDetails; + const contentScript = []; + if ( scriptletDetails.mainWorld !== '' ) { + contentScript.push(mainWorldInjector.assemble(hostname, scriptletDetails)); + } + if ( scriptletDetails.isolatedWorld !== '' ) { + contentScript.push(isolatedWorldInjector.assemble(hostname, scriptletDetails)); + } + + const cachedScriptletDetails = { + bcSecret, + code: contentScript.join('\n\n'), + filters: scriptletDetails.filters, + }; + + if ( request.nocache !== true ) { + this.scriptletCache.add(hostname, cachedScriptletDetails); + } + + return cachedScriptletDetails; } injectNow(details) { if ( typeof details.frameId !== 'number' ) { return; } + const hostname = hostnameFromURI(details.url); + const request = { tabId: details.tabId, frameId: details.frameId, url: details.url, - hostname: hostnameFromURI(details.url), - domain: undefined, - entity: undefined + hostname, + domain: domainFromHostname(hostname), + entity: entityFromDomain(hostname), }; - request.domain = domainFromHostname(request.hostname); - request.entity = entityFromDomain(request.domain); - const scriptletDetails = this.retrieve(request); if ( scriptletDetails === undefined ) { - contentScriptRegisterer.unregister(request.hostname); + contentScriptRegisterer.unregister(hostname); return; } - const contentScript = []; - if ( µb.hiddenSettings.debugScriptletInjector ) { - contentScript.push('debugger'); - } - const { mainWorld = '', isolatedWorld = '', filters } = scriptletDetails; - if ( mainWorld !== '' ) { - contentScript.push(mainWorldInjector.assemble(request.hostname, mainWorld, filters)); + const contentScript = [ scriptletDetails.code ]; + if ( logger.enabled ) { + contentScript.unshift( + onScriptletMessageInjector.assemble(scriptletDetails) + ); } - if ( isolatedWorld !== '' ) { - contentScript.push(isolatedWorldInjector.assemble(request.hostname, isolatedWorld)); + if ( µb.hiddenSettings.debugScriptletInjector ) { + contentScript.unshift('debugger'); } - const code = contentScript.join('\n\n'); - const isAlreadyInjected = contentScriptRegisterer.register(request.hostname, code); + const isAlreadyInjected = contentScriptRegisterer.register(hostname, code); if ( isAlreadyInjected !== true ) { vAPI.tabs.executeScript(details.tabId, { code, @@ -303,7 +361,6 @@ export class ScriptletFilteringEngineEx extends ScriptletFilteringEngine { runAt: 'document_start', }); } - return scriptletDetails; } diff --git a/src/js/scriptlets/scriptlet-loglevel-1.js b/src/js/scriptlets/scriptlet-loglevel-1.js new file mode 100644 index 0000000000000..296003c81bec6 --- /dev/null +++ b/src/js/scriptlets/scriptlet-loglevel-1.js @@ -0,0 +1,50 @@ +/******************************************************************************* + + uBlock Origin - a comprehensive, efficient content blocker + Copyright (C) 2024-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'; + +/******************************************************************************/ + +(( ) => { + if ( typeof vAPI !== 'object' || vAPI === null ) { return; } + if ( vAPI.bcSecret instanceof self.BroadcastChannel === false ) { return; } + vAPI.bcSecret.postMessage({ what: 'setScriptletLogLevel', level: 1 }); +})(); + + + + + + + + +/******************************************************************************* + + 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; diff --git a/src/js/scriptlets/scriptlet-loglevel-2.js b/src/js/scriptlets/scriptlet-loglevel-2.js new file mode 100644 index 0000000000000..73a8214b01635 --- /dev/null +++ b/src/js/scriptlets/scriptlet-loglevel-2.js @@ -0,0 +1,50 @@ +/******************************************************************************* + + uBlock Origin - a comprehensive, efficient content blocker + Copyright (C) 2024-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'; + +/******************************************************************************/ + +(( ) => { + if ( typeof vAPI !== 'object' || vAPI === null ) { return; } + if ( vAPI.bcSecret instanceof self.BroadcastChannel === false ) { return; } + vAPI.bcSecret.postMessage({ what: 'setScriptletLogLevel', level: 2 }); +})(); + + + + + + + + +/******************************************************************************* + + 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; diff --git a/src/js/scriptlets/should-inject-contentscript.js b/src/js/scriptlets/should-inject-contentscript.js index b9a26581f50d5..94d0cd3f1e571 100644 --- a/src/js/scriptlets/should-inject-contentscript.js +++ b/src/js/scriptlets/should-inject-contentscript.js @@ -29,7 +29,7 @@ (( ) => { try { - let status = vAPI.uBO !== true; + const status = vAPI.uBO !== true; if ( status === false && vAPI.bootstrap ) { self.requestIdleCallback(( ) => vAPI && vAPI.bootstrap()); } diff --git a/src/js/storage.js b/src/js/storage.js index 7847c43b994e5..0a01aaa6e35bd 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -850,7 +850,8 @@ onBroadcast(msg => { let t0 = 0; const onDone = ( ) => { - ubolog(`loadFilterLists() took ${Date.now()-t0} ms`); + const td = Date.now() - t0; + ubolog(`loadFilterLists() took ${td} ms`); staticNetFilteringEngine.freeze(); staticExtFilteringEngine.freeze(); @@ -863,7 +864,7 @@ onBroadcast(msg => { logger.writeOne({ realm: 'message', type: 'info', - text: 'Reloading all filter lists: done' + text: `Reloading all filter lists: done, took ${td} ms` }); broadcast({ diff --git a/src/logger-ui.html b/src/logger-ui.html index d4ccb70b3f0a4..bc70de4258fd5 100644 --- a/src/logger-ui.html +++ b/src/logger-ui.html @@ -74,10 +74,18 @@ +
+ +
infoerror
+
+
+
+ +
@@ -92,7 +100,7 @@
-
00:00:00 ** 3,3optionsinline-script 
+
00:00:00 ** 3,3optionsinline-script 
@@ -109,7 +117,7 @@