Skip to content

Commit 3087119

Browse files
committed
fix: resolve JS subclass registered prototypes
1 parent 353e727 commit 3087119

3 files changed

Lines changed: 89 additions & 51 deletions

File tree

NativeScript/ffi/shared/bridge/HostObjects.mm

Lines changed: 46 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,24 +1203,7 @@ Value prototypeFunctionForProperty(Runtime& runtime,
12031203
return Value::undefined();
12041204
}
12051205

1206-
Value classWrapperValue = bridge_->findObjectExpando(
1207-
runtime, object_, "__nativeApiClassWrapper");
1208-
if (!classWrapperValue.isObject()) {
1209-
classWrapperValue = bridge_->findClassValue(runtime, object_getClass(object_));
1210-
}
1211-
if (!classWrapperValue.isObject()) {
1212-
if (const NativeApiSymbol* symbol =
1213-
bridge_->findClassForRuntimeClass(object_getClass(object_))) {
1214-
classWrapperValue = bridge_->findClassValue(
1215-
runtime, objc_lookUpClass(symbol->runtimeName.c_str()));
1216-
}
1217-
}
1218-
if (!classWrapperValue.isObject()) {
1219-
return Value::undefined();
1220-
}
1221-
1222-
Object classWrapper = classWrapperValue.asObject(runtime);
1223-
Value prototypeValue = classWrapper.getProperty(runtime, "prototype");
1206+
Value prototypeValue = enginePrototypeForObject(runtime);
12241207
if (!prototypeValue.isObject()) {
12251208
return Value::undefined();
12261209
}
@@ -1257,6 +1240,48 @@ Value prototypeFunctionForProperty(Runtime& runtime,
12571240
return Value::undefined();
12581241
}
12591242

1243+
Value enginePrototypeForObject(Runtime& runtime) {
1244+
if (object_ == nil) {
1245+
return Value::undefined();
1246+
}
1247+
1248+
Value classWrapperValue = bridge_->findObjectExpando(
1249+
runtime, object_, "__nativeApiClassWrapper");
1250+
if (!classWrapperValue.isObject()) {
1251+
classWrapperValue = bridge_->findClassValue(runtime, object_getClass(object_));
1252+
}
1253+
if (!classWrapperValue.isObject()) {
1254+
if (const NativeApiSymbol* symbol =
1255+
bridge_->findClassForRuntimeClass(object_getClass(object_))) {
1256+
classWrapperValue = bridge_->findClassValue(
1257+
runtime, objc_lookUpClass(symbol->runtimeName.c_str()));
1258+
}
1259+
}
1260+
if (classWrapperValue.isObject()) {
1261+
Object classWrapper = classWrapperValue.asObject(runtime);
1262+
Value prototypeValue = classWrapper.getProperty(runtime, "prototype");
1263+
if (prototypeValue.isObject()) {
1264+
return prototypeValue;
1265+
}
1266+
}
1267+
1268+
Value prototypeValue =
1269+
bridge_->findClassPrototype(runtime, object_getClass(object_));
1270+
if (prototypeValue.isObject()) {
1271+
return prototypeValue;
1272+
}
1273+
if (const NativeApiSymbol* symbol =
1274+
bridge_->findClassForRuntimeClass(object_getClass(object_))) {
1275+
prototypeValue = bridge_->findClassPrototype(
1276+
runtime, objc_lookUpClass(symbol->runtimeName.c_str()));
1277+
if (prototypeValue.isObject()) {
1278+
return prototypeValue;
1279+
}
1280+
}
1281+
1282+
return Value::undefined();
1283+
}
1284+
12601285
// Invoke a JS-prototype getter accessor with this instance as the receiver.
12611286
// Sets *found and returns the resolved value.
12621287
Value resolveEnginePrototypeGetter(Runtime& runtime,
@@ -1265,17 +1290,7 @@ Value resolveEnginePrototypeGetter(Runtime& runtime,
12651290
if (object_ == nil || property.empty()) {
12661291
return Value::undefined();
12671292
}
1268-
Value classWrapperValue =
1269-
bridge_->findObjectExpando(runtime, object_, "__nativeApiClassWrapper");
1270-
if (!classWrapperValue.isObject()) {
1271-
classWrapperValue =
1272-
bridge_->findClassValue(runtime, object_getClass(object_));
1273-
}
1274-
if (!classWrapperValue.isObject()) {
1275-
return Value::undefined();
1276-
}
1277-
Value prototypeValue =
1278-
classWrapperValue.asObject(runtime).getProperty(runtime, "prototype");
1293+
Value prototypeValue = enginePrototypeForObject(runtime);
12791294
if (!prototypeValue.isObject()) {
12801295
return Value::undefined();
12811296
}
@@ -1324,17 +1339,7 @@ bool invokeEnginePrototypeSetter(Runtime& runtime, const std::string& property,
13241339
if (object_ == nil || property.empty()) {
13251340
return false;
13261341
}
1327-
Value classWrapperValue =
1328-
bridge_->findObjectExpando(runtime, object_, "__nativeApiClassWrapper");
1329-
if (!classWrapperValue.isObject()) {
1330-
classWrapperValue =
1331-
bridge_->findClassValue(runtime, object_getClass(object_));
1332-
}
1333-
if (!classWrapperValue.isObject()) {
1334-
return false;
1335-
}
1336-
Value prototypeValue =
1337-
classWrapperValue.asObject(runtime).getProperty(runtime, "prototype");
1342+
Value prototypeValue = enginePrototypeForObject(runtime);
13381343
if (!prototypeValue.isObject()) {
13391344
return false;
13401345
}
@@ -1377,17 +1382,7 @@ bool enginePrototypeHasSetter(Runtime& runtime,
13771382
if (object_ == nil || property.empty()) {
13781383
return false;
13791384
}
1380-
Value classWrapperValue =
1381-
bridge_->findObjectExpando(runtime, object_, "__nativeApiClassWrapper");
1382-
if (!classWrapperValue.isObject()) {
1383-
classWrapperValue =
1384-
bridge_->findClassValue(runtime, object_getClass(object_));
1385-
}
1386-
if (!classWrapperValue.isObject()) {
1387-
return false;
1388-
}
1389-
Value prototypeValue =
1390-
classWrapperValue.asObject(runtime).getProperty(runtime, "prototype");
1385+
Value prototypeValue = enginePrototypeForObject(runtime);
13911386
if (!prototypeValue.isObject()) {
13921387
return false;
13931388
}

PROGRESS.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,43 @@ TypeScript/UI worklets.
1717

1818
## Latest Update - 2026-06-30
1919

20+
### 2026-06-30 03:12 EDT - JS subclass accessors use registered class prototype fallback
21+
22+
- Scope check:
23+
- Still generic NativeScript runtime behavior required by the RN-module
24+
branch. No RNS-specific native implementation and no direct-engine backend
25+
refactor work.
26+
- Simulator-only rule remains active; no physical devices were used.
27+
- CI finding:
28+
- GitHub Actions run `28425597293` on `353e7274` kept setup, FFI boundary
29+
check, native build, CLI build, and macOS tests green.
30+
- iOS still had one failure:
31+
`ApiTests.js SpecialCaseProperty_When_CustomSelector_ImplementedInJS`.
32+
- The failure remained at the final `TNSGetOutput()` assertion, so the values
33+
were correct but native getter/setter dispatch still produced repeated JS
34+
`getter` logs.
35+
- Change:
36+
- JS-subclass method/getter/setter resolution now shares an
37+
`enginePrototypeForObject` helper.
38+
- The helper still checks the per-object class-wrapper expando and class
39+
wrapper cache, but now also falls back to the registered class prototype
40+
(`findClassPrototype`) that `extend()` stores for dynamic subclasses.
41+
- This lets JS subclass accessors resolve through their JS prototype before
42+
falling back to Objective-C runtime properties even when the wrapper object
43+
lookup is unavailable.
44+
- Verification:
45+
- Focused construction/property guard test passed:
46+
`node packages/react-native/test/runtime-instance-selector-base-dispatch.test.js`.
47+
- Runtime RN JS tests passed:
48+
`for f in packages/react-native/test/*.test.js; do node "$f" || exit 1; done`.
49+
- `git diff --check` passed.
50+
- `npm run check:ffi-boundaries` passed.
51+
- Existing generated macOS project compile/link passed with
52+
`xcodebuild -project dist/intermediates/macos/NativeScript.xcodeproj ...`.
53+
- Still next:
54+
- Commit/push this prototype-resolver fix and watch fresh PR CI. If iOS goes
55+
green, resume the RNS simulator parity sweep from this handoff.
56+
2057
### 2026-06-30 02:42 EDT - JS subclass setters invoke prototype accessors directly on V8
2158

2259
- Scope check:

packages/react-native/test/runtime-instance-selector-base-dispatch.test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ for (const relativePath of [
8484
guardedIndex < branchIndex,
8585
`${relativePath}: JS-subclassed property reads should prefer prototype getters before native runtime getters on every backend`,
8686
);
87+
assert(
88+
source.includes("Value enginePrototypeForObject(Runtime& runtime)") &&
89+
source.includes("bridge_->findClassPrototype(runtime, object_getClass(object_))") &&
90+
source.match(/Value prototypeValue = enginePrototypeForObject\(runtime\);/g)?.length >= 4,
91+
`${relativePath}: JS-subclassed prototype lookup should use the registered class prototype fallback for methods, getters, and setters`,
92+
);
8793

8894
const nilObjectSetIndex = source.indexOf(
8995
'throw JSError(runtime, "Cannot set property on nil object.");',

0 commit comments

Comments
 (0)