Search code examples
javascriptgoogle-chrome-extensiones6-promiseindexeddbchrome-runtime-messaging

Accessing Service Worker saved IndexedDB data from Content Script via Chrome Runtime Messaging


In a Chrome Extension, I have no problem adding, updating, and removing data to/from an IndexedDB database accessed by my service worker with Chrome Runtime Messaging sent from my content script. My trouble is doing a full table read from my content script. I do a console.log() to dump out the property before I send it back in my sendResponse in the Chrome Runtime Messaging, and I see the data there properly, but the content script receives an undefined. I assume this is because of the asynchronous nature of getting the data. I tried promises and async/await and the combination thereof and I just can't seem to get anything except an undefined in my content script on the message back from the service worker. I also ran sending a test array back and that worked just fine -- but receiving the IndexedDB table data does not work in the message passing. I also tried to JSONify the data and that didn't help either. What's the catch?

service-worker.js

importScripts('modules/idb.js');

var SW = {};

SW.onReady = function(){

  chrome.runtime.onMessage.addListener(function(o, sender, sendResponse) {
    (o.readTable) && sendResponse(SW.readTable(o,sender));
  });

};

SW.readTable = function(o,sender){
  var sTable = o.table;
  new Promise((resolve) => {
    IDB.readTable(sTable,function(asEntries){
      resolve(asEntries);
    });
  }).then((asEntries) => {
console.log('SW asEntries',asEntries); // this shows me valid data in tests
    var o = {};
    // can also change this to fake data with asEntries being a string array and bug goes away in content.js
    o.response = asEntries;
    return o;
  });
};

SW.onReady();

modules/idb.js

var IDB = {};

// Requires storage (or, even better, unlimitedStorage) permission in your manifest.json file.
// Note also that dev console of service worker will not show data -- have to use toolbar button popup panel (if you have one) and 
// dev console from there, or code to access it, which sucks.

IDB.connectStore = function(sTable,sReadWriteSetting,fn){
    var conn = indexedDB.open('unlimitedStorage', 1);
    conn.onupgradeneeded = function(e) {
        var db = e.target.result;
        db.createObjectStore(sTable);
    };
    conn.onsuccess = function(e) {
        var db = e.target.result;
        var tx = db.transaction(sTable,sReadWriteSetting);
        var store = tx.objectStore(sTable);
        fn(db,tx,store);
    };
};

IDB.addToTable = function(sTable,sKey,sVal){
    IDB.connectStore(sTable,'readwrite',function(db,tx,store){
        if ((sKey === undefined) || (sKey === '') || (sKey === null) || (sKey === false)) { // auto key by increment
            var req = store.count();
            req.onsuccess = function(e){
                sKey = e.target.result + 1;
                store.add(sVal,sKey);
                tx.complete;
            }
        } else {
            store.add(sVal,sKey);
            tx.complete;
        }
    });
};

IDB.removeFromTable = function(sTable,sKey){
    IDB.connectStore(sTable,'readwrite',function(db,tx,store){
        store.delete(sKey);
        tx.complete;
    });
};

IDB.readTableByKey = function(sTable,sKey,fn){
    IDB.connectStore(sTable,'readonly',function(db,tx,store){
        var req = store.get(sKey);
        req.onerror = function(e){
            fn(e.target.result);
        }
        req.onsuccess = function(e){
            fn(e.target.result);
        }
    });
};

IDB.readTable = function(sTable,fn){
    IDB.connectStore(sTable,'readonly',function(db,tx,store){
        var req = store.getAll();
        req.onerror = function(e){
            fn(e.target.result);
        }
        req.onsuccess = function(e){
            fn(e.target.result);
        }
    });
};

content.js

var CONTENT = {};

CONTENT.onReady = function(){
  var o = {};
  o.readTable = true;
  o.table = 'loadTimes';
  chrome.runtime.sendMessage(o,function(response){
    if (response.response) { // errors here with response property being undefined
      console.log('CONTENT RCVD asEntries',response.response);
    }
  });
};

CONTENT.onReady();

Solution

  • Chrome extensions API, unlike Firefox WebExtensions, can't handle Promise returned from a callback or provided in sendResponse, https://crbug.com/1185241.

    There's also a bug in your readTable: you need to add return before new Promise((resolve)

    The solution is two-fold:

    1. Use return true from the callback to allow asynchronous sendResponse
    2. Call sendReponse inside .then of a Promise chain.
    chrome.runtime.onMessage.addListener(function(o, sender, sendResponse) {
      if (o.readTable) {
        SW.readTable(o,sender).then(sendResponse);
        return true;
      } else {
        sendResponse(); // Chrome 99-101 bug workaround, https://crbug.com/1304272
      }
    });