主题
Content Script 注入机制
本文引用的源码文件
本文引用的文件
目录
- 引言
- 项目结构与注入入口
- 静态注入与动态注入
- 匹配规则、注入时机与隔离模式
- Content Script 消息路由与工具接口
- Shadow DOM 穿透与跨 iframe 通信
- 页面工具调用规范
- 架构总览
- 依赖关系分析
- 性能与稳定性考虑
- 常见问题排查
- 最佳实践与使用示例
- 结论
引言
本文围绕扩展的 Content Script 注入机制展开,重点说明 manifest.json 中的 content_scripts 配置、Background 端的自动注入逻辑、Content Script 的消息路由与页面工具封装,以及 Shadow DOM 和 iframe 场景下的元素定位、数据提取与交互方案。文档同时给出实际调用路径、错误处理策略和最佳实践建议,帮助开发者在 MV3 环境下稳定地扩展网页能力。
项目结构与注入入口
本项目的 Content Script 入口是 src/content/index.js,由 manifest 中声明的 content_scripts 加载。该入口负责:
- 初始化 i18n;
- 注册全局快捷键(如截图快捷键);
- 维护消息路由表 HANDLERS;
- 区分同步与异步处理器;
- 限制部分工具只在顶层 frame 响应;
- 屏蔽扩展上下文失效导致的异常。
flowchart TD Start["浏览器加载页面"] --> Manifest["读取 manifest.json<br/>content_scripts 配置"] Manifest --> InjectStatic["静态注入 index.js"] InjectStatic --> InitI18n["初始化国际化"] InitI18n --> RegisterHandlers["注册消息处理器"] RegisterHandlers --> ListenMessage["监听 chrome.runtime.onMessage"] ListenMessage --> RouteHandler["按 message.type 分发到具体工具"] RouteHandler --> ToolModule["page-tools / page-interaction / page-extract / interaction-tools"] ToolModule --> Response["返回结果给 Background"]
图表来源
章节来源
静态注入与动态注入
本项目同时支持两种注入方式:
| 注入方式 | 触发位置 | 实现手段 | 适用场景 | 行为差异 |
|---|---|---|---|---|
| 静态注入 | manifest.json 的 content_scripts | 浏览器在匹配页面时自动加载脚本 | 常规网页、需要尽早可用的功能 | 受 run_at、all_frames、match_about_blank 等规则控制 |
| 动态注入 | Background 端 tool-executor 与 tool-helpers | chrome.scripting.executeScript 或执行函数内创建 script 标签 | 某些页面未自动注入、或需要按需注入 | 可重试、可检测已注入标记、可延迟发送消息 |
静态注入
manifest.json 中声明了全局匹配的 content_scripts,并指定 JS、CSS、注入时机与帧范围。
章节来源
动态注入
Background 端在无法直接通过 tabs.sendMessage 与页面通信时,会尝试动态注入 Content Script。
- tool-executor 中根据 manifest.content_scripts[0].js 查找包含 content 的文件,并通过 executeScript 注入;注入成功后多次重试发送消息。
- tool-helpers 中则通过 executeScript 执行一个函数,在页面中检查是否存在特定 DOM 标记,若不存在则动态插入 script 标签加载 Content Script URL。
sequenceDiagram participant BG as "Background" participant Tab as "目标标签页" participant CS as "Content Script" participant Page as "页面 DOM" BG->>BG : "判断是否可发送消息" BG->>Tab : "chrome.scripting.executeScript(files)" Tab-->>BG : "注入成功" BG->>Tab : "chrome.tabs.sendMessage(重试)" Tab->>CS : "onMessage 路由" CS->>Page : "调用页面工具" Page-->>CS : "返回结果" CS-->>BG : "sendResponse"
图表来源
- src/background/tool-executor.js#L1761-L1810
- src/background/tool-helpers.js#L400-L444
- src/content/index.js#L209-L246
章节来源
匹配规则、注入时机与隔离模式
manifest.json 当前 content_scripts 配置如下:
| 字段 | 值 | 含义 |
|---|---|---|
| matches | <all_urls> | 匹配所有 URL |
| js | libs/qrcode.min.js、libs/marked.min.js、src/content/index.js | 注入顺序依次加载 |
| css | styles/styles.css | 注入样式 |
| run_at | document_idle | 在文档空闲后注入,避免阻塞首屏 |
| all_frames | true | 在所有 iframe 中注入 |
| match_about_blank | true | 匹配 about:blank |
| match_origin_as_fallback | true | 同源回退匹配 |
这些配置决定了 Content Script 会在哪些页面、何时、以何种范围被注入。由于 all_frames 为 true,子 iframe 也会拥有独立的 Content Script 实例,因此需要顶层 frame 与子帧协作,避免重复响应或覆盖主页面内容。
章节来源
Content Script 消息路由与工具接口
Content Script 入口维护了一个 HANDLERS Map,将 message.type 映射到具体处理函数。它同时维护 ASYNC_HANDLERS 集合,用于判断是否需要保持消息通道开放并异步返回结果。
主要工具分类
- 页面读取:GET_PAGE_TEXT、GET_FULL_HTML、QUERY_ELEMENTS、GET_SELECTED_CONTENT
- 页面交互:INTERACT_ELEMENT、FILL_FORM、SCROLL_TO
- 表单输入:KEYBOARD_INPUT、FILE_UPLOAD
- 信息提取:EXTRACT_TABLE、EXTRACT_METADATA、EXTRACT_LINKS、EXTRACT_FORMS、EXTRACT_IMAGES、SEARCH_IN_PAGE、IFRAME_CONTENT、SCROLL_COLLECT
- 媒体输出:MANAGE_STORAGE、INJECT_CSS
- 异步工具:COPY_TO_CLIPBOARD、PASTE_FROM_CLIPBOARD、WAIT_ELEMENT、SELECT_DROPDOWN、QRCODE、START_REGION_SELECTION
顶层帧限制
部分工具只在顶层 frame 响应,避免 all_frames 下多个 iframe 同时响应导致结果不确定。例如 GET_PAGE_TEXT、GET_FULL_HTML、EXTRACT_METADATA、EXTRACT_TABLE、IFRAME_CONTENT、QUERY_ELEMENTS、INTERACT_ELEMENT、SCROLL_TO 等。
flowchart TD
Msg["收到消息"] --> CheckTop{"是否顶层帧必要类型"}
CheckTop --> |是且非顶层| Drop["丢弃消息"]
CheckTop --> |否| FindHandler["查找 HANDLERS"]
FindHandler --> Found{"找到处理器?"}
Found --> |否| Ignore["忽略"]
Found --> |是| Run["执行处理器"]
Run --> Async{"是否异步?"}
Async --> |是| Await["Promise.then(sendResponse)"]
Async --> |否| Sync["直接 sendResponse"]
图表来源
章节来源
Shadow DOM 穿透与跨 iframe 通信
项目提供了专门的 Shadow DOM 与 iframe 工具模块,用于深度查询、文本提取、HTML 提取、选择器生成、视口坐标计算等。
核心能力
- deepQuerySelector:递归穿透 Shadow DOM 和同源 iframe 查询单个元素
- deepQuerySelectorAll:递归查询所有匹配元素
- deepGetSelection:获取当前页面的选中内容,包括 Shadow DOM
- deepGetText:穿透 Shadow DOM 和同源 iframe 获取文本
- deepGetHtml:穿透 Shadow DOM 和同源 iframe 获取 HTML
- getDeepSelector:生成包含 Shadow DOM 路径的选择器
- findShadowRoot:查找元素所在的 ShadowRoot
- getElementViewportPosition:获取元素在视口中的位置,处理 iframe 偏移
- getRangeViewportPosition:获取选择范围在视口中的位置
- attachSelectionListeners / removeSelectionListeners:在所有 Shadow DOM 上附加或移除选择变化监听器
- hasShadowDom / hasIframe:检测页面是否包含 Shadow DOM 或 iframe
跨 iframe 通信
Content Script 通过 chrome.runtime.onMessage 在每个 frame 中独立运行。对于跨 iframe 的数据采集,orchestrator 会通过定向消息(_frameRouted)让子帧处理 ref 相关操作,避免顶层帧误响应。
graph TB TopFrame["顶层 Content Script"] --> SubFrame["子帧 Content Script"] TopFrame --> Tools["页面工具模块"] SubFrame --> Tools Tools --> ShadowDOM["Shadow DOM 工具"] Tools --> Iframe["iframe 内容提取"] ShadowDOM --> Query["deepQuerySelector / deepGetText"] Iframe --> Extract["getIframeContent / collectSnapshotOps"]
图表来源
- src/content/shadow-dom-utils.js#L12-L45
- src/content/shadow-dom-utils.js#L95-L173
- src/content/page-extract.js#L602-L663
- src/content/index.js#L209-L215
章节来源
- src/content/shadow-dom-utils.js#L12-L45
- src/content/shadow-dom-utils.js#L95-L173
- src/content/page-extract.js#L602-L663
- src/content/index.js#L209-L215
页面工具调用规范
Content Script 暴露的工具函数集中在以下模块:
| 模块 | 主要职责 | 典型方法 |
|---|---|---|
| page-tools.js | 基础页面读取与提取 | getPageText、getFullHtml、getSelectedContent、extractTable、copyToClipboard、pasteFromClipboard、hoverElement、highlightText |
| page-interaction.js | 元素交互与快照 | queryInteractiveElements、collectSnapshotOps、interactByRef、scrollAndCollect、getElementCount |
| page-extract.js | 结构化数据提取 | extractMetadata、extractLinks、extractForms、extractImages、searchInPage、getIframeContent、pageToMarkdown、pageToJson |
| interaction-tools.js | 用户交互操作 | clickElement、fillForm、scrollToPosition、waitForElement、keyboardInput、fileUpload、clickByText、selectDropdown、manageStorage |
元素选择与 ref 机制
page-interaction.js 实现了基于 ref 的元素引用机制:
- queryInteractiveElements 扫描可交互元素,分配唯一 ref;
- elementRegistry 保存 ref 到元素的映射;
- resolveByRef 支持元素断开后的 selector 兜底重查;
- interactByRef 通过 ref 执行点击、悬停、输入等操作。
classDiagram
class ElementRegistry {
+Map refToEntry
+WeakMap elementToRef
+registerElement(el, role, name) number
+resolveByRef(ref) object
+getSelectorByRef(ref) string
+getElementByRef(ref) Element
}
class InteractionTools {
+clickElement(selector, waitTime, timeout) Promise
+fillForm(fields, waitTime) object
+scrollToPosition(options) object
+waitForElement(selector, state, timeout) Promise
+keyboardInput(params) Promise
+fileUpload(selector, fileName, fileContent, fileType) object
+clickByText(text, options) Promise
+selectDropdown(triggerSelector, optionText, optionSelector, timeout) Promise
+manageStorage(params) object
}
ElementRegistry <.. InteractionTools : "ref 解析"
图表来源
章节来源
架构总览
整体架构分为三层:
- Background 层:负责工具调度、动态注入、重试机制、错误处理;
- Content Script 层:负责消息路由、页面工具封装、Shadow DOM 与 iframe 穿透;
- 页面层:提供 DOM、事件、剪贴板、存储等运行时能力。
graph TB subgraph "Background" BE["tool-executor.js"] BH["tool-helpers.js"] end subgraph "Content Script" CI["index.js"] PT["page-tools.js"] PI["page-interaction.js"] PE["page-extract.js"] IT["interaction-tools.js"] SDU["shadow-dom-utils.js"] end subgraph "页面环境" DOM["DOM / Shadow DOM / iframe"] Storage["localStorage / sessionStorage"] Clipboard["剪贴板"] end BE --> CI BH --> CI CI --> PT CI --> PI CI --> PE CI --> IT PT --> SDU PI --> SDU PE --> SDU CI --> DOM IT --> Storage PT --> Clipboard
图表来源
- src/background/tool-executor.js#L1761-L1810
- src/background/tool-helpers.js#L400-L444
- src/content/index.js#L74-L246
- src/content/page-tools.js#L53-L329
- src/content/page-interaction.js#L410-L575
- src/content/page-extract.js#L31-L126
- src/content/interaction-tools.js#L100-L789
- src/content/shadow-dom-utils.js#L12-L45
依赖关系分析
Content Script 内部模块之间存在清晰的依赖关系:
- index.js 作为入口,依赖 page-tools、page-interaction、page-extract、interaction-tools、shadow-dom-utils;
- page-tools 依赖 shadow-dom-utils 与 page-utils;
- page-interaction 依赖 shadow-dom-utils、page-utils、i18n;
- page-extract 依赖 shadow-dom-utils、page-utils、i18n;
- interaction-tools 依赖 shadow-dom-utils、page-utils、page-interaction、i18n。
这种分层设计使得页面读取、交互、提取、通用工具相互解耦,便于测试与维护。
graph LR Index["index.js"] --> PageTools["page-tools.js"] Index --> PageInteraction["page-interaction.js"] Index --> PageExtract["page-extract.js"] Index --> InteractionTools["interaction-tools.js"] PageTools --> ShadowUtils["shadow-dom-utils.js"] PageInteraction --> ShadowUtils PageExtract --> ShadowUtils InteractionTools --> PageInteraction
图表来源
- src/content/index.js#L3-L31
- src/content/page-tools.js#L5-L7
- src/content/page-interaction.js#L4-L7
- src/content/page-extract.js#L4-L6
- src/content/interaction-tools.js#L3-L6
章节来源
- src/content/index.js#L3-L31
- src/content/page-tools.js#L5-L7
- src/content/page-interaction.js#L4-L7
- src/content/page-extract.js#L4-L6
- src/content/interaction-tools.js#L3-L6
性能与稳定性考虑
- 注入时机:run_at=document_idle 避免阻塞首屏渲染;
- 异步工具:ASYNC_HANDLERS 确保异步操作不会提前关闭消息通道;
- 顶层帧限制:避免 all_frames 下多个 iframe 同时响应;
- 动态注入重试:Background 端在注入失败或消息发送失败时进行指数退避重试;
- Shadow DOM 与 iframe:深度遍历设置最大深度,防止无限递归;
- 扩展上下文失效:全局 error 与 unhandledrejection 过滤器屏蔽 Extension context invalidated 错误。
章节来源
- manifest.json#L41-L44
- src/content/index.js#L183-L193
- src/content/index.js#L195-L215
- src/background/tool-executor.js#L1789-L1810
- src/content/shadow-dom-utils.js#L12-L45
- src/content/index.js#L249-L262
常见问题排查
| 问题 | 可能原因 | 排查建议 |
|---|---|---|
| Content Script 未注入 | 页面类型不被匹配、权限不足、Chrome 安全策略限制 | 检查 manifest.matches、host_permissions、页面 URL 前缀 |
| 消息发送失败 | Content Script 尚未初始化、页面导航导致上下文失效 | 使用动态注入 + 重试机制,检查 lastError |
| 子帧响应覆盖顶层 | all_frames=true 且未限制顶层帧 | 使用 TOP_FRAME_ONLY_TYPES 过滤 |
| Shadow DOM 元素找不到 | 选择器未穿透 Shadow DOM | 使用 deepQuerySelector 或 getDeepSelector |
| iframe 内容不可读 | 跨域 iframe | 仅同源可访问,跨域需通过后端代理或页面自身 API |
| 元素 ref 失效 | 页面刷新或 DOM 重建 | 重新调用 query_elements 获取最新快照 |
章节来源
- src/background/tool-executor.js#L1750-L1810
- src/background/tool-helpers.js#L394-L444
- src/content/index.js#L195-L215
- src/content/shadow-dom-utils.js#L12-L45
- src/content/page-extract.js#L602-L663
最佳实践与使用示例
推荐调用流程
- 优先使用静态注入:manifest.json 中声明 content_scripts,确保常用功能可用;
- 动态注入兜底:Background 端在无法通信时尝试动态注入;
- 使用 ref 定位元素:query_elements 返回 ref,后续操作通过 interact_by_ref 或对应工具;
- 处理 Shadow DOM:使用 deepQuerySelector 或 getDeepSelector;
- 处理 iframe:仅同源可访问,跨域需通过其他方案;
- 异步工具:确保返回 Promise 并在 Content Script 中正确处理异步响应。
示例场景
- 提取页面标题与正文:调用 GET_PAGE_TEXT;
- 高亮关键词:调用 INJECT_CSS highlight 模式;
- 点击按钮:调用 INTERACT_ELEMENT action=click;
- 填充表单:调用 FILL_FORM;
- 滚动到底部:调用 SCROLL_TO target=bottom;
- 等待元素出现:调用 WAIT_ELEMENT state=appeared;
- 上传文件:调用 FILE_UPLOAD;
- 选择下拉选项:调用 SELECT_DROPDOWN;
- 管理本地存储:调用 MANAGE_STORAGE。
章节来源
- src/content/index.js#L91-L193
- src/content/page-tools.js#L53-L329
- src/content/page-interaction.js#L410-L575
- src/content/interaction-tools.js#L100-L789
结论
本项目的 Content Script 注入机制结合了静态注入与动态注入,既保证了常见页面的稳定注入,又通过 Background 端的重试与兜底逻辑提升了鲁棒性。Content Script 内部通过清晰的消息路由与模块化封装,提供了丰富的页面工具接口,并针对 Shadow DOM 与 iframe 场景提供了深度穿透能力。开发者在使用时应优先遵循 ref 机制、顶层帧限制与异步处理规范,以获得更稳定的扩展体验。