Large file transfer

1,086 views
Skip to first unread message

NetDocuments

unread,
Jul 5, 2022, 8:41:54 AM7/5/22
to Chromium Extensions
Hello, I have a task - to transfer a large file (1GB) file from a service worker to a content script.
To do this, I use a FileReader and the readAsDataURL method.
But, since there is a file size limit for sending messages (about 50 megabytes), I split the file into slices and collect it on the side of the content script.
And it works great, but there is a problem.
The browser uses a lot of memory (about 3GB for a 500MB file) and does not always free up resources.

Question - perhaps someone knows a reliable way to transfer a large file size from a service worker to a content script?

Thank you!

wOxxOm

unread,
Jul 5, 2022, 12:41:49 PM7/5/22
to Chromium Extensions, ndde...@gmail.com
Since URL.createObjectURL doesn't work in a service worker, the next best thing is adding a web_accessible_resources iframe to the web page so that it receives the blob/file directly from SW via navigator.serviceWorker messaging. The iframe can be hidden/transparent or it can show your UI.

// sw.js
let blob = new Blob([new Uint8Array(1e9)]);
self.onmessage = e => {
  const { id, cmd, args } = e.data;
  if (cmd === 'getBlob') {
    blob.arrayBuffer().then(buf => {
      e.source.postMessage({ id, blob }, [buf]);
    });
  }
};

// iframe.js
askSW('getBlob').then(blob => {
  // ..........
});

function askSW(cmd, args) {
  return new Promise(async resolve => {
    const id = Math.random();
    const swr = navigator.serviceWorker;
    swr.addEventListener('message', function onMessage(e) {
      if (e.data.id === id) {
        swr.removeEventListener('message', onMessage);
        resolve(e.data.data);
      }
    });
    (await swr.ready).active.postMessage({ id, cmd, args });
  });
}

Note that in this code the blob becomes unusable in the service worker after the transfer, which is what makes the transfer instantaneous, so if you don't want that, remove the second parameter from postMessage and just call e.source.postMessage({ id, blob });

Ideally this would be the end of it - if you can show your UI inside this iframe and interact with the user inside.

Otherwise, if you just have to transfer this blob to the content script, there are two methods:
  1. super fast, using `parent.postMessage` which can be seen by the site though

    // iframe.js
    askSW('getBlob').then(async blob => {
      const buf = await blob.arrayBuffer();
      parent.postMessage({ cmd: 'blob', blob }, '*', [buf]);
    });

    // content.js
    window.onmessage = e => {
      if (e.origin === chrome.runtime.getURL('').slice(0, -1) && e.data.cmd === 'blob') {
        console.log(e.data.blob);
      }
    };

  2. slow: the iframe uses URL.createObjectURL on the blob and sends this tiny URL to the content script via chrome.tabs.sendMessage, the content script uses `fetch` to get the blob.

    // iframe.js
    askSW('getBlob').then(async blob => {
      const tab = await chrome.tabs.getCurrent();
      const url = URL.createObjectURL(blob);
      await chrome.tabs.sendMessage(tab.id, { cmd: 'blobUrl', url });
    });

    // content.js
    chrome.runtime.onMessage.addListener(msg => {
      if (msg.cmd === 'blobUrl') {
        receiveBlob(msg.url);
      }
    });
    async function receiveBlob(url) {
      const blob = await (await fetch(url)).blob();
      console.log(blob);
    }

NetDocuments

unread,
Jul 6, 2022, 7:25:23 AM7/6/22
to Chromium Extensions, wOxxOm, NetDocuments
Thanks for the answer. Let me ask one clarifying question:
If I have to use an iframe, how can I then transfer this file from the iframe to the actual (client's) web page?
I'll give an example:
There is a test.html web page that has an "upload file" control.
Previously (in manifest 2) I could pass a file from the background page to the content script (using URL.createObjectURL) and pass that file into an upload control:
const dT = new ClipboardEvent('').clipboardData || newDataTransfer();
dT.items.add(new File([blob], fileName, { type: type }));
fileInput.files = dT.files;

Now (in manifest 3) I'm doing the same but using readAsDataURL, but due to the large file size I need to split the file.
Question: can I transfer this file from the iframe to test.html  web page?

Thank you!

wOxxOm

unread,
Jul 6, 2022, 7:31:18 AM7/6/22
to Chromium Extensions, ndde...@gmail.com, wOxxOm
B-b-but... I've already explained it all in my previous comment with code examples.

NetDocuments

unread,
Jul 7, 2022, 10:19:47 AM7/7/22
to Chromium Extensions, wOxxOm
Thanks a lot for your answers.
Quick question:
This method "parent.postMessage" from  iframe.js  can only send one message at a time?
I've tried sending several large files at once and it seems that until the first one finishes sending, this method does not guarantee delivery of the second one.
I'm right?

wOxxOm

unread,
Jul 7, 2022, 10:36:59 AM7/7/22
to Chromium Extensions, ndde...@gmail.com, wOxxOm
Theoretically, there may be a limit of one blob message per task in JS event loop, but I can't say without seeing your code as there may be a mistake. 

wOxxOm

unread,
Nov 22, 2023, 10:43:08 AM11/22/23
to Chromium Extensions, wOxxOm, ndde...@gmail.com
dw1 found a mistake in my code: e.data.data should be e.data.blob
Reply all
Reply to author
Forward
0 new messages