Skip to content

Commit 0882bb9

Browse files
committed
refactor: adjust xhr code logic and sequence
1 parent e653d40 commit 0882bb9

File tree

3 files changed

+57
-54
lines changed

3 files changed

+57
-54
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,6 @@ Userscripts currently supports the following api methods. All methods are asynch
280280
- `status`
281281
- `statusText`
282282
- `timeout`
283-
- `withCredentials`
284283
- `responseText` (when `responseType` is `text`)
285284
- returns an object with a single property, `abort`, which is a `Function`
286285
- usage: `const foo = GM.xmlHttpRequest({...});` ... `foo.abort();` to abort the request

src/ext/background/main.js

Lines changed: 50 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -392,31 +392,51 @@ async function handleMessage(message, sender) {
392392
return { status: "fulfilled", result };
393393
}
394394
case "API_XHR": {
395-
// parse details and set up for XMLHttpRequest
395+
// initializing an xhr instance
396+
const xhr = new XMLHttpRequest();
397+
// establish a long-lived port connection to content script
398+
const port = browser.tabs.connect(sender.tab.id, {
399+
name: message.xhrPortName,
400+
});
401+
// receive messages from content script and process them
402+
port.onMessage.addListener((msg) => {
403+
if (msg.name === "ABORT") xhr.abort();
404+
if (msg.name === "DISCONNECT") port.disconnect();
405+
});
406+
// handle port disconnect and clean tasks
407+
port.onDisconnect.addListener((p) => {
408+
if (p?.error) {
409+
console.error(
410+
`port disconnected due to an error: ${p.error.message}`,
411+
);
412+
}
413+
});
414+
// parse details and set up for xhr instance
396415
const details = message.details;
397-
const method = details.method ? details.method : "GET";
416+
const method = details.method || "GET";
398417
const user = details.user || null;
399418
const password = details.password || null;
400419
let body = details.data || null;
401-
if (body != null && details.binary != null) {
420+
if (typeof body === "string" && details.binary) {
402421
const len = body.length;
403422
const arr = new Uint8Array(len);
404423
for (let i = 0; i < len; i++) {
405424
arr[i] = body.charCodeAt(i);
406425
}
407426
body = new Blob([arr], { type: "text/plain" });
408427
}
409-
// establish a long-lived port connection to content script
410-
const port = browser.tabs.connect(sender.tab.id, {
411-
name: message.xhrPortName,
412-
});
413-
// set up XMLHttpRequest
414-
const xhr = new XMLHttpRequest();
415-
xhr.withCredentials = details.user && details.password;
416-
xhr.timeout = details.timeout || 0;
417-
if (details.overrideMimeType) {
418-
xhr.overrideMimeType(details.overrideMimeType);
419-
}
428+
429+
// xhr instances automatically filter out unexpected user values
430+
xhr.timeout = details.timeout;
431+
xhr.responseType = details.responseType;
432+
// record parsed values for subsequent use
433+
const responseType = xhr.responseType;
434+
// avoid unexpected behavior of legacy defaults such as parsing XML
435+
if (responseType === "") xhr.responseType = "text";
436+
// transfer to content script via arraybuffer and then parse to blob
437+
if (responseType === "blob") xhr.responseType = "arraybuffer";
438+
// transfer to content script via text and then parse to document
439+
if (responseType === "document") xhr.responseType = "text";
420440
// add required listeners and send result back to the content script
421441
for (const e of message.events) {
422442
if (!details[e]) continue;
@@ -428,12 +448,11 @@ async function handleMessage(message, sender) {
428448
readyState: xhr.readyState,
429449
response: xhr.response,
430450
responseHeaders: xhr.getAllResponseHeaders(),
431-
responseType: xhr.responseType,
451+
responseType,
432452
responseURL: xhr.responseURL,
433453
status: xhr.status,
434454
statusText: xhr.statusText,
435455
timeout: xhr.timeout,
436-
withCredentials: xhr.withCredentials,
437456
};
438457
// get content-type when headers received
439458
if (xhr.readyState >= xhr.HEADERS_RECEIVED) {
@@ -442,45 +461,17 @@ async function handleMessage(message, sender) {
442461
// only process when xhr is complete and data exist
443462
if (xhr.readyState === xhr.DONE && xhr.response !== null) {
444463
// need to convert arraybuffer data to postMessage
445-
if (xhr.responseType === "arraybuffer") {
446-
/** @type {ArrayBuffer} */
464+
if (
465+
xhr.responseType === "arraybuffer" &&
466+
xhr.response instanceof ArrayBuffer
467+
) {
447468
const buffer = xhr.response;
448469
x.response = Array.from(new Uint8Array(buffer));
449470
}
450471
}
451472
port.postMessage({ name: e, event, response: x });
452473
};
453474
}
454-
xhr.open(method, details.url, true, user, password);
455-
xhr.responseType = details.responseType;
456-
// avoid unexpected behavior of legacy defaults such as parsing XML
457-
if (xhr.responseType === "") xhr.responseType = "text";
458-
// transfer to content script via arraybuffer and then parse to blob
459-
if (xhr.responseType === "blob") xhr.responseType = "arraybuffer";
460-
// transfer to content script via text and then parse to document
461-
if (xhr.responseType === "document") xhr.responseType = "text";
462-
if (details.headers) {
463-
for (const key in details.headers) {
464-
if (!key.startsWith("Proxy-") && !key.startsWith("Sec-")) {
465-
const val = details.headers[key];
466-
xhr.setRequestHeader(key, val);
467-
}
468-
}
469-
}
470-
// receive messages from content script and process them
471-
port.onMessage.addListener((msg) => {
472-
if (msg.name === "ABORT") xhr.abort();
473-
if (msg.name === "DISCONNECT") port.disconnect();
474-
});
475-
// handle port disconnect and clean tasks
476-
port.onDisconnect.addListener((p) => {
477-
if (p?.error) {
478-
console.error(
479-
`port disconnected due to an error: ${p.error.message}`,
480-
);
481-
}
482-
});
483-
xhr.send(body);
484475
// if onloadend not set in xhr details
485476
// onloadend event won't be passed to content script
486477
// if that happens port DISCONNECT message won't be posted
@@ -490,6 +481,17 @@ async function handleMessage(message, sender) {
490481
port.postMessage({ name: "onloadend", event });
491482
};
492483
}
484+
if (details.overrideMimeType) {
485+
xhr.overrideMimeType(details.overrideMimeType);
486+
}
487+
xhr.open(method, details.url, true, user, password);
488+
// must set headers after `xhr.open()`, but before `xhr.send()`
489+
if (typeof details.headers === "object") {
490+
for (const [key, val] of Object.entries(details.headers)) {
491+
xhr.setRequestHeader(key, val);
492+
}
493+
}
494+
xhr.send(body);
493495
return { status: "fulfilled" };
494496
}
495497
case "REFRESH_DNR_RULES": {

src/ext/content-scripts/api.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,10 @@ function xhr(details) {
129129
const response = {
130130
abort: () => console.error("xhr has not yet been initialized"),
131131
};
132-
// port listener, most of the messaging logic goes here
132+
/**
133+
* port listener, most of the messaging logic goes here
134+
* @type {Parameters<typeof browser.runtime.onConnect.addListener>[0]}
135+
*/
133136
const listener = (port) => {
134137
if (port.name !== xhrPortName) return;
135138
port.onMessage.addListener(async (msg) => {
@@ -167,7 +170,7 @@ function xhr(details) {
167170
*/
168171
// only process when xhr is complete and data exist
169172
if (r.readyState === 4 && r.response !== null) {
170-
if (r.responseType === "arraybuffer") {
173+
if (r.responseType === "arraybuffer" && Array.isArray(r.response)) {
171174
// arraybuffer responses had their data converted in background
172175
// convert it back to arraybuffer
173176
try {
@@ -176,7 +179,7 @@ function xhr(details) {
176179
console.error("error parsing xhr arraybuffer", err);
177180
}
178181
}
179-
if (r.responseType === "blob") {
182+
if (r.responseType === "blob" && Array.isArray(r.response)) {
180183
// blob responses had their data converted in background
181184
// convert it back to blob
182185
try {
@@ -187,7 +190,7 @@ function xhr(details) {
187190
console.error("error parsing xhr blob", err);
188191
}
189192
}
190-
if (r.responseType === "document") {
193+
if (r.responseType === "document" && typeof r.response === "string") {
191194
// document responses had their data converted in background
192195
// convert it back to document
193196
try {
@@ -211,7 +214,6 @@ function xhr(details) {
211214
port.postMessage({ name: "DISCONNECT" });
212215
}
213216
});
214-
215217
// handle port disconnect and clean tasks
216218
port.onDisconnect.addListener((p) => {
217219
if (p?.error) {

0 commit comments

Comments
 (0)