主题
Content Script 注入机制
本文引用的源码文件
本文引用的文件
目录
- 引言
- 项目结构与注入入口
- Content Script 生命周期与注入时机
- 消息路由表与异步处理模型
- 页面工具集合组织结构
- Shadow DOM 穿透与跨边界访问
- 选区工具栏实现
- 关键流程与调用序列图
- 依赖关系分析
- 性能优化、内存管理与兼容性最佳实践
- 故障排查指南
- 结论
引言
本文围绕扩展的 Content Script 注入机制展开,重点解释以下内容:
- Content Script 的生命周期、注入时机与
document_idle的实际影响。 - 基于
Map的消息路由表设计、异步工具通道保持策略和错误传播方式。 - 页面工具集合的分类:信息提取、交互操作、数据处理与高级能力。
- Shadow DOM 与 iframe 穿透方案、选择器生成与坐标计算。
- 选区工具栏的悬浮定位、拖拽、DOM 操作和用户反馈。
- 注入流程图、工具调用序列图和页面交互示意图。
- 性能优化、内存管理和 MV3 兼容性注意事项。
本项目为双进程架构:Chrome 扩展端(Manifest V3)与独立本地代理端 Agent 通过 HTTP/WebSocket 通信;本文聚焦扩展端的 Content Script 层,不涉及 Agent 内部执行逻辑。
章节来源
项目结构与注入入口
Content Script 的入口由 Manifest 声明,并在浏览器加载页面时按规则注入。核心文件职责如下:
| 文件 | 主要职责 |
|---|---|
manifest.json | 声明 Content Script 匹配规则、注入脚本、CSS、注入时机和跨帧行为 |
src/content/index.js | 消息路由表、快捷键、区域截图选择、全局错误兜底、初始化选区工具栏 |
src/content/page-tools.js | 基础页面读取工具:文本、HTML、选区、表格、剪贴板、悬停、高亮 |
src/content/page-extract.js | 元数据、链接、表单、图片、Markdown、JSON、iframe 内容、页面搜索 |
src/content/page-interaction.js | 可交互元素查询、ref 注册表、快照采集、滚动收集、按 ref 交互 |
src/content/interaction-tools.js | 点击、表单填充、滚动、等待元素、键盘输入、文件上传、下拉选择、存储管理 |
src/content/advanced-tools.js | 二维码、CSS 注入、性能审计、完成度校验、安全净化 |
src/content/shadow-dom-utils.js | Shadow DOM 与 iframe 深度查询、选择、文本、HTML、选择监听、坐标计算 |
src/content/selection-toolbar.js | 选中文本浮动工具栏、结果面板、i18n、拖拽、溢出菜单、Token 展示 |
graph TB Manifest["manifest.json<br/>声明 content_scripts"] --> Index["src/content/index.js<br/>消息路由 + 初始化"] Index --> PageTools["page-tools.js<br/>基础读取工具"] Index --> PageExtract["page-extract.js<br/>信息提取工具"] Index --> PageInteraction["page-interaction.js<br/>交互查询 + ref 注册"] Index --> InteractionTools["interaction-tools.js<br/>点击/表单/键盘/上传"] Index --> AdvancedTools["advanced-tools.js<br/>二维码/CSS/完成度校验"] Index --> SelectionToolbar["selection-toolbar.js<br/>选区工具栏"] PageTools --> ShadowUtils["shadow-dom-utils.js<br/>Shadow DOM / iframe 穿透"] PageExtract --> ShadowUtils PageInteraction --> ShadowUtils InteractionTools --> ShadowUtils SelectionToolbar --> ShadowUtils
图表来源
章节来源
Content Script 生命周期与注入时机
注入配置
Manifest 中 Content Script 的关键配置如下:
matches:<all_urls>,表示对所有网页生效。js: 先注入libs/qrcode.min.js,再注入libs/marked.min.js,最后注入src/content/index.js。css: 注入styles/styles.css。run_at:document_idle。all_frames:true,允许在所有 frame 中注入。match_about_blank:true,兼容空页面。match_origin_as_fallback:true,同源回退匹配。
这意味着 Content Script 在文档空闲阶段注入,而不是更早的 document-start。该选择有利于:
- 页面结构基本可用,减少因 DOM 未就绪导致的查询失败。
- 降低对首屏渲染的阻塞风险。
- 更适合以“读取页面 + 用户交互”为主的扩展场景。
但这也意味着:
- 如果需要在极早期拦截网络或修改初始 DOM,应改用
document-start并谨慎评估性能。 - 当前实现依赖
document.body、chrome.runtime、window.top等运行时对象存在。
运行期生命周期
Content Script 启动后执行以下关键步骤:
- 导入各模块工具函数。
- 注册 i18n 翻译键。
- 初始化国际化。
- 打印调试日志,记录 URL、是否顶层 frame、是否存在
document.body。 - 注册全局快捷键:组合键打开扩展 action、全页截图、区域截图。
- 构建消息路由表
HANDLERS和异步工具集合ASYNC_HANDLERS。 - 定义仅顶层 frame 响应的类型集合
TOP_FRAME_ONLY_TYPES。 - 在扩展上下文有效时注册
chrome.runtime.onMessage。 - 注册全局错误和未处理 Promise 拒绝过滤器,屏蔽
Extension context invalidated。 - 等待 i18n 就绪后初始化选区工具栏。
- 提供区域截图选择函数,供消息路由调用。
flowchart TD
Start["Content Script 加载"] --> ManifestCheck["检查 manifest 注入配置"]
ManifestCheck --> I18nInit["初始化 i18n"]
I18nInit --> LogContext["输出页面上下文日志"]
LogContext --> RegisterShortcuts["注册快捷键事件"]
RegisterShortcuts --> BuildHandlers["构建 HANDLERS 路由表"]
BuildHandlers --> DefineAsyncSet["定义 ASYNC_HANDLERS 集合"]
DefineAsyncSet --> DefineTopFrameOnly["定义 TOP_FRAME_ONLY_TYPES 集合"]
DefineTopFrameOnly --> CheckExtensionValid{"扩展上下文有效?"}
CheckExtensionValid --> |是| RegisterMessageListener["注册 onMessage 监听"]
CheckExtensionValid --> |否| SkipListener["跳过消息监听"]
RegisterMessageListener --> GlobalErrorGuard["注册全局错误与未处理拒绝过滤"]
SkipListener --> GlobalErrorGuard
GlobalErrorGuard --> WaitI18nReady["等待 i18n 就绪"]
WaitI18nReady --> InitSelectionToolbar["初始化选区工具栏"]
InitSelectionToolbar --> Ready["Content Script 就绪"]
图表来源
章节来源
消息路由表与异步处理模型
路由表设计
index.js 使用对象字面量作为消息路由表,键为消息类型,值为处理器函数。这种设计具备以下特点:
- 查找复杂度接近 O(1)。
- 新增工具只需添加键值对,无需遍历数组。
- 分类清晰:页面读取、页面交互、表单输入、信息提取、媒体输出、异步工具、区域截图、页面度量。
路由表覆盖的主要类别包括:
- 页面读取:获取页面文本、完整 HTML、查询交互元素、获取选中内容、子帧快照采集。
- 页面交互:按 ref/text/selector 交互、填充表单、滚动到目标。
- 表单输入:键盘输入、文件上传。
- 信息提取:表格、元数据、链接、表单、图片、页面搜索、iframe 内容、滚动收集。
- 媒体输出:存储管理、CSS 注入、完成度校验。
- 异步工具:剪贴板、等待元素、下拉选择、二维码、区域截图。
- 页面度量:页面宽高指标。
异步处理模型
异步工具分为两类:
- 显式标记为异步的工具:
COPY_TO_CLIPBOARD、PASTE_FROM_CLIPBOARD、WAIT_ELEMENT、SELECT_DROPDOWN、QRCODE、START_REGION_SELECTION,以及INTERACT_ELEMENT、SCROLL_TO、KEYBOARD_INPUT的某些分支。 - 返回 Promise 的处理器。
消息监听器统一处理同步和异步返回值:
- 如果消息类型属于
ASYNC_HANDLERS,或者返回值是 Promise,则保持消息通道开放,并在 Promise resolve 后回复。 - 否则直接同步回复。
sequenceDiagram
participant Background as "Background / Side Panel"
participant Content as "Content Script index.js"
participant Handler as "HANDLERS 处理器"
participant Tool as "具体工具模块"
Background->>Content : chrome.runtime.sendMessage({ type, payload })
Content->>Content : 判断 TOP_FRAME_ONLY_TYPES
Content->>Content : 查找 HANDLERS[type]
Content->>Handler : 调用处理器
alt 异步工具或返回 Promise
Handler-->>Content : Promise
Content->>Content : Promise.resolve(result).then(sendResponse)
Content-->>Background : sendResponse(result)
else 同步工具
Handler-->>Content : 同步结果
Content-->>Background : sendResponse(result)
end
图表来源
错误传播机制
错误传播遵循统一模式:
- 工具函数通常返回
{ success, data?, error?, message? }结构。 - 路由层不吞掉业务错误,而是将工具返回的错误原样传给调用方。
- 对于扩展上下文失效,Content Script 通过全局
error和unhandledrejection事件过滤,避免控制台刷屏。 - 部分工具在 try-catch 中捕获异常,并转为结构化错误响应。
需要注意:
- 异步工具的错误由 Promise reject 或工具内部返回
{ success: false, error }表达。 - 路由层对 Promise 使用
Promise.resolve(result),因此即使处理器返回 rejected Promise,也会被包装成 resolved Promise,最终通过sendResponse传递;若需要区分 reject 与业务错误,应在工具层明确返回结构化错误。
章节来源
页面工具集合组织结构
页面工具按职责拆分为多个模块,便于维护和测试:
| 分类 | 文件 | 典型功能 |
|---|---|---|
| 基础读取 | page-tools.js | 获取页面文本、HTML、选区、表格、剪贴板、悬停、文本高亮 |
| 信息提取 | page-extract.js | 元数据、链接、表单、图片、Markdown、JSON、iframe、搜索 |
| 交互查询 | page-interaction.js | 可交互元素查询、ref 注册、快照采集、滚动收集、按 ref 交互 |
| 交互操作 | interaction-tools.js | 点击、表单填充、滚动、等待元素、键盘输入、文件上传、下拉选择、存储管理 |
| 高级能力 | advanced-tools.js | 二维码、CSS 注入、性能审计、完成度校验、CSS 安全净化 |
| 底层穿透 | shadow-dom-utils.js | Shadow DOM、iframe、选择、文本、HTML、选择监听、坐标计算 |
| 用户界面 | selection-toolbar.js | 选区工具栏、结果面板、拖拽、溢出菜单、Token 明细 |
基础读取工具
page-tools.js 提供轻量、稳定的页面读取能力:
getPageText:提取标题、URL、正文、词数、可选标题和链接。getFullHtml:提取完整 HTML,支持去除内联样式和长度限制。getSelectedContent:读取用户选区,支持 text/html 两种格式。extractTable:解析表格为 JSON 或 Markdown。copyToClipboard/pasteFromClipboard:剪贴板读写,带降级方案。hoverElement:触发鼠标悬停,并自动检测导航或 DOM 变化。highlightText:移除旧高亮、插入样式、遍历文本节点包裹高亮 span。
信息提取工具
page-extract.js 负责结构化信息抽取:
extractMetadata:meta、Open Graph、Twitter Card、JSON-LD、Microdata。extractLinks:链接列表,支持内外链过滤和图片链接。extractForms:表单字段序列化,包含 input、textarea、select。extractImages:图片列表,支持尺寸过滤、背景图、下载。pageToMarkdown:HTML 转 Markdown。pageToJson:结构化表格、列表、JSON-LD、文章、Microdata。getIframeContent:同源 iframe 内容读取。searchInPage:页面内搜索,支持 plain/regex、大小写、上下文、高亮。
交互查询与操作
page-interaction.js 和 interaction-tools.js 共同构成页面自动化能力:
queryInteractiveElements:生成可交互元素树快照。collectSnapshotOps:采集 overlay tree、body tree、frame info。interactByRef:按 ref 点击、悬停、输入。clickElement:点击元素并自动等待稳定。fillForm:多字段表单填充,支持原生控件、contenteditable、checkbox/radio/select。scrollToPosition:滚动到顶部、底部、坐标或元素。waitForElement:等待元素出现、消失、可见、隐藏。keyboardInput:模拟按键和文本输入。fileUpload:通过 DataTransfer 设置文件。clickByText:按文本查找并点击或悬停。selectDropdown:原生 select 或自定义下拉选择。manageStorage:localStorage/sessionStorage 的 get/set/remove/clear。
章节来源
- src/content/page-tools.js#L53-L329
- src/content/page-extract.js#L28-L800
- src/content/page-interaction.js#L51-L800
- src/content/interaction-tools.js#L100-L789
Shadow DOM 穿透与跨边界访问
设计目标
现代前端大量使用 Shadow DOM 和 iframe,普通 querySelector 无法穿透。shadow-dom-utils.js 提供统一穿透能力:
- 递归遍历 shadowRoot。
- 尝试访问同源 iframe 的 document。
- 跨域 iframe 被安全跳过。
- 提供选择器、全选、选区、文本、HTML、路径选择器、视口坐标、选择监听等工具。
核心 API
| API | 用途 | 关键点 |
|---|---|---|
deepQuerySelector | 穿透 Shadow DOM 和 iframe 查询单个元素 | 优先深层匹配,跨域跳过 |
deepQuerySelectorAll | 穿透查询所有匹配元素 | 使用 Set 去重 |
deepGetSelection | 获取 Shadow DOM 中的选区 | 返回 text 和 range |
deepGetText | 合并页面文本 | 防止循环引用,trim 多余换行 |
deepGetHtml | 合并页面 HTML | 标注 shadow-root 和 iframe 片段 |
getDeepSelector | 生成包含 Shadow DOM 路径的选择器 | 记录 shadowHost 层级 |
findShadowRoot | 查找元素所属 ShadowRoot | 向上回溯 root |
getElementViewportPosition | 元素视口坐标 | 累加 iframe 偏移 |
getRangeViewportPosition | 选区范围视口坐标 | 处理 iframe 嵌套 |
attachSelectionListeners | 在所有 Shadow DOM 上附加 selectionchange | 返回监听器集合 |
removeSelectionListeners | 清理监听器 | 防止内存泄漏 |
hasShadowDom / hasIframe | 能力检测 | 安全 try-catch |
flowchart TD
Input["选择器或 Range"] --> Traverse["遍历 DOM 树"]
Traverse --> ShadowCheck{"存在 shadowRoot?"}
ShadowCheck --> |是| RecurseShadow["递归进入 shadowRoot"]
ShadowCheck --> |否| IframeCheck{"存在 iframe/frame?"}
IframeCheck --> |是| TryAccess["尝试访问 contentDocument"]
TryAccess --> AccessOk{"同源可访问?"}
AccessOk --> |是| RecurseIframe["递归进入 iframe document"]
AccessOk --> |否| SkipIframe["跳过跨域 iframe"]
RecurseShadow --> Query["执行 querySelector/querySelectorAll"]
RecurseIframe --> Query
SkipIframe --> Query
Query --> Result["返回元素或结果集"]
图表来源
与页面工具的协作
page-tools.js使用deepGetText、deepGetHtml、deepQuerySelector、deepQuerySelectorAll读取 Shadow DOM 内容。page-extract.js同样依赖这些工具进行元数据和结构化数据提取。page-interaction.js使用deepQuerySelector和deepQuerySelectorAll定位交互元素。interaction-tools.js使用deepQuerySelector定位点击目标、表单控件和上传控件。selection-toolbar.js使用deepGetSelection、getRangeViewportPosition、attachSelectionListeners实现跨边界选区识别和工具栏定位。
章节来源
- src/content/shadow-dom-utils.js#L1-L443
- src/content/page-tools.js#L5-L7
- src/content/page-extract.js#L4-L6
- src/content/page-interaction.js#L4-L5
- src/content/interaction-tools.js#L3-L5
- src/content/selection-toolbar.js#L3-L6
选区工具栏实现
选区工具栏是用户与 AI 能力之间的直接交互入口,核心行为包括:
- 检测页面选区,包括 Shadow DOM 中的选区。
- 在选区附近显示浮动工具栏。
- 支持内置工具:AI 搜索、解释、翻译、总结、复制。
- 支持更多工具溢出菜单、设置、临时禁用、站点禁用。
- 支持“问问 AI”输入框模式。
- 支持结果面板:流式渲染、复制、重新生成、推荐追问、Token 消耗明细。
- 支持拖拽移动、锁定面板、关闭面板。
- 支持 frameset 页面和 iframe 场景。
工具栏状态与变量
工具栏维护多种状态:
- 工具栏可见性、询问模式、结果面板可见性。
- 上次操作类型、自定义提示词、保存的选中文本。
- 工具栏工具配置缓存、图标精简模式。
- 流式内容、渲染节流令牌、Token 使用数据。
- Shadow DOM 选择监听器集合、顶层 frame 判定。
- frameset 页面宿主判定、拖拽状态、溢出下拉菜单。
悬浮定位与 DOM 操作
工具栏定位依赖:
getRangeViewportPosition计算选区在视口中的位置。appendToDoc兼容document.body不存在的情况,降级到document.documentElement。- 拖拽通过全局
mousemove和mouseup更新left、top,并限制在视口范围内。 - 结果面板和工具栏分别挂载到文档,并通过类名控制显示隐藏。
用户交互反馈
- 按钮支持 Enter/Space 触发。
- 溢出菜单支持点击、键盘操作。
- “问问 AI”输入框支持 focus、blur、Enter、Escape。
- 结果面板支持复制富文本、复制 Markdown、重新生成、发送追问。
- Token 标签点击弹出消耗明细。
- 工具名称通过 i18n 动态获取,避免硬编码文案。
sequenceDiagram
participant User as "用户"
participant Toolbar as "selection-toolbar.js"
participant ShadowUtils as "shadow-dom-utils.js"
participant DOM as "页面 DOM"
User->>DOM : 选中文本
DOM-->>Toolbar : selectionchange 事件
Toolbar->>ShadowUtils : deepGetSelection()
ShadowUtils-->>Toolbar : { text, range }
Toolbar->>DOM : 创建工具栏 DOM
Toolbar->>DOM : 计算选区视口位置
Toolbar->>DOM : 显示工具栏
User->>Toolbar : 点击工具按钮
Toolbar->>Toolbar : handleAction()
Toolbar->>DOM : 显示结果面板或发送侧边栏
图表来源
- src/content/selection-toolbar.js#L159-L202
- src/content/selection-toolbar.js#L280-L340
- src/content/selection-toolbar.js#L452-L648
- src/content/shadow-dom-utils.js#L95-L125
- src/content/shadow-dom-utils.js#L338-L377
章节来源
关键流程与调用序列图
消息路由调用序列
当 background 或 side panel 调用 Content Script 工具时,典型调用链如下:
sequenceDiagram
participant Caller as "调用方"
participant Runtime as "chrome.runtime"
participant Index as "index.js"
participant Router as "HANDLERS"
participant Module as "具体工具模块"
Caller->>Runtime : sendMessage({ type, ... })
Runtime->>Index : onMessage(message, sender, sendResponse)
Index->>Index : 检查 TOP_FRAME_ONLY_TYPES
Index->>Router : 查找 handler
Router->>Module : 调用工具函数
Module-->>Router : 返回结果或 Promise
Router-->>Index : 结果
Index->>Runtime : sendResponse(result)
Runtime-->>Caller : 收到响应
图表来源
按 ref 交互序列
interactByRef 展示了 ref 解析、元素定位、动作执行和自动等待的完整流程:
sequenceDiagram
participant Caller as "调用方"
participant Index as "index.js"
participant Interaction as "page-interaction.js"
participant Registry as "elementRegistry"
participant Utils as "page-utils.js"
Caller->>Index : INTERACT_ELEMENT(ref=...)
Index->>Interaction : interactByRef(ref, action, options)
Interaction->>Registry : resolveByRef(ref)
alt ref 有效且元素仍连接
Registry-->>Interaction : { entry, element }
else ref 无效或元素断开
Registry-->>Interaction : { error, suggestions }
Interaction-->>Caller : 返回错误与建议
return
end
Interaction->>Utils : 执行 click/hover/type
Utils-->>Interaction : autoWaitAfterAction(...)
Interaction-->>Caller : 返回成功结果与等待信息
图表来源
- src/content/index.js#L107-L119
- src/content/page-interaction.js#L793-L800
- src/content/page-interaction.js#L89-L106
页面交互示意图
选区工具栏与页面交互的关系如下:
graph TB Selection["用户选区"] --> Toolbar["选区工具栏"] Toolbar --> Actions["工具动作:AI 搜索/解释/翻译/总结/复制"] Actions --> SidePanel["发送到侧边栏或本地代理"] Toolbar --> ResultPanel["结果面板:流式回答/追问/Token 明细"] Toolbar --> DOM["DOM 操作:拖拽/显示/隐藏/复制"] Selection --> ShadowUtils["Shadow DOM 选区穿透"] ShadowUtils --> Toolbar
图表来源
依赖关系分析
Content Script 模块之间存在清晰的单向依赖:
index.js聚合所有工具模块,作为消息路由入口。page-tools.js依赖shadow-dom-utils.js和page-utils.js。page-extract.js依赖shadow-dom-utils.js和page-utils.js。page-interaction.js依赖shadow-dom-utils.js、page-utils.js和共享快照渲染器。interaction-tools.js依赖shadow-dom-utils.js、page-utils.js和page-interaction.js。advanced-tools.js依赖shadow-dom-utils.js。selection-toolbar.js依赖shadow-dom-utils.js、selection-toolbar-styles.js、i18n 和 logger。
graph LR Index["index.js"] --> PageTools["page-tools.js"] Index --> PageExtract["page-extract.js"] Index --> PageInteraction["page-interaction.js"] Index --> InteractionTools["interaction-tools.js"] Index --> AdvancedTools["advanced-tools.js"] Index --> SelectionToolbar["selection-toolbar.js"] PageTools --> ShadowUtils["shadow-dom-utils.js"] PageExtract --> ShadowUtils PageInteraction --> ShadowUtils InteractionTools --> ShadowUtils InteractionTools --> PageInteraction AdvancedTools --> ShadowUtils SelectionToolbar --> ShadowUtils
图表来源
- src/content/index.js#L3-L33
- src/content/page-tools.js#L5-L7
- src/content/page-extract.js#L4-L6
- src/content/page-interaction.js#L4-L8
- src/content/interaction-tools.js#L3-L6
- src/content/advanced-tools.js#L3-L4
- src/content/selection-toolbar.js#L3-L6
章节来源
- src/content/index.js#L3-L33
- src/content/page-tools.js#L5-L7
- src/content/page-extract.js#L4-L6
- src/content/page-interaction.js#L4-L8
- src/content/interaction-tools.js#L3-L6
- src/content/advanced-tools.js#L3-L4
- src/content/selection-toolbar.js#L3-L6
性能优化、内存管理与兼容性最佳实践
注入时机选择
当前使用 document_idle,适合大多数读取和交互场景。如果需要更早介入,可考虑 document-start,但需注意:
- DOM 可能尚未就绪。
- 大量 DOM 操作可能阻塞首屏。
- 需要更严格的错误处理和降级逻辑。
Map 与 Set 的使用
HANDLERS使用对象字面量作为路由表,查找接近 O(1)。ASYNC_HANDLERS和TOP_FRAME_ONLY_TYPES使用Set,成员判断 O(1)。elementRegistry使用Map,ref 到元素映射高效。elementRefMap使用WeakMap,避免阻止元素 GC,同时复用 ref 编号。
异步工具通道保持
异步工具通过 return true 保持消息通道开放,避免 Chrome 提前关闭响应。这要求:
- 异步工具必须返回 Promise。
- 路由层统一用
Promise.resolve(result).then(sendResponse)处理。 - 工具内部错误应返回结构化
{ success: false, error },而非抛出未捕获异常。
内存管理
- Shadow DOM 选择监听器通过
Set管理,并提供removeSelectionListeners清理。 - 工具栏工具配置缓存避免重复读取 storage。
- 高亮样式通过固定 id 替换,避免累积 style 节点。
- 区域截图遮罩层在完成后主动移除 DOM 节点。
兼容性处理
- 跨域 iframe 访问被 try-catch 保护,失败时跳过。
chrome.runtime调用被 try-catch 包裹,扩展上下文失效时静默忽略。- 全局
error和unhandledrejection过滤Extension context invalidated。 - CSS 注入前进行安全净化,剔除危险构造。
- QRCode 库未加载时使用 Canvas 降级方案。
章节来源
- manifest.json#L36-L45
- src/content/index.js#L74-L246
- src/content/index.js#L249-L265
- src/content/page-interaction.js#L51-L106
- src/content/shadow-dom-utils.js#L379-L423
- src/content/advanced-tools.js#L357-L456
故障排查指南
常见问题
| 问题 | 可能原因 | 排查建议 |
|---|---|---|
| 消息无响应 | 非顶层 frame 被 TOP_FRAME_ONLY_TYPES 过滤 | 检查消息类型和 _frameRouted 标志 |
| ref 无效 | 元素已断开或页面刷新 | 重新调用 query_elements,查看 suggestions |
| Shadow DOM 元素找不到 | 选择器未穿透 Shadow DOM | 使用 deepQuerySelector |
| 工具栏不显示 | 扩展上下文失效或 i18n 未就绪 | 检查全局错误过滤和 isExtensionValid |
| 异步工具卡住 | 未返回 Promise 或未保持通道 | 检查 ASYNC_HANDLERS 和处理器返回值 |
| CSS 注入无效 | 被安全净化剔除 | 检查 sanitizeInjectedCss 白名单 |
| iframe 内容缺失 | 跨域限制 | 确认同源或使用 orchestrator 定向路由 |
调试技巧
- 查看 Content Script 控制台日志,确认 URL、顶层 frame、
document.body状态。 - 使用
GET_PAGE_METRICS获取页面宽高,验证布局。 - 使用
QUERY_ELEMENTS查看可交互元素树快照。 - 使用
EVALUATE_DONE_CRITERIA验证完成度条件。 - 检查
selection-toolbar.js中的isTopFrame、toolbarTools、resultRawContent等状态变量。
章节来源
- src/content/index.js#L52-L52
- src/content/index.js#L195-L207
- src/content/page-interaction.js#L89-L136
- src/content/selection-toolbar.js#L280-L340
结论
本项目 Content Script 注入机制以 document_idle 为默认注入点,通过集中式消息路由表统一管理页面读取、交互、提取、高级能力和用户界面。其核心优势包括:
- 清晰的模块划分和依赖关系。
- 高效的 Map/Set 路由与异步处理模型。
- 完善的 Shadow DOM 与 iframe 穿透能力。
- 健壮的错误处理和扩展上下文失效防护。
- 可扩展的选区工具栏和结果面板。
在实际使用中,应根据页面特性选择合适的注入时机,合理使用 ref 机制避免脆弱选择器,并注意异步工具通道的正确保持。对于复杂页面,优先使用 query_elements 和 collectSnapshotOps 提供的结构化快照,再结合 interactByRef 进行稳定交互。