如何在 Manifest V3 service workers 下初始化 Chrome extension `storage.local`?

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

我有一个 Chrome 扩展,如果尚未设置(例如,在安装时),我希望能够在其中初始化设置。

我想以一种可以从内容脚本和选项页面访问数据的方式来执行此操作,假设数据已经初始化。在Manifest V2下,我用了一个后台页面,用localStorage同步初始化了数据,在Manifest V3下就没有了

一种候选方法是 运行 service worker 的以下代码:

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

但是,这似乎不能保证工作,类似于迁移文档中的 example,因为服务工作者可能会在异步处理完成之前终止。此外,即使 service worker 没有终止,似乎也不能保证在内容脚本执行时数据会被初始化。

我想以一种保证在内容脚本执行时设置和可用的方式初始化数据。一种解决方法是检查数据是否已在内容脚本中正确初始化,否则使用回退默认值。是否有一些替代方法可以避免这种额外处理?

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

SW 不会随时终止,有一定的规则,最简单的经验法则就是它存活 30 秒,时间是 auto-prolonged 每订阅一个 30 秒 chrome API 事件被触发,并在 5 分钟后打开 chrome.runtime 消息 channel/port(目前此类端口在 MV3 中 5 分钟后 auto-closed)。

因此,假设您的示例代码立即运行,而不是在超时 29.999 秒之后,SW 不会在 API 调用期间终止,因为存储 API 只需要几毫秒完成,而不是 30 秒。 ManifestV3 上的文档可能过于努力地向服务工作者推销切换扩展的 non-existent 好处,因此 fact-checking 在撰写本文时是次要的。

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.

是的。

一些解决方案:

  1. 在您的内容脚本和其他地方包含默认值。这是最简单的解决方案,也适用于用户通过 devtools(存储区域资源管理器扩展或控制台命令)清除存储的情况。

  2. 初始化chrome.runtime.onInstalled.

    里面的存储后使用chrome.scripting.registerContentScripts(而不是在manifest.json中声明content_scripts)
  3. 在 content/options 脚本而不是 chrome.storage 中使用消息传递,以便后台脚本是唯一的真实来源。当 service worker 已经是 运行 时,消息传递实际上会更快,因为 chrome.storage 在 Chrome 中非常慢,本地和同步变体,所以要使缓存真正有效,您可以使用 chrome.runtime 端口将 service worker 的生命周期延长至 5 分钟或更长时间 .

    后台脚本:

    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 脚本:

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