Skip to content

Commit

Permalink
perf: process and transmit xhr done data only once
Browse files Browse the repository at this point in the history
  • Loading branch information
ACTCD committed Sep 13, 2024
1 parent 6e7b83a commit 3e0a290
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 12 deletions.
12 changes: 11 additions & 1 deletion src/ext/background/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -512,12 +512,22 @@ async function handleMessage(message, sender) {
statusText: xhr.statusText,
timeout: xhr.timeout,
};
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/response#value
if (xhr.readyState < xhr.DONE && xhr.responseType !== "text") {
response.response = null;
}
// get content-type when headers received
if (xhr.readyState >= xhr.HEADERS_RECEIVED) {
response.contentType = xhr.getResponseHeader("Content-Type");
}
// only process when xhr is complete and data exist
if (xhr.readyState === xhr.DONE && xhr.response !== null) {
// note the status of the last `progress` event in Safari is DONE/4
// exclude this event to avoid unnecessary processing and transmission
if (
xhr.readyState === xhr.DONE &&
xhr.response !== null &&
handler !== "onprogress"
) {
// need to convert arraybuffer data to postMessage
if (
xhr.responseType === "arraybuffer" &&
Expand Down
66 changes: 55 additions & 11 deletions src/ext/content-scripts/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -303,15 +303,45 @@ async function xhr(details, control, promise) {
detailsParsed.headers[k.toLowerCase()] = v;
}
}
// get all the "on" events from XMLHttpRequest object
for (const k in XMLHttpRequest.prototype) {
if (k.slice(0, 2) !== "on") continue;
// preprocess handlers
const XHRHandlers = [
"onreadystatechange",
"onloadstart",
"onprogress",
"onabort",
"onerror",
"onload",
"ontimeout",
"onloadend",
];
for (const handler of XHRHandlers) {
// check which handlers are included in the original details object
if (typeof details[k] === "function") {
if (
handler in XMLHttpRequest.prototype &&
typeof details[handler] === "function"
) {
// add a bool to indicate if event listeners should be attached
detailsParsed.handlers[k] = true;
detailsParsed.handlers[handler] = true;
}
}
// resolving asynchronous xmlHttpRequest
if (promise) {
detailsParsed.handlers.onloadend = true;
const _onloadend = details.onloadend;
details.onloadend = (response) => {
promise.resolve(response);
if (typeof _onloadend === "function") _onloadend(response);
};
}
// make sure to listen to XHR.DONE events only once, to avoid processing
// and transmitting the same response data multiple times
if (detailsParsed.handlers.onreadystatechange) {
delete detailsParsed.handlers.onload;
delete detailsParsed.handlers.onloadend;
}
if (detailsParsed.handlers.onload) {
delete detailsParsed.handlers.onloadend;
}
// generate random port name for single xhr
const xhrPortName = Math.random().toString(36).substring(1, 9);
/**
Expand All @@ -321,10 +351,11 @@ async function xhr(details, control, promise) {
const listener = (port) => {
if (port.name !== xhrPortName) return;
port.onMessage.addListener(async (msg) => {
const handler = msg.handler;
if (
msg.response &&
detailsParsed.handlers[msg.handler] &&
typeof details[msg.handler] === "function"
detailsParsed.handlers[handler] &&
typeof details[handler] === "function"
) {
// process xhr response
const response = msg.response;
Expand All @@ -337,12 +368,25 @@ async function xhr(details, control, promise) {
xhrResponseProcessor(response);
}
// call userscript method
details[msg.handler](response);
details[handler](response);
// call the deleted XHR.DONE handlers above
if (response.readyState === 4) {
if (handler === "onreadystatechange") {
if (typeof details.onload === "function") {
details.onload(response);
}
if (typeof details.onloadend === "function") {
details.onloadend(response);
}
} else if (handler === "onload") {
if (typeof details.onloadend === "function") {
details.onloadend(response);
}
}
}
}
// all messages received
if (msg.handler === "onloadend") {
// resolving asynchronous xmlHttpRequest
promise && promise.resolve(msg.response);
if (handler === "onloadend") {
// tell background it's safe to close port
port.postMessage({ name: "DISCONNECT" });
}
Expand Down

0 comments on commit 3e0a290

Please sign in to comment.