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.dock为undefined。
这一点在源码中有直接印证。在 electron_api_app.cc 中,dock被注册为app对象的一个惰性 getter 属性:
.SetProperty("dock", &App::GetDockAPI)而 GetDockAPI 负责创建并填充dock对象,将bounce、cancelBounce、downloadFinished、setBadge、getBadge、hide、show、isVisible、setMenu、setIcon逐一绑定到底层Browser::Get()的同名 C++ 方法上。
因此,跨平台应用中访问 Dock 时必须使用可选链保护:
// Dock 在非 macOS 平台为 undefined app.dock?.setMenu(dockMenu)这一写法同时出现在 macOS Dock 官方教程 与 类型检查冒烟测试 中,是仓库内的标准实践。
dock.bounce([type])与dock.cancelBounce(id):图标弹跳
方法签名
typestring (可选) — 只接受critical或informational,默认值为informational- 返回
Integer— 一个代表该弹跳请求的 ID
两种弹跳模式的行为差异
| 类型 | 行为 |
|---|---|
critical | Dock 图标持续弹跳,直到应用变为活跃状态,或调用cancelBounce取消请求 |
informational | Dock 图标只弹跳约 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()返回false;show()返回一个 Promise,且最终 fulfilled 为undefined;show 后isVisible()返回true。
两个必须知道的坑
hide()的 1 秒节流:文档明确标注了一个已知问题——距离上一次调用hide()一秒钟之内再次调用会不产生任何效果。官方建议的 workaround 是:两次调用之间至少间隔 1 秒,例如用setTimeout延迟 1100ms 再发起下一次隐藏请求。- 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直接返回该缓存。这带来两个可推断的实践结论:
- Dock 会持有传入菜单的强引用——测试 专门构造了一个临时
Menu并强制触发 GC(requestGarbageCollectionForTesting)来验证这一点,因此菜单生命周期由 Electron 管理,不必担心被提前回收; 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各方法的完整调用链可以梳理为:
- 属性绑定:electron_api_app.cc 将
dock注册为app的属性,getter 为App::GetDockAPI; - 方法绑定:GetDockAPI 创建
dock对象,把 10 个方法分别SetMethod到Browser::Get()->DockXxx系列 C++ 方法(如DockBounce、DockSetBadgeText、DockHide、DockShow、DockSetIcon等),实际 UI 操作由shell/browser/browser_mac.mm中的 Cocoa 实现完成; - JS 层增强:lib/browser/api/app.ts 仅在
process.platform === 'darwin'分支中对setMenu/getMenu做包装,补齐菜单引用缓存; - 测试保障: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),仅供参考