Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,6 @@ TurboModule::TurboModule(
std::shared_ptr<CallInvoker> jsInvoker)
: name_(std::move(name)), jsInvoker_(std::move(jsInvoker)) {}

jsi::Value TurboModule::createHostFunction(
jsi::Runtime &runtime,
const jsi::PropNameID &propName,
const MethodMetadata &meta) {
return jsi::Function::createFromHostFunction(
runtime,
propName,
static_cast<unsigned int>(meta.argCount),
[this, meta](
jsi::Runtime &rt,
const jsi::Value &thisVal,
const jsi::Value *args,
size_t count) { return meta.invoker(rt, *this, args, count); });
}

void TurboModule::emitDeviceEvent(
jsi::Runtime &runtime,
const std::string &eventName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,15 @@ class JSI_EXPORT TurboModule : public facebook::jsi::HostObject {
facebook::jsi::Runtime &runtime,
const facebook::jsi::PropNameID &propName) override {
{
std::string propNameUtf8 = propName.utf8(runtime);
auto p = methodMap_.find(propNameUtf8);
if (p == methodMap_.end()) {
// Method was not found, let JS decide what to do.
return facebook::jsi::Value::undefined();
} else {
auto moduleMethod = createHostFunction(runtime, propName, p->second);
// 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_->lock(runtime).asObject(runtime).setProperty(
runtime, propName, moduleMethod);
}
return moduleMethod;
auto prop = create(runtime, propName);
// 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_ && !prop.isUndefined()) {
jsRepresentation_->lock(runtime).asObject(runtime).setProperty(
runtime, propName, prop);
}
return prop;
}
}

Expand Down Expand Up @@ -111,15 +104,32 @@ class JSI_EXPORT TurboModule : public facebook::jsi::HostObject {
const std::string &eventName,
ArgFactory argFactory = nullptr);

virtual jsi::Value create(
jsi::Runtime &runtime,
const jsi::PropNameID &propName) {
std::string propNameUtf8 = propName.utf8(runtime);
auto p = methodMap_.find(propNameUtf8);
if (p == methodMap_.end()) {
// Method was not found, let JS decide what to do.
return facebook::jsi::Value::undefined();
} else {
const MethodMetadata &meta = p->second;
return jsi::Function::createFromHostFunction(
runtime,
propName,
static_cast<unsigned int>(meta.argCount),
[this, meta](
jsi::Runtime &rt,
const jsi::Value &thisVal,
const jsi::Value *args,
size_t count) { return meta.invoker(rt, *this, args, count); });
}
}

private:
friend class TurboCxxModule;
friend class TurboModuleBinding;
std::unique_ptr<jsi::WeakObject> jsRepresentation_;

facebook::jsi::Value createHostFunction(
facebook::jsi::Runtime &runtime,
const facebook::jsi::PropNameID &propName,
const MethodMetadata &meta);
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,64 @@ using namespace facebook;
namespace facebook {
namespace react {

namespace {
class BridgelessNativeModuleProxy : public jsi::HostObject {
public:
jsi::Value get(jsi::Runtime &runtime, const jsi::PropNameID &name) override {
/**
* BatchedBridge/NativeModules.js contains this line:
*
* module.exports = global.nativeModuleProxy
*
* This means that NativeModuleProxy is exported as a module from
* 'NativeModules.js'. Whenever some JavaScript requires 'NativeModule.js',
* Metro checks this module's __esModule property to see if the module is an
* ES6 module.
*
* We return false from this property access, so that we can fail on the
* actual NativeModule require that happens later, which is more actionable.
*/
if (name.utf8(runtime) == "__esModule") {
return jsi::Value(false);
}
throw jsi::JSError(
runtime,
"Tried to access NativeModule \"" + name.utf8(runtime) +
"\" from the bridge. This isn't allowed in Bridgeless mode.");
}

void set(
jsi::Runtime &runtime,
const jsi::PropNameID & /*name*/,
const jsi::Value & /*value*/) override {
throw jsi::JSError(
runtime,
"Tried to insert a NativeModule into the bridge's NativeModule proxy.");
}
};
} // namespace

// TODO(148359183): Merge this with the Bridgeless defineReadOnlyGlobal util
static void defineReadOnlyGlobal(
jsi::Runtime &runtime,
std::string propName,
jsi::Value &&value) {
jsi::Object jsObject =
runtime.global().getProperty(runtime, "Object").asObject(runtime);
jsi::Function defineProperty = jsObject.getProperty(runtime, "defineProperty")
.asObject(runtime)
.asFunction(runtime);

jsi::Object descriptor = jsi::Object(runtime);
descriptor.setProperty(runtime, "value", std::move(value));
defineProperty.callWithThis(
runtime,
jsObject,
runtime.global(),
jsi::String::createFromUtf8(runtime, propName),
descriptor);
}

/**
* Public API to install the TurboModule system.
*/
Expand Down Expand Up @@ -51,6 +109,14 @@ void TurboModuleBinding::install(
std::string moduleName = args[0].getString(rt).utf8(rt);
return binding.getModule(rt, moduleName);
}));

if (runtime.global().hasProperty(runtime, "RN$Bridgeless")) {
defineReadOnlyGlobal(
runtime,
"nativeModuleProxy",
jsi::Object::createFromHostObject(
runtime, std::make_shared<BridgelessNativeModuleProxy>()));
}
}

TurboModuleBinding::~TurboModuleBinding() {
Expand All @@ -72,6 +138,13 @@ jsi::Value TurboModuleBinding::getModule(
return jsi::Object::createFromHostObject(runtime, std::move(module));
}

// What is jsRepresentation? A cache for the TurboModule's properties
// Henceforth, always return the cache (i.e: jsRepresentation) to JavaScript
//
// If a jsRepresentation is found on the TurboModule, return it.
//
// Note: TurboModules are cached by name in TurboModuleManagers. Hence,
// jsRepresentation is also cached by by name by the TurboModuleManager
auto &weakJsRepresentation = module->jsRepresentation_;
if (weakJsRepresentation) {
auto jsRepresentation = weakJsRepresentation->lock(runtime);
Expand All @@ -80,20 +153,28 @@ jsi::Value TurboModuleBinding::getModule(
}
}

// No JS representation found, or object has been collected
// Status: No jsRepresentation found on TurboModule
// Create a brand new jsRepresentation, and attach it to TurboModule
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
// Option 1: Lazily populate the jsRepresentation, on property access.
//
// How does this work?
// 1. Initially jsRepresentation is empty: {}
// 2. If property lookup on jsRepresentation fails, the JS runtime will
// search jsRepresentation's prototype: jsi::Object(TurboModule).
// 3. TurboModule::get(runtime, propKey) executes. This creates the
// property, caches it on jsRepresentation, then returns it to
// JavaScript.
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
// Option 2: Eagerly populate the jsRepresentation, on create.
// Object.assign(jsRepresentation, jsi::Object(TurboModule))
for (auto &propName : module->getPropertyNames(runtime)) {
module->get(runtime, propName);
}
Expand Down