dannyadam
dannyadam

Reputation: 4170

How to initialize Chrome extension `storage.local` under Manifest V3 service workers?

I have a Chrome extension where I want to be able to initialize settings if not already set (e.g., upon installation).

I want to do this in a way that the data can be accessed from content scripts and options pages under an assumption that the data has been initialized. Under Manifest V2, I used a background page and initialized the data synchronously with localStorage, which is no longer available under Manifest V3.

A candidate approach would be to run the following code from a service worker:

chrome.storage.local.get(['settings'], function(storage) {
    if (!storage.settings) {
        chrome.storage.local.set({settings: defaultSettings()});
    }
});

However, that seems not guaranteed to work, similar to the example from the migration documentation, since the service worker could be terminated prior to the completion of the asynchronous handling. Additionally, even if the service worker is not terminated, it seems to not be guaranteed that the data would be initialized by time the content script executes.

I would like to initialize the data in a way that it would be guaranteed to be set and available by time a content script executes. A workaround could be to check if the data has been properly initialized in the content script, and use a fallback default value otherwise. Is there some alternative approach that could avoid this extra handling?

Upvotes: 2

Views: 1974

Answers (1)

woxxom
woxxom

Reputation: 73766

since the service worker could be terminated prior to the completion of the asynchronous handling

SW doesn't terminate at any time, there are certain rules, the simplest rule of thumb is that it lives for 30 seconds, the time is auto-prolonged by 30 seconds whenever a subscribed chrome API event is triggered, and by 5 minutes when you have an open chrome.runtime messaging channel/port (currently such ports are auto-closed after 5 minutes in MV3).

So, assuming your example code runs right away, not after a timeout of 29.999 seconds, SW won't terminate during the API call as the storage API takes just a few milliseconds to complete, not 30 seconds. The documentation on ManifestV3 probably tried too hard to sell the non-existent benefits of switching extensions to service workers, so fact-checking was secondary when the article was written.

even if the service worker is not terminated, it seems to not be guaranteed that the data would be initialized by time the content script executes.

Yes.

Some solutions:

  1. Include the defaults in your content scripts and other places. It's the simplest solution that also works in case the user clears the storage via devtools (Storage Area Explorer extension or a console command).

  2. Use chrome.scripting.registerContentScripts (instead of declaring content_scripts in manifest.json) after initializing the storage inside chrome.runtime.onInstalled.

  3. Use messaging in content/options scripts instead of chrome.storage so that the background script is the sole source of truth. When the service worker is already running, messaging will be actually faster as chrome.storage is very slow in Chrome, both local and sync variants, so to make caching truly effective you can use chrome.runtime ports to prolong the lifetime of the service worker to 5 minutes or longer as shown here.

    background script:

    let settings;
    let busy = chrome.storage.local.get('settings').then(r => {
      busy = null;
      settings = r.settings;
      if (!settings) {
        settings = defaultSettings();
        chrome.storage.local.set({settings});
      }
      return settings;
    });
    
    chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
      if (msg === 'getSettings') {
        if (busy) {
          busy.then(sendResponse);
          return true;
        } else {
          sendResponse(settings)
        }
      }
    });
    

    content/option script:

    chrome.runtime.sendMessage('getSettings', settings => {
      // use settings inside this callback
    });
    

Upvotes: 4

Related Questions