Add logging ability to `acs` scriptlet, for the benefit of filter list

maintainers.

To enable logging, use the JSON approach to pass parameters to
`acs` scriptlet. Example:

    ..##+js(acs, { "target": "document.oncontextmenu", "log": true })

Whereas "target", "needle", and "context" correspond to their
respective positional argument. Using JSON form to pass parameters
allows to specify extra paramters to facilitate debugging of that
scriptlet:

- `"log": true` => output useful information at the dev console.
- `"debug": true` => break at key locations in the scriptlet.

The added logging/debugging capabilities work only in the dev build
of uBO or if the advanced setting `filterAuthorMode` is set to
`true`.
This commit is contained in:
Raymond Hill 2023-04-02 12:01:58 -04:00
parent 95bd52d01f
commit edbe96a401
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
2 changed files with 80 additions and 31 deletions

View File

@ -49,6 +49,12 @@ function safeSelf() {
'RegExp': self.RegExp,
'RegExp_test': self.RegExp.prototype.test,
'RegExp_exec': self.RegExp.prototype.exec,
'safeLog': console.log.bind(console),
'uboLog': function(msg) {
if ( msg !== '' ) {
this.safeLog(`[uBO] ${msg}`);
}
},
};
scriptletGlobals.set('safeSelf', safe);
return safe;
@ -90,6 +96,28 @@ function getExceptionToken() {
return token;
}
/******************************************************************************/
builtinScriptlets.push({
name: 'should-debug.fn',
fn: shouldDebug,
});
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;
}
/*******************************************************************************
Injectable scriptlets
@ -105,6 +133,9 @@ builtinScriptlets.push({
dependencies: [
'pattern-to-regex.fn',
'get-exception-token.fn',
'safe-self.fn',
'should-debug.fn',
'should-log.fn',
],
});
// Issues to mind before changing anything:
@ -117,9 +148,10 @@ function abortCurrentScript(
const details = typeof arg1 !== 'object'
? { target: arg1, needle: arg2, context: arg3 }
: arg1;
const { target, needle, context } = details;
const { target = '', needle = '', context = '' } = details;
if ( typeof target !== 'string' ) { return; }
if ( target === '' ) { return; }
const safe = safeSelf();
const reNeedle = patternToRegex(needle);
const reContext = patternToRegex(context);
const thisScript = document.currentScript;
@ -141,6 +173,8 @@ function abortCurrentScript(
value = owner[prop];
desc = undefined;
}
const log = shouldLog(details);
const debug = shouldDebug(details);
const exceptionToken = getExceptionToken();
const scriptTexts = new WeakMap();
const getScriptText = elem => {
@ -165,13 +199,19 @@ function abortCurrentScript(
return text;
};
const validate = ( ) => {
if ( debug ) { debugger; } // jshint ignore: line
const e = document.currentScript;
if ( e instanceof HTMLScriptElement === false ) { return; }
if ( e === thisScript ) { return; }
if ( e.src !== '' && log ) { safe.uboLog(`src: ${e.src}`); }
if ( reContext.test(e.src) === false ) { return; }
if ( reNeedle.test(getScriptText(e)) === false ) { return; }
const scriptText = getScriptText(e);
if ( log ) { safe.uboLog(`script text: ${scriptText}`); }
if ( reNeedle.test(scriptText) === false ) { return; }
throw new ReferenceError(exceptionToken);
};
if ( debug ) { debugger; } // jshint ignore: line
try {
Object.defineProperty(owner, prop, {
get: function() {
validate();
@ -188,6 +228,9 @@ function abortCurrentScript(
}
}
});
} catch(ex) {
if ( log ) { safe.uboLog(ex); }
}
}
/******************************************************************************/
@ -298,7 +341,6 @@ function abortOnStackTrace(
const safe = safeSelf();
const reNeedle = patternToRegex(needle);
const exceptionToken = getExceptionToken();
const log = console.log.bind(console);
const ErrorCtor = self.Error;
const mustAbort = function(err) {
let docURL = self.location.href;
@ -336,7 +378,7 @@ function abortOnStackTrace(
logLevel === '2' && r ||
logLevel === '3' && !r
) {
log(stack.replace(/\t/g, '\n'));
safe.uboLog(stack.replace(/\t/g, '\n'));
}
return r;
};
@ -392,6 +434,8 @@ builtinScriptlets.push({
dependencies: [
'pattern-to-regex.fn',
'safe-self.fn',
'should-debug.fn',
'should-log.fn',
],
});
// https://github.com/uBlockOrigin/uAssets/issues/9123#issuecomment-848255120
@ -408,7 +452,8 @@ function addEventListenerDefuser(
const safe = safeSelf();
const reType = patternToRegex(type);
const rePattern = patternToRegex(pattern);
const logfn = console.log.bind(console);
const log = shouldLog(details);
const debug = shouldDebug(details);
const proto = self.EventTarget.prototype;
proto.addEventListener = new Proxy(proto.addEventListener, {
apply: function(target, thisArg, args) {
@ -422,17 +467,10 @@ function addEventListenerDefuser(
const matchesHandler = safe.RegExp_test.call(rePattern, handler);
const matchesEither = matchesType || matchesHandler;
const matchesBoth = matchesType && matchesHandler;
if (
details.log === 1 && matchesBoth ||
details.log === 2 && matchesEither ||
details.log === 3
) {
logfn(`uBO: addEventListener('${type}', ${handler})`);
if ( log === 1 && matchesBoth || log === 2 && matchesEither || log === 3 ) {
safe.uboLog(`addEventListener('${type}', ${handler})`);
}
if (
details.debug === 1 && matchesBoth ||
details.debug === 2 && matchesEither
) {
if ( debug === 1 && matchesBoth || debug === 2 && matchesEither ) {
debugger; // jshint ignore:line
}
if ( matchesBoth ) { return; }

View File

@ -47,6 +47,8 @@ const scriptletDB = new StaticExtFilteringHostnameDB(1);
let acceptedCount = 0;
let discardedCount = 0;
let isDevBuild;
const scriptletFilteringEngine = {
get acceptedCount() {
return acceptedCount;
@ -386,6 +388,15 @@ scriptletFilteringEngine.retrieve = function(request, options = {}) {
if ( cacheDetails.code === '' ) { return; }
const scriptletGlobals = [];
if ( isDevBuild === undefined ) {
isDevBuild = vAPI.webextFlavor.soup.has('devbuild');
}
if ( isDevBuild || µb.hiddenSettings.filterAuthorMode ) {
scriptletGlobals.push([ 'canDebug', true ]);
}
const out = [
'(function() {',
'// >>>> start of private namespace',
@ -393,7 +404,7 @@ scriptletFilteringEngine.retrieve = function(request, options = {}) {
µb.hiddenSettings.debugScriptlets ? 'debugger;' : ';',
'',
// For use by scriptlets to share local data among themselves
'const scriptletGlobals = new Map();',
`const scriptletGlobals = new Map(${JSON.stringify(scriptletGlobals, null, 2)});`,
'',
cacheDetails.code,
'',