Repository navigation
fix(react-native): bugs reported from ng-native - #77
Open
NathanWalker wants to merge 15 commits into
Open
NathanWalker wants to merge 15 commits into
NathanWalker wants to merge 15 commits into
Conversation
…when it matches its last one Fabric keeps _props across prepareForRecycle while the container is cleared, so diffing against _props skipped setNativeViewHandle when a recycled host was mounted with the handle it last showed, leaving it empty. Diff against the oldProps argument instead, which is null on a fresh mount.
react-native-worklets shares host objects across runtimes, so a native object returned from runOnUI is finalized by the JS runtime after the UI runtime is gone. Its destructor released the UI bridge's expandos and caches into the dead runtime, crashing every JS reload (EXC_BAD_ACCESS in NativeApiBridge::forgetObjectExpandos during HadesGC::finalizeAll). The bridge's API host object now marks the bridge torn down as its runtime is finalized; from then on Values the bridge drops are leaked.
install() returned early on a process-wide flag, so after a reload the new runtime had none of NativeScript's globals while isInstalled() reported true. The old com.tns.Runtime also stayed subscribed to GC notifications and released handles into the destroyed jsi::Runtime. - isInstalled()/install() are per host jsi::Runtime. - invalidate() detaches: releases every handle while the runtime is still alive, unregisters it, and resets main-runtime state so the next attach binds to the new JS thread. Metadata is built once per process. - GC notifications for a detached runtime are no-ops at every layer. - A Java callback into JavaScript that was reloaded away logs a warning and returns a default instead of throwing on the calling thread.
Every key of an enum object became a global, including the short member
names (Center, Fill, CloudKit's Zone). Libraries that feature-detect a
global by name were misled: Angular's zoneless scheduler checks
typeof Zone and crashed at bootstrap after init({ globals: true }).
Only full C constant names (NSTextAlignmentCenter, NSOrderedAscending,
kCLAuthorizationStatusRestricted) are defined as globals now, in both
the JS installGlobals() and the shared global installer used by the
Worklets UI runtime. Short names stay on their enum.
- The TurboModule is looked up with TurboModuleRegistry.get, so importing the package in Expo Go or a build made before it was added no longer raises an uncatchable invariant. isAvailable() reports whether the module is present; init() and the other functions that need it throw an error saying to rebuild. - init() no longer installs into a Worklets runtime on Android, where there is none, so it returns true instead of throwing a message about missing react-native-worklets setup. runOnUI()/installWorklets() on Android say they are iOS only. - README: the Android example uses NativeScript.init(), and the threads section states that Android views cannot be touched from JavaScript.
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. Documented in the README.
….extend - The emitter wrote nullable callbacks as `() => void | null`, which TypeScript reads as a function returning void | null, so passing null (e.g. presentViewControllerAnimatedCompletion's completion) was a type error. They are parenthesized now. - extend() works on every native class at runtime but was undeclared. NativeObject.extend types overridden methods from the class, accepts protocol/exposed methods, and takes objc.ExtendOptions. - README: Android declarations are generated by nativescript.gradle by default from the app's classpath; @nativescript/types-android is not needed.
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.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…eact-native-reported-issues
Put requirements and installation first and move the monorepo build
scripts to a closing section. Document that iOS JS-thread globals are
opt-in via init("", { globals: true }), Android release metadata
filtering, and the Android steps of configure and the Expo plugin.
Fix examples that touched UIKit or undefined names off the UI runtime.
…act-native.json 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. Document the include list in the README.
…nfigure nativescript.gradle generates them unless -PnsGenerateTypings=false, so the next-steps hint no longer calls them on demand or passes =true.
A guest's JS thread is not the main thread, so no JavaScript could touch an Android view. The main thread now borrows the jsi::Runtime while the JS thread waits, as React Native's executeSynchronouslyOnSameThread does: - Java->JS callbacks made on the main thread (a view's listeners) run there instead of being posted to the JS thread. - __nativeScriptRunOnMainThread(fn) runs fn on the main thread from the JS thread, which lends the runtime until it returns. - A JS thread parked inside a Java->JS call releases the EngineHost lock for the borrower and takes it back after. - Callbacks delivered as plain looper messages drain microtasks, which no React Native task would otherwise do. Also installs __nativeScriptAndroidViewHandle, __nativeScriptAndroidContext and __nativeScriptIsMainThread for the JS API.
…View The Fabric component was registered on Android with no view manager. It now hosts the view its nativeViewHandle names, sized to the host, moving it from any previous host.
…ndroid runOnUI runs its callback on the Android main thread instead of throwing. nativeViewHandle(view) names a view for NativeScriptUIView on both platforms, androidContext() is the Context to build a view with, and isMainThread() answers on Android. The README's Android section describes the threading model and how to show a view.
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the
@nativescript/react-nativebugs reported in the "NativeScript bugs" section of ng-native/ng-native#931. The numbers below match that list.Fixes
1. iOS: a recycled
NativeScriptUIViewstayed empty when remounted with its last handle.updatePropsdiffed against_props, which Fabric keeps acrossprepareForRecyclewhile the container is cleared. It now diffs against theoldPropsargument.RCTMountingManagerpassesnullptron a fresh mount, which maps to the default props._propsis deliberately left alone, because RN's ownprepareForRecyclediffs layer state against it.2. iOS: a native object returned from a worklet crashed the next JS reload. react-native-worklets shares host objects between runtimes, so the JS runtime finalizes the UI bridge's host object after the UI runtime is gone, and the bridge then released
jsi::Values into a dead runtime.ObjCBridge.mmidentified a bridge by its memory address. A bridge allocated where a destroyed one used to live matched that bridge's cached entries, and the property-getter cache holds raw pointers into it. Each bridge now has a process-unique ID instead.3. Android: after a JS reload, NativeScript said it was installed but the new runtime had none of its globals.
install()andisInstalled()now work per hostjsi::Runtimeinstead of using a process-wide flag.invalidate()detaches the old runtime. RN 0.86 calls it after the JS thread has stopped and before the runtime is destroyed.4. Android: SIGSEGV in NativeScript's GC after a reload.
notifyGcFastno longer throws on the GC listener's thread.postDelayedRunnable) now logs a warning and returns the default for its return type, instead of calling into the destroyed runtime.5. The Worklets Babel plugin was not configured with
strictGlobal.nativescript-rn configurenow write['react-native-worklets/plugin', { strictGlobal: true }]and['babel-preset-expo', { worklets: false }].strictGlobal.6.
init("", { globals: true })made every enum member a bare global (Zone,Center). Only full C constant names become globals now, for exampleNSTextAlignmentCenter,NSOrderedAscendingandkCLAuthorizationStatusRestricted. Short names stay on their enum. The same rule applies to the JSinstallGlobals()and to the shared global installer that the Worklets UI runtime uses. It is consistent with the runtime'sEnumConstantsAsGlobalVariablestest.7. Android:
init()threw a misleading "could not install into the Worklets UI runtime".init()no longer tries the Worklets install on Android, so it returnstrue.runOnUI()andinstallWorklets()on Android now say they are iOS only.8. Importing the package without its native module raised an error no
try/catchcould catch. The module is looked up withTurboModuleRegistry.get.NativeScript.isAvailable()reports whether the module is present.isInstalled()returnsfalsewhen it isn't.init()and the other functions that need the module throw an error saying to rebuild.9. Generated typings.
(() => void) | null.NativeObject.extendis declared, withobjc.ExtendOptions.nativescript.gradlegenerates them from the app's classpath by default, so@nativescript/types-androidis not needed.Also in this PR
README rewritten for npm readers.
0.0.1tarball steps are gone.init("", { globals: true }). The examples that touched UIKit or undefined names off the UI runtime are fixed.metadata.android.include, and the Android steps ofconfigureand the Expo plugin.metadata.android.includewas deleted by every prebuild andconfigurerun.ensureMetadataConfigreplaced the wholereactNative.metadatablock innativescript.react-native.json. That list is the only way to keep a class in filtered Android release metadata when the bundle analysis cannot see it. It is also the only way to keep autolinked module packages, because the release blacklist beatswhitelist.mdg.metadata.android.includein its options.nativescript-rn configuresaid Android typings were generated on demand.nativescript.gradlegenerates them by default, so the hint no longer passes-PnsGenerateTypings=true.Not in this PR
10. Android has no Worklets runtime, so Android views can't be touched. This needs real work, not a patch:
WorkletRuntimeHolder, scheduled onto the main thread.The README now states the limitation. A follow-up could add a smaller "run on main and wait" call.
Also left for follow-ups:
runOnUIand then used on the JS thread still runs through the UI bridge. This PR only makes teardown safe; re-wrapping host objects as they cross runtimes would be the full fix.Testing
superviewis set.UIViewis held in JS, with no crash reports.ZoneandCenterareundefinedon both the JS runtime and the UI runtime, and full names are numbers.UILabelloads.isInstalled() === false,install()works, andtypeof java === 'object'.Runnableand aHandler.Callbackposted before a reload are dropped with a warning after it, and a callback whose runtime is still alive still runs.tsc --strictagainst freshly generated iOS 27.1 declarations coveringpresentViewControllerAnimatedCompletion(vc, true, null)andextend(). They fail before this branch and pass with it.config-pluginandenum-member-globalscases. Theconfig-plugincases include a hand-writtenmetadata.androidblock surviving a run and plugin options setting it; both fail before this branch.invalidate()path for it posts the detach to the JS thread.