首页
/ Web-Dev-For-Beginners 浏览器扩展实战(三):chrome.runtime 消息通信、动态图标绘制与浏览器性能剖析

Web-Dev-For-Beginners 浏览器扩展实战(三):chrome.runtime 消息通信、动态图标绘制与浏览器性能剖析

2026-09-06 15:43:25作者:尤辰城Agatha

本文基于开源课程仓库 Web-Dev-For-Beginners 的浏览器扩展模块第三课(西班牙语版教案 README.es.md),以仓库中的 Carbon Trigger 碳强度跟踪扩展为真实案例,完整讲解扩展后台任务链的实现——从 popup 脚本发送 chrome.runtime.sendMessage 消息,到后台脚本监听 onMessage 并用 Canvas 绘制动态图标——并给出使用浏览器开发者工具 Performance 面板做性能剖析、识别资源瓶颈的实操方法。读完后你将掌握扩展脚本间消息通信的完整调用链、MV3 后台服务 Worker 的图标更新机制,以及用开发者工具建立性能基线的标准流程。

Carbon Trigger 扩展在浏览器中的最终效果:工具栏上的彩色圆点图标与扩展弹窗界面

一、本课定位:补全 Carbon Trigger 扩展的最后一环

浏览器扩展模块 的前两课中,你已经完成了:

  • 了解浏览器架构与扩展加载方式(1-about-browsers);
  • 构建表单、使用 localStorage 持久化 API Key 与区域代码,并异步调用 CO2Signal API 获取所在地区的碳强度数据(2-forms-browsers-local-storage)。

本课要处理的是最后一块拼图:后台任务(background tasks)——根据实时碳强度数值,把扩展工具栏图标染成从绿色到深棕色的不同颜色,让用户扫一眼工具栏就知道"现在是否适合运行烘干机电费密集型的家务"。在动手之前,课程特意先讲浏览器性能管理,因为这类"图标刷新、数据轮询"的后台行为正是性能敏感点。

仓库中学生起点代码位于 start/src/index.js(一个带编号注释的空白脚手架),完整实现见 solution/src/index.js,二者对照即是本课的全部改动面。

二、Web 性能基础:用 DevTools 的 Performance 面板建立数据基线

"网站性能关乎两件事:页面加载有多快,以及页面上的代码运行有多快。" —— Zack Grossbart

教案指出的第一步不是优化,而是采集数据:在浏览器开发者工具中打开 Performance(性能)面板。在 Edge 中点击浏览器右上角的三个点,进入"更多工具 → 开发者工具",切换到 Performance 标签页,即可使用内置的 Profiler(剖析器)。

操作要点(教案原文步骤的完整继承):

  1. 打开一个目标网站(教案示例为 microsoft.com);
  2. 点击 Record(录制) 按钮,然后刷新页面,让 Profiler 完整捕获加载过程;
  3. 随时停止录制,时间线上会展示浏览器为 script(脚本)、render(渲染)、paint(绘制) 站点所产生的全部例行事件;
  4. 在时间线上选取一个片段,右侧 Summary(摘要)面板给出该时段的性能快照(加载量、内存峰值、事件统计);
  5. 检查 Event Log(事件日志) 面板,确认是否存在耗时超过 15 ms 的事件——这是教案给出的单事件性能警戒线。

Edge 开发者工具 Performance 面板:录制得到的脚本、渲染与绘制时间线

Performance 面板选中时间线片段后的摘要快照面板

两条来自教案的高价值提示:

  • 先清缓存再测:要获得站点真实的"首访"启动耗时,先清除浏览器缓存,缓存命中与冷启动的读数是两个世界;
  • 剖析扩展本身要"就地取材":扩展拥有自己独立的浏览器上下文实例,想给扩展做性能剖析,必须从扩展自身弹出的开发者工具里打开面板,而不是从普通网页的 DevTools 里看。

教案还布置了一个即学即练的小任务:对当前文档站点本身打开开发者工具,找出最慢与最快的加载资源,训练"读时间线"的手感。

三、性能剖析的三大常见"问题区"

教案归纳了每个 Web 开发者在上线前都应排查的三类典型瓶颈,这三点同样适用于扩展开发:

1. 资产体积(Asset sizes)

Web 页面在过去几年持续变"重",图片是主要增重来源。教案给出的检查项是:确认图片经过优化、以匹配用户设备的大小与分辨率交付。教案建议参考 The HTTP Archive 的页面重量历史报告观察页面体积的长期增长趋势,并用 WebPageTest 之类的在线测速服务跑一遍常见性能检查。

2. DOM 遍历(DOM traversals)

浏览器必须依据你的代码构建 DOM(Document Object Model)。保持页面高性能的关键是把标签数量压到最少,只用、只样式化页面真正需要的东西。教案特别指出:与某单页强绑定的 CSS 不必塞进主样式表——样式应按需组织。

3. JavaScript 渲染阻塞(render-blocking scripts)

同步脚本会在 DOM 遍历与绘制完成前阻塞管线。教案建议对内联脚本使用 defer 属性——这正是仓库中 Terrarium 模块 的实际做法(<script defer> 使脚本在 DOM 解析完成后再执行)。

理解完这三点后,接下来就是教案的核心实操:完成扩展的后台任务代码。

四、编写颜色计算函数 calculateColor()

教案要求在 /src/index.js 中、紧跟一组获取 DOM 引用的 const 变量之后,加入如下函数(以下为教案原文代码,完整继承):

function calculateColor(value) {
	let co2Scale = [0, 150, 600, 750, 800];
	let colors = ['#2AA364', '#F5EB4D', '#9E4229', '#381D02', '#381D02'];

	let closestNum = co2Scale.sort((a, b) => {
		return Math.abs(a - value) - Math.abs(b - value);
	})[0];
	console.log(value + ' is closest to ' + closestNum);
	let num = (element) => element > closestNum;
	let scaleIndex = co2Scale.findIndex(num);

	let closestColor = colors[scaleIndex];
	console.log(scaleIndex, closestColor);

	chrome.runtime.sendMessage({ action: 'updateIcon', value: { color: closestColor } });
}

逐段拆解这段代码在做什么:

  • co2Scale 是碳强度(克 CO2/千瓦时)分档刻度 [0, 150, 600, 750, 800]colors 是与之等长的一一对应色板:从绿 #2AA364(清洁)→ 黄 #F5EB4D(中等)→ 橙红 #9E4229(偏高)→ 深棕 #381D02(高碳),末两档同色意味着 750 以上全部归入最深色;
  • 传入 API 返回的碳强度值 value 后,用 sort 配合"与目标值的绝对差"比较器,把最接近的刻度值排到首位(closestNum);
  • 再用 findIndex 找到第一个大于 closestNum 的元素下标 scaleIndex,用它从 colors 中取出对应颜色 closestColor
  • 最后通过 chrome.runtime.sendMessage{ action: 'updateIcon', value: { color } } 消息发给扩展运行时,交由后台脚本消费。

细节提示:从源码结构看,Array.prototype.sort 的比较器本应返回严格的全序关系,而这里"与 value 的绝对差"并不总是满足传递性;仓库的实现沿用了这一写法并在实践中有效(配合 findIndex 总能取到正确的色档下标),阅读时理解其意图即可,若自行移植到对顺序有严格依赖的场景,建议改用显式的"遍历求最小差值"写法。

对照仓库最终实现solution/src/index.js 中的 calculateColor 与教案版本在算法上完全一致,工程化差异有三处——改为 async 箭头函数、两处 console.log 被注释掉、let 声明保留。也就是说,教案代码与仓库成品之间只差"打磨",逻辑没有分叉。

关于 chrome.runtime API,教案引用了 Chrome 扩展文档的定义:"使用 chrome.runtime API 可获取后台页面、返回 manifest 详情、监听并响应应用或扩展生命周期中的事件,还可将相对路径 URL 转换为完整 URL。" 在本扩展中它承担的核心职责是跨脚本上下文的消息总线:popup/内容脚本负责"说什么",后台脚本负责"做什么"。

教案还特别提醒了 Edge 开发者:虽然调用的是 chrome.* 前缀的 API,但新版 Edge 基于 Chromium 引擎,这些扩展工具在 Edge 上同样可用,无需意外。

五、在 init() 中设置默认图标颜色

在数据拉取完成之前,工具栏图标需要一个"合法起点"。教案要求在 init() 函数中主动调用一次 updateIcon 动作,把图标设为通用绿色:

chrome.runtime.sendMessage({
	action: 'updateIcon',
		value: {
			color: 'green',
		},
});

仓库实现中这段代码位于 solution/src/index.jsinit() 内,其时序含义是:扩展弹窗加载 → init() 先无条件把图标刷成绿色(用户立刻看到扩展是"活的")→ 再检查 localStorage 中是否已有 apiKey/region,有则直接请求数据,无则展示表单。默认绿图标保证了"任何时刻图标都有意义",避免用户对着空白图标困惑。

六、接通调用链:API 数据返回后触发计算

教案的第三步是把函数真正挂进数据流:在 CO2Signal API 的 Promise 返回链中,于拿到 CO2 值后追加调用:

//let CO2...
calculateColor(CO2);

仓库的完整上下文(solution/src/index.js)展示了它嵌在 displayCarbonUsageaxios.get(...).then(...) 回调里:

let CO2 = Math.floor(data.carbonIntensity);
calculateColor(CO2);

至此形成一条完整的单向数据流:表单提交 → displayCarbonUsage 请求 API → 取整碳强度 → calculateColor 映射色档 → chrome.runtime.sendMessage 发消息 → 后台脚本换图标。教案借此完成模块收尾:npm run build 重新构建、刷新扩展、观察颜色变化——"该不该现在洗一桶衣服?现在你知道了"。

七、后台脚本:onMessage 监听与 Canvas 绘制图标

教案的最后一块代码放在 /dist/background.js 中,为后台任务管理器注册消息监听器(教案原文版本):

chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
	if (msg.action === 'updateIcon') {
		chrome.browserAction.setIcon({ imageData: drawIcon(msg.value) });
	}
});
//tomado de la extensión Energy Lollipop, ¡buena característica!
function drawIcon(value) {
	let canvas = document.createElement('canvas');
	let context = canvas.getContext('2d');

	context.beginPath();
	context.fillStyle = value.color;
	context.arc(100, 100, 50, 0, 2 * Math.PI);
	context.fill();

	return context.getImageData(50, 50, 100, 100);
}

这段代码的注释表明 drawIcon 借鉴自 Energy Lollipop 扩展(模块 README 的 Credits 一节同样提及这一图标概念来源)。执行逻辑:任何消息到达后台时,若 msg.action === 'updateIcon',就用 Canvas API 现画一个彩色圆点作为新图标。

drawIcon 的绘制细节值得逐行看:

  1. 创建一块画布并拿到 2D 上下文;
  2. beginPath() 开启新路径,fillStyle 设为消息携带的颜色;
  3. arc(100, 100, 50, 0, 2 * Math.PI) 以 (100,100) 为圆心、50 为半径画整圆,fill() 填充——这就是工具栏上看到的"圆点";
  4. getImageData(50, 50, 100, 100) 裁取圆所在的 100×100 区域像素数据,返回给 setIcon 作为 imageData

对照仓库成品实现solution/dist/background.js):

chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
	if (msg.action === 'updateIcon') {
		chrome.action.setIcon({ imageData: drawIcon(msg.value) });
	}
});
//borrowed from energy lollipop extension, nice feature!
function drawIcon(value) {
	let canvas = new OffscreenCanvas(200, 200);
	let context = canvas.getContext('2d');

	context.beginPath();
	context.fillStyle = value.color;
	context.arc(100, 100, 50, 0, 2 * Math.PI);
	context.fill();

	return context.getImageData(50, 50, 100, 100);
}

两处关键演进值得注意:

  • chrome.browserActionchrome.action:前者是 MV2 时代的 API,MV3 中统一为 chrome.action,与扩展清单匹配;
  • document.createElement('canvas')new OffscreenCanvas(200, 200):教案版本依赖 DOM 文档创建画布,而仓库成品改用 OffscreenCanvas,在后台上下文中不依赖(也不触碰)可见 DOM 即可离屏绘制,避免不必要的 DOM 开销——这恰是本课"后台任务要轻"主题的代码级体现。

清单文件印证了架构solution/dist/manifest.json 声明了 "manifest_version": 3、后台以 "service_worker": "background.js" 运行、action.default_popup 指向 index.html。可以推断,由于 MV3 的后台是 Service Worker(没有传统后台页面 DOM),使用 OffscreenCanvas 正是"在 Worker 环境里画图标"的正确姿势。

Canvas API 的完整学习路径,仓库在 太空游戏第二课:向 Canvas 绘图 中有专门课程(教案西班牙语版亦指向该课)。

八、构建、加载与验证

教案给出的验证闭环是三步:重新构建 → 刷新扩展 → 观察颜色变化。结合仓库工程文件,完整操作如下:

1. 安装依赖并构建(以 solution/package.json 为准):

npm install
npm run build

构建脚本 "build": "webpack" 由 webpack 5 打包(devDependencies 中为 webpack ^5.105.4webpack-cli ^5.1.4),运行环境要求 Node ≥ 18、npm ≥ 9;运行期依赖仅 axios ^1.15.0 一个库用于调用 CO2Signal API。仓库未提交 webpack 配置文件,构建行为以 package.json 脚本声明为准。

2. 加载扩展:按 solution/README.md 的说明,在 Edge 三点菜单进入扩展面板,选择"Load Unpacked"(加载解压缩扩展),在弹出的目录选择中打开 dist 文件夹即可;使用时还需在扩展界面填入 CO2Signal 的 API Key 与区域代码(例如美东的 US-NEISO)。

3. 验证消息链:填好表单后,displayCarbonUsage 拉到数据即触发 calculateColor,工具栏圆点应从初始的绿色切换为与实时碳强度对应的色档;清空区域缓存(resetlocalStorage.removeItem('region') 后重走 init())时图标会回到绿色默认态——这一来一回恰好完整演练了第五、六节的消息链路。

九、课后挑战与延伸学习

教案在结尾布置了两个开放任务,均值得投入:

  • 考古挑战:挑选几个历史悠久、仍然活跃的开源网站,翻阅其提交历史,判断它们是如何随年份演进优化性能的(搜索 optimizeperformance 之类的提交关键词),并总结最常见的性能痛点。
  • 作业:分析一个站点的性能。该作业要求对选定站点做"多工具"审计(浏览器 DevTools Performance 面板 + 至少两个第三方审计服务 + 网络瀑布分析),产出包含 Core Web Vitals(LCP、FID、CLS)解读、资源拆解、瓶颈根因分析与优化建议排序的 2–3 页报告,并附评分量规(分析深度、工具多样性、问题识别、建议质量、呈现专业性五个维度)。它把本课的"性能基线 + 三大问题区"方法论升级为完整的工作流程。

自学习建议(教案原文保留):订阅性能类技术通讯保持跟进;打开不同浏览器的开发者工具 Performance 面板做横向对比,观察各厂商在指标口径与面板细节上的差异——本课"先采集数据、再谈优化"的立场在所有工具里都成立。

十、本课关键文件索引

文件 作用
3-background-tasks-and-performance/README.es.md 本课西班牙语教案(本文主体依据)
3-background-tasks-and-performance/README.md 对应英文版教案,含更多流程图与自检清单
solution/src/index.js 成品 popup 脚本:calculateColorinit()、API 数据流
solution/dist/background.js 成品后台脚本:onMessage 监听 + OffscreenCanvas 图标绘制
solution/dist/manifest.json MV3 清单:Service Worker 后台 + popup 动作
solution/package.json 构建脚本与环境要求(webpack、Node ≥ 18)
start/src/index.js 学生起点脚手架,对照成品即为本课改动面
assignment.md 性能分析作业与评分量规
登录后查看全文
热门项目推荐
相关项目推荐