Windows and the renderer

Understand the process model

Separate main-process responsibilities from renderer work before adding application behavior.

An Electron app is not one JavaScript environment. It is at least two, and they have very different powers. Let’s name them.

The main process is the application coordinator. There is only one. It owns the app lifecycle, creates windows, calls native APIs, and has full Node.js access. If it blocks, every window freezes. If it crashes, the whole application goes down.

Each BrowserWindow loads its web content in a renderer process. The renderer owns the DOM, interface state, and drawing. It behaves like a browser tab. My advice is to treat its input the same way you treat input from a web page: as untrusted.

The preload script runs inside the renderer process, before the page code. With context isolation on, it lives in a separate JavaScript world. It can talk to Electron, and it exposes a narrow API to the page through contextBridge.

The ownership map for Desktop Notes

Here is how the three parts talk to each other in our app:

flowchart LR
  accTitle: Electron process ownership
  accDescr: The renderer calls a narrow preload API, which sends validated actions to the main process. The main process returns note data and native menu events through the same bridge.
  Renderer -->|"saveNotes(notes)<br/>exportNotes()"| Preload["Preload bridge"]
  Preload -->|"Validated IPC"| Main["Main process"]
  Main -->|"Read notes<br/>native menu event"| Preload
  Preload -->|"loadNotes()<br/>onNewNote(callback)"| Renderer

The renderer never touches a file. It calls saveNotes(notes). The preload turns that into an IPC message. The main process validates the data and writes it. Results and menu events travel back the same way.

Every arrow is a trust boundary. Values are copied across IPC (inter-process communication), they are not shared. The two processes have no common objects and no common call stack. If you pass an object with a method, the method does not arrive.

Keep the main process free

Since the main process coordinates everything, don’t make it do heavy work. For Desktop Notes this is easy: file operations are asynchronous and short.

For a real app with heavier computation, say parsing a large file or resizing images, move that work to a utility process or a worker. Otherwise every window freezes while the main process is busy, and users see the whole app hang.

A question to ask before every feature

Before you implement anything, write down which process owns it and why. One line is enough.

If the answer is “the renderer, because it was convenient”, stop and check. Does the feature touch the filesystem, the network, or private data? Then it belongs to the main process, behind a validated IPC call. Convenience is how privileges leak into page code.

Lesson completed