desktop-multiwindow-electron

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Electron Multi-Window Patterns

Electron 多窗口模式

Quick Guide: Use
BrowserWindow
for single-view windows. Use
BaseWindow
+
WebContentsView
for multi-view layouts (tabs, split panes, panels).
BrowserView
is deprecated since Electron 30 -- migrate to
WebContentsView
. Track windows with a
Map<string, BrowserWindow>
registry. Communicate between windows via the main process or
MessagePort
for direct renderer-to-renderer channels. Persist window bounds manually or use the upcoming
windowStatePersistence
API. Always close
webContents
explicitly when using
BaseWindow
-- unlike
BrowserWindow
, it does not auto-cleanup.

<critical_requirements>
快速指南: 单视图窗口使用
BrowserWindow
。多视图布局(标签页、拆分窗格、面板)使用
BaseWindow
+
WebContentsView
BrowserView
自Electron 30起已被弃用——请迁移至
WebContentsView
。使用
Map<string, BrowserWindow>
注册表跟踪窗口。通过主进程或
MessagePort
实现窗口间通信,后者可建立直接的渲染器到渲染器通道。手动持久化窗口边界,或使用即将推出的
windowStatePersistence
API。使用
BaseWindow
时必须显式关闭
webContents
——与
BrowserWindow
不同,它不会自动清理资源。

<critical_requirements>

CRITICAL: Before Using This Skill

重要提示:使用本技能前须知

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type
, named constants)
(You MUST close
webContents
explicitly when destroying a
BaseWindow
-- it does not auto-cleanup like
BrowserWindow
, causing memory leaks)
(You MUST use
WebContentsView
instead of
BrowserView
--
BrowserView
is deprecated since Electron 30)
(You MUST route all inter-window communication through the main process or
MessagePort
-- never access another window's renderer directly)
(You MUST validate that saved window bounds are on a visible display before restoring -- monitors may disconnect between sessions)
</critical_requirements>

Auto-detection: multi-window, BaseWindow, WebContentsView, BrowserView migration, contentView, addChildView, removeChildView, parent window, child window, modal window, window registry, MessagePort, MessageChannelMain, window state persistence, screen API, workArea, getAllDisplays, split view, tabs, panels, window lifecycle, ready-to-show, window-all-closed
When to use:
  • Creating multi-view layouts (tabs, split panes, embedded panels) with BaseWindow + WebContentsView
  • Managing multiple BrowserWindow instances with a window registry
  • Migrating from deprecated BrowserView to WebContentsView
  • Setting up parent/child or modal windows
  • Communicating between windows (via main process relay or MessagePort)
  • Persisting and restoring window position, size, and display state
  • Placing windows on specific monitors using the screen API
When NOT to use:
  • Single-window apps with one view (
    BrowserWindow
    is sufficient on its own)
  • Choosing a UI framework for the renderer
  • IPC patterns between main and a single renderer (basic IPC is outside multi-window scope)
  • Styling or layout within a single renderer
Key patterns covered:
  • BaseWindow + WebContentsView for multi-view layouts
  • BrowserView to WebContentsView migration
  • Window lifecycle events (ready-to-show, close, closed)
  • Parent/child and modal windows
  • Window registry with Map-based tracking
  • Inter-window communication via main process and MessagePort
  • Window state persistence (bounds, maximized, fullscreen)
  • Multi-monitor placement with screen API

<philosophy>
所有代码必须遵循CLAUDE.md中的项目规范(短横线命名、命名导出、导入顺序、
import type
、命名常量)
(销毁
BaseWindow
时必须显式关闭
webContents
——它不像
BrowserWindow
那样自动清理,会导致内存泄漏)
(必须使用
WebContentsView
替代
BrowserView
——
BrowserView
自Electron 30起已被弃用)
(所有窗口间通信必须通过主进程或
MessagePort
进行——绝不能直接访问其他窗口的渲染器)
(恢复窗口边界前必须验证其是否在可见显示器上——会话期间显示器可能断开连接)
</critical_requirements>

自动检测关键词: multi-window, BaseWindow, WebContentsView, BrowserView迁移, contentView, addChildView, removeChildView, 父窗口, 子窗口, 模态窗口, 窗口注册表, MessagePort, MessageChannelMain, 窗口状态持久化, screen API, workArea, getAllDisplays, 拆分视图, 标签页, 面板, 窗口生命周期, ready-to-show, window-all-closed
适用场景:
  • 使用BaseWindow + WebContentsView创建多视图布局(标签页、拆分窗格、嵌入面板)
  • 通过窗口注册表管理多个BrowserWindow实例
  • 从已弃用的BrowserView迁移至WebContentsView
  • 设置父/子窗口或模态窗口
  • 实现窗口间通信(通过主进程转发或MessagePort)
  • 持久化和恢复窗口位置、大小及显示状态
  • 使用screen API将窗口放置在特定显示器上
不适用场景:
  • 单视图单窗口应用(
    BrowserWindow
    已足够)
  • 为渲染器选择UI框架
  • 主进程与单个渲染器之间的基础IPC模式(基础IPC不属于多窗口范畴)
  • 单个渲染器内的样式或布局
涵盖的核心模式:
  • 基于BaseWindow + WebContentsView的多视图布局
  • BrowserView到WebContentsView的迁移
  • 窗口生命周期事件(ready-to-show、close、closed)
  • 父/子窗口与模态窗口
  • 基于Map的窗口注册表跟踪
  • 通过主进程和MessagePort实现窗口间通信
  • 窗口状态持久化(边界、最大化、全屏)
  • 基于screen API的多显示器窗口放置

<philosophy>

Philosophy

设计理念

Electron's window model has two tiers. BrowserWindow is the simple path: one window, one web view, automatic lifecycle management. BaseWindow + WebContentsView is the flexible path: one window shell containing multiple independently managed web views, each with its own renderer process and preload script.
The key architectural decision: use BrowserWindow for single-view windows, BaseWindow for multi-view layouts. BaseWindow trades convenience for control -- you manage view lifecycle, bounds, and cleanup explicitly.
When to use BaseWindow + WebContentsView:
  • Tab bars, split editors, preview panels, embedded browser views
  • Any layout where multiple independent web pages share one OS window
  • Applications migrating from deprecated BrowserView
When NOT to use BaseWindow:
  • Single-view windows (BrowserWindow is simpler and handles cleanup automatically)
  • Windows that only need a toolbar or status bar (a single BrowserWindow with HTML layout is sufficient)
</philosophy>
<patterns>
Electron的窗口模型分为两个层级。BrowserWindow是简单方案:一个窗口对应一个Web视图,自动管理生命周期。BaseWindow + WebContentsView是灵活方案:一个窗口外壳包含多个独立管理的Web视图,每个视图拥有自己的渲染进程和预加载脚本。
关键架构决策:单视图窗口使用BrowserWindow,多视图布局使用BaseWindow。BaseWindow以便利性换取控制力——你需要显式管理视图的生命周期、边界和资源清理。
何时使用BaseWindow + WebContentsView:
  • 标签栏、拆分编辑器、预览面板、嵌入浏览器视图
  • 多个独立网页共享一个操作系统窗口的任何布局
  • 从已弃用的BrowserView迁移的应用
何时不使用BaseWindow:
  • 单视图窗口(BrowserWindow更简单且自动处理资源清理)
  • 仅需工具栏或状态栏的窗口(单个带HTML布局的BrowserWindow已足够)
</philosophy>
<patterns>

Core Patterns

核心模式

Pattern 1: BaseWindow with WebContentsView

模式1:BaseWindow搭配WebContentsView

BaseWindow is the window shell; WebContentsView instances are the content. Each view has its own renderer process and preload script.
javascript
const { BaseWindow, WebContentsView } = require("electron");

const win = new BaseWindow({ width: 1200, height: 800 });

const sidebar = new WebContentsView({
  webPreferences: { preload: path.join(__dirname, "preload.js") },
});
const main = new WebContentsView({
  webPreferences: { preload: path.join(__dirname, "preload.js") },
});

win.contentView.addChildView(sidebar);
win.contentView.addChildView(main);

const SIDEBAR_WIDTH = 250;
sidebar.setBounds({ x: 0, y: 0, width: SIDEBAR_WIDTH, height: 800 });
main.setBounds({ x: SIDEBAR_WIDTH, y: 0, width: 950, height: 800 });

sidebar.webContents.loadFile("sidebar.html");
main.webContents.loadFile("main.html");
Key point: Each WebContentsView needs its own
webPreferences
and preload script. BaseWindow has no
webContents
of its own. See examples/core.md for complete split-view and tab examples with resize handling.

BaseWindow是窗口外壳;WebContentsView实例是内容载体。每个视图拥有自己的渲染进程和预加载脚本。
javascript
const { BaseWindow, WebContentsView } = require("electron");

const win = new BaseWindow({ width: 1200, height: 800 });

const sidebar = new WebContentsView({
  webPreferences: { preload: path.join(__dirname, "preload.js") },
});
const main = new WebContentsView({
  webPreferences: { preload: path.join(__dirname, "preload.js") },
});

win.contentView.addChildView(sidebar);
win.contentView.addChildView(main);

const SIDEBAR_WIDTH = 250;
sidebar.setBounds({ x: 0, y: 0, width: SIDEBAR_WIDTH, height: 800 });
main.setBounds({ x: SIDEBAR_WIDTH, y: 0, width: 950, height: 800 });

sidebar.webContents.loadFile("sidebar.html");
main.webContents.loadFile("main.html");
重点: 每个WebContentsView需要自己的
webPreferences
和预加载脚本。BaseWindow自身没有
webContents
。完整的拆分视图和标签页示例(包含大小调整处理)请查看examples/core.md

Pattern 2: BrowserView to WebContentsView Migration

模式2:BrowserView到WebContentsView的迁移

BrowserView is deprecated since Electron 30. Migration is straightforward -- constructors have the same shape.
Deprecated (BrowserView)Replacement (WebContentsView)
new BrowserView(opts)
new WebContentsView(opts)
win.addBrowserView(view)
win.contentView.addChildView(view)
win.removeBrowserView(view)
win.contentView.removeChildView(view)
win.getBrowserViews()
win.contentView.children
win.setTopBrowserView(view)
win.contentView.addChildView(view)
(re-adding reorders)
view.setAutoResize({ vertical: true })
Manual resize via
win.on("resize", ...)
+
setBounds()
Gotcha: WebContentsView defaults to a white background; BrowserView defaulted to transparent. Set
view.setBackgroundColor("#00000000")
for transparency.
See examples/core.md for the complete migration pattern with auto-resize replacement.

BrowserView自Electron 30起已被弃用。迁移过程简单——构造函数结构一致。
已弃用(BrowserView)替代方案(WebContentsView)
new BrowserView(opts)
new WebContentsView(opts)
win.addBrowserView(view)
win.contentView.addChildView(view)
win.removeBrowserView(view)
win.contentView.removeChildView(view)
win.getBrowserViews()
win.contentView.children
win.setTopBrowserView(view)
win.contentView.addChildView(view)
(重新添加会调整顺序)
view.setAutoResize({ vertical: true })
通过
win.on("resize", ...)
+
setBounds()
手动调整大小
注意: WebContentsView默认背景为白色;而BrowserView默认背景为透明。如需透明背景,请设置
view.setBackgroundColor("#00000000")
完整的迁移模式(包含自动调整大小的替代实现)请查看examples/core.md

Pattern 3: Window Lifecycle Events

模式3:窗口生命周期事件

Window events fire in a predictable order. Use
ready-to-show
to prevent visual flash,
close
to intercept (confirmations, state saving),
closed
for final cleanup.
javascript
const win = new BrowserWindow({ show: false });

// Prevent white flash -- show only after first paint
win.once("ready-to-show", () => {
  win.show();
});

// Intercept close for unsaved changes
win.on("close", (event) => {
  if (hasUnsavedChanges()) {
    event.preventDefault();
    promptSaveDialog(win);
  }
});

// Final cleanup after window is gone
win.on("closed", () => {
  windowRegistry.delete(win.id);
});
Gotcha:
ready-to-show
fires on
BrowserWindow
(which owns a
webContents
) but NOT on
BaseWindow
(which has no
webContents
). For BaseWindow, listen on the individual WebContentsView's
webContents
instead:
view.webContents.once("ready-to-show", ...)
.
See examples/core.md for the full lifecycle sequence and BaseWindow workaround.

窗口事件按可预测的顺序触发。使用
ready-to-show
避免视觉闪烁,使用
close
拦截操作(如确认提示、状态保存),使用
closed
进行最终清理。
javascript
const win = new BrowserWindow({ show: false });

// 防止白屏闪烁——首次绘制完成后再显示
win.once("ready-to-show", () => {
  win.show();
});

// 拦截关闭操作以处理未保存的更改
win.on("close", (event) => {
  if (hasUnsavedChanges()) {
    event.preventDefault();
    promptSaveDialog(win);
  }
});

// 窗口关闭后的最终清理
win.on("closed", () => {
  windowRegistry.delete(win.id);
});
注意:
ready-to-show
事件会在
BrowserWindow
(拥有
webContents
)上触发,但不会在
BaseWindow
(无
webContents
)上触发。对于BaseWindow,请监听各个WebContentsView的
webContents
事件:
view.webContents.once("ready-to-show", ...)
完整的生命周期序列和BaseWindow解决方案请查看examples/core.md

Pattern 4: Parent/Child and Modal Windows

模式4:父/子窗口与模态窗口

Child windows always appear above their parent. Modal windows additionally disable the parent until closed.
javascript
const parent = new BrowserWindow({ width: 800, height: 600 });

// Child window: always on top of parent, non-blocking
const child = new BrowserWindow({
  parent,
  width: 400,
  height: 300,
});

// Modal window: blocks parent interaction
const modal = new BrowserWindow({
  parent,
  modal: true,
  show: false,
  width: 500,
  height: 400,
});
modal.once("ready-to-show", () => modal.show());
Platform behavior: On macOS, modal child windows display as sheets attached to the parent. On Windows/Linux, they display as separate windows with the parent disabled.
See examples/core.md for confirmation dialogs and settings windows.

子窗口始终显示在父窗口上方。模态窗口还会在关闭前禁用父窗口。
javascript
const parent = new BrowserWindow({ width: 800, height: 600 });

// 子窗口:始终位于父窗口上方,非阻塞
const child = new BrowserWindow({
  parent,
  width: 400,
  height: 300,
});

// 模态窗口:阻塞父窗口交互
const modal = new BrowserWindow({
  parent,
  modal: true,
  show: false,
  width: 500,
  height: 400,
});
modal.once("ready-to-show", () => modal.show());
平台行为: 在macOS上,模态子窗口显示为附加到父窗口的表单;在Windows/Linux上,它们显示为独立窗口且父窗口被禁用。
确认对话框和设置窗口的示例请查看examples/core.md

Pattern 5: Window Registry

模式5:窗口注册表

Track all open windows with a Map for reliable lookup, messaging, and cleanup.
javascript
const windowRegistry = new Map();

function createWindow(id, options) {
  const win = new BrowserWindow(options);
  windowRegistry.set(id, win);

  win.on("closed", () => {
    windowRegistry.delete(id);
  });

  return win;
}

// Find and focus a window by ID
function focusWindow(id) {
  const win = windowRegistry.get(id);
  if (!win) return;
  if (win.isMinimized()) win.restore();
  win.focus();
}
Key point: Use string IDs (not window objects) as keys. Clean up on
closed
event. The registry enables "show existing or create new" patterns for settings, about, and preferences windows.
See examples/core.md for the full singleton window pattern.

使用Map跟踪所有打开的窗口,以便可靠查找、消息传递和资源清理。
javascript
const windowRegistry = new Map();

function createWindow(id, options) {
  const win = new BrowserWindow(options);
  windowRegistry.set(id, win);

  win.on("closed", () => {
    windowRegistry.delete(id);
  });

  return win;
}

// 通过ID查找并聚焦窗口
function focusWindow(id) {
  const win = windowRegistry.get(id);
  if (!win) return;
  if (win.isMinimized()) win.restore();
  win.focus();
}
重点: 使用字符串ID(而非窗口对象)作为键。在
closed
事件中清理注册表。注册表支持“显示现有窗口或创建新窗口”模式,适用于设置、关于和偏好设置窗口。
完整的单例窗口模式请查看examples/core.md

Pattern 6: Inter-Window Communication

模式6:窗口间通信

Two patterns: main process relay for simple messages, MessagePort for direct high-frequency renderer-to-renderer channels.
javascript
// Pattern A: Main process relay
ipcMain.on("message-to-window", (_event, targetId, channel, data) => {
  const target = windowRegistry.get(targetId);
  if (target) target.webContents.send(channel, data);
});

// Pattern B: MessagePort -- direct renderer-to-renderer
const { MessageChannelMain } = require("electron");
const { port1, port2 } = new MessageChannelMain();

window1.webContents.postMessage("port", null, [port1]);
window2.webContents.postMessage("port", null, [port2]);
Key point: Main process relay is simpler but adds latency. MessagePort creates a direct channel after initial setup. Use
postMessage
(not
send
) to transfer ports.
See examples/inter-window-communication.md for complete examples of both patterns.

两种模式:主进程转发适用于简单消息,MessagePort适用于高频直接的渲染器到渲染器通道。
javascript
// 模式A:主进程转发
ipcMain.on("message-to-window", (_event, targetId, channel, data) => {
  const target = windowRegistry.get(targetId);
  if (target) target.webContents.send(channel, data);
});

// 模式B:MessagePort——直接渲染器到渲染器通信
const { MessageChannelMain } = require("electron");
const { port1, port2 } = new MessageChannelMain();

window1.webContents.postMessage("port", null, [port1]);
window2.webContents.postMessage("port", null, [port2]);
重点: 主进程转发更简单但会增加延迟。MessagePort在初始设置后创建直接通道。使用
postMessage
(而非
send
)传输端口。
两种模式的完整示例请查看examples/inter-window-communication.md

Pattern 7: Window State Persistence

模式7:窗口状态持久化

Save and restore window bounds, maximized state, and display information across sessions.
javascript
function saveWindowState(win, stateFile) {
  const bounds = win.getBounds();
  const state = {
    bounds,
    isMaximized: win.isMaximized(),
    isFullScreen: win.isFullScreen(),
    displayId: screen.getDisplayMatching(bounds).id,
  };
  fs.writeFileSync(stateFile, JSON.stringify(state));
}
Key point: Always validate restored bounds against current displays -- a monitor may have been disconnected. Fall back to the primary display's work area if the saved display is unavailable.
See examples/core.md for the complete save/restore cycle with multi-monitor validation.

跨会话保存和恢复窗口边界、最大化状态及显示信息。
javascript
function saveWindowState(win, stateFile) {
  const bounds = win.getBounds();
  const state = {
    bounds,
    isMaximized: win.isMaximized(),
    isFullScreen: win.isFullScreen(),
    displayId: screen.getDisplayMatching(bounds).id,
  };
  fs.writeFileSync(stateFile, JSON.stringify(state));
}
重点: 恢复窗口边界前必须验证其是否与当前显示器匹配——显示器可能已断开连接。如果保存的显示器不可用,回退到主显示器的工作区域。
完整的保存/恢复周期(包含多显示器验证)请查看examples/core.md

Pattern 8: Multi-Monitor Placement

模式8:多显示器窗口放置

Use the
screen
API to enumerate displays, find work areas, and place windows on specific monitors.
javascript
const { screen } = require("electron");

const displays = screen.getAllDisplays();
const externalDisplay = displays.find(
  (d) => d.bounds.x !== 0 || d.bounds.y !== 0,
);

if (externalDisplay) {
  const win = new BrowserWindow({
    x: externalDisplay.bounds.x,
    y: externalDisplay.bounds.y,
    width: 800,
    height: 600,
  });
}
Key point: Use
workArea
(not
bounds
) to avoid placing windows behind taskbars/docks. Listen for
display-added
,
display-removed
, and
display-metrics-changed
events to react to monitor changes at runtime.
See examples/core.md for display enumeration and safe placement.
</patterns>
<decision_framework>
使用
screen
API枚举显示器、查找工作区域,并将窗口放置在特定显示器上。
javascript
const { screen } = require("electron");

const displays = screen.getAllDisplays();
const externalDisplay = displays.find(
  (d) => d.bounds.x !== 0 || d.bounds.y !== 0,
);

if (externalDisplay) {
  const win = new BrowserWindow({
    x: externalDisplay.bounds.x,
    y: externalDisplay.bounds.y,
    width: 800,
    height: 600,
  });
}
重点: 使用
workArea
(而非
bounds
)避免窗口被任务栏/ dock遮挡。监听
display-added
display-removed
display-metrics-changed
事件,以便在运行时响应显示器变化。
显示器枚举和安全放置的示例请查看examples/core.md
</patterns>
<decision_framework>

Decision Framework

决策框架

Window Type Selection

窗口类型选择

How many web views does this window need?
+-- One full-size view?
|   +-- BrowserWindow (simpler, automatic lifecycle)
+-- Multiple views (tabs, split pane, sidebar + content)?
|   +-- BaseWindow + WebContentsView
+-- Frameless window with custom layout?
    +-- One view? -> BrowserWindow with frame: false
    +-- Multiple views? -> BaseWindow with frame: false
该窗口需要多少个Web视图?
+-- 一个全屏视图?
|   +-- BrowserWindow(更简单,自动管理生命周期)
+-- 多个视图(标签页、拆分窗格、侧边栏+内容)?
|   +-- BaseWindow + WebContentsView
+-- 无边框窗口自定义布局?
    +-- 一个视图? -> 带frame: false的BrowserWindow
    +-- 多个视图? -> 带frame: false的BaseWindow

Inter-Window Communication

窗口间通信方式

How should windows communicate?
+-- Simple, infrequent messages?
|   +-- Main process relay (ipcMain/webContents.send)
+-- High-frequency or streaming data?
|   +-- MessagePort (direct renderer-to-renderer after setup)
+-- Shared state across windows?
    +-- Main process as single source of truth, push updates via IPC
窗口应如何通信?
+-- 简单、低频消息?
|   +-- 主进程转发(ipcMain/webContents.send)
+-- 高频或流式数据?
|   +-- MessagePort(初始设置后直接渲染器到渲染器通信)
+-- 跨窗口共享状态?
    +-- 主进程作为单一数据源,通过IPC推送更新

Window Relationship

窗口关系

What is the relationship between windows?
+-- Independent (editor, browser tabs)?
|   +-- Separate BrowserWindow instances, window registry
+-- Always above parent (inspector, palette)?
|   +-- Child window: { parent: parentWin }
+-- Blocks parent (save dialog, settings confirmation)?
    +-- Modal window: { parent: parentWin, modal: true }
</decision_framework>

Detailed resources:
  • examples/core.md - BaseWindow + WebContentsView, lifecycle, registry, state persistence, multi-monitor
  • examples/inter-window-communication.md - Main process relay, MessagePort, typed channels
  • reference.md - API quick-reference tables, migration checklist, event order

<red_flags>
窗口之间是什么关系?
+-- 独立窗口(编辑器、浏览器标签页)?
|   +-- 独立的BrowserWindow实例,搭配窗口注册表
+-- 始终位于父窗口上方(检查器、面板)?
|   +-- 子窗口:{ parent: parentWin }
+-- 阻塞父窗口(保存对话框、设置确认)?
    +-- 模态窗口:{ parent: parentWin, modal: true }
</decision_framework>

详细资源:
  • examples/core.md - BaseWindow + WebContentsView、生命周期、注册表、状态持久化、多显示器
  • examples/inter-window-communication.md - 主进程转发、MessagePort、类型化通道
  • reference.md - API快速参考表、迁移清单、事件顺序

<red_flags>

RED FLAGS

警示事项

Critical Issues:
  • Not closing
    webContents
    when destroying a
    BaseWindow
    -- causes memory leaks (BrowserWindow auto-cleans, BaseWindow does not)
  • Using deprecated
    BrowserView
    instead of
    WebContentsView
    -- deprecated since Electron 30
  • Direct renderer-to-renderer communication bypassing the main process -- violates process isolation
  • Restoring window bounds without checking if the target display still exists -- window appears off-screen
Architecture Issues:
  • Using
    BaseWindow
    for single-view windows -- unnecessary complexity, use
    BrowserWindow
  • Using
    BrowserView.setAutoResize()
    patterns with
    WebContentsView
    -- no equivalent exists, use manual resize listeners
  • Storing
    BrowserWindow
    objects as Map values without cleaning up on
    closed
    -- stale references
  • Creating child windows from the renderer process -- always create from main
Common Mistakes:
  • Expecting
    ready-to-show
    on
    BaseWindow
    -- it fires on
    BrowserWindow
    only; for
    BaseWindow
    , listen on
    view.webContents
  • Forgetting that
    WebContentsView
    defaults to white background (BrowserView defaulted to transparent) -- set
    "#00000000"
    explicitly
  • Using
    ipcRenderer.send()
    to transfer
    MessagePort
    -- only
    postMessage()
    can transfer ports
  • Placing windows using
    display.bounds
    instead of
    display.workArea
    -- window ends up behind taskbar/dock
  • Not handling
    display-removed
    event -- window references a disconnected monitor
Gotchas & Edge Cases:
  • Re-adding a child view with
    addChildView()
    moves it to the top of the z-order -- this is intentional, not a bug
  • setBounds()
    coordinates are relative to the parent view, not the screen
  • On macOS, modal windows display as sheets attached to the parent window
  • win.getBounds()
    returns the outer frame bounds on some platforms -- content area may differ
  • Each
    WebContentsView
    runs its own renderer process -- resource usage scales linearly with view count
  • MessagePortMain
    requires calling
    .start()
    before messages are delivered -- they queue until then
</red_flags>

<critical_reminders>
严重问题:
  • 销毁BaseWindow时未关闭webContents——导致内存泄漏(BrowserWindow自动清理,BaseWindow不会)
  • 使用已弃用的BrowserView而非WebContentsView——自Electron 30起已弃用
  • 绕过主进程直接进行渲染器到渲染器通信——违反进程隔离原则
  • 恢复窗口边界前未检查目标显示器是否存在——窗口可能显示在屏幕外
架构问题:
  • 为单视图窗口使用BaseWindow——不必要的复杂性,应使用BrowserWindow
  • 为WebContentsView使用BrowserView.setAutoResize()模式——无等效功能,应使用手动大小调整监听器
  • 将BrowserWindow对象存储为Map值但未在closed事件中清理——存在过期引用
  • 从渲染器进程创建子窗口——始终从主进程创建
常见错误:
  • 期望BaseWindow触发ready-to-show事件——仅BrowserWindow会触发;对于BaseWindow,请监听view.webContents
  • 忘记WebContentsView默认背景为白色(BrowserView默认透明)——需显式设置"#00000000"
  • 使用ipcRenderer.send()传输MessagePort——只有postMessage()可以传输端口
  • 使用display.bounds而非display.workArea放置窗口——窗口会被任务栏/dock遮挡
  • 未处理display-removed事件——窗口引用已断开的显示器
注意事项与边缘情况:
  • 使用addChildView()重新添加子视图会将其移至z-order顶部——这是有意设计,而非bug
  • setBounds()坐标相对于父视图,而非屏幕
  • 在macOS上,模态窗口显示为附加到父窗口的表单
  • win.getBounds()在某些平台上返回外框边界——内容区域可能不同
  • 每个WebContentsView运行自己的渲染进程——资源使用随视图数量线性增长
  • MessagePortMain需要调用.start()才能传递消息——消息会在调用前排队
</red_flags>

<critical_reminders>

CRITICAL REMINDERS

重要提醒

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type
, named constants)
(You MUST close
webContents
explicitly when destroying a
BaseWindow
-- it does not auto-cleanup like
BrowserWindow
, causing memory leaks)
(You MUST use
WebContentsView
instead of
BrowserView
--
BrowserView
is deprecated since Electron 30)
(You MUST route all inter-window communication through the main process or
MessagePort
-- never access another window's renderer directly)
(You MUST validate that saved window bounds are on a visible display before restoring -- monitors may disconnect between sessions)
Failure to follow these rules will cause memory leaks, deprecated API warnings, broken inter-process communication, or off-screen windows.
</critical_reminders>
所有代码必须遵循CLAUDE.md中的项目规范(短横线命名、命名导出、导入顺序、
import type
、命名常量)
(销毁
BaseWindow
时必须显式关闭
webContents
——它不像
BrowserWindow
那样自动清理,会导致内存泄漏)
(必须使用
WebContentsView
替代
BrowserView
——
BrowserView
自Electron 30起已被弃用)
(所有窗口间通信必须通过主进程或
MessagePort
进行——绝不能直接访问其他窗口的渲染器)
(恢复窗口边界前必须验证其是否在可见显示器上——会话期间显示器可能断开连接)
不遵守这些规则会导致内存泄漏、已弃用API警告、进程间通信故障或窗口显示在屏幕外。
</critical_reminders>