浏览器网页剪贴板及其存储不同类型数据的方式

如果你使用电脑有一段时间了,你可能知道剪贴板可以存储多种类型的数据(图像、富文本内容、文件等)。作为一名软件开发者,我开始感到沮丧,因为我对剪贴板如何存储和组织不同类型的数据缺乏深入的理解。

我最近决定揭开剪贴板的神秘面纱,并基于我的学习成果撰写了这篇文章。我们将重点探讨网页剪贴板及其API,同时也会涉及它与操作系统剪贴板的交互方式。

我们将首先探索网页剪贴板API及其发展历史。剪贴板API在数据类型方面存在一些有趣的限制,我们将看到一些公司是如何绕过这些限制的。我们还将探讨一些旨在解决这些限制的提案(最值得注意的是Web Custom Formats)。

如果你曾经好奇过网页剪贴板是如何工作的,这篇文章就是为你准备的。

使用异步剪贴板 API

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

图0:浏览器网页剪贴板及其存储不同类型数据的方式

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

图1:浏览器网页剪贴板及其存储不同类型数据的方式

剪贴板通过允许信息以与 MIME 类型关联的多种 表示形式 进行存储,来支持这两种使用场景。W3C 剪贴板规范规定在写入和读取剪贴板时,必须支持以下三种数据类型:

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

因此,当我之前粘贴时,Google Docs 读取了 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();
    // Do stuff with 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({
  [textBlob.type]: textBlob,
  [htmlBlob.type]: htmlBlob,
});

注意:我喜欢 ClipboardItem 接受键值存储。这与使用一种使非法状态无法表示的数据结构的理念相契合,如在 解析,不要验证 中讨论的 .

最后,我们使用新构建的 ClipboardItem 调用 write

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

其它数据类型呢?

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

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

// Create JSON blob
const json = JSON.stringify({ message: "Hello" });
const blob = new Blob([json], { type: "application/json" });


// Write JSON blob to clipboard
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 的规范,除了 text/plaintext/htmlimage/png 之外的数据类型必须被拒绝:

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

值得注意的是,application/json MIME 类型在 2012 to 2021 期间一直是强制性数据类型列表的一部分,但在 w3c/clipboard-apis#155 中被移除。在此变更之前,强制性数据类型的列表要长得多,从剪贴板读取时有 16 个强制性数据类型,写入时有 8 个。变更后,仅保留了 text/plaintext/htmlimage/png 三种数据类型。

这一变更是在浏览器因安全问题而选择不支持许多强制类型后做出的。这一变化在规范的强制性数据类型部分中以警告形式体现:

警告!出于安全考虑,未受信任的脚本写入剪贴板的数据类型受到限制。

未受信任的脚本可能会通过将已知会触发这些漏洞的数据放置到剪贴板中,来尝试利用本地软件中的安全漏洞。

好的,所以我们只能将有限类型的数据写入剪贴板。但“未受信任的脚本”是什么意思?我们是否可以通过在“受信任的脚本”中运行代码,来实现将其他类型的数据写入剪贴板?

isTrusted 属性

或许“可信”部分指的是事件上的 isTrusted 属性isTrusted 是一个只读属性,仅当事件由用户代理触发时才设置为 true。

document.addEventListener("copy", (e) => {
  if (e.isTrusted) {
    // This event was triggered by the user agent
  }
})

“由用户代理触发”意味着该事件是由用户触发的,例如用户按下 Command C 触发的复制事件。这与通过 dispatchEvent() 方法程序化触发的合成事件不同:

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


document.dispatchEvent(new ClipboardEvent("copy"));
//=> "e.isTrusted is false"

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

剪贴板事件 API

ClipboardEvent 用于触发复制、剪切和粘贴事件,它包含一个类型为 DataTransferclipboardData 属性。DataTransfer 对象用于存储数据的多种表示形式。

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

document.addEventListener("copy", (e) => {
  e.preventDefault(); // Prevent default copy behavior


  e.clipboardData.setData("text/plain", "Hello, world");
  e.clipboardData.setData("text/html", "Hello, <em>world</em>");
});

而在 paste 事件中从剪贴板读取数据同样简单:

document.addEventListener("paste", (e) => {
  e.preventDefault(); // Prevent default paste behavior


  const html = e.clipboardData.getData("text/html");
  if (html) {
    // Do stuff with HTML...
  }
});

现在来回答关键问题:我们能否将 JSON 写入剪贴板?

document.addEventListener("copy", (e) => {
  e.preventDefault();


  const json = JSON.stringify({ message: "Hello" });
  e.clipboardData.setData("application/json", json); // No error
});

没有抛出异常,但这是否真的将 JSON 写入了剪贴板?让我们通过编写一个粘贴处理程序来验证,该处理程序会遍历剪贴板中的所有条目并将其输出:

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

添加这两个处理程序并执行复制粘贴操作后,会输出以下内容:

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

成功了!似乎 clipboardData.setData 方法并未像异步 write 方法那样对数据类型进行限制。

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

clipboardData 的历史

相对较新的异步剪贴板 API 于 2017 被添加到规范中,但 clipboardData 要比这早得多。W3C于2006年发布的剪贴板API草案中定义了clipboardData及其setDatagetData方法(这表明当时尚未使用MIME类型):

setData() 该方法接受一个或两个参数。第一个参数必须设置为‘text’或‘URL’(不区分大小写)。

getData() 该方法接受一个参数,允许目标请求特定类型的数据。

但事实证明,clipboardData 的历史比 2006 年的草案还要悠久。请看“本文件的状态”部分中的这段引文:

本文档在很大程度上描述了Internet Explorer中实现的功能…

本文档的目的是[…]指定当前浏览器中实际可用的功能,或[作为]一个简单的目标,以促进浏览器之间的互操作性,而非添加新功能。

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

MIME类型于2011年被纳入规范

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

如果脚本调用getData(‘text/html’)…

当时,规范尚未确定应使用哪些数据类型:

虽然可以为setData()的类型参数使用任何字符串,但建议使用常见类型。

[Issue] 是否应列出一些“常见类型”?

使用任何字符串作为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); // Logs "Hello, world!"
  }
});

如果你将这段代码片段粘贴到开发者工具中,然后点击复制粘贴,你将在控制台看到“Hello, world”的日志输出。

Clipboard Events API 的 clipboardData 允许使用任何数据类型的原因似乎是历史遗留问题。“不要破坏网络”

重新审视 isTrusted

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

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

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

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


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

此操作执行成功,但并未修改剪贴板内容。这是预期行为如规范中所解释的

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

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

因此,只有由用户代理触发的复制和粘贴事件才允许修改剪贴板。这完全合理——我可不希望网站随意读取我的剪贴板内容并窃取我的密码。


总结我们目前的发现:

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

如果想要将非纯文本、HTML 或图像类型的数据写入剪贴板,似乎使用剪贴板事件 API 是唯一可行方案。在这一点上,它要宽松得多。

但如果你想构建一个将非标准数据类型写入剪贴板的复制按钮,该怎么办?如果用户未触发复制事件,似乎无法使用剪贴板事件 API。对吧?

构建一个可写入任意数据类型的复制按钮

我尝试在不同网页应用中测试了复制按钮,并检查了写入剪贴板的内容。结果颇具启发性。

Google Docs 的右键菜单中包含一个“复制”按钮。

图2:浏览器网页剪贴板及其存储不同类型数据的方式

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

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

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

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

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

图3:浏览器网页剪贴板及其存储不同类型数据的方式

这让我感到意外。我的第一反应是“execCommand 不是过时且被废弃的复制文本到剪贴板的方式吗?”

是的,它是,但谷歌有其使用原因。execCommand 的特殊之处在于,它允许你程序化地触发一个可信的复制事件,就好像用户自己调用了复制命令一样。

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


document.execCommand("copy");
//=> "e.isTrusted is true"

注意:Safari 要求在调用 execCommand(“copy”) 时必须有活跃的选中内容。可以通过在 DOM 中添加一个非空的输入元素并在此之前选中它来模拟选中内容,随后再移除该输入元素。

好,使用 execCommand 允许我们在点击事件响应中将任意数据类型写入剪贴板。太棒了!

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

创建粘贴按钮

让我们尝试 Google 文档中的粘贴按钮,看看它会做什么。

图4:浏览器网页剪贴板及其存储不同类型数据的方式

在我的MacBook上,弹出一个提示框,告诉我需要安装扩展程序才能使用粘贴按钮。

图5:浏览器网页剪贴板及其存储不同类型数据的方式

但奇怪的是,在我的Windows笔记本上,粘贴按钮直接就能用。

奇怪。这种不一致性是从哪里来的?其实,是否能使用粘贴按钮可以通过运行queryCommandSupported(“paste”)来检查:

document.queryCommandSupported("paste");

在我的MacBook上,Chrome和Firefox返回false,而Safari返回true

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

图6:浏览器网页剪贴板及其存储不同类型数据的方式

在我的 Windows 笔记本上,Chrome 和 Edge 返回 true,而 Firefox 返回 false。Chrome 与 Firefox 之间的差异令人惊讶。为什么 Chrome 在 Windows 上允许 execCommand(“paste”),但在 macOS 上不允许?我未能找到相关信息。

我感到惊讶的是,Google 并未尝试在 execCommand(“paste”) 不可用时回退到异步剪贴板 API。尽管他们无法通过该 API 读取 application/x-vnd.google-[...] 格式,但 HTML 格式中包含可用的内部 ID。

<!-- HTML representation, cleaned up -->
<meta charset="utf-8">
<b id="docs-internal-guid-[guid]" style="...">
  <span style="...">Copied text</span>
</b>

另一个带有粘贴按钮的网页应用是 Figma,他们采用了完全不同的方法。让我们看看他们是如何实现的。

Figma 中的复制和粘贴

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

图7:浏览器网页剪贴板及其存储不同类型数据的方式

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;">Text</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 消息格式(由 Figma 的联合创始人兼前 CTO Evan Wallace 创建),用于编码 .fig 文件。

由于Kiwi是一种基于模式的格式,似乎在不知道模式的情况下无法解析这些数据。不过幸运的是,Evan创建了一个公开的.fig文件解析器。让我们尝试将缓冲区插入其中!

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

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


console.log(URL.createObjectURL(blob));
//=> blob:<origin>/1fdf7c0a-5b56-4cb5-b7c0-fb665122b2ab

随后,我将生成的 Blob 下载为 .fig 文件,将其上传至 .fig 文件解析器,结果如愿以偿:

图8:浏览器网页剪贴板及其存储不同类型数据的方式

因此,Figma 中的复制功能是通过创建一个小型 Figma 文件,将其编码为 base64,将生成的 base64 字符串放入空 HTML span 元素的 data-buffer 属性中,并将其存储在用户的剪贴板中。

复制粘贴 HTML 的优势

起初我认为这有点荒谬,但这种方法确实有显著优势。要理解原因,需考虑基于网页的剪贴板 API 与各操作系统剪贴板 API 的交互方式。

Windows、macOS 和 Linux 系统在将数据写入剪贴板时采用不同的格式。若要将 HTML 写入剪贴板,Windows 提供 CF_HTML 和 macOS 提供 NSPasteboard.PasteboardType.html

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

没有合适的匹配项,因此浏览器不会将该表示形式写入操作系统剪贴板的常见格式。相反,该表示形式仅存在于操作系统剪贴板中的自定义浏览器专用剪贴板格式中。这使得可以在浏览器标签页之间复制和粘贴任意数据类型,但不能在应用程序之间进行。

这就是为什么使用常见数据类型 text/plaintext/htmlimage/png 如此方便。它们映射到操作系统剪贴板的通用格式,因此其他应用程序可以轻松读取,从而实现跨应用程序的复制粘贴功能。以 Figma 为例,使用 text/html 格式可实现从浏览器中的 figma.com 复制 Figma 元素,然后将其粘贴到原生 Figma 应用程序中,反之亦然。

浏览器会将自定义数据类型写入剪贴板的哪些内容?

我们已经了解到,可以在浏览器标签页之间写入和读取自定义数据类型,但无法在应用程序之间进行。那么,当我们将自定义数据类型写入网页剪贴板时,浏览器会向原生操作系统剪贴板写入什么内容?

我在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");
});

随后我使用剪贴板查看器检查了剪贴板。Chrome在剪贴板中添加了四个条目:

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

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

图9:浏览器网页剪贴板及其存储不同类型数据的方式

我推测带重音的“î”和不一致的换行符是由于某些分隔符显示不正确所致。

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 存储在此处。

注意:Safari 允许在浏览器标签页之间复制粘贴自定义数据类型,前提是源 URL(域名)相同,但不支持跨不同域名。这一限制似乎在 Chrome 或 Firefox 中不存在(尽管 Chrome 会存储源 URL)。

网页的原始剪贴板访问

关于 原始剪贴板访问 的提案于2019年提出,该提案旨在为网页应用程序提供访问原生操作系统剪贴板的原始读写权限。

以下是 chromestatus.com 上 动机部分 中关于原始剪贴板访问功能的摘录,简明扼要地概述了其优势:

没有原始剪贴板访问权限 […],网络应用程序通常只能处理少量格式,无法与大量其他格式实现互操作。例如,Figma 和 Photopea 无法与大多数图像格式实现互操作。

然而,由于安全问题而未被采纳,这些担忧主要围绕远程代码执行等漏洞在原生应用中的利用。

目前最新的将自定义数据类型写入剪贴板的提案是 Web 自定义格式提案(常被称为 pickling)。

Web 自定义格式(Pickling)

2022 年,Chromium 在异步剪贴板 API 中实现了对 Web Custom Formats 的支持。

它允许网页应用通过在数据类型前缀 “web ” 来使用异步剪贴板 API 写入自定义数据类型:

// Create JSON blob
const json = JSON.stringify({ message: "Hello, world" });
const jsonBlob = new Blob([json], { type: "application/json" });


// Write JSON blob to clipboard as a Web Custom Format
const clipboardItem = new ClipboardItem({
  [`web ${jsonBlob.type}`]: 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 json = await blob.text();
    // Do stuff with JSON...
  }
}

更值得注意的是写入原生剪贴板的内容。在写入网页自定义格式时,以下内容会被写入原生操作系统剪贴板:

  • 数据类型与剪贴板条目名称的映射关系
  • 每个数据类型的剪贴板条目

在 macOS 上,该映射关系会被写入 org.w3.web-custom-format.map,其内容如下:

{
  "application/json": "org.w3.web-custom-format.type-0",
  "application/octet-stream": "org.w3.web-custom-format.type-1"
}

org.w3.web-custom-format.type-[index] 键对应于操作系统剪贴板中包含原始数据的条目。这使得原生应用程序可以查看映射以确定给定表示是否可用,然后从对应的剪贴板条目中读取原始内容。

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

这避免了与原始剪贴板访问相关的安全问题,因为网络应用程序无法将未经处理的数据写入任意操作系统剪贴板格式。这带来了一个明确列在异步剪贴板 API 规范中的序列化方案中明确列出:

非目标

允许与旧版原生应用程序实现互操作性,无需更新。这一方案曾在原始剪贴板提案中探讨过,未来可能进一步研究,但会带来显著的安全挑战(系统原生应用程序中的远程代码执行)。

这意味着,当使用自定义数据类型时,原生应用程序需要更新才能与网页应用程序实现剪贴板互操作性。

自2022年起,基于Chromium的浏览器已支持Web自定义格式,但其他浏览器尚未实现该提案。

结语

目前尚无一种通用的方法可在所有浏览器中实现自定义数据类型的剪贴板写入。Figma通过将Base64字符串转换为HTML表示形式的方案虽粗糙但有效,因其绕过了剪贴板API的诸多限制。若需通过剪贴板传输自定义数据类型,这似乎是一个值得采纳的方案。

我认为 Web Custom Formats 提案颇具潜力,希望它能被所有主流浏览器采纳。它似乎能以安全且实用的方式实现将自定义数据类型写入剪贴板的功能。

感谢阅读!希望这篇文章对您有所启发。

— Alex Harri

附录:unsanitized 选项

通过异步剪贴板 API 从剪贴板读取数据时,浏览器可能会对数据进行清理。例如,浏览器可能会移除 HTML 中潜在危险的脚本标签,并重新编码 PNG 图片以防止 zip 炸弹 攻击。

因此,异步剪贴板 API 的 read 方法包含一个 unsanitized 选项,允许您请求未经清理的数据。您可以在 Thomas Steiner 的这篇博文中了解更多关于此选项及其工作原理的信息。

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

感谢 Tom 就提及 unsanitized 选项与我们联系!该选项与本文的讨论范围非常契合。

共有 77 条讨论

  1. 这篇说明很清楚。这解释了为什么我在Mac上的Firefox浏览器中使用Google Docs时,粘贴带格式或不带格式的内容会遇到问题。有趣的是,Google正在使用一个已废弃的API来为剪贴板添加自己的内容类型格式。

    1. 迟早有一天,Google的网络应该被宣布为网络的官方分支。

      1. 如果某个大佬将一个API标记为“过时”,但没有提供可行的替代方案,而当用户代理不再允许用户调用这些有用的功能时,用户体验会受到影响,那么允许用户做他们想做的用户代理并不是问题。

        1. 挑剔:

          “贬值”意味着价值下降。

          “过时”意味着被淘汰。

          我经常在工作中听到这些说法。这让我抓狂。

          1. 加入我,我们可以组成一支军队。我们需要解决这个问题,以及“学习”。我认为这可能演变成“巴特勒人圣战”,但没有香料。

            1. 你要香料?

              无论如何,我不在乎。

                1. 字面意思、比喻意思、虚拟意思和实际意思。这些词很好,但经常被误用。

                  我也喜欢“非常独特”

                  1. “字面意思”被滥用得如此严重,以至于他们正式给它下了一个非正式的定义,意思是“比喻意思”。

                    这意味着“字面意思”现在成了自己的反义词。真是疯狂

            2. > 我们需要解决这个问题,以及“learnings”。

              什么是“learnings”?

              1. 这是14世纪中世纪英语中曾流行的一种复数形式,后来消失,直到在北美中部商业英语中重新出现。

                2016年《语法主义者》一针见血地指出:

                “`
                “learnings是‘learning’作为单数名词的错误形式的复数化。该单数名词(如‘a learning’)并不存在,至少根据大多数词典的定义。在口语中,尤其在医学领域,learnings指的是新发现或新掌握的具体内容。”
                “`

                [https://www.quora.com/What-is-the-plural-form-of-learning-Is…](https://www.quora.com/What-is-the-plural-form-of-learning-Is-it-learnings)

                他们后来软化了这一立场,现在讨论的是它被错误使用的情况。

                [https://grammarist.com/usage/learnings/](https://grammarist.com/usage/learnings/)

              2. 这个词被发明/重新使用,不知何故,用来取代“lessons”一词。

            3. 加上“asks”。还有“compliment”与“complement”、“affect”与“effect”等。

        2. 我也怀念Flash播放器和Unity播放器插件。还记得ActiveX有多强大吗?

          1. 强大工具的主要问题在于,它们会被威胁行为者用来对付你。

            我们的社会基于最自私的群体设定限制。我们的计算机则基于最有效的威胁设定限制。

            1. 剪贴板API的唯一“问题”是,糟糕的应用程序会盲目信任客户端数据。

          2. 你提到的三件事唯一的问题是它们都是专有技术。谷歌并没有用 W3C 标准来挟持网络。

      2. 考虑到 Chrome 的影响力,这可能会让非分支网络变得过时

      3. 一个只有一个虚拟机的语言有什么问题?我们真的需要 5 个虚拟机吗?

  2. 我的结论是,通过剪贴板发送自定义应用数据最可靠的方式是使用嵌入在HTML中的数据,类似于Figma的做法。额外好处是,该方法允许你通过HTML消息定义接收应用不支持时的失败行为

  3. WordPress在这方面有些问题。在编辑模式下跨多个段落复制粘贴可能会出大问题(将WordPress中的文字复制到另一个网页系统以实现文本依赖关系的交叉引用)——可能是因为这些段落实际上是不同DIV逻辑下的不同区域,且受不同控制效果影响?

    另一个让我感到困扰的问题是,系统会将我认为是 ASCII 的内容转换为 UTF-8 或 iso-latin1,随后触发类似 Clippy 的“我已将这些引号调整得更美观,以后再感谢我”行为,而这显然不是我想要的。如果我想要 `this’,我本不会输入 ‘this’

  4. 多年前,当我还是学生时,JavaScript可以未经用户同意获取用户的剪贴板内容,我制作了一个名为‘getpasted’的网站,该网站会自动将用户的剪贴板内容粘贴并发布到一个公共数据库中。

    不用说,有些人对此并不满意。但这是一个很好的项目,旨在提高人们对随时可以读取剪贴板内容的认识。

    1. 等到人们发现,在 Windows 11 中,默认情况下所有剪贴板数据都会通过剪贴板历史记录/云剪贴板功能发送至微软服务器时,情况会如何。

      1. 剪贴板历史记录和跨设备同步功能默认均处于关闭状态,据我所知。

        1. 嗯。我的工作电脑上这些功能是开启的,但可能是通过策略设置的。我稍后会检查我的Aya Neo(仅个人使用的Win11设备)。

      2. 苹果的版本是否更好?剪贴板会在登录iCloud的手机/电脑之间同步,尽管它不记录历史记录(至少从表面上看是这样)

          1. 由于其采用端到端加密,即使涉及苹果服务器,也不会泄露内容。

      3. 即使未登录微软账户时也是如此吗?

  5. 在范围(包括不同系统和浏览器之间的差异)和深度(包括常见问题和解决方法)方面,这篇文章的结合非常出色。

    1. 谢谢!我一直试图在写作中限制过多的细节和偏离主题的内容(我的文章通常比较长)。很高兴听到我在这方面找到了一个很好的平衡点。

      1. 这是迄今为止关于网页剪贴板接口的最佳文章。

        直到几周前我正在编写一些小工具来通过自动化网页控件加速某些任务时,我才了解到isTrusted属性。我无法让自动粘贴功能正常工作,于是发现了这个属性,并陷入了尝试在本地浏览器上绕过安全机制的困境(这并不容易!)。

  6. 关于从浏览器获取“私有”数据的问题。

    不知何故,我的银行网页应用在登录时能通过我的主机名(aluminium)触发双因素认证提示。它是如何获取这个信息的?而且在移动设备上使用该网站时,它还能识别双因素认证代码的文本并自动粘贴!哇/怎么可能?Pixel+Chrome或Linux+Chrome。

    1. 至少2FA代码可能是Chrome的功能,Safari会将2FA短信代码作为自动完成建议显示,我猜想Android上的Chrome会自动填充。至于主机名,情况更复杂。你确定从未将该信息作为用户名等提供给银行吗?这是哪家银行?

      1. 储蓄机构。他们使用由第三方开发的应用/网页应用,覆盖了大量小型银行。

        1. 具体在哪里显示你的主机名?你确定从未将其用作账户名或其他类似用途吗?我无法想象他们能通过 JavaScript 访问主机名。

          1. 是的,这是我第一次从这台机器访问他们的网站。而且手机上弹出了提示:“允许铝?”

            总有一天我要在调试器激活的情况下登录。

            1. 一种可能的解释:如果手机应用是原生应用,它可能监听HTTP端口并向网页服务器发送手机的网络信息,随后网页应用会指示手机应用进行ping操作,而手机则通过反向mDNS获取主机名。

              我不确定他们为何要费力让网页应用直接与手机应用通信,但这种可能性是存在的。

              1. 我在笔记本电脑上登录时,手机上会收到包含笔记本电脑主机名的通知。

                两台设备上均未安装任何银行应用。

                这让人困惑。

                1. 奇怪。是什么类型的通知?短信、邮件还是网页推送通知?

            2. 在未修改的浏览器中,确实无法通过JavaScript获取主机名。

  7. 这篇文章完美地说明了为什么网页应用永远无法与原生应用媲美。网页应用始终被视为“不受信任”的代码,因此会被任意且人为地限制访问本地机器上的资源。

    1. 默认不信任是设计特性。普通用户无法保护自己,除非他们是时刻保持警惕的高级安全专家,而这种情况并不存在。

      这已不再是互联网早期那段充满脚本小子肆意妄为的激动人心的日子,如今主要服务于极客群体。现在是政府关联团伙对关键基础设施发起勒索软件攻击以资助核计划的时代。访问本地机器上的任意资源正是这种攻击的实现方式。

      基于现代浏览器的网页应用自然具有更严格的沙箱隔离机制,但原生应用在任何现代操作系统中也被视为不可信。如果我启动任何新应用,系统会提示我确认是否允许其访问除自身隔离的应用数据目录之外的任何内容。

      1. 我在五个操作系统上运行的所有原生应用程序都不需要弹出提示来访问剪贴板,也不存在无法将除文本、HTML和PNG以外的任何数据复制到剪贴板的情况。

        1. 苹果的每款移动操作系统都会在应用程序尝试在未明确粘贴的情况下访问剪贴板时显示弹出提示。显然,如果你点击了“粘贴”,那么弹出提示就变得多余,因为你是在批准自己的操作,而不是应用程序的操作。

          macOS 目前还没有这样做,这有点疯狂。

          但我回复的评论是在泛泛而谈。是的,对于某些 API,原生应用程序目前比网络应用程序更值得信任,这取决于操作系统,但趋势是它们正变得越来越不值得信任。

          1. 如果你指的是移动操作系统,我同意它们随着时间推移变得更糟。但对于真正的计算机操作系统,情况并非如此。

            1. 我指的是所有消费级操作系统,它们正在变得更好,除非你坚持使用旧版且存在漏洞的版本。

              1. 再次强调,没有非移动操作系统会阻止原生应用程序以应有的方式使用剪贴板。而且它们都没有显示出任何改变的迹象。

                1. 你是说原生应用程序的沙箱隔离程度不如网页应用程序吗?因为这就是我所说的。但它们正在朝着这个方向发展,这是好事。

                  1. 不,原生应用程序永远不会达到文章中提到的访问剪贴板时所示的荒谬沙箱化程度。它们也不会像网页应用程序那样被阻止访问文件系统。

                    1. 在所有良好的系统中,它们都会出于非常正当的理由实现这一点,而且它们已经走了一半的路程。

  8. 顺便问一下,你在MacBook上被提示的Chrome扩展是什么?

  9. 这太棒了。谢谢!

    对这些细节的深入探讨非常精彩。

  10. 非常启发人且富有洞见的深度探讨。谢谢。

  11. 遗憾的是,目前不支持有损压缩的图像。人们经常复制粘贴图像而无意修改它们,每次使用JPEG源时,压缩信息会丢失但 artifacts 会保留,而多次JPEG压缩会添加更多 artifacts,这种情况在大部分目标网页应用中会立即发生。[https://xkcd.com/1683/](https://xkcd.com/1683/)

    1. 等等,这就是为什么我在Safari中右键点击>复制图片,然后粘贴到消息中时,它会被转换为PNG格式吗?

  12. 富文本内容绝不应作为默认粘贴选项。直接给我复制的纯文本就行

    1. 软件(操作系统、浏览器、原生应用、网页应用等)应让用户更轻松地使用纯文本进行剪贴板的读写操作。并且可能应让用户更倾向于这样做。

      我知道这样说虽然用了更多字,但避免了你表述方式中的两个问题:

      1. 并非所有人都与你有相同偏好。(我猜这可能是少数人的偏好,尽管我大多认同。)

      2. 这种说法自相矛盾。“我复制的该死文本”不仅仅是一串字符:它通常包含格式设置;它经常嵌入其他根本不是文本的内容。

      后者基本上是前者的基础。你——以及我,大部分时候!——可能想要的只是那串字符。但许多人想要的是更丰富的内容,如果他们复制了丰富的内容却只得到那串字符,他们会感到非常困惑。而且很多人几乎没有办法得到他们想要的。

      这就是为什么多部分剪贴板解决方案是一个相当不错的折中方案。它可以更好!它肯定可以满足对纯文本的偏好。但它只能对那些有此偏好的人更好,而不会损害更常见的偏好,因为它将常见的偏好作为默认设置。

      1. 问题的一部分在于富文本复制粘贴的实现不够完善(或不够智能)。如果我复制的文本中有部分加粗或斜体,那确实是“我复制的文本”。但源网站恰好使用12pt蓝色Verdana字体的事实,不应覆盖我正在使用11pt黑色Arial字体撰写邮件的事实。

        在许多情况下,我并不特别在意邮件是使用12pt蓝色Verdana还是11pt黑色Arial字体,但我绝对在意突然在原本一致的段落中出现一个大大的蓝色单词。

        因此,无论是“富文本”还是“纯文本”都并非完全正确:通常纯文本已足够,但有时修正富文本会更方便。

        1. 这可能是一个难以通用解决的问题。Word 在粘贴时会弹出一个小窗口,让你选择希望如何应用该粘贴内容:仅文本、保留原始格式的文本,或调整为文档样式的格式化文本。到目前为止我已经用过这三种方式,所以我想没有一种方案能适用于所有使用场景。

          1. 是的,我正要说同样的事情。Word(令人惊讶地)做对了这一点。

    2. 值得一提的是,即使有富格式可用,Ctrl/Cmd + Shift + V 也会粘贴原始剪贴板内容。

      1. 我通过纯文本编辑器进行粘贴(即先粘贴到纯文本编辑器中,验证并复制所需内容,再粘贴到目标位置)。我知道这并非理想方案,但至少这样能在粘贴到重要位置前查看并验证所有内容。我相信有人会利用富格式进行一些技巧操作。

        1. 但你是否需要按下Ctrl/Cmd + C三次或更多次以确保生效?

        2. 我会在新浏览器标签页的地址栏中执行此操作,因为快捷键输入起来相当麻烦。

          1. 如果你启用了预测输入功能,你的剪贴板数据会被发送到谷歌

    3. 有时我同意,但最近我试图将一个Quip中的所有内容复制到另一个Quip中,系统完全无法支持粘贴我复制的相同样式。不,我不想让我的标题、链接、代码和列表……都以纯ASCII格式返回。这让我抓狂。

    4. 我对富文本粘贴的问题是,它会传输不需要的呈现格式,导致字体、大小和排版不匹配。我通常希望是纯文本粘贴,或者保留语义元素(如加粗/斜体强调、链接和项目符号)。

      更普遍地说,在编辑文档时,我希望能够同时查看格式化文本和“转义”内容(如“查看源代码”),而在复制粘贴或保存文本时,我希望能够将格式化文本转换为“转义”文本以进行差异比较,同时粘贴文本时不会将星号和尖括号误认为格式化信息 (除非我明确将文本以源代码形式复制并以格式化形式粘贴)。

      1. 我将上面的评论复制到这里,因为你可能会发现它有用:

        我使用Obsidian进行笔记记录,并发现了一个非常有用的功能:如果你粘贴格式化文本,它会删除除Markdown可处理的内容之外的所有内容(大致对应HTML标签,而非样式/CSS)。然后你可以将其复制为HTML并粘贴,它将具有完全正确的格式。

    5. 我使用Obsidian进行笔记记录,发现了一个非常有用的功能:如果你粘贴格式化的文本,它会去除所有无法在Markdown中处理的内容(大致对应HTML标签,而非样式/CSS)。你可以将它复制为HTML格式并粘贴,这样格式就会完全正确。

  13. 题外话:我经常发现复制粘贴功能无法正常工作。我明确点击了复制,但粘贴时没有任何反应,或者粘贴出之前的复制内容。不确定为什么会这样,我在Mac和Ubuntu系统上都遇到过。仿佛经过数十年的发展,我们仍然无法可靠地实现这样一个简单的操作。这种情况在浏览器和其他应用程序中都会发生。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

你也许感兴趣的: