一、描述

扩展程序是基于事件的程序,用于修改或增强 Chrome 浏览体验。

Event 是浏览器触发器,例如导航到新页面、删除书签或关闭选项卡。

扩展程序在后台脚本中监视这些事件,然后对指定的指令作出反应。

后台运行的页面会在需要的时候再去加载, 不需要的时候会进行卸载,一些事件的例子包括:

  • 扩展程序第一次安装或者是更新成新版本的时候
  • 后台页面正在监听事件,并且需要调度这个事件
  • 前台的脚本或者是其他的扩展对后台脚本发送了消息
  • 这个扩展程序中的其他页面比如 popup 页面,调用了 runtime.getBackgroundPage API

后台页面挂载之后,只需要执行操作(比如调用 Chrome API 或者发出网络请求),后台页面就会一直运行。此外,在关闭所有可见视图和所有消息端口之前,不会写在后台页面,需要注意的是,打开视图不会导致加载 event page,只会阻止它在加载后关闭。

有效的后台脚本会保持休眠状态,知道它们监听到有消息 fire,并且对指定的指令做出反应然后卸载。

二、注册后台脚本

后台脚本在 manifest 的 background 字段中注册,可以注册多个后台脚本,会放在 background.scripts 字段下面,同时 persistent 应该指定为 false

示例:

{
  "name": "Awesome Test Extension",
  ...
  "background": {
    "scripts": ["background.js"],
    "persistent": false
  },
  ...
}

我们可以为模块化代码注册多个后台脚本:

{
     "name": "Awesome Test Extension",
     ...
     "background": {
       "scripts": [
         "backgroundContextMenus.js",
         "backgroundOmniBox.js",
         "backgroundOauth.js"
       ],
       "persistent": false
     },
     ...
   }

保持后台脚本 active 状态的唯一场景是扩展程序使用 chrome.webRequest API 来拦截或者修改网络请求, webRequest API 和非持久性后台页面不兼容。

如果扩展程序正在使用持久性后台页面,可以参照 后台脚本迁移指南 来切换成非持久性后台页面。

三、初始化扩展程序的时机

监听 runtime.onInstalled 事件以便于在安装的时候初始化扩展程序,使用这个事件可以设置状态或者一次性初始化,比如上下文菜单。

chrome.runtime.onInstalled.addListener(() => {
    chrome.contextMenus.create({
        "id": "testMenus",
        "title": "test Context Menu",
        "contexts": ["selection"]
    });
});

四、监听事件

围绕扩展所依赖的事件去构造后台脚本,定义功能相关事件,允许后台脚本处于休眠状态,直到触发这些事件的时候,并且能够防止扩展缺少重要触发器。

必须在页面开始的时候就注册监听器。

// 下面将会在创建书签的时候触发
chrome.bookmarks.onCreated.addListener(() => {
    // ...
});

事件监听器不能异步注册,否则不会正确的被触发,比如你不能在一个扩展 onInstalled 触发的时候注册监听器:

chrome.runtime.onInstalled.addListener(function() {
    // ERROR! Events must be registered synchronously from the start of
    // the page.
    chrome.bookmarks.onCreated.addListener(function() {
      // do something
    });
  });

扩展程序同时可以通过 removeListener 从后台脚本中删除侦听器,如果删除了某个事件的所有监听者, chrome 不会加载这个 active 事件的扩展程序的后台脚本。

chrome.runtime.onMessage.addListener(function(message, sender, reply) {
      chrome.runtime.onMessage.removeListener(event);
  });

五、事件过滤

使用支持事件过滤器的 API 来限制扩展程序关心场景的监听器。如果扩展程序正在监听 tabs.onUpdated 事件,需要使用带油过滤器的 webNavigation.onCompleted 事件,因为标签 API 不支持过滤器。

 chrome.webNavigation.onCompleted.addListener(function() {
      alert("This is my favorite website!");
  }, {url: [{urlMatches : 'https://www.google.com/'}]});

六、事件监听器响应时间

触发器是在事件触发之后去触发相应的功能,如果要对事件做出反应,需要在监听事件内部构件所需要的逻辑。

chrome.runtime.onMessage.addListener((message, callback) => {
    if (message.data == "setAlarm") {
        chrome.alarms.create({ delayInMinutes: 5 })
    } else if (message.data == "runLogic") {
        chrome.tabs.executeScript({ file: 'logic.js' });
    } else if (message.data == "changeColor") {
        chrome.tabs.executeScript(
            { code: 'document.body.style.backgroundColor="orange"' });
    };
});

七、卸载 background scripts

数据应当定期存储,以便于在没有收到 onSuspend 的情况下扩展程序 crash 的时候不会丢失重要的信息,可以使用 storage API 来解决这个问题。

 chrome.storage.local.set({variable: variableInformation});

如果扩展程序使用 message passing,需要确保关闭所有的端口,在所有消息端口关闭之前,后台脚本不会卸载。

可以通过监听 runtime.Port.onDisconnect 事件,能够知道打开的端口什么时候关闭,使用 runtime.Port.disconnect 可以手动关闭这些端口。

chrome.runtime.onMessage.addListener(function(message, callback) {
    if (message == 'hello') {
      sendResponse({greeting: 'welcome!'})
    } else if (message == 'goodbye') {
      chrome.runtime.Port.disconnect();
    }
  });

通过监查看扩展程序何时出现在任务管理器已经什么时候从任务管理器中消息,能够知道后台脚本的生命周期。

1.jpg

点击 chrome 菜单,然后选择更多工具,打开任务管理器。

1.jpg

几秒钟不活动后,后台脚本会自行卸载。如果需要最后一分钟清理,需要监听 runtime.onSuspend 事件。

 chrome.runtime.onSuspend.addListener(function() {
    console.log("Unloading.");
    chrome.browserAction.setBadgeText({text: ""});
  });

但是,依赖于 runtime.onSuspend,应该优先考虑持久化数据。

它不允许进行尽可能多的清理操作,并且在发生 crash 时无法提供帮助。