From 2ef2888805db5dad140a2bbe28dfd137d30edee7 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Fri, 19 May 2023 12:55:01 -0400 Subject: [PATCH] Expand/harden some scriptlets Related issue: - https://github.com/uBlockOrigin/uBlock-issues/issues/2615 Expand `set-constant`: 3rd parameter and beyond are tokens which modify the behavior of `set-contant`. Valid tokens: - `interactive`, `end`, `2`: set the constant when the event `DOMContentInteractive` is fired. - `complete`, `idle`, `3`: set the constant when the event `load` is fired. - `asFunction`: the constant will be a function returning the specified value. - `asCallback`: the constant will be a function returning a function returning the specified value. - `asResolved`: the constant will be a promise resolving to the specified value. - `asRejected`: the constant will be a promise failing with the specified value. Harden `no-setimeout-if` and `no-setinterval-if` as per feedback from filter list maintainers. --- assets/resources/scriptlets.js | 62 ++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/assets/resources/scriptlets.js b/assets/resources/scriptlets.js index ea9945b0ea707..092e5bf363a60 100644 --- a/assets/resources/scriptlets.js +++ b/assets/resources/scriptlets.js @@ -130,13 +130,18 @@ builtinScriptlets.push({ }); function runAt(fn, when) { const intFromReadyState = state => { - return ({ - loading: 1, - interactive: 2, - end: 2, - complete: 3, - idle: 3, - })[`${state}`] || 0; + const targets = { + 'loading': 1, + 'interactive': 2, 'end': 2, '2': 2, + 'complete': 3, 'idle': 3, '3': 3, + }; + const tokens = Array.isArray(state) ? state : [ state ]; + for ( const token of tokens ) { + const prop = `${token}`; + if ( targets.hasOwnProperty(prop) === false ) { continue; } + return targets[prop]; + } + return 0; }; const runAt = intFromReadyState(when); if ( intFromReadyState(document.readyState) >= runAt ) { @@ -1030,14 +1035,22 @@ builtinScriptlets.push({ function setConstant( arg1 = '', arg2 = '', - arg3 = 0 + arg3 = '' ) { const details = typeof arg1 !== 'object' - ? { prop: arg1, value: arg2, runAt: parseInt(arg3, 10) || 0 } + ? { prop: arg1, value: arg2 } : arg1; + if ( arg3 !== '' ) { + if ( /^\d$/.test(arg3) ) { + details.options = [ arg3 ]; + } else { + details.options = Array.from(arguments).slice(2); + } + } const { prop: chain = '', value: cValue = '' } = details; if ( typeof chain !== 'string' ) { return; } if ( chain === '' ) { return; } + const options = details.options || []; function setConstant(chain, cValue) { const trappedProp = (( ) => { const pos = chain.lastIndexOf('.'); @@ -1093,13 +1106,22 @@ function setConstant( cValue = cloakFunc(function(){ return true; }); } else if ( cValue === 'falseFunc' ) { cValue = cloakFunc(function(){ return false; }); - } else if ( /^\d+$/.test(cValue) ) { - cValue = parseFloat(cValue); + } else if ( /^-?\d+$/.test(cValue) ) { + cValue = parseInt(cValue); if ( isNaN(cValue) ) { return; } if ( Math.abs(cValue) > 0x7FFF ) { return; } } else { return; } + if ( options.includes('asFunction') ) { + cValue = ( ) => cValue; + } else if ( options.includes('asCallback') ) { + cValue = ( ) => (( ) => cValue); + } else if ( options.includes('asResolved') ) { + cValue = Promise.resolve(cValue); + } else if ( options.includes('asRejected') ) { + cValue = Promise.reject(cValue); + } let aborted = false; const mustAbort = function(v) { if ( aborted ) { return true; } @@ -1193,7 +1215,7 @@ function setConstant( } runAt(( ) => { setConstant(chain, cValue); - }, details.runAt); + }, options); } /******************************************************************************/ @@ -1243,7 +1265,13 @@ function noSetIntervalIf( } } return target.apply(thisArg, args); - } + }, + get(target, prop, receiver) { + if ( prop === 'toString' ) { + return target.toString.bind(target); + } + return Reflect.get(target, prop, receiver); + }, }); } @@ -1294,7 +1322,13 @@ function noSetTimeoutIf( } } return target.apply(thisArg, args); - } + }, + get(target, prop, receiver) { + if ( prop === 'toString' ) { + return target.toString.bind(target); + } + return Reflect.get(target, prop, receiver); + }, }); }