Async Clipboard API 中未经过排错的 HTML

从 Chrome 120 开始,异步剪贴板中新增了 unsanitized 选项 API。在与 HTML 相关的特殊情况中,您需要 粘贴剪贴板中的内容,与复制时完全相同。 也就是说,无需浏览器和 有充分的理由提出申请。请参阅本指南,了解如何使用这项功能。

使用 Async Clipboard API 在大多数情况下,开发者无需担心 剪贴板中的内容,并假设他们将写入 剪贴板(复制)与从应用中读取数据时得到的内容相同 剪贴板(粘贴)。

对于文字,确实如此。尝试将以下代码粘贴到开发者工具中 然后立即重新聚焦页面。(setTimeout() 是必需的 这样您有足够的时间聚焦在页面上,而这是异步 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 的完整性对于正确生成内容至关重要, 应用的功能。在这种情况下,您有两种选择:

  1. 如果您同时控制复制和粘贴,例如,如果您复制 然后同样将其粘贴在应用中 适用于 Async Clipboard API 的网页自定义格式。 别再继续读下去了,不妨看看链接到的文章。
  2. 如果您只在应用中控制粘贴端,而不控制复制端, 可能是因为复制操作发生在不支持 网站自定义格式,则应使用 unsanitized 选项, 具体说明。

清理包括移除 script 标记、内嵌样式以及 确保 HTML 格式正确。此列表并不全面, 以后可能会添加步骤。

复制并粘贴未清理的 HTML

当您使用 Async Clipboard API write()(复制)HTML 到剪贴板时, 浏览器可以通过 DOM 解析器运行它,确保其格式正确 并对生成的 HTML 字符串进行序列化,但系统不会对 此步骤。您无需执行任何操作。当您将 read() HTML 放置在 剪贴板,而您的 Web 应用选择获取 并且需要在您自己的代码中执行任何清理, 您可以使用属性将选项对象传递给 read() 方法 unsanitized,值为 ['text/html']。具体如下所示: navigator.clipboard.read({ unsanitized: ['text/html'] })。以下代码示例 与之前显示的示例几乎相同,但这次使用 unsanitized 选项。在开发者工具控制台中试用此功能时,您会发现输入和 输出内容还是一样的

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 团队负责实现。