14

我有这个内容脚本,它使用 XHR 下载一些二进制数据,稍后将其发送到后台脚本:

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});

在后台脚本中接收到这些数据后,我想形成另一个 XHR 请求,将这个二进制数据上传到我的服务器,所以我这样做:

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);

问题是上传到服务器的文件只包含这个字符串:“[object Object]”。我猜这是因为 ArrayBuffer 类型在从内容进程传输到后台时以某种方式丢失了?我该如何解决?

4

2 回答 2

21

在内容脚本和背景页面之间传递的消息是 JSON 序列化的。

如果要ArrayBuffer通过 JSON 序列化通道传输对象,请在传输之前和之后将缓冲区包装在视图中。

我展示了一个孤立的示例,以便该解决方案普遍适用,而不仅仅是您的情况。该示例显示了如何传递s 和类型化数组,但该方法也可以通过使用APIArrayBuffer应用于对象File和对象。BlobFileReader

// 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

该解决方案已在 Chrome 18 和 Firefox 中成功测试。

  • new Uint8Array(xhr.response)用于创建 的视图ArrayBuffer,以便可以读取各个字节。
  • Array.apply(null, <Uint8Array>)Uint8Array用于使用视图中的键创建一个普通数组。此步骤减小了序列化消息的大小。警告:此方法仅适用于少量数据。当类型化数组的大小超过 125836 时,会抛出 RangeError。如果需要处理大块数据,可以使用其他方法来做类型数组和普通数组之间的转换。

  • 在接收端,可以通过创建一个新的Uint8Array,并读取buffer属性来获得原始缓冲区。

在您的 Google Chrome 扩展程序中实现:

// 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;

文档

于 2012-04-09T11:37:15.180 回答
16

有一种更好的方法可以在同一Chrome 扩展程序的任何部分(内容脚本、背景页面和普通页面)之间传递Blob(或),然后创建一个普通的 JS数组二进制字符串并将这个(有时非常大的)数据块传递到消息体!请记住,它们在发送者端是 JSON 化的,然后在接收者端是非 JSON 化的!ArrayBuffer

只需创建并传递Object URL

sendResponse(URL.createObjectURL(blob));

或首先从 ArrayBuffer 创建 Blob:

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

BTWXMLHttpRequest 2可以同时返回BlobArrayBuffer

笔记

  • 对象 URL可以存在很长时间,因此如果您不再需要数据,请不要忘记释放此类 URL 调用URL.revokeObjectURL(objectURL)
  • 像任何 URL 一样的对象URL 都受到跨域限制,但扩展的所有部分当然都在同一个源中。
  • 顺便说一句:当开始传递此类 URL 而不是在我的 Chrome 扩展程序中传递数据本身时,我的性能提升了 4 倍!(我的数据是相当大的图像。)
于 2013-09-15T16:18:52.890 回答