如果你使用计算机已经有一段时间了,你可能知道剪贴板可以存储多种类型的数据(图像、富文本内容、文件等)。作为一名软件开发人员,我开始感到沮丧,因为我对剪贴板如何存储和组织不同类型的数据没有很好的理解。
最近,我决定揭开剪贴板的神秘面纱,并根据我的学习写了这篇文章。我们将重点关注网络剪贴板及其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/plain
、text/html
和image/png
之外的数据类型必须被拒绝:
如果_type_不在 强制数据类型[2] 列表中,则拒绝[...]并中止这些步骤。
有趣的是,application/json
MIME类型从 2012年[6] 到 2021年[7] 都在强制数据类型列表中,但在 w3c/clipboard-apis#155[8] 中从规范中移除。在那次更改之前,强制数据类型列表要长得多,从剪贴板读取有16种强制数据类型,写入有8种。更改后,只剩下text/plain
、text/html
和image/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
属性,类型为DataTransfer
。DataTransfer
对象被剪贴板事件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
及其setData
和getData
方法(这向我们展示了当时没有使用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参数使用任何字符串,但建议坚持使用常见类型。
[问题]我们应该列出一些"常见类型"吗?
能够为setData
和getData
使用_任何_字符串在今天仍然适用。这完全可以正常工作:
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/plain
和 text/html
。这一开始让我感到惊讶。Figma 如何在纯 HTML 中表示他们的各种布局和样式特性?
但看看 HTML,我们看到两个空的 span
元素,带有 data-metadata
和 data-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" }
注意:我替换了真实的 fileKey
和 pasteID
。
但是大的 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/plain
、text/html
和 image/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.html
和 public.utf8-plain-text
条目,但将自定义数据写入 org.mozilla.custom-clipdata
。它不像 Chrome 那样存储源 URL。
正如你可能预期的那样,Safari 也创建了 public.html
和 public.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
选项与我联系!它很好地契合了这篇文章的范围。
参考链接
- Web自定义格式: https://github.com/w3c/editing/blob/gh-pages/docs/clipboard-pickling/explainer.md
- 表示形式: https://www.w3.org/TR/clipboard-apis/#list-of-representations
- 要求: https://www.w3.org/TR/clipboard-apis/#mandatory-data-types-x
- 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
- 规范: https://www.w3.org/TR/clipboard-apis/#dom-clipboard-write
- 2012年: https://www.w3.org/TR/2012/WD-clipboard-apis-20120223/#mandatory-data-types-1
- 2021年: https://www.w3.org/TR/2021/WD-clipboard-apis-20210806/#mandatory-data-types-x
- w3c/clipboard-apis#155: https://github.com/w3c/clipboard-apis/pull/155
- 安全考虑: https://webkit.org/blog/8170/clipboard-api-improvements/#custom-mime-types:~:text=into web pages.-,Custom MIME Types,-Because the system
isTrusted
: https://developer.mozilla.org/en-US/docs/Web/API/Event/isTrusted- 2017年: https://www.w3.org/TR/2017/WD-clipboard-apis-20170929/
- 2003年的文章: https://www.arstdesign.com/articles/clipboardexploit.html
- 2011年进入规范: https://www.w3.org/TR/2011/WD-clipboard-apis-20110412/
- 规范中解释的: https://www.w3.org/TR/clipboard-apis/#integration-with-other-scripts-and-events
- 文档处于焦点状态: https://www.w3.org/TR/clipboard-apis/#privacy-async
- Electron: https://www.electronjs.org/
- Kiwi 消息格式: https://github.com/evanw/kiwi
- Evan Wallace: https://github.com/evanw
- 公开的: https://github.com/evanw/kiwi/issues/17#issuecomment-1937797254
- Windows 有: https://learn.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format
- macOS 有: https://developer.apple.com/documentation/appkit/nspasteboard/pasteboardtype/1529057-html
- Pasteboard Viewer: https://apps.apple.com/us/app/pasteboard-viewer/id1499215709
- 原始剪贴板访问: https://github.com/WICG/raw-clipboard-access/blob/f58f5cedc753d55c77994aa05e75d5d2ad7344a7/explainer.md
- chromestatus.com 上原始剪贴板访问功能的动机部分: https://chromestatus.com/feature/5682768497344512
- 安全问题: https://github.com/WICG/raw-clipboard-access/blob/f58f5cedc753d55c77994aa05e75d5d2ad7344a7/explainer.md#stakeholder-feedback--opposition
- Web 自定义格式: https://developer.chrome.com/blog/web-custom-formats-for-the-async-clipboard-api
- 使用不同的命名约定: https://github.com/dway123/clipboard-pickling/blob/bce5101564d379f48f11839e2c141ee51438e13c/explainer.md#os-interaction-format-naming
- 异步剪贴板API规范的Pickling: https://github.com/dway123/clipboard-pickling/blob/bce5101564d379f48f11839e2c141ee51438e13c/explainer.md#non-goals
- zip炸弹: https://en.wikipedia.org/wiki/Zip_bomb
- Thomas Steiner: https://developer.chrome.com/docs/web-platform/unsanitized-html-async-clipboard
- 利益相关者反馈/反对意见: https://github.com/w3c/editing/blob/gh-pages/docs/clipboard-unsanitized/explainer.md#stakeholder-feedback--opposition