Skip to content

Content Script 注入机制 ​

本文引用的源码文件

本文引用的文件

目录 ​

  1. 引言
  2. 项目结构与注入入口
  3. 静态注入与动态注入
  4. 匹配规则、注入时机与隔离模式
  5. Content Script 消息路由与工具接口
  6. Shadow DOM 穿透与跨 iframe 通信
  7. 页面工具调用规范
  8. 架构总览
  9. 依赖关系分析
  10. 性能与稳定性考虑
  11. 常见问题排查
  12. 最佳实践与使用示例
  13. 结论

引言 ​

本文围绕扩展的 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-helperschrome.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"

图表来源

章节来源

匹配规则、注入时机与隔离模式 ​

manifest.json 当前 content_scripts 配置如下:

字段值含义
matches<all_urls>匹配所有 URL
jslibs/qrcode.min.js、libs/marked.min.js、src/content/index.js注入顺序依次加载
cssstyles/styles.css注入样式
run_atdocument_idle在文档空闲后注入,避免阻塞首屏
all_framestrue在所有 iframe 中注入
match_about_blanktrue匹配 about:blank
match_origin_as_fallbacktrue同源回退匹配

这些配置决定了 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"]

图表来源

章节来源

页面工具调用规范 ​

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

图表来源

依赖关系分析 ​

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

图表来源

章节来源

性能与稳定性考虑 ​

  • 注入时机:run_at=document_idle 避免阻塞首屏渲染;
  • 异步工具:ASYNC_HANDLERS 确保异步操作不会提前关闭消息通道;
  • 顶层帧限制:避免 all_frames 下多个 iframe 同时响应;
  • 动态注入重试:Background 端在注入失败或消息发送失败时进行指数退避重试;
  • Shadow DOM 与 iframe:深度遍历设置最大深度,防止无限递归;
  • 扩展上下文失效:全局 error 与 unhandledrejection 过滤器屏蔽 Extension context invalidated 错误。

章节来源

常见问题排查 ​

问题可能原因排查建议
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 获取最新快照

章节来源

最佳实践与使用示例 ​

推荐调用流程 ​

  1. 优先使用静态注入:manifest.json 中声明 content_scripts,确保常用功能可用;
  2. 动态注入兜底:Background 端在无法通信时尝试动态注入;
  3. 使用 ref 定位元素:query_elements 返回 ref,后续操作通过 interact_by_ref 或对应工具;
  4. 处理 Shadow DOM:使用 deepQuerySelector 或 getDeepSelector;
  5. 处理 iframe:仅同源可访问,跨域需通过其他方案;
  6. 异步工具:确保返回 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。

章节来源

结论 ​

本项目的 Content Script 注入机制结合了静态注入与动态注入,既保证了常见页面的稳定注入,又通过 Background 端的重试与兜底逻辑提升了鲁棒性。Content Script 内部通过清晰的消息路由与模块化封装,提供了丰富的页面工具接口,并针对 Shadow DOM 与 iframe 场景提供了深度穿透能力。开发者在使用时应优先遵循 ref 机制、顶层帧限制与异步处理规范,以获得更稳定的扩展体验。