Chrome extension: how to pass ArrayBuffer or Blob from content script to the background without losing its type?

六眼飞鱼酱① 提交于 2019-11-27 11:51:25
Rob W

Messages passed between a Content Script and a background page are JSON-serialized.

If you want to transfer an ArrayBuffer object through a JSON-serialized channel, wrap the buffer in a view, before and after transferring.

I show an isolated example, so that the solution is generally applicable, and not just in your case. The example shows how to pass around ArrayBuffers and typed arrays, but the method can also be applied to File and Blob objects, by using the FileReader API.

// In your case: self.data = { data: new Uint8Array(xhr.response), ...
// Generic example:
var example = new ArrayBuffer(10);
var data = {
    // Create a view
    data: Array.apply(null, new Uint8Array(example)),
    contentType: 'x-an-example'
};

// Transport over a JSON-serialized channel. In your case: sendResponse
var transportData = JSON.stringify(data);
//"{"data":[0,0,0,0,0,0,0,0,0,0],"contentType":"x-an-example"}"

// At the receivers end. In your case: chrome.extension.onRequest
var receivedData = JSON.parse(transportData);

// data.data is an Object, NOT an ArrayBuffer or Uint8Array
receivedData.data = new Uint8Array(receivedData.data).buffer;
// Now, receivedData is the expected ArrayBuffer object

This solution has been tested successfully in Chrome 18 and Firefox.

  • new Uint8Array(xhr.response) is used to create a view of the ArrayBuffer, so that the individual bytes can be read.
  • Array.apply(null, <Uint8Array>) is used to create a plain array, using the keys from the Uint8Array view. This step reduces the size of the serialized message. WARNING: This method only works for small amounts of data. When the size of the typed array exceeds 125836, a RangeError will be thrown. If you need to handle large pieces of data, use other methods to do the conversion between typed arrays and plain arrays.

  • At the receivers end, the original buffer can be obtained by creating a new Uint8Array, and reading the buffer attribute.

Implementation in your Google Chrome extension:

// Part of the Content script
    self.data = {
        data: Array.apply(null, new Uint8Array(xhr.response)),
        contentType: xhr.getResponseHeader('Content-Type')
    };
...
sendResponse({data: self.data});

// Part of the background page
chrome.runtime.onMessage.addListener(function(data, sender, callback) {
    ...
    data.data = new Uint8Array(data.data).buffer;

Documentation

There is a better way to pass Blob (or ArrayBuffer) between any parts of the same Chrome extension (content scripts, background page and ordinary pages) then creating an ordinary JS Array or a binary string and passing this (sometimes extremely big) chunk of data in a message body! Remember that they are JSONified at the sender's end and then unJSONified at the receiver's end!

Just create and pass Object URL:

sendResponse(URL.createObjectURL(blob));

or create Blob first from ArrayBuffer:

var blob = new Blob([ arrayBuffer ], { type: 'image/jpeg' });
sendResponse(URL.createObjectURL(blob));

BTW XMLHttpRequest 2 can return both Blob and ArrayBuffer.

Notes

  • Object URLs can be alive for a qute long time so if you don't need data anymore don't forget to release such URLs calling URL.revokeObjectURL(objectURL)
  • Object URLs as any URL are subject of Cross-Origin restrictions but all parts of your extension are in the same origin of course.
  • BTW: I got a 4x performance boost when start passing such URLs instead of passing data itself in my Chrome extension! (My data were quite big images.)
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!