Summary: Looks like FBJNI exports a C Macro that does exactly what `throwIfJNIReportsPendingException` does. Therefore, I'm replacing `throwIfJNIReportsPendingException` with calls to `FACEBOOK_JNI_THROW_PENDING_EXCEPTION()`.
Reviewed By: mdvacca
Differential Revision: D15174820
fbshipit-source-id: 9dfb519352cbd5f37527675323cbabad05e31d4a
Summary:
`jclass` in `JNI` is just a regular local reference. Therefore, it's unsafe to keep a static reference to it. Link: http://journals.ecs.soton.ac.uk/java/tutorial/native1.1/implementing/refs.html.
This bug made it so that when you clicked on `getConstants` twice in the TurboModule playground, the app would crash.
Reviewed By: mdvacca
Differential Revision: D15174821
fbshipit-source-id: 13b2b8726473acc9b07306558044d26bed0db92d
Summary: Previously, we'd override the `TurboModule::get` method inside the `JavaTurboModule` class to return a special `jsi::Function` in the case that the property being accessed was "getConstants". We really don't need to do this because we can simply special-case the invocation of the `getConstants` method inside the `JavaTurboModule::invokeJavaMethod` method.
Reviewed By: mdvacca
Differential Revision: D15174822
fbshipit-source-id: 0ee705be841757d3870c908da911c3872b977a9f
Summary:
* invokeMethod() ends up not useful because each platform has its own way of invoking the platform methods
* invalidate() is not necessary because there's already the destructor of each C++ class
Reviewed By: mdvacca
Differential Revision: D15187833
fbshipit-source-id: 9478ed1e6288da30c67179e03a7bc7da6043280b
Summary: This adds support for specifying the exact selector name for each exposed ObjC method. This allows us to avoid dynamic method lookup every time a method is called from JS.
Reviewed By: RSNara
Differential Revision: D15141611
fbshipit-source-id: ed2820782ab013369e4e1f22dbce31d9838a17bb
Summary: In `ObjCTurboModule::getArgumentTypeName`, I replaced all instances of `':'` with `''` to transform the selector into a TurboModule methodName. This transformation works when the method has 0 or 1 argument, however, it breaks when the method has more than 1 argument. In all cases, we just want to get the substring until the first `':'`.
Reviewed By: fkgozali
Differential Revision: D15056937
fbshipit-source-id: 3a7dce1ce62ca9758e46c0af951b269166d68454
Summary: `jClassName_` is unnecessary because you can use `JNIEnv::GetObjectClass` to get the TurboModule's Java class.
Reviewed By: fkgozali
Differential Revision: D14937480
fbshipit-source-id: 2c1c9be53217331152270dbac3d13f372a2ed818
Summary: This installs the sample module to the RNTester.xcodeproj without using any TurboModule infra. This is possible because SampleTurboModule is backward compatible with the existing NativeModules system. This also fixes CI test failure: https://circleci.com/gh/facebook/react-native/84752
Reviewed By: RSNara
Differential Revision: D14987572
fbshipit-source-id: f5f2c4330c7f6558c7d4beeb43198869090dee02
Summary:
If you call into a Java method (from C++ using JNI) that raises an exception, the JNI call won't actually raise a C++ error. Instead, the `JNIEnv` will record the pending Java exception and the C++ will continue executing as normal. This is bad because the next time you call into JNI, the app will actually crash, unless you explicitly cleared the exception using `JNIEnv::ExceptionClear()` before the JNI call.
With respect to TurboModules, we need to make sure that RedBoxes show up whenever a native methods raise an exception. We also don't want the app to crash when a JNI method call fails because of a raised exception. Therefore, in this diff, I raise a C++ exception if `JNIEnv::ExceptionCheck()` is true.
Reviewed By: mdvacca
Differential Revision: D14738540
fbshipit-source-id: 4c3063aa93ae7aef025bd2dab6b45059bb8fb409
Summary:
If the return type of a TurboModule method is `Promise`, the infra should create a `com.facebook.react.bridge.Promise` object and pass it as the final argument of the TurboModule Java method call. The Java TurboModule method can then do some work asynchronously and either resolve or reject the promise at some point in time.
**Note:** I stacked a diff for error handling on top of this one.
Reviewed By: mdvacca
Differential Revision: D13653156
fbshipit-source-id: 4c30c3223ad8f47c6ba7f1236527aaced01c8ae8
Summary: This sets up RCTSampleTurboModule (and other variants) in RNTester when built with cocoapods. There's no call site yet though. And RNTester.xcodeproj doesn't support it.
Reviewed By: cpojer
Differential Revision: D14932535
fbshipit-source-id: db8eafd6777cbec8f3592dafdccbdd7cf44e38bc
Summary:
This provides various versions of SampleTurboModule, that are:
* compatible with existing NativeModule
* TurboModule compliant
Variants:
* RCTSampleTurboModule (traditional objc module)
* RCTSampleTurboCxxModule (objc++ module using CxxModule)
* SampleTurboModule (pure C++ impl of a TurboModule, no ObjC)
As noted in some files, they need to be codegen'ed based on the `NativeSampleTurboModule.js` (Flow type). The codegen script is not yet usable in OSS (we'll work on it some time in H2 2019). For now, these files need to be manually synced with Flow type.
Reviewed By: cpojer
Differential Revision: D14932539
fbshipit-source-id: fb887192384e5e6e4dff4cac68b4e037a4783cd9
Summary:
For CocoaPods variant only: install TurboModule binding so that sample modules can start using it. This commit only installs `global.__turboModuleProxy` - no sample module is provided.
Note: RNTester.xcodeproj will NOT have TurboModule enabled, due to complication in the .xcodeproj setup (doable, but maybe for some other time...)
To test:
```
console.error(global.__turboModuleProxy == null ? 'BOO' : 'YAY!');
```
Saw `YAY!` in RNTester pod version.
Reviewed By: cpojer
Differential Revision: D14932536
fbshipit-source-id: 3dc083da9154ec320ce6789ec7f2cef5a08fd6a7
Summary:
There is a timing issue when reloading the bridge (in dev mode) and the tear down of the TurboModules. This causes `Instance` to never get freed, hence the "bridge" isn't cleaning up properly. The side effect can be bogus error saying that it's unable to find a module.
To address this, JSCallInvoker should just take in weak_ptr<Instance>.
Reviewed By: RSNara
Differential Revision: D14739181
fbshipit-source-id: f9f2a55486debaeb28d3d293df3cf1d3f6b9a031
Summary:
If a NativeModule method requires an optional boolean argument, our codegen translates those optional booleans into `NSNumber*` instead of `BOOL`. The reason why is probably because this is the closest object analogue to `BOOL` in Objective C. The same boxing occurs with numbers. If the type of a number argument in JavaScript is optional, we'll map it to the `NSNumber*` Objective C type instead of `double`. Our existing TurboModules argument conversion code would not take this behaviour into account. Therefore, we'd try to insert a `BOOL` where the `NSInvocation` would expect a `NSNumber*`. This, in turn, would cause the app to crash. (Why would it crash at the point of NSInvocation retainArguments, I'm still not sure).
Our flow typechecking ensures that if the type of a method argument is a boolean, we pass in a boolean. Therefore, on the Native side, if we detect a boolean, we can check the type of the Native argument to see whether we should box the primitive. If the native argument type is an object, then we know it has to be an `NSNumber*` in both cases, so we simply wrap the `BOOL` or `double` in a `NSNumber*`.
Reviewed By: shergin
Differential Revision: D14679590
fbshipit-source-id: c394a878492aab8e98c71d74ec8740a94fc3a6c5
Summary: When calling into JS (e.g. promise resolve/reject, callback) in TurboModule, we bypass the bridge's message queue. At times this causes race condition, where there are a bunch of pending UI operations (in RCTUImanager) waiting to be flushed, but nothing adds calls to the message queue. Usually tapping the screen will trigger the flush because we're sending down touch events to JS.
Reviewed By: JoshuaGross
Differential Revision: D14656466
fbshipit-source-id: cb3a174e97542bf80f0a37b4170b6a8e6780fa35
Summary:
In D14571128, we made it so that when a JS object's property was `undefined`, we wouldn't insert that property into the corresponding NSDictionary. Here are two important observations about that diff:
1. ALL JS `null`s were now being converted to `NSNull`, and JS `undefined`s were now being converted to `nil`.
2. If a JS object's property was explicitly `null`, then we'd insert `NSNull` into the corresponding dictionary.
Considering that when a property doesn't exist in a `NSDictionary`, property access returns `nil`, I've made it so that if a JS object's property is either `null` or `undefined`, then we simply do not insert it in the corresponding `NSDictionary`. Also, I've reverted #1 and made it so that `undefined` and `null` always map to the ObjC `nil`.
This shouldn't unfix the problem that D14571128 was trying to fix.
Here's my understanding of the problem that D14571128 was trying to fix (to make sure I'm not breaking something by this diff).
This method was invoked from JS.
```
RCT_EXPORT_METHOD(logEvents:(NSDictionary *)events)
{
RCTAssert(events, @"You should provide events for logger");
[[NSNotificationCenter defaultCenter] postNotificationName:@"FBReactPerfLoggerDidReceiveEventsNotification"
object:nil
userInfo:@{@"FBReactPerfLoggerUserInfoPerfEventsKey" : [events copy]}];
}
```
The above dispatch calls into this method, which appends `events` into `_pendingJSPerfEvents`.
```
- (void)reactPerfLoggerDidReceiveEvents:(NSNotification *)notification
{
NSDictionary *events = notification.userInfo[@"FBReactPerfLoggerUserInfoPerfEventsKey"];
if (events) {
dispatch_async(_eventQueue, ^{
if (self->_sessionData.perfLoggerFlagId != nil) {
if ([self processJSPerfEvents:events]) {
[self reportMetricsIfFinished];
}
} else {
[self->_pendingJSPerfEvents addObject:events];
}
});
}
}
```
Then, in `_processJSPerfEvents`, we do the following (link: https://fburl.com/tr4wr2a7):
```
NSNumber *actionId = events[@"actionId"];
if (actionId) {
self->_sessionData.actionId = actionId;
}
```
So, if `undefined` or `null` was passed in as the `actionId` property of the `events` JS object in `FBReactPerformanceLogger logEvents:`, then we'd default the `NSDictionary` to have `NSNull` in the corresponding property. This is bad because we had this line in FBReactWildePerfLogger (link: https://fburl.com/2nsywl2n): `actionId ? [actionId shortValue] : PerfLoggerActions.SUCCESS`. Essentially, this is the same problem that my diff is trying to fix.
Reviewed By: fkgozali
Differential Revision: D14625287
fbshipit-source-id: c701d4b6172484cee62494256175e8b205b23c73
Summary:
With our current infra, we support automatic conversion of method arguments using `RCTConvert`.
```
RCT_EXPORT_METHOD(foo:(RCTSound*) sound)
{
//...
}
```
```
interface RCTConvert (RCTSound)
+ (RCTSound *) RCTSound: (NSDictionary *) dict;
end
implementation RCTConvert (RCTSound)
+ (RCTSound *) RCTSound: (NSDictionary *) dict
{
//...
}
end
```
```
export interface Spec extends TurboModule {
+foo: (dict: Object) => void,
}
```
With this setup, when we call the foo method on the TurboModule in JS, we'd first convert `dict` from a JS Object to an `NSDictionary`. Then, because the `foo` method has an argument of type`RCTSound*`, and because `RCTConvert` has a method called `RCTSound`, before we invoke the `foo` NativeModule native method, we first convert the `NSDictionary` to `RCTSound` using `[RCTConvert RCTSound:obj]`. Essentially, if an argument type of a TurboModule method is neither a primitive type nor a struct (i.e: is an identifier), and it corresponds to a selector on `RCTConvert`, we call `[RCTConvert argumentType:obj]` to convert `obj` to the type `argumentType` before passing in `obj` as an argument to the NativeModule method call.
**Note:** I originally planned on using `NSMethodSignature` to get the argument types. Unfortunately, while the Objective C Runtime lets us know that the type is an identifier, it doesn't inform us which identifier it is. In other words, at runtime, we can't determine whether identifier represents `RCTSound *` or some other Objective C class. I figure this also the reason why the old code relies on the `RCT_EXPORT_METHOD` macros to implement this very same feature: https://git.io/fjJsC. It uses `NSMethodSignature` to switch on the argument type, and then uses the `RCTMethodInfo` struct to parse the argument type name, from which it constructs the RCTConvert selector.
One caveat of the current solution is that it won't work work unless we decorate our TurboModule methods with `RCT_EXPORT_METHOD`.
Reviewed By: fkgozali
Differential Revision: D14582661
fbshipit-source-id: 3c7dfb2059f031dba7495f12cbdf406b14f0b5b4
Summary:
Before invoking TurboModule ObjC methods, we loop through all the arguments and transform them from `jsi::Value` to ObjC data structures. `jsi::Value`s that represent JS Objects get converted to `NSDictionary`s in ObjC. This isn't good enough because `NSDictionary` isn't typed. What we really need is a C/C++ struct that represents the type of the JS Object.
Therefore, for every argument of a TurboModule method that is a JS Object, this diff allows you to specify a method on `RCTCxxConvert` (which you have to declare and implement) that transforms that type of JS Object into something else. Basically, with the changes in this diff, we'll be able to transform JS Objects into C++ struct instances, which gives us type safety for JS Object method argumetns to TurboModule functions.
I modified the codegen to also create a mapping from NativeModule method name => argument num => `RCTCxxModule` conversion selector. This way, the FB codegen that generates the `RCTCxxConversion` function also informs our TurboModule system which conversion function to use before we call the method requiring complex argument. I just had to extend the `ObjCTurboModule` class to accomplish this.
The old system relies on the additional method generated by `RCT_EXPORT_METHOD` macro. It takes the written code, does string processing on it to parse the type of the struct arguments, and then replaces all instances of `::` with `_`. Super hacky. I didn't take this approach because it seemed unnecessarily hacky brittle.
The other approach I considered was to try to use reflection to infer the type of the struct at runtime. We'd have to do a bit of string processing and concat the TurboModule class name with the struct type. The solution would be simpler because it'd only modify the objective C and it wouldn't touch the Codegen system. **Edit:** I implemented it here: D14513078. The one downside of this design is that the name of the conversion function is individually constructed in two different locations in the code (i.e: no source of truth for this name). The other downside is that we have to rely on `RCTBridgeModuleNameForClass`, which we want to eliminate. This also computationally more expensive since it requires string processing/regex parsing and additional reflection. Therefore, we decided to move on with the current solution.
Reviewed By: fkgozali
Differential Revision: D14513429
fbshipit-source-id: 3d1b87e02ee908a19305686ff82b2ed624d8ac67
Summary: [iOS] [Fixed] - The existing logic defaults `undefined` & `null` in JS to be `[NSNull null]` when converting JS object to `NSDictionary`. Let's not insert the prop to the dictionary if it's `undefined`.
Reviewed By: blairvanderhoof
Differential Revision: D14571128
fbshipit-source-id: e03c713b055672b0a001d3305d694912ee36ab36
Summary:
When TurboModules are created, we need to broadcast this event using `NSNotificationCenter`. This is important because a number of TurboModules/ObjC classes listen to this notification and perform side effects.
I found these using https://fburl.com/codesearch/j0a9p3dr.
- FBReactKitServerSnapshotTests: After the RCTExceptionsManager is initialized, it assigns itself as the delegate to the RCTExceptionsManager instance.
- FBProfilePictureUploadFlowController: After FBProfileFrameNativeModule is initialized, it calls `[module setFlowController: self]`
- RCTFBSession: After every NativeModule is initialized, if that NativeModule conforms to the RCTFBSessionModule protocol, assign the session to it.
- TwilightLocalMedia: Hack to figure out when the bridge is initialized.
- FBMarketplaceMenuItemHandler: After FBMarketplaceNativeModule is initialized, attach a data source obtained from the session to the module. Then, attach module to data source.
- RCTModuleInitTests: Set local state based on if certain modules are initialized (https://fburl.com/e34kezb5).
- IGReactModule: When the RCTImageLoader is initialized, set it's image cache to `[IGReactImageCache new]`.
- RCTIGUserSession: Like RCTFBSession
- FBReactModuleTracker: This keeps a list of all dispatched notifications (for some reason).
- RCTModuleInitNotificationRaceTests: Sets self.didDetectViewManagerInit to true if RCTTestViewManager is initialized.
Reviewed By: fkgozali
Differential Revision: D14281557
fbshipit-source-id: 9fcf1bb5f5650fcc3a3e50c6d93405b0b618e271
Summary: [IOS] [FIXED] - This may cause weird race condition if JSI is accessed via multiple threads within the same method invocation. Since only the objc class methods need to be executed on the right queue, we can keep the conversion to jsi::Value in the originating JS thread.
Reviewed By: RSNara
Differential Revision: D14277167
fbshipit-source-id: c506aebb71e190e2afcbf19dce05088ce2b97833
Summary: Let JS decide if a missing method should be treated as an error, or whether it allows optional methods (e.g. methods that are only for android or for ios).
Reviewed By: JoshuaGross
Differential Revision: D14155799
fbshipit-source-id: 1e298b46a59761cf09e98147da885b1e9a9a675a
Summary:
Some modules may have args with types stricter than just `NSDictionary`. For now, allow these modules to manually define a __turbo__* variant of the same method that receives basic NSDictionary/NSArray typed args. That helper method can do the conversion to the stricter type as necessary. This is only needed during migration phase of TurboModule.
Without this workaround, existing methods may get unrecognized selector errors.
Reviewed By: PeteTheHeat
Differential Revision: D14115981
fbshipit-source-id: ca7fcf497490ef9cce14c3d3444991c50d3cb550
Summary: Depending on the timing of the method call from JS to a CxxModule, we may be accessing memory that has been deallocated, causing exception to RN runtime. This fixes it.
Reviewed By: JoshuaGross, mdvacca
Differential Revision: D14033831
fbshipit-source-id: 5a77aa41223b1fc3146dcf78b7f8e93375605d6d
Summary: Similar macro as the existing one, but this one checks for the class directly.
Reviewed By: RSNara
Differential Revision: D14016664
fbshipit-source-id: aae9a9c1cc95f56d2eff6c9021a714ed4a843db3
Summary: At times, the lookup logic may find a class that's not compliant with RCTTurboModule. If so, it shouldn't be instantiated, and we assume the module doesn't exist.
Reviewed By: JoshuaGross, RSNara
Differential Revision: D13979004
fbshipit-source-id: ac1fa9cc456715cddd101fff13f5a41f9528a74e
Summary: Simple macro to do check if TurboModule is enabled and the particular object is RCTTurboModule compliant.
Reviewed By: PeteTheHeat
Differential Revision: D13978368
fbshipit-source-id: 660c7cab7bb074d80d57abead951dad19306ae73
Summary: Just tightened up the lookup logic to guard against nullptr.
Reviewed By: RSNara
Differential Revision: D13948001
fbshipit-source-id: 55bf46619712e60e195ea12f2b8eb799f80a3bbe
Summary:
It's okay for TurboModules to not exist. Therefore, `__turboModuleProvider(moduleName)` should return null if `moduleName` doesn't refer to an actual TurboModule. The current implementation assumes that the TurboModule must exist, and therefore, it crashes the app when it doesn't (assertion error).
This change is important because it helps us land D13887962. When we switch `I18nResources` to `TurboModuleRegistry` in D13887962, on Wilde, it won't be found in `NativeModules`. Therefore, we'll try to lookup this module in `__turboModuleProvider`, and this will crash the app. It seems like `I18nResources` is a Java-only module?
Reviewed By: fkgozali
Differential Revision: D13924589
fbshipit-source-id: 7ac7b1873e06852e5aafcaaef5c24cbc548ee444
Summary:
NativeModules are instantiated by the bridge. If they choose, they can capture the bridge instance that instantiated them. From within the NativeModule, the bridge can then be used to lookup other NativeModules. TurboModules have no way to do such a lookup.
Both NativeModules and TurboModules need to be able to query for one another. Therefore, we have four cases:
1. NativeModule accesses NativeModule.
2. NativeModule accesses TurboModule.
3. TurboModule accesses NativeModule.
4. TurboModule accesses TurboModule.
In summary, this solution extends the bridge to support querying TurboModules. It also introduces a `RCTTurboModuleLookupDelegate` protocol, which, implemented by `RCTTurboModuleManager`, supports querying TurboModules:
```
protocol RCTTurboModuleLookupDelegate <NSObject>
- (id)moduleForName:(NSString *)moduleName;
- (id)moduleForName:(NSString *)moduleName warnOnLookupFailure:(BOOL)warnOnLookupFailure;
- (BOOL)moduleIsInitialized:(NSString *)moduleName
end
```
If TurboModules want to query other TurboModules, then they need to implement this protocol and synthesize `turboModuleLookupDelegate`:
```
protocol RCTTurboModuleWithLookupCapabilities
property (nonatomic, weak) id<RCTTurboModuleLookupDelegate> turboModuleLookupDelegate;
end
```
NativeModules will continue to use `RCTBridge` to access other NativeModules. Nothing needs to change.
When we attach the bridge to `RCTTurboModuleManager`, we also attach `RCTTurboModuleManager` to the bridge as a `RCTTurboModuleLookupDelegate`. This allows the bridge to query TurboModules, which enables our NativeModules to transparently (i.e: without any NativeModule code modification) query TurboModules.
In an ideal world, all modules would be TurboModules. Until then, we're going to require that TurboModules use the bridge to query for NativeModules or TurboModules.
`RCTTurboModuleManager` keeps a map of all TurboModules that we instantiated. We simply search in this map and return the TurboModule.
This setup allows us to switch NativeModules to TurboModules without compromising their ability to use the bridge to search for other NativeModules (and TurboModules). When we write new TurboModules, we can have them use `RCTTurboModuleLookupDelegate` to do access other TurboModules. Eventually, after we migrate all NativeModules to TurboModules, we can migrate all old callsites to use `RCTTurboModuleLookupDelegate`.
Reviewed By: fkgozali
Differential Revision: D13553186
fbshipit-source-id: 4d0488eef081332c8b70782e1337eccf10717dae
Summary:
For better modularity, each module conforming to RCTTurboModule should provide a getter for the specific TurboModule instance for itself. This is a bit more extra work for devs, but simplify tooling and allow better modularity vs having a central function that provides the correct instance based on name.
Note: Android may or may not follow this new pattern -- TBD.
Reviewed By: RSNara
Differential Revision: D13882073
fbshipit-source-id: 6d5f82af67278c39c43c4f7970995690d4a82a98
Summary:
This commit moves all the turbo module files for Android to Github.
Note that gradle build is not yet enabled.
Sample Turbo Modules will be added in a later commit.
Other missing features
- Support for `CxxModule`
- Remove usage of folly::dynamic for arguments and result conversion
- Support for Promise return types.
Reviewed By: mdvacca
Differential Revision: D13647438
fbshipit-source-id: 5f1188556d6c64bfa2b2fd2146ac72b0fb456891
Summary:
The original commit was backed out due to a build failure, which seems to be due to compiler flag misconfiguration. That has been fixed.
Original commit: f2fccbb327
Reviewed By: mdvacca
Differential Revision: D13593075
fbshipit-source-id: f3a65e8bd9830f6ac2ea829020500b4632ee2149
Summary:
This is the iOS binding for TurboModule.
To install the TurboModule binding:
* Provide `RCTCxxBridgeDelegate`
* Provide `RCTTurboModuleManagerDelegate`
Somewhere in `RCTCxxBridgeDelegate` impl:
```
RCTTurboModuleManager *manager = [[RCTTurboModuleManager alloc] initWithRuntime:&runtime bridge:bridge delegate:self];
[manager installJSBinding];
```
Doing so will install `global.__turboModuleProxy()` in JS space.
Note:
* The full instructions will be provided once all pieces are moved to OSS.
* Sample modules and binding setup will be provided later.
Reviewed By: RSNara
Differential Revision: D13583442
fbshipit-source-id: bb1cabd973e8a9ec59da6b145826e9ea234a96b3
Summary:
This is only the core C++ part of TurboModule - moving to github to make integration with existing NativeModules system slightly easier. Other bindings for iOS/Android are not yet ready to move.
Notes:
* TurboModules is not ready to use at the moment.
* Build configuration is not yet provided (cocoapods/.xcodeproj/gradle), just like Fabric.
* No effort was done to make this lib C++17 strictly compliant yet (there will be in the future).
Reviewed By: RSNara
Differential Revision: D13551211
fbshipit-source-id: cd3b458e6746ee9218451962ca65b1ad641a32db