Improve scriptlets proxying fetch

Related issue:
https://github.com/uBlockOrigin/uBlock-issues/issues/3892
This commit is contained in:
Raymond Hill 2025-12-09 10:33:20 -05:00
parent d2b680f6bc
commit 13612d1d29
No known key found for this signature in database
GPG key ID: F5630CAE62A14316
6 changed files with 60 additions and 84 deletions

View file

@ -21,6 +21,7 @@
*/
import {
collateFetchArgumentsFn,
matchObjectPropertiesFn,
parsePropertiesToMatchFn,
} from './utils.js';
@ -569,18 +570,8 @@ function jsonEditFetchResponseFn(trusted, jsonq = '') {
const args = context.callArgs;
const fetchPromise = context.reflect();
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) {
safe.uboErr(logPrefix, 'Error:', ex);
}
}
if ( args[1] instanceof Object ) {
objs.push(args[1]);
}
const matched = matchObjectPropertiesFn(propNeedles, ...objs);
const props = collateFetchArgumentsFn(...args);
const matched = matchObjectPropertiesFn(propNeedles, props);
if ( matched === undefined ) { return fetchPromise; }
if ( safe.logLevel > 1 ) {
safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`);
@ -618,6 +609,7 @@ function jsonEditFetchResponseFn(trusted, jsonq = '') {
registerScriptlet(jsonEditFetchResponseFn, {
name: 'json-edit-fetch-response.fn',
dependencies: [
collateFetchArgumentsFn,
JSONPath,
matchObjectPropertiesFn,
parsePropertiesToMatchFn,
@ -716,17 +708,8 @@ function jsonEditFetchRequestFn(trusted, jsonq = '') {
return context.reflect();
}
if ( propNeedles.size !== 0 ) {
const objs = [
resource instanceof Object ? resource : { url: `${resource}` }
];
if ( objs[0] instanceof Request ) {
try {
objs[0] = safe.Request_clone.call(objs[0]);
} catch(ex) {
safe.uboErr(logPrefix, 'Error:', ex);
}
}
const matched = matchObjectPropertiesFn(propNeedles, ...objs);
const props = collateFetchArgumentsFn(resource, options);
const matched = matchObjectPropertiesFn(propNeedles, props);
if ( matched === undefined ) { return context.reflect(); }
if ( safe.logLevel > 1 ) {
safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`);
@ -745,6 +728,7 @@ function jsonEditFetchRequestFn(trusted, jsonq = '') {
registerScriptlet(jsonEditFetchRequestFn, {
name: 'json-edit-fetch-request.fn',
dependencies: [
collateFetchArgumentsFn,
JSONPath,
matchObjectPropertiesFn,
parsePropertiesToMatchFn,
@ -986,18 +970,8 @@ function jsonlEditFetchResponseFn(trusted, jsonq = '') {
const args = context.callArgs;
const fetchPromise = context.reflect();
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) {
safe.uboErr(logPrefix, 'Error:', ex);
}
}
if ( args[1] instanceof Object ) {
objs.push(args[1]);
}
const matched = matchObjectPropertiesFn(propNeedles, ...objs);
const props = collateFetchArgumentsFn(...args);
const matched = matchObjectPropertiesFn(propNeedles, props);
if ( matched === undefined ) { return fetchPromise; }
if ( safe.logLevel > 1 ) {
safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`);
@ -1039,6 +1013,7 @@ function jsonlEditFetchResponseFn(trusted, jsonq = '') {
registerScriptlet(jsonlEditFetchResponseFn, {
name: 'jsonl-edit-fetch-response.fn',
dependencies: [
collateFetchArgumentsFn,
JSONPath,
jsonlEditFn,
matchObjectPropertiesFn,

View file

@ -21,6 +21,7 @@
*/
import {
collateFetchArgumentsFn,
matchObjectPropertiesFn,
parsePropertiesToMatchFn,
} from './utils.js';
@ -86,18 +87,8 @@ function jsonPruneFetchResponse(
const applyHandler = function(target, thisArg, args) {
const fetchPromise = Reflect.apply(target, thisArg, args);
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) {
safe.uboErr(logPrefix, 'Error:', ex);
}
}
if ( args[1] instanceof Object ) {
objs.push(args[1]);
}
const matched = matchObjectPropertiesFn(propNeedles, ...objs);
const props = collateFetchArgumentsFn(...args);
const matched = matchObjectPropertiesFn(propNeedles, props);
if ( matched === undefined ) { return fetchPromise; }
if ( safe.logLevel > 1 ) {
safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`);
@ -148,6 +139,7 @@ function jsonPruneFetchResponse(
registerScriptlet(jsonPruneFetchResponse, {
name: 'json-prune-fetch-response.js',
dependencies: [
collateFetchArgumentsFn,
matchObjectPropertiesFn,
objectPruneFn,
parsePropertiesToMatchFn,

View file

@ -21,6 +21,7 @@
*/
import {
collateFetchArgumentsFn,
generateContentFn,
matchObjectPropertiesFn,
parsePropertiesToMatchFn,
@ -75,32 +76,9 @@ function preventFetchFn(
responseProps.type = { value: responseType };
}
}
const fetchProps = (src, out) => {
if ( typeof src !== 'object' || src === null ) { return; }
const props = [
'body', 'cache', 'credentials', 'duplex', 'headers',
'integrity', 'keepalive', 'method', 'mode', 'priority',
'redirect', 'referrer', 'referrerPolicy', 'signal',
];
for ( const prop of props ) {
if ( src[prop] === undefined ) { continue; }
out[prop] = src[prop];
}
};
const fetchDetails = args => {
const out = {};
if ( args[0] instanceof self.Request ) {
out.url = `${args[0].url}`;
fetchProps(args[0], out);
} else {
out.url = `${args[0]}`;
}
fetchProps(args[1], out);
return out;
};
proxyApplyFn('fetch', function fetch(context) {
const { callArgs } = context;
const details = fetchDetails(callArgs);
const details = collateFetchArgumentsFn(...callArgs);
if ( safe.logLevel > 1 || propsToMatch === '' && responseBody === '' ) {
const out = Array.from(Object.entries(details)).map(a => `${a[0]}:${a[1]}`);
safe.uboLog(logPrefix, `Called: ${out.join('\n')}`);
@ -136,6 +114,7 @@ function preventFetchFn(
registerScriptlet(preventFetchFn, {
name: 'prevent-fetch.fn',
dependencies: [
collateFetchArgumentsFn,
generateContentFn,
matchObjectPropertiesFn,
parsePropertiesToMatchFn,

View file

@ -47,6 +47,7 @@ export function safeSelf() {
'Object_fromEntries': Object.fromEntries.bind(Object),
'Object_getOwnPropertyDescriptor': Object.getOwnPropertyDescriptor.bind(Object),
'Object_hasOwn': Object.hasOwn.bind(Object),
'Object_toString': Object.prototype.toString,
'RegExp': self.RegExp,
'RegExp_test': self.RegExp.prototype.test,
'RegExp_exec': self.RegExp.prototype.exec,

View file

@ -35,6 +35,7 @@ import './replace-argument.js';
import './spoof-css.js';
import {
collateFetchArgumentsFn,
generateContentFn,
getExceptionTokenFn,
getRandomTokenFn,
@ -314,6 +315,7 @@ builtinScriptlets.push({
name: 'replace-fetch-response.fn',
fn: replaceFetchResponseFn,
dependencies: [
'collate-fetch-arguments.fn',
'match-object-properties.fn',
'parse-properties-to-match.fn',
'safe-self.fn',
@ -338,19 +340,8 @@ function replaceFetchResponseFn(
const fetchPromise = Reflect.apply(target, thisArg, args);
if ( pattern === '' ) { return fetchPromise; }
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) {
safe.uboErr(logPrefix, ex);
}
}
if ( args[1] instanceof Object ) {
objs.push(args[1]);
}
const matched = matchObjectPropertiesFn(propNeedles, ...objs);
const props = collateFetchArgumentsFn(...args);
const matched = matchObjectPropertiesFn(propNeedles, props);
if ( matched === undefined ) { return fetchPromise; }
if ( safe.logLevel > 1 ) {
safe.uboLog(logPrefix, `Matched "propsToMatch":\n\t${matched.join('\n\t')}`);

View file

@ -62,6 +62,44 @@ registerScriptlet(getExceptionTokenFn, {
/******************************************************************************/
export function collateFetchArgumentsFn(resource, options) {
const safe = safeSelf();
const props = [
'body', 'cache', 'credentials', 'duplex', 'headers',
'integrity', 'keepalive', 'method', 'mode', 'priority',
'redirect', 'referrer', 'referrerPolicy', 'signal',
];
const out = {};
if ( collateFetchArgumentsFn.collateKnownProps === undefined ) {
collateFetchArgumentsFn.collateKnownProps = (src, out) => {
for ( const prop of props ) {
if ( src[prop] === undefined ) { continue; }
out[prop] = src[prop];
}
};
}
if (
typeof resource !== 'object' ||
safe.Object_toString.call(resource) !== '[object Request]'
) {
out.url = `${resource}`;
} else {
collateFetchArgumentsFn.collateKnownProps(resource, out);
}
if ( typeof options === 'object' && options !== null ) {
collateFetchArgumentsFn.collateKnownProps(options, out);
}
return out;
}
registerScriptlet(collateFetchArgumentsFn, {
name: 'collate-fetch-arguments.fn',
dependencies: [
safeSelf,
],
});
/******************************************************************************/
export function parsePropertiesToMatchFn(propsToMatch, implicit = '') {
const safe = safeSelf();
const needles = new Map();