Use WeakObject for new turbomodule binding mechanism

Summary:
A previous version of this experiment saw crashes on iOS once rolled out. Switching to WeakObject to make sure we're not accessing invalid references, and setting up the ability to experiment with this on iOS.

Changelog: [Internal]

Reviewed By: cipolleschi

Differential Revision: D41552667

fbshipit-source-id: dc0c54edc2ad18c1947941119ffd50038a47c5f6
This commit is contained in:
Pieter De Baets
2023-03-07 01:17:44 -08:00
committed by Facebook GitHub Bot
parent c28c6f2531
commit b73dd6726d
7 changed files with 51 additions and 28 deletions
@@ -111,7 +111,12 @@ jsi::Value TurboCxxModule::get(
// If we have a JS wrapper, cache the result of this lookup
if (jsRepresentation_) {
jsRepresentation_->setProperty(runtime, propName, result);
auto jsRepresentation = jsRepresentation_->lock(runtime);
if (!jsRepresentation.isUndefined()) {
std::move(jsRepresentation)
.asObject(runtime)
.setProperty(runtime, propName, result);
}
}
return result;
@@ -31,7 +31,8 @@ jsi::Value TurboModule::get(
// If we have a JS wrapper, cache the result of this lookup
// We don't cache misses, to allow for methodMap_ to dynamically be extended
if (jsRepresentation_) {
jsRepresentation_->setProperty(runtime, propName, result);
jsRepresentation_->lock(runtime).asObject(runtime).setProperty(
runtime, propName, result);
}
return result;
}
@@ -106,7 +106,7 @@ class JSI_EXPORT TurboModule : public facebook::jsi::HostObject {
private:
friend class TurboCxxModule;
friend class TurboModuleBinding;
std::unique_ptr<jsi::Object> jsRepresentation_;
std::unique_ptr<jsi::WeakObject> jsRepresentation_;
facebook::jsi::Value get(
facebook::jsi::Runtime &runtime,
@@ -43,7 +43,7 @@ void TurboModuleBinding::install(
jsi::Runtime &rt,
const jsi::Value &thisVal,
const jsi::Value *args,
size_t count) mutable {
size_t count) {
return binding.getModule(rt, thisVal, args, count);
}));
}
@@ -56,7 +56,7 @@ jsi::Value TurboModuleBinding::getModule(
jsi::Runtime &runtime,
const jsi::Value &thisVal,
const jsi::Value *args,
size_t count) {
size_t count) const {
if (count < 1) {
throw std::invalid_argument(
"__turboModuleProxy must be called with at least 1 argument");
@@ -75,25 +75,33 @@ jsi::Value TurboModuleBinding::getModule(
return jsi::Object::createFromHostObject(runtime, std::move(module));
}
auto &jsRepresentation = module->jsRepresentation_;
if (!jsRepresentation) {
jsRepresentation = std::make_unique<jsi::Object>(runtime);
if (bindingMode_ == TurboModuleBindingMode::Prototype) {
// Option 1: create plain object, with it's prototype mapped back to the
// hostobject. Any properties accessed are stored on the plain object
auto hostObject =
jsi::Object::createFromHostObject(runtime, std::move(module));
jsRepresentation->setProperty(
runtime, "__proto__", std::move(hostObject));
} else {
// Option 2: eagerly install all hostfunctions at this point, avoids
// prototype
for (auto &propName : module->getPropertyNames(runtime)) {
module->get(runtime, propName);
}
auto &weakJsRepresentation = module->jsRepresentation_;
if (weakJsRepresentation) {
auto jsRepresentation = weakJsRepresentation->lock(runtime);
if (!jsRepresentation.isUndefined()) {
return jsRepresentation;
}
}
return jsi::Value(runtime, *jsRepresentation);
// No JS representation found, or object has been collected
jsi::Object jsRepresentation(runtime);
weakJsRepresentation =
std::make_unique<jsi::WeakObject>(runtime, jsRepresentation);
if (bindingMode_ == TurboModuleBindingMode::Prototype) {
// Option 1: create plain object, with it's prototype mapped back to the
// hostobject. Any properties accessed are stored on the plain object
auto hostObject =
jsi::Object::createFromHostObject(runtime, std::move(module));
jsRepresentation.setProperty(runtime, "__proto__", std::move(hostObject));
} else {
// Option 2: eagerly install all hostfunctions at this point, avoids
// prototype
for (auto &propName : module->getPropertyNames(runtime)) {
module->get(runtime, propName);
}
}
return jsRepresentation;
} else {
return jsi::Value::null();
}
@@ -50,7 +50,7 @@ class TurboModuleBinding {
jsi::Runtime &runtime,
const jsi::Value &thisVal,
const jsi::Value *args,
size_t count);
size_t count) const;
TurboModuleProviderFunctionType moduleProvider_;
TurboModuleBindingMode bindingMode_;
@@ -9,10 +9,14 @@
#import <memory>
#import <React/RCTDefines.h>
#import <React/RCTTurboModuleRegistry.h>
#import <ReactCommon/RuntimeExecutor.h>
#import <ReactCommon/TurboModuleBinding.h>
#import "RCTTurboModule.h"
RCT_EXTERN void RCTTurboModuleSetBindingMode(facebook::react::TurboModuleBindingMode bindingMode);
@protocol RCTTurboModuleManagerDelegate <NSObject>
@optional
@@ -44,7 +48,7 @@
delegate:(id<RCTTurboModuleManagerDelegate>)delegate
jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker;
- (void)installJSBindingWithRuntimeExecutor:(facebook::react::RuntimeExecutor)runtimeExecutor;
- (void)installJSBindingWithRuntimeExecutor:(facebook::react::RuntimeExecutor &)runtimeExecutor;
- (void)invalidate;
@@ -25,13 +25,18 @@
#import <React/RCTUtils.h>
#import <ReactCommon/RuntimeExecutor.h>
#import <ReactCommon/TurboCxxModule.h>
#import <ReactCommon/TurboModuleBinding.h>
#import <ReactCommon/TurboModulePerfLogger.h>
#import <ReactCommon/TurboModuleUtils.h>
using namespace facebook;
using namespace facebook::react;
static TurboModuleBindingMode sTurboModuleBindingMode = TurboModuleBindingMode::HostObject;
void RCTTurboModuleSetBindingMode(TurboModuleBindingMode bindingMode)
{
sTurboModuleBindingMode = bindingMode;
}
/**
* A global variable whose address we use to associate method queues to id<RCTTurboModule> objects.
*/
@@ -181,7 +186,7 @@ static Class getFallbackClassFromName(const char *name)
jsInvoker:(std::shared_ptr<CallInvoker>)jsInvoker
{
if (self = [super init]) {
_jsInvoker = jsInvoker;
_jsInvoker = std::move(jsInvoker);
_delegate = delegate;
_bridge = bridge;
_invalidating = false;
@@ -691,7 +696,7 @@ static Class getFallbackClassFromName(const char *name)
return requiresMainQueueSetup;
}
- (void)installJSBindingWithRuntimeExecutor:(facebook::react::RuntimeExecutor)runtimeExecutor
- (void)installJSBindingWithRuntimeExecutor:(facebook::react::RuntimeExecutor &)runtimeExecutor
{
if (!runtimeExecutor) {
// jsi::Runtime doesn't exist when attached to Chrome debugger.
@@ -736,7 +741,7 @@ static Class getFallbackClassFromName(const char *name)
};
runtimeExecutor([turboModuleProvider = std::move(turboModuleProvider)](jsi::Runtime &runtime) {
TurboModuleBinding::install(runtime, std::move(turboModuleProvider), TurboModuleBindingMode::HostObject);
TurboModuleBinding::install(runtime, std::move(turboModuleProvider), sTurboModuleBindingMode);
});
}