首页
/ Electron TouchBarOtherItemsProxy 完全指南:定位 Chromium 继承控件的“其他项代理”

Electron TouchBarOtherItemsProxy 完全指南:定位 Chromium 继承控件的“其他项代理”

2026-09-06 18:02:34作者:管翌锬

Electron(macOS Touch Bar API)中的 TouchBarOtherItemsProxy 是一个特殊占位控件:它并不渲染任何自定义内容,而是为 Chromium 通过系统机制注入 Touch Bar 的“继承自浏览器本身”的触控栏元素(如输入框联想、播放控制等原生 NSTouchBarItem)指定一个存放位置。本文以官方 API 文档为骨架,结合 lib/browser/api/touch-bar.tsshell/browser/ui/cocoa/electron_touch_bar.mmspec/api-touch-bar-spec.ts 中的实现与测试,讲清它的创建方式、单实例约束、默认行为以及如何在自定义 Touch Bar 布局中精确安放这一占位符。

TouchBarOtherItemsProxy 是什么

当应用为 BrowserWindow 设置自定义 Touch Bar 时,Touch Bar 上实际显示的并不只有开发者通过 TouchBar 显式声明的那些按钮、标签和滑杆。Chromium / AppKit 系统中的某些交互(例如文本输入框的联想候选条、媒体相关的原生控件)会以系统托管的 NSTouchBarItem 形式出现在响应链上。这些由 Chromium 继承而来、非 Electron API 显式创建的条目,需要通过一个特殊占位符——Other Items Proxy——才能决定其落位。

TouchBarOtherItemsProxy 类实例化的正是这样一个“其他项代理”。在 AppKit 层面,它对应系统常量 NSTouchBarItemIdentifierOtherItemsProxy,即 Apple 官方文档中所说的 “Other Items Proxy” 标识符:凡是系统想往当前 Touch Bar 里塞的额外条目,都会统一被摆放到该代理所占据的位置。

与核心 TouchBar 体系的关系

TouchBarOtherItemsProxy 是 Electron Touch Bar 控件体系的一员。在 TouchBar 类静态属性 中,它与 TouchBarButtonTouchBarLabelTouchBarColorPickerTouchBarGroupTouchBarPopoverTouchBarSliderTouchBarSpacerTouchBarSegmentedControlTouchBarScrubber 并列,共同通过 TouchBar 上的静态引用对外暴露。

关键属性一览

项目 说明
类名 TouchBarOtherItemsProxy
构造签名 new TouchBarOtherItemsProxy()(无任何 options 参数)
运行进程 Main(主进程)
作用 为 Chromium / AppKit 继承的 Touch Bar 条目指定显示位置
单例约束 每个 TouchBar 只能添加一个实例
默认位置 每个 TouchBar 的条目列表末尾(未显式添加时自动补入)

'electron' 模块获取

该代理类不是 require('electron') 的顶层导出。正确的取用方式是解构 TouchBar 的静态属性:

const { TouchBar, TouchBarButton } = require('electron')
const { TouchBarOtherItemsProxy } = TouchBar

之所以走这条路,是因为在 lib/browser/api/touch-bar.ts 的源码实现中,TouchBar 类以静态字段的方式挂载了全部子类:

static TouchBarButton = TouchBarButton;
static TouchBarSpacer = TouchBarSpacer;
static TouchBarSegmentedControl = TouchBarSegmentedControl;
static TouchBarScrubber = TouchBarScrubber;
static TouchBarOtherItemsProxy = TouchBarOtherItemsProxy;

从 TS 源码可以确认,TouchBarOtherItemsProxy 本身是一个最小化的空壳子类,它继承自抽象基类 TouchBarItem,不携带任何配置项与交互回调(touch-bar.ts):

class TouchBarOtherItemsProxy extends TouchBarItem<null> implements Electron.TouchBarOtherItemsProxy {
  @ImmutableProperty(() => 'other_items_proxy') type!: string;
  onInteraction = null;
}

它的 type 固定为字符串 'other_items_proxy',该标识贯穿 JS 层与原生层,用于识别代理条目。

构造与基本用法

new TouchBarOtherItemsProxy() 不接受参数,直接实例化后放进 TouchBaritems 数组即可:

const { app, BrowserWindow, TouchBar } = require('electron')
const { TouchBarButton, TouchBarLabel, TouchBarOtherItemsProxy } = TouchBar

const touchBar = new TouchBar({
  items: [
    new TouchBarLabel({ label: '我的自定义按钮区' }),
    new TouchBarButton({ label: '确定' }),
    // 告诉系统:Chromium 继承来的控件显示在这个占位处
    new TouchBarOtherItemsProxy()
  ]
})

let window

app.whenReady().then(() => {
  window = new BrowserWindow({ width: 600, height: 400 })
  window.setTouchBar(touchBar)
})

将代理放在 items哪个下标位置,Chromium 继承的条目就会渲染在哪个位置。例如把它放在按钮之前,那么系统注入的控件会出现在 Touch Bar 的左侧;放在中间,则被自定义按钮夹在中央。

核心约束:每个 TouchBar 只能有一个代理

[!NOTE] 每个 TouchBar 只能添加一个 TouchBarOtherItemsProxy 实例。

这条约束既有 JS 层校验,也有对应单元测试覆盖。在 touch-bar.ts 中,TouchBar 构造函数会遍历所有条目并对 type === 'other_items_proxy' 计数:

let hasOtherItemsProxy = false;
const idSet = new Set();
for (const item of items) {
  // 类型合法性校验
  if (!(item instanceof TouchBarItem)) {
    throw new TypeError('Each item must be an instance of TouchBarItem');
  }

  if (item.type === 'other_items_proxy') {
    if (!hasOtherItemsProxy) {
      hasOtherItemsProxy = true;
    } else {
      throw new Error('Must only have one OtherItemsProxy per TouchBar');
    }
  }
  // 同一实例不可重复加入
  if (!idSet.has(item.id)) {
    idSet.add(item.id);
  } else {
    throw new Error('Cannot add a single instance of TouchBarItem multiple times in a TouchBar');
  }
}

也就是说,往同一个 TouchBaritems 里放两个代理会直接抛出运行时错误。对应测试位于 spec/api-touch-bar-spec.ts

it('throws an error if multiple OtherItemProxy items are added', () => {
  expect(() => {
    const touchBar = new TouchBar({ items: [new TouchBarOtherItemsProxy(), new TouchBarOtherItemsProxy()] });
    touchBar.toString();
  }).to.throw('Must only have one OtherItemsProxy per TouchBar');
});

提示:同一 TouchBarItem 实例重复加入 items 也会抛错(测试见 spec/api-touch-bar-spec.ts),因此即使每个 TouchBar 只需要一个代理,也应各自 new 一个实例。

默认行为:自动追加到条目末尾

即使开发者完全不创建代理,Electron 也会默认在每条 TouchBar 的末尾追加一个 Other Items Proxy。这条规则写在原生实现 electron_touch_bar.mm 中——identifiersFromSettings 遍历开发者提供的条目字典,将 type == "other_items_proxy" 的条目翻译成系统常量:

} else if (type == "other_items_proxy") {
  identifier = NSTouchBarItemIdentifierOtherItemsProxy;
  has_other_items_proxy = true;
}

遍历结束后,若发现没有任何代理,则自动在尾部补上系统代理标识符:

if (!has_other_items_proxy)
  [identifiers addObject:NSTouchBarItemIdentifierOtherItemsProxy];

因此有两种等效结果:

  • 不写代理:Chromium 继承条目被追加到 Touch Bar 最右侧;
  • 显式写代理:继承条目出现在你所指定的位置,而自定义条目按顺序排在其两侧。

「默认在末尾」与「可被显式代理改写位置」这两个行为共同解释了为什么代理适合被当作布局中的“系统区锚点”。

源码层面的实现细节

识别符映射与分层 Touch Bar

每个 TouchBar 最终在原生层被构造成一个 NSTouchBar,其 defaultItemIdentifiers 即为上述映射后的标识符数组(electron_touch_bar.mm)。由于代理条目并非可交互的自定义控件,makeItemForIdentifier: 中没有任何分支会为 NSTouchBarItemIdentifierOtherItemsProxy 构造真实 item——它只作为位置标记由 AppKit 识别并填入系统条目。

值得注意的是分层(Group/Popover)场景下代理的处理存在差异:

  • makeGroupForID: 在手工组装 NSGroupTouchBarItem显式跳过 NSTouchBarItemIdentifierOtherItemsProxy 标识符,避免将其作为普通 item 生成;
  • updateGroup: 仍经由 identifiersFromSettings 重建组内 Touch Bar,因此代理的“自动末尾补齐”逻辑在组内同样生效。

从源码结构可以推断:组内与顶层 Touch Bar 各自独立决定是否追加代理,这条约束是逐 TouchBar 实例生效的。

与“继承元素”的关系再强调

Apple 的 NSTouchBarItemIdentifierOtherItemsProxy 之所以命名带 “Other Items”,是指那些不属于当前 Touch Bar defaultItemIdentifiers 显式声明、却由 AppKit/Chromium 视上下文自动提供(例如系统级功能)的条目。Electron 将这套机制原样桥接:代理即“Chromium 条目与自定义条目同框排版”的唯一衔接点。

完整可运行示例

将下面的代码保存为 touchbar-proxy.js(macOS + Touch Bar 环境,含模拟器)并运行:

const { app, BrowserWindow, TouchBar } = require('electron')

// 注意:代理并非顶层导出,必须从 TouchBar 静态属性解构
const { TouchBarButton, TouchBarLabel, TouchBarOtherItemsProxy } = TouchBar

app.whenReady().then(() => {
  const window = new BrowserWindow({ width: 500, height: 300 })

  // 显式把“其他项代理”夹在两组自定义按钮中间,
  // 让 Chromium 注入的控件呈现在 Touch Bar 中部
  const touchBar = new TouchBar({
    items: [
      new TouchBarLabel({ label: '左侧' }),
      new TouchBarButton({ label: '上一页' }),
      new TouchBarOtherItemsProxy(),
      new TouchBarLabel({ label: '右侧' }),
      new TouchBarButton({ label: '下一页' })
    ]
  })

  window.setTouchBar(touchBar)
})

运行方式与 Touch Bar 的其他示例一致(详见 touch-bar.md 中的示例章节):

  1. 将文件保存到本机;
  2. 安装 Electron:npm install electron
  3. 执行:./node_modules/.bin/electron touchbar-proxy.js

如果改用无代理版本(仅保留前后两组自定义按钮),Chromium 继承条目会被自动排到整条 Touch Bar 的最右端——两者对比即可直观感受到显式代理对“继承区位置”的控制力。

参考文档

提醒:与整个 Touch Bar API 一致,该能力属于 macOS 专有,且 Touch Bar API 目前仍标注为实验性,可能在未来的 Electron 版本中调整或移除(参见 touch-bar.md 的说明),设计新界面时应预留在回归测试上的余量。

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