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

IT技术 javascript google-chrome google-chrome-extension request xmlhttprequest
2021-02-11 00:03:21

我需要在我的 Chrome 扩展程序中将我的 Service Worker 定义为持久的,因为我使用 webRequest API 来拦截在表单中为特定请求传递的一些数据,但我不知道如何做到这一点。我已经尝试了所有方法,但我的 Service Worker 一直在卸载。

如何保持加载并等待请求被拦截?

3个回答

在您的情况下,它可能是 ManifestV3,https ://crbug.com/1024211 中的一个错误:工作人员不会因 webRequest 事件而醒来。所以只要使用 ManifestV2 直到它被修复,因为没有持久化服务工作者这样的东西。

防止扩展的 Service Worker 卸载的解决方法:

0. 精简版:最多五分钟通过 waitUntil

  • 示例 1,硬编码超时:

    self.onactivate = e => {
      e.waitUntil(new Promise(resolve => setTimeout(resolve, 5 * 60e3)));
    };
    
  • 示例 2,等待Promise:

    let allDone;
    self.onactivate = e => e.waitUntil(new Promise(r => { allDone = r; })));
    //................
    chrome.some.event.addListener(() => {
      foo().bar().finally(allDone);
    });
    

1. 通过运行时端口永远保持活动状态

从任何选项卡的内容脚本或从扩展程序的另一个页面(如弹出页面)打开运行时端口此端口将持续五分钟(服务工作者的固有限制),因此您必须使用计时器和端口的 onDisconnect 事件再次与某个随机选项卡重新连接。

缺点:

  • 需要网页选项卡或扩展选项卡/弹出窗口。
  • 内容脚本的广泛主机权限(如<all_urls>*://*/*),将大多数扩展放入网上商店的慢速审查队列。

实现示例:

  • manifest.json,相关部分:

      "permissions": ["scripting"],
      "host_permissions": ["<all_urls>"],
      "background": {"service_worker": "bg.js"}
    
    
  • 后台服务工作者 bg.js:

    let lifeline;
    
    keepAlive();
    
    chrome.runtime.onConnect.addListener(port => {
      if (port.name === 'keepAlive') {
        lifeline = port;
        setTimeout(keepAliveForced, 295e3); // 5 minutes minus 5 seconds
        port.onDisconnect.addListener(keepAliveForced);
      }
    });
    
    function keepAliveForced() {
      lifeline?.disconnect();
      lifeline = null;
      keepAlive();
    }
    
    async function keepAlive() {
      if (lifeline) return;
      for (const tab of await chrome.tabs.query({ url: '*://*/*' })) {
        try {
          await chrome.scripting.executeScript({
            target: { tabId: tab.id },
            function: () => chrome.runtime.connect({ name: 'keepAlive' }),
            // `function` will become `func` in Chrome 93+
          });
          chrome.tabs.onUpdated.removeListener(retryOnTabUpdate);
          return;
        } catch (e) {}
      }
      chrome.tabs.onUpdated.addListener(retryOnTabUpdate);
    }
    
    async function retryOnTabUpdate(tabId, info, tab) {
      if (info.url && /^(file|https?):/.test(info.url)) {
        keepAlive();
      }
    }
    

2. 替代方法:专用选项卡

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

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

缺点:

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

您可以通过将信息/日志/图表/仪表板添加到页面并添加一个beforeunload侦听器来防止选项卡被意外关闭,从而使您的用户更容易忍受

ManifestV3 的未来

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

这些方法都没有达到答案的要求。keepAlive策略只是冗余,谁会想保持打开标签?由于这些原因,这个答案被低估了。
2021-03-21 00:03:21
非常感谢你!!我花了两天时间认为这是我的代码中的错误,我刚刚切换到 MV2,现在它可以工作了!!
2021-03-22 00:03:21
@surajsharma,这个答案中的所有内容都经过了多个用户的测试,并且完全按照它所说的去做。
2021-03-28 00:03:21
所以人们知道在“通过运行时端口永远保持活动”选项中,executeScript调用ScriptInjection需要一个function密钥而不是一个func密钥(版本 90.0.4430.212)。这与ScriptInjection文档相矛盾,但与脚本文档相匹配
2021-04-06 00:03:21

如果我理解正确,您可以通过警报唤醒服务工作者(background.js)。看下面的例子:

  1. 清单 v3
"permissions": [
    "alarms"
],
  1. 服务工作者 background.js:
chrome.alarms.create({ periodInMinutes: 4.9 })
chrome.alarms.onAlarm.addListener(() => {
  console.log('log for debug')
});

不幸的是,这不是我的问题,可能你也有不同的问题。当我刷新 dev 扩展或停止并运行 prod 扩展时,Service Worker 完全死了。当我关闭并打开浏览器时,worker 不会运行,worker 中的任何侦听器也不会运行它。它尝试手动注册工人。例如:

// override.html
<!DOCTYPE html>
<html lang="en">

  <head>...<head>
  <body>
    ...
    <script defer src="override.js"></script>
  <body>
<html>
// override.js - this code is running in new tab page
navigator.serviceWorker.getRegistrations().then((res) => {
  for (let worker of res) {
    console.log(worker)
    if (worker.active.scriptURL.includes('background.js')) {
      return
    }
  }

  navigator.serviceWorker
    .register(chrome.runtime.getURL('background.js'))
    .then((registration) => {
      console.log('Service worker success:', registration)
    }).catch((error) => {
      console.log('Error service:', error)
    })
})

这个解决方案部分帮助了我,但没关系,因为我必须在不同的选项卡上注册工人。可能有人知道决定。我会很高兴。

从技术上讲,这个答案与后台脚本的持久性无关,但它仍然为 ManifestV3 中的一个固有错误提供了一种解决方法,其中后台脚本在更新期间完全丢失,crbug.com/1271154
2021-03-13 00:03:21
可以通过其他支持信息来改进您的答案。编辑以添加更多详细信息,例如引文或文档,以便其他人可以确认您的答案是正确的。您可以在帮助中心找到有关如何编写好的答案的更多信息
2021-03-29 00:03:21

WebSocketchrome.runtime我的扩展服务工作者侦听器注册中注册的回调不会被调用,这听起来几乎是相同的问题。

我通过向它添加以下代码来确保我的服务工作者永不结束来解决这个问题:

function keepServiceRunning() {
    setTimeout(keepServiceRunning, 2000);
  }

keepServiceRunning()

在此之后,我的回调现在按预期调用。

这不符合服务工作者的规范。您必须为 service worker 打开 devtools,这使 worker 保持活动状态,有意规避规范的超时以简化调试。
2021-03-23 00:03:21
谢谢,然后我已将我的用例添加到crbug.com/1152255,因为我没有以我所知道的任何方式明确使用扩展超时
2021-04-01 00:03:21
实际上,我有点困惑,因为我建议的答案代码确实使服务无限期地保持活动状态,而无需打开 devtools。Chromium 测试版 93.0.4577.51(官方版本)测试版(64 位)。
2021-04-03 00:03:21
这意味着浏览器中存在错误或脚本中的某些内容正在使用延长的 5 分钟超时,例如端口、消息和其他一些内容。
2021-04-10 00:03:21
没有MCVE,我不知道出了什么问题。我只验证了它在几个不同版本的 Chrome 中不起作用,包括每个规范的 93。请注意, chrome.runtime 消息传递是启用延长 5 分钟超时的因素之一。
2021-04-12 00:03:21