# NW.js Admin & Architecture Guide

This guide is the deep reference for the people who build, ship, secure, and extend applications on NW.js — release engineers, architects, security reviewers, and contributors to NW.js itself. Where the User Guide explains how to *use* the `nw.*` APIs, this guide explains *how NW.js works internally*: how it fuses Chromium and Node.js, how the JavaScript APIs cross the process boundary into native C++, how an app is loaded as an internal Chrome extension, how the runtime is built from source, and how to operate it in production (signing, distribution, hardening, crash reporting).

Written from the NW.js source tree at the commit recorded in the footer.

---

## 1. Executive Summary

**What NW.js is.** NW.js is an *application runtime* — not a library you import, but a modified build of the Chromium browser whose renderer also embeds a full Node.js. It lets a single JavaScript heap host both the DOM/Web Platform (from Blink/V8) and the Node.js core API and npm ecosystem. Developers write desktop apps in HTML/CSS/JS; NW.js supplies the native window, menu, tray, clipboard, global-hotkey, and screen capabilities, plus filesystem and OS access via Node.

**Where the code lives.** This repository (`nwjs/nw.js`) is **one piece** of a much larger system. The buildable product is assembled from several repos that are checked out into Chromium's source tree:

| Path in the Chromium superproject | Repository | Role |
|---|---|---|
| `src/content/nw` | `nwjs/nw.js` (this repo) | NW.js's own C++/JS source, the landing page, and the issue tracker. |
| `src/third_party/node-nw` | `nwjs/node` | A modified Node.js. |
| `src/v8` | `nwjs/v8` | A modified V8 (shared by Blink and Node). |
| `src` | `nwjs/chromium.src` | The patched Chromium that hosts it all. |

This is why a clone of this repository, by itself, does **not** build into a runnable binary: the code here is compiled *inside* a Chromium checkout. (It also explains why most of this repository's file count is the regression-test suite under `test/`, not product code — see [Section 11](#11-repository-structure).)

**Version under analysis.** `src/nw_version.h` defines `NW_MAJOR_VERSION 0`, `NW_MINOR_VERSION 112`, `NW_PATCH_VERSION 0` → **0.112.0**, a release build (`NW_VERSION_IS_RELEASE 1`). The README corroborates v0.112.0 (May 24, 2026), based on **Node.js v26.1.0** and **Chromium 149**.

**The defining architectural fact.** Since 0.13, an NW.js application runs *internally as a Chrome App / extension*. App pages execute in a `privileged_extension` context under the `chrome-extension://` protocol; the `nw.*` APIs are registered as extension APIs; and the bridge from JavaScript to native behavior is the Chromium **`ExtensionFunction`** mechanism. Understanding this single design choice explains nearly everything else in the codebase.

---

## 2. System Architecture

### 2.1 The two-engine model

NW.js merges two runtimes that were never designed to coexist:

```
            ┌─────────────────────────────────────────────┐
            │                  NW.js process                │
            │                                               │
   Browser  │   ┌───────────────┐      ┌────────────────┐  │
   process  │   │ Chromium       │      │ Node.js        │  │
            │   │ browser logic  │      │ (third_party/  │  │
            │   │ (UI, IPC hub)  │      │  node-nw)      │  │
            │   └───────┬────────┘      └───────┬────────┘  │
            └───────────┼───────────────────────┼──────────┘
                        │ Mojo / legacy IPC      │ shared V8 heap
            ┌───────────┼───────────────────────┼──────────┐
   Renderer │   ┌───────▼────────┐      ┌───────▼────────┐  │
   process  │   │ Blink (DOM)    │◄────►│ Node event loop│  │
   (per     │   │ V8 context(s)  │ same │ in DOM context │  │
   window)  │   └────────────────┘ heap └────────────────┘  │
            └───────────────────────────────────────────────┘
```

The key engineering achievement: **Node and Blink run in the same thread and share one V8 heap**, so a DOM object and a Node object can reference each other directly — no serialization, no marshalling. This is why `require('fs')` works inside `<script>` and why `process.version` prints in an HTML page.

### 2.2 Process model

NW.js inherits Chromium's multi-process architecture:

- **Browser process** — owns the UI, the window list, the extension/app system, and is the IPC hub. NW.js's native window/menu/tray/clipboard operations execute here.
- **Renderer process(es)** — one (or more) per window; runs Blink + V8 + the embedded Node event loop. Your app's JavaScript runs here.
- **Helper / child processes** — GPU, utility, etc. (On macOS these are the per-role **Helper** apps; `product_string` renames them.)

The `nw.*` APIs that manipulate native UI therefore cannot run directly in the renderer — they must **call across to the browser process**. That bridge is the subject of the next section.

### 2.3 The JS → native bridge

The `nw.*` objects you call in your page are **thin JavaScript shims** (in `src/api/*/*.js`). They don't do the native work themselves; they forward to C++ in the browser process through three mechanisms:

**1. Static methods via `ExtensionFunction`.** Each native operation is a C++ class declared with the `DECLARE_EXTENSION_FUNCTION` macro and a string name. For example, in `src/api/nw_app_api.h`:

```cpp
class NwAppQuitFunction : public ExtensionFunction {
  ResponseAction Run() override;
  DECLARE_EXTENSION_FUNCTION("nw.App.quit", UNKNOWN)
};
```

The JS shim (`src/api/app/app.js`) calls `nw.callStaticMethod('App', 'Quit', [])`, which is routed by Chromium's extension-function dispatcher to the matching C++ class in the browser process. Synchronous variants subclass `NWSyncExtensionFunction` and implement `RunNWSync(...)`.

The complete set of registered native functions (one per file, grouped by API object):

| Header | Registered functions (`DECLARE_EXTENSION_FUNCTION`) |
|---|---|
| `nw_app_api.h` | `nw.App.quit`, `.closeAllWindows`, `.enableComponent`, `.updateComponent`, `.getArgvSync`, `.clearCache`, `.clearAppCache`, `.setProxyConfig`, `.getDataPath`, `.crashBrowser` |
| `nw_window_api.h` | ~30 `nw.currentWindowInternal.*` functions: `close`, `showDevToolsInternal`, `showDevTools2Internal`, `closeDevTools`, `capturePageInternal`, `clearMenu`, `setMenu`, `setShadowInternal`, `setBadgeLabelInternal`, `requestAttentionInternal`, `setProgressBarInternal`, `reloadIgnoringCache`, `getZoom`/`setZoom`, `enter/leave/toggleKioskModeInternal`, `isKioskInternal`, `setShowInTaskbarInternal`, `setTitleInternal`/`getTitleInternal`, `getWinParamInternal`, `getCurrent`, `getPrinters`, `setPrintSettingsInternal` |
| `nw_clipboard_api.h` | `nw.Clipboard.getListSync`, `.setListSync`, `.clearSync`, `.readAvailableTypes` |
| `nw_menu_api.h` | `nw.Menu.getNSStringWithFixup`, `.getNSStringFWithFixup` (Mac string fixups) |
| `nw_screen_api.h` | `nw.Screen.getScreens`, `.initEventListeners`, `.startMonitor`, `.stopMonitor`, `.isMonitorStarted`, `.registerStream` |
| `nw_shell_api.h` | `nw.Shell.openItem`, `.openExternal`, `.showItemInFolder` |
| `nw_shortcut_api.h` | `nw.Shortcut.registerAccelerator`, `.unregisterAccelerator` |
| `nw_object_api.h` | `nw.Obj.allocateId`, `.create`, `.destroy`, `.callObjectMethod`, `.callObjectMethodSync`, `.callObjectMethodAsync` |

**2. Stateful objects via the `ObjectManager`.** APIs like `Window`, `Menu`, `MenuItem`, and `Tray` have a JS object on the renderer side and a paired C++ object on the browser side. The pairing is managed by `nw::ObjectManager` (`src/api/object_manager.h`), a Chromium `KeyedService` (one per `BrowserContext`):

```cpp
class ObjectManager : public KeyedService {
  static Base* GetApiObject(int id);          // C++ object by id
  static int  AllocateId();
  void OnAllocateObject(int id, const std::string& type, ...);
  void OnCallObjectMethod(... int object_id, type, method, args);
  void OnCallObjectMethodSync(...);
  void SendEvent(Base* object, const std::string& event, const ListValue& args);
};
```

The renderer calls `nw.Obj.create` (allocating a C++ object of a given `type` with options), then `nw.Obj.callObjectMethod[Sync/Async]` to invoke methods on it by id. Native events flow the other way: the C++ object calls `ObjectManager::SendEvent`, which delivers an event to the JS object — this is how `win.on('minimize', ...)` and tray clicks work. `ObjectManagerFactory` (a `BrowserContextKeyedServiceFactory`) wires the service into each browser context.

**3. Renderer-side custom bindings.** Some operations are implemented as native handlers *inside the renderer*, in `NWCustomBindings` (`src/nw_custom_bindings.h`), an `ObjectBackedNativeHandler`:

```cpp
class NWCustomBindings : public ObjectBackedNativeHandler {
  void CrashRenderer(...);
  void EvalScript(...);        // win.eval
  void EvalNWBin(...);         // win.evalNWBin (compiled binaries)
  void GetAbsolutePath(...);
  void AddOriginAccessWhitelistEntry(...);
  void RemoveOriginAccessWhitelistEntry(...);
  void GetProxyForURL(...);    // App.getProxyForURL
  void SetDevToolsJail(...);
  void GetWidgetToken(...);
  void CallInWindow(...);
};
```

These run in-process because they touch the renderer's own V8/Blink state directly (script evaluation, origin-access lists, proxy lookup).

### 2.4 API surface registration

`src/api/_api_features.json` declares each `nw.*` namespace as a stable extension API restricted to the `privileged_extension` context:

```json
{
  "nw.App":    { "channel": "stable", "contexts": ["privileged_extension"] },
  "nw.Window": { "channel": "stable", "contexts": ["privileged_extension"] },
  "nw.Clipboard": { ... }, "nw.Menu": { ... }, "nw.MenuItem": { ... },
  "nw.Shell": { ... }, "nw.Tray": { ... }, "nw.Screen": { ... },
  "nw.Shortcut": { ... }, "nw.Obj": { ... },
  "nw.currentWindowInternal": {
     "internal": true, "matches": ["<all_urls>"],
     "contexts": ["privileged_extension", "web_page"]
  }
}
```

The `nw.Obj` and `nw.currentWindowInternal` namespaces are the **internal plumbing** behind the public `Window`/`Menu`/`Tray`/etc. objects — they are not meant to be called directly by app code.

### 2.5 The package and content hooks

Two C++ components anchor the runtime behavior of an app:

**`nw::Package`** (`src/nw_package.h`) models the loaded `package.json`. It is constructed from the command line or a path and exposes:

- `GetStartupURL()`, `GetName()`, `GetUseNode()`, `GetUseExtension()`
- `root()` / `window()` — the parsed manifest dictionary and its `window` subobject
- `ReadChromiumArgs()` and `ReadJsFlags()` — parse `chromium-args` and `js-flags`
- self-extraction support: `ExtractPackage(zip, ...)` unzips a `package.nw`/`app.nw` into a `ScopedTempDir` (`self_extract()` flags this case). This is what makes the "append the zip to the binary" packaging trick work.

**`nw_content.cc`** (declared in `src/nw_content.h`) is the set of hook functions patched into Chromium's lifecycle. The most important for app behavior:

| Hook | What it powers |
|---|---|
| `LoadNWAppAsExtensionHook(manifest, error)` | Loads the app's `package.json` as an internal extension manifest. |
| `ContextCreationHook(frame, context)` | Injects the `nw` bindings when a V8 context is created. |
| `DocumentElementHook(...)` / `DocumentHook2(...)` | Runs `inject_js_start`; fires `document-start`. |
| `DocumentFinishHook(...)` | Runs `inject_js_end`; fires `document-end`. |
| `willHandleNavigationPolicy(...)` | Implements the `new-win-policy` and `navigation` events. |
| `CalcNewWinParams(...)` | Computes options (incl. injected JS) for newly-opened windows. |
| `OverrideWebkitPrefsHook(...)` | Applies manifest-driven WebKit preferences. |
| `GetUserAgentFromManifest` / `SetUserAgentOverride` | Implements the `user-agent` manifest field and its `%`-placeholders. |
| `MergeManifest()` | Merges the per-new-window manifest set via `setNewWindowManifest`. |
| `ExecuteAppCommandHook`, `ProcessSingletonNotificationCallbackHook` | Single-instance handling → the `App` `open` event. |
| `ShowDevtools`, `InspectElement` | DevTools control (SDK builds). |
| Mac-only: `ApplicationShouldHandleReopenHook` (`reopen` event), `OSXOpenURLsHook`, `NWChangeAppMenu`, `NWSetNSWindowShowInTaskbar`, `NWSetNSAppKioskOptions` | macOS integration. |

### 2.6 Custom protocol

`src/nw_protocol_handler.h` defines `NwProtocolHandler`, a `net::URLRequestJobFactory::ProtocolHandler` that serves NW's internal scheme — part of how app resources are resolved under `chrome-extension://`.

### 2.7 Export macro

`src/nw_export.h` defines `NW_EXPORT` for symbol visibility across the component-build boundary (`__declspec(dllexport/dllimport)` on Windows, `visibility("default")` elsewhere) — relevant when NW.js is built as a shared library in a component build.

---

## 3. Requirements

### 3.1 To run an NW.js app (end users)

| Platform | Minimum |
|---|---|
| Windows | Win10+ for current builds (legacy builds for XP). 32- or 64-bit binaries. Transparency needs DWM (Vista+). |
| macOS | 10.10+. Apps must be **signed** (Gatekeeper). arm64 builds shipped. |
| Linux | x64 or arm64; a desktop environment with a window manager. Transparency needs a compositing WM. |

No separate Node.js or Chromium install is required — both are embedded in the redistributed binaries.

### 3.2 To build NW.js from source (see [Section 9](#9-building-nwjs-from-source))

- Chromium's `depot_tools` and platform build prerequisites.
- **20 GB+** of download for `gclient sync`, plus tens of GB of build output.
- A strong machine: ≥8 CPU cores, SSD, ≥8 GB RAM (more is better); a full build takes **hours**.
- Windows: `set DEPOT_TOOLS_WIN_TOOLCHAIN=0`; Visual Studio toolchain.

### 3.3 To rebuild native Node modules (app developers)

- `node-gyp` (0.111.3+) or `nw-gyp` (older), plus a C++ toolchain (Visual Studio "Desktop development with C++" on Windows). See [Section 7.4 of the User Guide](nwjs__nw.js__UserGuide.md) and [Section 8.3](#83-native-module-abi) below.

---

## 4. Configuration Mastery

Configuration is split across three surfaces. All three are documented exhaustively in the **User Guide**; this section frames them for operators and notes the security-sensitive items.

### 4.1 The three configuration surfaces

| Surface | Where | Scope |
|---|---|---|
| **Manifest** (`package.json`) | App root | Per-app, baked into the package |
| **Command-line switches** | `nw <flags>` or `chromium-args` in the manifest | Per-launch (CLI) or per-app (manifest) |
| **Environment variables** | Process environment | Per-launch |

### 4.2 Security-sensitive manifest fields

| Field | Risk | Operator guidance |
|---|---|---|
| `node-remote` | **Critical.** Grants Node (full machine access) to remote origins. | Keep empty unless absolutely required; never point at third-party origins. |
| `nodejs: false` | Hardening lever. | Set `false` for apps that display untrusted content and don't need Node. |
| `chromium-args` | Can disable security features. | Audit every flag; treat `--disable-*` security flags as red flags. |
| `additional_trust_anchors` | Adds trusted CAs. | Only embed certificates you control. |
| `crash_report_url` | Sends crash data (incl. command-line switches) off-machine. | Point only at a server you operate; review what minidumps contain. |
| `js-flags` | Exposes V8 internals (e.g. `--expose-gc`). | Avoid shipping debug flags to production. |

### 4.3 Environment variables

| Variable | Effect |
|---|---|
| `NW_PRE_ARGS` | Value is **prepended** to `chromium-args` — a way to inject switches without editing the manifest. |
| `NW_DESKTOP` | (Linux/Ubuntu) `.desktop` file used by taskbar progress/badge APIs (default `nw.desktop`). |
| `DEPOT_TOOLS_WIN_TOOLCHAIN` | (Build, Windows) set to `0` to use a local toolchain. |
| `GYP_DEFINES`, `GYP_GENERATORS`, `npm_config_*` | Build/native-module configuration (see Sections 8–9). |

### 4.4 The data directory

Per-user app data lives at `App.dataPath` — `%LOCALAPPDATA%/<name>` (Windows), `~/.config/<name>` (Linux), `~/Library/Application Support/<name>/Default` (macOS). It holds cookies, localStorage, caches, and crash dumps (under `Crash Reports`). Override with `--user-data-dir`.

> [!CAUTION]
> Because the data directory is derived from the manifest `name`, two apps with the same `name` share all stored data. Choose a globally unique `name`.

---

## 5. Security Architecture

### 5.1 Threat model

An NW.js app runs with the user's full privileges. The trust boundary is **between your app's Node-enabled pages and any content you don't control**. There is no built-in sandbox protecting the OS from a Node frame — Node frames *deliberately* bypass the renderer sandbox and same-origin policy.

### 5.2 Frame trust levels

| Frame type | Node/NW APIs | Extended DOM | Sandbox/SOP | When |
|---|---|---|---|---|
| **Node frame** | Yes | Yes | Bypassed | `nodejs:true` + (`chrome-extension://` or `node-remote` match) + no `nwdisable` + not in a `<webview>` |
| **Normal frame** | No | No | Enforced | everything else (incl. `nwdisable` frames) |
| **`<webview>` guest** | No (unless `allownw`) | No | Separate process | embedded untrusted content |

### 5.3 Isolation primitives

- **`<webview>`** — runs guest content in a *separate process* with no Node; the correct container for remote/untrusted pages.
- **`nwdisable`** — turns an `<iframe>` into a normal frame (no `require`/`nw`).
- **`nwfaketop`** — additionally hides `window.parent`/`window.top` (breaks `postMessage` to parent).
- **Separate Context Mode** — keeps Node objects in a context distinct from page scripts (the default).

### 5.4 Code-integrity primitives

| Mechanism | Protects against | Limitation |
|---|---|---|
| `nwjc` compilation | Casual source reading | Not cross-platform/version; reversible by a determined attacker |
| Content verification (`--verify-content`) | File tampering under the *official* binary | Bypassed by loading files under a different NW.js binary |
| Native module / NaCl | Source exposure of critical logic | Adds build complexity |
| macOS code signing / MAS sandbox | Gatekeeper rejection; OS-level sandbox | macOS only |

### 5.5 Content verification internals

`NWContentVerifierDelegate` (`src/nw_content_verifier_delegate.h`) extends Chromium's extension `ContentVerifierDelegate` with four modes:

```cpp
enum Mode { NONE, BOOTSTRAP, ENFORCE, ENFORCE_STRICT };
```

- `NONE` — don't fetch or enforce hashes.
- `BOOTSTRAP` — fetch missing hashes, don't enforce.
- `ENFORCE` — enforce present hashes; fetch missing ones.
- `ENFORCE_STRICT` — treat **missing** hashes as a verification *failure*.

The public key is compiled into the binary (`GetPublicKey()`); `GetDefaultMode()` sets the default. To use your own key you must rebuild NW.js and change the default mode to `ENFORCE_STRICT` (see [Section 9.6](#96-content-verification-with-your-own-key)).

### 5.6 Hardening checklist

- [ ] `node-remote` empty (or tightly scoped to origins you own)
- [ ] All remote/untrusted content rendered in `<webview>`, not `<iframe>+node-remote`
- [ ] `nodejs:false` for display-only apps
- [ ] No security-weakening `chromium-args` in production
- [ ] Source protected with `nwjc`; critical logic in native modules
- [ ] Content verification enabled for tamper detection
- [ ] macOS app signed (Developer ID or MAS); Windows installer signed
- [ ] Crash reports sent only to a server you operate

---

## 6. Operations

### 6.1 Distribution model

There is no server to operate: NW.js apps are **client-side binaries** shipped to users. "Operations" therefore means the build/sign/package/update pipeline.

```
build app → npm install (per platform) → rebuild native modules →
nwjc (optional) → combine with NW.js binary → sign → package installer → distribute → autoupdate
```

### 6.2 Choosing a build flavor for release

| Flavor | Ship to | Why |
|---|---|---|
| SDK | Internal QA / dev | DevTools, `nwjc`, `chromedriver` |
| Normal | End users | Smaller; no DevTools |

Detect at runtime: `process.versions['nw-flavor']`. **Exclude** `nwjc`, `payload`, and `chromedriver` from production packages.

### 6.3 Packaging layout (canonical)

| Platform | App files location |
|---|---|
| Windows / Linux | Alongside `nw`(`.exe`) with `package.json`, **or** a `package.nw` (folder or zip) **or** zip appended to the binary |
| macOS | `nwjs.app/Contents/Resources/app.nw` |

> [!WARNING]
> Large or many-file `package.nw` zips slow startup (unzipped to temp on launch). Prefer plain files for big apps.

### 6.4 Crash reporting

Set `crash_report_url` in the manifest. On crash, NW.js POSTs a `multipart/form-data` request (the same format as Chromium's crashpad) containing at least `prod` (manifest `name`), `ver` (manifest `version`), `upload_file_minidump` (the minidump), and `switch-N` fields (the crashing process's command-line switches). Any breakpad/crashpad-compatible server can receive it. Crash dumps are also stored locally under `<dataPath>/Crash Reports`. `--disable-crash-handler=true` (with `--single-process`) disables crash dumping.

### 6.5 Auto-update strategy

NW.js ships no built-in updater. Standard options: `nw-autoupdater` (modern async API), `nwjs-autoupdater` (tiny Go helper, self-updating, no elevation), `node-webkit-updater` (low-level). The general flow: read a remote manifest, compare versions, download the platform package, unpack to temp, relaunch from temp, copy over the install dir, restart from the original location.

### 6.6 Testing apps with ChromeDriver

The SDK build includes `chromedriver`. Point it at your app with `--nwapp=<path>`; the `--chromedriver-port` / `CHROMEDRIVER_PORT` settings (used by NW.js's own test harness in `test/sanity/nw_util.py`) configure the driver port. This is the basis of NW.js's automated `test/sanity` and `test/browser` suites.

---

## 7. Performance & Tuning

| Lever | Effect |
|---|---|
| `--disable-raf-throttling` | Keep `requestAnimationFrame` firing while hidden/minimized — important for games and background animation. |
| `js-flags` (e.g. `--expose-gc`) | Tune V8; expose manual GC for memory-sensitive apps. |
| Mixed vs. Separate Context | Mixed avoids cross-context type pitfalls but loses easy variable sharing; choose per app. |
| Plain files vs. zip packaging | Plain files start faster (no unzip-to-temp). |
| `dom_storage_quota` | Raise the DOM-storage cap (value in MB; set to ~2× the target). |
| `nwjc` (post-0.22) | No runtime penalty after the 0.22 fix; pre-0.22 ran ~30% slower. |

> [!NOTE]
> Because Node and Blink share a thread, **blocking Node work blocks the UI**. Offload CPU-heavy work to Web Workers (`--enable-node-worker` gives them Node access) or child processes.

---

## 8. Internals Deep Dive

### 8.1 `src/` module map

| Directory | Responsibility |
|---|---|
| `src/api/` | JS API shims + their C++ `ExtensionFunction` implementations + `ObjectManager`. The public `nw.*` surface. |
| `src/api/<obj>/` | Per-API JS shim + native binding (`app`, `menu`, `menuitem`, `tray`, `clipboard`, `shell`, `screen`, `shortcut`, `base`, `event`). |
| `src/browser/` | Browser-process C++ (incl. `printing/`, `pepper/`). |
| `src/renderer/` | Renderer-process C++ (incl. `common/`, `printing/`, `prerenderer/`). |
| `src/common/` | Shared browser/renderer code. |
| `src/net/` | Networking integration. |
| `src/media/` | Media integration. |
| `src/geolocation/` | Geolocation integration. |
| `src/mac/` | macOS-specific code and `.lproj` resources. |
| `src/resources/` | Built-in resources (error pages, locale, default app). |
| Top-level `src/*.h/.cc` | The runtime spine: `nw_base`, `nw_content`, `nw_package`, `nw_protocol_handler`, `nw_custom_bindings`, `nw_content_verifier_delegate`, `nw_version`, plus Mac/Windows resource files. |

### 8.2 Lifecycle of an API call (worked example: `win.minimize()`)

1. App JS calls `win.minimize()` on a `Window` shim (renderer).
2. The shim invokes `nw.Obj.callObjectMethod(id, 'Window', 'Minimize', [])` (or a `currentWindowInternal.*` `ExtensionFunction` for window-scoped ops).
3. Chromium routes the call (Mojo/legacy IPC) to the browser process.
4. `ObjectManager::OnCallObjectMethod` looks up the C++ `Window` object by id and invokes its native `Minimize`.
5. The native window minimizes; the OS emits a minimize notification.
6. The C++ object calls `ObjectManager::SendEvent(this, "minimize", [])`.
7. The event is delivered to the renderer JS object, firing `win.on('minimize', ...)`.

### 8.3 Native-module ABI

NW.js's V8 is a modified `nwjs/v8`, so the ABI differs from stock Node for **non-LTS** releases — hence `nw-gyp` historically. Since **0.111.3**, NW.js publishes Node.js-compatible header tarballs (`https://dl.nwjs.io/`), so stock `node-gyp` works with `--target=<ver> --disturl=https://dl.nwjs.io/`. `node-pre-gyp` packages use `--runtime=node-webkit`. Modules are version- and arch-specific; rebuild after every NW.js upgrade.

### 8.4 The "internal Chrome App" consequence

Because the app is loaded as an extension:

- The app origin is `chrome-extension://<generated-id>` (since 0.13; replaced `file://`/`app://`).
- All `chrome.*` extension/app platform APIs are available to app pages.
- App pages run in the `privileged_extension` V8 context.
- `domain` in the manifest sets the host portion of that URL so an app can share cookies with a same-domain website.

### 8.5 Versioning macros

`src/nw_version.h` exposes `NW_VERSION_STRING`, `NW_VERSION` (`"v0.112.0"`), and the compile-time predicate `NW_VERSION_AT_LEAST(major, minor, patch)`. The runtime equivalent for app code is `process.versions['nw']`.

---

## 9. Building NW.js From Source

> [!IMPORTANT]
> Building NW.js means building a **patched Chromium**. This is a large, multi-hour, multi-tens-of-GB undertaking. Most teams should download official binaries; build from source only to enable proprietary codecs, change the content-verification key, or contribute to NW.js itself.

### 9.1 Prerequisites

Install Chromium's `depot_tools` and follow the upstream platform build instructions (Windows/macOS/Linux). On Windows, `set DEPOT_TOOLS_WIN_TOOLCHAIN=0`.

### 9.2 Fetch the code

```bash
mkdir -p $HOME/nwjs && cd $HOME/nwjs
gclient config --name=src https://github.com/nwjs/chromium.src.git@origin/nw17
```

Trim test/reference downloads by editing `custom_deps` in the generated `.gclient`, then:

```bash
gclient sync --with_branch_heads     # downloads 20G+
```

Manually check out the NW component repos onto the right branches:

| Path | Repo |
|---|---|
| `src/content/nw` | `https://github.com/nwjs/nw.js` |
| `src/third_party/node-nw` | `https://github.com/nwjs/node` |
| `src/v8` | `https://github.com/nwjs/v8` |

On a first Linux build, run `gclient sync --with_branch_heads --nohooks` then `./build/install-build-deps.sh`.

### 9.3 Generate build files

Chromium uses **GN + Ninja**; Node still uses **GYP + Ninja**.

```bash
cd src
gn gen out/nw          # do not rename 'out' or 'nw'
```

Official-style GN args:

```text
is_debug=false
is_component_ffmpeg=true
target_cpu="x64"
```

Use `is_component_build=true` for faster debug iteration. For Node, set `GYP_DEFINES` (e.g. `target_arch=x64 building_nw=1 clang=1 nwjs_sdk=1 disable_nacl=0 buildtype=Official`), `GYP_GENERATORS=ninja`, then run `gyp_main.py` against `third_party/node-nw/node.gyp`.

### 9.4 Build

```bash
cd src
ninja -C out/nw nwjs      # the NW.js binary
ninja -C out/Release node # Node
ninja -C out/nw copy_node # copy Node into the nwjs output
```

A full build takes hours. See the upstream "faster builds" docs for caching/jumbo tips.

### 9.5 Build flavors from source

| Flavor | GN/GYP setting |
|---|---|
| Standard (Normal) | `nwjs_sdk=false` |
| SDK | `enable_nacl=true` (SDK adds DevTools + NaCl) |

32/64-bit selection is via `target_arch`/`GYP_DEFINES` per platform (Windows defaults to 32-bit; Linux/Mac default to 64-bit).

### 9.6 Content verification with your own key

1. `openssl genrsa -out private_key.pem 2048`
2. `python convertkey.py` → C source for the public key
3. Paste it into `content/nw/src/nw_content_verifier_delegate.cc`, replacing the default key
4. Change the default mode to `ContentVerifierDelegate::ENFORCE_STRICT`
5. Rebuild NW.js

The demo keypair and signing tools (`sign.py`, `payload`, `convertkey.py`) live in `tools/sign/`.

### 9.7 Proprietary codecs

Prebuilt binaries exclude H.264/MP3/MP4 for licensing reasons. To enable them, build from source per the repo's *Enable Proprietary Codecs* document.

---

## 10. Contributing

### 10.1 Where development happens

The README is explicit: NW.js development spans **multiple repositories** in the `nwjs` org. **This** repository is for *issue tracking, the landing page, and part of the source* (`content/nw`). Core patches often land in `nwjs/chromium.src`, `nwjs/node`, or `nwjs/v8`.

### 10.2 Test suites

The `test/` tree (the bulk of this repository by file count) drives NW.js's regression testing:

| Suite | Purpose |
|---|---|
| `test/sanity/` | Hundreds of focused regression apps, most named `issueNNNN-*`, each reproducing a specific historical bug. Driven via ChromeDriver. |
| `test/browser/` | Browser-level behavior tests. |
| `test/full/` | Full end-to-end scenarios (shortcuts, window behavior). |
| `test/auto/` | Automated process/exit-code tests. |
| `test/testpy/`, `test/jsdriver/`, `test/sanity/testcfg.py`, `nw_util.py` | The Python/JS test harness (test configuration, ChromeDriver setup, helpers). |

Each `test/sanity/issueNNNN-*` directory is a complete NW.js app (its own `package.json` + assets) plus a `test.py` that launches it under ChromeDriver and asserts behavior — a living catalog of fixed bugs and a model for writing reproductions.

### 10.3 Writing a regression test

Add a directory under `test/sanity/` with a `package.json`, the minimal app that reproduces the issue, and a `test.py` using the helpers in `nw_util.py` (e.g. `wait_for_element_id`, `switch_to_app`, `wait_for_execute_script`). The harness reads `CHROMEDRIVER`/`CHROMEDRIVER_PORT` from the environment.

### 10.4 Documentation

Project docs live in `docs/` (`For Users`, `For Developers`, `References`) and are published to `http://docs.nwjs.io/`. See `docs/For Developers/Writing Documents for NW.js.md` and `Contributors of Documents.md` for the docs workflow.

---

## 11. Repository Structure

```text
nw.js/
├── src/                         NW.js C++/JS source (built as content/nw)
│   ├── api/                     nw.* JS shims + C++ ExtensionFunctions + ObjectManager
│   │   ├── app/ window/ menu/ menuitem/ tray/ clipboard/ shell/ screen/ shortcut/
│   │   ├── base/ event/         base object + event plumbing
│   │   └── _api_features.json   API namespace registration
│   ├── browser/  renderer/  common/  net/  media/  geolocation/  mac/
│   ├── resources/               error pages, locale, default app
│   ├── nw_version.h             version macros (0.112.0)
│   ├── nw_package.h/.cc         manifest model + zip self-extract
│   ├── nw_content.h/.cc         Chromium lifecycle hooks
│   ├── nw_custom_bindings.h/.cc renderer-side native handlers
│   ├── nw_content_verifier_delegate.h/.cc   content verification
│   └── nw_protocol_handler.h/.cc            internal protocol
├── docs/                        canonical product docs (→ docs.nwjs.io)
│   ├── For Users/  For Developers/  References/
├── test/                        regression suite (sanity/browser/full/auto/testpy)
├── tools/sign/                  content-verification signing tools + demo key
├── patch/                       patches applied to the wider tree
└── README.md
```

> [!NOTE]
> The file count here is dominated by `test/sanity/issueNNNN-*` fixtures (each its own mini-app). The actual product code is concentrated in `src/` and the canonical documentation in `docs/`.

---

## 12. Troubleshooting (Operations)

Community-reported issues most relevant to operators and integrators (from the project tracker):

| Symptom | Issue | Operator action |
|---|---|---|
| Custom `--user-data-dir` still creates an `AppData` folder (Windows) | #6328 | Use the explicit dir for real data; the stray default folder is benign. |
| Node ESM built-ins not importable in DOM context | #8333 | Launch with `--enable-features=NWESM,NWChainImportNode`; load ESM via `node-main` (0.98.2+). |
| `nwjc`-generated file → `SyntaxError: Unexpected identifier` | #8289 | Recompile per platform/version; use `--nw-module` for modules. |
| Window positioning fails on Wayland | #8348 | Use X11/XWayland; set positions in callbacks. |
| `--single-process` not working | #8173 | Avoid; pair with `--disable-crash-handler=true` if testing. |
| Processes leak / stay open after killing main process | #7852 | Ensure `App.quit()`/`closeAllWindows()` on shutdown; investigate detached child processes. |
| DevTools segfault / GPU exit on tooltip (Linux/X11) | #7844, #8184 | Try a different build/flavor; disable GPU as a workaround. |
| App fails to launch on older macOS / Intel | #8141, #1934 | Match the NW.js build to the OS; use legacy builds for old macOS. |

For app-level errors (the `crypto`/`global` name collisions, AngularJS scheme whitelisting, native-module ABI, context `instanceof` pitfalls, menu/tray after navigation), see the **User Guide → Troubleshooting**.

---

## 13. Quick Reference

### 13.1 Native function namespaces

`nw.App`, `nw.Window` (+ internal `nw.currentWindowInternal`), `nw.Menu`, `nw.MenuItem`, `nw.Tray`, `nw.Clipboard`, `nw.Shell`, `nw.Screen`, `nw.Shortcut`, `nw.Obj` (internal object manager).

### 13.2 Key source files

| File | Role |
|---|---|
| `src/nw_version.h` | Version (0.112.0) |
| `src/nw_package.h` | Manifest model + self-extract |
| `src/nw_content.h` | Chromium lifecycle hooks |
| `src/api/object_manager.h` | JS↔C++ object bridge |
| `src/api/_api_features.json` | API namespace registration |
| `src/nw_content_verifier_delegate.h` | Content verification modes |
| `src/nw_custom_bindings.h` | Renderer-side native handlers |

### 13.3 Build commands

```bash
gclient config --name=src https://github.com/nwjs/chromium.src.git@origin/nw17
gclient sync --with_branch_heads
cd src && gn gen out/nw
ninja -C out/nw nwjs && ninja -C out/Release node && ninja -C out/nw copy_node
```

### 13.4 Native module rebuild (0.111.3+)

```bash
npm install --target=0.112.0 --disturl=https://dl.nwjs.io/
```

### 13.5 Sign / verify

```bash
# content verification
nw --verify-content=enforce_strict .
# macOS App Store
python build_mas.py -C build.cfg -I dev.app -O Release.app
# verify a download
grep nwjs-vX.Y.Z.tar.gz SHASUMS256.txt | sha256sum -c -
```

### 13.6 External resources

- Docs: http://docs.nwjs.io/ · Downloads: https://nwjs.io / https://dl.nwjs.io · Wiki: https://github.com/nwjs/nw.js/wiki · Issues: https://github.com/nwjs/nw.js/issues · `nw-gyp`: https://github.com/nwjs/nw-gyp

---

_Generated June 2026 from nwjs/nw.js at 036da15. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
