首页
/ Electron clipboard 模块实战指南:基于 W3C Clipboard API 的系统剪贴板操作与自定义 MIME 格式

Electron clipboard 模块实战指南:基于 W3C Clipboard API 的系统剪贴板操作与自定义 MIME 格式

2026-09-04 15:26:30作者:伍希望

Electron 的 clipboard 模块运行在主进程,是当前版本中面向系统剪贴板进行复制/粘贴的唯一官方入口。本篇以 clipboard API 文档 为核心,完整覆盖其 W3C 风格方法(read/write/readText/writeText/has/clear)、Electron 自定义 MIME 格式(bookmark、findtext、osclipboard、web 前缀)以及 Linux 特有的 selection 剪贴板,并结合仓库中的 JavaScript 封装层(lib/browser/api/clipboard.ts)与 C++ 原生实现(shell/browser/api/electron_api_clipboard.cc)说明底层调用链,读完后你可以掌握跨平台多格式剪贴板读写、原子写入与原生格式穿透(raw format round-trip)的完整实现方案。

模块定位与设计模型

clipboard 模块的设计目标是复刻 W3C Clipboard API(navigator.clipboard)的接口形态,但提供的是不受隐私沙箱限制的原生剪贴板访问能力:

  • clipboard.read() 返回 Promise<ClipboardItem[]>,其中 ClipboardItem 携带一个或多个 MIME 类型到 Blob 负载的映射;
  • clipboard.write() 接受 ClipboardItem[] 数组,所有条目在一次调用内原子性地提交到系统剪贴板;
  • 与渲染进程的 navigator.clipboard 不同,主进程的 clipboard 能读到文件的真实绝对路径(例如 text/uri-list 不做隐私脱敏),并且能访问平台级原始剪贴板格式。

需要注意一条已废弃行为(见 docs/api/clipboard.md 中的 history 注释):在渲染进程中直接使用 clipboard API 已被废弃,该模块只在主进程中可用,进程模型定义参见 glossary

[!NOTE] ClipboardItem 不能被用户代码继承(Electron 内置类的通用限制),详见 FAQ

Electron 自定义 MIME 格式

除了标准 MIME 类型(text/plaintext/htmltext/rtfimage/pngimage/jpeg 等),Electron 暴露了一小组自定义格式,遵循 W3C 自定义格式提案,但使用 electron 前缀而非 web 以避免命名冲突:

自定义格式 平台 说明
electron application/bookmark 全平台(读取侧 Linux 不支持) URL 书签。唯一例外:读写两侧都是 ClipboardBookmark 对象({ title, url })而非 Blob,即 getType('electron application/bookmark') 解析为对象
electron application/findtext 仅 macOS 活跃应用"查找"粘贴板(find pasteboard)的内容
electron application/osclipboard;format="<name>" 全平台 平台特定剪贴板格式的原始负载。<name> 是平台格式名(Windows 如 HTML Format,macOS 如 public.utf8-plain-text)。clipboard.read() 还会把没有标准 MIME 映射的任意平台格式归入该自定义格式暴露,因此原始 OS 格式可以"原样往返"(写进去和读出来是同一个 MIME 字符串)

此外,read()write() 都接受任意 MIME 类型,包括以 web 前缀(后跟空格,如 web application/x.my-format)开头的 W3C web 自定义格式:

const { clipboard, ClipboardItem } = require('electron')

async function writeClipboard () {
  await clipboard.write([
    new ClipboardItem({
      'web application/x.my-app-clip': new Blob(['arbitrary payload'])
    })
  ])
}

writeClipboard()

核心方法详解

clipboard.readText()

返回 Promise<string>,以纯文本形式读取剪贴板内容,对标 W3C navigator.clipboard.readText

const { clipboard } = require('electron')

async function readText () {
  await clipboard.writeText('hello i am a bit of text!')
  const text = await clipboard.readText()
  console.log(text)
  // 'hello i am a bit of text!'
}

readText()

从源码结构看,electron_api_clipboard.ccClipboard::ReadText 是常见调用的"快速路径",直接走 ui::Clipboard::ReadText,绕过了 getType() 的逐 MIME 分派开销;在 Windows 上若 Unicode 读取为空,还会自动回退到 ReadAsciiTextReadText 中的 IS_WIN 分支)。

clipboard.writeText(text)

  • text string

返回 Promise<void>,文本写入完成时 resolve,对标 W3C navigator.clipboard.writeText

const { clipboard } = require('electron')

async function writeClipboardText () {
  await clipboard.writeText('hello i am a bit of text!')
}

writeClipboardText()

clipboard.read()

返回 Promise<ClipboardItem[]>,resolve 为携带剪贴板全部内容的 ClipboardItem 数组:

const { clipboard } = require('electron')

async function dumpClipboard () {
  const items = await clipboard.read()
  for (const item of items) {
    for (const type of item.types) {
      const blob = await item.getType(type)
      console.log(type, blob)
    }
  }
}

dumpClipboard()

原生侧的类型枚举管线 EnumerateAvailableTypes(见 electron_api_clipboard.cc)解释了 types 数组是如何聚合出来的,它按顺序合并三个来源:

  1. ReadAvailableStandardAndCustomFormatNames — 标准 MIME 类型及 web 前缀的 W3C 自定义格式;
  2. GetAllAvailableFormats — 其余所有原始平台格式。标准格式会被过滤掉(避免 text/plainelectron application/osclipboard;format="public.utf8-plain-text" 重复暴露同一条内容),其余包装为 osclipboard MIME;macOS 上还在此处探测 find pasteboard 并追加 findtext 伪 MIME;
  3. ReadURL(非 Linux)— 若剪贴板中有书签,则追加 electron application/bookmark

因此 read() 返回的 types 是一份完整的"聚合 MIME 列表",而每个类型的具体负载由 getType(type) 按需从平台剪贴板惰性读取。

clipboard.write(data)

  • data ClipboardItem[] — 通过 new ClipboardItem({ [mime]: payload }) 构造的条目数组

返回 Promise<void>。单次 write() 调用中的所有条目原子地提交到系统剪贴板:

const { clipboard, ClipboardItem, nativeImage } = require('electron')

const png = nativeImage.createFromPath('/path/to/icon.png').toPNG()

async function writeClipboard () {
  await clipboard.write([
    new ClipboardItem({
      'text/plain': 'hello',
      'text/html': '<b>hello</b>',
      'image/png': new Blob([png], { type: 'image/png' }),
      'electron application/bookmark': {
        title: 'Electron',
        url: 'https://electronjs.org'
      }
    })
  ])
}

writeClipboard()

JS 封装层如何保证"先解析、后原子提交"可以清晰地从源码看出:lib/browser/api/clipboard.ts 中的 wrapClipboard 拦截 write,先把每个 ClipboardItemBlob/Promise 负载全部 resolve 成 BufferkToNative),再经一次同步的原生 write 提交。而 C++ 侧 Clipboard::Writeelectron_api_clipboard.cc)用单个 ui::ScopedClipboardWriter 依次写入所有条目,析构时才真正提交——这就是原子性的实现来源。

两个值得注意的运行时约束(均可在测试与源码中验证):

  • write() 参数必须是 ClipboardItem 数组,非数组或非 ClipboardItem 元素会抛出 TypeError
  • clipboard.read() 返回的 ClipboardItem 是"只读"的轻量读取器,不能回传给 write(),必须重新构造新的 ClipboardItem(见 lib/browser/api/clipboard-item.ts[kToNative] 的显式拒绝逻辑)。

clipboard.has(mimetype)

  • mimetype string - 要检查的 MIME 类型

返回 Promise<boolean>,剪贴板中存在该 MIME 数据时 resolve 为 true。要检查原始平台格式(如 public/utf8-plain-text),需使用 osclipboard 自定义格式:

const { clipboard } = require('electron')

async function check () {
  const hasFormat = await clipboard.has('text/html')
  console.log(hasFormat)
  // 'true' 或 'false'
  const rawFormat = 'electron application/osclipboard;format="public/utf8-plain-text"'
  const hasRawFormat = await clipboard.has(rawFormat)
}

check()

实现上,hasread 共用同一条聚合枚举管线(EnumerateAvailableTypes 后做成员判定),所以 hasread 暴露的每一种 MIME——标准类型、web 前缀格式、osclipboard 原始格式、bookmark、macOS findtext——都保持一致的判定结果。

clipboard.clear()

清除剪贴板内容(同步方法)。

安全边界:MIME 键即能力面

clipboard-item.md 文档中有一条重要警告:不要直接用不可信对象构造 ClipboardItem(例如从渲染进程经 IPC 传来的负载)。MIME 键本身是能力面:text/uri-list 会把真实文件引用放到 OS 剪贴板(允许粘贴文件到其他应用),electron application/osclipboard;format=...web 前缀格式会写入原始平台数据。从未经自己审核的数据构建 ClipboardItem 之前,务必对 MIME 类型和负载结构做白名单校验。

Linux 特有:clipboard.selection 属性

在 Linux 上还存在一个 selection 剪贴板(对应 X11 的 PRIMARY 选择),通过 clipboard.selection 子命名空间暴露,它与顶层 clipboard 接口完全同构:

const { clipboard } = require('electron')

async function run () {
  await clipboard.selection.writeText('Example string')
  console.log(await clipboard.selection.readText())
}

run()

两个剪贴板相互独立:通过 clipboard.selection 写入的数据不会影响 clipboard.read() 的返回值(反之亦然)。注意 selection 剪贴板不支持 W3C web 自定义格式。

[!NOTE] clipboard.selection 是只读属性:Linux 上为一个 Clipboard 对象,暴露与顶层一致的 readwritereadTextwriteTexthasclear 方法;其他平台为 undefined

从源码结构看,electron_api_clipboard.cc 中的 Initialize 在 Linux 分支上调用两次 PopulateClipboardObject——分别绑定 kCopyPastekSelection 两个 ui::ClipboardBuffer——同一套 C++ 方法通过绑定期注入不同的 buffer 实例化为两个 JS 对象。JS 层(lib/browser/api/clipboard.ts)仅在 binding.selection 存在时才挂 selection 包装对象。

测试视角的行为验证

仓库中的 spec/api-clipboard-spec.ts 对上述文档声明提供了逐项的行为验证,可作为可信行为参考:

  • image/* 负载通过 clipboard.write + getType 完成 NativeImage 往返(写入后读回的数据 URL 一致);
  • 剪贴板只有文本时,read()types 中不出现任何 image 类型;
  • readText()/writeText() 均返回 Promise,且 Unicode 文本(如 千江有水千江月,万里无云万里天)正确往返;
  • has() 对已写入的标准 MIME、web 前缀自定义 MIME(如 web text/plain+electron-test)、以及 osclipboard 原始格式(public/utf8-plain-text)均能正确返回 true,未写入时返回 false

小结

能力 API 关键约束
纯文本读写 readText() / writeText(text) 返回 Promise;Windows 自动回退 ASCII 读取
多格式原子写入 write(ClipboardItem[]) 所有条目单次原子提交;不能接受 read() 产生的读取器
全量读取 read() 返回单元素 ClipboardItem[]types 为聚合 MIME 列表,负载惰性读取
存在性检查 has(mimetype) read() 的暴露面严格一致;原始格式需用 osclipboard MIME
清空 clear() 同步
Linux 选择剪贴板 clipboard.selection.* 与系统剪贴板隔离;不支持 web 自定义格式

这套 API 的实用价值在于:主进程既能以 W3C 标准形态操作常见文本/HTML/图片/书签/文件列表,又能通过 electron application/osclipboard;format="..." 穿透到 Windows 注册格式、macOS pasteboard 类型等任意平台格式,实现与其他桌面应用的数据互操作——这正是"以 W3C Clipboard API 为骨架、以平台原始格式为逃生舱"的完整落地。

登录后查看全文
热门项目推荐
相关项目推荐