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

View File

@ -47,6 +47,8 @@ const scriptletDB = new StaticExtFilteringHostnameDB(1);
let acceptedCount = 0; let acceptedCount = 0;
let discardedCount = 0; let discardedCount = 0;
let isDevBuild;
const scriptletFilteringEngine = { const scriptletFilteringEngine = {
get acceptedCount() { get acceptedCount() {
return acceptedCount; return acceptedCount;
@ -386,6 +388,15 @@ scriptletFilteringEngine.retrieve = function(request, options = {}) {
if ( cacheDetails.code === '' ) { return; } 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 = [ const out = [
'(function() {', '(function() {',
'// >>>> start of private namespace', '// >>>> start of private namespace',
@ -393,7 +404,7 @@ scriptletFilteringEngine.retrieve = function(request, options = {}) {
µb.hiddenSettings.debugScriptlets ? 'debugger;' : ';', µb.hiddenSettings.debugScriptlets ? 'debugger;' : ';',
'', '',
// For use by scriptlets to share local data among themselves // 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, cacheDetails.code,
'', '',