Prechádzať zdrojové kódy

fix: don't block page/extension when transferring chunks

tophf 4 rokov pred
rodič
commit
7dc67ba6ef

+ 4 - 2
src/background/utils/requests.js

@@ -193,7 +193,9 @@ const HeaderInjector = (() => {
   };
   };
 })();
 })();
 
 
-const CHUNK_SIZE = 64e6 / 4;
+/* 1MB takes ~20ms to encode/decode so it doesn't block the process of the extension and web page,
+ * which lets us and them be responsive to other events or user input. */
+const CHUNK_SIZE = 1e6;
 
 
 async function blob2chunk(response, index) {
 async function blob2chunk(response, index) {
   return blob2base64(response, index * CHUNK_SIZE, CHUNK_SIZE);
   return blob2base64(response, index * CHUNK_SIZE, CHUNK_SIZE);
@@ -274,7 +276,7 @@ function xhrCallbackWrapper(req) {
           await req.cb({
           await req.cb({
             id,
             id,
             chunk: {
             chunk: {
-              i,
+              pos: i * CHUNK_SIZE,
               data: await getChunk(response, i),
               data: await getChunk(response, i),
               last: i + 1 === numChunks,
               last: i + 1 === numChunks,
             },
             },

+ 23 - 22
src/injected/content/requests.js

@@ -29,14 +29,16 @@ bridge.addHandlers({
 
 
 bridge.addBackgroundHandlers({
 bridge.addBackgroundHandlers({
   async HttpRequested(msg) {
   async HttpRequested(msg) {
-    const { id } = msg;
+    const { id, chunk } = msg;
     const req = requests[id];
     const req = requests[id];
     if (!req) return;
     if (!req) return;
-    const { chunk } = msg;
     if (chunk) {
     if (chunk) {
       receiveChunk(req, chunk);
       receiveChunk(req, chunk);
       return;
       return;
     }
     }
+    if ((msg.numChunks || 1) === 1) {
+      req.gotChunks = true;
+    }
     const { blobbed, data, chunked, type } = msg;
     const { blobbed, data, chunked, type } = msg;
     const isLoadEnd = type === 'loadend';
     const isLoadEnd = type === 'loadend';
     // only CONTENT realm can read blobs from an extension:// URL
     // only CONTENT realm can read blobs from an extension:// URL
@@ -55,15 +57,12 @@ bridge.addBackgroundHandlers({
       req.bin = await req.bin;
       req.bin = await req.bin;
     }
     }
     // If the user in incognito supplied only `onloadend` then it arrives first, followed by chunks
     // If the user in incognito supplied only `onloadend` then it arrives first, followed by chunks
+    // If the user supplied any event before `loadend`, all chunks finish before `loadend` arrives
     if (isLoadEnd) {
     if (isLoadEnd) {
       // loadend's bridge.post() should run last
       // loadend's bridge.post() should run last
       await 0;
       await 0;
       req.gotLoadEnd = true;
       req.gotLoadEnd = true;
     }
     }
-    // If the user supplied any event before `loadend`, all chunks finish before `loadend` arrives
-    if ((msg.numChunks || 0) <= 1) {
-      req.gotChunks = true;
-    }
     if (importing) {
     if (importing) {
       data.response = req.bin;
       data.response = req.bin;
     }
     }
@@ -108,31 +107,33 @@ async function revokeBlobAfterTimeout(url) {
 /** ArrayBuffer/Blob in Chrome incognito is transferred in string chunks */
 /** ArrayBuffer/Blob in Chrome incognito is transferred in string chunks */
 function receiveAllChunks(req, msg) {
 function receiveAllChunks(req, msg) {
   req::pickIntoThis(msg, ['dataSize', 'contentType']);
   req::pickIntoThis(msg, ['dataSize', 'contentType']);
-  req.chunks = [msg.data.response];
-  return msg.numChunks > 1
+  req.arr = new Uint8ArraySafe(req.dataSize);
+  processChunk(req, msg.data.response, 0);
+  return !req.gotChunks
     ? new PromiseSafe(resolve => { req.resolve = resolve; })
     ? new PromiseSafe(resolve => { req.resolve = resolve; })
-    : decodeChunks(req);
+    : finishChunks(req);
 }
 }
 
 
-function receiveChunk(req, { data, i, last }) {
-  setOwnProp(req.chunks, i, data);
+function receiveChunk(req, { data, pos, last }) {
+  processChunk(req, data, pos);
   if (last) {
   if (last) {
     req.gotChunks = true;
     req.gotChunks = true;
-    req.resolve(decodeChunks(req));
+    req.resolve(finishChunks(req));
     delete req.resolve;
     delete req.resolve;
   }
   }
 }
 }
 
 
-function decodeChunks(req) {
-  const arr = new Uint8ArraySafe(req.dataSize);
-  let dstIndex = 0;
-  req.chunks::forEach(chunk => {
-    chunk = atobSafe(chunk);
-    for (let len = chunk.length, i = 0; i < len; i += 1, dstIndex += 1) {
-      arr[dstIndex] = chunk::charCodeAt(i);
-    }
-  });
-  delete req.chunks;
+function processChunk(req, data, pos) {
+  const { arr } = req;
+  data = atobSafe(data);
+  for (let len = data.length, i = 0; i < len; i += 1, pos += 1) {
+    arr[pos] = data::charCodeAt(i);
+  }
+}
+
+function finishChunks(req) {
+  const { arr } = req;
+  delete req.arr;
   return req.wantsBlob
   return req.wantsBlob
     ? new BlobSafe([arr], { type: req.contentType })
     ? new BlobSafe([arr], { type: req.contentType })
     : arr.buffer;
     : arr.buffer;