Chrome extension: how to pass an ArrayBuffer or Blob from the contents of a script in the background without losing its type?

I have this script content that loads some binary data using XHR, which is later sent to the back of the script:

var self = this; var xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.responseType = 'arraybuffer'; xhr.onload = function(e) { if (this.status == 200) { self.data = { data: xhr.response, contentType: xhr.getResponseHeader('Content-Type') }; } }; xhr.send(); ... later ... sendResponse({data: self.data}); 

After receiving this data in the background script, I would like to generate another XHR request that uploads this binary data to my server, so I:

 var formData = new FormData(); var bb = new WebKitBlobBuilder(); bb.append(data.data); formData.append("data", bb.getBlob(data.contentType)); var req = new XMLHttpRequest(); req.open("POST", serverUrl); req.send(formData); 

The problem is that the file uploaded to the server contains only this line: "[object Object]". I assume this is because the ArrayBuffer type is somehow lost, passing it from the content process to the background? How can i solve this?

+8
json javascript google-chrome-extension typed-arrays
source share
2 answers

Messages sent between Script content and the man page are JSON-serialized.

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

I am showing an isolated example, so the solution is usually applicable, and not just in your case. This example shows how to pass ArrayBuffer and typed arrays, but this method can also be applied to File and Blob objects 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 successfully tested in Chrome 18 and Firefox.

  • new Uint8Array(xhr.response) used to create an ArrayBuffer so that individual bytes can be read.
  • Array.apply(null, <Uint8Array>) used to create a simple array using the keys from the Uint8Array . This step reduces the size of the serialized message. A 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 process large pieces of data, use other methods to convert between typed arrays and arrays.

  • At the end of the receiver, the source buffer can be obtained by creating a new Uint8Array and reading buffer .

Implementation of the 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

+15
source share

There is a better way to pass a Blob (or ArrayBuffer ) between any parts of the same Chrome extension (content scripts, background page and regular pages) , and then create a regular JS array or binary string and pass this (sometimes extremely large) piece of data in the body messages! Remember that they are JSONified at the end of the sender, and then unJSONified at the end of the receiver!

Just create and pass an Object URL :

 sendResponse(URL.createObjectURL(blob)); 

or first create a Blob 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 live for a long time, so if you no longer need data, be sure to release such URLs calling URL.revokeObjectURL(objectURL)
  • Object URLs , since any URL has Cross-Origin restrictions , but all parts of your extension, of course, are the same.
  • BTW: I got 4x acceleration when I started sending such URLs instead of transferring data in my Chrome extension! (My data was pretty large images.)
+8
source share

All Articles