How can a Chrome extension transfer an ArrayBuffer or Blob from a content script to the background script without compromising its data type?

In my current script, I am downloading binary data using XHR in the content script and sending it to the background script:

let me = this;
let xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'arraybuffer';
xhr.onload = function(event) {
  if (this.status == 200) {
     me.binaryData = {
        data: xhr.response,
        contentType: xhr.getResponseHeader('Content-Type')
     };
  }
};
xhr.send();

... later ...
sendResponse({data: me.binaryData});

Once the data is received in the background script, I want to make another XHR request to upload this binary data to the server. This is how I attempt to do it:

let formData = new FormData();
let blobBuilder = new WebKitBlobBuilder();
blobBuilder.append(data.data);
formData.append("data", blobBuilder.getBlob(data.contentType));
let request = new XMLHttpRequest();
request.open("POST", serverUrl);
request.send(formData);

The issue I encountered is that the file uploaded to the server only contains the string: "[object Object]". I suspect that ArrayBuffer type is somehow lost during the transfer from the content process to the background. How can I address this problem?

Answer №1

When communicating between a Content Script and a background page, the messages exchanged are JSON-serialized.

If you need to send an ArrayBuffer object through a JSON-serialized channel, make sure to wrap the buffer in a view both before and after transferring it.

This example is presented in isolation to demonstrate a generally applicable solution. It showcases how to manipulate ArrayBuffers and typed arrays, but this method can also be utilized with File and Blob objects by leveraging the FileReader API.

// In your scenario: self.data = { data: new Uint8Array(xhr.response), ...
// General example:
var example = new ArrayBuffer(10);
var data = {
    // Creating a view
    data: Array.apply(null, new Uint8Array(example)),
    contentType: 'x-an-example'
};

// Sending data over a JSON-serialized channel (e.g., sendResponse)
var transportData = JSON.stringify(data);

// Receiving end of the transmission (e.g., chrome.extension.onRequest)
var receivedData = JSON.parse(transportData);

// Transforming received data into an ArrayBuffer object
receivedData.data = new Uint8Array(receivedData.data).buffer;
// Now, receivedData represents the expected ArrayBuffer object

This solution has been successfully tested on Chrome 18 and Firefox.

  • new Uint8Array(xhr.response) is used to create a view of the ArrayBuffer, allowing access to individual bytes.
  • Array.apply(null, <Uint8Array>)
    creates a plain array using keys from the Uint8Array view, reducing serialization message size. Note: This method may not work for large amounts of data. If handling substantial data, opt for alternative conversion methods between typed arrays and plain arrays.

  • At the receiving end, obtain the original buffer by creating a new Uint8Array and accessing the buffer attribute.

Integration within your Google Chrome extension:

// Within the Content script
    self.data = {
        data: Array.apply(null, new Uint8Array(xhr.response)),
        contentType: xhr.getResponseHeader('Content-Type')
    };
...
sendResponse({data: self.data});

// Within the background page
chrome.runtime.onMessage.addListener(function(data, sender, callback) {
    ...
    data.data = new Uint8Array(data.data).buffer;

Further Reading

Answer №2

When it comes to Chromium Extensions manifest v3, using the URL.createObjectURL() approach is no longer an option due to restrictions in service workers.

The most efficient way to exchange data between a service worker and a content script is by converting the blob into a base64 representation.

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

Afterwards, transmit the base64 data to the content script.

Service worker:

chrome.tabs.sendMessage(sender.tab.id, { type: "LOADED_FILE", base64: base64 });

Content script:

chrome.runtime.onMessage.addListener(async (request, sender) => {
    if (request.type == "LOADED_FILE" && sender.id == '<your_extension_id>') {
        // Perform desired operations on the data received from the service worker.
        // For example, you could convert it back to a blob
        const response = await fetch(request.base64);
        const blob = await response.blob();
    }
});

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Obtain JSON data instead of XML data from a web service through Ajax with the option 'contentType' set to 'false'

Upon making an AJAX call to send an image file to one of my web service (.asmx) methods, I encountered a problem where the web service returns XML instead of JSON. This issue arose because I needed to set the contentType to false, in order to successfully ...

Unable to connect with controller after deploying asp.net mvc ajax call on server

For the first time, I am encountering a new issue. Although my code runs smoothly on my localhost, when I upload it to the server, I get a bad request error. The specific error message is: 400 (Bad Request) Below is the snippet of my code: Controll ...

The type 'Data' is lacking the following attributes from its definition

Being a newcomer to Angular 8, I can't figure out why this error is popping up. If you have any suggestions on how to improve the code below, please feel free to share your tips. The error message reads: Type 'Data' is missing the follo ...

Passing a class as a parameter in Typescript functions

When working with Angular 2 testing utilities, I usually follow this process: fixture = TestBed.createComponent(EditableValueComponent); The EditableValueComponent is just a standard component class that I use. I am curious about the inner workings: st ...

iOS app launch does not trigger Phonegap handleOpenURL

Receiving an alert message when the app is open in the background. However, when I close the app from the background and then relaunch it, the alert message doesn't appear. The handleOpenURL function cannot be invoked in JavaScript when the app is lau ...

Upgrading Bootstrap from version 3.4 to 5.3 in a .NET project

I have a website project created using .Net Framework 4.7.2. Currently, I am utilizing Bootstrap version 3.4.1 from here. Since Bootstrap 3.4 is now in an end-of-life phase, I need to upgrade to the latest version, 5.3. Upon replacing the old version fil ...

Trouble with Title Updating in Next.js and Tailwind CSS Project

I am currently facing an issue with the title of my website not updating, even though I am using next/Head and have included the title tag. "use client"; import Head from 'next/head'; import { BsFillMoonStarsFill } from 'react-ico ...

What is the best method for retrieving a URL from a property within a JSON object?

Looking to access the video URLs from Frontendmasters courses through an API endpoint? The JSON object returned by the endpoint includes the URLs in the 'lessondata' property under 'sourcebase'. Wondering how to extract these URLs and s ...

Error encountered when trying to fetch data from JSON using Ajax

Having trouble sending data to my PHP file which is supposed to insert values into the database and then return JSON encoded data back to my JavaScript file for display. Despite looking at multiple working examples, I can't seem to get it right and do ...

The page abruptly jumps to the top whenever I display an ID element

Im currently facing an issue with my webpage where everything seems to be functioning properly, except for one problem. When I set a hidden element to be shown, the page suddenly jolts upwards. Although the previously concealed element is successfully disp ...

What is the best method to set variables to zero using this array in JavaScript?

I am looking to extract strings from an array and set them all to zero. The original array consists of: var myArray = ["apple", "banana", "cherry", "date"]; The expected outcome should be: var apple = 0; var banana = 0; var cherry = 0; var date = 0; I ...

The Art of jQuery Data Processing

I'm currently working on extracting data from a form submission. Below is the code I've been using. function handleSubmission() { $("#questionForm").submit(function() { $.post("SubmitQuestion.php", $("#questionForm").serialize()).done(functi ...

What is the process for generating an array of arrays in a React component?

I'm currently working on developing a word-guessing game similar to Wordle using React. The interesting twist in this game is that players have the ability to choose both the number of attempts allowed and the length of the word they are trying to gue ...

Caution in React: Utilizing functions with Object.assign() may not be a valid approach when dealing with React children

I have a setup using react for front-end and node for back-end. My goal is to retrieve data from the server to update the user entries on the front-end. I came across using Object.assign() as a potential solution to re-render user entries, but encountered ...

Pop-up message on card selection using JavaScript, CSS, and PHP

I have a total of 6 cards displayed in my HTML. Each card, when clicked, should trigger a modal window to pop up (with additional information corresponding to that specific card). After spending a day searching for a solution online, I've come here s ...

Link HTMLMediaElement with Android's playback controls

For my HTML/Javascript audio player that supports playlists, I have implemented a feature where the ended event automatically plays the next media in line. Everything works smoothly when using the player on Android Bromite browser, including the playback c ...

creating a countdown timer for the carousel

While experimenting, I decided to create a basic carousel from scratch. Initially, I used onclick="function()" to cycle through the images. Later on, I attempted to replace it with onload="setInterval(function, 4000)", but it seems like something went wron ...

The jQuery library triggers an error that can only be resolved by refreshing the

I am currently experiencing an issue with my form (form links are provided below, specifically referring to form1). The problem arises when I include jquery.js, as it fails to load the doAjax and getIP functions that are contained in a separate js file nam ...

Javascript - Single line conditional statement

As I continue to improve my JavaScript skills, I'm looking for guidance on optimizing the following if statement. Is there a way to shorten it, possibly even condense it into one line? How can I achieve that? onSelect: function (sortOption) { th ...

Combining two classes into a single class using ‘this’ in JavaScript

I'm encountering an issue where I am unable to figure out how to extend from the third class. So, I really need guidance on how to call the A class with the parameter 'TYPE', extend it with C, and then be able to call getType() with class C. ...