desktop-multiwindow-electron
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseElectron Multi-Window Patterns
Electron 多窗口模式
Quick Guide: Usefor single-view windows. UseBrowserWindow+BaseWindowfor multi-view layouts (tabs, split panes, panels).WebContentsViewis deprecated since Electron 30 -- migrate toBrowserView. Track windows with aWebContentsViewregistry. Communicate between windows via the main process orMap<string, BrowserWindow>for direct renderer-to-renderer channels. Persist window bounds manually or use the upcomingMessagePortAPI. Always closewindowStatePersistenceexplicitly when usingwebContents-- unlikeBaseWindow, it does not auto-cleanup.BrowserWindow
<critical_requirements>
快速指南: 单视图窗口使用。多视图布局(标签页、拆分窗格、面板)使用BrowserWindow+BaseWindow。WebContentsView自Electron 30起已被弃用——请迁移至BrowserView。使用WebContentsView注册表跟踪窗口。通过主进程或Map<string, BrowserWindow>实现窗口间通信,后者可建立直接的渲染器到渲染器通道。手动持久化窗口边界,或使用即将推出的MessagePortAPI。使用windowStatePersistence时必须显式关闭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,, named constants)import type
(You MUST close explicitly when destroying a -- it does not auto-cleanup like , causing memory leaks)
webContentsBaseWindowBrowserWindow(You MUST use instead of -- is deprecated since Electron 30)
WebContentsViewBrowserViewBrowserView(You MUST route all inter-window communication through the main process or -- never access another window's renderer directly)
MessagePort(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 (is sufficient on its own)
BrowserWindow - 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
(销毁时必须显式关闭——它不像那样自动清理,会导致内存泄漏)
BaseWindowwebContentsBrowserWindow(必须使用替代——自Electron 30起已被弃用)
WebContentsViewBrowserViewBrowserView(所有窗口间通信必须通过主进程或进行——绝不能直接访问其他窗口的渲染器)
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)
<patterns>
Electron的窗口模型分为两个层级。BrowserWindow是简单方案:一个窗口对应一个Web视图,自动管理生命周期。BaseWindow + WebContentsView是灵活方案:一个窗口外壳包含多个独立管理的Web视图,每个视图拥有自己的渲染进程和预加载脚本。
关键架构决策:单视图窗口使用BrowserWindow,多视图布局使用BaseWindow。BaseWindow以便利性换取控制力——你需要显式管理视图的生命周期、边界和资源清理。
何时使用BaseWindow + WebContentsView:
- 标签栏、拆分编辑器、预览面板、嵌入浏览器视图
- 多个独立网页共享一个操作系统窗口的任何布局
- 从已弃用的BrowserView迁移的应用
何时不使用BaseWindow:
- 单视图窗口(BrowserWindow更简单且自动处理资源清理)
- 仅需工具栏或状态栏的窗口(单个带HTML布局的BrowserWindow已足够)
<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 and preload script. BaseWindow has no of its own. See examples/core.md for complete split-view and tab examples with resize handling.
webPreferenceswebContentsBaseWindow是窗口外壳;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需要自己的和预加载脚本。BaseWindow自身没有。完整的拆分视图和标签页示例(包含大小调整处理)请查看examples/core.md。
webPreferenceswebContentsPattern 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) |
|---|---|
| |
| |
| |
| |
| |
| Manual resize via |
Gotcha: WebContentsView defaults to a white background; BrowserView defaulted to transparent. Set for transparency.
view.setBackgroundColor("#00000000")See examples/core.md for the complete migration pattern with auto-resize replacement.
BrowserView自Electron 30起已被弃用。迁移过程简单——构造函数结构一致。
| 已弃用(BrowserView) | 替代方案(WebContentsView) |
|---|---|
| |
| |
| |
| |
| |
| 通过 |
注意: WebContentsView默认背景为白色;而BrowserView默认背景为透明。如需透明背景,请设置。
view.setBackgroundColor("#00000000")完整的迁移模式(包含自动调整大小的替代实现)请查看examples/core.md。
Pattern 3: Window Lifecycle Events
模式3:窗口生命周期事件
Window events fire in a predictable order. Use to prevent visual flash, to intercept (confirmations, state saving), for final cleanup.
ready-to-showcloseclosedjavascript
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: fires on (which owns a ) but NOT on (which has no ). For BaseWindow, listen on the individual WebContentsView's instead: .
ready-to-showBrowserWindowwebContentsBaseWindowwebContentswebContentsview.webContents.once("ready-to-show", ...)See examples/core.md for the full lifecycle sequence and BaseWindow workaround.
窗口事件按可预测的顺序触发。使用避免视觉闪烁,使用拦截操作(如确认提示、状态保存),使用进行最终清理。
ready-to-showcloseclosedjavascript
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);
});注意: 事件会在(拥有)上触发,但不会在(无)上触发。对于BaseWindow,请监听各个WebContentsView的事件:。
ready-to-showBrowserWindowwebContentsBaseWindowwebContentswebContentsview.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 event. The registry enables "show existing or create new" patterns for settings, about, and preferences windows.
closedSee 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 (not ) to transfer ports.
postMessagesendSee 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在初始设置后创建直接通道。使用(而非)传输端口。
postMessagesend两种模式的完整示例请查看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 API to enumerate displays, find work areas, and place windows on specific monitors.
screenjavascript
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 (not ) to avoid placing windows behind taskbars/docks. Listen for , , and events to react to monitor changes at runtime.
workAreaboundsdisplay-addeddisplay-removeddisplay-metrics-changedSee examples/core.md for display enumeration and safe placement.
</patterns>
<decision_framework>
使用 API枚举显示器、查找工作区域,并将窗口放置在特定显示器上。
screenjavascript
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,
});
}重点: 使用(而非)避免窗口被任务栏/ dock遮挡。监听、和事件,以便在运行时响应显示器变化。
workAreaboundsdisplay-addeddisplay-removeddisplay-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的BaseWindowInter-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 when destroying a
webContents-- causes memory leaks (BrowserWindow auto-cleans, BaseWindow does not)BaseWindow - Using deprecated instead of
BrowserView-- deprecated since Electron 30WebContentsView - 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 for single-view windows -- unnecessary complexity, use
BaseWindowBrowserWindow - Using patterns with
BrowserView.setAutoResize()-- no equivalent exists, use manual resize listenersWebContentsView - Storing objects as Map values without cleaning up on
BrowserWindow-- stale referencesclosed - Creating child windows from the renderer process -- always create from main
Common Mistakes:
- Expecting on
ready-to-show-- it fires onBaseWindowonly; forBrowserWindow, listen onBaseWindowview.webContents - Forgetting that defaults to white background (BrowserView defaulted to transparent) -- set
WebContentsViewexplicitly"#00000000" - Using to transfer
ipcRenderer.send()-- onlyMessagePortcan transfer portspostMessage() - Placing windows using instead of
display.bounds-- window ends up behind taskbar/dockdisplay.workArea - Not handling event -- window references a disconnected monitor
display-removed
Gotchas & Edge Cases:
- Re-adding a child view with moves it to the top of the z-order -- this is intentional, not a bug
addChildView() - coordinates are relative to the parent view, not the screen
setBounds() - On macOS, modal windows display as sheets attached to the parent window
- returns the outer frame bounds on some platforms -- content area may differ
win.getBounds() - Each runs its own renderer process -- resource usage scales linearly with view count
WebContentsView - requires calling
MessagePortMainbefore messages are delivered -- they queue until then.start()
</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,, named constants)import type
(You MUST close explicitly when destroying a -- it does not auto-cleanup like , causing memory leaks)
webContentsBaseWindowBrowserWindow(You MUST use instead of -- is deprecated since Electron 30)
WebContentsViewBrowserViewBrowserView(You MUST route all inter-window communication through the main process or -- never access another window's renderer directly)
MessagePort(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
(销毁时必须显式关闭——它不像那样自动清理,会导致内存泄漏)
BaseWindowwebContentsBrowserWindow(必须使用替代——自Electron 30起已被弃用)
WebContentsViewBrowserViewBrowserView(所有窗口间通信必须通过主进程或进行——绝不能直接访问其他窗口的渲染器)
MessagePort(恢复窗口边界前必须验证其是否在可见显示器上——会话期间显示器可能断开连接)
不遵守这些规则会导致内存泄漏、已弃用API警告、进程间通信故障或窗口显示在屏幕外。
</critical_reminders>