Skip to content

Offscreen Document 剪贴板兼容层 ​

本文引用的源码文件

本文引用的文件

目录 ​

  1. 引言
  2. 项目结构定位
  3. 核心组件
  4. 架构总览
  5. 详细组件分析
  6. 依赖关系分析
  7. 性能与资源优化
  8. 故障恢复与排错指南
  9. 结论

引言 ​

本文件聚焦 MV3 扩展中的 Offscreen Document 剪贴板兼容层。该兼容层解决的是:在 Manifest V3 下,Background Service Worker 和 Content Script 对系统剪贴板的访问受到严格限制;而扩展需要为 AI 工具提供稳定的“复制”“粘贴”“获取选中内容”能力。为此,项目通过一个独立的 Offscreen Document 作为剪贴板操作的安全边界,由 Background Service Worker 按需创建、复用并通信。

关键设计动机包括:

  • MV3 的 Background Service Worker 是短暂进程,不适合长期持有敏感状态或 UI 交互上下文。
  • Content Script 运行在网页上下文中,直接读写剪贴板会暴露给页面脚本,存在权限泄露风险。
  • Offscreen Document 拥有独立文档上下文,可声明 clipboardRead / clipboardWrite 权限,并通过 chrome.offscreen.createDocument 以 CLIPBOARD 理由申请权限,从而把剪贴板访问收敛到最小可信边界。

项目结构定位 ​

Offscreen Document 剪贴板兼容层涉及以下代码位置:

  • 扩展清单中声明 offscreen、clipboardRead、clipboardWrite 权限。
  • Background 侧负责检查、创建和复用 Offscreen Document,并封装剪贴板工具调用。
  • Offscreen HTML 页面承载最小 DOM,用于兼容旧式 document.execCommand 回退路径。
  • Offscreen JS 监听来自 Background 的消息,执行实际的剪贴板读写。
  • 构建脚本将 offscreen 目录复制到产物目录,并对 ES Module 形式的 offscreen.js 做 IIFE 打包处理。
graph TB
A["扩展清单<br/>manifest.json"] --> B["Background Service Worker<br/>tool-executor.js"]
B --> C["Offscreen Document<br/>offscreen.html + offscreen.js"]
B --> D["媒体工具定义<br/>media-tools.js"]
E["构建脚本<br/>fix-build.js"] --> C

图表来源

章节来源

核心组件 ​

本节从职责角度拆解兼容层的核心部分。

Background 剪贴板协调器 ​

Background 侧承担以下职责:

  • 维护 Offscreen Document 生命周期:检测是否存在、避免重复创建、并发等待创建完成。
  • 将 AI 工具调用转换为 Offscreen Document 消息。
  • 统一返回结构化结果,包含成功标志、用户可读消息或错误信息。
  • 对 get_selected 走 Content Script 路径,因为选中文本属于页面上下文,不属于系统剪贴板。

关键实现要点:

  • 使用 chrome.runtime.getContexts 查询是否已有目标 Offscreen Document。
  • 使用 creatingOffscreenDocument 变量防止并发重复创建。
  • 使用 chrome.offscreen.createDocument 并以 reasons: ['CLIPBOARD'] 声明用途。
  • 通过 chrome.runtime.sendMessage 向 Offscreen Document 发送 COPY_TO_CLIPBOARD 或 PASTE_FROM_CLIPBOARD 消息。

章节来源

Offscreen Document 剪贴板处理器 ​

Offscreen Document 承担实际剪贴板操作:

  • 注册国际化文案,使失败提示具备多语言支持。
  • 监听 chrome.runtime.onMessage,根据消息类型分发到复制或粘贴逻辑。
  • 优先使用现代 navigator.clipboard API。
  • 当现代 API 失败时,回退到隐藏 <textarea> 配合 document.execCommand('copy' | 'paste') 的兼容路径。
  • 始终返回 { success, message? | text? | error? } 结构,便于 Background 统一处理。

章节来源

媒体工具定义 ​

media-tools.js 对外暴露 clipboard 工具定义,供 Agent 工具调度系统识别和执行。其参数契约包括:

  • action:copy、paste、get_selected。
  • text:复制时需要。
  • format:获取选中内容时可选,支持 text 或 html。

章节来源

架构总览 ​

下图展示 MV3 扩展中剪贴板能力的完整调用链:AI 工具 → Background 工具执行器 → Offscreen Document → 系统剪贴板。

sequenceDiagram
participant User as "用户"
participant Agent as "Agent 工具调度"
participant BG as "Background 工具执行器"
participant OS as "Offscreen Document"
participant Clipboard as "系统剪贴板"
User->>Agent : 触发复制/粘贴工具
Agent->>BG : 调用 clipboard 工具
BG->>BG : ensureOffscreenDocument()
BG->>OS : sendMessage(COPY_TO_CLIPBOARD 或 PASTE_FROM_CLIPBOARD)
OS->>Clipboard : navigator.clipboard.writeText/readText
alt 现代 API 成功
Clipboard-->>OS : 成功
OS-->>BG : { success, message|text }
BG-->>Agent : 工具结果
else 现代 API 失败
OS->>OS : 回退 textarea + execCommand
OS-->>BG : { success|error }
BG-->>Agent : 工具结果
end

图表来源

详细组件分析 ​

Offscreen Document 生命周期管理 ​

Background 侧的 ensureOffscreenDocument 是生命周期控制入口。其行为可概括为:

  1. 计算 Offscreen Document URL。
  2. 查询当前已存在的 OFFSCREEN_DOCUMENT 上下文。
  3. 若已存在,直接返回。
  4. 若正在创建,则等待已有 Promise。
  5. 否则发起 chrome.offscreen.createDocument,并使用 finally 清理创建锁。
flowchart TD
Start(["进入 ensureOffscreenDocument"]) --> CheckExisting["查询现有 Offscreen Document"]
CheckExisting --> Exists{"已存在?"}
Exists --> |是| ReturnOK["直接返回"]
Exists --> |否| HasCreating{"正在创建?"}
HasCreating --> |是| WaitPromise["等待已有创建 Promise"]
HasCreating --> |否| CreateDoc["调用 chrome.offscreen.createDocument"]
CreateDoc --> AwaitCreate["await 创建完成"]
AwaitCreate --> Cleanup["清理 creatingOffscreenDocument"]
Cleanup --> ReturnOK
WaitPromise --> ReturnOK

图表来源

章节来源

剪贴板读写权限模型 ​

权限模型分为三层:

  • 扩展清单层:声明 offscreen、clipboardRead、clipboardWrite。
  • Chrome 权限层:通过 createDocument 的 reasons: ['CLIPBOARD'] 说明用途。
  • 运行时层:Offscreen Document 内使用 navigator.clipboard 进行实际读写。
flowchart TD
Manifest["manifest.json<br/>声明 offscreen / clipboardRead / clipboardWrite"] --> BrowserPolicy["Chrome 权限策略"]
BrowserPolicy --> CreateDoc["Background 调用 createDocument<br/>reasons: CLIPBOARD"]
CreateDoc --> OffscreenContext["Offscreen Document 上下文"]
OffscreenContext --> NavigatorClipboard["navigator.clipboard API"]
NavigatorClipboard --> SystemClipboard["系统剪贴板"]

图表来源

章节来源

剪贴板操作封装接口 ​

Background 侧暴露两个主要导出函数:

  • executeCopyToClipboard(args, toolCallId)
  • executePasteFromClipboard(args, toolCallId)

它们共同遵循以下流程:

  1. 校验必要参数(例如复制时必须提供文本)。
  2. 确保 Offscreen Document 可用。
  3. 发送消息到 Offscreen Document。
  4. 解析响应并包装为标准工具结果。
flowchart TD
Entry(["executeCopyToClipboard / executePasteFromClipboard"]) --> Validate["参数校验"]
Validate --> EnsureOS["ensureOffscreenDocument()"]
EnsureOS --> SendMsg["chrome.runtime.sendMessage"]
SendMsg --> ParseResp["解析 response.success / error / text"]
ParseResp --> WrapResult["包装为工具结果"]
WrapResult --> Exit(["返回给上层工具调度"])

图表来源

章节来源

Offscreen Document 内部处理流程 ​

Offscreen Document 内部对复制和粘贴分别实现主路径与回退路径。

复制流程 ​

flowchart TD
CopyEntry(["收到 COPY_TO_CLIPBOARD"]) --> TryModern["尝试 navigator.clipboard.writeText"]
TryModern --> ModernOk{"成功?"}
ModernOk --> |是| RespondSuccess["返回 success + 成功消息"]
ModernOk --> |否| Fallback["隐藏 textarea 设置值并 select"]
Fallback --> ExecCopy["document.execCommand('copy')"]
ExecCopy --> CopyOk{"execCommand 成功?"}
CopyOk --> |是| RespondFallbackSuccess["返回 success + 成功消息"]
CopyOk --> |否| RespondError["返回 success=false + 错误消息"]

图表来源

粘贴流程 ​

flowchart TD
PasteEntry(["收到 PASTE_FROM_CLIPBOARD"]) --> TryRead["尝试 navigator.clipboard.readText"]
TryRead --> ReadOk{"成功?"}
ReadOk --> |是| RespondText["返回 success + text"]
ReadOk --> |否| FocusTextarea["清空并聚焦 textarea"]
FocusTextarea --> ExecPaste["document.execCommand('paste')"]
ExecPaste --> ReadValue["读取 textarea.value"]
ReadValue --> PasteOk{"成功且有文本?"}
PasteOk --> |是| RespondFallbackText["返回 success + text"]
PasteOk --> |否| RespondPasteError["返回 success=false + 错误消息"]

图表来源

章节来源

为什么不在 Content Script 中直接访问剪贴板 ​

虽然 Content Script 在某些场景下可以访问 navigator.clipboard,但本项目选择 Offscreen Document 的原因包括:

  • 权限隔离:Content Script 与网页共享 DOM 环境,若允许网页脚本间接读取剪贴板,会造成隐私泄露。
  • Background 进程约束:MV3 Background Service Worker 可能被终止,不适合长期持有剪贴板相关状态。
  • 最小可信边界:Offscreen Document 是扩展自有页面,可通过清单权限精确控制剪贴板访问范围。
  • 兼容性兜底:Offscreen Document 可安全使用 document.execCommand 等旧 API 作为回退,而不污染网页上下文。

因此,get_selected 仍走 Content Script,因为选中内容是页面级状态;而 copy 和 paste 走 Offscreen Document,因为它们是系统级剪贴板操作。

章节来源

依赖关系分析 ​

Offscreen Document 剪贴板兼容层的依赖关系如下:

graph LR
Manifest["manifest.json"] --> ToolExecutor["tool-executor.js"]
MediaTools["media-tools.js"] --> ToolExecutor
ToolExecutor --> OffscreenHTML["offscreen.html"]
ToolExecutor --> OffscreenJS["offscreen.js"]
FixBuild["fix-build.js"] --> OffscreenDir["offscreen 目录产物"]

图表来源

章节来源

性能与资源优化 ​

当前实现已经做了若干基础优化:

  • 按需创建:仅在首次需要剪贴板功能时创建 Offscreen Document。
  • 去重查询:通过 chrome.runtime.getContexts 判断是否已存在,避免重复创建。
  • 并发保护:使用 creatingOffscreenDocument 变量防止多个调用同时创建。
  • 轻量 DOM:Offscreen HTML 仅包含一个隐藏 textarea,不加载重型 UI。
  • IIFE 打包:构建脚本将 offscreen.js 打包为 IIFE,减少模块加载开销。

可进一步优化的方向:

  • 显式销毁:当前未看到主动销毁 Offscreen Document 的逻辑;可在长时间无剪贴板操作后考虑关闭空闲上下文。
  • 消息队列:若未来出现高频剪贴板请求,可增加队列合并与节流。
  • 错误重试上限:对反复失败的剪贴板操作增加降级策略,避免无限重试。
  • 日志采样:频繁复制粘贴场景下建议降低调试日志频率。

章节来源

故障恢复与排错指南 ​

常见问题与定位思路 ​

现象可能原因排查建议
复制失败Offscreen Document 未创建或权限不足检查 manifest 是否声明 offscreen、clipboardWrite;确认 createDocument 是否成功
粘贴失败浏览器不允许读取剪贴板或用户未授权检查 manifest 是否声明 clipboardRead;查看 Offscreen 回退路径是否被触发
工具调用无响应Background 与 Offscreen 消息通道异常检查 chrome.runtime.sendMessage 是否返回;确认 Offscreen 监听器是否注册
构建后 Offscreen 不可用offscreen 目录未被正确复制或打包检查 fix-build.js 是否复制 offscreen 目录并打包 offscreen.js

错误处理机制 ​

  • Background 层捕获异常并返回结构化错误,避免上层工具调度崩溃。
  • Offscreen 层先尝试现代 API,失败后进入回退路径,再失败则返回明确错误消息。
  • 所有错误消息均经过 i18n 处理,便于用户理解。
flowchart TD
Call["工具调用"] --> BGTry["Background 调用 try/catch"]
BGTry --> OSTry["Offscreen 调用 try/catch"]
OSTry --> ModernAPI["navigator.clipboard"]
ModernAPI --> Fallback["textarea + execCommand"]
Fallback --> ErrorReturn["返回 success=false + error"]
ModernAPI --> SuccessReturn["返回 success=true + data"]
ErrorReturn --> BGWrap["Background 包装错误"]
SuccessReturn --> BGWrap
BGWrap --> Result["返回工具结果"]

图表来源

章节来源

结论 ​

Offscreen Document 剪贴板兼容层在本项目中承担了 MV3 环境下剪贴板访问的关键桥接职责。它通过 Background 侧的生命周期管理和 Offscreen 侧的最小化剪贴板处理器,实现了:

  • 安全的权限边界:剪贴板访问集中在扩展自有页面。
  • 可靠的兼容性:现代 API 失败时可回退到旧式命令。
  • 清晰的职责分离:Background 负责协调,Offscreen 负责执行。
  • 可扩展的工具契约:通过 media-tools.js 暴露统一的 clipboard 工具定义。

未来若扩展更多敏感系统能力,可沿用该模式:将高风险操作收敛到 Offscreen Document,并由 Background 统一编排、鉴权和监控。