Chrome 扩展中的持久服务工作者

新手上路,请多包涵

我需要在我的 Chrome 扩展中将我的 Service Worker 定义为持久的,因为我正在使用 webRequest API 拦截以特定请求的形式传递的一些数据,但我不知道我该怎么做。我已经尝试了所有方法,但我的 Service Worker 一直在卸载。

我怎样才能保持加载并等待请求被拦截?

原文由 Keven Augusto 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.5k
2 个回答

Service worker (SW) 不能根据定义持久化,浏览器必须在一定时间后强行终止所有 SW 连接,例如网络请求或运行时端口,在 Chrome 中是 5 分钟。没有此类请求或端口打开时的不活动计时器甚至更短:30 秒。

Chromium 团队 目前认为 这种行为是有意且良好的,但这仅适用于观察偶发事件的扩展,因此它们每天只运行几次,从而减少运行之间的浏览器内存占用,例如 webRequest/webNavigation 事件与 urls 过滤很少访问的站点。这些扩展可以重新设计以维护状态, 例如

在许多情况下,这样的田园诗是不可持续的。

尽管您可以尝试订阅 chrome.webNavigation 之类的 API,如其他答案所示,但它仅对工作人员启动后发生的事件有帮助。

没有解决方法。尝试卸载扩展并重新安装。

  • 问题 3: SW 不活动计时器不会针对已经运行的后台脚本中的新 chrome API 事件而延长。这意味着当事件发生在 30 秒不活动超时的最后几毫秒时,您的代码将无法可靠地运行任何异步操作。这意味着您的扩展将被用户视为不可靠。

  • 问题 4:如果扩展保持套接字连接或状态(变量)需要很长时间重建或您观察到像这样的频繁事件,则性能比 MV2 差:

    • chrome.tabs.onUpdated/onActivated,
    • chrome.webNavigation 如果没有限定到一个罕见的 url,
    • chrome.webRequest 如果没有限定为罕见的 url 或类型,
    • chrome.runtime.onMessage/onConnect 用于所有选项卡中来自内容脚本的消息。

为新事件启动 SW 本质上就像打开新选项卡一样。创建环境大约需要 50 毫秒,运行整个 SW 脚本可能需要 100 毫秒(甚至 1000 毫秒,具体取决于代码量),从存储中读取状态并重建/水化可能需要 1 毫秒(或 1000 毫秒,具体取决于数据的复杂性) .即使是一个几乎为空的脚本,它也至少需要 50 毫秒,这对于调用只需要 1 毫秒的事件监听器来说是一个相当大的开销。

SW 可能每天重启数百次,因为此类事件是响应用户操作而生成的,这些操作之间存在自然间隙,例如单击选项卡然后写了一些东西,在此期间 SW 终止并为新事件再次重新启动,从而消耗 CPU ,磁盘,电池,经常引入扩展反应的频繁可察觉的滞后。

“持久”服务工作者,而 nativeMessaging 主机已连接

在 Chrome 105 和更新版本中,只要通过 chrome.runtime.connectNative 连接到 nativeMessaging 主机,service worker 就会运行。如果主机进程由于崩溃或用户操作而终止,端口将关闭,软件将照常终止。您可以通过监听端口的 onDisconnect 事件并再次调用 chrome.runtime.connectNative 来防范它。

存在可连接选项卡时的“持久”服务工作者

缺点:

  • 需要一个打开的网页选项卡
  • 内容脚本的广泛主机权限(如 <all_urls>*://*/* )将大多数扩展放入网络商店的慢速审查队列。

警告!如果您已经连接端口,请不要使用此变通方法,为下面的端口使用另一个变通方法。

警告!如果您使用 sendMessage,还要实施 sendMessage 的解决方法(如下)。

  • manifest.json,相关部分:
     "permissions": ["scripting"],
    "host_permissions": ["<all_urls>"],
    "background": {"service_worker": "bg.js"}


  • 后台服务工作者 bg.js:
   const onUpdate = (tabId, info, tab) => /^https?:/.test(info.url) && findTab([tab]);
  findTab();
  chrome.runtime.onConnect.addListener(port => {
    if (port.name === 'keepAlive') {
      setTimeout(() => port.disconnect(), 250e3);
      port.onDisconnect.addListener(() => findTab());
    }
  });
  async function findTab(tabs) {
    if (chrome.runtime.lastError) { /* tab was closed before setTimeout ran */ }
    for (const {id: tabId} of tabs || await chrome.tabs.query({url: '*://*/*'})) {
      try {
        await chrome.scripting.executeScript({target: {tabId}, func: connect});
        chrome.tabs.onUpdated.removeListener(onUpdate);
        return;
      } catch (e) {}
    }
    chrome.tabs.onUpdated.addListener(onUpdate);
  }
  function connect() {
    chrome.runtime.connect({name: 'keepAlive'})
      .onDisconnect.addListener(connect);
  }

  • 所有其他扩展页面,如弹出窗口或选项:
   ;(function connect() {
    chrome.runtime.connect({name: 'keepAlive'})
      .onDisconnect.addListener(connect);
  })();

如果你也使用 sendMessage

在 Chrome 99-101 中,即使不需要响应,您也需要始终在 chrome.runtime.onMessage 侦听器中调用 sendResponse() 。这是 MV3 中的错误。另外,确保你在不到 5 分钟的时间内完成,否则立即调用 sendResponse 并在工作完成后通过 chrome.tabs.sendMessage(到选项卡)或 chrome.runtime.sendMessage(到弹出窗口)发回新消息完毕。

如果您已经使用端口,例如 chrome.runtime.connect

警告!如果您还将更多端口连接到 service worker,则需要在 5 分钟过去之前重新连接每个端口,例如 295 秒。这在 104 之前的 Chrome 版本中至关重要,无论是否有额外的连接端口,它都会杀死 SW。在 Chrome 104 和更新版本中,此错误已修复,但您仍然需要重新连接它们,因为它们的 5 分钟生命周期没有改变,因此最简单的解决方案是在所有版本的 Chrome 中以相同的方式重新连接:例如每 295 秒.

  • 后台脚本示例:
   chrome.runtime.onConnect.addListener(port => {
    if (port.name !== 'foo') return;
    port.onMessage.addListener(onMessage);
    port.onDisconnect.addListener(deleteTimer);
    port._timer = setTimeout(forceReconnect, 250e3, port);
  });
  function onMessage(msg, port) {
    console.log('received', msg, 'from', port.sender);
  }
  function forceReconnect(port) {
    deleteTimer(port);
    port.disconnect();
  }
  function deleteTimer(port) {
    if (port._timer) {
      clearTimeout(port._timer);
      delete port._timer;
    }
  }

  • 客户端脚本示例,例如内容脚本:
   let port;
  function connect() {
    port = chrome.runtime.connect({name: 'foo'});
    port.onDisconnect.addListener(connect);
    port.onMessage.addListener(msg => {
      console.log('received', msg, 'from bg');
    });
  }
  connect();

“Forever”, via a dedicated tab, while the tab is open

打开一个带有扩展页面的新选项卡,例如 chrome.tabs.create({url: 'bg.html'})

它将具有与 ManifestV2 的永久后台页面相同的功能,但 a) 它是可见的并且 b) 无法通过 chrome.extension.getBackgroundPage 访问(可以用 chrome.extension.getViews 替换)。

缺点:

  • 消耗更多的内存,
  • 浪费标签条中的空间,
  • 分散用户的注意力,
  • 当多个扩展程序打开这样一个选项卡时,不利因素会滚雪球并成为真正的 PITA。

您可以通过向页面添加 info/logs/charts/dashboard 并添加 beforeunload 侦听器来防止选项卡被意外关闭,从而让您的用户更容易接受它。

关于持久性的警告

您仍然需要保存/恢复状态(变量),因为不存在持久性服务工作者这样的东西,并且这些变通办法具有如上所述的限制,因此工作者可以终止。您可以在存储中维护状态, 例如

请注意,您不应该仅仅为了简化状态/变量管理而让您的工作人员持久化。这样做只是为了恢复通过重新启动工作人员而恶化的性能,以防您的状态重建成本非常高,或者如果您挂钩到此答案开头列出的频繁事件。

ManifestV3 的未来

让我们希望 Chromium 将提供一个 API 来控制这种行为,而无需求助于这种肮脏的黑客攻击和可悲的解决方法。同时在 crbug.com/1152255 中描述您的用例(如果此处尚未描述)以帮助 Chromium 团队意识到一个既定事实,即许多扩展可能需要一个持久的后台脚本持续任意时间段,并且至少有一个这样的大多数扩展用户可能会安装扩展。

原文由 wOxxOm 发布,翻译遵循 CC BY-SA 4.0 许可协议

chrome.webRequest API 不同, chrome.webNavigation API 工作得很好,因为 chrome.webNavigation API 可以唤醒 service worker ,现在您可以尝试将 chrome.webRequest API api 放在 chrome.webNavigation 中。

 chrome.webNavigation.onBeforeNavigate.addListener(function(){

   chrome.webRequest.onResponseStarted.addListener(function(details){

      //.............

      //.............

   },{urls: ["*://domain/*"],types: ["main_frame"]});

},{
    url: [{hostContains:"domain"}]
});

原文由 Clairzil Bawon samdi 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题