Web Share API:权限边界与安全回退
围绕能力检查、临时激活、数据校验、取消处理和可访问回退,设计安全的 Web Share 控件。
BotBrowser Team
Web Share 是由用户介导的浏览器交接。页面提出标题、文本、URL 或浏览器支持的文件,用户激活可见控件后,浏览器可能打开操作系统的分享界面。页面不会选择接收方、查看接收方列表,也不会收到其他应用已完成交付的证明。BotBrowser 可以在受控环境中验证感知能力的分享 UI 和回退路径,但不能选择用户的分享目标、控制操作系统分享面板,也不能绕过浏览器介导。
TL;DR
- 检测 Web Share 能力,并保留有用的复制、下载或普通链接回退。
- 仅在带有临时激活的明确用户操作中调用
navigator.share()。 - 可用时用
navigator.canShare()校验数据,但不要把它当成交付证明。 - 处理取消和失败时不要抹掉用户正在完成的任务。
- Web Share 不会枚举接收方、授予权限或建立身份。
目录
能力与激活
W3C Web Share 规范 和 MDN Web Share API 指南描述的是有条件的能力。安全上下文、浏览器策略、当前文档和操作系统集成都会影响可用性。在实际使用时测试 navigator.share。加载时存在的方法,之后仍可能因为上下文、数据或激活不再符合要求而拒绝。
临时激活持续时间很短。把调用放在分享按钮的点击或键盘处理器中。不要通过定时器延后,不要等待无关的网络请求,也不要在页面加载时调用。可见操作让用户知道将发生什么,并为浏览器提供有效的激活链。如果准备过程耗时,应在点击前准备安全数据,并在处理器内只做有界的同步校验。
激活边界不是权限授予。已解决的 promise 只报告页面能看到的本地交接结果;它不能证明原生分享界面已展示、接收方已选定或内容已交付。取消是普通结果。交接结束时保留页面、当前标题、表单状态和发起控件。
能力检查应描述产品状态,而不是浏览器身份。“分享可用”可以启用控件;“此处无法分享”可以显示复制和下载替代项。不要把缺少方法、被拒绝的 promise 或不同的激活结果变成用户或设备分类。浏览器支持会随版本和上下文变化。
数据校验与隐私
分享数据可以包含 title、text、url,以及浏览器支持的文件。根据当前用户可见对象构造提议数据。URL 不要包含访问令牌和私密查询值。不要因为字段接受任意文本就加入账户标识或隐藏元数据。MDN Navigator.share() 记录了该方法,navigator.canShare() 则可检查当前上下文是否接受提议的数据集。
canShare() 是能力检查,不是接收方检查。它不会透露安装了哪些应用、哪位联系人会接收数据,或传输是否完成。算法会忽略未知字典成员,因此额外字段不能证明整个对象被拒绝。不要把不同的 canShare() 结果用作指纹。同一数据集可能在一个浏览器上下文中接受、在另一个上下文中因文件、大小、策略或平台集成而拒绝。
将授权与分享分开。私有文档不会因为显示了分享按钮就自动公开。构造 URL 或导出内容前,重新确认当前用户有权读取对象。如果链接会过期,在交接前说明其范围。接收服务必须独立认证每个请求;分享面板结果不是服务器授权信号。
数据最小化同样适用于分析。记录有界结果,例如 share-request-resolved、share-cancelled、share-rejected、copy-success 或 download-started。不要记录原始文本、私密 URL、文件内容或接收方姓名。只保留改进功能所需的事件字段,并声明用途和删除期限。W3C Privacy Principles 支持目的限制和数据最小化。
取消与可访问回退
用户取消或没有可用目标时,分享 promise 可能以 AbortError 拒绝。把它当作安全返回当前任务,而不是用户决定的证明。其他错误可能表示数据无效、文件不受支持、策略限制或平台故障。显示简洁状态,并保留复制、下载和普通链接操作。不要重新加载页面、清除草稿或循环重试。
回退应保留用户意图。复制明确选择的 URL,下载已授权的表示,或在文档中提供普通链接。每个操作都要有可见标签和键盘路径。剪贴板访问失败时,让 URL 仍可选择。文件生成等待期间显示进度以及重试或取消操作。缺少分享界面绝不能让必要内容无法访问。
使用可访问名称、焦点顺序和状态文本。取消后将焦点返回发起控件,除非用户已移到别处。播报“分享已取消”或“复制链接已就绪”,不要播报原始异常或私密 URL。确保回退在缩放、大字号、高对比度、键盘导航和屏幕阅读器下可用。仅图标控件需要可访问名称和工具提示;主要操作通常适合图标加文字。
嵌入和离线环境也需要相同契约。嵌入组件对顶层文档的控制可能更少,因此可能需要提供普通链接或下载。离线页面仍可复制 URL,而生成的导出可能要等待本地准备。说明哪些内容留在本地、哪些内容会离开页面。不要假定每个 frame 内都有顶层分享界面。
测试长标题、非拉丁文字、双向文本和翻译后的状态消息。不要用浏览器区域设置选择接收方或推断位置。激活前让被分享对象保持可见,交接取消时也保留它。这些界面规则不取决于某个操作系统显示哪一种分享面板。
使用 BotBrowser 测试
BotBrowser 支持在受控环境中验证感知能力的分享 UI 和回退路径。限制很明确:它不能选择用户的分享目标、控制操作系统分享面板或绕过浏览器介导。fixture 可以移除 navigator.share、以 AbortError 或其他错误拒绝、返回不同的 canShare() 结果,并断言可见的复制或下载行为;但不能证明接收方身份或交付完成。
使用合成标题、URL、文件和用户操作。记录浏览器构建版本、上下文类型、安全上下文状态、激活路径、提议的数据类别、promise 结果、可见回退和应用版本。不要记录账户名、私密 URL、完整导出内容或接收方列表。用方法缺失、canShare 缺失、数据无效、取消和成功交接请求运行同一 fixture。
狭义解释成功测试:它只表明声明的上下文接受了请求,且页面保留了回退状态。它不表明存在目标、不表明操作系统稍后会显示同一面板,也不表明其他应用完成了传输。浏览器、操作系统或数据集变化后应重跑。生产监控要与兼容性证据分开。
实用的决策契约。
接入按钮前先定义分享对象。写明可见的分享内容、所需数据字段、每个字段的授权规则,以及让任务继续的回退。产品页可以分享公开规范 URL;私有工作区可能需要短期且经过访问检查的链接,或只在用户确认范围后提供导出。这些是产品决策,浏览器交接不会自动使其安全。
让一个责任方负责准备表示。如果页面构造文件,应说明文件何时就绪、用户开始分享前是否仍留在本地,以及取消后如何丢弃。准备失败时保留原任务可读并提供重试。没有解释的禁用按钮不是回退。清晰的“准备下载”或“复制链接”操作提供替代项,不假定系统分享界面一定出现。
分离控件状态和结果状态。控件可以是可用、不可用、准备中或暂时忙碌;结果可以是已取消、已拒绝、已复制、已下载或等待浏览器交接。resolved share promise 不应标为“已交付”,因为页面不知道浏览器转交请求后发生了什么。取消后应将控件恢复为正常可用,而不是给用户任务留下错误标记。
为每种数据类别使用一条有界路径。页面 URL 通常可立即检查;文件可能需要专门的 canShare() 调用和可访问下载替代项;文本在提出前可能需要长度或隐私审核。项目未通过当前能力检查时,不要反复修改直到浏览器接受。显示选定回退,让用户决定复制链接、下载表示或不分享继续。
分享按钮不应悄悄创建新的账户关系。“邀请”可能发送服务器请求,需要独立授权、确认和审计规则。“复制链接”在用户粘贴到别处前可以保持本地;“下载”在浏览器常规控制下创建本地文件;“分享”请求浏览器打开用户选择的交接。视觉上可将选项分组,但它们的数据边界和成功含义不同。
链接离开当前页面前要审核。移除访问令牌、内部路由数据和泄露他人信息的值;对象应为公开内容时优先使用规范公开 URL。URL 授予限时访问时,在操作前显示过期时间和受众规则。接收应用可能保存或转发链接,因此背后的服务必须校验后续每个请求。Web Share 不能替代正常的访问控制设计。
状态文案应事实准确且可逆。不要把“分享面板已打开”当作 promise 结果:resolve 并不始终证明原生界面已展示。“分享已取消”说明页面未改变;“复制链接不可用”后应提供可选择链接或下载。避免暗示操作系统目标存在、联系人已选定或其他应用已接受内容。清晰措辞也能减少跨平台支持歧义。
把浏览器差异当作预期输入。一个浏览器可能支持 URL 而不支持文件,另一个可能要求更严格的激活路径,嵌入文档可能没有可用分享界面。产品应在每种状态保留同一核心任务。测试可见控件,不要测试厂商特定的面板布局。即使浏览器更新改变可用性,也能保持稳定的界面契约。
可访问性审核既要覆盖激活也要覆盖取消。确认键盘用户无需指针即可开始、返回同一控件并选择回退;屏幕阅读器状态不会播报私密内容;长本地化标题换行时不会遮住回退操作;强制颜色和高缩放仍保持主要分享控件与次要选项的边界。这些检查让有条件的能力可理解而非令人意外。
隐私审核应追问应用从一次尝试中学到了什么。答案通常仅限用户主动点击和有界本地结果,不是接收方清单、联系人图谱、操作系统画像或交付确认。如果产品分析无法用某字段改进具体决策,就不要收集。事件模式要独立于账户元数据,短期诊断完成支持用途后删除。
自有兼容性 fixture 可以覆盖普通状态,但不要假装能自动化接收方。先在没有分享方法的页面验证复制或下载;恢复方法后验证在预期点击处理器之外调用会停留在回退路径;再测试有效数据、取消、不支持的文件和错误。每个用例都应保留文档并让下一步用户操作明确。
同一契约也适用于实现变更后。增加文件、修改链接格式、嵌入分享控件或改变访问策略时,用新表示重跑 fixture。比较用户可见结果和最小诊断记录。不要从旧成功推断新文件类型或 frame 上下文一定有效;验证新路径期间保留回退。
文件处理需要明确限制。展示主要操作前检查类型和大小,并说明文件过大或当前上下文不可用的原因。文件路径无法继续时,复制链接回退仍可保留任务。应用创建临时文件时,为其设定应用拥有的生命周期,取消或完成后释放,并保留重新生成方式。绝不要通过静默上传到新服务或改变受众来解决大小拒绝。
安全上下文检查应与能力检查并列。普通 HTTP 页面、策略严格的嵌入文档和顶层 HTTPS 页面即使渲染同一按钮,也可能给出不同结果。把每种上下文视为受支持或不受支持的产品状态,并显示相同核心回退。若差异来自文档或浏览器条件,不要描述为用户权限选择;这样支持团队能区分部署配置问题和用户取消。
取消测试应覆盖时序:数据准备完成前取消、浏览器界面打开后取消、回退可用后取消。任何情况下页面都不应留下禁用控件、重复下载或丢失编辑字段。取消后第二次激活应以新校验开始一次新尝试。触控设备上的系统界面可能以不可预测时长遮盖页面,因此这些检查尤其重要。
最终状态应局限于本次操作。分享尝试可以更新 live region、按钮标签或小状态行,但不应改变账户状态或把服务器记录标记为已交付。需要服务器邀请时,应作为有独立响应和审计规则的单独确认操作;浏览器交接只是其旁边的便利功能,不应成为隐式授权步骤。
Permissions Policy 与嵌入环境
顶层文档与 iframe 的 Web Share 可用性可能不同。受策略控制功能的默认 allowlist 是 self,因此同源 iframe 可能不需要跨源委托。顶层策略允许时,跨源嵌入文档需要显式委托;最小嵌入可以声明 allow="web-share"。该属性不会选择接收方或授予用户权限,只参与浏览器的策略决策。
<iframe src="https://share.example.test/panel" title="Share panel" allow="web-share"></iframe>
响应策略、iframe 属性、安全上下文和浏览器实现都很重要。Permissions-Policy: web-share=() 这样的响应头会为文档禁用该功能,iframe 的 allow="web-share" 属性也无法重新打开它。当策略、临时激活或被安全阻止的文件阻止请求时,方法可能缺失,canShare() 可能拒绝数据集,或 navigator.share() 以 NotAllowedError 拒绝。不要把每个 NotAllowedError 都标成用户拒绝。检查上下文并保留相同回退。iframe 要分别测试已委托和未委托状态。
可复制的分享状态机
明确表示能力、激活、取消和并发请求。这个由页面拥有的状态机不模拟原生面板,也不声称 resolved promise 表示交付。
const shareState = { value: 'idle' };
async function runShare(data, render, fallback) {
if (shareState.value === 'sharing') return;
if (!window.isSecureContext || typeof navigator.share !== 'function') return fallback('unavailable');
if (navigator.canShare && !navigator.canShare(data)) return fallback('unsupported-data');
shareState.value = 'sharing';
render(shareState.value);
try {
await navigator.share(data);
shareState.value = 'complete';
} catch (error) {
shareState.value = error?.name === 'AbortError' ? 'cancelled' : 'failed';
fallback(shareState.value);
}
render(shareState.value);
}
阻止第二次请求的是应用状态。InvalidStateError 不同于 NotAllowedError:前者用于处理活跃或其他无效的分享请求,后者通常反映策略、临时激活或被安全阻止的文件。恢复控件并提供回退。Promise 时序和系统可视界面因平台而异。只报告本地结果,绝不要做普遍的“已交付”声明。
决策表与预期结果
| 条件 | 页面决策 | 预期可见结果 |
|---|---|---|
| 顶层 HTTPS、方法存在、URL 有效、刚刚点击 | 只调用一次 share() | 先忙碌,再完成或取消;保留回退 |
| HTTP 或受限嵌入上下文 | 不调用 | 说明分享不可用并显示复制/下载 |
| 未委托策略的 iframe | 将能力视为不可用 | 相同的可访问回退,不重复提示 |
| 已知分享字段无效或不受支持 | 保持数据不变 | 为受支持形式提供链接或下载 |
NotAllowedError | 分类为策略、激活或安全阻止文件 | 恢复控件并显示回退状态 |
InvalidStateError 或并发请求 | 保持一个请求责任方 | 显示忙碌/重试状态,不重复调用 |
已取消或没有可用目标(AbortError) | 将本地状态设为 cancelled | 保留任务、显示回退状态并返回 idle |
该表是产品契约,不是对某个操作系统面板的承诺。每一行都要让核心任务可继续。私有资源在创建 URL 前重新授权;文件要检查类型和大小并保留下载路径。
未知字典成员会被忽略;canShare() 结果由已知分享字段及其数据决定,不由额外的未知属性决定。已知字段无效或不受支持时,保持原任务完整并使用上面的回退。
常见问题
allow="web-share" 会授予分享权限吗?
不会。周围策略允许时,它只是委托受策略控制的功能。用户激活、安全上下文、数据检查、浏览器规则和操作系统选择仍然适用。
resolved 的 navigator.share() promise 能证明交付吗?
不能。它只确定调用页面可见的结果。平台可能在接受交接后 resolve,却不暴露接收方或传输完成情况。状态应保持事实准确,并保留复制或下载回退。
为什么两个有效按钮会产生 NotAllowedError?
调用可能在激活时机、frame 策略、安全上下文或文件是否被安全阻止方面不同。活跃或其他无效请求应处理为 InvalidStateError。记录受控上下文并恢复可访问回退,不要把任一错误当作身份或权限证据。
结论
Web Share 是用户发起的交接,能力有条件且不保证接收方或交付。使用时检查能力,保留临时激活,校验数据,把取消视为正常结果,并将复制、下载或链接回退作为一等路径。BotBrowser 让受控 UI 和回退检查可重复,同时把分享目标、浏览器介导、授权和隐私决策留给各自负责的系统。
延伸阅读
- Web Share API and user-initiated sharing
- Browser permission fingerprinting
- Browser feature support versus feature quality