跳转到内容

Manifest · 清单成员

share_target 清单成员

发布于

share_target 把已安装的 Web 应用注册为操作系统分享面板中的一个目标。用户选中该应用时,浏览器 把分享来的标题、文本、URL 与文件送到应用作用域内的一个 URL:要么作为 GET 导航的查询参数,要么 作为可被 Service Worker 拦截的 multipart/form-data POST。

Android 上的 Chrome 76、桌面端的 Edge 89(GET,文件支持有限)与 Samsung Internet 12.0 读取该 成员(BCD html.manifest.share_target)。iOS 与 macOS 上的 Safari 27 以及 Firefox 157 没有实现 它,应用不会出现在它们的分享面板里。该成员是分享的接收端;发送端是 navigator.share()。

分享目标流程图:manifest 以 action、method、enctype 与 params 声明 share_target;应用安装后系统分享面板会列出它,用户选中后浏览器要么带着 title、text、url 查询参数导航到 action URL(GET),要么提交 multipart/form-data,由 service worker 的 fetch 处理器用 formData() 读取、保存并以 303 重定向回应(POST);两条路径最终都落在展示分享内容的页面上。
分享目标:从 manifest 声明,经系统分享面板,到 GET 或 POST 处理器。
  • 类型:对象,含 action(接收分享的作用域内 URL)、method("GET" 或 "POST")、 enctype("application/x-www-form-urlencoded" 或 "multipart/form-data")以及 params: 一个把分享字段映射到参数名的对象,包括 title、text、url,以及 files,后者是 { "name", "accept" } 数组,accept 列出 MIME 类型或扩展名。
  • 默认值:缺省,应用不是分享目标。对象内部 method 默认 "GET",enctype 默认 "application/x-www-form-urlencoded"。
  • 示例值:{ "action": "/share", "method": "GET", "params": { "title": "title", "text": "text", "url": "url" } }。

Chromium 校验这些组合,组合不合法时丢弃整个成员,并记入清单的错误列表: invalid method. Allowed methods are: GET and POST.、 invalid enctype for GET method. Only application/x-www-form-urlencoded is supported.、 files are only supported with multipart/form-data POST.,以及 action 跨源或在 scope 之外时的 property 'share_target' ignored. Property 'action' is invalid.。因此文件要求 "method": "POST" 搭配 "enctype": "multipart/form-data";纯文本目标用默认值即可。

应用必须先安装才会出现在分享面板里,条目使用清单的 short_name 与图标。Android 把它与原生应用 并列显示;没有安装这个 PWA 的用户无法向它分享,所以分享目标页面还需要一条接收粘贴内容的普通 路由。分享载荷只有 text 没有 url 时,Android 上的 Chrome 经常把链接放在 text 里,处理器 要两个字段都解析。

示例沿着两条投递路径展开,最后处理两条路径都够不着的用户。

一个稍后阅读应用接收三个文本字段。分享会在已安装应用中打开 /share?title=…&text=…&url=…, 页面在加载时读取查询串。由于 Android 有时把链接放在 text 里,处理器回退到在其中找到的第一个 URL。

{
"share_target": {
"action": "/share",
"method": "GET",
"params": { "title": "title", "text": "text", "url": "url" }
}
}
const params = new URLSearchParams(location.search);
const text = params.get('text') ?? '';
const url = params.get('url') ?? text.match(/https?:\/\/\S+/)?.[0] ?? null;
if (url) {
saveLink({ title: params.get('title') ?? '', url });
} else {
showPasteForm(text); // 没有可分享的内容到达:让用户改为粘贴链接
}

保存后用 history.replaceState 清掉查询串,这样重新加载不会把同一个链接存两次。

图片需要 POST 与 multipart 编码。Service Worker 拦截发往 action 的 POST,读取表单数据,保存 文件,然后用 303 重定向回应,让客户端落在一个普通的 GET 页面上;没有这次重定向,重新加载会 重复提交这次分享。

{
"share_target": {
"action": "/share/upload",
"method": "POST",
"enctype": "multipart/form-data",
"params": {
"title": "title",
"files": [{ "name": "photos", "accept": ["image/jpeg", "image/png", "image/webp"] }]
}
}
}
service-worker.js
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);
if (event.request.method !== 'POST' || url.pathname !== '/share/upload') return;
event.respondWith((async () => {
const form = await event.request.formData();
const files = form.getAll('photos');
const cache = await caches.open('shared-inbox');
await Promise.all(files.map((file, i) => cache.put(`/shared/${Date.now()}-${i}`, new Response(file))));
return Response.redirect('/share/review', 303);
})());
});

/share/review 页面列出 caches.open('shared-inbox') 里的内容,让用户确认后再上传到服务器, 这也覆盖了设备离线时到达的分享。

分享面板只列出已安装的应用,Safari 27 与 Firefox 157 则根本不列出 Web 应用。一条接受粘贴内容、并在 navigator.share 存在时提供发送方向的路由,能让这项功能在任何地方都可用。

const canReceive = matchMedia('(display-mode: standalone)').matches; // 已安装:系统可以向我们分享
const canSend = 'share' in navigator;
document.querySelector('#hint').textContent = canReceive
? '从任意应用分享并选择本应用。'
: canSend
? '安装应用即可接收分享;现在仍可用按钮向外分享。'
: '在输入框中粘贴链接或文本。';

display-mode: standalone 只是“已安装”的代理信号,不保证分享目标已经注册;但它是页面能拿到的 最好信号。

规范

规范状态
Web Share Target APIW3C 草案