# Web Share API

> navigator.share() 把 URL、标题、文本或文件交给系统分享面板，canShare() 先行校验。本页列出 ShareData 全部成员、每个 DOMException 及带回退分支的示例。

`navigator.share()` 把 URL、标题、文本或文件交给操作系统的分享面板，PWA 不必自建分享菜单就能把内容发给用户装的任意应用。`navigator.canShare()` 同步校验同一份载荷，并且与 `share()` 不同，它不要求用户手势。

桌面端支持是局部的：Chrome 89 与 Edge 81 只在 Windows 和 ChromeOS 上实现（Chromium bug [40542648](https://crbug.com/40542648) 与 [40729163](https://crbug.com/40729163) 跟踪 macOS 和 Linux），Firefox 71 把它放在 `dom.webshare.enabled` 偏好设置之后，Android WebView 没有实现（[crbug.com/40540400](https://crbug.com/40540400)）。macOS 上的 Safari 12.1、iOS 上的 Safari 12.2、Android 上的 Chrome 61 与 Firefox 79 均无附加条件地支持。

## 语法

```js
navigator.share()
navigator.share(data)

navigator.canShare()
navigator.canShare(data)
```

`share()` 返回 `Promise<undefined>`，数据传给所选目标（或目标无法确认接收时传给操作系统）后兑现。`canShare()` 返回 `boolean`，是两者中唯一可以在用户激活处理函数之外调用的方法。两者都带 `[SecureContext]`：在 `localhost` 以外的 `http://` 源上，`navigator.share` 为 `undefined`。

## 参数

两个方法都接受一个可选的 `data` 参数，类型为 `ShareData` 字典。每个成员单独看都是可选的，但字典必须至少含有 `title`、`text`、`url` 之一或非空的 `files`，否则校验失败。

| 成员 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `title` | `USVString` | 否 | 分享内容的标题。邮件类目标用作主题；许多即时通讯类目标会丢弃它。 |
| `text` | `USVString` | 否 | 自由文本正文，可与 `url` 同传或替代 `url`。 |
| `url` | `USVString` | 否 | 绝对或相对 URL，分享前按文档的 base URL 解析；`""` 表示当前页面。只有 `http:`、`https:` 以及浏览器安全名单内的协议可分享。 |
| `files` | `sequence<File>` | 否 | 要分享的文件。没有其他成员时 `{ files: [] }` 按空字典处理；`{ text: "x", files: [] }` 会被接受，空列表被忽略。 |

浏览器不认识的成员会被静默丢弃（WebIDL 字典语义），所以只认 `title`、`text`、`url` 的浏览器会分享这三项并忽略 `files`。需要确认每个成员都受支持时，把它们逐个单独传给 `canShare()`。

## 异常

`share()` 以下列 `DOMException` 名称之一拒绝其 Promise，顺序即规范算法的检查顺序。

| 异常 | 条件 |
|---|---|
| `InvalidStateError` | 文档不是 fully active，或上一次 `share()` 的 Promise 仍未完结（`[[sharePromise]]` 非 `null`）。Chromium 在 Android 上跳过第二项检查。 |
| `NotAllowedError` | `web-share` Permissions Policy 拒绝了该文档（默认允许列表为 `'self'`，跨源 iframe 需要 `allow="web-share"`）；或调用时没有瞬时用户激活；或某文件类型因安全原因被拦。 |
| `TypeError` | 「validate share data」返回 `false`：没有任何成员；`url` 解析失败；`url` 使用 local scheme、`file:`、`javascript:`、`ws:`、`wss:` 或任何不可分享的协议；传了 `files` 但浏览器不支持文件分享，或判定某个文件可能有害。 |
| `AbortError` | 没有可用的分享目标，或用户关闭了分享面板。 |
| `DataError` | 所选目标启动失败，或向它传输数据失败。 |

`canShare()` 不抛任何异常。它对会让 `share()` 以 `TypeError` 拒绝的那些条件返回 `false`，其余情况返回 `true`，包括 `share()` 之后会因用户取消而以 `AbortError` 拒绝的情况（见 [canShare() 算法](https://www.w3.org/TR/web-share/#canshare-method)（w3.org））。

:::observed
在 Chrome 中从 `setTimeout` 或 `DOMContentLoaded` 处理函数调用 `navigator.share()`，会以 `NotAllowedError: Must be handling a user gesture to perform a share request.` 拒绝；未处理该拒绝时，DevTools Console 打印 `Uncaught (in promise) NotAllowedError: Must be handling a user gesture to perform a share request.`。分享面板尚未关闭时再次调用，在 Windows 与 ChromeOS 上以 `InvalidStateError: An earlier share has not yet completed.` 拒绝；被 Permissions Policy 拦下的调用则以 `NotAllowedError: Permission denied` 拒绝。三条字符串都来自 Chromium 的 [`navigator_share.cc`](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/modules/webshare/navigator_share.cc)（chromium.googlesource.com）。
:::

## 示例

每个示例都先做特性检测，并给出不支持时实际执行的分支。三段代码都必须由 `click` 之类的用户激活处理函数触发。

### 分享当前页面，缺少支持时回退到剪贴板

绑定按钮前先检测 `navigator.share`；缺失时（偏好设置关闭的桌面 Firefox、Android WebView、任何 `http://` 源）改为复制 URL 并告知用户。分享调用本身必须留在 `click` 处理函数内，这样 `share()` 执行时瞬时激活仍然有效。

```js
const button = document.querySelector('#share');

button.addEventListener('click', async () => {
  const payload = { title: document.title, url: location.href };

  if (!navigator.share) {
    await navigator.clipboard.writeText(location.href);
    button.textContent = '链接已复制';
    return;
  }

  try {
    await navigator.share(payload);
  } catch (err) {
    if (err.name === 'AbortError') return; // 用户关闭了面板
    console.error(`${err.name}: ${err.message}`);
  }
});
```

在 `share()` 之前 `await` 任何东西（统计上报、一次 `fetch`）是丢失激活的常见原因；先分享，待 Promise 兑现后再记录事件。

### 仅在 `canShare()` 接受时分享生成的文本文件

文件分享的支持面比 URL 分享窄：Android 上的 Chrome 76 加入，桌面 Chrome 89 只在 Windows 和 ChromeOS 上分享文件，Safari 14 加入 `files`，Firefox 任何版本都没有文件分享（BCD `api.Navigator.share.data_files_parameter`）。先构造 `File`，用 `canShare()` 询问当前浏览器是否接受，不接受时回退为下载链接。

```js
async function shareReport(csvText) {
  const file = new File([csvText], 'report.csv', { type: 'text/csv' });

  if (navigator.canShare?.({ files: [file] })) {
    try {
      await navigator.share({ files: [file], title: '报表' });
    } catch (err) {
      if (err.name !== 'AbortError') throw err;
    }
    return;
  }

  const link = document.createElement('a');
  link.href = URL.createObjectURL(file);
  link.download = file.name;
  link.click();
  URL.revokeObjectURL(link.href);
}
```

浏览器完全不能分享文件，或只是拒绝这一个文件，`canShare({ files })` 都返回 `false`，所以一个回退分支就能覆盖两种情况。

### 多成员分享前逐个检查成员

未知成员被忽略而不是被拒绝，所以不支持 `files` 的浏览器仍会兑现 `share({ text, files })`，只是只分享了文本。部分分享会误导用户时，按成员逐个校验。

```js
function supportedMembers(data) {
  if (!navigator.canShare) return [];
  return Object.entries(data)
    .filter(([key, value]) => navigator.canShare({ [key]: value }))
    .map(([key]) => key);
}

const data = { title: '旅行照片', text: '周末拍的', files: photoFiles };
const ok = supportedMembers(data);

if (ok.includes('files')) {
  await navigator.share(data);
} else {
  await navigator.share({ title: data.title, text: data.text, url: galleryUrl });
}
```

设备带不动文件本身时，`url` 分支让接收方拿到指向同一批照片的链接。

## 另请参阅

- [Manifest share_target 字段](/zh/reference/manifest/share-target/)，分享的接收端
- [接收分享的内容（share target）](/zh/guides/share-target/)
- [Async Clipboard API](/zh/reference/capabilities/clipboard/)，`navigator.share` 为 undefined 时的常用回退
- [Web Share API: share() method](https://www.w3.org/TR/web-share/#share-method)（w3.org）
- [Web Share API: validate share data](https://www.w3.org/TR/web-share/#validate-share-data)（w3.org）
- [Chromium bug 40542648: Web Share on macOS](https://crbug.com/40542648)（crbug.com）
- [Chromium bug 40540400: Web Share in Android WebView](https://crbug.com/40540400)（crbug.com）