nwjs/nw.js · error · Error

nw.callStaticMethodSync(Screen, AddScreenChangeCallback) fai

Error message

nw.callStaticMethodSync(Screen, AddScreenChangeCallback) fails

What it means

On the first listener, Screen.on calls the native AddScreenChangeCallback and expects a truthy result. If the native side returns false (callback registration failed in the C++ layer), a plain Error is thrown. Unlike the validation errors, this reflects an environment/platform failure rather than bad arguments. The `return` after the throw is dead code.

Source

Thrown at src/api/screen/screen.js:46

// Override the addListener method.
Screen.prototype.on = Screen.prototype.addListener = function(ev, callback) {
  if ( ev != "displayBoundsChanged" && ev != "displayAdded" && ev != "displayRemoved" && ev != "chooseDesktopMedia")
    throw new TypeError('only following event can be listened: displayBoundsChanged, displayAdded, displayRemoved');
  
  var onRemoveListener = function (type, listener) {
    if (this._numListener > 0) {
      this._numListener--;
      if (this._numListener == 0) {
        process.EventEmitter.prototype.removeListener.apply(this, ["removeListener", onRemoveListener]);
        nw.callStaticMethodSync('Screen', 'RemoveScreenChangeCallback', [ this.id ]);
      }
    }
  };

  if(this._numListener == 0) {
    if (nw.callStaticMethodSync('Screen', 'AddScreenChangeCallback', [ this.id ])[0] == false ) {
      throw new Error('nw.callStaticMethodSync(Screen, AddScreenChangeCallback) fails');
      return;
    }
    process.EventEmitter.prototype.addListener.apply(this, ["removeListener", onRemoveListener]);
  }
  
  // Call parent.
  process.EventEmitter.prototype.addListener.apply(this, arguments);
  this._numListener++;
}

// Route events.
Screen.prototype.handleEvent = function(ev) {
  if (ev != "chooseDesktopMedia")
    arguments[1] = JSON.parse(arguments[1]);
  // Call parent.
  this.emit.apply(this, arguments);
}

View on GitHub (pinned to e15da848e9)

Solutions

  1. Run in an environment with a real display (or Xvfb on Linux CI).
  2. Defer Screen listener registration until after the window/main module has loaded.
  3. Wrap the call in try/catch and degrade gracefully (log and skip the feature).

Example fix

// before
nw.Screen.on('displayAdded', syncDisplays); // may throw in headless CI

// after
try {
  nw.Screen.on('displayAdded', syncDisplays);
} catch (e) {
  if (/AddScreenChangeCallback/.test(e.message)) {
    console.warn('Screen observer unavailable; multi-monitor features disabled.');
  } else {
    throw e;
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Cannot validate a native failure up front; ensure a display exists instead.
function hasDisplay() {
  try { return nw.Screen && !!nw.Screen.screens; } catch (e) { return false; }
}

Try / catch

try {
  nw.Screen.on('displayAdded', fn);
} catch (e) {
  if (e instanceof Error && /AddScreenChangeCallback/.test(e.message)) {
    console.warn('Screen observer unavailable; skipping multi-monitor features.');
  } else { throw e; }
}

Prevention

When it happens

Trigger: Calling nw.Screen.on('displayBoundsChanged', fn) when the native screen-observer infrastructure failed to initialize — e.g., during very early startup, headless/CI environments without a display, or a platform where the screen-change observer is unsupported.

Common situations: Running nw.js in a headless/automated environment (CI, Docker without X11). Platform-specific observer failures (some Linux WMs, certain virtual displays). Resource exhaustion during startup.

Related errors


AI-assisted analysis of nwjs/nw.js@e15da848e9 (2026-08-13). Data as JSON: /api/errors/fa524c4a9f1d9f48. Report an issue: GitHub.