javascript Chrome 扩展:如何将 ArrayBuffer 或 Blob 从内容脚本传递到后台而不丢失其类型?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/8593896/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Chrome extension: how to pass ArrayBuffer or Blob from content script to the background without losing its type?
提问by Michael Spector
I have this content script that downloads some binary data using XHR, which is sent later to the background script:
我有这个内容脚本,它使用 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});
After receiving this data in background script, I'd like to form another XHR request that uploads this binary data to my server, so I do:
在后台脚本中接收到这些数据后,我想形成另一个 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);
The problem is that the file uploaded to the server contains just this string: "[object Object]". I guess this happens because ArrayBuffer type is lost somehow while transferring it from content process to the background? How can I solve that?
问题是上传到服务器的文件只包含这个字符串:“[object Object]”。我猜这是因为 ArrayBuffer 类型在从内容进程传输到后台时以某种方式丢失了?我该如何解决?
回答by Rob W
Messages passed between a Content Script and a background page are JSON-serialized.
在内容脚本和后台页面之间传递的消息是 JSON 序列化的。
If you want to transfer an ArrayBuffer
object through a JSON-serialized channel, wrap the buffer in a view, before and after transferring.
如果要ArrayBuffer
通过 JSON 序列化通道传输对象,请在传输之前和之后将缓冲区包装在视图中。
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 ArrayBuffer
s and typed arrays, but the method can also be applied to File
and Blob
objects, by using the FileReader
API.
我展示了一个孤立的示例,因此该解决方案通常适用,而不仅仅是您的情况。该示例显示了如何传递ArrayBuffer
s 和类型化数组,但该方法也可以通过使用API应用于File
和Blob
对象FileReader
。
// 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.
此解决方案已在 Chrome 18 和 Firefox 中成功测试。
new Uint8Array(xhr.response)
is used to create a view of theArrayBuffer
, so that the individual bytes can be read.Array.apply(null, <Uint8Array>)
is used to create a plain array, using the keys from theUint8Array
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 thebuffer
attribute.
new Uint8Array(xhr.response)
用于创建 的视图ArrayBuffer
,以便可以读取各个字节。Array.apply(null, <Uint8Array>)
用于创建一个普通数组,使用Uint8Array
视图中的键。此步骤减少了序列化消息的大小。警告:此方法仅适用于少量数据。当类型化数组的大小超过 125836 时,将抛出 RangeError。如果需要处理大块数据,可以使用其他方法进行类型数组和普通数组的转换。在接收端,可以通过创建一个新的
Uint8Array
并读取buffer
属性来获得原始缓冲区。
Implementation in your Google Chrome extension:
在您的 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;
Documentation
文档
- MDN: Typed Arrays
- MDN:
ArrayBuffer
- MDN:
Uint8Array
- MDN:
<Function> .apply
- Google Chrome Extension docs: Messaging > Simple one-time requests
"This lets you send a one-time JSON-serializable messagefrom a content script to extension, or vice versa, respectively" - SO bonus: Upload a File in a Google Chrome Extension- Using a Web worker to request, validate, process and submit binary data.
- MDN:类型化数组
- MDN:
ArrayBuffer
- MDN:
Uint8Array
- MDN:
<Function> .apply
- Google Chrome 扩展文档:消息 > 简单的一次性请求
“这使您可以分别从内容脚本向扩展发送一次性JSON 可序列化消息,反之亦然” - SO 奖励:在 Google Chrome 扩展程序中上传文件- 使用 Web 工作者请求、验证、处理和提交二进制数据。
回答by Konstantin Smolyanin
There is a better way to pass Blob
(or ArrayBuffer
) between any parts of the sameChrome extension (content scripts, background page and ordinary pages)then creating an ordinary JS Arrayor a binary stringand 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!
有一种更好的方法可以在同一个Chrome 扩展程序的任何部分(内容脚本、背景页面和普通页面)之间传递Blob
(或ArrayBuffer
),然后创建一个普通的 JS数组或一个二进制字符串并将这个(有时非常大的)数据块传递给一个消息体!请记住,它们在发送端是 JSONified,然后在接收端是 unJSONified!
Just create and pass Object URL
:
只需创建并通过Object URL
:
sendResponse(URL.createObjectURL(blob));
or create Blob first from ArrayBuffer:
或者首先从 ArrayBuffer 创建 Blob:
var blob = new Blob([ arrayBuffer ], { type: 'image/jpeg' });
sendResponse(URL.createObjectURL(blob));
BTW XMLHttpRequest 2
can return both Blob
and ArrayBuffer
.
顺便说一句XMLHttpRequest 2
,Blob
和都可以返回ArrayBuffer
。
Notes
笔记
- Object URLscan 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 URLsas any URL are subject of Cross-Origin restrictionsbut all parts of your extension are in the same origin of course.
- BTW: I got a 4x performance boostwhen start passing such URLs instead of passing data itself in my Chrome extension! (My data were quite big images.)
- 对象 URL可以存活很长时间,因此如果您不再需要数据,请不要忘记释放此类 URL 调用
URL.revokeObjectURL(objectURL)
- 对象的URL的任何URL是受跨源限制,但分机的所有部分都在,当然相同的起源。
- 顺便说一句:当开始传递此类 URL 而不是在我的 Chrome 扩展程序中传递数据本身时,我的性能提升了4 倍!(我的数据是相当大的图像。)