Traditional GUI frameworks block the calling thread in aDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/webviewjs/webview/llms.txt
Use this file to discover all available pages before exploring further.
run() call that never returns until the application exits. WebviewJS takes a fundamentally different approach: it uses Tao’s non-blocking pump_events() integration so that every tick of the GUI message queue is just one call inside an ordinary Node.js setInterval. The result is that file I/O, network requests, child processes, Promises, and all other Node APIs continue operating normally in the same thread — right alongside an open native window.
How the Pump Works
Internally,app.run() is a thin JavaScript wrapper that sets up a setInterval which calls app.pumpEvents() on every tick:
pumpEvents() drains the OS native message queue without waiting for new messages. It returns true while the application is still alive and false when the native layer signals that it should shut down (e.g. all windows closed and no tray icon active). The interval is then cleared automatically.
Because this runs as a normal Node timer, the JS event loop never stalls. You can await a network fetch, read a file, or query a database between frames without any special threading or async bridging.
On macOS the native GUI must run on the main thread — the thread that called
new Application(). Never create an Application or BrowserWindow from a worker thread or worker_threads module.app.whenReady() — Promise-Based Startup
app.whenReady() is the recommended way to start most apps. It starts the event pump and returns a Promise that resolves when Tao emits its first native Resumed event, signalling that the application is fully initialised and ready to create windows.
await at the top level of an ES module:
whenReady Options
whenReady() accepts the same timer configuration options as run():
| Option | Default | Description |
|---|---|---|
interval | 16 | Milliseconds between event pumps (~60 FPS at 16 ms) |
ref | true | If false, the timer is unref()’d and won’t prevent process exit |
autoRun | true | Set to false to skip auto-starting the pump (see Manual Mode below) |
app.run(options?) — Explicit Pump Start
Call app.run() directly when you want to start the pump separately from the readiness Promise, or when you don’t use whenReady() at all.
| Option | Default | Description |
|---|---|---|
interval | 16 | Milliseconds between pumpEvents() calls |
ref | true | If false, the interval is unref()’d via Node’s timer API |
Manual Mode: autoRun: false
For advanced use cases where you need to control the exact moment the pump starts — for example, to show a window only after some async initialisation — disable auto-run in whenReady() and call app.run() yourself:
app.pumpEvents() — Low-Level Manual Pump
pumpEvents() is the primitive that run() calls internally. You can call it directly for maximum control over when the native message queue is drained.
pumpEvents() is a synchronous, non-blocking call. It returns immediately after draining the current message queue — it does not wait for new messages to arrive.app.stop() — Pause the Pump
app.stop() clears the setInterval timer but leaves all windows open and the application alive. The native state is preserved; you just stop ticking the message queue.
app.exit(), which calls stop(), disposes all windows and native resources, and marks the application as exited so subsequent method calls throw.
Ref/Unref Semantics
Node.js timers have aref()/unref() concept: a ref’d timer prevents the process from exiting normally, while an unref’d timer does not. WebviewJS exposes this as the ref option.
- ref: true (default)
- ref: false
The pump timer keeps the process alive for as long as the GUI is running. The process only exits when Use this for full desktop applications where the GUI is the primary purpose of the process.
app.exit() is called (or the last window closes and application-close-requested triggers it).runSync() vs Non-Blocking run()
WebviewJS also exposes a runSync() method that blocks the JavaScript thread in a synchronous loop until the application exits. This mirrors how traditional GUI frameworks behave.
app.run():
app.run() | app.runSync() | |
|---|---|---|
| Blocks JS thread | No | Yes |
| Node I/O / timers | ✅ Continue running | ❌ Suspended |
| async/await | ✅ Works normally | ❌ Does not progress |
| Use case | All standard applications | Compatibility shim, legacy scripts |