Using Persistent File Storage for storing blob in manifest V3

162 views
Skip to first unread message

Sagar Mittal

unread,
Oct 12, 2023, 8:35:17 AM10/12/23
to Chromium Extensions
Hi all,

I am working on migrating my extension to manifest V3. My extension utilizes "window.webkitRequestFileSystem" to store blob content. Here is the code for the same:

export function writeBlob(fileName, blob, onSuccess, onFailure, appendContent) {
    const fileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
    if (!fileSystem) {
        return;
    }

    navigator.webkitPersistentStorage.queryUsageAndQuota((usedBytes, grantedBytes) => {
        if (grantedBytes - usedBytes < blob.size) {
            window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
            navigator.webkitPersistentStorage.requestQuota(grantedBytes + quotaIncreaseAmount, (newGrantedBytes) => {
                writeToFileSystem(fileSystem, newGrantedBytes, fileName, blob, onSuccess, onFailure, appendContent);
            }, onFailure);
        } else {
            writeToFileSystem(fileSystem, grantedBytes, fileName, blob, onSuccess, onFailure, appendContent);
        }
    });
}

function writeToFileSystem(fileSystem, grantedBytes, fileName, blob, onSuccess, onFailure, appendContent) {
    fileSystem(window.PERSISTENT, grantedBytes, (fs) => {
        fs.root.getFile(fileName, { create: true }, (fileEntry) => {
            fileEntry.createWriter((fileWriter) => {
                if (appendContent) {
                    fileWriter.seek(fileWriter.length);
                }
                let truncated = false;
                fileWriter.onwrite = (e) => {
                    if (!truncated) {
                        truncated = true;
                        this.truncate(this.position);
                        onSuccess(fileEntry.toURL());
                    }
                };
                fileWriter.write(blob);
            }, onFailure);
        }, onFailure);
    }, onFailure);
}
 
I want to achieve similar functionality in manifest V3. However, I am not able to use this since window and navigator objects are not accessible inside the service worker. 
Is there an alternative to this implementation in manifest V3? 

Thanks,
Sagar

wOxxOm

unread,
Oct 12, 2023, 9:12:31 AM10/12/23
to Chromium Extensions, Sagar Mittal
Either do it inside the offscreen document or switch to the OPFS:
In case you don't need access to arbitrary offsets inside a file, you can also use IndexedDB as it can store blobs and files directly and there are libraries to promisify it and IIRC to imitate the deprecated HTML5 FS API that's similar to the one you were using.

Sagar Mittal

unread,
Oct 13, 2023, 3:18:46 AM10/13/23
to Chromium Extensions, wOxxOm, Sagar Mittal
Thanks a lot! I guess i can use IndexedDB for my use case. 

However, while storing the content in the IndexedDB, i want to return the file object URL as callback, something like this:
transaction.oncomplete = function () {
          const fileUrl = URL.createObjectURL(blob);
          onSuccess(fileUrl);
        };

It seems URL.createObjectURL() method is no longer supported in service worker scope. Do we have any alternatives to this?

Thanks

wOxxOm

unread,
Oct 13, 2023, 7:00:45 AM10/13/23
to Chromium Extensions, Sagar Mittal, wOxxOm
There's no workaround for URL.createObjectURL, https://crbug.com/1381188, you'll have to use it inside the offscreen document.

Sagar Mittal

unread,
Oct 13, 2023, 8:16:26 AM10/13/23
to Chromium Extensions, wOxxOm, Sagar Mittal
Thanks for the suggestion. I have been able to do this by creating a base64 encoding of the blob like this:
private storeFile(db: any, fileName: string, blob: Blob, onSuccess: (fileUrl) => any, onFailure: (e) => any) {
        const reader = new FileReader();
        reader.onload = function (event) {
            const transaction = db.transaction(['blobs'], 'readwrite');
            const objectStore = transaction.objectStore('blobs');
            const base64DataUrl = event.target.result;
            const record = {
                name: fileName,
                data: base64DataUrl,
            };
         
            const request = objectStore.add(record);
   
            request.onsuccess = function () {
                onSuccess(base64DataUrl);
            };
   
            request.onerror = function (event) {
                onFailure(event.target.error);
            };
        };
       
        reader.readAsDataURL(blob);
       
    }

This gives me a base 64 encoded url, something like this: "data:image/png;base64,iVBORw0..."

However, when i am trying to fetch the content using this URL on my extension popup, i get the following error on my console:
GET unsafe:data:image/png;base64,iVBORw0... net::ERR_UNKNOWN_URL_SCHEME

Is this something to do with the content security policy? Is there any way i can fix this? 

Thanks

wOxxOm

unread,
Oct 13, 2023, 8:25:54 AM10/13/23
to Chromium Extensions, Sagar Mittal, wOxxOm
I don't see your code in the popup so I don't know what's wrong. Either way, this is one of the slowest methods, it's like 10 times slower than direct access. The fastest method is to access IndexedDB directly in the popup. Next best one, if you don't mind sacrificing performance, is to do it in the service worker, then use the built-in service worker messaging that can send blobs directly i.e. navigator.serviceWorker.controller.postMessage in the popup and self.onmessage in the worker.
Reply all
Reply to author
Forward
0 new messages