Every native GUI toolkit needs to drive its own event loop — a tight loop that continuously drains the OS message queue to process redraws, input events, and window signals. Node.js also has its own event loop for async I/O and timers. Traditionally these two loops are incompatible: you can only block on one at a time.
WebviewJS solves this by using tao’s non-blocking pump_events() primitive. Instead of handing full control to the native event loop, it drains the OS message queue in a single call that returns immediately, then hands control back to Node. By scheduling that call inside a regular setInterval, both loops run cooperatively on the same thread — the GUI stays responsive and all of Node’s async I/O continues working normally.
How app.run() works
app.run() is a small JavaScript wrapper that sets up a setInterval targeting app.pumpEvents() at a configurable interval (default 16 ms, roughly 60 FPS):
pumpEvents() drains all pending OS events without waiting, then returns:
true — the application is still alive; keep pumping.
false — the application should exit; the interval is cleared.
Because the pump runs inside a Node timer, you keep full access to fetch, fs.promises, child_process, WebSockets, and every other Node API while the window is open.
Starting the event loop
You have three ways to start the pump, depending on how much control you need:
On macOS, the GUI event loop must run on the main thread — the thread that called new Application(). Do not create Application or BrowserWindow instances from a worker_threads Worker. This is a macOS system requirement enforced by AppKit, not a WebviewJS limitation.
Application readiness
app.whenReady() resolves after tao emits its first native Resumed event, which signals that the application has been granted its window by the OS. Use it as the canonical entry point for creating windows and webviews:
Or with async/await:
Pass an options object to configure the pump that whenReady() starts internally:
To wait for readiness without starting the pump automatically — for example, when you want to call app.run() yourself with different options — set autoRun: false:
app.whenReady() is the idiomatic entry point for WebviewJS applications. It handles pump startup, readiness detection, and async/await integration in a single call. Prefer it over calling app.run() manually unless you need fine-grained control over the timer.
Controlling pump frequency
The interval and ref options let you trade responsiveness for CPU usage:
Stopping the application
Two methods stop the event pump, with different levels of finality:
stop() without exit() is useful when you want to take over the loop yourself temporarily:
Handling graceful shutdown
Listen for application-close-requested to run cleanup code before the process exits:
Without calling app.exit() in this handler, the pump timer keeps the process alive even after the last window is closed.