Chrome扩展程序:如何将ArrayBuffer或Blob从内容脚本传递到后台而不会丢失其类型?

Mic*_*tor 11 javascript json google-chrome-extension typed-arrays xmlhttprequest-level2

我有这个内容脚本,使用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});
Run Code Online (Sandbox Code Playgroud)

在后台脚本中接收到这些数据之后,我想形成另一个将这个二进制数据上传到我的服务器的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);
Run Code Online (Sandbox Code Playgroud)

问题是上传到服务器的文件只包含这个字符串:"[object Object]".我想这是因为ArrayBuffer类型在从内容进程转移到后台时以某种方式丢失了?我怎么解决这个问题?

Rob*_*b W 18

在内容脚本和后台页面之间传递的消息是JSON序列化的.

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

我展示了一个孤立的示例,以便解决方案通常适用,而不仅仅是在您的情况下.该示例显示了如何传递ArrayBuffers和类型化数组,但该方法也可以通过使用API 应用于对象FileBlob对象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
Run Code Online (Sandbox Code Playgroud)

此解决方案已在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;
Run Code Online (Sandbox Code Playgroud)

文档

  • 对于更大的数组 `Array.from(new Uint8Array(xhr.response))` 对我有用。 (2认同)

Kon*_*nin 10

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

只需创建并传递Object URL:

sendResponse(URL.createObjectURL(blob));
Run Code Online (Sandbox Code Playgroud)

或者首先从ArrayBuffer创建Blob:

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

BTW XMLHttpRequest 2可以返回BlobArrayBuffer.

笔记

  • 对象URL可以长时间处于活动状态,因此如果您不再需要数据,请不要忘记发布此类URL调用URL.revokeObjectURL(objectURL)
  • 对象的网址为任何URL如有的跨源限制,但分机的所有部分都在,当然相同的起源.
  • 顺便说一句:开始传递这些URL时,我的性能提升4倍,而不是在Chrome扩展程序中传递数据本身!(我的数据非常大.)

  • 如何从objectUrl重新创建blob? (4认同)
  • +1好答案.除了你所说的:在卸载文档时,`blob:`URL将自动被撤销. (2认同)

Mat*_*w C 6

对于 Chromium 扩展清单 v3,URL.createObjectURL() 方法不再起作用,因为它在服务工作线程中被禁止。

将数据从 Service Worker 传递到内容脚本(反之亦然)的最佳(最简单)方法是将 Blob 转换为 Base64 表示形式。

const fetchBlob = async url => {
    const response = await fetch(url);
    const blob = await response.blob();
    const base64 = await convertBlobToBase64(blob);
    return base64;
};

const convertBlobToBase64 = blob => new Promise(resolve => {
    const reader = new FileReader();
    reader.readAsDataURL(blob);
    reader.onloadend = () => {
        const base64data = reader.result;
        resolve(base64data);
    };
});
Run Code Online (Sandbox Code Playgroud)

然后将base64发送到内容脚本。

服务人员:

chrome.tabs.sendMessage(sender.tab.id, { type: "LOADED_FILE", base64: base64 });
Run Code Online (Sandbox Code Playgroud)

内容脚本:

chrome.runtime.onMessage.addListener(async (request, sender) => {
    if (request.type == "LOADED_FILE" && sender.id == '<your_extension_id>') {
        // do anything you want with the data from the service worker.
        // e.g. convert it back to a blob
        const response = await fetch(request.base64);
        const blob = await response.blob();
    }
});
Run Code Online (Sandbox Code Playgroud)