首页  >  问答  >  正文

Chrome 扩展中的持久 Service Worker

<p>我需要将我的 Service Worker 在 Chrome 扩展程序中定义为持久的,因为我正在使用 webRequest API 来拦截特定请求的表单中传递的一些数据,但我不知道如何做到这一点。我已经尝试了所有方法,但我的 Service Worker 不断卸载。</p> <p>如何保持其加载并等待直到请求被拦截?</p>
P粉323224129P粉323224129443 天前635

全部回复(2)我来回复

  • P粉034571623

    P粉0345716232023-08-25 17:51:37

    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"}]
    });

    回复
    0
  • P粉386318086

    P粉3863180862023-08-25 00:41:03

    目录

    • 问题描述

    • 解决方法:

      • 漏洞利用
      离屏 API
      nativeMessaging API
      WebSocket API
      chrome 消息 API
      • 专用选项卡

    • 注意

    根据定义,Service Worker (SW) 不能持久,浏览器必须在一定时间后强制终止其所有活动/请求,在 Chrome 中为 5 分钟。不活动计时器(即没有正在进行的此类活动时)甚至更短:30 秒。

    Chromium 团队目前认为这种行为很好(团队偶尔放宽了某些方面,例如 Chrome 114 延长了 chrome.runtime每条消息后的端口),但这仅适用于观察不频繁事件的扩展,这些事件每天只运行几次,从而减少运行之间的浏览器内存占用(例如,带有 url 的 webRequest/webNavigation 事件> 过滤很少访问的网站)。可以重新设计这些扩展以维持状态,示例。不幸的是,这样的田园风光在很多情况下是不可持续的。

    已知问题

    • 问题 1:Chrome 106 及更早版本不会针对 webRequest 事件唤醒软件

      尽管您可以尝试订阅像其他答案中所示的 chrome.webNavigation 这样的 API,但它仅对工作线程启动后发生的事件有帮助。

    • 问题 2:工作人员随机停止因事件而醒来

      解决方法可能是调用 chrome.runtime.reload()。

    • 问题 3:Chrome 109 及更早版本无法延长新 chrome 的软件生命周期 已经运行的后台脚本中的 API 事件。这意味着当事件发生在 30 秒不活动超时的最后几毫秒内时,您的代码将无法可靠地异步运行任何内容。这意味着用户会认为您的扩展不可靠。

    • 问题 4:如果扩展程序维持远程连接或状态(变量)需要很长时间才能重建,或者您观察到如下频繁事件,则性能会比 MV2 差:

      • chrome.tabs.onUpdated/onActivated,
      • chrome.webNavigation 如果范围不限于罕见网址,
      • chrome.webRequest 如果范围不限于罕见的网址或类型,
      • chrome.runtime.onMessage/onConnect 用于所有选项卡中内容脚本的消息。

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

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

    通过错误利用“持久”服务工作者

    Chrome 110 引入了一个错误:调用任何异步 chrome API 都会使工作线程多运行 30 秒。该错误尚未修复。

    //背景.js

    const keepAlive = () => setInterval(chrome.runtime.getPlatformInfo, 20e3);
    chrome.runtime.onStartup.addListener(keepAlive);
    keepAlive();
    

    具有离屏 API 的“持久”服务工作人员

    凯文·奥古斯托提供。

    在 Chrome 109 及更高版本中,您可以使用offscreen API 创建离屏文档并每 30 秒或更短时间从其中发送一些消息,以保持 Service Worker 运行。目前该文档的生命周期不受限制(仅音频播放受到限制,我们不使用),但将来可能会发生变化。

    • manifest.json

        "permissions": ["offscreen"]
      
    • offscreen.html

      <script src="offscreen.js"></script>
      
    • offscreen.js

      setInterval(async () => {
        (await navigator.serviceWorker.ready).active.postMessage('keepAlive');
      }, 20e3);
      
    • 背景.js

      async function createOffscreen() {
        await chrome.offscreen.createDocument({
          url: 'offscreen.html',
          reasons: ['BLOBS'],
          justification: 'keep service worker running',
        }).catch(() => {});
      }
      chrome.runtime.onStartup.addListener(createOffscreen);
      self.onmessage = e => {}; // keepAlive
      createOffscreen();
      

    连接 nativeMessaging 主机时“持久”服务工作线程

    在 Chrome 105 及更高版本中,只要通过 chrome.runtime.connectNative。如果主机进程由于崩溃或用户操作而终止,则端口将关闭,并且软件将照常终止。您可以通过监听端口的 onDisconnect 事件来防范它再次调用 chrome.runtime.connectNative。

    WebSocket 处于活动状态时“持久”服务工作线程

    Chrome 116 及更高版本:每隔 30 秒交换一次 WebSocket 消息以保持其活动状态,例如每 25 秒一次。

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

    缺点:

    • 需要一个打开的网页选项卡
    • 内容脚本的广泛主机权限(例如 *://*/*),这会将大多数扩展程序放入网上商店的缓慢审核队列中.

    警告!如果您已连接端口,请不要使用此解决方法,请对下面的端口使用另一种解决方法。

    警告!如果您使用 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();
      

    “永远”,通过专用选项卡,当选项卡打开时

    不使用软件,而是打开一个内部包含扩展页面的新选项卡,因此该页面将充当“可见背景页面”,即软件要做的唯一事情就是打开此选项卡。您也可以从操作弹出窗口中打开它。

    chrome.tabs.create({url: 'bg.html'})
    

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

    缺点:

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

    您可以通过向页面添加 info/logs/charts/dashboard 来让用户更容易忍受,还可以添加一个 beforeunload 侦听器以防止选项卡被意外关闭。

    关于持久性的警告

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

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

    回复
    0
  • 取消回复