mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-10 01:02:08 +01:00
Improve scriptlet helper proxy-apply
Related issue: https://github.com/uBlockOrigin/uBlock-issues/issues/3378
This commit is contained in:
parent
ef1e134460
commit
547fae4842
1 changed files with 104 additions and 83 deletions
|
@ -1488,26 +1488,44 @@ function proxyApplyFn(
|
||||||
}
|
}
|
||||||
const fn = context[prop];
|
const fn = context[prop];
|
||||||
if ( typeof fn !== 'function' ) { return; }
|
if ( typeof fn !== 'function' ) { return; }
|
||||||
|
if ( proxyApplyFn.CtorContext === undefined ) {
|
||||||
|
proxyApplyFn.CtorContext = class {
|
||||||
|
constructor(callFn, callArgs) {
|
||||||
|
this.callFn = callFn;
|
||||||
|
this.callArgs = callArgs;
|
||||||
|
}
|
||||||
|
reflect() {
|
||||||
|
return Reflect.construct(this.callFn, this.callArgs);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
proxyApplyFn.ApplyContext = class {
|
||||||
|
constructor(callFn, thisArg, callArgs) {
|
||||||
|
this.callFn = callFn;
|
||||||
|
this.thisArg = thisArg;
|
||||||
|
this.callArgs = callArgs;
|
||||||
|
}
|
||||||
|
reflect() {
|
||||||
|
return Reflect.apply(this.callFn, this.thisArg, this.callArgs);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
const fnStr = fn.toString();
|
const fnStr = fn.toString();
|
||||||
const toString = (function toString() { return fnStr; }).bind(null);
|
const toString = (function toString() { return fnStr; }).bind(null);
|
||||||
if ( fn.prototype && fn.prototype.constructor === fn ) {
|
const proxyDetails = {
|
||||||
context[prop] = new Proxy(fn, {
|
apply(target, thisArg, args) {
|
||||||
construct: handler,
|
return handler(new proxyApplyFn.ApplyContext(target, thisArg, args));
|
||||||
get(target, prop, receiver) {
|
},
|
||||||
if ( prop === 'toString' ) { return toString; }
|
|
||||||
return Reflect.get(target, prop, receiver);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return (...args) => Reflect.construct(...args);
|
|
||||||
}
|
|
||||||
context[prop] = new Proxy(fn, {
|
|
||||||
apply: handler,
|
|
||||||
get(target, prop, receiver) {
|
get(target, prop, receiver) {
|
||||||
if ( prop === 'toString' ) { return toString; }
|
if ( prop === 'toString' ) { return toString; }
|
||||||
return Reflect.get(target, prop, receiver);
|
return Reflect.get(target, prop, receiver);
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
return (...args) => Reflect.apply(...args);
|
if ( fn.prototype?.constructor === fn ) {
|
||||||
|
proxyDetails.construct = function(target, args) {
|
||||||
|
return handler(new proxyApplyFn.CtorContext(target, args));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
context[prop] = new Proxy(fn, proxyDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
|
@ -1771,18 +1789,19 @@ function addEventListenerDefuser(
|
||||||
return matchesBoth;
|
return matchesBoth;
|
||||||
};
|
};
|
||||||
runAt(( ) => {
|
runAt(( ) => {
|
||||||
proxyApplyFn('EventTarget.prototype.addEventListener', function(target, thisArg, args) {
|
proxyApplyFn('EventTarget.prototype.addEventListener', function(context) {
|
||||||
|
const { callArgs, thisArg } = context;
|
||||||
let t, h;
|
let t, h;
|
||||||
try {
|
try {
|
||||||
t = String(args[0]);
|
t = String(callArgs[0]);
|
||||||
if ( typeof args[1] === 'function' ) {
|
if ( typeof callArgs[1] === 'function' ) {
|
||||||
h = String(safe.Function_toString(args[1]));
|
h = String(safe.Function_toString(callArgs[1]));
|
||||||
} else if ( typeof args[1] === 'object' && args[1] !== null ) {
|
} else if ( typeof callArgs[1] === 'object' && callArgs[1] !== null ) {
|
||||||
if ( typeof args[1].handleEvent === 'function' ) {
|
if ( typeof callArgs[1].handleEvent === 'function' ) {
|
||||||
h = String(safe.Function_toString(args[1].handleEvent));
|
h = String(safe.Function_toString(callArgs[1].handleEvent));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
h = String(args[1]);
|
h = String(callArgs[1]);
|
||||||
}
|
}
|
||||||
} catch(ex) {
|
} catch(ex) {
|
||||||
}
|
}
|
||||||
|
@ -1791,7 +1810,7 @@ function addEventListenerDefuser(
|
||||||
} else if ( shouldPrevent(thisArg, t, h) ) {
|
} else if ( shouldPrevent(thisArg, t, h) ) {
|
||||||
return safe.uboLog(logPrefix, `Prevented: ${t}\n${h}\n${elementDetails(thisArg)}`);
|
return safe.uboLog(logPrefix, `Prevented: ${t}\n${h}\n${elementDetails(thisArg)}`);
|
||||||
}
|
}
|
||||||
return Reflect.apply(target, thisArg, args);
|
return context.reflect();
|
||||||
});
|
});
|
||||||
}, extraArgs.runAt);
|
}, extraArgs.runAt);
|
||||||
}
|
}
|
||||||
|
@ -2176,10 +2195,11 @@ function noFetchIf(
|
||||||
responseProps.type = { value: responseType };
|
responseProps.type = { value: responseType };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
proxyApplyFn('fetch', function fetch(target, thisArg, args) {
|
proxyApplyFn('fetch', function fetch(context) {
|
||||||
const details = args[0] instanceof self.Request
|
const { callArgs } = context;
|
||||||
? args[0]
|
const details = callArgs[0] instanceof self.Request
|
||||||
: Object.assign({ url: args[0] }, args[1]);
|
? callArgs[0]
|
||||||
|
: Object.assign({ url: callArgs[0] }, callArgs[1]);
|
||||||
let proceed = true;
|
let proceed = true;
|
||||||
try {
|
try {
|
||||||
const props = new Map();
|
const props = new Map();
|
||||||
|
@ -2197,7 +2217,7 @@ function noFetchIf(
|
||||||
safe.uboLog(logPrefix, `Called: ${out.join('\n')}`);
|
safe.uboLog(logPrefix, `Called: ${out.join('\n')}`);
|
||||||
}
|
}
|
||||||
if ( propsToMatch === '' && responseBody === '' ) {
|
if ( propsToMatch === '' && responseBody === '' ) {
|
||||||
return Reflect.apply(target, thisArg, args);
|
return context.reflect();
|
||||||
}
|
}
|
||||||
proceed = needles.length === 0;
|
proceed = needles.length === 0;
|
||||||
for ( const { key, pattern } of needles ) {
|
for ( const { key, pattern } of needles ) {
|
||||||
|
@ -2212,7 +2232,7 @@ function noFetchIf(
|
||||||
} catch(ex) {
|
} catch(ex) {
|
||||||
}
|
}
|
||||||
if ( proceed ) {
|
if ( proceed ) {
|
||||||
return Reflect.apply(target, thisArg, args);
|
return context.reflect();
|
||||||
}
|
}
|
||||||
return generateContentFn(responseBody).then(text => {
|
return generateContentFn(responseBody).then(text => {
|
||||||
safe.uboLog(logPrefix, `Prevented with response "${text}"`);
|
safe.uboLog(logPrefix, `Prevented with response "${text}"`);
|
||||||
|
@ -2520,14 +2540,15 @@ function noSetIntervalIf(
|
||||||
delay = parseInt(delay, 10);
|
delay = parseInt(delay, 10);
|
||||||
}
|
}
|
||||||
const reNeedle = safe.patternToRegex(needle);
|
const reNeedle = safe.patternToRegex(needle);
|
||||||
proxyApplyFn('setInterval', function setInterval(target, thisArg, args) {
|
proxyApplyFn('setInterval', function setInterval(context) {
|
||||||
const a = args[0] instanceof Function
|
const { callArgs } = context;
|
||||||
? String(safe.Function_toString(args[0]))
|
const a = callArgs[0] instanceof Function
|
||||||
: String(args[0]);
|
? String(safe.Function_toString(callArgs[0]))
|
||||||
const b = args[1];
|
: String(callArgs[0]);
|
||||||
|
const b = callArgs[1];
|
||||||
if ( needle === '' && delay === undefined ) {
|
if ( needle === '' && delay === undefined ) {
|
||||||
safe.uboLog(logPrefix, `Called:\n${a}\n${b}`);
|
safe.uboLog(logPrefix, `Called:\n${a}\n${b}`);
|
||||||
return Reflect.apply(target, thisArg, args);
|
return context.reflect();
|
||||||
}
|
}
|
||||||
let defuse;
|
let defuse;
|
||||||
if ( needle !== '' ) {
|
if ( needle !== '' ) {
|
||||||
|
@ -2537,10 +2558,10 @@ function noSetIntervalIf(
|
||||||
defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot;
|
defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot;
|
||||||
}
|
}
|
||||||
if ( defuse ) {
|
if ( defuse ) {
|
||||||
args[0] = function(){};
|
callArgs[0] = function(){};
|
||||||
safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`);
|
safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`);
|
||||||
}
|
}
|
||||||
return Reflect.apply(target, thisArg, args);
|
return context.reflect();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2576,14 +2597,15 @@ function noSetTimeoutIf(
|
||||||
delay = parseInt(delay, 10);
|
delay = parseInt(delay, 10);
|
||||||
}
|
}
|
||||||
const reNeedle = safe.patternToRegex(needle);
|
const reNeedle = safe.patternToRegex(needle);
|
||||||
proxyApplyFn('setTimeout', function setTimeout(target, thisArg, args) {
|
proxyApplyFn('setTimeout', function setTimeout(context) {
|
||||||
const a = args[0] instanceof Function
|
const { callArgs } = context;
|
||||||
? String(safe.Function_toString(args[0]))
|
const a = callArgs[0] instanceof Function
|
||||||
: String(args[0]);
|
? String(safe.Function_toString(callArgs[0]))
|
||||||
const b = args[1];
|
: String(callArgs[0]);
|
||||||
|
const b = callArgs[1];
|
||||||
if ( needle === '' && delay === undefined ) {
|
if ( needle === '' && delay === undefined ) {
|
||||||
safe.uboLog(logPrefix, `Called:\n${a}\n${b}`);
|
safe.uboLog(logPrefix, `Called:\n${a}\n${b}`);
|
||||||
return Reflect.apply(target, thisArg, args);
|
return context.reflect();
|
||||||
}
|
}
|
||||||
let defuse;
|
let defuse;
|
||||||
if ( needle !== '' ) {
|
if ( needle !== '' ) {
|
||||||
|
@ -2593,10 +2615,10 @@ function noSetTimeoutIf(
|
||||||
defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot;
|
defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot;
|
||||||
}
|
}
|
||||||
if ( defuse ) {
|
if ( defuse ) {
|
||||||
args[0] = function(){};
|
callArgs[0] = function(){};
|
||||||
safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`);
|
safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`);
|
||||||
}
|
}
|
||||||
return Reflect.apply(target, thisArg, args);
|
return context.reflect();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2900,25 +2922,26 @@ function noWindowOpenIf(
|
||||||
return decoyElem;
|
return decoyElem;
|
||||||
};
|
};
|
||||||
const noopFunc = function(){};
|
const noopFunc = function(){};
|
||||||
proxyApplyFn('open', function open(target, thisArg, args) {
|
proxyApplyFn('open', function open(context) {
|
||||||
const haystack = args.join(' ');
|
const { callArgs } = context;
|
||||||
|
const haystack = callArgs.join(' ');
|
||||||
if ( rePattern.test(haystack) !== targetMatchResult ) {
|
if ( rePattern.test(haystack) !== targetMatchResult ) {
|
||||||
if ( safe.logLevel > 1 ) {
|
if ( safe.logLevel > 1 ) {
|
||||||
safe.uboLog(logPrefix, `Allowed (${args.join(', ')})`);
|
safe.uboLog(logPrefix, `Allowed (${callArgs.join(', ')})`);
|
||||||
}
|
}
|
||||||
return Reflect.apply(target, thisArg, args);
|
return context.reflect();
|
||||||
}
|
}
|
||||||
safe.uboLog(logPrefix, `Prevented (${args.join(', ')})`);
|
safe.uboLog(logPrefix, `Prevented (${callArgs.join(', ')})`);
|
||||||
if ( delay === '' ) { return null; }
|
if ( delay === '' ) { return null; }
|
||||||
if ( decoy === 'blank' ) {
|
if ( decoy === 'blank' ) {
|
||||||
args[0] = 'about:blank';
|
callArgs[0] = 'about:blank';
|
||||||
const r = Reflect.apply(target, thisArg, args);
|
const r = context.reflect();
|
||||||
setTimeout(( ) => { r.close(); }, autoRemoveAfter);
|
setTimeout(( ) => { r.close(); }, autoRemoveAfter);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
const decoyElem = decoy === 'obj'
|
const decoyElem = decoy === 'obj'
|
||||||
? createDecoy('object', 'data', ...args)
|
? createDecoy('object', 'data', ...callArgs)
|
||||||
: createDecoy('iframe', 'src', ...args);
|
: createDecoy('iframe', 'src', ...callArgs);
|
||||||
let popup = decoyElem.contentWindow;
|
let popup = decoyElem.contentWindow;
|
||||||
if ( typeof popup === 'object' && popup !== null ) {
|
if ( typeof popup === 'object' && popup !== null ) {
|
||||||
Object.defineProperty(popup, 'closed', { value: false });
|
Object.defineProperty(popup, 'closed', { value: false });
|
||||||
|
@ -4850,8 +4873,8 @@ function trustedPruneOutboundObject(
|
||||||
if ( propChain === '' ) { return; }
|
if ( propChain === '' ) { return; }
|
||||||
const safe = safeSelf();
|
const safe = safeSelf();
|
||||||
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
|
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
|
||||||
const reflector = proxyApplyFn(propChain, function(...args) {
|
proxyApplyFn(propChain, function(context) {
|
||||||
const objBefore = reflector(...args);
|
const objBefore = context.reflect();
|
||||||
if ( objBefore instanceof Object === false ) { return objBefore; }
|
if ( objBefore instanceof Object === false ) { return objBefore; }
|
||||||
const objAfter = objectPruneFn(
|
const objAfter = objectPruneFn(
|
||||||
objBefore,
|
objBefore,
|
||||||
|
@ -4884,26 +4907,27 @@ function trustedReplaceArgument(
|
||||||
if ( propChain === '' ) { return; }
|
if ( propChain === '' ) { return; }
|
||||||
const safe = safeSelf();
|
const safe = safeSelf();
|
||||||
const logPrefix = safe.makeLogPrefix('trusted-replace-argument', propChain, argposRaw, argraw);
|
const logPrefix = safe.makeLogPrefix('trusted-replace-argument', propChain, argposRaw, argraw);
|
||||||
const argpos = parseInt(argposRaw, 10) || 0;
|
const argoffset = parseInt(argposRaw, 10) || 0;
|
||||||
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
|
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
|
||||||
const normalValue = validateConstantFn(true, argraw, extraArgs);
|
const normalValue = validateConstantFn(true, argraw, extraArgs);
|
||||||
const reCondition = extraArgs.condition
|
const reCondition = extraArgs.condition
|
||||||
? safe.patternToRegex(extraArgs.condition)
|
? safe.patternToRegex(extraArgs.condition)
|
||||||
: /^/;
|
: /^/;
|
||||||
const reflector = proxyApplyFn(propChain, function(...args) {
|
proxyApplyFn(propChain, function(context) {
|
||||||
|
const { callArgs } = context;
|
||||||
if ( argposRaw === '' ) {
|
if ( argposRaw === '' ) {
|
||||||
safe.uboLog(logPrefix, `Arguments:\n${args.join('\n')}`);
|
safe.uboLog(logPrefix, `Arguments:\n${callArgs.join('\n')}`);
|
||||||
return reflector(...args);
|
return context.reflect();
|
||||||
}
|
}
|
||||||
const arglist = args[args.length-1];
|
const argpos = argoffset >= 0 ? argoffset : callArgs.length - argoffset;
|
||||||
if ( Array.isArray(arglist) === false ) { return reflector(...args); }
|
if ( argpos >= 0 && argpos < callArgs.length ) {
|
||||||
const argBefore = arglist[argpos];
|
const argBefore = callArgs[argpos];
|
||||||
if ( safe.RegExp_test.call(reCondition, argBefore) === false ) {
|
if ( safe.RegExp_test.call(reCondition, argBefore) ) {
|
||||||
return reflector(...args);
|
callArgs[argpos] = normalValue;
|
||||||
|
safe.uboLog(logPrefix, `Replaced argument:\nBefore: ${JSON.stringify(argBefore)}\nAfter: ${normalValue}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
arglist[argpos] = normalValue;
|
return context.reflect();
|
||||||
safe.uboLog(logPrefix, `Replaced argument:\nBefore: ${JSON.stringify(argBefore)}\nAfter: ${normalValue}`);
|
|
||||||
return reflector(...args);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4933,8 +4957,8 @@ function trustedReplaceOutboundText(
|
||||||
: rawReplacement;
|
: rawReplacement;
|
||||||
const extraArgs = safe.getExtraArgs(args);
|
const extraArgs = safe.getExtraArgs(args);
|
||||||
const reCondition = safe.patternToRegex(extraArgs.condition || '');
|
const reCondition = safe.patternToRegex(extraArgs.condition || '');
|
||||||
const reflector = proxyApplyFn(propChain, function(...args) {
|
proxyApplyFn(propChain, function(context) {
|
||||||
const encodedTextBefore = reflector(...args);
|
const encodedTextBefore = context.reflect();
|
||||||
let textBefore = encodedTextBefore;
|
let textBefore = encodedTextBefore;
|
||||||
if ( extraArgs.encoding === 'base64' ) {
|
if ( extraArgs.encoding === 'base64' ) {
|
||||||
try { textBefore = self.atob(encodedTextBefore); }
|
try { textBefore = self.atob(encodedTextBefore); }
|
||||||
|
@ -5011,34 +5035,31 @@ function trustedSuppressNativeMethod(
|
||||||
return { type: 'exact', value: undefined };
|
return { type: 'exact', value: undefined };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const reflector = proxyApplyFn(methodPath, function(...args) {
|
proxyApplyFn(methodPath, function(context) {
|
||||||
|
const { callArgs } = context;
|
||||||
if ( signature === '' ) {
|
if ( signature === '' ) {
|
||||||
safe.uboLog(logPrefix, `Arguments:\n${args.join('\n')}`);
|
safe.uboLog(logPrefix, `Arguments:\n${callArgs.join('\n')}`);
|
||||||
return reflector(...args);
|
return context.reflect();
|
||||||
}
|
}
|
||||||
const arglist = args[args.length-1];
|
if ( callArgs.length < signatureArgs.length ) {
|
||||||
if ( Array.isArray(arglist) === false ) {
|
return context.reflect();
|
||||||
return reflector(...args);
|
|
||||||
}
|
|
||||||
if ( arglist.length < signatureArgs.length ) {
|
|
||||||
return reflector(...args);
|
|
||||||
}
|
}
|
||||||
for ( let i = 0; i < signatureArgs.length; i++ ) {
|
for ( let i = 0; i < signatureArgs.length; i++ ) {
|
||||||
const signatureArg = signatureArgs[i];
|
const signatureArg = signatureArgs[i];
|
||||||
if ( signatureArg === undefined ) { continue; }
|
if ( signatureArg === undefined ) { continue; }
|
||||||
const targetArg = arglist[i];
|
const targetArg = callArgs[i];
|
||||||
if ( signatureArg.type === 'exact' ) {
|
if ( signatureArg.type === 'exact' ) {
|
||||||
if ( targetArg !== signatureArg.value ) {
|
if ( targetArg !== signatureArg.value ) {
|
||||||
return reflector(...args);
|
return context.reflect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ( signatureArg.type === 'pattern' ) {
|
if ( signatureArg.type === 'pattern' ) {
|
||||||
if ( safe.RegExp_test.call(signatureArg.re, targetArg) === false ) {
|
if ( safe.RegExp_test.call(signatureArg.re, targetArg) === false ) {
|
||||||
return reflector(...args);
|
return context.reflect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
safe.uboLog(logPrefix, `Suppressed:\n${args.join('\n')}`);
|
safe.uboLog(logPrefix, `Suppressed:\n${callArgs.join('\n')}`);
|
||||||
if ( how === 'abort' ) {
|
if ( how === 'abort' ) {
|
||||||
throw new ReferenceError();
|
throw new ReferenceError();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue