mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Add JSI-based JSExecutor for the bridge
Summary: This is similar in function to the old JSCExecutor, but uses the more abstract JSI API to interact with the JSVM. @public Reviewed By: axe-fb Differential Revision: D9328241 fbshipit-source-id: 3212ff4f43d0589a70d7bebc4d463d4433590f1d
This commit is contained in:
1 parent
a23fb1a22c
commit
749b18dbc9
10 files changed
+888
No files matched your search
@@ -28,6 +28,7 @@ rn_android_library(
|
||||
react_native_target("java/com/facebook/react/common:common"),
|
||||
react_native_target("java/com/facebook/react/devsupport:interfaces"),
|
||||
react_native_target("java/com/facebook/react/fabric:fabric"),
|
||||
react_native_target("java/com/facebook/react/jscexecutor:jscexecutor"),
|
||||
react_native_target("java/com/facebook/react/module/annotations:annotations"),
|
||||
react_native_target("java/com/facebook/react/module/model:model"),
|
||||
react_native_target("java/com/facebook/react/modules/core:core"),
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
load("//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "FBJNI_TARGET", "rn_xplat_cxx_library", "react_native_xplat_target", "react_native_target", "react_native_dep", "rn_android_library")
|
||||
|
||||
rn_android_library(
|
||||
name = "jscexecutor",
|
||||
srcs = glob(["*.java"]),
|
||||
visibility = [
|
||||
"PUBLIC",
|
||||
],
|
||||
deps = [
|
||||
":jni",
|
||||
react_native_dep("libraries/soloader/java/com/facebook/soloader:soloader"),
|
||||
react_native_target("java/com/facebook/react/bridge:bridge"),
|
||||
],
|
||||
)
|
||||
|
||||
rn_xplat_cxx_library(
|
||||
name = "jni",
|
||||
srcs = glob(["*.cpp"]),
|
||||
headers = glob(["*.h"]),
|
||||
header_namespace = "",
|
||||
compiler_flags = ["-fexceptions"],
|
||||
platforms = ANDROID,
|
||||
fbandroid_allow_jni_merging = True,
|
||||
soname = "libjscexecutor.$(ext)",
|
||||
visibility = [
|
||||
react_native_target("java/com/facebook/react/jscexecutor:jscexecutor"),
|
||||
],
|
||||
deps = [
|
||||
"xplat//folly:molly",
|
||||
FBJNI_TARGET,
|
||||
react_native_target("jni/react/jni:jni"),
|
||||
react_native_xplat_target("jsi:JSCRuntime"),
|
||||
react_native_xplat_target("jsiexecutor:jsiexecutor"),
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
|
||||
* directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.jscexecutor;
|
||||
|
||||
import com.facebook.jni.HybridData;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.react.bridge.JavaScriptExecutor;
|
||||
import com.facebook.react.bridge.ReadableNativeMap;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
@DoNotStrip
|
||||
/* package */ class JSCExecutor extends JavaScriptExecutor {
|
||||
static {
|
||||
SoLoader.loadLibrary("jscexecutor");
|
||||
}
|
||||
|
||||
/* package */ JSCExecutor(ReadableNativeMap jscConfig) {
|
||||
super(initHybrid(jscConfig));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "JSCExecutor";
|
||||
}
|
||||
|
||||
private static native HybridData initHybrid(ReadableNativeMap jscConfig);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.jscexecutor;
|
||||
|
||||
import com.facebook.react.bridge.JavaScriptExecutor;
|
||||
import com.facebook.react.bridge.JavaScriptExecutorFactory;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
|
||||
public class JSCExecutorFactory implements JavaScriptExecutorFactory {
|
||||
private final String mAppName;
|
||||
private final String mDeviceName;
|
||||
|
||||
public JSCExecutorFactory(String appName, String deviceName) {
|
||||
this.mAppName = appName;
|
||||
this.mDeviceName = deviceName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaScriptExecutor create() throws Exception {
|
||||
WritableNativeMap jscConfig = new WritableNativeMap();
|
||||
jscConfig.putString("OwnerIdentity", "ReactNative");
|
||||
jscConfig.putString("AppIdentity", mAppName);
|
||||
jscConfig.putString("DeviceIdentity", mDeviceName);
|
||||
return new JSCExecutor(jscConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JSIExecutor+JSCRuntime";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#include <fb/fbjni.h>
|
||||
#include <folly/Memory.h>
|
||||
#include <jsi/JSCRuntime.h>
|
||||
#include <jsireact/JSIExecutor.h>
|
||||
#include <react/jni/JavaScriptExecutorHolder.h>
|
||||
#include <react/jni/JReactMarker.h>
|
||||
#include <react/jni/JSLogging.h>
|
||||
#include <react/jni/ReadableNativeMap.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
namespace {
|
||||
|
||||
class JSCExecutorFactory : public JSExecutorFactory {
|
||||
public:
|
||||
std::unique_ptr<JSExecutor> createJSExecutor(
|
||||
std::shared_ptr<ExecutorDelegate> delegate,
|
||||
std::shared_ptr<MessageQueueThread> jsQueue) override {
|
||||
return folly::make_unique<JSIExecutor>(
|
||||
jsc::makeJSCRuntime(),
|
||||
delegate,
|
||||
[](const std::string& message, unsigned int logLevel) {
|
||||
reactAndroidLoggingHook(message, logLevel);
|
||||
},
|
||||
JSIExecutor::defaultTimeoutInvoker,
|
||||
nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// This is not like JSCJavaScriptExecutor, which calls JSC directly. This uses
|
||||
// JSIExecutor with JSCRuntime.
|
||||
class JSCExecutorHolder
|
||||
: public jni::HybridClass<JSCExecutorHolder, JavaScriptExecutorHolder> {
|
||||
public:
|
||||
static constexpr auto kJavaDescriptor = "Lcom/facebook/react/jscexecutor/JSCExecutor;";
|
||||
|
||||
static jni::local_ref<jhybriddata> initHybrid(
|
||||
jni::alias_ref<jclass>, ReadableNativeMap*) {
|
||||
// This is kind of a weird place for stuff, but there's no other
|
||||
// good place for initialization which is specific to JSC on
|
||||
// Android.
|
||||
JReactMarker::setLogPerfMarkerIfNeeded();
|
||||
// TODO mhorowitz T28461666 fill in some missing nice to have glue
|
||||
return makeCxxInstance(folly::make_unique<JSCExecutorFactory>());
|
||||
}
|
||||
|
||||
static void registerNatives() {
|
||||
registerHybrid({
|
||||
makeNativeMethod("initHybrid", JSCExecutorHolder::initHybrid),
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
friend HybridBase;
|
||||
using HybridBase::HybridBase;
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
|
||||
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
|
||||
return facebook::jni::initialize(vm, [] {
|
||||
facebook::react::JSCExecutorHolder::registerNatives();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
load("//tools/build_defs/oss:rn_defs.bzl", "cxx_library", "react_native_xplat_dep", "react_native_xplat_target")
|
||||
|
||||
cxx_library(
|
||||
name = "jsiexecutor",
|
||||
srcs = [
|
||||
"jsireact/JSIExecutor.cpp",
|
||||
"jsireact/JSINativeModules.cpp",
|
||||
],
|
||||
header_namespace = "",
|
||||
exported_headers = {
|
||||
"jsireact/JSIExecutor.h": "jsireact/JSIExecutor.h",
|
||||
"jsireact/JSINativeModules.h": "jsireact/JSINativeModules.h",
|
||||
},
|
||||
compiler_flags = [
|
||||
"-fexceptions",
|
||||
"-frtti",
|
||||
],
|
||||
fbandroid_deps = [
|
||||
"xplat//folly:molly",
|
||||
"xplat//third-party/glog:glog",
|
||||
"xplat//third-party/linker_lib:atomic",
|
||||
],
|
||||
fbobjc_force_static = True,
|
||||
fbobjc_header_path_prefix = "",
|
||||
platforms = (ANDROID, APPLE),
|
||||
preprocessor_flags = [
|
||||
"-DLOG_TAG=\"ReactNative\"",
|
||||
"-DWITH_FBSYSTRACE=1",
|
||||
],
|
||||
visibility = [
|
||||
"PUBLIC",
|
||||
],
|
||||
xcode_public_headers_symlinks = True,
|
||||
deps = [
|
||||
react_native_xplat_dep("jsi:jsi"),
|
||||
react_native_xplat_dep("jsi:JSIDynamic"),
|
||||
react_native_xplat_target("cxxreact:bridge"),
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,405 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#include "jsireact/JSIExecutor.h"
|
||||
|
||||
#include <cxxreact/JSBigString.h>
|
||||
#include <cxxreact/ModuleRegistry.h>
|
||||
#include <cxxreact/ReactMarker.h>
|
||||
#include <cxxreact/SystraceSection.h>
|
||||
#include <folly/Conv.h>
|
||||
#include <folly/json.h>
|
||||
#include <glog/logging.h>
|
||||
#include <jsi/JSIDynamic.h>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace facebook::jsi;
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
JSIExecutorFactory::JSIExecutorFactory(
|
||||
std::shared_ptr<jsi::Runtime> runtime,
|
||||
JSIExecutor::Logger logger,
|
||||
JSIExecutor::RuntimeInstaller runtimeInstaller)
|
||||
: runtime_(runtime),
|
||||
logger_(logger),
|
||||
runtimeInstaller_(runtimeInstaller) {}
|
||||
|
||||
std::unique_ptr<JSExecutor> JSIExecutorFactory::createJSExecutor(
|
||||
std::shared_ptr<ExecutorDelegate> delegate,
|
||||
std::shared_ptr<MessageQueueThread>) {
|
||||
return std::make_unique<JSIExecutor>(
|
||||
runtime_,
|
||||
delegate,
|
||||
logger_,
|
||||
JSIExecutor::defaultTimeoutInvoker,
|
||||
runtimeInstaller_);
|
||||
}
|
||||
|
||||
class JSIExecutor::NativeModuleProxy : public jsi::HostObject {
|
||||
public:
|
||||
NativeModuleProxy(JSIExecutor& executor) : executor_(executor) {}
|
||||
|
||||
Value get(Runtime& rt, const PropNameID& name) override {
|
||||
if (name.utf8(rt) == "name") {
|
||||
return jsi::String::createFromAscii(rt, "NativeModules");
|
||||
}
|
||||
|
||||
return executor_.nativeModules_.getModule(rt, name);
|
||||
}
|
||||
|
||||
void set(Runtime&, const PropNameID&, const Value&) override {
|
||||
throw std::runtime_error(
|
||||
"Unable to put on NativeModules: Operation unsupported");
|
||||
}
|
||||
|
||||
private:
|
||||
JSIExecutor& executor_;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
// basename_r isn't in all iOS SDKs, so use this simple version instead.
|
||||
std::string simpleBasename(const std::string& path) {
|
||||
size_t pos = path.rfind("/");
|
||||
return (pos != std::string::npos) ? path.substr(pos) : path;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
JSIExecutor::JSIExecutor(
|
||||
std::shared_ptr<jsi::Runtime> runtime,
|
||||
std::shared_ptr<ExecutorDelegate> delegate,
|
||||
Logger logger,
|
||||
const JSIScopedTimeoutInvoker& scopedTimeoutInvoker,
|
||||
RuntimeInstaller runtimeInstaller)
|
||||
: runtime_(runtime),
|
||||
delegate_(delegate),
|
||||
nativeModules_(delegate ? delegate->getModuleRegistry() : nullptr),
|
||||
logger_(logger),
|
||||
scopedTimeoutInvoker_(scopedTimeoutInvoker),
|
||||
runtimeInstaller_(runtimeInstaller) {
|
||||
runtime_->global().setProperty(
|
||||
*runtime, "__jsiExecutorDescription", runtime->description());
|
||||
}
|
||||
|
||||
void JSIExecutor::loadApplicationScript(
|
||||
std::unique_ptr<const JSBigString> script,
|
||||
std::string sourceURL) {
|
||||
SystraceSection s("JSIExecutor::loadApplicationScript");
|
||||
|
||||
// TODO: check for and use precompiled HBC
|
||||
|
||||
runtime_->global().setProperty(
|
||||
*runtime_,
|
||||
"nativeModuleProxy",
|
||||
Object::createFromHostObject(
|
||||
*runtime_, std::make_shared<NativeModuleProxy>(*this)));
|
||||
|
||||
runtime_->global().setProperty(
|
||||
*runtime_,
|
||||
"nativeFlushQueueImmediate",
|
||||
Function::createFromHostFunction(
|
||||
*runtime_,
|
||||
PropNameID::forAscii(*runtime_, "nativeFlushQueueImmediate"),
|
||||
1,
|
||||
[this](
|
||||
jsi::Runtime&,
|
||||
const jsi::Value&,
|
||||
const jsi::Value* args,
|
||||
size_t count) {
|
||||
if (count != 1) {
|
||||
throw std::invalid_argument(
|
||||
"nativeFlushQueueImmediate arg count must be 1");
|
||||
}
|
||||
callNativeModules(args[0], false);
|
||||
return Value::undefined();
|
||||
}));
|
||||
|
||||
runtime_->global().setProperty(
|
||||
*runtime_,
|
||||
"nativeCallSyncHook",
|
||||
Function::createFromHostFunction(
|
||||
*runtime_,
|
||||
PropNameID::forAscii(*runtime_, "nativeCallSyncHook"),
|
||||
1,
|
||||
[this](
|
||||
jsi::Runtime&,
|
||||
const jsi::Value&,
|
||||
const jsi::Value* args,
|
||||
size_t count) { return nativeCallSyncHook(args, count); }));
|
||||
|
||||
if (logger_) {
|
||||
// Only inject the logging function if it was supplied by the caller.
|
||||
runtime_->global().setProperty(
|
||||
*runtime_,
|
||||
"nativeLoggingHook",
|
||||
Function::createFromHostFunction(
|
||||
*runtime_,
|
||||
PropNameID::forAscii(*runtime_, "nativeLoggingHook"),
|
||||
2,
|
||||
[this](
|
||||
jsi::Runtime&,
|
||||
const jsi::Value&,
|
||||
const jsi::Value* args,
|
||||
size_t count) {
|
||||
if (count != 2) {
|
||||
throw std::invalid_argument(
|
||||
"nativeLoggingHook takes 2 arguments");
|
||||
}
|
||||
logger_(
|
||||
args[0].asString(*runtime_).utf8(*runtime_),
|
||||
folly::to<unsigned int>(args[1].asNumber()));
|
||||
return Value::undefined();
|
||||
}));
|
||||
}
|
||||
|
||||
if (runtimeInstaller_) {
|
||||
runtimeInstaller_(*runtime_);
|
||||
}
|
||||
|
||||
bool hasLogger(ReactMarker::logTaggedMarker);
|
||||
std::string scriptName = simpleBasename(sourceURL);
|
||||
if (hasLogger) {
|
||||
ReactMarker::logTaggedMarker(
|
||||
ReactMarker::RUN_JS_BUNDLE_START, scriptName.c_str());
|
||||
}
|
||||
runtime_->evaluateJavaScript(
|
||||
std::make_unique<BigStringBuffer>(std::move(script)), sourceURL);
|
||||
flush();
|
||||
if (hasLogger) {
|
||||
ReactMarker::logMarker(ReactMarker::CREATE_REACT_CONTEXT_STOP);
|
||||
ReactMarker::logTaggedMarker(
|
||||
ReactMarker::RUN_JS_BUNDLE_STOP, scriptName.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void JSIExecutor::setBundleRegistry(std::unique_ptr<RAMBundleRegistry> r) {
|
||||
if (!bundleRegistry_) {
|
||||
runtime_->global().setProperty(
|
||||
*runtime_,
|
||||
"nativeRequire",
|
||||
Function::createFromHostFunction(
|
||||
*runtime_,
|
||||
PropNameID::forAscii(*runtime_, "nativeRequire"),
|
||||
2,
|
||||
[this](
|
||||
Runtime& rt,
|
||||
const facebook::jsi::Value&,
|
||||
const facebook::jsi::Value* args,
|
||||
size_t count) { return nativeRequire(args, count); }));
|
||||
}
|
||||
bundleRegistry_ = std::move(r);
|
||||
}
|
||||
|
||||
void JSIExecutor::registerBundle(
|
||||
uint32_t bundleId,
|
||||
const std::string& bundlePath) {
|
||||
const auto tag = folly::to<std::string>(bundleId);
|
||||
ReactMarker::logTaggedMarker(
|
||||
ReactMarker::REGISTER_JS_SEGMENT_START, tag.c_str());
|
||||
if (bundleRegistry_) {
|
||||
bundleRegistry_->registerBundle(bundleId, bundlePath);
|
||||
} else {
|
||||
auto script = JSBigFileString::fromPath(bundlePath);
|
||||
runtime_->evaluateJavaScript(
|
||||
std::make_unique<BigStringBuffer>(std::move(script)),
|
||||
JSExecutor::getSyntheticBundlePath(bundleId, bundlePath));
|
||||
}
|
||||
ReactMarker::logTaggedMarker(
|
||||
ReactMarker::REGISTER_JS_SEGMENT_STOP, tag.c_str());
|
||||
}
|
||||
|
||||
void JSIExecutor::callFunction(
|
||||
const std::string& moduleId,
|
||||
const std::string& methodId,
|
||||
const folly::dynamic& arguments) {
|
||||
SystraceSection s(
|
||||
"JSIExecutor::callFunction", "moduleId", moduleId, "methodId", methodId);
|
||||
if (!callFunctionReturnFlushedQueue_) {
|
||||
bindBridge();
|
||||
}
|
||||
|
||||
// Construct the error message producer in case this times out.
|
||||
// This is executed on a background thread, so it must capture its parameters
|
||||
// by value.
|
||||
auto errorProducer = [=] {
|
||||
std::stringstream ss;
|
||||
ss << "moduleID: " << moduleId << " methodID: " << methodId
|
||||
<< " arguments: " << folly::toJson(arguments);
|
||||
return ss.str();
|
||||
};
|
||||
|
||||
Value ret = Value::undefined();
|
||||
try {
|
||||
scopedTimeoutInvoker_(
|
||||
[&] {
|
||||
ret = callFunctionReturnFlushedQueue_->call(
|
||||
*runtime_,
|
||||
moduleId,
|
||||
methodId,
|
||||
valueFromDynamic(*runtime_, arguments));
|
||||
},
|
||||
std::move(errorProducer));
|
||||
} catch (...) {
|
||||
std::throw_with_nested(
|
||||
std::runtime_error("Error calling " + moduleId + "." + methodId));
|
||||
}
|
||||
|
||||
callNativeModules(ret, true);
|
||||
}
|
||||
|
||||
void JSIExecutor::invokeCallback(
|
||||
const double callbackId,
|
||||
const folly::dynamic& arguments) {
|
||||
SystraceSection s("JSIExecutor::invokeCallback", "callbackId", callbackId);
|
||||
if (!invokeCallbackAndReturnFlushedQueue_) {
|
||||
bindBridge();
|
||||
}
|
||||
Value ret;
|
||||
try {
|
||||
ret = invokeCallbackAndReturnFlushedQueue_->call(
|
||||
*runtime_, callbackId, valueFromDynamic(*runtime_, arguments));
|
||||
} catch (...) {
|
||||
std::throw_with_nested(std::runtime_error(
|
||||
folly::to<std::string>("Error invoking callback ", callbackId)));
|
||||
}
|
||||
|
||||
callNativeModules(ret, true);
|
||||
}
|
||||
|
||||
void JSIExecutor::setGlobalVariable(
|
||||
std::string propName,
|
||||
std::unique_ptr<const JSBigString> jsonValue) {
|
||||
SystraceSection s("JSIExecutor::setGlobalVariable", "propName", propName);
|
||||
runtime_->global().setProperty(
|
||||
*runtime_,
|
||||
propName.c_str(),
|
||||
Value::createFromJsonUtf8(
|
||||
*runtime_,
|
||||
reinterpret_cast<const uint8_t*>(jsonValue->c_str()),
|
||||
jsonValue->size()));
|
||||
}
|
||||
|
||||
std::string JSIExecutor::getDescription() {
|
||||
return "JSI " + runtime_->description();
|
||||
}
|
||||
|
||||
void* JSIExecutor::getJavaScriptContext() {
|
||||
return runtime_.get();
|
||||
}
|
||||
|
||||
bool JSIExecutor::isInspectable() {
|
||||
return runtime_->isInspectable();
|
||||
}
|
||||
|
||||
void JSIExecutor::bindBridge() {
|
||||
std::call_once(bindFlag_, [this] {
|
||||
SystraceSection s("JSIExecutor::bindBridge (once)");
|
||||
Value batchedBridgeValue =
|
||||
runtime_->global().getProperty(*runtime_, "__fbBatchedBridge");
|
||||
if (batchedBridgeValue.isUndefined()) {
|
||||
Function requireBatchedBridge = runtime_->global().getPropertyAsFunction(
|
||||
*runtime_, "__fbRequireBatchedBridge");
|
||||
batchedBridgeValue = requireBatchedBridge.call(*runtime_);
|
||||
if (batchedBridgeValue.isUndefined()) {
|
||||
throw JSINativeException(
|
||||
"Could not get BatchedBridge, make sure your bundle is packaged correctly");
|
||||
}
|
||||
}
|
||||
|
||||
Object batchedBridge = batchedBridgeValue.asObject(*runtime_);
|
||||
callFunctionReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction(
|
||||
*runtime_, "callFunctionReturnFlushedQueue");
|
||||
invokeCallbackAndReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction(
|
||||
*runtime_, "invokeCallbackAndReturnFlushedQueue");
|
||||
flushedQueue_ =
|
||||
batchedBridge.getPropertyAsFunction(*runtime_, "flushedQueue");
|
||||
callFunctionReturnResultAndFlushedQueue_ =
|
||||
batchedBridge.getPropertyAsFunction(
|
||||
*runtime_, "callFunctionReturnResultAndFlushedQueue");
|
||||
});
|
||||
}
|
||||
|
||||
void JSIExecutor::callNativeModules(const Value& queue, bool isEndOfBatch) {
|
||||
SystraceSection s("JSIExecutor::callNativeModules");
|
||||
// If this fails, you need to pass a fully functional delegate with a
|
||||
// module registry to the factory/ctor.
|
||||
CHECK(delegate_) << "Attempting to use native modules without a delegate";
|
||||
#if 0 // maybe useful for debugging
|
||||
std::string json = runtime_->global().getPropertyAsObject(*runtime_, "JSON")
|
||||
.getPropertyAsFunction(*runtime_, "stringify").call(*runtime_, queue)
|
||||
.getString(*runtime_).utf8(*runtime_);
|
||||
#endif
|
||||
delegate_->callNativeModules(
|
||||
*this, dynamicFromValue(*runtime_, queue), isEndOfBatch);
|
||||
}
|
||||
|
||||
void JSIExecutor::flush() {
|
||||
SystraceSection s("JSIExecutor::flush");
|
||||
if (flushedQueue_) {
|
||||
callNativeModules(flushedQueue_->call(*runtime_), true);
|
||||
return;
|
||||
}
|
||||
|
||||
// When a native module is called from JS, BatchedBridge.enqueueNativeCall()
|
||||
// is invoked. For that to work, require('BatchedBridge') has to be called,
|
||||
// and when that happens, __fbBatchedBridge is set as a side effect.
|
||||
Value batchedBridge =
|
||||
runtime_->global().getProperty(*runtime_, "__fbBatchedBridge");
|
||||
// So here, if __fbBatchedBridge doesn't exist, then we know no native calls
|
||||
// have happened, and we were able to determine this without forcing
|
||||
// BatchedBridge to be loaded as a side effect.
|
||||
if (!batchedBridge.isUndefined()) {
|
||||
// If calls were made, we bind to the JS bridge methods, and use them to
|
||||
// get the pending queue of native calls.
|
||||
bindBridge();
|
||||
callNativeModules(flushedQueue_->call(*runtime_), true);
|
||||
} else if (delegate_) {
|
||||
// If we have a delegate, we need to call it; we pass a null list to
|
||||
// callNativeModules, since we know there are no native calls, without
|
||||
// calling into JS again. If no calls were made and there's no delegate,
|
||||
// nothing happens, which is correct.
|
||||
callNativeModules(nullptr, true);
|
||||
}
|
||||
}
|
||||
|
||||
Value JSIExecutor::nativeRequire(const Value* args, size_t count) {
|
||||
if (count > 2 || count == 0) {
|
||||
throw std::invalid_argument("Got wrong number of args");
|
||||
}
|
||||
|
||||
uint32_t moduleId = folly::to<uint32_t>(args[0].getNumber());
|
||||
uint32_t bundleId = count == 2 ? folly::to<uint32_t>(args[1].getNumber()) : 0;
|
||||
auto module = bundleRegistry_->getModule(bundleId, moduleId);
|
||||
|
||||
runtime_->evaluateJavaScript(
|
||||
std::make_unique<StringBuffer>(module.code), module.name);
|
||||
return facebook::jsi::Value();
|
||||
}
|
||||
|
||||
Value JSIExecutor::nativeCallSyncHook(const Value* args, size_t count) {
|
||||
if (count != 3) {
|
||||
throw std::invalid_argument("nativeCallSyncHook arg count must be 3");
|
||||
}
|
||||
|
||||
if (!args[2].asObject(*runtime_).isArray(*runtime_)) {
|
||||
throw std::invalid_argument(
|
||||
folly::to<std::string>("method parameters should be array"));
|
||||
}
|
||||
|
||||
MethodCallResult result = delegate_->callSerializableNativeHook(
|
||||
*this,
|
||||
static_cast<unsigned int>(args[0].getNumber()), // moduleId
|
||||
static_cast<unsigned int>(args[1].getNumber()), // methodId
|
||||
dynamicFromValue(*runtime_, args[2])); // args
|
||||
|
||||
if (!result.hasValue()) {
|
||||
return Value::undefined();
|
||||
}
|
||||
return valueFromDynamic(*runtime_, result.value());
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
@@ -0,0 +1,149 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "JSINativeModules.h"
|
||||
|
||||
#include <cxxreact/JSBigString.h>
|
||||
#include <cxxreact/JSExecutor.h>
|
||||
#include <cxxreact/RAMBundleRegistry.h>
|
||||
#include <jsi/jsi.h>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
// A JSIScopedTimeoutInvoker is a trampoline-type function for introducing
|
||||
// timeouts. Call the TimeoutInvoker with a function to execute, the invokee.
|
||||
// The TimeoutInvoker will immediately invoke it, synchronously on the same
|
||||
// thread. If the invokee fails to return after some timeout (private to the
|
||||
// TimeoutInvoker), a soft error may be reported.
|
||||
//
|
||||
// If a soft error is reported, the second parameter errorMessageProducer will
|
||||
// be invoked to produce an error message, which will be included in the soft
|
||||
// error report. Note that the errorMessageProducer will be invoked
|
||||
// asynchronously on a different thread.
|
||||
//
|
||||
// The timeout behavior does NOT caues the invokee to aborted. If the invokee
|
||||
// blocks forever, so will the ScopedTimeoutInvoker (but the soft error may
|
||||
// still be reported).
|
||||
//
|
||||
// The invokee is passed by const ref because it is executed synchronously, but
|
||||
// the errorMessageProducer is passed by value because it must be copied or
|
||||
// moved for async execution.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// int param = ...;
|
||||
// timeoutInvoker(
|
||||
// [&]{ someBigWork(param); },
|
||||
// [=] -> std::string {
|
||||
// return "someBigWork, param " + std::to_string(param);
|
||||
// })
|
||||
//
|
||||
using JSIScopedTimeoutInvoker = std::function<void(
|
||||
const std::function<void()>& invokee,
|
||||
std::function<std::string()> errorMessageProducer)>;
|
||||
|
||||
class BigStringBuffer : public jsi::Buffer {
|
||||
public:
|
||||
BigStringBuffer(std::unique_ptr<const JSBigString> script)
|
||||
: script_(std::move(script)) {}
|
||||
|
||||
size_t size() const override {
|
||||
return script_->size();
|
||||
}
|
||||
|
||||
const uint8_t* data() const override {
|
||||
return reinterpret_cast<const uint8_t*>(script_->c_str());
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<const JSBigString> script_;
|
||||
};
|
||||
|
||||
class JSIExecutor : public JSExecutor {
|
||||
public:
|
||||
using Logger =
|
||||
std::function<void(const std::string& message, unsigned int logLevel)>;
|
||||
|
||||
using RuntimeInstaller = std::function<void(jsi::Runtime& runtime)>;
|
||||
|
||||
JSIExecutor(
|
||||
std::shared_ptr<jsi::Runtime> runtime,
|
||||
std::shared_ptr<ExecutorDelegate> delegate,
|
||||
Logger logger,
|
||||
const JSIScopedTimeoutInvoker& timeoutInvoker,
|
||||
RuntimeInstaller runtimeInstaller);
|
||||
void loadApplicationScript(
|
||||
std::unique_ptr<const JSBigString> script,
|
||||
std::string sourceURL) override;
|
||||
void setBundleRegistry(std::unique_ptr<RAMBundleRegistry>) override;
|
||||
void registerBundle(uint32_t bundleId, const std::string& bundlePath)
|
||||
override;
|
||||
void callFunction(
|
||||
const std::string& moduleId,
|
||||
const std::string& methodId,
|
||||
const folly::dynamic& arguments) override;
|
||||
void invokeCallback(const double callbackId, const folly::dynamic& arguments)
|
||||
override;
|
||||
void setGlobalVariable(
|
||||
std::string propName,
|
||||
std::unique_ptr<const JSBigString> jsonValue) override;
|
||||
std::string getDescription() override;
|
||||
void* getJavaScriptContext() override;
|
||||
bool isInspectable() override;
|
||||
|
||||
// An implementation of JSIScopedTimeoutInvoker that simply runs the
|
||||
// invokee, with no timeout.
|
||||
static void defaultTimeoutInvoker(
|
||||
const std::function<void()>& invokee,
|
||||
std::function<std::string()> errorMessageProducer) {
|
||||
(void)errorMessageProducer;
|
||||
invokee();
|
||||
}
|
||||
|
||||
private:
|
||||
class NativeModuleProxy;
|
||||
|
||||
void flush();
|
||||
void bindBridge();
|
||||
void callNativeModules(const jsi::Value& queue, bool isEndOfBatch);
|
||||
jsi::Value nativeCallSyncHook(const jsi::Value* args, size_t count);
|
||||
jsi::Value nativeRequire(const jsi::Value* args, size_t count);
|
||||
|
||||
std::shared_ptr<jsi::Runtime> runtime_;
|
||||
std::shared_ptr<ExecutorDelegate> delegate_;
|
||||
JSINativeModules nativeModules_;
|
||||
std::once_flag bindFlag_;
|
||||
std::unique_ptr<RAMBundleRegistry> bundleRegistry_;
|
||||
Logger logger_;
|
||||
JSIScopedTimeoutInvoker scopedTimeoutInvoker_;
|
||||
RuntimeInstaller runtimeInstaller_;
|
||||
|
||||
folly::Optional<jsi::Function> callFunctionReturnFlushedQueue_;
|
||||
folly::Optional<jsi::Function> invokeCallbackAndReturnFlushedQueue_;
|
||||
folly::Optional<jsi::Function> flushedQueue_;
|
||||
folly::Optional<jsi::Function> callFunctionReturnResultAndFlushedQueue_;
|
||||
};
|
||||
|
||||
class JSIExecutorFactory : public JSExecutorFactory {
|
||||
public:
|
||||
explicit JSIExecutorFactory(
|
||||
std::shared_ptr<jsi::Runtime> runtime,
|
||||
JSIExecutor::Logger logger,
|
||||
JSIExecutor::RuntimeInstaller);
|
||||
|
||||
std::unique_ptr<JSExecutor> createJSExecutor(
|
||||
std::shared_ptr<ExecutorDelegate> delegate,
|
||||
std::shared_ptr<MessageQueueThread> jsQueue) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<jsi::Runtime> runtime_;
|
||||
JSIExecutor::Logger logger_;
|
||||
JSIExecutor::RuntimeInstaller runtimeInstaller_;
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#include "jsireact/JSINativeModules.h"
|
||||
|
||||
#include <cxxreact/ReactMarker.h>
|
||||
|
||||
#include <jsi/JSIDynamic.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
using namespace facebook::jsi;
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
JSINativeModules::JSINativeModules(
|
||||
std::shared_ptr<ModuleRegistry> moduleRegistry)
|
||||
: m_moduleRegistry(std::move(moduleRegistry)) {}
|
||||
|
||||
Value JSINativeModules::getModule(Runtime& rt, const PropNameID& name) {
|
||||
if (!m_moduleRegistry) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string moduleName = name.utf8(rt);
|
||||
|
||||
const auto it = m_objects.find(moduleName);
|
||||
if (it != m_objects.end()) {
|
||||
return Value(rt, it->second);
|
||||
}
|
||||
|
||||
auto module = createModule(rt, moduleName);
|
||||
if (!module.hasValue()) {
|
||||
// Allow lookup to continue in the objects own properties, which allows for
|
||||
// overrides of NativeModules
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto result =
|
||||
m_objects.emplace(std::move(moduleName), std::move(*module)).first;
|
||||
return Value(rt, result->second);
|
||||
}
|
||||
|
||||
void JSINativeModules::reset() {
|
||||
m_genNativeModuleJS = nullptr;
|
||||
m_objects.clear();
|
||||
}
|
||||
|
||||
folly::Optional<Object> JSINativeModules::createModule(
|
||||
Runtime& rt,
|
||||
const std::string& name) {
|
||||
bool hasLogger(ReactMarker::logTaggedMarker);
|
||||
if (hasLogger) {
|
||||
ReactMarker::logTaggedMarker(
|
||||
ReactMarker::NATIVE_MODULE_SETUP_START, name.c_str());
|
||||
}
|
||||
|
||||
if (!m_genNativeModuleJS) {
|
||||
m_genNativeModuleJS =
|
||||
rt.global().getPropertyAsFunction(rt, "__fbGenNativeModule");
|
||||
}
|
||||
|
||||
auto result = m_moduleRegistry->getConfig(name);
|
||||
if (!result.hasValue()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Value moduleInfo = m_genNativeModuleJS->call(
|
||||
rt,
|
||||
valueFromDynamic(rt, result->config),
|
||||
static_cast<double>(result->index));
|
||||
CHECK(!moduleInfo.isNull()) << "Module returned from genNativeModule is null";
|
||||
|
||||
folly::Optional<Object> module(
|
||||
moduleInfo.asObject(rt).getPropertyAsObject(rt, "module"));
|
||||
|
||||
if (hasLogger) {
|
||||
ReactMarker::logTaggedMarker(
|
||||
ReactMarker::NATIVE_MODULE_SETUP_STOP, name.c_str());
|
||||
}
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <cxxreact/ModuleRegistry.h>
|
||||
#include <folly/Optional.h>
|
||||
#include <jsi/jsi.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
/**
|
||||
* Holds and creates JS representations of the modules in ModuleRegistry
|
||||
*/
|
||||
class JSINativeModules {
|
||||
public:
|
||||
explicit JSINativeModules(std::shared_ptr<ModuleRegistry> moduleRegistry);
|
||||
jsi::Value getModule(jsi::Runtime& rt, const jsi::PropNameID& name);
|
||||
void reset();
|
||||
|
||||
private:
|
||||
folly::Optional<jsi::Function> m_genNativeModuleJS;
|
||||
std::shared_ptr<ModuleRegistry> m_moduleRegistry;
|
||||
std::unordered_map<std::string, jsi::Object> m_objects;
|
||||
|
||||
folly::Optional<jsi::Object> createModule(
|
||||
jsi::Runtime& rt,
|
||||
const std::string& name);
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
Reference in new issue
Block a user