Clipboard API:先尊重用户意图
围绕明确的用户操作设计 Clipboard API 的写入、读取、权限、回退和隐私检查。
BotBrowser Team
Clipboard API 允许获准页面写入或读取系统剪贴板,但 API 结果不能证明用户想要披露其中内容。BotBrowser 支持在声明 profile 的自有页面中重放流程,并记录可见的权限、回退和状态;它不能读取真实用户的剪贴板,不能授予权限,也不能判断用户是否有意分享。安全设计应先明确用户请求的任务,再申请完成任务所需的最小剪贴板操作。
W3C Clipboard API 规范把异步剪贴板操作与权限和用户激活规则分开。MDN Clipboard API 参考介绍浏览器接口,web.dev 异步 Clipboard 指南展示渐进增强模式。三者共同支持一个重要边界:已完成的 promise 只说明浏览器接受了什么,不说明用户粘贴、查看或授权了什么远程服务行为。
要点
- 从可见用户操作开始,并说明页面将写入、读取还是转换剪贴板数据。
- 把
navigator.clipboard.writeText()与navigator.clipboard.readText()当作不同的隐私决定,并分别准备回退。 - 当安全上下文、焦点、策略或权限阻止 API 时,保留可选择字段、键盘路径和状态消息。
- 只记录
write-success或read-denied等脱敏状态,绝不记录剪贴板内容。 - 用合成 fixture 比较支持与失败分支;BotBrowser 不能制造真实剪贴板或权限决定。
内容导航
定义剪贴板动作和用户意图
剪贴板功能应从具体任务开始,而不是从后台探测开始。“复制这个发票号码”是写入已显示的值。“把代码粘贴到此字段”是读取动作,应在用户选择目标后开始。“导入格式化内容”可能需要 ClipboardItem 和格式白名单。在调用 navigator.clipboard 之前,把任务写进控件标签和状态文字,让用户知道将发生什么。
浏览器上下文很重要。W3C 模型和 MDN 资料描述了安全上下文和检查条件,这些条件可能随 origin、焦点、嵌入方式、策略和短暂用户激活变化。页面加载不是用户意图。不要因为未来可能需要某个值,就在定时器、隐藏 iframe、分析回调或路由预取中调用 readText()。拒绝或空结果不能证明剪贴板没有内容。
界面和支持记录应共享一套状态词:idle、intent-recorded、write-requested、read-requested、completed、denied、cancelled、unsupported 和 failed。completed 应表示页面收到浏览器结果并更新了可见控件,不表示操作系统显示了通知、其他应用接受了粘贴或服务器保存了值。
浏览器权限指南说明权限状态为何可以被观察,却不能成为身份结论。这里也应遵守同一边界:剪贴板状态服务于当前交互,不应成为长期 profile。用 clip-17 这类请求标识关联本次操作,并在任务或路由改变时让它失效。
分开写入与读取权限路径
页面主动选择的写入值通常比读取任意剪贴板内容带来的披露风险更小。为写入控件使用清楚标签,例如“复制恢复代码”,并在旁边显示来源值。点击后可以调用 navigator.clipboard.writeText(value),再宣布 write-success 或 write-denied。promise 被拒绝时,应把值保留在可选择字段中,不要谎称复制成功。
读取是另一项决定。先让用户把焦点放进目标字段,说明页面将读取剪贴板,再只从这一明确动作中调用 navigator.clipboard.readText()。不要静默读取后再查询账户或排序建议。剪贴板可能含有密码、私信或支付资料。读取结果应只消费一次,按当前字段校验,并从临时状态中删除。
功能检测可以区分 API 缺失和请求被拒绝,但不能预测权限结果。在展示首选路径前,检查 globalThis.isSecureContext 和 navigator.clipboard?.writeText 或 readText。下表把浏览器观察与产品决定分开。
| 浏览器观察 | 产品状态 | 用户可见动作 |
|---|---|---|
| API 方法不存在 | unsupported | 保留可选择字段或键盘粘贴路径。 |
promise 以 NotAllowedError 拒绝 | denied | 说明浏览器没有允许操作,并保留回退。 |
| 用户关闭或离开流程 | cancelled | 将焦点还给原控件,不自动重试。 |
| 写入后 promise 完成 | completed | 说明页面已把所选值交给浏览器。 |
| 读取文本未通过校验 | failed | 保持字段不变并显示所需格式。 |
Web Share 权限边界指南介绍了另一种由用户发起的能力。其原则同样适用:回退是正常合约的一部分,不是隐藏的策略绕过。首屏就应保留复制或粘贴选项。
处理文本和富格式
纯文本流程更容易解释,因为可以在写入前设定长度和字符策略。只有任务明确允许时才删除空白,否则应保留用户选中的文本。代码字段可以在读取后验证 AB-1234 这类形状,发现多余行时拒绝而不记录内容。通过格式校验只表示适合当前字段,不表示值真实或适合其他服务。
富格式操作需要更窄的合约。ClipboardItem 可能提供 text/plain 和 text/html,应用只应请求或写入真正需要的类型。插入文档前先清理 HTML;浏览器提供的表示不是可信标记。在 fixture 中明确设置 256 KiB 上限,解析前拒绝未知 MIME 类型。规范定义接口,却不会让应用 HTML 自动安全。
不要把富格式读取静默降级为任意文本导入。说明将使用哪种表示,复杂内容显示预览,并提供撤销或清除。如果没有 text/html,可以提供 text/plain,同时说明会丢失格式。若项目包含图片,解码和存储限制应写入应用合约,而不是把完成的 read() promise 当成安全证明。
写入时,只要合理就设置纯文本表示。只写 HTML 的复制按钮可能让键盘用户或终端流程得不到可用结果。下面的 fixture 表达预期接口:
const value = selectedValue;
const result = { operation: 'write', fixtureId: 'clip-17', state: 'requested' };
if (!globalThis.isSecureContext || !navigator.clipboard?.writeText) {
result.state = 'unsupported';
} else {
try {
await navigator.clipboard.writeText(value);
result.state = 'write-success';
} catch (error) {
result.state = error?.name === 'NotAllowedError' ? 'write-denied' : 'write-failed';
}
}
记录刻意排除 value。测试可以用合成的 clip-17 数据断言可见状态,而不保留剪贴板内容。翻译时保留 MIME 名称、限制和标识符不变。
保持回退和焦点可见
当 Clipboard API 不存在或被拒绝时,渐进增强仍应让任务可用。复制操作可以选择 <input readonly> 并显示键盘说明。粘贴操作可以让字段保持可编辑,允许用户使用系统快捷键。富格式导入可以接受文件或纯文本。这些路径应无需再次打开权限提示即可访问。
状态反馈是正确性的一部分。在 live region 中播报 write-success、read-denied 或 unsupported,成功后将焦点移到下一项有意义的控件,失败时将焦点还给来源控件。promise 等待期间不要禁用唯一可用字段。重试必须来自新的点击,不能在导航或账户改变后重放旧值。
键盘和辅助技术路径也要遵守意图边界。激活有标签的按钮是用户操作,页面加载效果不是。使用具体的可访问名称,不要用“继续”描述剪贴板操作,并说明结果是已复制、已插入还是可手动选择。测试缩放、高对比度、屏幕阅读器和较长的本地化标签。
嵌入方式会改变回退决定。跨 origin frame 即使顶层页面安全,也可能受到 Permissions Policy 或焦点边界影响。只有应用已经提供第一方 handoff 时,frame 才应说明负责人并提供该路径。不要把剪贴板文本放进 query string 在 frame 之间传递。
记录结果但不收集秘密
剪贴板 telemetry 应回答“产品走了哪条分支”,而不是“剪贴板里有什么”。白名单可以包括 fixtureId、operation、state、mimeType、browserVersion、context 和 fallback,但应排除文本、HTML、图片字节、账户标识和完整 URL。read-denied 记录有助于支持排障,复制的密码没有价值。
分开浏览器和应用的责任。浏览器决定是否暴露请求,并可以拒绝;页面验证并显示结果。服务器可以确认收到某个提交值,但这不能证明浏览器写入或读取了剪贴板。分开记录阶段,避免迟到响应覆盖新的 clip-17 尝试。
权限状态不是安全指纹。浏览器版本、profile、焦点、策略、用户动作和操作系统集成都会改变结果。不要把剪贴板可用性与字体、设备列表或其他权限合成风险分数。安全上下文指南给出的通用规则是:能力检查应服务于明确功能,而不应识别用户或设备。
保留时间应短且有明确理由。合成 fixture 记录只保留到版本比较结束,然后按策略聚合或删除。支持团队只需要最小的脱敏阶段标识。不要要求用户提交含私密内容的截图、完整浏览器 profile 或未经脱敏的事件。
用有界 fixture 验证行为
用一个自有页面和一个合成值覆盖完整状态矩阵。先使用 clip-17、可见复制按钮、可编辑粘贴字段和 status live region。在安全上下文运行,然后分别移除 API 方法、拒绝 promise、选择回退。把 browserVersion、viewport、origin 和 fixtureRevision 与结果放在一起。比较第一个改变的声明字段,不要据此宣称所有浏览器和操作系统都会相同。
web.dev 异步 Clipboard 指南适合查阅渐进增强模式,W3C 和 MDN 则定义语义与接口。修改支持说明前重新检查三者。不要使用旧的 Chrome 公告或失效 Chrome URL 作为证据;本主题的公开来源就是 W3C 规范、MDN 参考和 web.dev 指南。
审计记录可以保持紧凑:
{
"fixtureId": "clip-17",
"operation": "write",
"state": "write-success",
"fallback": "none",
"mimeType": "text/plain",
"browserVersion": "156.0",
"viewport": "1280x800"
}
这行记录的是浏览器边界上的结果,不是用户粘贴值的证明。若第一个差异是 unsupported,检查安全上下文和功能检测;若是 write-denied,检查激活、焦点、策略和用户选择;若 promise 完成但可见状态错误,检查状态机和 live region。
BotBrowser 可以在受控上下文中重放这段声明的合成流程,并保留比较使用的 profile 和 viewport 输入。它不能读取真实剪贴板、制造系统粘贴、授予权限提示,也不能证明另一应用接受了值。最终决定只应覆盖实际观察到的 fixture、版本、origin 和可见结果。
公开来源
实际结论
要求可见操作,分开处理写入和读取,在使用前校验格式,并在页面上保留可选择的回退。只记录操作状态和声明上下文,不要把剪贴板内容或权限结果变成身份信号。BotBrowser 可以重放获准的合成流程,但浏览器、用户、页面和服务器各自仍有边界。
本文属于指南: Web 平台能力面:一致性复核地图