Repository navigation
React Native on Android, build-time bindings and metadata filtering, QuickJS-NG submodule - #117
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the
|
(cherry picked from commit 42b234c572b721028fe7336e1a3d94fef766f20f)
…ntime (cherry picked from commit 4d2c2d1a04f332e3d02c499f2edc5f878b7819cd)
(cherry picked from commit db52e93d8ced33ee20378022374ee516a2ff13ca)
(cherry picked from commit 69b555a61b1c149198c935e0407325eaf327fca9)
(cherry picked from commit 8708971abe09ed7f1b6c3256a6a1d9d0224cd2b3)
(cherry picked from commit 9bd3c6bd582d8ee3aff93e42da93d6c173b7a3c8)
(cherry picked from commit bccb89f941edf004e74665356f06105395fb7f2e)
(cherry picked from commit 42f0fe319048f23431a931bb2f1eb92f6323792d)
…sses (cherry picked from commit 7362b0b595d33fbb3767551bf7824fc4ea235f8e)
(cherry picked from commit 2cbdaa5e2131b9f1fa8b3ec8b839867574121001)
(cherry picked from commit 982b8e63668289ab13d23a78673d0afa9d78bec7)
…me is required (cherry picked from commit 5eec7c635d6f35a28a27033d28586ce24a5d3b40)
…s a module (cherry picked from commit baba1e25b5e97da5333511e43918e9a721f05435)
(cherry picked from commit 2092742600c7c9e09067ba72ee98b32f60d949c0)
…ixture (cherry picked from commit 3e9d5c5a0a44f45e0c18f8c172b6fe2c74279e23)
(cherry picked from commit c104203bb20a763f6063242a892730940988a7ce)
(cherry picked from commit 0bf91af89b677c7bc0672a6141c61436e3a2c1ad)
…st build (cherry picked from commit 868a85fe508a4275dccb1b8c596b6fbdfced3ba8)
(cherry picked from commit f0952667900d75cc9cdb35c1d2c8b4cbe4190104)
…rules (cherry picked from commit 31a5be8c26b117387c6b39a5f0f0638b179f07c0)
…n gradle (cherry picked from commit 89cc963996347e394836ceb47240e75b0b693563)
…e set (cherry picked from commit d80fccd42f472555da8b138f35110fead6a7fd7b)
…xtures (cherry picked from commit 40b002aa3c3ad5d263a4ed503af7a1587d40209b)
(cherry picked from commit ad84359619b76558e1ec7e07d93ede96e799044f)
…nerated interfaces (cherry picked from commit e085e2fce4797f59dad66adc18f218fc0639c12e)
(cherry picked from commit 2ebf266ac7c23eccdaeca3ddf449585e95578630)
(cherry picked from commit 01e8f058d159b66a972c031189691f9ec52961ac)
(cherry picked from commit cbd61b07bade956899f2b0fc2fe7ca3986168183)
…-array args can't be type-checked
Expo installs global.expo as a non-writable property, so exposing the expo.* Java package under that name threw from CreateTopLevelNamespaces on Hermes. Fall back to the $-prefixed name already used for packages that collide with JS keywords.
The react-native target builds the Android tools jars, which compile against jars stored in Git LFS under platforms/android/test-app/**/libs. Without lfs on the Apple job's checkout, those resolve to pointer files and compileKotlin fails with "zip END header not found".
The property-getter, round-trip and expando lookup caches identified a bridge by its address. After a JS reload a new bridge can be allocated where a destroyed one lived and match its entries: the getter cache then returned a raw pointer into the freed bridge (EXC_BAD_ACCESS in preparedObjCInvocationIsInit on the next launch), and cached misses could hide values that exist.
…act-native.json (#85) The config plugin and nativescript-rn configure replaced the whole reactNative.metadata block, so every prebuild or configure run deleted a hand-written metadata.android.include. That list is what keeps classes, including autolinked module packages, in filtered Android release metadata. Merge into the existing metadata instead, accept metadata.android.include in the plugin options, and write the file on Android-only prebuilds too.
* fix(react-native): explain a missing native module at import Keep failing at import when NativeScriptNativeApi is not in the app binary, but with an error that says the package cannot run in Expo Go and that a build made before the package was added must be rebuilt, instead of React Native's generic TurboModuleRegistry invariant. Drop isAvailable() and the Android-specific init() path.
* fix(android): use $name when a Java package's global name is taken Check whether the global already has the package's name and, if it does, expose the package as `$name`, logging a warning. Both the jsi and napi trees now do the same check, so an existing global is never overwritten and no engine-specific read-only handling is needed.
With globals enabled, every key of every enum object became a global, including short member names such as Center, Fill and CloudKit's Zone. Libraries that feature-detect a global by name were misled: Angular's zoneless scheduler checks typeof Zone and crashed at bootstrap. The metadata only stored the shortened member names, so the bridge could not tell which alias was the real C constant. Metadata format v2 records, per enum, the prefix and suffix the generator stripped from its members, and flags members whose stored name is already the full C name. The reader rebuilds every member's exact C name from that; v1 metadata still loads but yields no C names. A shared alias builder (ffi/objc/shared/EnumMemberAliases.h) tags the C name among each member's aliases and is used by both the JSI bridge and the Node-API tree. The bridge exposes getEnumGlobalNames(enumName), and the shared global installer and installGlobals() in @nativescript/react-native define only those names. The Node-API tree defines only C names as globals. Short names and other conveniences stay on the enum object, which also gains the C name as a key, so constants like MKMapTypeStandard and CKErrorZoneBusy now resolve.
* refactor(android): detach through the worker teardown path Narrows the reload fix to what React Native needs: the module tracks its own install, invalidate() detaches, and detaching reuses the teardown a worker already goes through. - Runtime.detach() is detachRuntime() (formerly detachWorkerRuntime) plus Runtime::DisposeRuntime (formerly DisposeWorkerRuntime), now built in the host-runtime flavour too. The separate Runtime::Detach is gone. - ~Runtime still resets the main-runtime statics, and BuildMetadata still builds the tree once. Dropped, to land separately or not at all: throwing on a failed post to the JS thread, the GcListener delivery lock and the notifyGc change, the orphaned-object map, the LRU cache clear, the global-ref delete in ~Runtime, MethodCallbackData ownership, and their napi mirrors. * refactor(android): name napi's runtime teardown DisposeRuntime too
A GC batch could reach a worker runtime while it was being disposed: the GC thread took the runtime from the subscriber map, the worker thread unsubscribed and disposed its native Runtime, and the GC thread then entered notifyGc for it and crashed (SIGSEGV on the GC thread). Each subscriber now carries its own lock. A batch is delivered while holding that lock, and only if the subscriber is still subscribed; unsubscribe() removes the subscriber, then takes its lock to mark it unsubscribed. Once unsubscribe() returns, no GC thread is inside that runtime's notifyGc and none will enter it. The memory monitor's notifyGc() takes the same per-runtime lock instead of the listener-wide one, and unsubscribe() no longer takes the listener-wide lock, so subscribing or unsubscribing one runtime never waits on a GC delivery into another.
… dispose (#92) Disposing a runtime (a worker, or a React Native guest runtime) left three things behind: - ObjectManager's LRU cache of Java objects: ~LRUCache drops its map without calling the evict callback, so every cached weak global ref stayed in the process-wide weak reference table. Add LRUCache::clear(), which evicts each entry, and call it from ObjectManager's dispose. - The JNI global ref ~Runtime holds on the Java com.tns.Runtime. - MethodCallbackData for static methods, extension-only methods and methods added from a skipped base class. CtorFuncCache only owns the instance method data, so these were never deleted (about 180 per worker that touches a handful of classes). MetadataNodeCache now owns them and deletes them in onDisposeEnv/onDisposeRuntime. Applied to both the napi and jsi trees. Split out of #89.
…destroying (#95) DestroyRuntime set is_destroying before flushing the finalizer queue. HostObjectProxyPostFinalizer treats is_destroying as "leave the target handle", a rule carried over from napi, where js_free_napi_env frees leftover refs. engine:: handles have no such owner, so every queued proxy cleanup leaked its JS wrapper, and on QuickJS JS_FreeRuntime aborted on list_empty(&rt->gc_obj_list). The queue is normally drained on the next looper tick. A worker that calls close() from onmessage quits its looper first, which drops that tick, so any proxy collected during the handler was still queued at dispose. terminate() from the parent arrives after the tick has run. Flush the queue first, while the runtime is not yet marked destroying. Fixes #94
s_type_long_operations_cache is process-wide, but each runtime inserts its entry on init and erases it on dispose from its own thread. Make it a tns::ConcurrentMap, as MetadataNode's per-runtime cache already is, in both the napi and jsi trees. Fixes #93
…bal (#83) Without strictGlobal the Worklets plugin captures every name a worklet does not declare into its closure, read on the JS thread when the module is evaluated, where NativeScript's classes do not exist: a module-level worklet naming UILabel failed at startup with "ReferenceError: Property 'UILabel' doesn't exist". The config plugin and configure CLI now write ['react-native-worklets/plugin', { strictGlobal: true }] and ['babel-preset-expo', { worklets: false }] (so the preset adds no second copy), upgrade bare entries in an existing config, and warn when an entry with options lacks strictGlobal. The Babel setup also runs on Android-only prebuilds.
…ates A JS-subclass instance keeps its expandos when its last wrapper goes away, so its JS state survives a later re-wrap. Nothing removed them once the native object deallocated, and they are keyed by address: the next object allocated there was served the dead one's cached methods. A base-class instance then ran a JS override, and a JS-subclass instance got a stale native function that skipped its own (Base_InitBaseProtocolMethod2 and ConstructorOverrides on macOS Hermes, flaky before, deterministic once startup allocations shifted). Where expandos are preserved, attach an associated block; it is released while the object deallocates, before its memory can be reused. Dealloc can run on any thread, so it only retires the address: lookups stop seeing the values at once, and they are destroyed on a runtime thread when the address is next wrapped or written to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e patch The vendored quickjs-ng tree threw "Cannot read properties of undefined (reading 'x')", a NativeScript change to upstream's "cannot read property 'x' of undefined". Moving the source to a pinned submodule plus a patch dropped those four lines, the only local change the patch lost. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Node-API defines a NULL return from a callback as undefined, but
CallAsFunction handed the NULL JSValueRef straight to JavaScriptCore,
which reads it as null. Every void Java method returned null on Android
JSC ("returns arrays and handles void methods"). The host-object getter
already mapped NULL to undefined.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Globals on the React runtime became opt-in, so the fixture's bare init() failed with "Property 'NSObject' doesn't exist" before reaching any of its checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…bric work The spec gained installUIRuntime (replacing installWorkletRuntime), registerComponent and three test-marker methods, so the Android module no longer compiled against the generated base class. Neither UI-runtime install nor native components exist on Android yet, so both report false, which scheduleOnUI and defineNativeComponent turn into named errors. The marker methods are file-backed as on iOS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The engine-tests and performance jobs built QuickJS from the pristine submodule, so quickjs-api.c failed on the patched-in APIs (JS_WeakRef_Deref and friends). The performance job also could not check out a base that still vendors QuickJS-NG as plain files over the candidate's submodule work tree; deinit the submodule first, and patch the base only when it uses the patch layout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
expectWeakRefCleared waits up to 10s for JSC to clear the WeakRef, but Jasmine times a spec out after 5s, so a slow collection on a CI runner failed 'frees up resources after complete' with a Jasmine timeout instead of letting the poll finish. It failed this way on refactor's CI as well. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4f87f05 to
9633aba
Compare
JSC scans the stack conservatively, so a block's function can stay reachable past the 2s poll; the iOS simulator run on CI hit it. Poll for up to 10s on JSC, as the timer specs do, and raise the Jasmine timeout to cover it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CGPointMake's timed run lasts about a millisecond, shorter than a collection triggered by the garbage the earlier cases leave behind. Whether that collection lands inside the window depends on heap size, so a heap change alone moved V8's CGPointMake from 7.6 to 25-30 ns/op and failed the per-case limit. Re-running the case right after, or collecting first, gives the same 7.6 ns/op on both builds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings the
android-react-nativework intorefactor: NativeScript running inside React Native on Android, build-time Java bindings and metadata filtering for release builds, the QuickJS-NG submodule move, and the Android stability fixes from #82–#96.The branch was rebased onto
refactor, not merged. Only the 92 commits made after thejsi-native-androidwork are replayed; everything before that point is already inrefactorin its rebased form. Every conflict was resolved by hand against whatrefactorhad changed (details below). Five commits at the end fix problems the full test matrix found.What this adds
React Native on Android
EngineHostcan adopt ajsi::Runtimethe embedder owns, so NativeScript attaches to React Native's Hermes runtime instead of starting its own.@nativescript/react-native. Metadata is generated and attached to RN's runtime.@NativeClass, and@JavaProxynames for build-time generated classes.scripts/test_react_native_android.sh).Bindings, metadata and typings
Engines and build
vendor/quickjs/patchesand are applied bynpm run patch-quickjs.QUICKJSis an alias forQUICKJS_NG.scripts/. Outputs go todist/<variant>, npm tarballs tobuild/npm-tarballs.NativeScript/runtimes.Fixes
ArgConverter's long cache synchronized (fix(android): synchronize ArgConverter's per-runtime long cache #96).$nameused when a Java package's global is taken (fix(android): use $name when a Java package's global name is taken #86).metadata.androidpreserved innativescript.react-native.json(fix(react-native): keep metadata.android when writing nativescript.react-native.json #85). A clear error for a missing native module (fix(react-native): explain a missing native module at import #82). The Worklets Babel plugin configured withstrictGlobal(fix(react-native): configure the Worklets Babel plugin with strictGlobal #83).Reconciling with
refactorPlaces where both sides changed the same code:
MethodCallbackDataownership (fix(android): free runtime-owned JNI refs and method callback data on dispose #92):refactoralready tracks and frees everyMethodCallbackDatain the napi tree, so fix(android): free runtime-owned JNI refs and method callback data on dispose #92's napi half is dropped. Keeping both would have registered extension-method data twice and double-freed it on dispose. fix(android): free runtime-owned JNI refs and method callback data on dispose #92's jsi-tree changes are kept.refactor's restructured caches, including the Hermes weak round-trip path.refactor'sbundleModeand Metro config are kept, andstrictGlobal: trueis now always written. Only bare plugin entries are upgraded.refactor's old upgrade regex could corrupt['plugin', {...}]entries.ffi/objc/shared/, where every engine includes it from. With the oldhermes/path, Hermes builds silently lose their generated fast paths.NativeScriptUIViewComponentViewis dropped.refactorreplaced that view with the extensible Fabric components, andNativeScriptComponentViewalready compares againstoldProps.refactoralready had an equivalent fix, its version is kept. Examples:NullTerminatedLengthinjsc-api.cpp, the deleted duplicatelibzip.aarchives, and the existingm_runtimerelease in the napi~Runtime.Fixes found while testing
refactor. A JS-subclass instance keeps its expandos (including cached methods) after its last wrapper is released, keyed by address, and nothing removed them when the object deallocated. The next object at that address got the dead object's methods: a base-class instance ran a JS override, or a subclass instance skipped its own. It failed about 1 run in 3 onrefactor(ConstructorOverrides: prototype), and every time once the startup allocation pattern changed. Retiring happens at dealloc on any thread. Engine values are only destroyed on a runtime thread.Cannot read properties of undefined (reading 'x')messages. Those four lines were the only local change missing from the patch file.CallAsFunctionpassed a NULL result to JavaScriptCore, which reads it asnull. EveryvoidJava method returnednullon Android JSC.init(), but globals are opt-in on the React runtime, so it failed onNSObjectbefore running any checks.installUIRuntime,registerComponent, test-marker methods), so the Android module no longer compiled. Neither feature exists on Android yet, so both returnfalse, whichscheduleOnUIanddefineNativeComponentturn into named errors.Each failure was checked with a control run before it was fixed. The QuickJS and JSC failures also occur on the
android-react-nativetip. The expando bug also occurs onrefactor.Testing
All on the final commit.
test_react_native_turbomodule.sh)Notes for reviewers
git submodule update --init vendor/quickjs/source_ng && npm run patch-quickjsbefore any QuickJS build, Apple included. The RN package refuses to pack untilscripts/build_react_native_android_tools.shandscripts/build_react_native_android.shhave staged the Android half.SetMissingBaseMethodsnever resetscallbackData.refactorfixed this only in the napi tree.NativeScript/runtime/android/workers/holds seven uncompiled files left over from before the napi/jsi split.init().🤖 Generated with Claude Code