Skip to content

Commit 1235e4d

Browse files
committed
Improve xmlPrune scriptlet
Related issue: uBlockOrigin/uAssets#34277
1 parent f05fd05 commit 1235e4d

3 files changed

Lines changed: 95 additions & 36 deletions

File tree

src/js/resources/prevent-xhr.js

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ import {
2525
matchObjectPropertiesFn,
2626
parsePropertiesToMatchFn,
2727
} from './utils.js';
28-
import { proxyApplyFn } from './proxy-apply.js';
28+
import {
29+
proxyApplyFn,
30+
proxyToStringFn,
31+
} from './proxy-apply.js';
2932
import { registerScriptlet } from './base.js';
3033
import { safeSelf } from './safe-self.js';
3134

@@ -34,6 +37,62 @@ import { safeSelf } from './safe-self.js';
3437

3538
/******************************************************************************/
3639

40+
export function modifyXhrResponseFn(
41+
propsToMatch = '',
42+
modifierFn = ''
43+
) {
44+
if ( typeof propsToMatch !== 'string' ) { return; }
45+
const safe = safeSelf();
46+
if ( modifyXhrResponseFn.xhrInstances === undefined ) {
47+
modifyXhrResponseFn.xhrInstances = new WeakMap();
48+
}
49+
const propNeedles = parsePropertiesToMatchFn(propsToMatch, 'url');
50+
const NativeXMLHttpRequest = self.XMLHttpRequest;
51+
const TrappedXMLHttpRequest = class XMLHttpRequest extends NativeXMLHttpRequest {
52+
open(method, url, ...args) {
53+
const haystack = { method, url };
54+
if ( propsToMatch === '' ) {
55+
safe.uboLog(`modifyXhrResponseFn() / Called: ${safe.JSON_stringify(haystack, null, 2)}`);
56+
} else if ( matchObjectPropertiesFn(propNeedles, haystack) ) {
57+
modifyXhrResponseFn.xhrInstances.set(this, modifierFn);
58+
}
59+
return super.open(method, url, ...args);
60+
}
61+
get response() {
62+
const modifierFn = modifyXhrResponseFn.xhrInstances.get(this);
63+
return modifierFn
64+
? modifierFn(this, super.response)
65+
: super.response;
66+
}
67+
get responseText() {
68+
const modifierFn = modifyXhrResponseFn.xhrInstances.get(this);
69+
return modifierFn
70+
? modifierFn(this, super.responseText)
71+
: super.responseText;
72+
}
73+
get responseXML() {
74+
const modifierFn = modifyXhrResponseFn.xhrInstances.get(this);
75+
return modifierFn
76+
? modifierFn(this, super.responseXML)
77+
: super.responseXML;
78+
}
79+
};
80+
proxyToStringFn(TrappedXMLHttpRequest.prototype.open, NativeXMLHttpRequest.prototype.open);
81+
proxyToStringFn(TrappedXMLHttpRequest, NativeXMLHttpRequest);
82+
self.XMLHttpRequest = TrappedXMLHttpRequest;
83+
}
84+
registerScriptlet(modifyXhrResponseFn, {
85+
name: 'modify-xhr-response.fn',
86+
dependencies: [
87+
matchObjectPropertiesFn,
88+
parsePropertiesToMatchFn,
89+
proxyToStringFn,
90+
safeSelf,
91+
],
92+
});
93+
94+
/******************************************************************************/
95+
3796
function preventXhrFn(
3897
trusted = false,
3998
propsToMatch = '',

src/js/resources/proxy-apply.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,32 @@ import { registerScriptlet } from './base.js';
2424

2525
/******************************************************************************/
2626

27+
export function proxyToStringFn(proxiedFn, nativeFn) {
28+
if ( proxyToStringFn.proxies === undefined ) {
29+
proxyToStringFn.proxies = new WeakMap();
30+
proxyToStringFn.nativeToString = Function.prototype.toString;
31+
const proxiedToString = new Proxy(Function.prototype.toString, {
32+
apply(target, thisArg) {
33+
let proxied = thisArg;
34+
for(;;) {
35+
const fn = proxyToStringFn.proxies.get(proxied);
36+
if ( fn === undefined ) { break; }
37+
proxied = fn;
38+
}
39+
return proxyToStringFn.nativeToString.call(proxied);
40+
}
41+
});
42+
proxyToStringFn.proxies.set(proxiedToString, proxyToStringFn.nativeToString);
43+
Function.prototype.toString = proxiedToString;
44+
}
45+
proxyToStringFn.proxies.set(proxiedFn, nativeFn);
46+
}
47+
registerScriptlet(proxyToStringFn, {
48+
name: 'proxy-tostring.fn',
49+
});
50+
51+
/******************************************************************************/
52+
2753
export function proxyApplyFn(
2854
target = '',
2955
handler = '',

src/js/resources/scriptlets.js

Lines changed: 9 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ import './prevent-fetch.js';
3636
import './prevent-innerHTML.js';
3737
import './prevent-navigation.js';
3838
import './prevent-settimeout.js';
39-
import './prevent-xhr.js';
4039
import './replace-argument.js';
4140
import './spoof-css.js';
4241

@@ -54,6 +53,7 @@ import { runAt, runAtHtmlElementFn } from './run-at.js';
5453
import { getAllCookiesFn } from './cookie.js';
5554
import { getAllLocalStorageFn } from './localstorage.js';
5655
import { matchesStackTraceFn } from './stack-trace.js';
56+
import { modifyXhrResponseFn } from './prevent-xhr.js';
5757
import { proxyApplyFn } from './proxy-apply.js';
5858
import { registeredScriptlets } from './base.js';
5959
import { safeSelf } from './safe-self.js';
@@ -984,6 +984,7 @@ builtinScriptlets.push({
984984
name: 'xml-prune.js',
985985
fn: xmlPrune,
986986
dependencies: [
987+
'modify-xhr-response.fn',
987988
'safe-self.fn',
988989
],
989990
});
@@ -1089,41 +1090,14 @@ function xmlPrune(
10891090
});
10901091
}
10911092
});
1092-
self.XMLHttpRequest.prototype.open = new Proxy(self.XMLHttpRequest.prototype.open, {
1093-
apply: async (target, thisArg, args) => {
1094-
if ( reUrl.test(urlFromArg(args[1])) === false ) {
1095-
return Reflect.apply(target, thisArg, args);
1096-
}
1097-
thisArg.addEventListener('readystatechange', function() {
1098-
if ( thisArg.readyState !== 4 ) { return; }
1099-
const type = thisArg.responseType;
1100-
if (
1101-
type === 'document' ||
1102-
type === '' && thisArg.responseXML instanceof XMLDocument
1103-
) {
1104-
pruneFromDoc(thisArg.responseXML);
1105-
const serializer = new XMLSerializer();
1106-
const textout = serializer.serializeToString(thisArg.responseXML);
1107-
Object.defineProperty(thisArg, 'responseText', { value: textout });
1108-
if ( typeof thisArg.response === 'string' ) {
1109-
Object.defineProperty(thisArg, 'response', { value: textout });
1110-
}
1111-
return;
1112-
}
1113-
if (
1114-
type === 'text' ||
1115-
type === '' && typeof thisArg.responseText === 'string'
1116-
) {
1117-
const textin = thisArg.responseText;
1118-
const textout = pruneFromText(textin);
1119-
if ( textout === textin ) { return; }
1120-
Object.defineProperty(thisArg, 'response', { value: textout });
1121-
Object.defineProperty(thisArg, 'responseText', { value: textout });
1122-
return;
1123-
}
1124-
});
1125-
return Reflect.apply(target, thisArg, args);
1093+
modifyXhrResponseFn(urlPattern, (xhr, before) => {
1094+
if ( before instanceof XMLDocument ) {
1095+
return pruneFromDoc(before);
1096+
}
1097+
if ( typeof before === 'string' ) {
1098+
return pruneFromText(before);
11261099
}
1100+
return before;
11271101
});
11281102
}
11291103

0 commit comments

Comments
 (0)