从 Chrome 120 开始,Async Clipboard API 中提供了新的 unsanitized
选项。在 HTML 的特殊情况下,此选项会很有用,因为您需要将剪贴板中的内容粘贴为与复制时相同的形式。也就是说,没有任何中间清理步骤,而浏览器通常会出于充分的理由应用这些步骤。如需了解如何使用此 API,请参阅本指南。
在使用 Async Clipboard API 时,在大多数情况下,开发者无需担心剪贴板上内容的完整性,并且可以假定他们写入剪贴板的内容(复制)与从剪贴板读取数据(粘贴)时得到的内容相同。
这对于文本来说绝对正确。请尝试在 DevTools 控制台中粘贴以下代码,然后立即重新聚焦页面。(必须使用 setTimeout()
,以便您有足够的时间将焦点移至页面,这是 Async Clipboard API 的要求。)如您所见,输入与输出完全相同。
setTimeout(async () => {
const input = 'Hello';
await navigator.clipboard.writeText(input);
const output = await navigator.clipboard.readText();
console.log(input, output, input === output);
// Logs "Hello Hello true".
}, 3000);
图片的情况略有不同。为了防范所谓的压缩炸弹攻击,浏览器会重新编码 PNG 等图片,但输入图片和输出图片在视觉上完全相同,像素与像素完全一样。
setTimeout(async () => {
const dataURL =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=';
const input = await fetch(dataURL).then((response) => response.blob());
await navigator.clipboard.write([
new ClipboardItem({
[input.type]: input,
}),
]);
const [clipboardItem] = await navigator.clipboard.read();
const output = await clipboardItem.getType(input.type);
console.log(input.size, output.size, input.type === output.type);
// Logs "68 161 true".
}, 3000);
不过,HTML 文本会怎么样?您可能已经猜到了,HTML 的情况有所不同。在这里,浏览器会对 HTML 代码进行排错,以防止出现不良情况,例如从 HTML 代码中剥离 <script>
标记(以及 <meta>
、<head>
和 <style>
等其他标记),并内嵌 CSS。请参考以下示例,并在开发者工具控制台中试用。您会发现,输出与输入有很大不同。
setTimeout(async () => {
const input = `<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="ProgId" content="Excel.Sheet" />
<meta name="Generator" content="Microsoft Excel 15" />
<style>
body {
font-family: HK Grotesk;
background-color: var(--color-bg);
}
</style>
</head>
<body>
<div>hello</div>
</body>
</html>`;
const inputBlob = new Blob([input], { type: 'text/html' });
await navigator.clipboard.write([
new ClipboardItem({
'text/html': inputBlob,
}),
]);
const [clipboardItem] = await navigator.clipboard.read();
const outputBlob = await clipboardItem.getType('text/html');
const output = await outputBlob.text();
console.log(input, output);
}, 3000);
通常,进行 HTML 清理是件好事。在大多数情况下,您不希望允许未经净化的 HTML,以免导致安全问题。不过,在某些情况下,开发者确切知道自己在做什么,并且输入和输出 HTML 的完整性对应用的正常运行至关重要。在这种情况下,您有两种选择:
- 如果您同时控制复制和粘贴端(例如,从应用中复制,然后同样在应用中粘贴),则应使用适用于 Async Clipboard API 的 Web 自定义格式。请停止阅读此处内容,并参阅链接的文章。
- 如果您只能控制应用中的粘贴端,而不能控制复制端(可能是因为复制操作发生在不支持 Web 自定义格式的原生应用中),则应使用
unsanitized
选项,本文的其余部分将对此进行介绍。
清理包括移除 script
标记、内嵌样式以及确保 HTML 格式正确等操作。此列表并非详尽无遗,未来可能会添加更多步骤。
复制并粘贴未经排错的 HTML
当您使用 Async Clipboard API 将 HTML write()
(复制)到剪贴板时,浏览器会通过 DOM 解析器运行 HTML 并序列化生成的 HTML 字符串,以确保其格式正确无误,但此步骤不会进行任何净化操作。您无需执行任何操作。当您 read()
其他应用放置在剪贴板上的 HTML 时,如果您的 Web 应用选择获取完整保真度内容,并且需要在自己的代码中执行任何净化操作,您可以将一个选项对象传递给 read()
方法,其中包含属性 unsanitized
和值 ['text/html']
。单独使用时,它看起来如下所示:navigator.clipboard.read({ unsanitized: ['text/html'] })
。以下代码示例与之前显示的代码示例几乎相同,但这次使用了 unsanitized
选项。在 DevTools 控制台中试用时,您会发现输入和输出相同。
setTimeout(async () => {
const input = `<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="ProgId" content="Excel.Sheet" />
<meta name="Generator" content="Microsoft Excel 15" />
<style>
body {
font-family: HK Grotesk;
background-color: var(--color-bg);
}
</style>
</head>
<body>
<div>hello</div>
</body>
</html>`;
const inputBlob = new Blob([input], { type: 'text/html' });
await navigator.clipboard.write([
new ClipboardItem({
'text/html': inputBlob,
}),
]);
const [clipboardItem] = await navigator.clipboard.read({
unsanitized: ['text/html'],
});
const outputBlob = await clipboardItem.getType('text/html');
const output = await outputBlob.text();
console.log(input, output);
}, 3000);
浏览器支持和功能检测
没有直接的方法来检查是否支持该功能,因此功能检测是基于观察行为进行的。因此,以下示例依赖于检测 <style>
标记是否保留(表示支持)或是否被内嵌(表示不支持)这一事实。请注意,为此,网页需要已获得剪贴板权限。
const supportsUnsanitized = async () => {
const input = `<style>p{color:red}</style><p>a`;
const inputBlob = new Blob([input], { type: 'text/html' });
await navigator.clipboard.write([
new ClipboardItem({
'text/html': inputBlob,
}),
]);
const [clipboardItem] = await navigator.clipboard.read({
unsanitized: ['text/html],
});
const outputBlob = await clipboardItem.getType('text/html');
const output = await outputBlob.text();
return /<style>/.test(output);
};
演示
如需查看 unsanitized
选项的实际运作方式,请参阅 Glitch 上的演示并查看其源代码。
总结
如简介中所述,大多数开发者无需担心剪贴板的清理工作,只需使用浏览器提供的默认清理选项即可。对于开发者需要注意的极少数情况,unsanitized
选项可用。
实用链接
致谢
本文由 Anupam Snigdha 和 Rachel Andrew 审核。该 API 由 Microsoft Edge 团队指定和实现。