> ## Documentation Index
> Fetch the complete documentation index at: https://webview.js.org/llms.txt
> Use this file to discover all available pages before exploring further.

# WebviewJS Event Loop: Non-Blocking GUI Pump Explained

> Learn how WebviewJS integrates with Node's event loop using non-blocking event pumping, and how to control pump frequency, readiness, and shutdown.

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):

```js theme={null}
// This is the conceptual equivalent of what app.run() does
const timer = setInterval(() => {
  if (!app.pumpEvents()) {
    clearInterval(timer);
  }
}, 16);
```

`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:

<CodeGroup>
  ```js app.whenReady() — recommended theme={null}
  import { Application } from '@webviewjs/webview';

  const app = new Application();

  // Starts the pump automatically and resolves after the first native "resumed" event
  await app.whenReady();

  const win = app.createBrowserWindow({ title: 'Ready', width: 1024, height: 768 });
  win.createWebview({ url: 'https://example.com' });
  ```

  ```js app.run() — non-blocking, manual theme={null}
  import { Application } from '@webviewjs/webview';

  const app = new Application();

  // Start the pump first, then create windows
  app.run({ interval: 16, ref: true });

  const win = app.createBrowserWindow({ title: 'Manual run', width: 1024, height: 768 });
  win.createWebview({ url: 'https://example.com' });
  ```

  ```js app.runSync() — blocks the JS thread theme={null}
  import { Application } from '@webviewjs/webview';

  const app = new Application();

  const win = app.createBrowserWindow({ title: 'Sync', width: 1024, height: 768 });
  win.createWebview({ url: 'https://example.com' });

  // Hands control to Tao's native loop; no other JS runs until the app exits
  app.runSync();
  ```
</CodeGroup>

<Warning>
  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.
</Warning>

## 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:

```js theme={null}
app.whenReady().then(() => {
  const win = app.createBrowserWindow({ title: 'Hello', width: 800, height: 600 });
  win.createWebview({ url: 'https://nodejs.org' });
});
```

Or with `async/await`:

```js theme={null}
await app.whenReady();

const win = app.createBrowserWindow({ title: 'Hello', width: 800, height: 600 });
win.createWebview({ url: 'https://nodejs.org' });
```

Pass an options object to configure the pump that `whenReady()` starts internally:

```js theme={null}
await app.whenReady({
  interval: 16,  // milliseconds between pumps (default: 16)
  ref: true,     // keep the process alive while the timer is running (default: true)
});
```

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`:

```js theme={null}
const ready = app.whenReady({ autoRun: false });

// Start your own pump with custom settings
app.run({ interval: 33, ref: false });

// Await the readiness signal separately
await ready;
```

<Tip>
  `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.
</Tip>

## Controlling pump frequency

The `interval` and `ref` options let you trade responsiveness for CPU usage:

| Option     | Type      | Default | Description                                                                                             |
| ---------- | --------- | ------- | ------------------------------------------------------------------------------------------------------- |
| `interval` | `number`  | `16`    | Milliseconds between `pumpEvents()` calls. 16 ms ≈ 60 FPS.                                              |
| `ref`      | `boolean` | `true`  | When `false`, the timer is `unref()`'d — the process can exit naturally even while the pump is running. |

```js theme={null}
// 30 FPS — friendlier to battery and CPU for simple or mostly-idle UIs
app.run({ interval: 33 });
```

```js theme={null}
// Unref'd timer — the process exits when all other async work finishes,
// regardless of whether the GUI is still open
app.run({ interval: 16, ref: false });
```

```js theme={null}
// Combine with whenReady for a 30 FPS unref'd pump
await app.whenReady({ interval: 33, ref: false });
```

## Stopping the application

Two methods stop the event pump, with different levels of finality:

```js theme={null}
// Stop the interval but leave all windows open and native resources intact.
// You can restart pumping manually with app.run() afterwards.
app.stop();

// Stop the pump AND hide all windows AND mark the application as exited.
// Retained BrowserWindow / Webview wrappers subsequently report isDisposed() === true.
app.exit();
```

`stop()` without `exit()` is useful when you want to take over the loop yourself temporarily:

```js theme={null}
app.stop();

// Drive a tight custom loop — for example, a CPU-intensive rendering burst
while (framesToProcess-- > 0) {
  app.pumpEvents();
}

// Hand control back to the interval-based pump
app.run();
```

### Handling graceful shutdown

Listen for `application-close-requested` to run cleanup code before the process exits:

```js theme={null}
app.on('application-close-requested', () => {
  console.log('All windows closed — cleaning up.');
  // flush logs, save state, etc.
  app.exit();
});
```

Without calling `app.exit()` in this handler, the pump timer keeps the process alive even after the last window is closed.
