Skip to content

Commit 885c149

Browse files
authored
Merge pull request #809 from quoid/xhr-upload-property
feat: surpoort `GM.xmlHttpRequest` upload property
2 parents 00216e6 + ca2ab86 commit 885c149

File tree

6 files changed

+121
-7
lines changed

6 files changed

+121
-7
lines changed

README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ Userscripts currently supports the following api methods. All methods are asynch
208208
> For the v4.5.x and earlier versions:
209209
> https://github.com/quoid/userscripts/tree/v4.5.4#api
210210
211-
For API type definitions, please refer to: [`types.d.ts`](https://github.com/userscriptsup/testscripts/blob/bfce18746cd6bcab0616727401fa7ab6ef4086ac/userscripts/types.d.ts)
211+
For API type definitions, please refer to: [`types.d.ts`](https://github.com/userscriptsup/testscripts/blob/f2fcde4b556fa436fe806a44a89afb9eb5dccd0b/userscripts/types.d.ts)
212212

213213
- `GM.addStyle(css)`
214214
- `css: String`
@@ -278,6 +278,19 @@ For API type definitions, please refer to: [`types.d.ts`](https://github.com/use
278278
- `data: String | Blob | ArrayBuffer | TypedArray | DataView | FormData | URLSearchParams` - optional
279279
- `responseType: String` - optional
280280
- refer to [`XMLHttpRequests`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest)
281+
- upload event handlers:
282+
- `upload: Object` - optional
283+
- `onabort: Function` - optional
284+
- `onerror: Function` - optional
285+
- `onload: Function` - optional
286+
- `onloadend: Function` - optional
287+
- `onloadstart: Function` - optional
288+
- `onprogress: Function` - optional
289+
- `ontimeout: Function` - optional
290+
- the progress object passed to the event handlers has the following properties:
291+
- `lengthComputable`
292+
- `loaded`
293+
- `total`
281294
- event handlers:
282295
- `onabort: Function` - optional
283296
- `onerror: Function` - optional

src/ext/background/main.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ async function handleMessage(message, sender) {
364364
// initializing an xhr instance
365365
const xhr = new XMLHttpRequest();
366366
// establish a long-lived port connection to content script
367+
/** @type {import("../global.d.ts").TypeBackground.XHRPort} */
367368
const port = browser.tabs.connect(sender.tab.id, {
368369
name: message.xhrPortName,
369370
});
@@ -465,7 +466,21 @@ async function handleMessage(message, sender) {
465466
// transfer to content script via text and then parse to document
466467
if (responseType === "document") xhr.responseType = "text";
467468
// add required listeners and send result back to the content script
468-
const handlers = details.hasHandlers || {};
469+
if (details.hasUploadHandlers) {
470+
for (const handler of Object.keys(details.hasUploadHandlers)) {
471+
/** @param {ProgressEvent} event */
472+
xhr.upload[handler] = async (event) => {
473+
/** @type {TypeExtMessages.XHRProgress} */
474+
const progress = {
475+
lengthComputable: event.lengthComputable,
476+
loaded: event.loaded,
477+
total: event.total,
478+
};
479+
port.postMessage({ handler, progress });
480+
};
481+
}
482+
}
483+
const handlers = details.hasHandlers ?? {};
469484
for (const handler of Object.keys(handlers)) {
470485
xhr[handler] = async () => {
471486
// can not send xhr through postMessage
@@ -513,7 +528,7 @@ async function handleMessage(message, sender) {
513528
// if onloadend not set in xhr details
514529
// onloadend event won't be passed to content script
515530
// if that happens port DISCONNECT message won't be posted
516-
// if details lacks onloadend attach listener
531+
// so if details lacks onloadend then attach the listener
517532
if (!handlers.onloadend) {
518533
xhr.onloadend = () => {
519534
port.postMessage({ handler: "onloadend" });

src/ext/content-scripts/api.js

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@ async function xhr(details, control, promise) {
296296
url: String(details.url),
297297
user: String(details.user),
298298
hasHandlers: {},
299+
hasUploadHandlers: {},
299300
};
300301
// preprocess data key
301302
try {
@@ -340,6 +341,30 @@ async function xhr(details, control, promise) {
340341
handlers[handler] = details[handler];
341342
}
342343
}
344+
// preprocess upload handlers
345+
/** @type {TypeExtMessages.XHRUploadHandlersObj} */
346+
const uploadHandlers = {};
347+
/** @type {TypeExtMessages.XHRUploadHandlers} */
348+
const XHRUploadHandlers = [
349+
"onabort",
350+
"onerror",
351+
"onload",
352+
"onloadend",
353+
"onloadstart",
354+
"onprogress",
355+
"ontimeout",
356+
];
357+
if (typeof details.upload === "object") {
358+
for (const handler of XHRUploadHandlers) {
359+
if (
360+
handler in XMLHttpRequestEventTarget.prototype &&
361+
typeof details.upload[handler] === "function"
362+
) {
363+
detailsParsed.hasUploadHandlers[handler] = true;
364+
uploadHandlers[handler] = details.upload[handler];
365+
}
366+
}
367+
}
343368
// resolving asynchronous xmlHttpRequest
344369
if (promise) {
345370
detailsParsed.hasHandlers.onloadend = true;
@@ -363,14 +388,26 @@ async function xhr(details, control, promise) {
363388
/**
364389
* port listener, most of the messaging logic goes here
365390
* @type {Parameters<typeof browser.runtime.onConnect.addListener>[0]}
391+
* @param {import("../global.d.ts").TypeContentScripts.XHRPort} port
366392
*/
367393
const listener = (port) => {
368394
if (port.name !== xhrPortName) return;
395+
// handle port messages
369396
port.onMessage.addListener(async (msg) => {
370-
/** @type {TypeExtMessages.XHRHandlers[number]} */
371397
const handler = msg.handler;
398+
// handle upload progress
399+
if (
400+
"progress" in msg &&
401+
detailsParsed.hasUploadHandlers[handler] &&
402+
typeof uploadHandlers[handler] === "function"
403+
) {
404+
// call userscript handler
405+
uploadHandlers[handler](msg.progress);
406+
return;
407+
}
408+
// handle download events
372409
if (
373-
msg.response &&
410+
"response" in msg &&
374411
detailsParsed.hasHandlers[handler] &&
375412
typeof handlers[handler] === "function"
376413
) {
@@ -387,7 +424,7 @@ async function xhr(details, control, promise) {
387424
if (response.readyState === 4 && response.response !== null) {
388425
xhrResponseProcessor(msgResponse, response);
389426
}
390-
// call userscript method
427+
// call userscript handler
391428
handlers[handler](response);
392429
// call the deleted XHR.DONE handlers above
393430
if (response.readyState === 4) {

src/ext/content-scripts/entry-userscripts.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ async function injection() {
198198
// loop through each userscript @grant value, add methods as needed
199199
for (let j = 0; j < grants.length; j++) {
200200
const grant = grants[j];
201-
const method = grant.split(".")[1] || grant.split(".")[0];
201+
const method = grant.startsWith("GM.") ? grant.slice(3) : grant;
202202
// ensure API method exists in USAPI object
203203
if (!Object.keys(USAPI).includes(method)) continue;
204204
// add granted methods

src/ext/global.d.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,31 @@ declare global {
1111
browser: Browser.Browser;
1212
}
1313
}
14+
15+
declare namespace TypeBackground {
16+
interface XHRMessage {
17+
handler: string;
18+
progress?: TypeExtMessages.XHRProgress;
19+
response?: TypeExtMessages.XHRTransportableResponse;
20+
}
21+
22+
interface XHRPort extends Browser.Runtime.Port {
23+
onMessage: Browser.Events.Event<
24+
(message: TypeContentScripts.XHRMessage, port: XHRPort) => void
25+
>;
26+
postMessage: (message: XHRMessage) => void;
27+
}
28+
}
29+
30+
declare namespace TypeContentScripts {
31+
interface XHRMessage {
32+
name: string;
33+
}
34+
35+
interface XHRPort extends Browser.Runtime.Port {
36+
onMessage: Browser.Events.Event<
37+
(message: TypeBackground.XHRMessage, port: XHRPort) => void
38+
>;
39+
postMessage(message: XHRMessage): void;
40+
}
41+
}

src/ext/types.d.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,24 @@ declare namespace TypeExtMessages {
4343
"onloadend",
4444
];
4545

46+
type XHRUploadHandlers = [
47+
"onabort",
48+
"onerror",
49+
"onload",
50+
"onloadend",
51+
"onloadstart",
52+
"onprogress",
53+
"ontimeout",
54+
];
55+
4656
type XHRHandlersObj = {
4757
[handler in XHRHandlers[number]]?: (response: XHRResponse) => void;
4858
};
4959

60+
type XHRUploadHandlersObj = {
61+
[handler in XHRUploadHandlers[number]]?: (response: XHRProgress) => void;
62+
};
63+
5064
interface XHRTransportableDetails {
5165
binary: boolean;
5266
data: XHRProcessedData;
@@ -59,6 +73,7 @@ declare namespace TypeExtMessages {
5973
url: string;
6074
user: string;
6175
hasHandlers: { [handler in XHRHandlers[number]]?: boolean };
76+
hasUploadHandlers: { [handler in XHRUploadHandlers[number]]?: boolean };
6277
}
6378

6479
interface XHRTransportableResponse {
@@ -78,4 +93,10 @@ declare namespace TypeExtMessages {
7893
responseText?: string;
7994
responseXML?: Document;
8095
}
96+
97+
interface XHRProgress {
98+
lengthComputable: boolean;
99+
loaded: number;
100+
total: number;
101+
}
81102
}

0 commit comments

Comments
 (0)