网络剪贴板及其如何存储不同类型的数据

文摘   2024-09-17 11:00   上海  

如果你使用计算机已经有一段时间了,你可能知道剪贴板可以存储多种类型的数据(图像、富文本内容、文件等)。作为一名软件开发人员,我开始感到沮丧,因为我对剪贴板如何存储和组织不同类型的数据没有很好的理解。

最近,我决定揭开剪贴板的神秘面纱,并根据我的学习写了这篇文章。我们将重点关注网络剪贴板及其API,同时也会涉及它如何与操作系统剪贴板交互。

我们将从探索网络剪贴板API及其历史开始。剪贴板API在数据类型方面有一些有趣的限制,我们将看到一些公司如何绕过这些限制。我们还将看看一些旨在解决这些限制的提案(最值得注意的是 Web自定义格式[1] )。

如果你曾经想知道网络剪贴板是如何工作的,这篇文章就是为你准备的。

使用异步剪贴板API

如果我从一个网站复制一些内容并粘贴到Google文档中,一些格式会被保留,比如链接、字体大小和颜色。

但如果我打开VS Code并在那里粘贴,只有原始文本内容会被粘贴。

剪贴板通过允许信息以与MIME类型相关联的多个 表示形式[2] 存储来服务这两个用例。W3C剪贴板规范 要求[3] 对于写入和读取剪贴板,必须支持以下三种数据类型:

  • text/plain 用于纯文本。
  • text/html 用于HTML。
  • image/png 用于PNG图像。

所以当我之前粘贴时,Google文档读取了text/html表示并用它来保留富文本格式。VS Code只关心原始文本,读取text/plain表示。这很合理。

通过异步剪贴板API的read方法读取特定表示非常简单:

const items = await navigator.clipboard.read();

for (const item of items) {
  if (item.types.includes("text/html")) {
    const blob = await item.getType("text/html");
    const html = await blob.text();
    console.log(html);
  }
}

通过write向剪贴板写入多个表示稍微复杂一些,但仍然相对简单。首先,我们为每个想要写入剪贴板的表示构造Blob:

const textBlob = new Blob(["Hello, world"], { type: "text/plain" });
const htmlBlob = new Blob(["Hello, <em>world<em>"], { type: "text/html" });

一旦我们有了blob,我们将它们传递给一个新的ClipboardItem,以数据类型为键、blob为值的键值存储:

const clipboardItem = new ClipboardItem({
  "text/plain": textBlob,
  "text/html": htmlBlob
});

注意:我喜欢ClipboardItem接受键值存储。它很好地符合使用使非法状态无法表示的数据结构的想法,正如 Parse, don't validate[4] 中所讨论的。

最后,我们用新构造的ClipboardItem调用write:

await navigator.clipboard.write([clipboardItem]);

其他数据类型呢?

HTML和图像很酷,但是像JSON这样的通用数据交换格式呢?如果我正在编写一个支持复制粘贴的应用程序,我可以想象想要将JSON或一些二进制数据写入剪贴板。

让我们尝试将JSON数据写入剪贴板:

const json = JSON.stringify({ message: "Hello" });
const blob = new Blob([json], { type: "application/json" });
const clipboardItem = new ClipboardItem({ [blob.type]: blob });
await navigator.clipboard.write([clipboardItem]);

运行这个时,会抛出一个异常:

Failed to execute 'write' on 'Clipboard':
Type application/json not supported on write.

嗯,这是怎么回事?好吧,write的 规范[5] 告诉我们,除text/plaintext/htmlimage/png之外的数据类型必须被拒绝:

如果_type_不在 强制数据类型[2] 列表中,则拒绝[...]并中止这些步骤。

有趣的是,application/json MIME类型从 2012年[6] 到 2021年[7] 都在强制数据类型列表中,但在 w3c/clipboard-apis#155[8] 中从规范中移除。在那次更改之前,强制数据类型列表要长得多,从剪贴板读取有16种强制数据类型,写入有8种。更改后,只剩下text/plaintext/htmlimage/png

这一更改是在浏览器由于 安全考虑[9] 选择不支持许多强制类型之后做出的。这反映在规范中 强制数据类型[2] 部分的警告中:

警告!允许不受信任的脚本写入剪贴板的数据类型作为安全预防措施受到限制。
不受信任的脚本可以尝试通过将已知触发这些漏洞的数据放置在剪贴板上来利用本地软件中的安全漏洞。

好的,所以我们只能将有限的数据类型写入剪贴板。但是关于"_不受信任的_脚本"是什么意思?我们能否在"受信任的"脚本中运行代码,让我们将其他数据类型写入剪贴板?

isTrusted属性

也许"受信任"部分指的是事件上的 isTrusted[10]isTrusted是一个只读属性,只有在事件由用户代理分派时才设置为true。

document.addEventListener("copy", (e) => {
  if (e.isTrusted) {
    // 这里的代码只在用户触发复制时运行
  }
})

"由用户代理分派"意味着它是由用户触发的,例如用户按下Command C触发的复制事件。这与通过dispatchEvent()以编程方式分派的合成事件形成对比:

document.addEventListener("copy", (e) => {
  console.log("e.isTrusted is " + e.isTrusted);
});

document.dispatchEvent(new ClipboardEvent("copy"));

让我们看看剪贴板事件,看看它们是否允许我们将任意数据类型写入剪贴板。

剪贴板事件API

ClipboardEvent为复制、剪切和粘贴事件分派,它包含一个clipboardData属性,类型为DataTransferDataTransfer对象被剪贴板事件API用来保存数据的多个表示。

copy事件中写入剪贴板非常简单:

document.addEventListener("copy", (e) => {
  e.preventDefault();
  e.clipboardData.setData("text/plain", "Hello, world");
  e.clipboardData.setData("text/html", "Hello, <em>world</em>");
});

paste事件中从剪贴板读取同样简单:

document.addEventListener("paste", (e) => {
  e.preventDefault();
  const html = e.clipboardData.getData("text/html");
  if (html) {
    console.log(html);
  }
});

现在是大问题:我们能将JSON写入剪贴板吗?

document.addEventListener("copy", (e) => {
  e.preventDefault();
  const json = JSON.stringify({ message: "Hello" });
  e.clipboardData.setData("application/json", json);
});

没有抛出异常,但这实际上将JSON写入剪贴板了吗?让我们通过编写一个粘贴处理程序来验证,该处理程序遍历剪贴板中的所有条目并将它们记录下来:

document.addEventListener("paste", (e) => {
  for (const item of e.clipboardData.items) {
    console.log({ type: item.type, content: e.clipboardData.getData(item.type) });
  }
});

添加这两个处理程序并调用复制粘贴会导致记录以下内容:

{ "type": "application/json", content: "{\"message\":\"Hello\"}" }

它起作用了!似乎clipboardData.setData不像异步write方法那样限制数据类型。

但是...为什么?为什么我们可以使用clipboardData读写任意数据类型,但在使用异步剪贴板API时却不行?

clipboardData的历史

相对较新的异步剪贴板API是在 2017年[11] 添加到规范中的,但clipboardData比那要_老得多_。2006年的W3C剪贴板API草案定义了clipboardData及其setDatagetData方法(这向我们展示了当时没有使用MIME类型):

setData() 这需要一个或两个参数。第一个必须设置为"text"或"URL"(不区分大小写)。
getData() 这需要一个参数,允许目标请求特定类型的数据。

但事实证明,clipboardData甚至比2006年的草案还要老。看看"本文档的状态"部分的这段引文:

在很大程度上[本文档]描述了在Internet Explorer中实现的功能...
本文档的目的是[...]指定当前浏览器中实际有效的内容,或者[成为]它们改善互操作性的简单目标,而不是添加新功能。

这篇 2003年的文章[12] 详细介绍了当时,在Internet Explorer 4及以上版本中,你可以使用clipboardData在未经用户同意的情况下读取用户的剪贴板。由于Internet Explorer 4于1997年发布,因此在撰写本文时,clipboardData接口似乎至少有26年的历史。

MIME类型在 2011年进入规范[13] :

_dataType_参数是一个字符串,例如但不限于MIME类型...

如果脚本调用getData('text/html')...

当时,规范还没有确定应该使用哪些数据类型:

虽然可以为setData()的type参数使用任何字符串,但建议坚持使用常见类型。
[问题]我们应该列出一些"常见类型"吗?

能够为setDatagetData使用_任何_字符串在今天仍然适用。这完全可以正常工作:

document.addEventListener("copy", (e) => {
  e.preventDefault();
  e.clipboardData.setData("foo bar baz", "Hello, world");
});

document.addEventListener("paste", (e) => {
  const content = e.clipboardData.getData("foo bar baz");
  if (content) {
    console.log(content);
  }
});

如果你将这个代码片段粘贴到你的DevTools中,然后点击复制和粘贴,你会看到消息"Hello, world"被记录到你的控制台。

剪贴板事件API的clipboardData允许我们使用任何数据类型的原因似乎是历史性的。"不要破坏网络"

重新审视isTrusted

让我们再次考虑 强制数据类型[2] 部分的这句话:

允许不受信任的脚本写入剪贴板的数据类型作为安全预防措施受到限制。

那么,如果我们尝试在合成(不受信任的)剪贴板事件中写入剪贴板会发生什么?

document.addEventListener("copy", (e) => {
  e.preventDefault();
  e.clipboardData.setData("text/plain", "Hello");
});

document.dispatchEvent(new ClipboardEvent("copy", {
  clipboardData: new DataTransfer(),
}));

这成功运行了,但它并没有修改剪贴板。这是 规范中解释的[14] 预期行为:

合成剪切和复制事件_不得_修改系统剪贴板上的数据。

合成粘贴事件_不得_让脚本访问真实系统剪贴板上的数据。

所以只有由用户代理分派的复制和粘贴事件才允许修改剪贴板。这完全有道理 - 我不希望网站自由地读取我的剪贴板内容并窃取我的密码。


总结我们到目前为止的发现:

  • 2017年引入的异步剪贴板API限制了可以写入和读取剪贴板的数据类型。然而,只要用户已经授予权限(并且 文档处于焦点状态[15] ),它就可以随时读取和写入剪贴板。
  • 较旧的剪贴板事件API对可以写入和读取剪贴板的数据类型没有真正的限制。然而,它只能在由用户代理触发的复制和粘贴事件处理程序中使用(即当isTrusted为true时)。

看来如果你想将纯文本、HTML或图像以外的数据类型写入剪贴板,使用剪贴板事件API是唯一的前进方向。在这方面,它的限制要少得多。

但是如果你想要构建一个可以将非标准数据类型写入剪贴板的复制按钮呢?如果用户没有触发复制事件,似乎就无法使用剪贴板事件 API。对吗?

构建可复制任意数据类型的复制按钮

我尝试了不同 web 应用程序中的复制按钮,并检查了写入剪贴板的内容。得到了一些有趣的结果。

Google Docs 有一个复制按钮,可以在右键菜单中找到。

这个复制按钮会向剪贴板写入三种表示形式:

  • text/plain,
  • text/html, 和
  • application/x-vnd.google-docs-document-slice-clip+wrapped

注意:第三种表示形式包含 JSON 数据。

他们正在向剪贴板写入自定义数据类型,这意味着他们没有使用异步剪贴板 API。他们是如何通过点击处理程序来实现这一点的呢?

我运行了性能分析器,点击了复制按钮,并检查了结果。结果发现,点击复制按钮会触发对 document.execCommand("copy") 的调用。

这让我很惊讶。我的第一反应是"execCommand 不是复制文本到剪贴板的旧的、已弃用的方式吗?"。

是的,确实如此,但 Google 使用它是有原因的。execCommand 的特殊之处在于,它允许你以编程方式分发一个可信的复制事件,就好像用户自己调用了复制命令一样。

document.addEventListener("copy", (e) => {
  console.log("e.isTrusted is " + e.isTrusted);
});

document.execCommand("copy");

注意:Safari 要求有一个活动选择才能让 execCommand("copy") 分发复制事件。可以通过在调用 execCommand("copy") 之前向 DOM 添加一个非空的 input 元素并选中它来伪造这个选择,之后可以从 DOM 中移除该 input。

好的,所以使用 execCommand 允许我们在响应点击事件时向剪贴板写入任意数据类型。很酷!

那粘贴呢?我们可以使用 execCommand("paste") 吗?

构建粘贴按钮

让我们试试 Google Docs 中的粘贴按钮,看看它是如何工作的。

在我的 Macbook 上,我得到一个弹出窗口,告诉我需要安装一个扩展程序才能使用粘贴按钮。

但奇怪的是,在我的 Windows 笔记本电脑上,粘贴按钮就可以正常工作。

很奇怪。这种不一致性从何而来?嗯,是否可以使用粘贴按钮可以通过运行 queryCommandSupported("paste") 来检查:

document.queryCommandSupported("paste");

在我的 Macbook 上,Chrome 和 Firefox 返回 false,但 Safari 返回 true

Safari 出于对隐私的考虑,要求我确认粘贴操作。我认为这是一个非常好的想法。它明确表示网站将从你的剪贴板读取内容。

在我的 Windows 笔记本电脑上,Chrome 和 Edge 返回 true,但 Firefox 返回 false。Chrome 的不一致性令人惊讶。为什么 Chrome 在 Windows 上允许 execCommand("paste"),但在 macOS 上不允许?我没能找到关于这一点的任何信息。

我觉得很奇怪,Google 在 execCommand("paste") 不可用时没有尝试回退到异步剪贴板 API。尽管他们无法使用它读取 application/x-vnd.google-[...] 表示形式,但 HTML 表示形式包含可以使用的内部 ID。

<meta charset="utf-8">
<b id="docs-internal-guid-[guid]" style="...">
  <span style="...">复制的文本</span>
</b>


另一个带有粘贴按钮的 web 应用程序是 Figma,他们采取了完全不同的方法。让我们看看他们是怎么做的。

Figma 中的复制和粘贴

Figma 是一个基于 web 的应用程序(他们的原生应用使用  Electron[16] )。让我们看看他们的复制按钮向剪贴板写入了什么。

Figma 的复制按钮向剪贴板写入两种表示形式:text/plaintext/html。这一开始让我感到惊讶。Figma 如何在纯 HTML 中表示他们的各种布局和样式特性?

但看看 HTML,我们看到两个空的 span 元素,带有 data-metadatadata-buffer 属性:

<meta charset="utf-8">
<div>
  <span data-metadata="<!--(figmeta)eyJma[...]9ifQo=(/figmeta)-->"></span>
  <span data-buffer="<!--(figma)ZmlnL[...]P/Ag==(/figma)-->"></span>
</div>
<span style="white-space:pre-wrap;">文本</span>

注意:对于一个空帧,data-buffer 字符串长度约为 26,000 个字符。之后,data-buffer 的长度似乎随着复制内容的增加而线性增长。

看起来像是 base64。eyJ 开头清楚地表明 data-metadata 是一个 base64 编码的 JSON 字符串。对 data-metadata 运行 JSON.parse(atob()) 得到:

{
  "fileKey": "4XvKUK38NtRPZASgUJiZ87",
  "pasteID": 1261442360,
  "dataType": "scene"
}

注意:我替换了真实的 fileKeypasteID

但是大的 data-buffer 属性呢?对其进行 base64 解码得到以下结果:

fig-kiwiF\x00\x00\x00\x1CK\x00\x00µ½\v\x9CdI[...]\x197Ü\x83\x03

看起来像是二进制格式。经过一番挖掘——使用 fig-kiwi 作为线索——我发现这是  Kiwi 消息格式[17] (由 Figma 的联合创始人和前 CTO  Evan Wallace[18]  创建),用于编码 .fig 文件。

由于 Kiwi 是一种基于模式的格式,似乎如果不知道模式,我们就无法解析这些数据。然而,幸运的是,Evan 创建了一个 公开的[19] 。让我们试试把缓冲区插入其中!

为了将缓冲区转换为 .fig 文件,我写了一个小脚本来生成一个 Blob URL:

const base64 = "ZmlnL[...]P/Ag==";
const blob = base64toBlob(base64, "application/octet-stream");
console.log(URL.createObjectURL(blob));

然后我将生成的 blob 下载为 .fig 文件,上传到 .fig 文件解析器,瞧:

所以 Figma 中的复制是通过创建一个小的 Figma 文件,将其编码为 base64,将结果 base64 字符串放入空 HTML span 元素的 data-buffer 属性中,并将其存储在用户的剪贴板中来实现的。

复制粘贴 HTML 的好处

这一开始对我来说似乎有点愚蠢,但采取这种方法有一个很大的好处。要理解原因,请考虑基于 web 的剪贴板 API 如何与各种操作系统剪贴板 API 交互。

Windows、macOS 和 Linux 都提供了不同的格式来向剪贴板写入数据。如果你想向剪贴板写入 HTML, Windows 有[20]  而  macOS 有[21]

所有操作系统都为"标准"格式(纯文本、HTML 和 PNG 图像)提供类型。但是当用户尝试将任意数据类型(如 application/foo-bar)写入剪贴板时,浏览器应该使用哪种操作系统格式呢?

没有很好的匹配,所以浏览器不会将该表示形式写入操作系统剪贴板上的常见格式。相反,该表示形式只存在于操作系统剪贴板上的自定义浏览器特定剪贴板格式中。这导致能够在浏览器标签页之间复制和粘贴任意数据类型,但无法在应用程序之间进行复制和粘贴。

这就是为什么使用常见数据类型 text/plaintext/htmlimage/png 如此方便。它们被映射到常见的操作系统剪贴板格式,因此可以很容易地被其他应用程序读取,这使得跨应用程序的复制/粘贴成为可能。在 Figma 的情况下,使用 text/html 可以在浏览器中从 figma.com 复制 Figma 元素,然后将其粘贴到原生 Figma 应用程序中,反之亦然。

浏览器为自定义数据类型向剪贴板写入什么?

我们已经了解到,我们可以在浏览器标签页之间写入和读取自定义数据类型到剪贴板,但不能跨应用程序。但是当我们向 web 剪贴板写入自定义数据类型时,浏览器究竟向原生操作系统剪贴板写入了什么?

我在 Macbook 上的每个主要浏览器的 copy 监听器中运行了以下代码:

document.addEventListener("copy", (e) => {
  e.preventDefault();
  e.clipboardData.setData("text/plain", "Hello, world");
  e.clipboardData.setData("text/html", "<em>Hello, world</em>");
  e.clipboardData.setData("application/json", JSON.stringify({ type: "Hello, world" }));
  e.clipboardData.setData("foo bar baz", "Hello, world");
});

然后我使用  Pasteboard Viewer[22]  检查了剪贴板。Chrome 向 Pasteboard 添加了四个条目:

  • public.html 包含 HTML 表示形式。
  • public.utf8-plain-text 包含纯文本表示形式。
  • org.chromium.web-custom-data 包含自定义表示形式。
  • org.chromium.source-url 包含执行复制操作的网页 URL。

查看 org.chromium.web-custom-data,我们看到了我们复制的数据:

我想带重音的"î"和不一致的换行是由于某些分隔符显示不正确造成的。

Firefox 也创建了 public.htmlpublic.utf8-plain-text 条目,但将自定义数据写入 org.mozilla.custom-clipdata。它不像 Chrome 那样存储源 URL。

正如你可能预期的那样,Safari 也创建了 public.htmlpublic.utf8-plain-text 条目。它将自定义数据写入 com.apple.WebKit.custom-pasteboard-data,有趣的是,它还在那里存储了完整的表示形式列表(包括纯文本和 HTML)和源 URL。

注意:如果源 URL(域名)相同,Safari 允许在浏览器标签页之间复制粘贴自定义数据类型,但不允许在不同域名之间进行。Chrome 或 Firefox 似乎没有这个限制(尽管 Chrome 存储了源 URL)。

Web 的原始剪贴板访问

2019 年提出了一个 原始剪贴板访问[23] 的提案,该提案提出了一个 API,用于给 web 应用程序提供对原生操作系统剪贴板的原始读写访问。

chromestatus.com 上原始剪贴板访问功能的动机部分[24] 中的这段摘录简洁地突出了其好处:

没有原始剪贴板访问 [...] web 应用程序通常仅限于一小部分格式,并且无法与长尾格式进行互操作。例如,Figma 和 Photopea 无法与大多数图像格式进行互操作。

然而,由于 安全问题[25] (如在原生应用程序中远程执行代码的漏洞),原始剪贴板访问提案最终没有进一步推进。

最近关于向剪贴板写入自定义数据类型的提案是 Web 自定义格式提案(通常被称为 pickling)。

Web 自定义格式(Pickling)

2022 年,Chromium 在异步剪贴板 API 中实现了对  Web 自定义格式[26] 的支持。

它允许 web 应用程序通过在数据类型前加上 "web " 前缀,通过异步剪贴板 API 写入自定义数据类型:

const json = JSON.stringify({ message: "Hello, world" });
const jsonBlob = new Blob([json], { type: "application/json" });
const clipboardItem = new ClipboardItem({
  "web application/json": jsonBlob
});
navigator.clipboard.write([clipboardItem]);

这些可以像任何其他数据类型一样使用异步剪贴板 API 读取:

const items = await navigator.clipboard.read();
for (const item of items) {
  if (item.types.includes("web application/json")) {
    const blob = await item.getType("web application/json");
    const text = await blob.text();
    const data = JSON.parse(text);
    console.log(data);
  }
}

更有趣的是写入原生剪贴板的内容。在写入 web 自定义格式时,以下内容会被写入原生操作系统剪贴板:

  • 从数据类型到剪贴板条目名称的映射
  • 每种数据类型的剪贴板条目

在 macOS 上,映射被写入 org.w3.web-custom-format.map,其内容如下所示:

{
  "application/json": "org.w3.web-custom-format.type-0",
  "foo bar baz": "org.w3.web-custom-format.type-1"
}

"application/octet-stream": "org.w3.web-custom-format.type-1"

}

org.w3.web-custom-format.type-[索引]键对应于操作系统剪贴板上包含来自blob的未经净化数据的条目。这允许原生应用程序查看映射以了解是否有给定的表示可用,然后从相应的剪贴板条目中读取未经净化的内容。

注意:Windows和Linux 使用不同的命名约定[27] 来进行映射和剪贴板条目。

这避免了原始剪贴板访问带来的安全问题,因为Web应用程序无法将未经净化的数据写入它们想要的任何操作系统剪贴板格式。这带来了互操作性的权衡,这在 异步剪贴板API规范的Pickling[28] 中明确列出:

非目标

允许与未更新的传统原生应用程序进行互操作。这在原始剪贴板提案中进行了探讨,未来可能会进一步探讨,但会带来重大的安全挑战(在系统原生应用程序中远程执行代码)。

这意味着在使用自定义数据类型时,原生应用程序需要更新以实现与Web应用程序的剪贴板互操作。

Web自定义格式自2022年以来已在基于Chromium的浏览器中可用,但其他浏览器尚未实现此提案。

结语

截至目前,还没有一种在所有浏览器中都能正常工作的写入自定义数据类型到剪贴板的好方法。Figma的方法是将base64字符串放入HTML表示中,虽然粗糙但有效,因为它规避了剪贴板API的诸多限制。如果你需要通过剪贴板传输自定义数据类型,这似乎是一个不错的方法。

我认为Web自定义格式提案很有前景,希望它能被所有主要浏览器实现。它似乎能够以安全和实用的方式将自定义数据类型写入剪贴板。

感谢阅读!希望这篇文章对你有所帮助。

附录:unsanitized选项

通过异步剪贴板API从剪贴板读取时,浏览器可能会对数据进行净化。例如,浏览器可能会从HTML中删除潜在危险的脚本标签,并可能重新编码PNG图像以避免 zip炸弹[29] 攻击。

因此,异步剪贴板API的read方法包含一个unsanitized选项,允许你请求未经净化的数据。你可以在 Thomas Steiner[30] 的这篇文章中阅读更多关于此选项及其工作原理的信息。

目前,unsanitized选项仅在基于Chromium的浏览器中支持(2023年底添加)。其他浏览器可能会在未来支持此选项(尽管Safari似乎不太可能这样做,参见 利益相关者反馈/反对意见[31] )。

感谢Tom就提及unsanitized选项与我联系!它很好地契合了这篇文章的范围。

参考链接

  1. Web自定义格式: https://github.com/w3c/editing/blob/gh-pages/docs/clipboard-pickling/explainer.md
  2. 表示形式: https://www.w3.org/TR/clipboard-apis/#list-of-representations
  3. 要求: https://www.w3.org/TR/clipboard-apis/#mandatory-data-types-x
  4. Parse, don't validate: https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/#:~:text=Use a data structure that makes illegal states unrepresentable
  5. 规范: https://www.w3.org/TR/clipboard-apis/#dom-clipboard-write
  6. 2012年: https://www.w3.org/TR/2012/WD-clipboard-apis-20120223/#mandatory-data-types-1
  7. 2021年: https://www.w3.org/TR/2021/WD-clipboard-apis-20210806/#mandatory-data-types-x
  8. w3c/clipboard-apis#155: https://github.com/w3c/clipboard-apis/pull/155
  9. 安全考虑: https://webkit.org/blog/8170/clipboard-api-improvements/#custom-mime-types:~:text=into web pages.-,Custom MIME Types,-Because the system
  10. isTrusted: https://developer.mozilla.org/en-US/docs/Web/API/Event/isTrusted
  11. 2017年: https://www.w3.org/TR/2017/WD-clipboard-apis-20170929/
  12. 2003年的文章: https://www.arstdesign.com/articles/clipboardexploit.html
  13. 2011年进入规范: https://www.w3.org/TR/2011/WD-clipboard-apis-20110412/
  14. 规范中解释的: https://www.w3.org/TR/clipboard-apis/#integration-with-other-scripts-and-events
  15. 文档处于焦点状态: https://www.w3.org/TR/clipboard-apis/#privacy-async
  16. Electron: https://www.electronjs.org/
  17. Kiwi 消息格式: https://github.com/evanw/kiwi
  18. Evan Wallace: https://github.com/evanw
  19. 公开的: https://github.com/evanw/kiwi/issues/17#issuecomment-1937797254
  20. Windows 有: https://learn.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format
  21. macOS 有: https://developer.apple.com/documentation/appkit/nspasteboard/pasteboardtype/1529057-html
  22. Pasteboard Viewer: https://apps.apple.com/us/app/pasteboard-viewer/id1499215709
  23. 原始剪贴板访问: https://github.com/WICG/raw-clipboard-access/blob/f58f5cedc753d55c77994aa05e75d5d2ad7344a7/explainer.md
  24. chromestatus.com 上原始剪贴板访问功能的动机部分: https://chromestatus.com/feature/5682768497344512
  25. 安全问题: https://github.com/WICG/raw-clipboard-access/blob/f58f5cedc753d55c77994aa05e75d5d2ad7344a7/explainer.md#stakeholder-feedback--opposition
  26. Web 自定义格式: https://developer.chrome.com/blog/web-custom-formats-for-the-async-clipboard-api
  27. 使用不同的命名约定: https://github.com/dway123/clipboard-pickling/blob/bce5101564d379f48f11839e2c141ee51438e13c/explainer.md#os-interaction-format-naming
  28. 异步剪贴板API规范的Pickling: https://github.com/dway123/clipboard-pickling/blob/bce5101564d379f48f11839e2c141ee51438e13c/explainer.md#non-goals
  29. zip炸弹: https://en.wikipedia.org/wiki/Zip_bomb
  30. Thomas Steiner: https://developer.chrome.com/docs/web-platform/unsanitized-html-async-clipboard
  31. 利益相关者反馈/反对意见: https://github.com/w3c/editing/blob/gh-pages/docs/clipboard-unsanitized/explainer.md#stakeholder-feedback--opposition

幻想发生器
图解技术本质
 最新文章