Electron Dock API 深度指南:用 Dock 类掌控 macOS 程序坞图标
2026/9/5 22:48:40 网站建设 项目流程

Electron Dock API 深度指南:用 Dock 类掌控 macOS 程序坞图标

【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron

Electron 的Dock类是 macOS 平台上与系统程序坞(Dock)交互的唯一官方入口,覆盖图标弹跳(bounce)、角标(badge)、显隐控制、自定义 Dock 菜单与图标替换等能力。本篇基于 Electron 仓库中的 Dock API 文档 与对应 C++/TypeScript 实现源码,逐一讲解每个实例方法的参数、返回值与限制条件,并给出 官方测试用例 中可验证的行为依据,帮助你写出既符合 macOS 习惯、又能在 CI 中被测试覆盖的 Dock 交互代码。

Dock 类总览与获取方式

Dock类有一个与其他 Electron 模块不同的特点:它不从'electron'模块直接导出,而是仅作为其他 API 的返回值/属性存在。具体来说:

  • 运行进程:仅在主进程(Main process)中可用;
  • 获取途径:通过app.dock只读属性访问。每个 Electron 应用在 macOS 上只有一个Dock实例,且该属性只在 macOS 上存在,在 Windows / Linux 上app.dockundefined

这一点在源码中有直接印证。在 electron_api_app.cc 中,dock被注册为app对象的一个惰性 getter 属性:

.SetProperty("dock", &App::GetDockAPI)

而 GetDockAPI 负责创建并填充dock对象,将bouncecancelBouncedownloadFinishedsetBadgegetBadgehideshowisVisiblesetMenusetIcon逐一绑定到底层Browser::Get()的同名 C++ 方法上。

因此,跨平台应用中访问 Dock 时必须使用可选链保护:

// Dock 在非 macOS 平台为 undefined app.dock?.setMenu(dockMenu)

这一写法同时出现在 macOS Dock 官方教程 与 类型检查冒烟测试 中,是仓库内的标准实践。

dock.bounce([type])dock.cancelBounce(id):图标弹跳

方法签名

  • typestring (可选) — 只接受criticalinformational默认值为informational
  • 返回Integer— 一个代表该弹跳请求的 ID

两种弹跳模式的行为差异

类型行为
criticalDock 图标持续弹跳,直到应用变为活跃状态,或调用cancelBounce取消请求
informationalDock 图标只弹跳约 1 秒,但请求本身仍然保持激活,直到应用变为活跃状态或被取消

也就是说,两者都可以通过cancelBounce撤销,区别仅在于动画持续时长。

限制与边界条件

  • 应用已聚焦时返回-1:该方法只能在应用未获得焦点时生效,应用处于焦点状态时调用直接返回-1
  • 非法类型同样返回-1:DockBounce 的 C++ 实现 中,只有"critical"映射到Browser::BounceType::kCritical"informational"映射到kInformational,其他任何字符串都落入 else 分支返回-1

spec/api-app-spec.ts 中的测试精确验证了这三条行为:

it('should return -1 for unknown bounce type', () => { expect(app.dock?.bounce('bad type' as any)).to.equal(-1); }); it('should return a positive number for informational type', () => { if (!app.isActive()) { expect(app.dock?.bounce('informational')).to.be.at.least(0); } }); // cancelBounce 不应抛错 app.dock?.cancelBounce(app.dock?.bounce('critical'));

注意测试里用app.isActive()前置判断——与文档中“应用未聚焦才生效”的约束一一对应。

典型用法

const { app } = require('electron') // 触发一次提示性弹跳,并在需要时取消 const id = app.dock?.bounce('informational') if (id !== undefined && id !== -1) { setTimeout(() => app.dock?.cancelBounce(id), 3000) }

dock.downloadFinished(filePath):Downloads 堆栈弹跳

dock.downloadFinished(filePath) * filePath string

filePath指向Downloads 文件夹内部的文件时,系统会让 Dock 中的“Downloads”堆栈图标弹跳一下,提示用户有新的下载完成。这是 macOS 原生下载体验的一部分:

// 在 BrowserWindow 的 session 下载完成后调用 win.webContents.on('did-finish-load', () => { /* ... */ }) app.dock?.downloadFinished('/Users/xxx/Downloads/report.pdf')

从 GetDockAPI 绑定 看,该方法直接转发到Browser::DockDownloadFinished,由系统判断路径是否落在 Downloads 目录并决定是否弹跳。

dock.setBadge(text)dock.getBadge():文字角标

方法说明

  • setBadge(text)text为 string,设置显示在 Dock 图标 badge 区域的字符串
  • getBadge()— 返回当前 Dock 的 badge 字符串(string)。
app.dock?.setBadge('3') console.log(app.dock?.getBadge()) // '3'

测试用例 验证了 set/get 的往返一致性(setBadge('test')getBadge()返回'test'),并在after钩子中用空字符串清掉 badge。

重要前提:通知权限

文档中有一条 IMPORTANT 级提示:必须确保应用拥有显示通知的权限setBadge才能生效。macOS 将 Dock badge 的展示权限与通知授权绑定在一起,在隐私严格模式(如沙盒应用)下,未授权通知会导致 badge 静默不显示。

app.badgeCount的区别

Electron 还有一个跨平台的数字角标 APIapp.badgeCount(在 lib/browser/api/app.ts 中包装了原生getBadgeCount/setBadgeCount),它接受整数并可跨平台使用;而dock.setBadge是 macOS 专属、接受任意字符串的版本。需要跨平台时优先用app.badgeCount,仅在需要 macOS 上显示任意文字(如用户名、状态文本)时用dock.setBadge

dock.hide()dock.show()dock.isVisible():显隐控制

方法返回说明
hide()void隐藏 Dock 图标
show()Promise<void>显示 Dock 图标,图标实际显示时 Promise resolve
isVisible()boolean当前 Dock 图标是否可见
app.dock?.hide() console.log(app.dock?.isVisible()) // false await app.dock?.show() console.log(app.dock?.isVisible()) // true

测试 覆盖了三者组合:hide()isVisible()返回falseshow()返回一个 Promise,且最终 fulfilled 为undefined;show 后isVisible()返回true

两个必须知道的坑

  1. hide()的 1 秒节流:文档明确标注了一个已知问题——距离上一次调用hide()一秒钟之内再次调用会不产生任何效果。官方建议的 workaround 是:两次调用之间至少间隔 1 秒,例如用setTimeout延迟 1100ms 再发起下一次隐藏请求。
  2. hide/show 的顺序依赖:测试源码中的注释 特别说明,dock.show的测试必须排在dock.hide之后运行,以绕开一个 macOS 系统层面的 bug(源自 Electron PR 25269)。这提示我们:在测试或脚本中连续切换显隐时,务必串行等待show()的 Promise 完成,避免依赖调用顺序之外的时序假设。

dock.setMenu(menu)dock.getMenu():自定义 Dock 菜单

方法说明

  • setMenu(menu)menu为 Menu 实例,设置应用的 Dock 菜单;
  • getMenu()— 返回Menu | null,即当前 Dock 菜单。

Dock 菜单通过右键(或 Ctrl 点击)Dock 图标触发。默认情况下,系统会提供一组窗口管理项(显示所有窗口、隐藏应用、在窗口间切换等);一旦调用了setMenu,就替换为应用自定义的菜单。完整的菜单设计建议(如何贴近原生体验)可参考 macOS Dock 教程,其中还引用了 Apple HIG 关于 Dock 菜单的规范。

可运行的设置示例

下面这段代码来自 macos-dock 教程,仓库中也有配套的可运行 fiddle 示例 docs/fiddles/menus/dock-menu:

const { app, BrowserWindow, Menu } = require('electron/main') // dock.setMenu 只能在 'ready' 事件触发之后调用 app.whenReady().then(() => { const dockMenu = Menu.buildFromTemplate([ { label: 'New Window', click: () => { const win = new BrowserWindow() } } // 在此数组中追加更多菜单项 ]) // Dock 在非 macOS 平台为 undefined app.dock?.setMenu(dockMenu) })

两个关键细节:

  • dock.setMenu必须在app.whenReady()之后调用,ready 之前调用不会生效;
  • 与右键上下文菜单不同,Dock 菜单无需手动menu.popup()——Dock 对象自行处理点击事件的呈现,菜单项里直接写click回调即可。

源码级实现:JS 层为什么能getMenu

从源码结构看,getMenu的实现在 JS 层而非 C++ 层。lib/browser/api/app.ts 中:

let dockMenu: Electron.Menu | null = null; if (process.platform === 'darwin') { const setDockMenu = app.dock!.setMenu; app.dock!.setMenu = (menu) => { dockMenu = menu; // JS 层保存引用 setDockMenu(menu); // 再交给原生实现 }; app.dock!.getMenu = () => dockMenu; }

即 JS 层劫持了setMenu,把传入的Menu实例缓存到一个模块级变量中,getMenu直接返回该缓存。这带来两个可推断的实践结论:

  1. Dock 会持有传入菜单的强引用——测试 专门构造了一个临时Menu并强制触发 GC(requestGarbageCollectionForTesting)来验证这一点,因此菜单生命周期由 Electron 管理,不必担心被提前回收;
  2. getMenu()的返回值与setMenu()的入参是同一个 JS 对象,测试中用expect(app.dock?.getMenu()).to.equal(menu)做严格相等断言,初始值(未设置时)为null

dock.setIcon(image):运行时替换 Dock 图标

dock.setIcon(image) * image ([NativeImage](https://link.gitcode.com/i/ceb18b9b779989e0d0182d1f37f8f45f) | string)

设置与当前 Dock 图标关联的图像,参数可以是NativeImage实例,也可以是一个图片文件路径字符串(加载.icns/.png等)。

const { nativeImage } = require('electron') // 方式一:文件路径 app.dock?.setIcon('/path/to/icon.png') // 方式二:NativeImage app.dock?.setIcon(nativeImage.createFromDataURL(dataURL))

测试用例 验证了错误路径的行为:传入不存在的文件时,setIcon会抛出带有描述性信息的异常:

const badPath = path.resolve('I', 'Do', 'Not', 'Exist'); expect(() => { app.dock?.setIcon(badPath); }).to.throw(/Failed to load image from path (.+)/);

这提示在生产代码中应使用try/catch包裹该调用,避免图标加载失败导致主进程异常。注意路径参数不是base64 或Buffer,字符串仅被解释为磁盘路径。

实现架构小结:从 JS 到底层调用链

结合仓库源码,app.dock各方法的完整调用链可以梳理为:

  1. 属性绑定:electron_api_app.cc 将dock注册为app的属性,getter 为App::GetDockAPI
  2. 方法绑定:GetDockAPI 创建dock对象,把 10 个方法分别SetMethodBrowser::Get()->DockXxx系列 C++ 方法(如DockBounceDockSetBadgeTextDockHideDockShowDockSetIcon等),实际 UI 操作由shell/browser/browser_mac.mm中的 Cocoa 实现完成;
  3. JS 层增强:lib/browser/api/app.ts 仅在process.platform === 'darwin'分支中对setMenu/getMenu做包装,补齐菜单引用缓存;
  4. 测试保障:spec/api-app-spec.ts 的dock APIs测试块整体用ifdescribe(process.platform === 'darwin')包裹,保证只在 macOS 上运行,逐项覆盖了菜单、图标、弹跳、badge、显隐的全部行为边界。

常见陷阱与最佳实践清单

陷阱依据建议
非 macOS 平台访问app.dock报错dock属性仅在 darwin 分支注册(app.ts)一律使用app.dock?.xxx可选链
应用有焦点时bounce无效文档 NOTE:聚焦时返回 -1触发前检查app.isActive(),或仅在blur后的场景触发
快速连续hide()失效文档 IMPORTANT:1 秒内重复调用无效两次调用间隔 ≥ 1 秒,setTimeout(..., 1100)
setBadge不显示文档 IMPORTANT:需要通知权限引导用户在系统设置中授权通知,或改用跨平台app.badgeCount
setIcon传错路径崩溃测试验证会抛Failed to load image from path异常try/catch包裹并回退到默认图标
setMenu在 ready 前调用无效macOS Dock 教程 明确标注放到app.whenReady().then(...)中执行
测试中 hide/show 顺序问题测试注释 引用的 macOS 系统 bug串行执行,await app.dock.show()后再断言

参考文档与相关能力

  • API 原始文档:docs/api/dock.md
  • Dock 菜单完整教程:docs/tutorial/macos-dock.md
  • 可运行 fiddle 示例:docs/fiddles/menus/dock-menu
  • 测试套件:spec/api-app-spec.ts
  • 原生绑定实现:shell/browser/api/electron_api_app.cc、JS 层包装 lib/browser/api/app.ts

此外,macOS 上 Dock 还是若干跨平台特性的入口:例如 进度条(Progress Bar) 在 macOS 上以 Dock 图标进度条形式呈现,最近文档(Recent Documents) 也依赖 Dock 菜单展示。涉及这些场景时,可结合对应的教程文档与本文的Dock类方法一起使用。

【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询