Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ Userscripts currently supports the following api methods. All methods are asynch
> For the v4.5.x and earlier versions:
> https://github.com/quoid/userscripts/tree/v4.5.4#api

For API type definitions, please refer to: [`types.d.ts`](https://github.com/userscriptsup/testscripts/blob/bfce18746cd6bcab0616727401fa7ab6ef4086ac/userscripts/types.d.ts)
For API type definitions, please refer to: [`types.d.ts`](https://github.com/userscriptsup/testscripts/blob/f2fcde4b556fa436fe806a44a89afb9eb5dccd0b/userscripts/types.d.ts)

- `GM.addStyle(css)`
- `css: String`
Expand Down Expand Up @@ -278,6 +278,19 @@ For API type definitions, please refer to: [`types.d.ts`](https://github.com/use
- `data: String | Blob | ArrayBuffer | TypedArray | DataView | FormData | URLSearchParams` - optional
- `responseType: String` - optional
- refer to [`XMLHttpRequests`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest)
- upload event handlers:
- `upload: Object` - optional
- `onabort: Function` - optional
- `onerror: Function` - optional
- `onload: Function` - optional
- `onloadend: Function` - optional
- `onloadstart: Function` - optional
- `onprogress: Function` - optional
- `ontimeout: Function` - optional
- the progress object passed to the event handlers has the following properties:
- `lengthComputable`
- `loaded`
- `total`
- event handlers:
- `onabort: Function` - optional
- `onerror: Function` - optional
Expand Down
19 changes: 17 additions & 2 deletions src/ext/background/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ async function handleMessage(message, sender) {
// initializing an xhr instance
const xhr = new XMLHttpRequest();
// establish a long-lived port connection to content script
/** @type {import("../global.d.ts").TypeBackground.XHRPort} */
const port = browser.tabs.connect(sender.tab.id, {
name: message.xhrPortName,
});
Expand Down Expand Up @@ -465,7 +466,21 @@ async function handleMessage(message, sender) {
// transfer to content script via text and then parse to document
if (responseType === "document") xhr.responseType = "text";
// add required listeners and send result back to the content script
const handlers = details.hasHandlers || {};
if (details.hasUploadHandlers) {
for (const handler of Object.keys(details.hasUploadHandlers)) {
/** @param {ProgressEvent} event */
xhr.upload[handler] = async (event) => {
/** @type {TypeExtMessages.XHRProgress} */
const progress = {
lengthComputable: event.lengthComputable,
loaded: event.loaded,
total: event.total,
};
port.postMessage({ handler, progress });
};
}
}
const handlers = details.hasHandlers ?? {};
for (const handler of Object.keys(handlers)) {
xhr[handler] = async () => {
// can not send xhr through postMessage
Expand Down Expand Up @@ -513,7 +528,7 @@ async function handleMessage(message, sender) {
// if onloadend not set in xhr details
// onloadend event won't be passed to content script
// if that happens port DISCONNECT message won't be posted
// if details lacks onloadend attach listener
// so if details lacks onloadend then attach the listener
if (!handlers.onloadend) {
xhr.onloadend = () => {
port.postMessage({ handler: "onloadend" });
Expand Down
43 changes: 40 additions & 3 deletions src/ext/content-scripts/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ async function xhr(details, control, promise) {
url: String(details.url),
user: String(details.user),
hasHandlers: {},
hasUploadHandlers: {},
};
// preprocess data key
try {
Expand Down Expand Up @@ -340,6 +341,30 @@ async function xhr(details, control, promise) {
handlers[handler] = details[handler];
}
}
// preprocess upload handlers
/** @type {TypeExtMessages.XHRUploadHandlersObj} */
const uploadHandlers = {};
/** @type {TypeExtMessages.XHRUploadHandlers} */
const XHRUploadHandlers = [
"onabort",
"onerror",
"onload",
"onloadend",
"onloadstart",
"onprogress",
"ontimeout",
];
if (typeof details.upload === "object") {
for (const handler of XHRUploadHandlers) {
if (
handler in XMLHttpRequestEventTarget.prototype &&
typeof details.upload[handler] === "function"
) {
detailsParsed.hasUploadHandlers[handler] = true;
uploadHandlers[handler] = details.upload[handler];
}
}
}
// resolving asynchronous xmlHttpRequest
if (promise) {
detailsParsed.hasHandlers.onloadend = true;
Expand All @@ -363,14 +388,26 @@ async function xhr(details, control, promise) {
/**
* port listener, most of the messaging logic goes here
* @type {Parameters<typeof browser.runtime.onConnect.addListener>[0]}
* @param {import("../global.d.ts").TypeContentScripts.XHRPort} port
*/
const listener = (port) => {
if (port.name !== xhrPortName) return;
// handle port messages
port.onMessage.addListener(async (msg) => {
/** @type {TypeExtMessages.XHRHandlers[number]} */
const handler = msg.handler;
// handle upload progress
if (
"progress" in msg &&
detailsParsed.hasUploadHandlers[handler] &&
typeof uploadHandlers[handler] === "function"
) {
// call userscript handler
uploadHandlers[handler](msg.progress);
return;
}
// handle download events
if (
msg.response &&
"response" in msg &&
detailsParsed.hasHandlers[handler] &&
typeof handlers[handler] === "function"
) {
Expand All @@ -387,7 +424,7 @@ async function xhr(details, control, promise) {
if (response.readyState === 4 && response.response !== null) {
xhrResponseProcessor(msgResponse, response);
}
// call userscript method
// call userscript handler
handlers[handler](response);
// call the deleted XHR.DONE handlers above
if (response.readyState === 4) {
Expand Down
2 changes: 1 addition & 1 deletion src/ext/content-scripts/entry-userscripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ async function injection() {
// loop through each userscript @grant value, add methods as needed
for (let j = 0; j < grants.length; j++) {
const grant = grants[j];
const method = grant.split(".")[1] || grant.split(".")[0];
const method = grant.startsWith("GM.") ? grant.slice(3) : grant;
// ensure API method exists in USAPI object
if (!Object.keys(USAPI).includes(method)) continue;
// add granted methods
Expand Down
28 changes: 28 additions & 0 deletions src/ext/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,31 @@ declare global {
browser: Browser.Browser;
}
}

declare namespace TypeBackground {
interface XHRMessage {
handler: string;
progress?: TypeExtMessages.XHRProgress;
response?: TypeExtMessages.XHRTransportableResponse;
}

interface XHRPort extends Browser.Runtime.Port {
onMessage: Browser.Events.Event<
(message: TypeContentScripts.XHRMessage, port: XHRPort) => void
>;
postMessage: (message: XHRMessage) => void;
}
}

declare namespace TypeContentScripts {
interface XHRMessage {
name: string;
}

interface XHRPort extends Browser.Runtime.Port {
onMessage: Browser.Events.Event<
(message: TypeBackground.XHRMessage, port: XHRPort) => void
>;
postMessage(message: XHRMessage): void;
}
}
21 changes: 21 additions & 0 deletions src/ext/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,24 @@ declare namespace TypeExtMessages {
"onloadend",
];

type XHRUploadHandlers = [
"onabort",
"onerror",
"onload",
"onloadend",
"onloadstart",
"onprogress",
"ontimeout",
];

type XHRHandlersObj = {
[handler in XHRHandlers[number]]?: (response: XHRResponse) => void;
};

type XHRUploadHandlersObj = {
[handler in XHRUploadHandlers[number]]?: (response: XHRProgress) => void;
};

interface XHRTransportableDetails {
binary: boolean;
data: XHRProcessedData;
Expand All @@ -59,6 +73,7 @@ declare namespace TypeExtMessages {
url: string;
user: string;
hasHandlers: { [handler in XHRHandlers[number]]?: boolean };
hasUploadHandlers: { [handler in XHRUploadHandlers[number]]?: boolean };
}

interface XHRTransportableResponse {
Expand All @@ -78,4 +93,10 @@ declare namespace TypeExtMessages {
responseText?: string;
responseXML?: Document;
}

interface XHRProgress {
lengthComputable: boolean;
loaded: number;
total: number;
}
}