首页
/ Web-Dev-For-Beginners 浏览器扩展实战:chrome.runtime 消息传递、动态图标与 Web 性能剖析

Web-Dev-For-Beginners 浏览器扩展实战:chrome.runtime 消息传递、动态图标与 Web 性能剖析

2026-09-06 15:56:46作者:吴年前Myrtle

本篇是 Web-Dev-For-Beginners 课程中浏览器扩展项目(Carbon Trigger 碳强度追踪扩展)三部曲的最后一课,核心主题是「背景任务(Background Tasks)与 Web 性能」。读完本文,你将掌握:如何用浏览器 Developer Tools 的 Performance 面板录制并解读 script / render / paint 数据,识别资产体积、DOM 复杂度与 render-blocking 脚本三大性能问题区域,并通过 chrome.runtime 消息传递 + Canvas API 实现扩展图标随实时碳强度数据动态变色的完整链路。

背景:系列最后一课要完成什么

在前两课中,扩展的「内容脚本」部分已经完成:一个收集用户 API Key 与地区的表单、调用 CO2Signal API 的异步请求、以及渲染输出区域——这是构建网页存在感的标准套路:表单提交后异步拉取数据、处理后展示。

本课时要收尾的是两件事:

  1. 背景任务管理:让扩展图标颜色随碳强度(grams of CO2 per kWh)变化,这部分逻辑运行在浏览器为扩展单独划出的后台环境中,需要通过消息机制与弹出窗口(popup)通信;
  2. 性能意识:在构建 Web 资产(无论是普通网页还是扩展)时,学习如何发现并预防性能问题。

课程仓库中扩展项目的起点代码位于 5-browser-extension/start,完整实现位于 5-browser-extension/solution,两者共用同一套 dist/ 产物结构(manifest.jsonbackground.jsindex.htmlmain.jsstyles.css)。

Web 性能基础:用 Performance 面板做性能"体检"

“Website performance is about two things: how fast the page loads, and how fast the code on it runs.” —— Zack Grossbart

在不同设备、不同网络、不同使用场景下让站点跑得又快又稳是一个庞大的主题。优化之前,第一步永远是收集性能数据,而第一站就是浏览器自带的开发工具。

打开 Performance 面板并录制

在 Edge 中,点击浏览器右上角的「三个点」,进入 More Tools > Developer Tools,打开 Performance 标签页;也可以用快捷键 Ctrl + Shift + I(Windows)或 Option + Command + I(Mac)。

录制流程:

  1. 打开一个网站(例如 microsoft.com);
  2. 点击 Record 按钮开始录制;
  3. 刷新页面,让剖析器捕获完整加载过程;
  4. 随时停止录制,即可看到浏览器为「script(脚本执行)」「render(渲染)」「paint(绘制)」所做的完整时间线。

Edge 浏览器 Developer Tools 的 Performance 标签页录制结果

提示:想看到网站真实的"冷启动"速度,测试前请清除浏览器缓存,因为首次访问与命中缓存后的表现差异很大。

解读剖析结果:时间线、快照与事件日志

  • 时间线:选中 profile 时间线的某一段,即可放大查看页面加载期间发生的具体事件;
  • 性能快照:选中时间线片段后查看 Summary 面板,可获得该时间段页面性能的整体快照:

Performance 面板的时间线选择与性能快照视图

  • 事件日志(Event Log):重点检查是否存在耗时超过 15ms 的事件,这是识别阻塞用户输入与长任务的关键阈值:

Performance 面板的 Event Log 事件日志

动手练习:对任意一个站点打开开发者工具进行剖析,找出加载最慢的资源与最快的资源,定位当前页面的瓶颈在哪里。

剖析时的三大"问题区域"

所有 Web 开发者在构建站点时都应注意以下几类常见问题,以免在生产环境中遇到意外:

1. 资产体积(Asset size):过去几年 Web 页面持续"变重",其中相当一部分重量来自图片。应检查图片是否经过优化,是否以适合当前用户的尺寸与分辨率提供(不要把桌面级大图发给手机)。可参考 Internet Archive 的页面重量报告了解页面体积的历史增长趋势,也可以用 WebPageTest 之类的站点速度测试工具了解性能检测的常见检查项。

2. DOM 遍历(DOM traversals):浏览器要基于你写的标记构建 Document Object Model,因此保持标签精简、只使用并样式化页面真正需要的内容,是良好页面性能的前提。与之配套的是 CSS 优化:例如只在某一个页面使用的样式,没必要塞进主样式表。

3. JavaScript 的 render-blocking 问题:如果脚本必须等浏览器遍历完其余 DOM 才能执行,就会阻塞渲染。对此应考虑内联脚本配合 defer 属性的用法(课程 Terrarium 模块中就是这样做的),让脚本在 DOM 解析完成后再执行。

理解完浏览器如何渲染你发送的资产,就可以完成扩展最后几项工作了。

动手:创建颜色计算函数 calculateColor()

src/index.js 中,紧跟之前设置好的、用于访问 DOM 的那组 const 变量之后,添加 calculateColor() 函数:

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

这段代码的工作方式值得逐步拆解:

  1. 两个平行数组co2Scale 是碳强度刻度(0 / 150 / 600 / 750 / 800 g/kWh),colors 是对应的 5 档颜色,从绿色 #2AA364(清洁)过渡到黄、橙、深棕(高碳);
  2. 找最近刻度sort((a, b) => Math.abs(a - value) - Math.abs(b - value)) 按「与传入值的距离」升序重排刻度数组,[0] 即最接近实际碳强度的刻度点;
  3. 映射颜色索引findIndex((element) => element > closestNum) 找到第一个大于最近刻度的位置,即碳强度所处色档的索引,再取 colors[scaleIndex]
  4. 发消息给后台:最终通过 chrome.runtime.sendMessage{ action: 'updateIcon', value: { color: closestColor } } 发给扩展的后台脚本。

从仓库最终实现的 solution/src/index.js 可以看到,这一函数被写成 async 箭头函数、并移除了调试用的 console.log,调用方 displayCarbonUsage 在请求 https://api.co2signal.com/v1/latest(带 countryCode 参数与 auth-token 请求头)成功后,取 Math.floor(data.carbonIntensity) 作为 CO2 传入:

// solution/src/index.js 中的实际调用(节选)
let CO2 = Math.floor(data.carbonIntensity);
calculateColor(CO2);

从源码结构看还有一个值得注意的边界:当碳强度超过刻度上限 800 时,findIndex 返回 -1,colors[-1]undefined,此时图标颜色不会被更新——刻度数组是按目标地区(例如北美电力强度范围)设计的,这属于教学示例的简化处理。

chrome.runtime API:扩展的"神经系统"

chrome.runtime 是控制各类后台任务的核心 API。官方对它的描述是:

"Use the chrome.runtime API to retrieve the background page, return details about the manifest, and listen for and respond to events in the app or extension lifecycle. You can also use this API to convert the relative path of URLs to fully-qualified URLs."

它承担的职责包括:获取后台页面、读取 manifest 信息、监听并响应扩展生命周期事件、在 popup 脚本与后台脚本之间传递消息等。

两点实践提示:

  • 在 Edge 上也能用 chrome.* API:新版 Edge 基于 Chromium 引擎,Chrome 扩展 API 在 Edge 中同样可用,开发同一套扩展代码时不必惊讶;
  • 扩展要单独剖析:由于扩展运行在独立的浏览器实例中,若要为扩展做性能剖析,应从扩展本身(例如扩展页面 / Service Worker 入口)启动它的专属 DevTools,而不是从普通网页的开发者工具入手。

设置默认图标颜色

init() 函数中,先向 chrome 发出一次 updateIcon 动作,把启动时的图标设为朴素的绿色,让用户在数据加载完成前就能看到扩展处于可工作状态:

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

仓库最终实现的 solution/src/index.js 中,init() 的完整逻辑是:先发送绿色默认图标,再从 localStorage 读取保存的 apiKeyregion——若两者缺失就显示表单,否则隐藏表单并立即调用 displayCarbonUsage 拉取数据。这样扩展每次打开都有确定的初始视觉状态。

打通调用链:API 数据 → 颜色 → 图标

接下来把新函数挂到 CO2Signal API 返回的 promise 上。在拿到碳强度数值之后调用:

//let CO2...(API 返回并取出的碳强度值)
calculateColor(CO2);

最后一步在 dist/background.js 中:添加一个响应这些后台动作调用的消息监听器,并用 Canvas API 现场绘制对应颜色的图标:

chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
	if (msg.action === 'updateIcon') {
		chrome.browserAction.setIcon({ imageData: drawIcon(msg.value) });
	}
});
//borrowed from energy lollipop extension, nice feature!
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 的几何逻辑:在画布上以 (100, 100) 为圆心、50 为半径填充指定颜色的圆,再截取 (50, 50) 起点、100×100 的 ImageData 交给 setIcon,浏览器自动把它作为工具栏图标使用。Canvas API 的完整用法可以在课程的 Space Game 画布绘图课 中进一步学习。

仓库最终实现:Manifest V3 与 OffscreenCanvas

值得对照的是,仓库中实际交付的 solution/dist/background.js 与课文示例有一处关键差异,它反映了扩展平台向 Manifest V3 的演进:

// solution/dist/background.js(实际交付版本)
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
	if (msg.action === 'updateIcon') {
		chrome.action.setIcon({ imageData: drawIcon(msg.value) });
	}
});

function drawIcon(value) {
	let canvas = new OffscreenCanvas(200, 200);
	let context = canvas.getContext('2d');
	// ... 同样的 arc + fill + getImageData(50, 50, 100, 100)
}

对照 solution/dist/manifest.json 可以解释这一差异:

{
	"manifest_version": 3,
	"name": "My Carbon Trigger",
	"version": "0.1.0",
	"host_permissions": ["<all_urls>"],
	"background": { "service_worker": "background.js" },
	"action": { "default_popup": "index.html" }
}
  • MV3 的后台不再是普通页面而是 service worker"background": { "service_worker": "background.js" }),其中没有 document,因此不能 document.createElement('canvas'),交付版本改用 new OffscreenCanvas(200, 200) 在后台上下文里完成绘制,不阻塞任何 UI;
  • 配套的图标 API 也从 MV2 的 chrome.browserAction 演进为 MV3 的 chrome.action
  • default_popup 指向 index.html,即扩展图标点击后打开的弹出窗口页面。

整条消息链路因此是:popup 脚本(webpack 打包产物 dist/main.js)在 API 数据到达后调用 calculateColorchrome.runtime.sendMessage → service worker 中的 onMessage 监听器 → OffscreenCanvas 绘制 → chrome.action.setIcon 更新工具栏图标。

构建与验证

solution/package.json 可以看到项目的构建方式与运行前提:

  • 构建脚本:npm run build(执行 webpack)、npm run watchwebpack --watch 持续重打包);
  • 环境要求:node >= 18.0.0npm >= 9.0.0
  • 依赖:axios(用于 API 请求),开发依赖为 webpack / webpack-cli

验证步骤:执行 npm run build 重新打包 → 在浏览器扩展管理页重新加载扩展(这一步容易被遗漏)→ 打开扩展,观察图标颜色随真实碳强度数据变化。至此扩展具备了一个很实用的场景:一眼看出当前电网是否"干净",从而判断是否适合开洗衣机或充电动车。

挑战与课后自测

挑战题:挑选几个历史悠久的开源站点,查看其代码仓库的提交历史,观察它们多年来为性能做了哪些优化,哪些问题反复出现。建议搜索包含 "optimize"、"performance"、"faster" 等关键词的提交信息,归纳共性瓶颈。

自我学习

  • 可以关注专注于 Web 性能的专业 newsletter(例如 perf.email)持续跟进;
  • 对比不同浏览器 Web 工具中的 performance 面板,观察它们在度量网页性能方式上的差异。

课后作业:为真实站点做性能分析

本课程的配套作业是 Analyze a site for performance。作业要求选择真实网站,使用至少三种分析手段(浏览器 DevTools Performance 面板、Lighthouse / WebPageTest 等在线审计工具、网络瀑布流分析)完成一份 2–3 页的性能报告,覆盖:

  • 指标分析:多工具加载时间、Core Web Vitals(LCP / FID / CLS)、资源体积构成、阻塞资源识别;
  • 问题定位:每个瓶颈需附数据支撑、根因分析与用户影响评估,并按严重程度与修复难度排优先级;
  • 优化建议:给出可落地的改进项(懒加载、压缩、现代最佳实践)及预期收益,并附上工具截图、瀑布图、前后对比等可视化证据。

评分细则中,"4 种以上工具 + 深度根因分析 + 用户影响评估"对应最高档(90–100%),这提示剖析能力本身——而不仅是跑一遍 profiler——才是本课真正要沉淀的技能。

登录后查看全文
热门项目推荐
相关项目推荐