From 869a653fdf2dd4e5b0151530bd58e1059d1318d2 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 25 Jan 2024 12:20:38 -0500 Subject: [PATCH] Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered). --- assets/resources/scriptlets.js | 447 +++++++++--------- platform/common/vapi-background.js | 22 +- src/css/fa-icons.css | 1 + src/css/logger-ui.css | 120 +++-- src/img/fontawesome/fontawesome-defs.svg | 1 + src/js/background.js | 2 +- src/js/benchmarks.js | 1 + src/js/click2load.js | 5 +- src/js/contentscript.js | 31 -- src/js/fa-icons.js | 1 + src/js/logger-ui.js | 83 +++- src/js/logger.js | 37 +- src/js/messaging.js | 21 +- src/js/scriptlet-filtering-core.js | 8 +- src/js/scriptlet-filtering.js | 161 +++++-- src/js/scriptlets/scriptlet-loglevel-1.js | 50 ++ src/js/scriptlets/scriptlet-loglevel-2.js | 50 ++ .../scriptlets/should-inject-contentscript.js | 2 +- src/js/storage.js | 5 +- src/logger-ui.html | 12 +- 20 files changed, 641 insertions(+), 419 deletions(-) create mode 100644 src/js/scriptlets/scriptlet-loglevel-1.js create mode 100644 src/js/scriptlets/scriptlet-loglevel-2.js diff --git a/assets/resources/scriptlets.js b/assets/resources/scriptlets.js index c87e5d945..8cd1aa2e9 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 0d6fcdd5d..54148039e 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 f6d517d40..42cfa7f40 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 8e5065c56..fc35341cc 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) { +#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 > .fields > span:nth-of-type(8) { position: relative; } -#vwRenderer .logEntry > div > span:nth-of-type(7) { - } -#vwRenderer #vwContent .logEntry > div > span:nth-of-type(7) { - } -#vwRenderer .logEntry > div > 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 75bc67ff9..e457794f6 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 578d8a65c..e8fab0513 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 8792f0359..f3fc53d9e 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 42b7525e0..b441d973e 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 8f3a4cfe4..95dbdb601 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 79968d081..d9d658627 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 668435f99..8c08676fa 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 5d1114f61..2ff1d6705 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 c5e229606..ec3f0f4e5 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 021e3af30..f0d9164dc 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 806c870bb..02ba07bb1 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 = []; + const contentScript = [ scriptletDetails.code ]; + if ( logger.enabled ) { + contentScript.unshift( + onScriptletMessageInjector.assemble(scriptletDetails) + ); + } if ( µb.hiddenSettings.debugScriptletInjector ) { - contentScript.push('debugger'); + contentScript.unshift('debugger'); } - const { mainWorld = '', isolatedWorld = '', filters } = scriptletDetails; - if ( mainWorld !== '' ) { - contentScript.push(mainWorldInjector.assemble(request.hostname, mainWorld, filters)); - } - if ( isolatedWorld !== '' ) { - contentScript.push(isolatedWorldInjector.assemble(request.hostname, isolatedWorld)); - } - 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 000000000..296003c81 --- /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 000000000..73a8214b0 --- /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 b9a26581f..94d0cd3f1 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 7847c43b9..0a01aaa6e 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 d4ccb70b3..bc70de425 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 @@