Расширение Chrome: как передать ArrayBuffer или Blob из скрипта контента в фоновый режим, не теряя своего типа? - PullRequest
9 голосов
/ 21 декабря 2011

У меня есть этот скрипт содержимого, который загружает некоторые двоичные данные, используя 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);

Проблема в том, что файл, загруженный на сервер, содержит только эту строку: «[объект объекта]». Я думаю, это происходит из-за того, что тип ArrayBuffer как-то теряется при переносе его из процесса контента в фон? Как я могу решить это?

Ответы [ 2 ]

18 голосов
/ 09 апреля 2012

Сообщения, передаваемые между скриптом содержимого и фоновой страницей, сериализуются в формате JSON.

Если вы хотите передать объект ArrayBuffer через сериализованный JSON канал, оберните буфер в представление до и после передачи.

Я показываю отдельный пример, так что решение в целом применимо, а не только в вашем случае. В примере показано, как обойти ArrayBuffer s и типизированные массивы, но этот метод также можно применить к объектам File и Blob, используя 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

Это решение было успешно протестировано в 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;

Документация

8 голосов
/ 15 сентября 2013

Существует лучший способ передать Blob (или ArrayBuffer) между любыми частями того же расширения Chrome (скрипты содержимого, фоновые страницы и обычные страницы) затем создание обычной JS Array или двоичной строки и передача этого (иногда чрезвычайно большого) куска данных в теле сообщения! Помните, что они JSONified на стороне отправителя, а затем не JSONified на стороне получателя!

Просто создайте и передайте Object URL:

sendResponse(URL.createObjectURL(blob));

или сначала создайте Blob из ArrayBuffer:

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

КСТАТИ XMLHttpRequest 2 может вернуть Blob и ArrayBuffer.

Примечания

  • URL-адреса объектов могут существовать довольно долго, поэтому, если вам больше не нужны данные, не забудьте опубликовать такие URL-адреса, вызывая URL.revokeObjectURL(objectURL)
  • URL-адреса объектов , поскольку любой URL-адрес подпадает под Ограничения перекрестного происхождения , но все части вашего расширения, разумеется, имеют одинаковое происхождение.
  • Кстати: я получил 4-кратное повышение производительности при запуске передачи таких URL-адресов вместо передачи самих данных в моем расширении Chrome! (Мои данные были довольно большими изображениями.)
...