From 5a3ce13dbffd1445d440630c7f3bf020d145fe7f Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Sun, 9 Aug 2026 14:40:48 -0700 Subject: [PATCH] fix(ffi): answer instanceof for TypeScript-derived classes against the derived class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A @NativeClass constructor inherits the base wrapper's Symbol.hasInstance through the wrapper prototype chain until its Objective-C class materializes, so `x instanceof Derived` ran the BASE class's membership check: any UIViewController subclass instance tested true for instanceof UILayoutViewController. @nativescript/core's setViewControllerView gates on exactly that check for root views, so a root Frame's iOSFrame helper object was marshalled (as NSDictionary) into addSubview: and UIKit threw NSInvalidArgumentException at boot. Detect the inherited invocation via hasInstance's `this` (the spec passes the constructor instanceof was invoked on): delegate to the materialized derived wrapper when one exists, and report false before materialization — no instance of an unmaterialized class can exist. After materialization the constructor's prototype is already the derived wrapper, whose own hasInstance answers directly. Co-Authored-By: Claude Fable 5 --- .../ffi/shared/jsi/NativeApiJsiInstall.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/NativeScript/ffi/shared/jsi/NativeApiJsiInstall.h b/NativeScript/ffi/shared/jsi/NativeApiJsiInstall.h index 6e711d5be..72030b6da 100644 --- a/NativeScript/ffi/shared/jsi/NativeApiJsiInstall.h +++ b/NativeScript/ffi/shared/jsi/NativeApiJsiInstall.h @@ -928,6 +928,24 @@ void InstallNativeApiJsiGlobalSymbols(Runtime& runtime, const char* globalName) if (!value || typeof value !== 'object') { return false; } + // `this` is the constructor instanceof was invoked on. A + // TypeScript-derived native class inherits this method through the + // wrapper prototype chain until it materializes, so membership must + // be answered for the DERIVED Objective-C class — and before + // materialization no instance of it can exist. + try { + if (this && this !== constructable && this !== wrapper && + this.__nativeApiTypeScriptState) { + var derivedWrapper = this.__nativeApiTypeScriptState.wrapper; + if (!derivedWrapper) { + return false; + } + if (derivedWrapper !== constructable && derivedWrapper !== wrapper) { + return derivedWrapper[Symbol.hasInstance](value); + } + } + } catch (_) { + } var expectedName = nativeClass.runtimeName || nativeClass.name; try { if (typeof value.isKindOfClass === 'function' &&