> 文档中心 > chrome extension v3 version background service worker deep analysis

chrome extension v3 version background service worker deep analysis


背景

由于插件开发人员对于 V3 的 service worker 是有畏惧感的,所以尽量绕开 background 的使用,但是 background 作为中转信息的关键点,不使用则会造成设计更加复杂,时间效率的浪费,所以要解决 background 的认知问题

background 配置

英文名: _generated_background_page.html  不生成一个 html,你 console 控制台打不开

V2 中区分两个概念

  1. background page 就是一个完整的html页面,运行之后,作为一个独立的app运行,相当于一个独立运行的小型服务器,谷歌浏览器会开一个线程来运行这个 html 脚本,并且监听一些端口,同时将该 html 中写的监听函数绑定到监听端口的回调函数里

    1. 好处是,让开发者思维跨度不那么大,毕竟开发者都是前端技术人员,对于服务器啥的根本没有概念,类似提供接口这种事情完全不懂,运行在某端口上进行监听,更是迷惑,所以谷歌浏览器就把服务端概念给弱化了,反正你知道在这里写监听函数就行了

    2. 坏处就是谷歌浏览器要给每个插件都运行一个 window dom 环境,有可能该插件几乎用不到 window 里面的任何一种东西,但是谷歌浏览器仍然要加载在线程里,耗费了资源却没啥用处

  2. event page 鉴于 background page 缺点,谷歌官方,在插件开发达到一定数量之后,认为前端开发者应该在 webpack 这种东西越来越普及的情况下,对 service worker 这种微型服务端概念逐渐成熟了,所以鼓励前端技术提高后端技术理解能力,同时也想降低谷歌浏览器的内存耗费,因此提出了 event page 的方式,也即 background 变成了一种短暂加载进内存的脚本,就类似于 PHP 脚本被 PHP-FPM 载入一样,这里 的 php-fpm 就是谷歌浏览器本身,php 脚本就是 background-script,因此脚本的变量值是不会常驻保存的,脚本可以多次被线程加载执行,执行完毕后就释放,释放时触发 onSuspend 事件

    1. 坏处是脚本因为不会被常驻内存了,因为谷歌统一管理了,这个概念在后端也有例子,如 springboot 或者 django 运行起来后本身就是一个独立的服务,代码请求时,只要是一个全局变量,就能统计数字,但是 PHP 因为每次执行都是一个进程单独加载,运行完释放,所以没有全局变量的概念,但是 PHP 可以实现代码不重启,即可更新,因为每次都重新执行一遍脚本,满足成百上千的项目,可以被一个 PHP-FPM 来运行,内存消耗极低,谁执行给谁分配内存

    2. 好处就是节省内存,更新之后不用重启,直接就可以以最新的代码运行

// 谷歌V2推荐模式,为V3做铺垫和准备{  "name": "My extension",  ...  "background": {    "scripts": ["background.js"],    "persistent": false // 如果为true的话则就是一个background page页面  },  ...}

V2 事件页面加载与卸载

从概念中我们得知,事件模式是运行脚本,运行完后释放,因此就有两个事件需要我们注意,那就是加载和卸载

https://developer.chrome.com/docs/extensions/mv2/background_pages/

触发加载的 4 个事件

  1. 扩展首次安装或更新到新版本。

  2. 后台页面正在侦听一个事件,并且该事件已被调度,debugger 也是事件,并且还会一直阻拦者卸载事件的发生

  3. 内容脚本或其他扩展会发送消息。

  4. 扩展中的另一个视图,例如弹出窗口,调用runtime.getBackgroundPage.

触发卸载事件

  1. 这里不能成为卸载,而应该成为暂挂,因为插件并没有卸载,只是脚本暂时从内存中释放,等待下次装载后使用所以用了onSuspend 事件,该事件很难测试出来,一般人会开 console 等着关闭,结果问题就是开启 console 时,会生成_generated_background_page.html 页面,因为只有 html 页面才有 console,而文档中有一句是:加载后,只要后台页面正在执行某个操作,例如调用 Chrome API 或发出网络请求,它就会一直运行。此外,在关闭所有可见视图和所有消息端口之前,不会卸载背景页面。请注意,打开视图不会导致事件页面加载,而只会阻止它在加载后关闭。该问题导致 onSuspend 迟迟不会触发,如果改为 console.log 打印日志的方式也输出不了,因为不知道输出到哪里了,最后的套路是让他打开一个新 tab,如果你想带点参数,可以放在 url?后面

chrome.runtime.onSuspend.addListener(function () {    console.log("卸载事件被触发")    chrome.tabs.create({        url: "https://www.baidu.com/"    });    chrome.storage.local.set({"key_wangsen": "bengkuile"}, function () {        console.log("设置成功")    })})

V3 数据状态怎么存?

官方给的方式是用 chrome.storage.sync.set/get 来存取,在加载脚本的 4 个条件处 get,在暂存的事件中 set

V3 中 setTimeout 和 setInterval 

是肯定不能用了,因为 setTimeout 是存在于线程中的事件循环的,V3 的 service worker 在不运行后几秒后,就暂存了,线程也被释放了,所以 seTimeout 根本就不执行,但谷歌提供了一个统一 alarms 管理器,这样就避免了大家乱建 setTimeout 和 setInterval,保证了非常多插件的情况各自开线程造成的耗费内存和阻塞问题

V3 background 迁移要点

https://developer.chrome.com/docs/extensions/mv3/migrating_to_service_workers/

链接中主要讲了几个点:

  1. manifest 调整

  2. 不能嵌套监听,原理是一个监听类似一个 controller,如果监听中套一个 controller,就会导致第二个 controller 根本执行不了,因为异步的原因,当第一个 controller 执行完成后,该次线程就被关闭了,第二次还没来的及执行,就被从内存中销毁了。这种设计机制,注定 background 与其他页面主动通信,都必须放在 alar

  3. 那第一次接收的数据,在后续异步行为中要用到怎么办?这个数据怎么存?则使用插件的存储机制,chrome.storage.local.set/get 来解决,第一次 controller 中 set 进去,等到 alarm 拉起时暂停掉

  4. 所以异步延迟的事件,都不能在用 setTimeout 和 setInterval 了,必须用 alarm 来处理

  5. 要把 service worker 当成一个 worker 对待,worker 为一个临时性的线程,是 H5 中的一个新概念,为了解决流媒体中的视频流和音频流同时渲染的问题,原来的浏览器为了节省内存,采用事件循环的方式,也即所有异步回调或者延迟执行,都会先排在一个队列里,然后一个个被执行,类似于协程+线程处理方式,但视频需要音视频流同时渲染,而不是交替渲染,协程是交替渲染,所以就创造了 worker,而 worker 是没有 window 的 dom 结构等其他一些 window 的功能的,是纯 JS 语言环境,连 XMLHttpRequest 这种 http 请求对象也没有,为了解决这个问题,worker 自带了简洁版的 fetch

  6. DOM 结构解析,请自行寻找解析库来用,如果非得用 Dom 结构或者 browser environment,则自己通过新打开一个 tab 或者 window,与 background 进行通信来解决

  7. 视频和音频捕捉怎么做?直接开 windows 和 tabs 来处理

  8. canvas 画布还能用吗?可以用,换成了 workder 中的一个对象了,叫new OffscreenCanvas(width, height)

alarm 的使用

因为 background 跟 alarm 的关联性较大,这里单独来类比学习 alarm 这个定时器

alarm 因为是一个总控调度器,并不是某一个定时函数体,正如 chrome.tabs,是针对于所有的 tabs 的管理器,而不是某一个 tab 的处理器

管理器的工作就是收集和提醒的作用,具体你到时怎么做,你要根据管理器收集的名称,来具体设定函数体,所以 alarm 有两个函数,一个收集定时器,一个监听到期的定时器

create 函数,收集定时器信息

class AlarmCreateInfo {  delayInMinutes?:number, // when之后延迟的分钟  periodInMinutes?:number, // 周期性执行的分钟  when?:number // 什么时候执行,单位毫秒}chrome.alarms.create(  name?: string,  alarmInfo: AlarmCreateInfo,)

onAlarm 事件,监听到期的定时器

class Alarm {  name, periodInMinutes?:number, scheduledTime:number}chrome.alarms.onAlarm.addListener(  callback: function(alarm: Alarm),)

clear 函数,提前清理不想再处理的定时器

chrome.alarms.clear(  name?: string,  callback?: function(wasCleared: boolean),)

get 函数

  1. get 不到定时器时,返回的是 undefined,所以可以用 if (!chrome.alarms.get("xxxx"))