- 教程
- 文档
- 示例工程
【免费下载链接】website-archive
Archive of the Coding Train website (first version)
导读:本文以《Programming with Text》课程第 11 章“Chrome Extensions”专题中的Pop-ups一课为骨架,系统讲解 Chrome 扩展“弹窗”机制:什么是 pop-up 扩展、如何通过
browser_action注册点击弹窗、如何在弹窗页(HTML+JavaScript)中与 background script、content script 进行双向消息通信,并以本仓库 session11/11-06_Pop-ups_Messaging 的完整示例与 CC_084_Word_Definition_Extension 实战项目为源码佐证。读完本文,你将能够独立构建一个“点击工具栏图标 → 弹出交互面板 → 修改当前网页内容”的完整 Chrome 扩展。
一、什么是 Pop-up 扩展
在 Chrome 扩展体系中,pop-up(弹窗)是一类特殊的扩展页面:它是一张以独立小窗口/覆盖层形式悬浮在当前标签页之上的网页,最常见的触发方式是用户点击工具栏上的扩展图标按钮(即 "browser action")时弹出。原文档 11.5-pop-ups.md 给出的定义可以概括为:
A Pop-up extension is a webpage that opens as a separate window/overlay on top of the current tab. It is typically opened when the extension button is pressed (a "browser action").
与同系列其他扩展形态相比,pop-up 的定位非常明确:
| 扩展组件 | 运行位置 | 典型用途 | 对应课程章节 |
|---|---|---|---|
| Pop-up | 点击工具栏图标时弹出的独立小页面 | 提供交互面板、输入框、按钮,或展示当前页面信息 | 本文(11.5) |
| Background script | 浏览器启动即运行的后台 JS | 监听全局事件(菜单、地址栏、扩展按钮等) | 11.4-background-scripts.md |
| Content script | 注入到当前网页的 JS | 读取、修改页面 DOM | 11.3-content-scripts.md |
| New Tab Override | 替换新建标签页 | 定制新标签页内容 | 11.7-new-tab-override.md |
需要特别说明的是,pop-up 虽然是一张“网页”,但它不是普通标签页:它在点击图标时出现、在用户点击页面其他位置时自动关闭,生命周期短暂。这也决定了它通常作为“入口/控制台”存在——真正持久的数据与逻辑放在 background script 中,真正操作页面 DOM 的工作交给 content script,pop-up 负责把它们串起来。这正是本课与下一课 11.6-pop-ups-messaging.md 的核心议题。
二、用 browser_action 注册 pop-up:manifest.json 详解
要让扩展图标被点击时弹出页面,需要在扩展清单manifest.json中声明browser_action,并指定default_popup指向弹窗 HTML 文件。仓库中 11-06_Pop-ups_Messaging/manifest.json 给出了一个完整可用的配置:
{ "manifest_version": 2, "name": "Coding Train Pop Up", "version": "0.1", "content_scripts": [ { "matches": ["<all_urls>"], "js": ["content.js"] } ], "content_security_policy": "script-src 'self' https://cdn.jsdelivr.net/npm/p5@1.1.4/lib/p5.min.js; object-src 'self'", "permissions": ["tabs"], "browser_action": { "default_icon": "ct.png", "default_popup": "sketch/index.html", "default_title": "Generate a maze!" } }各字段的含义与作用如下:
browser_action.default_popup:弹窗页面路径(相对manifest.json所在目录)。这里指向sketch/index.html,即 pop-up 就是一个普通的 HTML 页面,里面照常引用 CSS 与 JavaScript。browser_action.default_icon:工具栏图标文件(示例为ct.png)。browser_action.default_title:鼠标悬停在图标上时显示的提示文字。content_security_policy:弹窗页同普通扩展页一样受 CSP 约束。示例中允许从https://cdn.jsdelivr.net/npm/p5@1.1.4/lib/p5.min.js加载 p5.js,说明弹窗页面可以完整使用 p5.js 等库,这是本课程大量使用 p5.js 做可视化/交互界面(如迷宫生成、查词定义展示)的关键配置。permissions: ["tabs"]:申请tabs权限,弹窗页才能通过chrome.tabs.query()获取当前标签页信息并向其发送消息(详见下文)。content_scripts:声明注入所有页面的 content script,为“弹窗 → 页面”通信铺路。
对照阅读仓库中另一份示例 CC_084_Word_Definition_Extension/JavaScript/manifest.json(Word Lookup 查词扩展),可以看到同样的模式:browser_action.default_popup指向sketch/index.html,background声明background.js,content_scripts注入content.js。也就是说,一个典型的三层结构(pop-up + background + content)在清单中就是由这三段配置共同描述的。
版本提示:本仓库示例均基于
manifest_version: 2(MV2)。MV2 与 MV3(manifest_version: 3)在后台脚本、CSP 等字段上有差异,运行示例时请以本仓库代码为准,在 Chrome 中加载本地扩展时选择“开发者模式”即可。
三、弹窗页面本身:用 HTML + JavaScript(含 p5.js)写交互面板
default_popup指向的 HTML 就是一个普通网页,示例 11-06_Pop-ups_Messaging/sketch/index.html 内容如下:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>maze_challenge</title> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/p5@1.4.1/lib/p5.min.js"></script> <script type="text/javascript" src="sketch.js"></script> </head> <body> <h1>Paragraph Replacer</h1> <p> <input id="userinput" /> </p> </body> </html>该弹窗是一个“Paragraph Replacer(段落替换器)”:页面中只有一个文本框#userinput,用户输入文字后,当前网页中所有<p>段落的内容会被替换成该文字。注意这里的页面宽度由弹窗尺寸决定,实际开发时通常会在 HTML/CSS 中显式限制宽度(例如 CC_084 的 sketch/index.html 中设置了body { width: 800px; }),避免弹窗过宽。
对应的 sketch.js 使用 p5.js 的setup()初始化并监听输入事件:
function setup() { noCanvas(); let userinput = select('#userinput'); userinput.input(changeText); function changeText() { console.log('text changed'); let params = { active: true, currentWindow: true }; chrome.tabs.query(params, gotTabs); function gotTabs(tabs) { console.log('got tabs'); console.log(tabs); // send a message to the content script let message = userinput.value(); let msg = { txt: userinput.value() }; chrome.tabs.sendMessage(tabs[0].id, msg); } } }这段代码展示了 pop-up 页面中调用 Chrome 扩展 API 的典型流程:
noCanvas():本弹窗不需要 p5 画布,仅使用 p5 的选择器与事件绑定能力;select('#userinput').input(changeText):p5.js 的事件绑定,等价于 DOM 的input事件监听;chrome.tabs.query({ active: true, currentWindow: true }, gotTabs):查询当前活动窗口的活动标签页,拿到其tabs[0].id;chrome.tabs.sendMessage(tabs[0].id, msg):向该标签页注入的 content script 发送{ txt: '用户输入' }消息。
这就是“弹窗 ↔ 页面”通信的最短路径:pop-up 用chrome.tabs.sendMessage直接发给 content script,无需经过 background。由于该 API 需要tabs权限,所以清单中才出现了"permissions": ["tabs"]。
四、消息的另一端:content script 如何接收并改写页面
接收方是注入页面中的 content.js:
console.log('Chrome extension go?'); chrome.runtime.onMessage.addListener(gotMessage); function gotMessage(message, sender, sendResponse) { console.log(message); let paragraphs = document.getElementsByTagName('p'); for (elt of paragraphs) { elt.innerHTML = message.txt; } }关键点:
chrome.runtime.onMessage.addListener(gotMessage):注册消息监听器,gotMessage的第一个参数即对方发来的消息对象;document.getElementsByTagName('p'):content script 运行在页面上下文中,因此可以像普通脚本一样操作页面 DOM;elt.innerHTML = message.txt:把每个<p>的 HTML 内容整体替换为弹窗输入框中的文字。
对照同目录的背景版示例 11-04_Chrome_Extensions_Background_Scripts/content.js,其中gotMessage在收到message.txt === 'hello'时会把段落背景改为紫色(#FF00FF)——说明 content script 收到消息后可以执行任意 DOM 操作,弹窗只是“指令发起者”。
此外还有一条反向路径值得注意:content script 也可以主动向扩展其他部分发消息。在 CC_084_Word_Definition_Extension/JavaScript/content.js 中,用户用鼠标选中网页上的文字后:
window.addEventListener('mouseup', wordSelected); function wordSelected() { let selectedText = window .getSelection() .toString() .trim(); console.log(selectedText); if (selectedText.length > 0) { let message = { text: selectedText }; chrome.runtime.sendMessage(message); } }即通过chrome.runtime.sendMessage(message)把选中的文字发给 background script(详见下一节)。这说明消息通信是双向、多跳的:content → background → pop-up 可以构成完整链路。
五、通过 background script 中转:pop-up 读取后台共享状态
有些场景下,pop-up 需要的数据并不在 pop-up 自身,而在 background script 中。原文档对应的延伸示例 11-06_Pop-ups_Messaging/background.js 展示了 background 如何接收消息并保存状态:
console.log('background running'); chrome.browserAction.onClicked.addListener(buttonClicked); function buttonClicked(tab) { let msg = { txt: 'hello' }; chrome.tabs.sendMessage(tab.id, msg); }该 background 监听chrome.browserAction.onClicked:当用户点击扩展图标时向当前标签页的 content script 发送{ txt: 'hello' }(此处因为清单中已配置default_popup,图标点击会优先弹出 pop-up,onClicked与 pop-up 二选一使用,二者不能同时生效)。
更值得借鉴的是 CC_084_Word_Definition_Extension/JavaScript/background.js 中“把 background 当作共享存储”的做法:
console.log('background running'); chrome.runtime.onMessage.addListener(receiver); window.word = 'coding train'; function receiver(request, sender, sendResponse) { console.log(request); word = request.text; }这里window.word是挂在 background 全局对象上的变量。content script 选中的文字通过chrome.runtime.sendMessage送达后,background 更新word;随后 pop-up 通过chrome.extension.getBackgroundPage()直接读取:
// Word Lookup pop-up (CC_084) function setup() { noCanvas(); let bgpage = chrome.extension.getBackgroundPage(); let word = bgpage.word.trim(); // ... 用 word 拼装 Wordnik API 请求并 loadJSON 展示释义 }chrome.extension.getBackgroundPage()返回 background 页面的全局对象,pop-up 借此拿到bgpage.word,再请求 Wordnik 词典 API 把释义显示在弹窗中(完整代码见 CC_084_Word_Definition_Extension/JavaScript/sketch/sketch.js)。这是 pop-up 与 background 之间最简单直接的“共享内存”式通信,适合数据量小、无需回调的场合。
六、消息通信机制小结:三条典型链路
综合以上仓库源码,pop-up 参与的消息通信可以归纳为三条典型链路,它们覆盖了大多数实际需求:
链路一:pop-up → content script(弹窗直控页面)pop-up 用chrome.tabs.query({ active: true, currentWindow: true })拿到标签页 id,再用chrome.tabs.sendMessage(tabId, msg)发送;content script 用chrome.runtime.onMessage.addListener()接收。 代表实现:sketch.js + content.js。
链路二:content script → background(页面数据回传)content script 用chrome.runtime.sendMessage(msg)发送;background 用chrome.runtime.onMessage.addListener()接收并写入全局变量。 代表实现:content.js + background.js。
链路三:pop-up → background(读取共享状态)pop-up 用chrome.extension.getBackgroundPage()直接读取 background 的全局属性,无需异步消息。 代表实现:sketch.js。
需要留意chrome.tabs.sendMessage与chrome.runtime.sendMessage的区别:前者定向发送给指定标签页的 content script(需要tabs权限与标签页 id);后者广播给扩展自身的 background / 其他扩展页面。两者方向不同、用途不同,选择时以“发送目标”为判断依据。
七、加载、运行与调试 Pop-up 扩展
本仓库是只读归档,无法在此直接运行扩展,但你可以按以下流程在本地浏览器中加载示例验证(Chrome 桌面版):
- 将示例目录(如
Courses/programming_with_text/session11/11-06_Pop-ups_Messaging)复制到本地可写目录; - 打开 Chrome,进入
chrome://extensions/; - 打开右上角“开发者模式(Developer mode)”开关;
- 点击“加载已解压的扩展程序(Load unpacked)”,选择该目录;
- 点击
ct.png图标,弹出 "Paragraph Replacer" 面板,在输入框中打字,观察当前网页中所有<p>段落被替换。
调试要点:
- 弹窗页调试:在弹窗中右键 → “检查”,即可打开 DevTools 查看 pop-up 的 console 与 DOM;
- background / content script 调试:在
chrome://extensions/对应扩展卡片上点击“背景页 / service worker”链接查看 background console;content script 的日志会打印在其所在网页的 console 中; - 消息是否送达:各示例代码中保留了
console.log('text changed')、console.log('got tabs')、console.log(message)等日志,可据此逐跳确认消息链路; - 加载本地扩展的限制:Chrome 对开发者模式加载的本地扩展 API 限制较少(如
chrome.tabs.query、sendMessage均可使用),但以非商店签名扩展发布给用户仍受限制,本文不展开。
八、实践注意事项
结合仓库示例代码,整理几个容易踩坑的点:
default_popup与browserAction.onClicked互斥:清单配置了default_popup后,图标点击弹出 pop-up,onClicked回调不再触发;若需要“点击图标直接执行动作”而非弹窗,就不要配default_popup(如 11-04_Chrome_Extensions_Background_Scripts/manifest.json 只配了default_icon)。- pop-up 生命周期短暂:点击页面其他区域弹窗即关闭,
setup()中绑定的状态随之销毁;需要跨弹窗保持的数据应放到 background(如window.word)或chrome.storage中。 - CSP 决定能否用 p5.js:弹窗页从 CDN 加载库必须显式写入
content_security_policy,否则会被拦截;本仓库示例统一允许cdn.jsdelivr.net/npm/p5@.../p5.min.js。 permissions按需申请:chrome.tabs.query/sendMessage需要tabs权限;不需要时不要滥用,避免安装提示过于吓人。- 消息对象建议用结构化对象:示例中统一使用
{ txt: ... }/{ text: ... }包裹数据,便于扩展更多字段,也便于在接收端区分消息类型。
九、延伸阅读
- 弹窗消息通信的进阶内容(pop-up ↔ content 双向对话)见同系列 11.6-pop-ups-messaging.md,仓库配套代码在 session11/11-06_Pop-ups_Messaging;
- 后台脚本的全局事件监听机制见 11.4-background-scripts.md;
- content script 的注入与页面修改见 11.3-content-scripts.md;
- 同系列扩展主题还有书签脚本(11.2-bookmarklets.md)、新标签页覆盖(11.7-new-tab-override.md)与跨浏览器扩展(11.8-cross-browser-extensions.md);
- 更多完整可运行示例位于 CodingChallenges/CC_082_Image_Chrome_Extension_The_Ex-Kitten-sion/JavaScript、CodingChallenges/CC_083_Chrome_Extension_with_p5js_Sketch/JavaScript 与 CodingChallenges/CC_084_Word_Definition_Extension/JavaScript。
综上,pop-up 扩展的开发模式可以概括为:清单里配好browser_action.default_popup,弹窗页负责交互与 UI,content script 负责操作页面,background 负责共享状态与全局事件,三者通过chrome.runtime/chrome.tabs消息 API 串成完整链路。掌握本文的配置与三条通信链路,你就拥有了构建任意“工具栏弹窗型”扩展的基础能力。
- 教程
- 文档
- 示例工程
【免费下载链接】website-archive
Archive of the Coding Train website (first version)
相关推荐
Meshery模型与设计:构建可复用的云原生架构
Meshery模型与设计:构建可复用的云原生架构 Meshery是一个功能强大的云原生管理平台,它提供了丰富的模型与设计工具,帮助开发者轻松构建可复用的云原生架
文档/教程Chrome 扩展与 App 消息通信示例解析:chrome-extensions-samples 中的 Messaging 跨应用通信实战
Chrome 扩展与 App 消息通信示例解析:chrome extensions samples 中的 Messaging 跨应用通信实战 导读 本文以 Ch
示例工程从工具栏玩"打地鼠":Chrome 扩展 Mole Game 的多扩展协作与跨扩展消息通信实战
从工具栏玩"打地鼠":Chrome 扩展 Mole Game 的多扩展协作与跨扩展消息通信实战 Mole Game 是 chrome extensions sa
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考