Web-Dev-For-Beginners 浏览器扩展实战(三):chrome.runtime 消息通信、动态图标绘制与浏览器性能剖析
本文基于开源课程仓库 Web-Dev-For-Beginners 的浏览器扩展模块第三课(西班牙语版教案 README.es.md),以仓库中的 Carbon Trigger 碳强度跟踪扩展为真实案例,完整讲解扩展后台任务链的实现——从 popup 脚本发送 chrome.runtime.sendMessage 消息,到后台脚本监听 onMessage 并用 Canvas 绘制动态图标——并给出使用浏览器开发者工具 Performance 面板做性能剖析、识别资源瓶颈的实操方法。读完后你将掌握扩展脚本间消息通信的完整调用链、MV3 后台服务 Worker 的图标更新机制,以及用开发者工具建立性能基线的标准流程。
一、本课定位:补全 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(剖析器)。
操作要点(教案原文步骤的完整继承):
- 打开一个目标网站(教案示例为 microsoft.com);
- 点击 Record(录制) 按钮,然后刷新页面,让 Profiler 完整捕获加载过程;
- 随时停止录制,时间线上会展示浏览器为 script(脚本)、render(渲染)、paint(绘制) 站点所产生的全部例行事件;
- 在时间线上选取一个片段,右侧 Summary(摘要)面板给出该时段的性能快照(加载量、内存峰值、事件统计);
- 检查 Event Log(事件日志) 面板,确认是否存在耗时超过 15 ms 的事件——这是教案给出的单事件性能警戒线。
两条来自教案的高价值提示:
- 先清缓存再测:要获得站点真实的"首访"启动耗时,先清除浏览器缓存,缓存命中与冷启动的读数是两个世界;
- 剖析扩展本身要"就地取材":扩展拥有自己独立的浏览器上下文实例,想给扩展做性能剖析,必须从扩展自身弹出的开发者工具里打开面板,而不是从普通网页的 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.js 的 init() 内,其时序含义是:扩展弹窗加载 → init() 先无条件把图标刷成绿色(用户立刻看到扩展是"活的")→ 再检查 localStorage 中是否已有 apiKey/region,有则直接请求数据,无则展示表单。默认绿图标保证了"任何时刻图标都有意义",避免用户对着空白图标困惑。
六、接通调用链:API 数据返回后触发计算
教案的第三步是把函数真正挂进数据流:在 CO2Signal API 的 Promise 返回链中,于拿到 CO2 值后追加调用:
//let CO2...
calculateColor(CO2);
仓库的完整上下文(solution/src/index.js)展示了它嵌在 displayCarbonUsage 的 axios.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 的绘制细节值得逐行看:
- 创建一块画布并拿到 2D 上下文;
beginPath()开启新路径,fillStyle设为消息携带的颜色;arc(100, 100, 50, 0, 2 * Math.PI)以 (100,100) 为圆心、50 为半径画整圆,fill()填充——这就是工具栏上看到的"圆点";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.browserAction→chrome.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.4、webpack-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,工具栏圆点应从初始的绿色切换为与实时碳强度对应的色档;清空区域缓存(reset → localStorage.removeItem('region') 后重走 init())时图标会回到绿色默认态——这一来一回恰好完整演练了第五、六节的消息链路。
九、课后挑战与延伸学习
教案在结尾布置了两个开放任务,均值得投入:
- 考古挑战:挑选几个历史悠久、仍然活跃的开源网站,翻阅其提交历史,判断它们是如何随年份演进优化性能的(搜索
optimize、performance之类的提交关键词),并总结最常见的性能痛点。 - 作业:分析一个站点的性能。该作业要求对选定站点做"多工具"审计(浏览器 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 脚本:calculateColor、init()、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 | 性能分析作业与评分量规 |
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00


