1. 引言
本节是非规范性的。
本规范定义了系统剪贴板如何向 Web 应用程序公开。
本规范描述了两种通用 API:
-
剪贴板事件 API - 此 API 提供了一种挂钩到常见剪贴板操作(剪切、复制和粘贴)的方法,以便 Web 应用程序可以根据需要调整剪贴板数据。
-
异步剪贴板 API - 此 API 提供对读取和写入剪贴板数据的直接访问。由于这被认为是一个强大的功能, 因此对此 API 的访问受权限控制。
2. 用例
本节是非规范性的。
2.1. 更改默认剪贴板操作
在许多情况下,需要更改默认的剪贴板操作(剪切/复制/粘贴)。以下是一些示例:
- 元数据 从文档存储库复制文本时,让复制的文本包含有关所复制内容来源的元数据会很有用。
- 富内容编辑 复制包含超链接或其他结构的文本时,能够重新格式化内容以保留重要信息通常很有用。
- 具有内置语义的图形 为了使 Web 应用程序能够处理富文本或图形内容(例如 [SVG11]),提供一种允许复制不仅仅是渲染内容的机制会很有用。
- 数学信息 对于数学等内容,简单地复制渲染的文本并将其粘贴到另一个应用程序中通常会导致大部分语义丢失。MathML 通常需要转换为纯文本才能复制,例如,为了确保在公式纯文本输入中用脱字符“^”显示“的幂”。XML 源代码也可以放在剪贴板中,并在粘贴时进行适当的转换。
2.2. 远程剪贴板同步
对于与远程设备通信的 Web 应用程序(例如,远程访问或远程 shell 应用程序),通常需要在两个设备之间保持剪贴板数据同步。
此用例的一个重要方面是,它需要在没有用户手势或交互的情况下访问剪贴板。
write()
到本地剪贴板。 2.3. 触发剪贴板操作
为用户代理提供备用界面的应用程序有时需要能够触发用户代理中的剪贴板操作。
例如,考虑一个屏幕阅读器应用程序,它为标准 Web 浏览器提供更易于访问的界面。虽然阅读器可以显示内容并允许用户与之交互,但像剪贴板复制这样的操作需要在底层浏览器中进行,以确保正确设置剪贴板内容(以及浏览器在复制过程中添加的任何元数据)。
3. 术语
术语 可编辑上下文 指任何作为 编辑宿主、textarea 元素或其 type 属性设置为 "text"、"search"、"tel"、"url"、"email"、"password" 或 "number" 之一的 input 元素的元素。
4. 模型
平台提供一个 系统剪贴板。
系统剪贴板有一个系统剪贴板项目列表,这些项目统称为 系统剪贴板数据。
每个系统剪贴板项目都有一个系统剪贴板表示列表。
每个系统剪贴板表示都有一个名称(一个字符串)和数据(一个字节序列)。
系统剪贴板具有 一个 剪贴板变更 计数,它是一个每当系统剪贴板数据被修改时就会变化的值。
5. 剪贴板事件
5.1. 剪贴板事件接口
ClipboardEvent 接口扩展了 Event
接口。
dictionary :ClipboardEventInit EventInit {DataTransfer ?=clipboardData null ; };
- clipboardData
-
一个
DataTransfer对象,用于保存与事件相关的数据和元数据。
[Exposed =Window ]interface :ClipboardEvent Event {(constructor DOMString ,type optional ClipboardEventInit = {});eventInitDict readonly attribute DataTransfer ?; };clipboardData
- clipboardData
-
clipboardData 属性是
DataTransfer接口的一个实例,它允许脚本在用户发起的复制、剪切和粘贴操作期间读取和操作系统剪贴板上的值。关联的拖放数据存储是系统剪贴板的实时但经过筛选的视图,公开了实现知道脚本可以安全访问的强制数据类型。对于合成事件,拖放数据存储包含创建事件的脚本添加的数据。
该接口可用于构造事件。 下面给出一个示例:
var pasteEvent = new ClipboardEvent('paste');
pasteEvent.clipboardData.items.add('My string', 'text/plain');
document.dispatchEvent(pasteEvent);
注意: 合成的剪贴板事件实际上不会修改剪贴板或文档。换句话说,虽然上面的脚本会触发粘贴事件,但数据不会被粘贴到文档中。
5.2. 剪贴板事件
5.2.1.
clipboardchange 事件
当系统剪贴板的内容发生更改时,会触发 clipboardchange
事件。这些更改可能由以下任何原因(非详尽列表)引起:
-
用户发起的剪切或复制操作
-
使用 § 7 异步剪贴板 API 写入剪贴板的脚本
-
在用户代理外部更新剪贴板的操作
5.2.1.1. 触发 clipboardchange 事件
要在给定Document
document时触发 clipboardchange 事件:
-
如果 document 没有粘性激活,并且 document 没有 从剪贴板读取的权限,则返回。
-
令 global 为 document 的相关全局对象。
-
如果 document 具有系统焦点:
-
令 changeId 为在给定 document 的情况下运行生成 changeId 的结果。
-
令 eventInit 为一个新的
ClipboardChangeEventInit字典,其types成员设置为 types,并且其changeId成员设置为 changeId。 -
在 global 上触发一个事件,其名称为
clipboardchange, 使用ClipboardChangeEvent, 并带有 eventInit。
-
如果 document 没有系统焦点:
-
将 document 的 clipboardchange 待处理标志设置为 true。
-
用户代理可以选择跳过对在事件发送前已被更新为更新版本的剪贴板内容的clipboardchange事件的触发。这种优化可以提升在剪贴板更改频繁发生的场景下的性能,因为传递过时的更改通知对网页应用没有价值,反而会占用处理资源。
5.2.1.2. 文档聚焦步骤
-
如果 document 的clipboardchange 待处理标志为 true:
-
将 document 的clipboardchange 待处理标志 设置为 false。
-
令 changeId 为在给定 document 的情况下运行生成 changeId的结果。
-
令 eventInit 为一个新的
ClipboardChangeEventInit字典,其types成员设置为 types,并且其changeId成员设置为 changeId。 -
令 global 为 document 的相关全局对象。
-
触发一个事件,其名称为
clipboardchange, 目标为 global,使用ClipboardChangeEvent, 并带有 eventInit。
-
对于嵌套浏览上下文中的文档,clipboardchange事件将在每个Document中根据其自身的焦点状态独立触发。一次剪贴板更改将在具有系统焦点的单一文档中触发(如果该文档有粘性激活或持久剪贴板权限)。
注意:只有在粘性激活后,clipboardchange事件才可用,除非文档拥有从剪贴板读取的持久权限。在支持持久剪贴板权限的用户代理中,拥有该权限的站点可在无粘性激活的情况下收到clipboardchange事件,因为该权限已经授予访问更敏感剪贴板数据的权限。
changeId为每次剪贴板更改操作提供一个唯一标识符。对于相同剪贴板内容更改,所有具有相同存储键的窗口和标签页会收到具有相同changeId的事件,使多窗口应用可以去重事件避免重复处理。该标识符是存储键相关的,并不具备跨存储键的关联能力。
clipboardchange事件不会冒泡也不可取消,因为它不是由用户操作而是由于系统剪贴板状态的更改触发的。
dictionary :ClipboardChangeEventInit EventInit {sequence <DOMString >= [];types bigint = 0; };changeId
[Exposed =Window ]interface :ClipboardChangeEvent Event {(constructor DOMString ,type optional ClipboardChangeEventInit = {});eventInitDict readonly attribute FrozenArray <DOMString >;types readonly attribute bigint ; };changeId
5.2.1.4. ChangeId 生成
要为一个 Document
document 生成
changeId:
-
令 globalChangeId 为一个用户代理特定的唯一标识符,表示系统剪贴板的当前 状态。每当系统 剪贴板被修改时,此标识符都会改变,并且会在用户代理重启时重置。
-
令 storageKey 为在给定 document 的为非存储用途获取 存储键的相关设置对象的情况下运行结果。
-
令 storageKeyBytes 为 storageKey 的某种用户代理特定的二进制表示。
-
令 hashedValue 为将加密哈希函数(例如 SHA-256)应用于 globalChangeId(作为字节)和 storageKeyBytes 的拼接结果所得的结果。
-
返回一个从 hashedValue 派生出的 128 位整数(例如,通过取哈希输出的前 128 位)。
上述算法确保来自同一源且具有相同 分区的文档,会针对同一次剪贴板修改接收相同的变更 ID,从而能够在多个窗口和标签页之间正确地对事件 去重,同时防止可用于跟踪的跨分区关联。随着客户端存储分区的变更被纳入存储键 定义,此方法将自动在各分区之间提供匿名化。
changeId
不会在浏览器重启后持久存在,因为 globalChangeId 计数器会在用户
代理重启时重置。类似地,当用户清除站点数据时,受影响的标签页应该被刷新,这会导致
事件监听器被重新附加,并且只接收带有新变更 ID 的未来事件。
由于合成的 cut
和 copy
事件不会更新系统剪贴板,
它们不会触发 "clipboardchange" 事件。
clipboardchange
事件使 Web 应用能够高效地监控
剪贴板变化,并根据可用的数据格式提供动态用户界面:
// 对于具有多个窗口的应用,跟踪已处理的变更 ID 以避免重复
const processedChangeIds = new Set();
// 监听剪贴板变化
navigator.clipboard.addEventListener('clipboardchange', (e) => {
// 使用 changeId 对多个窗口之间的事件去重
if (processedChangeIds.has(e.changeId)) {
return; // 此变更已经被处理过
}
processedChangeIds.add(e.changeId);
// 检查剪贴板上有哪些可用的数据类型
const hasText = e.types.includes('text/plain');
const hasHTML = e.types.includes('text/html');
const hasImage = e.types.includes('image/png');
// 根据可用格式更新 UI
document.getElementById('paste-text-btn').disabled = !hasText;
document.getElementById('paste-html-btn').disabled = !hasHTML;
document.getElementById('paste-image-btn').disabled = !hasImage;
// 对于远程桌面应用,每次变更只将剪贴板同步到远程一次
if (hasText || hasHTML || hasImage) {
syncClipboardToRemote(e.changeId);
}
});
// 或者,也可以使用 onclipboardchange 属性
// navigator.clipboard.onclipboardchange = (e) => { ... };
这种事件驱动方法比使用
read()
或 readText()
等方法轮询剪贴板更高效,
并且可在要求用户激活才能访问剪贴板的浏览器中工作,因为 UI 可以立即响应剪贴板变化,而无需等待计时器
触发。
changeId
对于具有多个窗口的应用尤其有用,
可确保在具有相同存储键的所有窗口中,每次剪贴板变更只被处理一次。
5.2.2. copy
事件
当用户发起复制操作时,用户代理
触发一个剪贴板
事件,其名称为
copy。
如果该事件未被取消,则当前选中的 数据将被复制到系统剪贴板。 当前文档选择不会受到影响。
copy
事件会冒泡、可取消,并且是可组合的。
有关此事件处理模型的详细描述,参见 § 8.1 复制操作。
可以手动构造并分派一个合成的 copy
事件,但它
不会影响系统剪贴板的内容。
5.2.3. cut
事件
当用户发起剪切操作时,用户代理
触发一个
剪贴板事件,其名称为
cut。
在可编辑上下文中,如果
该事件未被
取消,该操作会将当前选中的数据放到
系统剪贴板上,并
从文档中移除该选择。
cut
事件会在选中的数据被移除之前触发。当
剪切操作完成时,选择会被折叠。
在非可编辑
上下文中,clipboardData
将
是一个空列表。注意,在这种情况下,cut
事件仍然会被触发。
cut
事件会冒泡、可取消,并且是可组合的。
有关此事件处理模型的详细描述,参见 § 8.2 剪切操作。
可以手动构造并分派一个合成的 cut
事件,但它
不会影响文档或系统剪贴板的内容。
5.2.4.
paste
事件
当用户发起粘贴操作时,用户代理会触发一个名为 paste
的剪贴板事件。
在将任何剪贴板数据插入文档之前会触发该事件。
如果光标位于可编辑上下文中,则粘贴操作将以给定上下文支持的最合适格式(如果有)插入剪贴板数据。
粘贴操作在非可编辑上下文中无效,但无论如何都会触发
paste
事件。
paste
事件会冒泡,可以取消,并且是组合事件。
有关此事件处理模型的详细说明,请参阅 § 8.3 粘贴操作。
可以手动构造和分派合成的 paste
事件,但它不会影响文档内容。
5.3. 与其他脚本和事件的集成
5.3.1. 允许修改剪贴板的事件处理程序
如果满足以下任一条件,事件处理程序可以写入剪贴板:
-
触发事件的操作是从用户代理自身的用户界面调用的,例如从“复制”菜单项或快捷键调用的。
-
触发事件的操作是从允许显示弹出窗口的脚本线程调用的。
如果实现作者认为其他受信任的事件类型可能表达用户意图,则实现可以允许这些事件类型修改剪贴板。实现还可以支持信任特定站点或应用程序修改剪贴板的配置,而不管脚本线程的来源如何。
合成的 cut
和 copy
事件不得修改系统剪贴板上的数据。
5.3.2. 允许从剪贴板读取的事件处理程序
如果满足以下任一条件,事件处理程序可以从系统剪贴板读取数据:
-
触发事件的操作是从用户代理自身的用户界面调用的,例如“粘贴”菜单项或快捷键。
-
触发操作的脚本正在通过依赖于实现的机制被授予从剪贴板读取数据的权限的站点上运行。
-
触发事件的操作是在具有读取剪贴板权限的应用程序中触发的。
合成的 paste
事件不得授予脚本访问真实系统剪贴板上数据的权限。
5.3.3. 与富文本编辑 API 的集成
如果实现支持通过脚本执行剪贴板命令的方法,例如通过使用命令 "cut"、"copy" 和 "paste" 调用 document.execCommand()
方法,则实现必须触发相应的操作,这将再次分派关联的剪贴板事件。
以下是通过脚本 API 触发复制、剪切或粘贴操作时要遵循的步骤:
-
同步执行相应的操作。
-
使用操作的返回值作为 API 调用的返回值。
注意: 通过脚本 API 触发的复制和剪切命令仅在事件是由受信任且由用户触发的事件分派,或者实现配置为允许此操作时,才会影响真实剪贴板的内容。通过脚本 API 触发的粘贴命令仅在实现配置为允许此操作时才会触发粘贴事件并授予对剪贴板内容的访问权限。如何配置实现以允许对剪贴板的读取或写入访问超出了本规范的范围。
5.3.4. 与其他事件的交互
如果剪贴板操作是由键盘输入触发的,则实现必须触发启动剪贴板操作的相应事件。该事件是异步的,但必须在相关键的 keyup 事件之前分派。
剪切和粘贴操作可能导致实现分派其他支持的事件,例如 textInput、input、change、验证事件、DOMCharacterDataModified 和 DOMNodeRemoved / DOMNodeInserted。任何此类事件都将排队,以便在剪切/粘贴事件处理完成后触发。
实现不得响应复制操作而分派其他与输入相关的事件,例如 textInput、input、change 和验证事件。
5.3.5. 修改选择或焦点的事件侦听器
如果事件侦听器修改选择或可聚焦区域,则剪贴板操作必须在修改后的选择上完成。
6. 剪贴板事件 API
剪贴板事件 API 允许您覆盖用户代理的默认剪切、复制和粘贴行为。
对剪贴板的访问是使用标准的 DataTransfer
方法来改变 ClipboardEvent
的
clipboardData
属性上的 items
来执行的。
这样做的结果之一是,这些剪贴板 API 只能在 ClipboardEvent
处理程序的上下文中访问剪贴板数据。
注意: 如果您需要在剪贴板事件处理程序之外访问剪贴板,请参阅 § 7 异步剪贴板 API。
注意: 剪贴板事件 API 是同步的,因此它们的功能有限。这些 API 不支持可能阻塞的操作(例如请求权限或转码图像)。有关可以支持阻塞或其他耗时操作的更强大 API,请参阅 § 7 异步剪贴板 API。
6.1. 覆盖复制事件
要覆盖默认的 copy
事件行为,必须添加一个 copy
事件处理程序,并且此事件处理程序必须调用 preventDefault()
来取消事件。
为了使用 clipboardData
中的数据更新系统剪贴板,需要取消事件。
如果未取消 ClipboardEvent,则将复制当前文档选择中的数据。
// 覆盖复制到剪贴板的内容。
document.addEventListener('copy', function(e) {
// e.clipboardData 最初为空,但我们可以将其设置为
// 我们希望复制到剪贴板的数据。
e.clipboardData.setData('text/plain', 'Hello, world!');
e.clipboardData.setData('text/html', '<b>Hello, world!</b>');
// 这是防止将当前文档选择写入剪贴板所必需的。
e.preventDefault();
});
6.2. 覆盖剪切事件
要覆盖默认的 cut
事件行为,必须添加一个 cut
事件处理程序,并且此事件处理程序必须调用 preventDefault()
来取消事件。
为了使用 clipboardData
中的数据更新系统剪贴板,需要取消事件。
如果未取消 ClipboardEvent,则将复制当前文档选择中的数据。
请注意,取消 cut
事件还将阻止更新文档(即,不会删除当前选择)。事件处理程序将需要手动更新文档以删除当前选定的文本。
// 覆盖复制到剪贴板的内容。
document.addEventListener('cut', function(e) {
// e.clipboardData 最初为空,但我们可以将其设置为
// 我们希望作为剪切操作的一部分复制到剪贴板的数据。
// 写入我们希望复制到剪贴板的数据。
e.clipboardData.setData('text/plain', 'Hello, world!');
e.clipboardData.setData('text/html', '<b>Hello, world!</b>');
// 由于我们将取消剪切操作,因此需要手动
// 更新文档以删除当前选定的文本。
deleteCurrentDocumentSelection();
// 这是防止将文档选择写入剪贴板所必需的。
e.preventDefault();
});
6.3. 覆盖粘贴事件
要覆盖默认的 paste
事件行为,必须添加一个 paste
事件处理程序,并且此事件处理程序必须调用 preventDefault()
来取消事件。
为了使用户代理不使用系统剪贴板中的数据更新文档,需要取消事件。
请注意,取消 paste
事件还将阻止更新文档(即,不会将任何内容粘贴到文档中)。事件处理程序将需要手动将数据粘贴到文档中。
另请注意,粘贴时,拖放数据存储模式标志为只读,因此从 paste
事件处理程序调用 setData()
不会修改插入的数据,也不会修改剪贴板上的数据。
// 覆盖粘贴到剪贴板的内容。
document.addEventListener('paste', function(e) {
// e.clipboardData 包含即将粘贴的数据。
if (e.clipboardData.types.indexOf('text/html') > -1) {
var oldData = e.clipboardData.getData('text/html');
var newData = '<b>Ha Ha!</b> ' + oldData;
// 由于我们正在取消粘贴操作,因此需要手动
// 将数据粘贴到文档中。
pasteClipboardData(newData);
// 这是防止默认粘贴操作所必需的。
e.preventDefault();
}
});
6.4. 强制数据类型
实现必须识别以下数据类型的本机操作系统剪贴板格式描述,以便能够使用正确的描述填充 DataTransferItemList
和 ClipboardItem
以用于粘贴事件,并在响应复制和剪切事件时在操作系统剪贴板上设置正确的数据格式。
6.4.1. 从剪贴板读取
如果剪贴板上存在相应的本机类型,则粘贴事件必须公开这些数据类型:
-
text/plain
-
text/html
-
image/png
6.4.2. 写入剪贴板
如果在复制和剪切事件期间将这些数据类型添加到 DataTransfer
对象,则必须使用相应的本机类型描述将它们放置在剪贴板上。
-
text/plain
-
text/html
-
image/png
警告!出于安全预防措施,限制了不受信任的脚本允许写入剪贴板的数据类型。不受信任的脚本可能会通过将已知会触发本地软件中安全漏洞的数据放置在剪贴板上,从而尝试利用这些漏洞。
6.5. 可选数据类型
实现可以识别以下数据类型的本机操作系统剪贴板格式描述,以便能够使用正确的描述填充 ClipboardItem
以用于粘贴事件,并在响应复制和剪切事件时在操作系统剪贴板上设置正确的数据格式。
如果剪贴板上存在相应的本机类型,用户代理 (UA) 可以公开这些数据类型:
-
text/uri-list
-
image/svg+xml
-
自定义格式,其开头为 `"web "`(“web”后跟 U+0020 空格)前缀,并且后缀(去除 `"web "` 后)通过解析 MIME 类型检查。
6.6. 未清理的数据类型
本节是非规范性的。用户代理 (UA) 不得清理这些数据类型:
-
image/png
用户代理 (UA) 可以不清理这些数据类型:
可选的未清理数据类型是 Web 作者指定的MIME 类型,用户代理可以不清理这些类型。 下面列出了有效的可选的未清理数据类型:
-
text/html
由于其隐私要求,用户代理可能不支持可选的未清理数据类型。
7. 异步剪贴板 API
7.1. Navigator 接口
partial interface Navigator { [SecureContext ,SameObject ]readonly attribute Clipboard ; };clipboard
7.2. ClipboardItem 接口
typedef Promise <(DOMString or Blob )>; [ClipboardItemData SecureContext ,Exposed =Window ]interface {ClipboardItem constructor (record <DOMString ,ClipboardItemData >,items optional ClipboardItemOptions = {});options readonly attribute PresentationStyle presentationStyle ;readonly attribute FrozenArray <DOMString >types ;Promise <Blob >getType (DOMString );type static boolean supports (DOMString ); };type enum {PresentationStyle ,"unspecified" ,"inline" };"attachment" dictionary {ClipboardItemOptions PresentationStyle = "unspecified"; };presentationStyle
clipboardItem = new ClipboardItem([items, options])-
创建一个新的
ClipboardItem对象。items 表示表示形式列表,每个表示形式都有一个MIME 类型和一个Promise,该 Promise 解析为与MIME 类型对应的Blob或DOMString, options 可用于填充其ClipboardItemOptions, 如下例所示。const format1= 'text/plain' ; const promise_text_blob= Promise. resolve( new Blob([ 'hello' ], { type: format1})); const clipboardItemInput= new ClipboardItem( {[ format1] : promise_text_blob}, { presentationStyle: "unspecified" }); clipboardItem.getType(type)- 返回一个
Promise, 该 Promise 解析为与MIME 类型 type 对应的Blob。 clipboardItem.types- 返回剪贴板项目对象中包含的MIME 类型列表。
ClipboardItem.supports(type)- 如果 type 属于强制数据类型或可选数据类型,则返回 true,否则返回 false。
一个 剪贴板项在概念上是 用户通过调用“剪切”或“复制” 命令而表达出想要使其可共享的数据。一个 剪贴板项 有两个用途。第一,它允许网站读取用户复制到系统剪贴板的数据。第二,它允许 网站将数据写入系统剪贴板。
例如,如果用户从原生应用的电子表格中复制一系列单元格,这会产生 一个剪贴板项。如果 用户从其桌面复制一组文件,该文件列表将由多个剪贴板项表示。
某些平台可能支持在剪贴板上同时拥有多个剪贴板项,而其他平台 会用新的剪贴板 项替换前一个。
一个剪贴板项具有一个表示
列表,每个 表示都具有关联的 MIME
类型(一个 MIME 类型)、一个 isCustom 标志,初始为
false,用于指示此表示是否应被视为 Web 自定义格式(而不是
系统剪贴板的知名格式),以及 数据(一个
ClipboardItemData)。
Web 自定义格式 的 isCustom 被设置为 true。
在用户从电子表格中复制一系列单元格的示例中,它可以被表示为图像 (image/png)、HTML 表格 (text/html)、纯文本 (text/plain),或者 Web 自定义格式 (web text/csv)。
这些MIME 类型中的每一个,都描述同一个剪贴板项在不同 保真度级别上的不同表示,并使该剪贴板项在粘贴期间更易被目标应用消费。
将这一系列单元格作为图像提供,将允许用户把这些单元格粘贴到照片编辑 应用中,而 text/plain 格式可供文本编辑器应用使用。
一个剪贴板项具有一个呈现样式
(一个 PresentationStyle)。
它有助于区分应用在“粘贴”一个剪贴板项时,是应将适当表示的内容内联插入到
粘贴点,还是应将其视为附件。
仅支持粘贴单个剪贴板项的 Web 应用,应使用第一个剪贴板项。
支持粘贴多个剪贴板项的 Web 应用,例如可以提供一个用户界面, 预览每个剪贴板项的内容,并允许用户选择要粘贴哪一个。 此外,应用应枚举它们正在粘贴的剪贴板项的MIME 类型, 并根据某种应用特定算法选择最适合该应用的一种。 或者,应用可以向用户呈现如何粘贴剪贴板项的选项,例如“粘贴为图像”或“粘贴 格式化文本”等。
一个 ClipboardItem
对象具有关联的 剪贴板项,它是一个剪贴板项。
一个 ClipboardItem
对象具有关联的 types 数组,它是一个 FrozenArray<DOMString>。
一个 ClipboardItem
对象具有关联的 读取时的剪贴板变更计数,
它初始为 null。
一个 ClipboardItem
对象具有关联的 源系统剪贴板
项,它是一个系统剪贴板项或 null,初始为 null。
一个 ClipboardItem
对象具有关联的 未清理的 MIME 类型,它是一个sequence<DOMString>,
初始为空。
一个 ClipboardItem
对象具有关联的 带解析器的表示,
它是一个从字符串(传递给
getType()
的MIME
类型,
包括存在时的 `"web "` 前缀)到 Promise<Blob>
的映射,
初始为空。每个条目表示针对该类型的一次 OS 剪贴板读取,该读取已经完成或
当前正在进行。
注: 此映射确保对从 read()
获得的 ClipboardItem
上同一 MIME 类型的并发 getType()
调用共享一次 OS 剪贴板读取。第一次调用会把一个待处理的 Promise
插入到映射中并发起读取;随后对同一 MIME 类型的调用返回现有的 Promise,
而不会重新读取系统剪贴板。
当一个 ClipboardItem
由 read()
创建时,它充当惰性获取器,而不是数据快照。它的types 数组反映
read()
调用时系统剪贴板上可用的格式,
但实际载荷字节要到 getType()
被调用时才会读取。如果在 read()
调用和后续 getType()
调用之间,系统
剪贴板内容发生变化,则该 getType()
调用将被拒绝。直接通过 ClipboardItem(items, options)
构造函数构造的 ClipboardItem
不受此行为约束。
要在给定一个剪贴板项
clipboardItem 和一个 Realm realm 的情况下,创建一个
ClipboardItem
对象,运行以下步骤:
-
令 clipboardItemObject 为一个带有 realm 的新的
ClipboardItem。 -
将 clipboardItemObject 的剪贴板项设置为 clipboardItem。
new ClipboardItem(items, options)
构造函数步骤如下:
-
如果 items 为空,则抛出一个
TypeError。 -
如果 options 为空,则设置 options["presentationStyle"] = "unspecified"。
-
将 this 的 剪贴板项的呈现样式设置为 options["
presentationStyle"]。 -
令 types 为一个
DOMString列表。 -
对于 items 中的每个(key,value):
-
令 representation 为一个新的表示。
-
令 isCustom 为 false。
-
如果 key 以 `"web "` 前缀开始,则
-
移除 `"web "` 前缀,并将剩余字符串赋给 key。
-
将 isCustom 设置为 true
-
-
将 representation 的 isCustom 标志设置为 isCustom。
-
令 mimeType 为在给定 key 的情况下解析 MIME 类型的结果。
-
如果 mimeType 为 failure,则抛出一个
TypeError。 -
如果 this 的剪贴板项的表示 列表包含一个表示,其MIME 类型为 mimeType 且其 [representation/isCustom] 为 isCustom,则抛出一个
TypeError。
上述步骤防止用户代理已知的 mime-types 与作者意图作为 自定义类型处理的 mime-types 之间发生冲突。例如,作者的 items 列表可能同时包含 "text/html" 和 "web text/html" 的表示。
-
7.2.1. presentationStyle
presentationStyle
getter 的步骤是返回此对象的剪贴板项目的表示样式。
7.2.2. types
7.2.3. getType(type)
此方法必须运行以下步骤:
-
令 isCustom 为 false。
-
如果 type 以 `"web "` 前缀开始,则:
-
移除 `"web "` 前缀,并将剩余字符串赋给 type。
-
将 isCustom 设置为 true。
-
-
令 mimeType 为在给定 type 的情况下解析 MIME 类型所得的结果。
-
如果 mimeType 为 failure,则抛出一个
TypeError。 -
令 p 为 realm 中的一个新的 promise。
-
对于 itemTypeList 中的每个 representation:
-
如果 representation 的MIME type 是 mimeType,并且 representation 的isCustom 是 isCustom,则:
-
如果 this 的读取时的剪贴板变更 计数不是 null,并且当前剪贴板 变更计数不等于 this 的读取时的剪贴板变更 计数,则在 realm 中用一个
"InvalidStateError"DOMException拒绝 p,并返回 p。注: 这确保了永远不会返回过期 数据。如果系统剪贴板内容自
read()被调用以来已发生变化,则这必须失败,而不是返回与当前系统剪贴板状态不对应的数据。注: null 的读取时的剪贴板变更 计数表示此
ClipboardItem是由作者直接构造的(例如,通过ClipboardItem(items, options)), 而不是从read()获得的。 在这种情况下,不需要过期数据检查,因为该数据并非源自 系统剪贴板。 -
如果 this 的读取时的剪贴板变更 计数不是 null,则:
注: 此分支涵盖从
ClipboardItems 获得的read(), 对于这些项,OS 剪贴板读取会推迟到此时。为确保每种类型的读取 最多只发生一次——即使针对同一 MIME 类型并发多次调用getType()——生成的Promise会缓存在 this 的带解析器的 表示映射中,并以一个规范化类型字符串作为键,使 `"text/html"` 和 `"web text/html"` 被独立跟踪,同时同一类型的不同大小写写法(例如 `"text/HTML"` 和 `"text/html"`)会折叠为单个条目。-
令 key 为 mimeType 的essence。如果 isCustom 为 true,则为 key 加上 `"web "` 前缀。
-
并行运行以下步骤:
-
如果 clipboardItem 为 null,则在剪贴板任务源上排队一个全局任务, 给定 realm 的全局对象,以在 realm 中用一个
"InvalidStateError"DOMException拒绝 p,然后中止这些步骤。 -
如果 isCustom 为 true,则:
-
令 mapName 为 OS 特定 自定义映射名称。
-
令 mapRepresentation 为 clipboardItem 的系统 剪贴板表示列表中,其名称 为 mapName 的系统 剪贴板表示。如果不存在这样的系统 剪贴板表示,则在剪贴板任务 源上排队一个全局 任务,给定 realm 的全局对象,以在 realm 中用一个
"NotFoundError"DOMException拒绝 p,然后中止这些步骤。 -
令 webCustomFormatMapString 为从 mapRepresentation 的数据 反序列化得到的 JSON 字符串。
-
令 osFormatName 为 webCustomFormatMapString 中键与 mimeType 序列化后匹配的值。
-
如果没有找到 osFormatName,则在剪贴板任务 源上排队一个全局 任务,给定 realm 的全局对象,以在 realm 中用一个
"NotFoundError"DOMException拒绝 p,然后中止这些步骤。
-
-
否则,令 osFormatName 为在给定 mimeType 的情况下运行OS 特定 知名格式所得的结果。
-
令 clipboardRepresentation 为 clipboardItem 的系统剪贴板 表示列表中,其名称为 osFormatName 的系统剪贴板 表示。如果不存在这样的系统剪贴板 表示,则在剪贴板任务源上排队一个全局任务, 给定 realm 的全局对象,以在 realm 中用一个
"NotFoundError"DOMException拒绝 p,然后中止这些步骤。 -
令 rawData 为 clipboardRepresentation 的数据。
-
令 cleanData 为 rawData 的一个副本。
-
如果 mimeType 的essence 位于 this 的未清理 MIME 类型中,并且 mimeType 的essence 位于可选 未清理数据类型列表中,则什么也不做。
-
否则,如果 mimeType 的essence 不是 "image/png", 用户代理可以清理 cleanData。
-
令 blob 为一个
Blob, 其type为 mimeType 序列化后的结果,且其 底层字节序列为 cleanData。
注: 一旦 p 已经 敲定,它会保留在 this 的带解析器的 表示映射中。随后对同一 MIME 类型的
getType()调用将返回缓存的已敲定Promise, 而不会重新读取系统剪贴板。不过,上面的过期数据检查 仍适用于每一次调用:如果系统剪贴板 自read()被调用以来已经发生变化,则此ClipboardItem上的所有getType()调用都将以"InvalidStateError"被拒绝——即使对于先前缓存的数据也是如此。-
返回 p。
-
-
令 representationDataPromise 为 representation 的数据。
-
响应 representationDataPromise:
-
如果 representationDataPromise 以值 v 兑现,则:
-
如果 representationDataPromise 被拒绝,则:
-
在 realm 中用
"NotFoundError"DOMException拒绝 p。
-
-
-
返回 p。
-
-
-
在 realm 中用
"NotFoundError"DOMException拒绝 p。 -
返回 p。
7.2.4. supports(type)
此方法必须运行以下步骤:
7.3. 剪贴板接口
typedef sequence <ClipboardItem >; [ClipboardItems SecureContext ,Exposed =Window ]interface :Clipboard EventTarget {Promise <ClipboardItems >read (optional ClipboardUnsanitizedFormats = {});formats Promise <DOMString >readText ();Promise <undefined >write (ClipboardItems );data Promise <undefined >writeText (DOMString ); };data dictionary {ClipboardUnsanitizedFormats sequence <DOMString >; };unsanitized
Clipboard
接口的某些方法会接受或返回多个 ClipboardItem
对象。然而,并非所有平台都支持多个剪贴板项;在这类平台上,下面的算法会忽略
传给 write()
的第一个之外的任何 ClipboardItem
对象,并且 read()
和 readText()
只会从 OS 获取一个剪贴板项。
Web 作者需要创建一个 data,它是一个由 ClipboardItems
组成的数组,以便使用 write(data)
方法向系统剪贴板写入内容。
read()
会返回一个指向剪贴板项对象的 Promise,
该对象表示系统剪贴板数据的内容。
unsanitized
是一个由 DOMStrings
组成的 sequence,
对应于作者希望被视为可选未清理数据类型的mime type。
unsanitized
选项可能不受用户代理支持。Web 作者不应假定 unsanitized
中列出的 MIME 类型的内容会是未清理的,因为可能存在不允许使用此选项的隐私模式。
剪贴板任务 源会响应系统剪贴板数据的读取或写入而被触发。
7.3.1. read(formats)
read(formats)
方法必须运行以下步骤:
-
令 p 为 realm 中的一个新的 promise。
-
如果 formats 不为空,则:
-
对于 formats["
unsanitized"] 中的每个 format:-
如果 format 不在可选未清理数据 类型中,则在 realm 中用 format
"NotAllowedError"DOMException拒绝 p。
-
-
-
并行运行以下步骤:
-
令 r 为运行检查剪贴板读取 权限所得的结果。
-
如果 r 为 false,则:
-
在权限任务源上排队一个全局任务,给定 realm 的全局对象,以在 realm 中用
"NotAllowedError"DOMException拒绝 p。 -
中止这些步骤。
-
-
令 data 为系统剪贴板数据。
-
令 snapshotChangeCount 为当前剪贴板 变更计数。
注: 此时只枚举来自 data 的名称(格式 标识符)。每个表示的底层 载荷字节要到
getType()被调用时才会读取。 -
令 items 为一个由有序映射组成的 sequence,每个条目都具有键 "item"(一个剪贴板项)和 "originating"(一个系统剪贴板项或 null)。
-
对于 data 中的每个 systemClipboardItem:
-
令 item 为一个新的剪贴板项。
-
对于 systemClipboardItem 中的每个 systemClipboardRepresentation:
-
令 mimeType 为在给定 systemClipboardRepresentation 的名称的情况下, 运行从 OS 特定格式得到知名 mime type算法所得的结果。
-
如果 mimeType 为 null,则继续此循环。
-
令 representation 为一个新的表示。
-
将 representation 的MIME 类型设置为 mimeType。
-
将 representation 的数据设置为 realm 中的一个新的 promise。
注: 此表示的 OS 剪贴板数据不会被获取,直到作者在所得的
ClipboardItem对象上调用getType()。 -
将 representation 追加到 item 的表示 列表。
-
-
如果 item 的表示列表大小 大于 0,则将有序映射 «[ "item" → item, "originating" → systemClipboardItem ]» 追加到 items。
-
-
如果 items 的大小 > 0,则:
-
令 firstItem 为 items[0]["item"]
-
在给定 firstItem 的情况下运行读取 Web 自定义格式算法。
-
-
否则:
-
令 customItem 为一个新的剪贴板项。
-
在给定 customItem 的情况下运行读取 Web 自定义格式 算法。
-
如果 customItem 的表示列表大小 大于 0,则将有序映射 «[ "item" → customItem, "originating" → null ]» 追加到 items。
-
-
在剪贴板任务源上排队一个全局任务,给定 realm 的全局对象,以执行以下 步骤:
-
令 clipboardItems 为一个 sequence<
ClipboardItem>。 -
对于 items 的每个有序映射 entry:
-
令 clipboardItem 为在给定 entry["item"] 和 realm 的情况下,运行创建 ClipboardItem 对象步骤所得的结果。
-
将 clipboardItem 的读取时的剪贴板 变更计数设置为 snapshotChangeCount。
-
将 clipboardItem 的源 系统剪贴板项设置为 entry["originating"]。
-
如果 formats 不为空,则将 clipboardItem 的未清理 MIME 类型设置为 formats["
unsanitized"]。 -
将 clipboardItem 追加到 clipboardItems。
-
-
用 clipboardItems 兑现 p。
-
-
-
返回 p。
// clipboard.read() 仅返回带有类型元数据的 ClipboardItem。 // 此时不会读取任何 OS 剪贴板数据。 const items= await navigator. clipboard. read(); // OS 剪贴板读取会在这里惰性发生,即调用 getType() 时。 const textBlob= await items[ 0 ]. getType( "text/plain" ); const text= await ( new Response( textBlob)). text();
7.3.2. readText()
readText()
方法必须执行以下步骤:
-
令 p 为 realm 中的一个新的 promise。
-
并行运行以下步骤:
-
令 r 为运行检查剪贴板读取 权限所得的结果。
-
如果 r 为 false,则:
-
在权限任务源上排队一个全局任务,给定 realm 的全局对象,以在 realm 中用
"NotAllowedError"DOMException拒绝 p。 -
中止这些步骤。
-
-
令 data 为系统 剪贴板数据的一个副本。
某些 OS 包含多个剪贴板(例如 Linux 的 "primary"、"secondary"、 "selection")。定义要从其中哪个读取数据。
-
在剪贴板任务源上排队一个全局任务,给定 realm 的全局对象,以执行以下 步骤:
-
对于 data 中的每个 systemClipboardItem:
-
对于 systemClipboardItem 中的每个 systemClipboardRepresentation:
-
令 mimeType 为在给定 systemClipboardRepresentation 的名称的情况下, 运行从 OS 特定格式得到知名 mime type算法所得的结果。
-
如果 mimeType 为 null,则继续此循环。
-
令 representation 为一个新的表示。
-
如果 representation 的MIME type essence是 "text/plain",则:
-
将 representation 的MIME type设置为 mimeType。
-
令 representationDataPromise 为 representation 的数据。
-
响应 representationDataPromise:
-
如果 representationDataPromise 以值 v 兑现,则:
-
如果 representationDataPromise 被 拒绝,则:
-
在 realm 中用
"NotFoundError"DOMException拒绝 p。 -
返回 p。
-
-
-
-
-
-
在 realm 中用
"NotFoundError"DOMException拒绝 p。 -
返回 p。
-
-
navigator. clipboard. readText(). then( function ( data) { console. log( "你的字符串: " , data); });
7.3.3. write(data)
write(data)
方法必须执行以下步骤:
-
令 p 为 realm 中的一个新的 promise。
-
并行运行以下步骤:
-
令 r 为运行检查剪贴板写入 权限所得的结果。
clipboard-write 已在 https://github.com/w3c/clipboard-apis/pull/164 中被移除。
-
如果 r 为 false,则:
-
在权限任务源上排队一个全局任务,给定 realm 的全局对象,以在 realm 中用
"NotAllowedError"DOMException拒绝 p。 -
中止这些步骤。
-
-
在剪贴板任务源上排队一个全局任务,给定 realm 的全局对象,以执行以下 步骤:
-
令 dataList 为一个 sequence<
ClipboardItem>。 -
如果 data 的大小大于 1,且当前 操作系统不支持在系统剪贴板上使用多个原生剪贴板项,则将 data[0] 添加到 dataList,否则,将 dataList 设置为 data。
当 data 包含多个项 且操作系统支持多个原生剪贴板项时,当前算法会按顺序将这些项写入系统剪贴板, 而不是将它们作为整体写入。
-
对于 dataList 中的每个 clipboardItem:
-
对于 clipboardItem 的剪贴板项的 表示 列表中的每个 representation:
-
令 representationDataPromise 为 representation 的数据。
-
响应 representationDataPromise:
-
如果 representationDataPromise 以值 v 兑现,则:
-
如果 representationDataPromise 被拒绝, 则:
-
在 realm 中用
"NotAllowedError"DOMException拒绝 p。 -
中止这些步骤。
-
-
-
-
对于 itemList 中的每个 blob:
-
令 type 为 blob 的
type。 -
如果 type 不在强制数据 类型或可选数据 类型列表中,则在 realm 中用
"NotAllowedError"DOMException拒绝 p 并中止这些步骤。 -
令 cleanItem 为 blob 的一个可选清理副本。
-
如果已尝试清理但未成功 完成,则执行以下步骤:
-
在 realm 中用
"NotAllowedError"DOMException 拒绝 p。 -
中止这些步骤。
-
-
将 cleanItem 追加到 cleanItemList。
-
-
用 cleanItemList 和 option 将 blob 和选项写入剪贴板。
-
-
兑现 p。
-
-
返回 p。
var data= [ new ClipboardItem({ "text/plain" : Promise. resolve( new Blob([ "文本数据" ], { type: "text/plain" })) })]; navigator. clipboard. write( data). then( function () { console. log( "已成功复制到剪贴板!" ); }, function () { console. error( "无法写入剪贴板。 :-(" ); });
7.3.4. writeText(data)
writeText(data)
方法必须执行以下步骤:
-
令 p 为 realm 中的一个新的 promise。
-
并行运行以下步骤:
-
令 r 为运行检查剪贴板写入 权限所得的结果。
clipboard-write 已在 https://github.com/w3c/clipboard-apis/pull/164 中被移除。
-
如果 r 为 false,则:
-
在权限任务源上排队一个全局任务,给定 realm 的全局对象,以在 realm 中用
"NotAllowedError"DOMException 拒绝 p。 -
中止这些步骤。
-
-
-
返回 p。
await navigator. clipboard. writeText( "你好,伙伴!" );
8. 剪贴板操作
本节定义剪贴板操作和事件分发的处理模型。
每个剪贴板操作都有两个标志,称为脚本触发和脚本可访问剪贴板。
如果操作因脚本而运行(例如 document.execCommand() 调用),则设置 脚本触发
标志。未来与剪贴板交互的脚本 API 也应使用这些操作,并且必须相应地设置 脚本触发标志。
脚本可访问剪贴板 标志设置如下:
8.1. 复制操作
复制操作包括以下步骤:
-
如果script-triggered 标志已设置,则
-
如果script-may-access-clipboard 标志未设置,则
-
从复制操作返回 false,终止本算法
-
-
-
如果该事件未被取消,则
-
将选中的内容(如果有)复制到剪贴板。实现 应当 在网页内容被选中时,创建 text/html 和 text/plain 的剪贴板格式。
-
触发名为
clipboardchange的剪贴板事件
-
-
否则,如果该事件被取消,则
-
调用写内容到剪贴板算法,传入
DataTransferItemList列表 items,clear-was-called 标志 和 types-to-clear 列表。
-
-
从复制操作返回 true
8.2. 剪切操作
剪切操作包括以下步骤:
-
如果 script-triggered 标志已设置,则
-
如果 script-may-access-clipboard 标志未设置,则
-
从剪切操作返回 false,并终止该算法
-
-
-
如果事件未被取消,则
-
如果在一个 可编辑上下文 中存在选择区域且允许剪切,则
-
将选中的内容(如果有)复制到剪贴板。实现 应当 在网页内容被选中时,创建 text/html 和 text/plain 的剪贴板格式。
-
从文档中移除选择的内容,并收起选区。
-
触发一个名为
clipboardchange的剪贴板事件 -
排队任务以触发应因该修改而触发的任何事件,详情见 § 5.3 与其他脚本和事件的集成。
-
-
否则,如果没有选区或上下文不可编辑,则
-
返回 false
-
-
-
否则,如果事件被取消,则
-
调用 写内容到剪贴板 算法, 传递
DataTransferItemList列表 items, clear-was-called 标志 和 types-to-clear 列表。 -
触发一个名为
clipboardchange的剪贴板事件
-
-
从剪切操作返回 true
8.3. 粘贴操作
对于粘贴操作,脚本可访问剪贴板标志取决于实现特定的权限机制,用于确定哪些站点或应用可以从剪贴板读取。当脚本触发粘贴操作时,实现不得在未经用户许可的情况下提供剪贴板内容。如果尚未授予权限,则权限提示必须包括与脚本线程关联的文档的主机名。
粘贴操作包括以下步骤:
9. Permissions API 集成
[permissions] API 为网站提供了一种统一的方式来访问强大的功能,例如剪贴板。它允许网站向用户请求权限并查询它们拥有的权限。
对于剪贴板,定义了一个权限:"clipboard-write"
注意:剪贴板权限目前仅适用于异步剪贴板 API。 此规范的未来版本可能会更新以将此权限应用于其他剪贴板交互。
这些剪贴板权限是强大的功能,与权限相关的算法和类型定义如下:
- 权限描述符类型
-
dictionary :ClipboardPermissionDescriptor PermissionDescriptor {boolean =allowWithoutGesture false ; };
有 4 个剪贴板权限:
-
{ name: "clipboard-write", allowWithoutGesture: false }
-
{ name: "clipboard-write", allowWithoutGesture: true }
具有以下关系:
-
{ "clipboard-write" + true }强于{ "clipboard-write" + false }
虽然用户代理必须支持本规范中描述的 ClipboardPermissionDescriptor,
但它们当然保留对默认设置以及如何(或是否)向用户公开这些设置的完全控制权。
-
{ "clipboard-write" + false }公开给用户控制 -
{ "clipboard-write" + true }始终是denied
9.1. 剪贴板读取权限
9.2. 剪贴板写入权限
9.2.1. 检查剪贴板写入权限
-
令 writeWithoutGesture 为
{ name: "clipboard-write", allowWithoutGesture: true }权限的权限状态。 -
如果 writeWithoutGesture 为
granted, 则返回 true。 -
如果 hasGesture 为 true,则:
-
如果当前脚本是由于用户与用户代理或操作系统创建的“剪切”或“复制”元素交互而运行的,则令 systemCopy 为 true。
-
如果 systemCopy 为 true,则返回 true。
-
返回请求使用权限
{ name: "clipboard-write", allowWithoutGesture: false }权限的结果。注意:用户代理可以选择请求一个更强的权限,该权限将隐式更新此权限。
-
-
返回请求使用权限
{ name: "clipboard-write", allowWithoutGesture: true }权限的结果。
10. 安全注意事项
允许作者更改用户复制的内容,或自动复制从未被选中的内容,以及允许无限制地调用粘贴信息,这些都可能引发各种安全问题。
一些示例场景包括:
-
用户选择一个链接并复制它,但一个不同的链接被复制到剪贴板。 其影响范围可以从粘贴时的意外结果到企图进行的“网络钓鱼”攻击。
-
(Self-XSS)可以将 Shell 命令或可执行脚本放入剪贴板,意图让用户运行粘贴的内容。
-
可以将特制图像写入剪贴板,以利用核心操作系统图像处理代码中的错误。
10.1. 粘贴 HTML 和多部分数据
本节是非规范性的。
粘贴格式化或多部分数据存在某些安全风险。
-
用户可能在没有意识到隐藏数据存在的情况下粘贴了隐藏数据。例如,如果标记包含 <input type="hidden"> 标签或 HTML 注释,就可能发生这种情况。此类隐藏数据可能包含敏感信息。
-
用户可能将恶意 JavaScript 粘贴到受信任的页面中。
-
实现可能会授予脚本访问用户不打算公开的本地文件的权限。
为了确定使用哪些策略,我们考虑的因素有:
-
被粘贴数据的来源
-
数据子部分(如引用的图像)的来源
-
运行脚本的来源
以下是场景和可能的安全策略概述:
| 数据来源 | 脚本来源 | 规则 |
|---|---|---|
| 源自在线资源 | 与数据相同 | 不清理 HTML。不访问任何本地文件。 |
| 不同来源 | 可选地清理内容。不访问任何本地文件。 | |
| 源自本地应用程序 | 任何 | 不清理 HTML。授予对本地文件的访问权限 |
一些实现通过在粘贴富文本时默认剥离潜在的恶意内容(例如 SCRIPT 元素和 javascript: 链接)来降低与粘贴富文本相关的风险,但允许粘贴事件处理程序检索和处理原始的、未清理的数据。
10.2. 通用安全策略
实现不得下载引用的在线资源,或在 files
列表或 DataTransferItemList
中公开其内容。
如果剪贴板上的数据不是来自本地应用程序,则实现不得授予对任何引用的本地文件的访问权限。例如,如果数据包含 <img src="file://localhost/example.jpg"> 但数据的来源是在线资源,则实现不得将 example.jpg 的条目添加到 clipboardData.items 列表中。
10.3. 转码图像
为防止恶意图像数据被放置在剪贴板上,可以对图像数据进行转码以生成图像的安全版本。这可以防止网站试图利用其他应用程序中的安全漏洞。
实现不应转码从剪贴板读取的图像。转码图像可能会丢失重要的元数据(例如图像的物理分辨率)。 这也与可以与网站共享图像的其他方法(例如 `<input type=file>`)一致。
10.4. 滋扰注意事项
脚本可以使用 DataTransfer
API 通过更改复制和剪切事件中系统剪贴板上的数据来骚扰和迷惑用户。本规范不试图阻止此类滋扰,但实现可能会添加额外的限制。
实现必须妥善处理试图在剪贴板上放置过多数据的脚本。
11. 隐私注意事项
由于这些 API 提供了对用户剪贴板数据的访问权限,因此存在严重的隐私问题,因为剪贴板可能包含个人身份信息 (PII),例如姓名、地址或密码。
通常,用户代理必须确保不受信任的脚本无法通过这些 API 不受控制地访问用户的剪贴板数据。
11.1. 隐私和剪贴板事件 API
剪贴板事件 API 允许在剪贴板事件处理程序上下文中运行的脚本访问系统剪贴板的副本,并可能修改写入剪贴板的数据。
用户代理应注意以下有关保护剪贴板事件 API 访问的数据的要求:
-
实现
DataTransfer接口以返回剪贴板数据的对象不得在提供数据的 ClipboardEvent 事件处理程序之外可用。 -
如果脚本存储对实现
DataTransfer接口的对象的引用,以便在 ClipboardEvent 事件处理程序之外使用,则在预期上下文之外调用时,所有方法必须为空操作。 -
实现不得让脚本创建合成剪贴板事件以获取对真实剪贴板数据的访问权限(除非用户已配置为这样做)。
尽管剪贴板事件 API 不受剪贴板权限的保护,但用户代理可以选择提供一种方法,让用户禁用此 API 或配置允许哪些站点访问它。
11.2. 隐私和异步剪贴板 API
异步剪贴板 API 是一项强大的功能,因为它允许从任何脚本访问剪贴板数据(访问不限于剪贴板事件处理程序),并且可以在没有用户提供手势的情况下访问数据。
为帮助防止滥用,此 API 不得可用,除非脚本在具有焦点的文档上下文中执行。
11.2.1. 隐私和剪贴板权限
剪贴板权限控制对此 API 的访问,但用户代理可以选择权限默认值以及用户可以设置哪些权限设置。例如,用户代理可以选择仅在存在用户手势时才允许访问异步剪贴板 API,并始终拒绝脚本在没有手势的情况下访问的请求。
用户代理可以选择让此权限在用户授予权限后的某个时间自动过期,例如,通过让权限过期:
-
首次授予权限后的一段时间
-
用户上次访问站点后的一段时间
-
当用户离开页面时
11.3. 其他隐私问题
如果用户代理允许使用 document.execCommand("paste") 读取剪贴板数据,则用户代理必须确保用户已明确允许进行该访问。
12. 致谢
本节为非规范性内容
编辑们感谢前任编辑们所做的贡献,他们帮助本规范经历了各种会议和邮件列表讨论,使其达到目前的状态。
-
Hallvord R. M. Steen
编辑们也感谢微软的 Data Transfer 功能文档 [MICROSOFT-CLIP-OP] 以及 [HTML5] 规范的早期草案在知识上的启发。 我们也感谢 Paul Libbrecht 提供的“安全复制和粘贴”草案(此草案已无法在网络上找到)。
最后,我们要感谢以下人员所做的贡献:
Adam Barth, Shawn Carnell, Daniel Cheng, Daniel Dardailler, Domenic Denicola, Al Gilman, James Graham, James Greene, Ian Hickson, Darwin Huang, Lachlan Hunt, Philip Jägenstedt, Anne van Kesteren, Marijn Kruisselbrink, Aaron Leventhal, Jim Ley, Paul Libbrecht, "Martijn", Glenn Maynard, Chris Mills, ms2ger, Ryosuke Niwa, Robert O’Callahan, Dave Poehlman, "ROBO Design", Janina Sajka, Rich Schwerdtfeger, Jonas Sicking, Maciej Stachowiak, Mihai Sucan, Dmitry Titov, Ojan Vafai, Tarquin Wilton-Jones, Tom Wlodkowski, Bo Cupp, mbrodesser 和 Boris Zbarsky。
附录 A:算法
将内容写入剪贴板
- 输入
-
items,一个要写入的项目的
DataTransferItemList列表 -
clear-was-called,一个布尔值
-
types-to-clear,一个列表
- 输出
-
无
-
如果 items 列表不为空,则
-
清除剪贴板
-
对于列表中的每个部分,
-
如果数据类型是 text/plain,则
-
确保编码根据操作系统和区域设置约定是正确的
-
根据平台约定规范化行尾
-
使用适当的操作系统剪贴板格式描述将文本放置在剪贴板上
-
-
否则,如果数据类型是强制数据类型列表中列出的类型,则
-
使用适当的操作系统剪贴板格式描述将部分放置在剪贴板上
-
-
否则
-
-
-
否则,items 列表为空。按照以下步骤确定是否清除剪贴板:
将 blobs 和 option 写入剪贴板
-
对于 items 中的每个 item:
-
如果 formatString 为空,则执行以下步骤:
-
令 webCustomFormatString 为 item 的
type。 -
令 webCustomFormat 为一个空的
type。 -
如果 webCustomFormatString 以 `"web "` 前缀开始,则 移除 `"web "` 前缀,并将剩余字符串存储在 webMimeTypeString 中。
-
令 webMimeType 为在给定 webMimeTypeString 的情况下解析 MIME 类型所得的结果。
-
如果 webMimeType 为 failure,则中止所有步骤。
-
将 item 的
type设置为 webCustomFormat。 -
将 webCustomFormat 追加到 webCustomFormats。
-
-
令 payload 为对 item 的底层字节 序列进行 UTF-8 解码所得的结果。
-
使用 formatString 作为原生剪贴板格式,将 payload 和 presentationStyle 插入到系统 剪贴板中。
某些 OS 包含多个剪贴板(例如 Linux 的 "primary"、"secondary"、"selection")。定义数据要写入其中哪一个。
-
在给定 webCustomFormats 的情况下写入 Web 自定义格式。
特定于操作系统的已知格式
- 输入
-
mimeType,一个
type - 输出
-
wellKnownFormat,一种平台特定的字符串类型。在 Mac 上是 NSPasteboardType,在 Windows 上是 LPCWSTR,在 Linux 上是 const char*。
Windows 参见 https://docs.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats 和 https://docs.microsoft.com/en-us/windows/win32/dataxchg/about-atom-tables?redirectedfrom=MSDN Mac 参见 https://developer.apple.com/documentation/appkit/nspasteboardtype
-
令 wellKnownFormat 为空字符串。
-
如果 mimeType 的 essence 是 "text/plain",则
在 Windows 上,遵循如下所述的约定:
-
将 CF_UNICODETEXT 赋给 wellKnownFormat。
在 MacOS 上,遵循如下所述的约定:
-
将 NSPasteboardTypeString 赋给 wellKnownFormat。
在 Linux、ChromeOS 和 Android 上,遵循如下所述的约定:
-
将 "text/plain" 赋给 wellKnownFormat。
-
-
否则,如果 mimeType 的 essence 是 "text/html",则
在 Windows 上,遵循如下所述的约定:
-
将 CF_HTML 赋给 wellKnownFormat。
在 MacOS 上,遵循如下所述的约定:
-
将 NSPasteboardTypeHTML 赋给 wellKnownFormat。
在 Linux、ChromeOS 和 Android 上,遵循如下所述的约定:
-
将 "text/html" 赋给 wellKnownFormat。
-
-
否则,如果 mimeType 的 essence 是 "image/png",则
在 Windows 上,遵循如下所述的约定:
-
将 "PNG" 赋给 wellKnownFormat。
在 MacOS 上,遵循如下所述的约定:
-
将 NSPasteboardTypePNG 赋给 wellKnownFormat。
在 Linux、ChromeOS 和 Android 上,遵循如下所述的约定:
-
将 "image/png" 赋给 wellKnownFormat。
-
-
否则,如果 mimeType 的 essence 是 "image/svg+xml",则
在 Windows 上,遵循如下所述的约定:
-
将 CFSTR_MIME_SVG_XML 赋给 wellKnownFormat。
在 MacOS 上,遵循如下所述的约定:
-
将 UTTypeSVG 赋给 wellKnownFormat。
在 Linux、ChromeOS 和 Android 上,遵循如下所述的约定:
-
将 "image/svg+xml" 赋给 wellKnownFormat。
-
-
返回 wellKnownFormat。
从特定于操作系统的格式获取已知 MIME 类型
- 输入
-
osFormatName,一个平台特定的字符串类型。在 Mac 上是 NSPasteboardType,在 Windows 上是 LPCWSTR,在 Linux 上是 const char*。
- 输出
-
mimeType,一个 MIME 类型
对于 Windows,请参阅 https://docs.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats 和 https://docs.microsoft.com/en-us/windows/win32/dataxchg/about-atom-tables?redirectedfrom=MSDN 对于 Mac,请参阅 https://developer.apple.com/documentation/appkit/nspasteboardtype
在 Windows 上,遵循以下描述的约定:
-
如果 osFormatName 是 "UnicodeText",则将 mimeTypeString 设置为 "text/plain"。
-
否则,如果 osFormatName 是 "HTML Format",则将 mimeTypeString 设置为 "text/html"。
-
否则,如果 osFormatName 是 "PNG",则将 mimeTypeString 设置为 "image/png"。
-
否则,如果 osFormatName 是 CFSTR_MIME_SVG_XML,则将 mimeTypeString 设置为 "image/svg+xml"。
在 MacOS 上,遵循以下描述的约定:
-
如果 osFormatName 是 NSPasteboardTypeString,则将 mimeTypeString 设置为 "text/plain"。
-
否则,如果 osFormatName 是 NSPasteboardTypeHTML,则将 mimeTypeString 设置为 "text/html"。
-
否则,如果 osFormatName 是 NSPasteboardTypePNG,则将 mimeTypeString 设置为 "image/png"。
-
否则,如果 osFormatName 是 UTTypeSVG,则将 mimeTypeString 设置为 "image/svg+xml"。
在 Linux、ChromeOS 和 Android 上,遵循以下描述的约定:
-
如果 osFormatName 是 "text/plain",则将 mimeTypeString 设置为 "text/plain"。
-
否则,如果 osFormatName 是 "text/html",则将 mimeTypeString 设置为 "text/html"。
-
否则,如果 osFormatName 是 "image/png",则将 mimeTypeString 设置为 "image/png"。
-
否则,如果 osFormatName 是 "image/svg+xml",则将 mimeTypeString 设置为 "image/svg+xml"。
-
令 mimeType 为解析 MIME 类型的结果,给定 mimeTypeString。
-
返回 mimeType。
读取 Web 自定义格式
- 输入
-
item,一个剪贴板项
-
令 webCustomFormatMap 为OS 特定 自定义映射名称。
-
从系统剪贴板读取 webCustomFormatMap。
-
如果 webCustomFormatMap 为空,则返回 item。
-
令 webCustomFormatMapString 为从 webCustomFormatMap 反序列化得到的 JSON 字符串。
注: 需要一个 JSON 读取器来反序列化 来自 webCustomFormatMap 的内容。
-
对于 webCustomFormatMapString 中的每个(key,value):
-
令 mimeType 为在给定 key 的情况下解析 MIME 类型所得的结果。
-
如果 mimeType 为 failure,则继续循环。
-
令 representation 为一个新的表示。
-
将 representation 的MIME 类型设置为 mimeType。
-
将 representation 的isCustom 标志设置为 true。
-
将 representation 的数据设置为一个 新的 promise。
注: 此 Web 自定义格式的 OS 剪贴板数据不会被获取,直到作者在所得的
getType()对象上调用ClipboardItem为止。 -
将 representation 追加到 item 的表示 列表。
-
写入 Web 自定义格式
-
令 idx 为初始化为 0 的数字。
-
令 webCustomFormatMap 为OS 特定 自定义映射名称。
-
令 webCustomFormatMapString 为空 JSON 字符串。
-
对于 items 中的每个 item:
-
令 webCustomFormat 为OS 特定 自定义名称。
-
令 webCustomFormatIdx 为将 idx 追加到 webCustomFormat 后所得的结果。
-
使用 JSON 序列化器,将 item 的
type作为键、将 webCustomFormatIdx 作为值插入 webCustomFormatMapString。注: 需要一个 JSON 写入器来 将内容序列化到 webCustomFormatMapString 中。
-
使用 webCustomFormatIdx 作为格式,将 item 插入系统剪贴板。
-
递增 idx。
-
如果 idx 大于 100,则跳出此循环。
-
-
使用 webCustomFormatMap 作为格式,将 webCustomFormatMapString 插入系统剪贴板。
特定于操作系统的自定义映射名称
- 输出
-
webCustomFormatMap,一个字符串
在 Windows 上,遵循以下描述的约定:
-
将 "Web Custom Format Map" 分配给 webCustomFormatMap。
-
返回 webCustomFormatMap。
在 MacOS 上,遵循以下描述的约定:
-
将 "org.w3.web-custom-format.map" 分配给 webCustomFormatMap。
-
返回 webCustomFormatMap。
在 Linux、ChromeOS 和 Android 上,遵循以下描述的约定:
-
将 "application/web;type=\"custom/formatmap\"" 分配给 webCustomFormatMap。
-
返回 webCustomFormatMap。
特定于操作系统的自定义名称
- 输出
-
webCustomFormat,一个字符串
在 Windows 上,遵循以下描述的约定:
-
将 "Web Custom Format" 分配给 webCustomFormat。
-
返回 webCustomFormat。
在 MacOS 上,遵循以下描述的约定:
-
将 "org.w3.web-custom-format.type-" 分配给 webCustomFormat。
-
返回 webCustomFormat。
在 Linux、ChromeOS 和 Android 上,遵循以下描述的约定:
-
将 "application/web;type="custom/format" 分配给 webCustomFormat。
-
返回 webCustomFormat。
触发剪贴板事件
- 输入
-
e,要触发的
ClipboardEvent - 输出
-
无
-
令 clear-was-called 为 false
-
令 types-to-clear 为一个空列表
-
令 clipboard-event-data 为一个新的
DataTransfer对象,其items列表为空。 -
令 clipboard-entry 为当前剪贴板内容的序列号,如果操作系统剪贴板不支持序列号,则为 null
-
如果事件由用户代理生成,则令 trusted 为 true,否则为 false
-
按如下方式设置 target:
-
按如下方式处理事件:
-
如果 e 是 "paste",则
-
如果 trusted 为 true,或者实现配置为授予脚本生成的事件对操作系统剪贴板的读取权限
-
对于操作系统剪贴板上的每个 clipboard-part,执行以下步骤:
-
如果 clipboard-part 包含纯文本,则
-
确保文本使用脚本引擎内部使用的编码
-
令 new-data 为一个新的
DataTransferItem, 其拖放数据项种类设置为 string,拖放数据项类型字符串设置为 text/plain。 -
将 new-data 的数据设置为纯文本。
-
将 new-data 添加到 clipboard-event-data 的
items中
-
-
如果 clipboard-part 表示文件引用,则对于每个文件引用:
-
确定引用文件的 MIME 类型
-
令 new-data 为一个新的
DataTransferItem, 其拖放数据项种类设置为 file,拖放数据项类型字符串设置为相应的 MIME 类型,如果文件类型未知,则设置为application/octet-stream。 -
将 new-data 的数据设置为文件引用数据。
-
将 new-data 添加到 clipboard-event-data 的
items中
-
-
如果 clipboard-part 包含 HTML 或 XHTML 格式的文本(根据操作系统描述此类剪贴板格式的约定),则
-
如果实现支持粘贴 HTML,则实现必须通过使用 clipboard-part 和 clipboard-event-data 调用处理 html 粘贴事件来处理标记。
-
-
如果 clipboard-part 包含其他受支持的二进制或基于文本的格式的数据(请参阅强制数据类型),则
-
确定数据的 MIME 类型
-
令 new-data 为一个新的
DataTransferItem, 其拖放数据项种类设置为 file,拖放数据项类型字符串设置为相应的 MIME 类型 -
将 new-data 的数据设置为二进制或基于文本的数据。
-
将 new-data 添加到 clipboard-event-data 的
items中
-
-
-
-
更新 clipboard-event-data 的
files属性以匹配 clipboard-event-data 的items中的条目。 -
更新 clipboard-event-data 的
types属性以匹配 clipboard-event-data 的items中的条目。
-
如果 e 是 "copy" 或 "cut",则
-
将关联的
DataTransfer对象的内部拖放数据存储模式标志设置为读/写
-
-
-
将 e 的
clipboardData设置为 clipboard-event-data。 -
将 e 的
isTrusted设置为 trusted。 -
将 e 的
composed设置为 true。 -
在 target 处分派事件 e,该事件冒泡且可取消,并使用
ClipboardEvent接口。事件分派期间数据访问的实现要求在[HTML]中定义。下面给出了一些额外的剪贴板事件特定处理规则:
-
如果脚本调用 clearData() 或
clear()并且DataTransfer对象的内部拖放数据存储模式标志是读/写,则-
将 clear-was-called 标志设置为 true。如果给定了参数,则将该参数添加到 types-to-clear 列表中。
-
-
如果脚本调用 setData() 或修改项目并且 clear-was-called 标志为 true,则
-
如果 types-to-clear 列表为空,则
-
将 clear-was-called 标志设置为 false,则
-
-
否则,如果 setData() 的
type参数或新项目的拖放数据项类型字符串在 types-to-clear 列表中找到,则-
将其从列表中删除。如果列表现在为空,则将 clear-was-called 标志设置为 false。
-
-
-
如果脚本调用 getData() 或访问
DataTransferItemList中的项目并且设置了 clipboard-entry,则-
检查剪贴板数据的序列号是否与 clipboard-entry 匹配。如果剪贴板不再包含相同的条目,则将
DataTransferItemList对象的内部拖放数据存储模式设置为受保护。
-
警告!监听粘贴事件的恶意脚本可能会设置一个无限循环,以便读取用户将来放置在剪贴板上的内容。在无法使用剪贴板序列号的平台上,应实施其他限制。
-
处理 HTML 粘贴事件
- 输入
-
clipboard-part,要处理的剪贴板部分
-
clipboard-event-data,此事件的
DataTransfer对象 - 输出
-
无
-
令 new-data 为一个新的
DataTransferItem, 其拖放数据项种类设置为纯 Unicode 字符串,拖放数据项类型字符串相应地设置为 text/html 或 application/xhtml+xml。 -
从 clipboard-part 中提取标记,并使用相关的解析器构建 DOM 树。
-
如果标记的源 URL 已知,则使用源 URL 作为基础 URL 解析 HREF 和 SRC 属性中的所有相对 URL,并将相应的属性设置为解析后的绝对 URL。
-
如果标记的来源是本地应用程序,则检查是否存在对本地文件和/或操作系统剪贴板内容的其他部分的引用。如果找到此类引用,则对子部分的引用必须使用 cid: URL 方案 [RFC2392] 替换为内容 ID 引用。为此,请按照以下步骤处理每个引用本地文件或剪贴板部分的属性:
这些步骤是必要的吗?我们是否了解支持具有内部引用的多个部分的本机(平台)剪贴板实现?
此功能存在风险,因为尚不清楚是否需要它,并且难以进行跨平台测试。
-
如果 clipboard-event-data 的
items已经包含对引用的文件或剪贴板部分的条目,则-
将 itemNumber 设置为现有条目的索引。
-
-
否则,
-
令 new-file-data 为一个新的
DataTransferItem, 其拖放数据项种类设置为 "file",拖放数据项类型字符串设置为文件或剪贴板部分的 MIME 类型(如果已知),如果文件类型未知,则设置为application/octet-stream。 -
令 file-info 为一个新的
File对象,其name设置为 HTML 属性内容的名称部分,lastModified设置为引用文件的时间戳,如果条目引用剪贴板部分,则设置为 0。 -
将 new-file-data 的内部
File对象设置为 file-info。 -
将 new-file-data 添加到 clipboard-event-data 的
items中,并令 itemNumber 为此条目在DataTransferItemList中的索引。
-
-
更新引用本地文件或剪贴板部分的 DOM 属性,使其包含字符串 'cid:' 后跟 itemNumber。
-
-
序列化处理后的 DOM 并使用生成的 HTML 代码更新 new-data。
-
将 new-data 添加到 clipboard-event-data 的
items中。