Move more new bridge code into OSS

Reviewed By: mhorowitz

Differential Revision: D3296231

fbshipit-source-id: 5a05b1ddacdfecda067a08398dd5652df76c1f14
This commit is contained in:
Chris Hopman
2016-05-19 18:52:46 -07:00
committed by Facebook Github Bot 2
parent 5e91a2a312
commit 830197847a
32 changed files with 2831 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
include_defs('//ReactAndroid/DEFS')
# We depend on JSC, support the same platforms
SUPPORTED_PLATFORMS = '^android-(armv7|x86)$'
EXPORTED_HEADERS = [
'CxxModuleWrapper.h',
]
cxx_library(
name='jni',
soname = 'libreactnativejnifb.so',
header_namespace = 'react/jni',
supported_platforms_regex = SUPPORTED_PLATFORMS,
deps = JSC_DEPS + [
'//native/fb:fb',
'//native/third-party/android-ndk:android',
'//xplat/folly:molly',
'//xplat/fbsystrace:fbsystrace',
'//xplat/react/module:module',
react_native_target('jni/react/jni:jni'),
react_native_xplat_target('bridge:bridge'),
],
srcs = glob(['*.cpp']),
exported_headers = EXPORTED_HEADERS,
headers = glob(['*.h'], excludes=EXPORTED_HEADERS),
preprocessor_flags = [
'-DLOG_TAG="ReactNativeJNI"',
'-DWITH_FBSYSTRACE=1',
],
compiler_flags = [
'-Wall',
'-Werror',
'-fexceptions',
'-std=c++11',
'-fvisibility=hidden',
'-frtti',
'-Wno-pessimizing-move',
'-Wno-inconsistent-missing-override',
],
visibility = [
'PUBLIC',
],
)
project_config(
src_target = ':jni',
)

View File

@@ -0,0 +1,228 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "CatalystInstanceImpl.h"
#include <mutex>
#include <condition_variable>
#include <folly/dynamic.h>
#include <folly/Memory.h>
#include <fb/log.h>
#include <jni/Countable.h>
#include <jni/LocalReference.h>
#include <react/jni/NativeArray.h>
#include <cxxreact/Instance.h>
#include <cxxreact/MethodCall.h>
#include <cxxreact/ModuleRegistry.h>
#include "JSLoader.h"
#include "JavaScriptExecutorHolder.h"
#include "JniJSModulesUnbundle.h"
#include "ModuleRegistryHolder.h"
#include "JNativeRunnable.h"
using namespace facebook::jni;
namespace facebook {
namespace react {
namespace {
class Exception : public jni::JavaClass<Exception> {
public:
static auto constexpr kJavaDescriptor = "Ljava/lang/Exception;";
};
class JInstanceCallback : public InstanceCallback {
public:
explicit JInstanceCallback(alias_ref<ReactCallback::javaobject> jobj)
: jobj_(make_global(jobj)) {}
void onBatchComplete() override {
static auto method =
ReactCallback::javaClassStatic()->getMethod<void()>("onBatchComplete");
method(jobj_);
}
void incrementPendingJSCalls() override {
static auto method =
ReactCallback::javaClassStatic()->getMethod<void()>("incrementPendingJSCalls");
method(jobj_);
}
void decrementPendingJSCalls() override {
static auto method =
ReactCallback::javaClassStatic()->getMethod<void()>("decrementPendingJSCalls");
method(jobj_);
}
void onNativeException(const std::string& what) override {
static auto exCtor =
Exception::javaClassStatic()->getConstructor<Exception::javaobject(jstring)>();
static auto method =
ReactCallback::javaClassStatic()->getMethod<void(Exception::javaobject)>("onNativeException");
method(jobj_, Exception::javaClassStatic()->newObject(
exCtor, jni::make_jstring(what).get()).get());
}
ExecutorToken createExecutorToken() override {
auto jobj = JExecutorToken::newObjectCxxArgs();
return jobj->cthis()->getExecutorToken(jobj);
}
void onExecutorStopped(ExecutorToken) override {
// TODO(cjhopman): implement this.
}
private:
global_ref<ReactCallback::javaobject> jobj_;
};
}
jni::local_ref<CatalystInstanceImpl::jhybriddata> CatalystInstanceImpl::initHybrid(
jni::alias_ref<jclass>) {
return makeCxxInstance();
}
CatalystInstanceImpl::CatalystInstanceImpl()
: instance_(folly::make_unique<Instance>()) {}
void CatalystInstanceImpl::registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", CatalystInstanceImpl::initHybrid),
makeNativeMethod("initializeBridge", CatalystInstanceImpl::initializeBridge),
makeNativeMethod("loadScriptFromAssets",
"(Landroid/content/res/AssetManager;Ljava/lang/String;)V",
CatalystInstanceImpl::loadScriptFromAssets),
makeNativeMethod("loadScriptFromFile", CatalystInstanceImpl::loadScriptFromFile),
makeNativeMethod("callJSFunction", CatalystInstanceImpl::callJSFunction),
makeNativeMethod("callJSCallback", CatalystInstanceImpl::callJSCallback),
makeNativeMethod("getMainExecutorToken", CatalystInstanceImpl::getMainExecutorToken),
makeNativeMethod("setGlobalVariable", CatalystInstanceImpl::setGlobalVariable),
makeNativeMethod("supportsProfiling", CatalystInstanceImpl::supportsProfiling),
makeNativeMethod("startProfiler", CatalystInstanceImpl::startProfiler),
makeNativeMethod("stopProfiler", CatalystInstanceImpl::stopProfiler),
});
JNativeRunnable::registerNatives();
}
void CatalystInstanceImpl::initializeBridge(
jni::alias_ref<ReactCallback::javaobject> callback,
// This executor is actually a factory holder.
JavaScriptExecutorHolder* jseh,
jni::alias_ref<JavaMessageQueueThread::javaobject> jsQueue,
jni::alias_ref<JavaMessageQueueThread::javaobject> moduleQueue,
ModuleRegistryHolder* mrh) {
// TODO mhorowitz: how to assert here?
// Assertions.assertCondition(mBridge == null, "initializeBridge should be called once");
// This used to be:
//
// Java CatalystInstanceImpl -> C++ CatalystInstanceImpl -> Bridge -> Bridge::Callback
// --weak--> ReactCallback -> Java CatalystInstanceImpl
//
// Now the weak ref is a global ref. So breaking the loop depends on
// CatalystInstanceImpl#destroy() calling mHybridData.resetNative(), which
// should cause all the C++ pointers to be cleaned up (except C++
// CatalystInstanceImpl might be kept alive for a short time by running
// callbacks). This also means that all native calls need to be pre-checked
// to avoid NPE.
// See the comment in callJSFunction. Once js calls switch to strings, we
// don't need jsModuleDescriptions any more, all the way up and down the
// stack.
instance_->initializeBridge(folly::make_unique<JInstanceCallback>(callback),
jseh->getExecutorFactory(),
folly::make_unique<JMessageQueueThread>(jsQueue),
folly::make_unique<JMessageQueueThread>(moduleQueue),
mrh->getModuleRegistry());
}
void CatalystInstanceImpl::loadScriptFromAssets(jobject assetManager,
const std::string& assetURL) {
const int kAssetsLength = 9; // strlen("assets://");
auto sourceURL = assetURL.substr(kAssetsLength);
auto manager = react::extractAssetManager(assetManager);
auto script = react::loadScriptFromAssets(manager, sourceURL);
if (JniJSModulesUnbundle::isUnbundle(manager, sourceURL)) {
instance_->loadUnbundle(
folly::make_unique<JniJSModulesUnbundle>(manager, sourceURL),
std::move(script),
sourceURL);
} else {
instance_->loadScriptFromString(std::move(script), std::move(sourceURL));
}
}
void CatalystInstanceImpl::loadScriptFromFile(jni::alias_ref<jstring> fileName,
const std::string& sourceURL) {
return instance_->loadScriptFromFile(fileName ? fileName->toStdString() : "",
sourceURL);
}
void CatalystInstanceImpl::callJSFunction(
JExecutorToken* token, std::string module, std::string method, NativeArray* arguments,
const std::string& tracingName) {
// We want to share the C++ code, and on iOS, modules pass module/method
// names as strings all the way through to JS, and there's no way to do
// string -> id mapping on the objc side. So on Android, we convert the
// number to a string, here which gets passed as-is to JS. There, they they
// used as ids if isFinite(), which handles this case, and looked up as
// strings otherwise. Eventually, we'll probably want to modify the stack
// from the JS proxy through here to use strings, too.
instance_->callJSFunction(token->getExecutorToken(nullptr),
module,
method,
std::move(arguments->array),
tracingName);
}
void CatalystInstanceImpl::callJSCallback(JExecutorToken* token, jint callbackId, NativeArray* arguments) {
instance_->callJSCallback(token->getExecutorToken(nullptr), callbackId, std::move(arguments->array));
}
local_ref<JExecutorToken::JavaPart> CatalystInstanceImpl::getMainExecutorToken() {
return JExecutorToken::extractJavaPartFromToken(instance_->getMainExecutorToken());
}
void CatalystInstanceImpl::setGlobalVariable(std::string propName,
std::string&& jsonValue) {
// This is only ever called from Java with short strings, and only
// for testing, so no need to try hard for zero-copy here.
instance_->setGlobalVariable(std::move(propName),
folly::make_unique<JSBigStdString>(std::move(jsonValue)));
}
jboolean CatalystInstanceImpl::supportsProfiling() {
if (!instance_) {
return false;
}
return instance_->supportsProfiling();
}
void CatalystInstanceImpl::startProfiler(const std::string& title) {
if (!instance_) {
return;
}
return instance_->startProfiler(title);
}
void CatalystInstanceImpl::stopProfiler(const std::string& title, const std::string& filename) {
if (!instance_) {
return;
}
return instance_->stopProfiler(title, filename);
}
}}

View File

@@ -0,0 +1,67 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include <string>
#include <folly/Memory.h>
#include <fb/fbjni.h>
#include "JMessageQueueThread.h"
#include "JExecutorToken.h"
namespace facebook {
namespace react {
class Instance;
class JavaScriptExecutorHolder;
class ModuleRegistryHolder;
class NativeArray;
struct ReactCallback : public jni::JavaClass<ReactCallback> {
static constexpr auto kJavaDescriptor =
"Lcom/facebook/react/cxxbridge/ReactCallback;";
};
class CatalystInstanceImpl : public jni::HybridClass<CatalystInstanceImpl> {
public:
static constexpr auto kJavaDescriptor =
"Lcom/facebook/react/cxxbridge/CatalystInstanceImpl;";
static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jclass>);
static void registerNatives();
std::shared_ptr<Instance> getInstance() {
return instance_;
}
private:
friend HybridBase;
CatalystInstanceImpl();
void initializeBridge(
jni::alias_ref<ReactCallback::javaobject> callback,
// This executor is actually a factory holder.
JavaScriptExecutorHolder* jseh,
jni::alias_ref<JavaMessageQueueThread::javaobject> jsQueue,
jni::alias_ref<JavaMessageQueueThread::javaobject> moduleQueue,
ModuleRegistryHolder* mrh);
void loadScriptFromAssets(jobject assetManager, const std::string& assetURL);
void loadScriptFromFile(jni::alias_ref<jstring> fileName, const std::string& sourceURL);
void callJSFunction(JExecutorToken* token, std::string module, std::string method, NativeArray* arguments,
const std::string& tracingName);
void callJSCallback(JExecutorToken* token, jint callbackId, NativeArray* arguments);
local_ref<JExecutorToken::JavaPart> getMainExecutorToken();
void setGlobalVariable(std::string propName,
std::string&& jsonValue);
jboolean supportsProfiling();
void startProfiler(const std::string& title);
void stopProfiler(const std::string& title, const std::string& filename);
// This should be the only long-lived strong reference, but every C++ class
// will have a weak reference.
std::shared_ptr<Instance> instance_;
};
}}

View File

@@ -0,0 +1,219 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "CxxModuleWrapper.h"
#include <react/jni/ReadableNativeArray.h>
#include <fb/fbjni.h>
#include <fb/Environment.h>
#include <jni/LocalString.h>
#include <jni/Registration.h>
#include <Module/JsArgumentHelpers.h>
#include <android/log.h>
#include <folly/json.h>
#include <folly/ScopeGuard.h>
#include <unordered_set>
#include <dlfcn.h>
using namespace facebook::jni;
using namespace facebook::xplat::module;
using namespace facebook::react;
namespace {
class ExecutorToken : public HybridClass<ExecutorToken> {
public:
constexpr static const char *const kJavaDescriptor = "Lcom/facebook/react/bridge/ExecutorToken;";
};
class CxxMethodWrapper : public HybridClass<CxxMethodWrapper> {
public:
constexpr static const char *const kJavaDescriptor =
"Lcom/facebook/react/cxxbridge/CxxModuleWrapper$MethodWrapper;";
static local_ref<jhybriddata> initHybrid(alias_ref<jhybridobject>) {
return makeCxxInstance();
}
static void registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", CxxMethodWrapper::initHybrid),
makeNativeMethod("invoke",
"(Lcom/facebook/react/bridge/CatalystInstance;Lcom/facebook/react/bridge/ExecutorToken;Lcom/facebook/react/bridge/ReadableNativeArray;)V",
CxxMethodWrapper::invoke),
});
}
void invoke(jobject catalystinstance, ExecutorToken::jhybridobject executorToken, NativeArray* args);
const CxxModule::Method* method_;
};
void CxxMethodWrapper::invoke(jobject jCatalystInstance, ExecutorToken::jhybridobject jExecutorToken, NativeArray* arguments) {
CxxModule::Callback first;
CxxModule::Callback second;
if (method_->callbacks >= 1) {
auto catalystInstance = make_global(adopt_local(jCatalystInstance));
global_ref<ExecutorToken::jhybridobject> executorToken = make_global(jExecutorToken);
// TODO(10184774): Support ExecutorToken in CxxModules
static auto sCatalystInstanceInvokeCallback =
catalystInstance->getClass()->getMethod<void(ExecutorToken::jhybridobject, jint, NativeArray::jhybridobject)>(
"invokeCallback");
int id1;
if (!arguments->array[arguments->array.size() - 1].isInt()) {
throwNewJavaException(gJavaLangIllegalArgumentException,
"Expected callback as last argument");
}
if (method_->callbacks == 2) {
if (!arguments->array[arguments->array.size() - 2].isInt()) {
throwNewJavaException(gJavaLangIllegalArgumentException,
"Expected callback as penultimate argument");
return;
}
id1 = arguments->array[arguments->array.size() - 2].getInt();
int id2 = arguments->array[arguments->array.size() - 1].getInt();
second = [catalystInstance, executorToken, id2](std::vector<folly::dynamic> args) mutable {
ThreadScope guard;
sCatalystInstanceInvokeCallback(
catalystInstance.get(), executorToken.get(), id2,
ReadableNativeArray::newObjectCxxArgs(std::move(args)).get());
catalystInstance.reset();
executorToken.reset();
};
} else {
id1 = arguments->array[arguments->array.size() - 1].getInt();
}
first = [catalystInstance, executorToken, id1](std::vector<folly::dynamic> args) mutable {
ThreadScope guard;
sCatalystInstanceInvokeCallback(
catalystInstance.get(), executorToken.get(), id1,
ReadableNativeArray::newObjectCxxArgs(std::move(args)).get());
// This is necessary because by the time the lambda's dtor runs,
// the guard has been destroyed, and it may not be possible to
// get a JNIEnv* to clean up the captured global_ref.
catalystInstance.reset();
executorToken.reset();
};
}
// I've got a few flawed options here. I can catch C++ exceptions
// here, and log/convert them to java exceptions. This lets all the
// java handling work ok, but the only info I can capture about the
// C++ exception is the what() string, not the stack. I can let the
// C++ exception escape, crashing the app. This causes the full,
// accurate C++ stack trace to be added to logcat by debuggerd. The
// java state is lost, but in practice, the java stack is always the
// same in this case since the javascript stack is not visible. The
// what() value is also lost. Finally, I can catch, log the java
// stack, then rethrow the C++ exception. In this case I get java
// and C++ stack data, but the C++ stack is as of the rethrow, not
// the original throw, both the C++ and java stacks always look the
// same.
//
// I am going with option 2, since that seems like the most useful
// choice. It would be nice to be able to get what() and the C++
// stack. I'm told that will be possible in the future. TODO
// mhorowitz #7128529: convert C++ exceptions to Java
folly::dynamic dargs = arguments->array;
dargs.resize(arguments->array.size() - method_->callbacks);
try {
method_->func(dargs, first, second);
} catch (const facebook::xplat::JsArgumentException& ex) {
throwNewJavaException(gJavaLangIllegalArgumentException, ex.what());
}
}
}
// CxxModuleWrapper
void CxxModuleWrapper::registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", CxxModuleWrapper::initHybrid),
makeNativeMethod("getName", CxxModuleWrapper::getName),
makeNativeMethod("getConstantsJson", CxxModuleWrapper::getConstantsJson),
makeNativeMethod("getMethods", "()Ljava/util/Map;", CxxModuleWrapper::getMethods),
});
CxxMethodWrapper::registerNatives();
}
CxxModuleWrapper::CxxModuleWrapper(const std::string& soPath, const std::string& fname) {
// soPath is the path of a library which has already been loaded by
// java SoLoader.loadLibrary(). So this returns the same handle,
// and increments the reference counter. We can't just use
// dlsym(RTLD_DEFAULT, ...), because that crashes on 4.4.2 and
// earlier: https://code.google.com/p/android/issues/detail?id=61799
void* handle = dlopen(soPath.c_str(), RTLD_NOW);
if (!handle) {
throwNewJavaException(gJavaLangIllegalArgumentException,
"module shared library %s is not found", soPath.c_str());
}
// Now, arrange to close the handle so the counter is decremented.
// The handle will remain valid until java closes it. There's no
// way to do this on Android, but that's no reason to be sloppy
// here.
auto guard = folly::makeGuard([&] { FBASSERT(dlclose(handle) == 0); });
void* sym = dlsym(handle, fname.c_str());
if (!sym) {
throwNewJavaException(gJavaLangIllegalArgumentException,
"module function %s in shared library %s is not found",
fname.c_str(), soPath.c_str());
}
auto factory = reinterpret_cast<CxxModule* (*)()>(sym);
module_.reset((*factory)());
methods_ = module_->getMethods();
}
std::string CxxModuleWrapper::getName() {
return module_->getName();
}
std::string CxxModuleWrapper::getConstantsJson() {
std::map<std::string, folly::dynamic> constants = module_->getConstants();
folly::dynamic constsobject = folly::dynamic::object;
for (auto& c : constants) {
constsobject.insert(std::move(c.first), std::move(c.second));
}
return folly::toJson(constsobject);
}
jobject CxxModuleWrapper::getMethods() {
static auto hashmap = findClassStatic("java/util/HashMap");
static auto hashmap_put = hashmap->getMethod<jobject(jobject, jobject)>("put");
auto methods = hashmap->newObject(hashmap->getConstructor<jobject()>());
std::unordered_set<std::string> names;
for (const auto& m : methods_) {
if (names.find(m.name) != names.end()) {
throwNewJavaException(gJavaLangIllegalArgumentException,
"C++ Module %s method name already registered: %s",
module_->getName().c_str(), m.name.c_str());
}
names.insert(m.name);
auto name = make_jstring(m.name);
static auto ctor =
CxxMethodWrapper::javaClassStatic()->getConstructor<CxxMethodWrapper::jhybridobject()>();
auto method = CxxMethodWrapper::javaClassStatic()->newObject(ctor);
cthis(method)->method_ = &m;
hashmap_put(methods.get(), name.get(), method.get());
}
return methods.release();
}

View File

@@ -0,0 +1,53 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <Module/CxxModule.h>
#include <fb/fbjni.h>
#include <memory>
#include <string>
#include <vector>
namespace facebook {
namespace react {
class CxxModuleWrapper : public jni::HybridClass<CxxModuleWrapper> {
public:
constexpr static const char *const kJavaDescriptor =
"Lcom/facebook/react/cxxbridge/CxxModuleWrapper;";
static void registerNatives();
CxxModuleWrapper(const std::string& soPath, const std::string& fname);
static jni::local_ref<jhybriddata> initHybrid(
jni::alias_ref<jhybridobject>, const std::string& soPath, const std::string& fname) {
return makeCxxInstance(soPath, fname);
}
// JNI methods
std::string getName();
std::string getConstantsJson();
jobject getMethods();
// This steals ownership of the underlying module for use by the C++ bridge
std::unique_ptr<xplat::module::CxxModule> getModule() {
// TODO mhorowitz: remove this (and a lot of other code) once the java
// bridge is dead.
methods_.clear();
return std::move(module_);
}
protected:
friend HybridBase;
explicit CxxModuleWrapper(std::unique_ptr<xplat::module::CxxModule> module)
: module_(std::move(module))
, methods_(module_->getMethods()) {}
std::unique_ptr<xplat::module::CxxModule> module_;
std::vector<xplat::module::CxxModule::Method> methods_;
};
}
}

View File

@@ -0,0 +1,44 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <memory>
#include <folly/dynamic.h>
#include <fb/fbjni.h>
#include <react/jni/NativeArray.h>
namespace facebook {
namespace react {
class Instance;
struct JCallback : public jni::JavaClass<JCallback> {
constexpr static auto kJavaDescriptor = "Lcom/facebook/react/bridge/Callback;";
};
class JCallbackImpl : public jni::HybridClass<JCallbackImpl, JCallback> {
public:
constexpr static auto kJavaDescriptor = "Lcom/facebook/react/cxxbridge/CallbackImpl;";
static void registerNatives() {
javaClassStatic()->registerNatives({
makeNativeMethod("nativeInvoke", JCallbackImpl::invoke),
});
}
private:
friend HybridBase;
using Callback = std::function<void(folly::dynamic)>;
JCallbackImpl(Callback callback) : callback_(std::move(callback)) {}
void invoke(NativeArray* arguments) {
callback_(std::move(arguments->array));
}
Callback callback_;
};
}
}

View File

@@ -0,0 +1,25 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "JExecutorToken.h"
using namespace facebook::jni;
namespace facebook {
namespace react {
ExecutorToken JExecutorToken::getExecutorToken(alias_ref<JExecutorToken::javaobject> jobj) {
std::lock_guard<std::mutex> guard(createTokenGuard_);
auto sharedOwner = owner_.lock();
if (!sharedOwner) {
sharedOwner = std::shared_ptr<PlatformExecutorToken>(new JExecutorTokenHolder(jobj));
owner_ = sharedOwner;
}
return ExecutorToken(sharedOwner);
}
local_ref<JExecutorToken::JavaPart> JExecutorToken::extractJavaPartFromToken(ExecutorToken token) {
return make_local(static_cast<JExecutorTokenHolder*>(token.getPlatformExecutorToken().get())->getJobj());
}
} }

View File

@@ -0,0 +1,60 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <mutex>
#include <fb/fbjni.h>
#include <cxxreact/ExecutorToken.h>
using namespace facebook::jni;
namespace facebook {
namespace react {
class JExecutorTokenHolder;
class JExecutorToken : public HybridClass<JExecutorToken> {
public:
static constexpr auto kJavaDescriptor = "Lcom/facebook/react/bridge/ExecutorToken;";
ExecutorToken getExecutorToken(alias_ref<JExecutorToken::javaobject> jobj);
static local_ref<JavaPart> extractJavaPartFromToken(ExecutorToken token);
private:
friend HybridBase;
friend JExecutorTokenHolder;
JExecutorToken() {}
std::weak_ptr<PlatformExecutorToken> owner_;
std::mutex createTokenGuard_;
};
/**
* Wrapper class to hold references to both the c++ and Java parts of the
* ExecutorToken object. The goal is to allow a reference to a token from either
* c++ or Java to keep both the Java object and c++ hybrid part alive. For c++
* references, we accomplish this by having JExecutorTokenHolder keep a reference
* to the Java object (which has a reference to the JExecutorToken hybrid part).
* For Java references, we allow the JExecutorTokenHolder to be deallocated if there
* are no references to it in c++ from a PlatformExecutorToken, but will dynamically
* create a new one in JExecutorToken.getExecutorToken if needed.
*/
class JExecutorTokenHolder : public PlatformExecutorToken, public noncopyable {
public:
explicit JExecutorTokenHolder(alias_ref<JExecutorToken::javaobject> jobj) :
jobj_(make_global(jobj)),
impl_(cthis(jobj)) {
}
JExecutorToken::javaobject getJobj() {
return jobj_.get();
}
private:
global_ref<JExecutorToken::javaobject> jobj_;
JExecutorToken *impl_;
};
} }

View File

@@ -0,0 +1,66 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "JMessageQueueThread.h"
#include <condition_variable>
#include <mutex>
#include <fb/log.h>
#include <folly/Memory.h>
#include <fb/fbjni.h>
#include "JNativeRunnable.h"
namespace facebook {
namespace react {
JMessageQueueThread::JMessageQueueThread(alias_ref<JavaMessageQueueThread::javaobject> jobj) :
m_jobj(make_global(jobj)) {
}
void JMessageQueueThread::runOnQueue(std::function<void()>&& runnable) {
static auto method = JavaMessageQueueThread::javaClassStatic()->
getMethod<void(Runnable::javaobject)>("runOnQueue");
method(m_jobj, JNativeRunnable::newObjectCxxArgs(runnable).get());
}
void JMessageQueueThread::runOnQueueSync(std::function<void()>&& runnable) {
static auto jIsOnThread = JavaMessageQueueThread::javaClassStatic()->
getMethod<jboolean()>("isOnThread");
if (jIsOnThread(m_jobj)) {
runnable();
} else {
std::mutex signalMutex;
std::condition_variable signalCv;
bool runnableComplete = false;
runOnQueue([&] () mutable {
std::lock_guard<std::mutex> lock(signalMutex);
runnable();
runnableComplete = true;
signalCv.notify_one();
});
std::unique_lock<std::mutex> lock(signalMutex);
signalCv.wait(lock, [&runnableComplete] { return runnableComplete; });
}
}
void JMessageQueueThread::quitSynchronous() {
static auto method = JavaMessageQueueThread::javaClassStatic()->
getMethod<void()>("quitSynchronous");
method(m_jobj);
}
/* static */
std::unique_ptr<JMessageQueueThread> JMessageQueueThread::currentMessageQueueThread() {
static auto method = MessageQueueThreadRegistry::javaClassStatic()->
getStaticMethod<JavaMessageQueueThread::javaobject()>("myMessageQueueThread");
return folly::make_unique<JMessageQueueThread>(method(MessageQueueThreadRegistry::javaClassStatic()));
}
} }

View File

@@ -0,0 +1,60 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <functional>
#include <cxxreact/MessageQueueThread.h>
#include <fb/fbjni.h>
using namespace facebook::jni;
namespace facebook {
namespace react {
class JavaMessageQueueThread : public jni::JavaClass<JavaMessageQueueThread> {
public:
static constexpr auto kJavaDescriptor = "Lcom/facebook/react/bridge/queue/MessageQueueThread;";
};
class JMessageQueueThread : public MessageQueueThread {
public:
JMessageQueueThread(alias_ref<JavaMessageQueueThread::javaobject> jobj);
/**
* Enqueues the given function to run on this MessageQueueThread.
*/
void runOnQueue(std::function<void()>&& runnable) override;
/**
* Synchronously executes the given function to run on this
* MessageQueueThread, waiting until it completes. Can be called from any
* thread, but will block if not called on this MessageQueueThread.
*/
void runOnQueueSync(std::function<void()>&& runnable) override;
/**
* Synchronously quits the current MessageQueueThread. Can be called from any thread, but will
* block if not called on this MessageQueueThread.
*/
void quitSynchronous() override;
JavaMessageQueueThread::javaobject jobj() {
return m_jobj.get();
}
/**
* Returns the current MessageQueueThread that owns this thread.
*/
static std::unique_ptr<JMessageQueueThread> currentMessageQueueThread();
private:
global_ref<JavaMessageQueueThread::javaobject> m_jobj;
};
class MessageQueueThreadRegistry : public jni::JavaClass<MessageQueueThreadRegistry> {
public:
static constexpr auto kJavaDescriptor = "Lcom/facebook/react/bridge/queue/MessageQueueThreadRegistry;";
};
} }

View File

@@ -0,0 +1,44 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <functional>
#include <jni.h>
using namespace facebook::jni;
namespace facebook {
namespace react {
class Runnable : public JavaClass<Runnable> {
public:
static constexpr auto kJavaDescriptor = "Ljava/lang/Runnable;";
};
/**
* The c++ interface for the Java NativeRunnable class
*/
class JNativeRunnable : public HybridClass<JNativeRunnable, Runnable> {
public:
static auto constexpr kJavaDescriptor = "Lcom/facebook/react/bridge/queue/NativeRunnable;";
void run() {
m_runnable();
}
static void registerNatives() {
javaClassStatic()->registerNatives({
makeNativeMethod("run", JNativeRunnable::run),
});
}
private:
friend HybridBase;
JNativeRunnable(std::function<void()> runnable)
: m_runnable(std::move(runnable)) {}
std::function<void()> m_runnable;
};
} }

View File

@@ -0,0 +1,244 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "JSCPerfLogging.h"
#include <fb/log.h>
#include <fb/fbjni.h>
#include <react/JSCHelpers.h>
using namespace facebook::jni;
struct _jqplProvider : _jobject {};
using jqplProvider = _jqplProvider*;
struct _jqpl : _jobject {};
using jqpl = _jqpl*;
namespace facebook { namespace jni {
template<>
class JObjectWrapper<jqpl> : public JObjectWrapper<jobject> {
public:
static constexpr const char* kJavaDescriptor = "Lcom/facebook/quicklog/QuickPerformanceLogger;";
using JObjectWrapper<jobject>::JObjectWrapper;
void markerStart(int markerId, int instanceKey, long timestamp) {
static auto markerStartMethod =
qplClass()->getMethod<void(int32_t, int32_t, int64_t)>("markerStart");
markerStartMethod(this_, markerId, instanceKey, timestamp);
}
void markerEnd(int markerId, int instanceKey, short actionId, long timestamp) {
static auto markerEndMethod =
qplClass()->getMethod<void(int32_t, int32_t, int16_t, int64_t)>("markerEnd");
markerEndMethod(this_, markerId, instanceKey, actionId, timestamp);
}
void markerNote(int markerId, int instanceKey, short actionId, long timestamp) {
static auto markerNoteMethod =
qplClass()->getMethod<void(int32_t, int32_t, int16_t, int64_t)>("markerNote");
markerNoteMethod(this_, markerId, instanceKey, actionId, timestamp);
}
void markerCancel(int markerId, int instanceKey) {
static auto markerCancelMethod =
qplClass()->getMethod<void(int32_t, int32_t)>("markerCancel");
markerCancelMethod(this_, markerId, instanceKey);
}
int64_t currentMonotonicTimestamp() {
static auto currentTimestampMethod =
qplClass()->getMethod<int64_t()>("currentMonotonicTimestamp");
return currentTimestampMethod(this_);
}
private:
static alias_ref<jclass> qplClass() {
static auto cls = findClassStatic("com/facebook/quicklog/QuickPerformanceLogger");
return cls;
}
};
using JQuickPerformanceLogger = JObjectWrapper<jqpl>;
template<>
class JObjectWrapper<jqplProvider> : public JObjectWrapper<jobject> {
public:
static constexpr const char* kJavaDescriptor =
"Lcom/facebook/quicklog/QuickPerformanceLoggerProvider;";
using JObjectWrapper<jobject>::JObjectWrapper;
static global_ref<jqpl> get() {
static auto getQPLInstMethod = qplProviderClass()->getStaticMethod<jqpl()>("getQPLInstance");
static global_ref<jqpl> theQpl = make_global(getQPLInstMethod(qplProviderClass().get()));
return theQpl;
}
static bool check() {
static auto getQPLInstMethod = qplProviderClass()->getStaticMethod<jqpl()>("getQPLInstance");
auto theQpl = getQPLInstMethod(qplProviderClass().get());
return (theQpl.get() != nullptr);
}
private:
static alias_ref<jclass> qplProviderClass() {
static auto cls = findClassStatic("com/facebook/quicklog/QuickPerformanceLoggerProvider");
return cls;
}
};
using JQuickPerformanceLoggerProvider = JObjectWrapper<jqplProvider>;
}}
static bool isReady() {
static bool ready = false;
if (!ready) {
try {
findClassStatic("com/facebook/quicklog/QuickPerformanceLoggerProvider");
} catch(...) {
// Swallow this exception - we don't want to crash the app, an error is enough.
FBLOGE("Calling QPL from JS before class has been loaded in Java. Ignored.");
return false;
}
if (JQuickPerformanceLoggerProvider::check()) {
ready = true;
} else {
FBLOGE("Calling QPL from JS before it has been initialized in Java. Ignored.");
return false;
}
}
return ready;
}
// After having read the implementation of PNaN that is returned from JSValueToNumber, and some
// more material on how NaNs are constructed, I think this is the most consistent way to verify
// NaN with how we generate it.
// Once the integration completes, I'll play around with it some more and potentially change this
// implementation to use std::isnan() if it is exactly commensurate with our usage.
static bool isNan(double value) {
return (value != value);
}
// Safely translates JSValues to an array of doubles.
static bool grabDoubles(
size_t targetsCount,
double targets[],
JSContextRef ctx,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception) {
if (argumentCount < targetsCount) {
return false;
}
for (size_t i = 0 ; i < targetsCount ; i++) {
targets[i] = JSValueToNumber(ctx, arguments[i], exception);
if (isNan(targets[i])) {
return false;
}
}
return true;
}
static JSValueRef nativeQPLMarkerStart(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception) {
double targets[3];
if (isReady() && grabDoubles(3, targets, ctx, argumentCount, arguments, exception)) {
int32_t markerId = (int32_t) targets[0];
int32_t instanceKey = (int32_t) targets[1];
int64_t timestamp = (int64_t) targets[2];
JQuickPerformanceLoggerProvider::get()->markerStart(markerId, instanceKey, timestamp);
}
return JSValueMakeUndefined(ctx);
}
static JSValueRef nativeQPLMarkerEnd(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception) {
double targets[4];
if (isReady() && grabDoubles(4, targets, ctx, argumentCount, arguments, exception)) {
int32_t markerId = (int32_t) targets[0];
int32_t instanceKey = (int32_t) targets[1];
int16_t actionId = (int16_t) targets[2];
int64_t timestamp = (int64_t) targets[3];
JQuickPerformanceLoggerProvider::get()->markerEnd(markerId, instanceKey, actionId, timestamp);
}
return JSValueMakeUndefined(ctx);
}
static JSValueRef nativeQPLMarkerNote(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception) {
double targets[4];
if (isReady() && grabDoubles(4, targets, ctx, argumentCount, arguments, exception)) {
int32_t markerId = (int32_t) targets[0];
int32_t instanceKey = (int32_t) targets[1];
int16_t actionId = (int16_t) targets[2];
int64_t timestamp = (int64_t) targets[3];
JQuickPerformanceLoggerProvider::get()->markerNote(markerId, instanceKey, actionId, timestamp);
}
return JSValueMakeUndefined(ctx);
}
static JSValueRef nativeQPLMarkerCancel(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception) {
double targets[2];
if (isReady() && grabDoubles(2, targets, ctx, argumentCount, arguments, exception)) {
int32_t markerId = (int32_t) targets[0];
int32_t instanceKey = (int32_t) targets[1];
JQuickPerformanceLoggerProvider::get()->markerCancel(markerId, instanceKey);
}
return JSValueMakeUndefined(ctx);
}
static JSValueRef nativeQPLTimestamp(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception) {
if (!isReady()) {
return JSValueMakeNumber(ctx, 0);
}
int64_t timestamp = JQuickPerformanceLoggerProvider::get()->currentMonotonicTimestamp();
// Since this is monotonic time, I assume the 52 bits of mantissa are enough in the double value.
return JSValueMakeNumber(ctx, timestamp);
}
namespace facebook {
namespace react {
void addNativePerfLoggingHooks(JSGlobalContextRef ctx) {
installGlobalFunction(ctx, "nativeQPLMarkerStart", nativeQPLMarkerStart);
installGlobalFunction(ctx, "nativeQPLMarkerEnd", nativeQPLMarkerEnd);
installGlobalFunction(ctx, "nativeQPLMarkerNote", nativeQPLMarkerNote);
installGlobalFunction(ctx, "nativeQPLMarkerCancel", nativeQPLMarkerCancel);
installGlobalFunction(ctx, "nativeQPLTimestamp", nativeQPLTimestamp);
}
} }

View File

@@ -0,0 +1,11 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <JavaScriptCore/JSContextRef.h>
namespace facebook {
namespace react {
void addNativePerfLoggingHooks(JSGlobalContextRef ctx);
} }

View File

@@ -0,0 +1,99 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "JSLoader.h"
#include <folly/Memory.h>
#include <android/asset_manager_jni.h>
#include <fb/Environment.h>
#include <fstream>
#include <sstream>
#include <streambuf>
#include <string>
#include <fb/log.h>
#ifdef WITH_FBSYSTRACE
#include <fbsystrace.h>
using fbsystrace::FbSystraceSection;
#endif
namespace facebook {
namespace react {
static jclass gApplicationHolderClass;
static jmethodID gGetApplicationMethod;
static jmethodID gGetAssetManagerMethod;
std::unique_ptr<const JSBigString> loadScriptFromAssets(const std::string& assetName) {
JNIEnv *env = jni::Environment::current();
jobject application = env->CallStaticObjectMethod(
gApplicationHolderClass,
gGetApplicationMethod);
jobject assetManager = env->CallObjectMethod(application, gGetAssetManagerMethod);
return loadScriptFromAssets(AAssetManager_fromJava(env, assetManager), assetName);
}
AAssetManager *extractAssetManager(jobject jassetManager) {
auto env = jni::Environment::current();
return AAssetManager_fromJava(env, jassetManager);
}
std::unique_ptr<const JSBigString> loadScriptFromAssets(
AAssetManager *manager,
const std::string& assetName) {
#ifdef WITH_FBSYSTRACE
FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "reactbridge_jni_loadScriptFromAssets",
"assetName", assetName);
#endif
if (manager) {
auto asset = AAssetManager_open(
manager,
assetName.c_str(),
AASSET_MODE_STREAMING); // Optimized for sequential read: see AssetManager.java for docs
if (asset) {
auto buf = folly::make_unique<JSBigBufferString>(AAsset_getLength(asset));
size_t offset = 0;
int readbytes;
while ((readbytes = AAsset_read(asset, buf->data() + offset, buf->size() - offset)) > 0) {
offset += readbytes;
}
AAsset_close(asset);
if (offset == buf->size()) {
return std::move(buf);
}
}
}
FBLOGE("Unable to load script from assets: %s", assetName.c_str());
return folly::make_unique<JSBigStdString>("");
}
std::string loadScriptFromFile(const std::string& fileName) {
#ifdef WITH_FBSYSTRACE
FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "reactbridge_jni_loadScriptFromFile",
"fileName", fileName);
#endif
std::ifstream jsfile(fileName);
if (jsfile) {
std::string output;
jsfile.seekg(0, std::ios::end);
output.reserve(jsfile.tellg());
jsfile.seekg(0, std::ios::beg);
output.assign(
(std::istreambuf_iterator<char>(jsfile)),
std::istreambuf_iterator<char>());
return output;
}
FBLOGE("Unable to load script from file: %s", fileName.c_str());
return "";
}
void registerJSLoaderNatives() {
JNIEnv *env = jni::Environment::current();
jclass applicationHolderClass = env->FindClass("com/facebook/react/common/ApplicationHolder");
gApplicationHolderClass = (jclass)env->NewGlobalRef(applicationHolderClass);
gGetApplicationMethod = env->GetStaticMethodID(applicationHolderClass, "getApplication", "()Landroid/app/Application;");
jclass appClass = env->FindClass("android/app/Application");
gGetAssetManagerMethod = env->GetMethodID(appClass, "getAssets", "()Landroid/content/res/AssetManager;");
}
} }

View File

@@ -0,0 +1,33 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <cxxreact/Executor.h>
#include <android/asset_manager.h>
#include <string>
#include <jni.h>
namespace facebook {
namespace react {
/**
* Helper method for loading a JS script from Android assets without
* a reference to an AssetManager.
*/
std::unique_ptr<const JSBigString> loadScriptFromAssets(const std::string& assetName);
/**
* Helper method for loading JS script from android asset
*/
AAssetManager *extractAssetManager(jobject jassetManager);
std::unique_ptr<const JSBigString> loadScriptFromAssets(AAssetManager *assetManager, const std::string& assetName);
/**
* Helper method for loading JS script from a file
*/
std::string loadScriptFromFile(const std::string& fileName);
void registerJSLoaderNatives();
} }

View File

@@ -0,0 +1,36 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "JSLogging.h"
#include <android/log.h>
#include <algorithm>
#include <react/Value.h>
#include <fb/log.h>
namespace facebook {
namespace react {
JSValueRef nativeLoggingHook(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[], JSValueRef *exception) {
android_LogPriority logLevel = ANDROID_LOG_DEBUG;
if (argumentCount > 1) {
int level = (int) JSValueToNumber(ctx, arguments[1], NULL);
// The lowest log level we get from JS is 0. We shift and cap it to be
// in the range the Android logging method expects.
logLevel = std::min(
static_cast<android_LogPriority>(level + ANDROID_LOG_DEBUG),
ANDROID_LOG_FATAL);
}
if (argumentCount > 0) {
JSStringRef jsString = JSValueToStringCopy(ctx, arguments[0], NULL);
String message = String::adopt(jsString);
FBLOG_PRI(logLevel, "ReactNativeJS", "%s", message.str().c_str());
}
return JSValueMakeUndefined(ctx);
}
}};

View File

@@ -0,0 +1,15 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <JavaScriptCore/JSContextRef.h>
namespace facebook {
namespace react {
JSValueRef nativeLoggingHook(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[], JSValueRef *exception);
}}

View File

@@ -0,0 +1,29 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include <memory>
#include <fb/fbjni.h>
#include <cxxreact/Executor.h>
namespace facebook {
namespace react {
class JavaScriptExecutorHolder : public jni::HybridClass<JavaScriptExecutorHolder> {
public:
static constexpr auto kJavaDescriptor =
"Lcom/facebook/react/cxxbridge/JavaScriptExecutor;";
std::shared_ptr<JSExecutorFactory> getExecutorFactory() {
return mExecutorFactory;
}
protected:
JavaScriptExecutorHolder(std::shared_ptr<JSExecutorFactory> factory)
: mExecutorFactory(factory) {}
private:
std::shared_ptr<JSExecutorFactory> mExecutorFactory;
};
}}

View File

@@ -0,0 +1,83 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "JniJSModulesUnbundle.h"
#include <cstdint>
#include <fb/assert.h>
#include <libgen.h>
#include <memory>
#include <sstream>
#include <sys/endian.h>
#include <utility>
using magic_number_t = uint32_t;
const magic_number_t MAGIC_FILE_HEADER = 0xFB0BD1E5;
const std::string MAGIC_FILE_NAME = "UNBUNDLE";
namespace facebook {
namespace react {
using asset_ptr =
std::unique_ptr<AAsset, std::function<decltype(AAsset_close)>>;
static std::string jsModulesDir(const std::string& entryFile) {
std::string dir = dirname(entryFile.c_str());
// android's asset manager does not work with paths that start with a dot
return dir == "." ? "js-modules/" : dir + "/js-modules/";
}
static asset_ptr openAsset(
AAssetManager *manager,
const std::string& fileName,
int mode = AASSET_MODE_STREAMING) {
return asset_ptr(
AAssetManager_open(manager, fileName.c_str(), mode),
AAsset_close);
}
JniJSModulesUnbundle::JniJSModulesUnbundle(AAssetManager *assetManager, const std::string& entryFile) :
m_assetManager(assetManager),
m_moduleDirectory(jsModulesDir(entryFile)) {}
bool JniJSModulesUnbundle::isUnbundle(
AAssetManager *assetManager,
const std::string& assetName) {
if (!assetManager) {
return false;
}
auto magicFileName = jsModulesDir(assetName) + MAGIC_FILE_NAME;
auto asset = openAsset(assetManager, magicFileName.c_str());
if (asset == nullptr) {
return false;
}
magic_number_t fileHeader = 0;
AAsset_read(asset.get(), &fileHeader, sizeof(fileHeader));
return fileHeader == htole32(MAGIC_FILE_HEADER);
}
JSModulesUnbundle::Module JniJSModulesUnbundle::getModule(uint32_t moduleId) const {
// can be nullptr for default constructor.
FBASSERTMSGF(m_assetManager != nullptr, "Unbundle has not been initialized with an asset manager");
std::ostringstream sourceUrlBuilder;
sourceUrlBuilder << moduleId << ".js";
auto sourceUrl = sourceUrlBuilder.str();
auto fileName = m_moduleDirectory + sourceUrl;
auto asset = openAsset(m_assetManager, fileName, AASSET_MODE_BUFFER);
const char *buffer = nullptr;
if (asset != nullptr) {
buffer = static_cast<const char *>(AAsset_getBuffer(asset.get()));
}
if (buffer == nullptr) {
throw ModuleNotFound("Module not found: " + sourceUrl);
}
return {sourceUrl, std::string(buffer, AAsset_getLength(asset.get()))};
}
}
}

View File

@@ -0,0 +1,31 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <android/asset_manager.h>
#include <cxxreact/JSModulesUnbundle.h>
namespace facebook {
namespace react {
class JniJSModulesUnbundle : public JSModulesUnbundle {
/**
* This implementation reads modules as single file from the assets of an apk.
*/
public:
JniJSModulesUnbundle() = default;
JniJSModulesUnbundle(AAssetManager *assetManager, const std::string& entryFile);
JniJSModulesUnbundle(JniJSModulesUnbundle&& other) = delete;
JniJSModulesUnbundle& operator= (JSModulesUnbundle&& other) = delete;
static bool isUnbundle(
AAssetManager *assetManager,
const std::string& assetName);
virtual Module getModule(uint32_t moduleId) const override;
private:
AAssetManager *m_assetManager = nullptr;
std::string m_moduleDirectory;
};
}
}

View File

@@ -0,0 +1,32 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <memory>
#include <jni.h>
#include <folly/Memory.h>
#include "JMessageQueueThread.h"
#include <react/MessageQueue.h>
using namespace facebook::jni;
namespace facebook {
namespace react {
class JniWebWorkers : public JavaClass<JniWebWorkers> {
public:
static constexpr auto kJavaDescriptor = "Lcom/facebook/react/bridge/webworkers/WebWorkers;";
static std::unique_ptr<MessageQueue> createWebWorkerQueue(int id, MessageQueue* ownerMessageQueue) {
static auto method = JniWebWorkers::javaClassStatic()->
getStaticMethod<MessageQueueThread::javaobject(jint, MessageQueueThread::javaobject)>("createWebWorkerThread");
JMessageQueueThread* ownerMessageQueueThread = static_cast<JMessageQueueThread*>(ownerMessageQueue);
auto res = method(JniWebWorkers::javaClassStatic(), id, ownerMessageQueueThread->jobj());
return folly::make_unique<JMessageQueueThread>(res);
}
};
} }

View File

@@ -0,0 +1,230 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "MethodInvoker.h"
#include <react/jni/ReadableNativeArray.h>
#ifdef WITH_FBSYSTRACE
#include <fbsystrace.h>
#endif
#include "ModuleRegistryHolder.h"
#include "JCallback.h"
#include "JExecutorToken.h"
namespace facebook {
namespace react {
namespace {
using dynamic_iterator = folly::dynamic::const_iterator;
struct JPromiseImpl : public jni::JavaClass<JPromiseImpl> {
constexpr static auto kJavaDescriptor = "Lcom/facebook/react/bridge/PromiseImpl;";
static jni::local_ref<javaobject> create(jni::local_ref<JCallback::javaobject> resolve, jni::local_ref<JCallback::javaobject> reject) {
return newInstance(resolve, reject);
}
};
// HACK: Exposes constructor
struct ExposedReadableNativeArray : public ReadableNativeArray {
explicit ExposedReadableNativeArray(folly::dynamic array)
: ReadableNativeArray(std::move(array)) {}
};
jdouble extractDouble(const folly::dynamic& value) {
if (value.isInt()) {
return static_cast<jdouble>(value.getInt());
} else {
return static_cast<jdouble>(value.getDouble());
}
}
jni::local_ref<JCallbackImpl::jhybridobject> extractCallback(std::weak_ptr<Instance>& instance, ExecutorToken token, const folly::dynamic& value) {
if (value.isNull()) {
return jni::local_ref<JCallbackImpl::jhybridobject>(nullptr);
} else {
return JCallbackImpl::newObjectCxxArgs(makeCallback(instance, token, value));
}
}
jni::local_ref<JPromiseImpl::javaobject> extractPromise(std::weak_ptr<Instance>& instance, ExecutorToken token, dynamic_iterator& it, dynamic_iterator& end) {
auto resolve = extractCallback(instance, token, *it++);
CHECK(it != end);
auto reject = extractCallback(instance, token, *it++);
return JPromiseImpl::create(resolve, reject);
}
jobject valueOf(jboolean value) {
static auto kClass = jni::findClassStatic("java/lang/Boolean");
static auto kValueOf = kClass->getStaticMethod<jobject(jboolean)>("valueOf");
return kValueOf(kClass, value).release();
}
jobject valueOf(jint value) {
static auto kClass = jni::findClassStatic("java/lang/Integer");
static auto kValueOf = kClass->getStaticMethod<jobject(jint)>("valueOf");
return kValueOf(kClass, value).release();
}
jobject valueOf(jdouble value) {
static auto kClass = jni::findClassStatic("java/lang/Double");
static auto kValueOf = kClass->getStaticMethod<jobject(jdouble)>("valueOf");
return kValueOf(kClass, value).release();
}
jobject valueOf(jfloat value) {
static auto kClass = jni::findClassStatic("java/lang/Float");
static auto kValueOf = kClass->getStaticMethod<jobject(jfloat)>("valueOf");
return kValueOf(kClass, value).release();
}
bool isNullable(char type) {
switch (type) {
case 'Z':
case 'I':
case 'F':
case 'S':
case 'A':
case 'M':
case 'X':
return true;
default:
return false;;
}
}
jvalue extract(std::weak_ptr<Instance>& instance, ExecutorToken token, char type, dynamic_iterator& it, dynamic_iterator& end) {
CHECK(it != end);
jvalue value;
if (type == 'P') {
value.l = extractPromise(instance, token, it, end).release();
return value;
} else if (type == 'T') {
value.l = JExecutorToken::extractJavaPartFromToken(token).release();
return value;
}
const auto& arg = *it++;
if (isNullable(type) && arg.isNull()) {
value.l = nullptr;
return value;
}
switch (type) {
case 'z':
value.z = static_cast<jboolean>(arg.getBool());
break;
case 'Z':
value.l = valueOf(static_cast<jboolean>(arg.getBool()));
break;
case 'i':
value.i = static_cast<jint>(arg.getInt());
break;
case 'I':
value.l = valueOf(static_cast<jint>(arg.getInt()));
break;
case 'f':
value.f = static_cast<jfloat>(extractDouble(arg));
break;
case 'F':
value.l = valueOf(static_cast<jfloat>(extractDouble(arg)));
break;
case 'd':
value.d = extractDouble(arg);
break;
case 'D':
value.l = valueOf(extractDouble(arg));
break;
case 'S':
value.l = jni::make_jstring(arg.getString()).release();
break;
case 'A':
value.l = ReadableNativeArray::newObjectCxxArgs(arg).release();
break;
case 'M':
// HACK: Workaround for constructing ReadableNativeMap
value.l = ExposedReadableNativeArray(folly::dynamic::array(arg)).getMap(0);
break;
case 'X':
value.l = extractCallback(instance, token, arg).release();
break;
default:
LOG(FATAL) << "Unknown param type: " << type;
}
return value;
}
std::size_t countJsArgs(const std::string& signature) {
std::size_t count = 0;
for (char c : signature) {
switch (c) {
case 'T':
break;
case 'P':
count += 2;
break;
default:
count += 1;
break;
}
}
return count;
}
}
MethodInvoker::MethodInvoker(jni::alias_ref<JReflectMethod::javaobject> method, std::string signature, std::string traceName, bool isSync)
: method_(method->getMethodID()),
jsArgCount_(countJsArgs(signature) - 2),
signature_(std::move(signature)),
traceName_(std::move(traceName)),
isSync_(isSync) {
CHECK(signature_.at(1) == '.') << "Improper module method signature";
CHECK(!isSync || signature_.at(0) == 'v') << "Non-sync hooks cannot have a non-void return type";
}
MethodCallResult MethodInvoker::invoke(std::weak_ptr<Instance>& instance, JBaseJavaModule::javaobject module, ExecutorToken token, const folly::dynamic& params) {
#ifdef WITH_FBSYSTRACE
fbsystrace::FbSystraceSection s(
TRACE_TAG_REACT_CXX_BRIDGE,
isSync_ ? "callJavaSyncHook" : "callJavaModuleMethod",
"method",
traceName_);
#endif
if (params.size() != jsArgCount_) {
throw std::invalid_argument(folly::to<std::string>("expected ", jsArgCount_, " arguments, got ", params.size()));
}
auto argCount = signature_.size() - 2;
jni::JniLocalScope scope(jni::Environment::current(), argCount);
jvalue args[argCount];
std::transform(
signature_.begin() + 2,
signature_.end(),
args,
[&instance, token, it = params.begin(), end = params.end()] (char type) mutable {
return extract(instance, token, type, it, end);
});
// TODO(t10768795): Use fbjni here
folly::dynamic ret = folly::dynamic::object();
bool isReturnUndefined = false;
char returnType = signature_.at(0);
switch (returnType) {
case 'v':
jni::Environment::current()->CallVoidMethodA(module, method_, args);
ret = nullptr;
isReturnUndefined = true;
break;
default:
LOG(FATAL) << "Unknown return type: " << returnType;
// TODO: other cases
}
jni::throwPendingJniExceptionAsCppException();
return MethodCallResult{ret, isReturnUndefined};
}
}
}

View File

@@ -0,0 +1,37 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <vector>
#include <fb/fbjni.h>
#include <folly/dynamic.h>
#include <cxxreact/ExecutorToken.h>
#include "ModuleRegistryHolder.h"
namespace facebook {
namespace react {
class Instance;
class MethodInvoker {
public:
MethodInvoker(jni::alias_ref<JReflectMethod::javaobject> method, std::string signature, std::string traceName, bool isSync);
MethodCallResult invoke(std::weak_ptr<Instance>& instance, JBaseJavaModule::javaobject module, ExecutorToken token, const folly::dynamic& params);
bool isSyncHook() const {
return isSync_;
}
private:
jmethodID method_;
std::size_t jsArgCount_;
std::string signature_;
std::string traceName_;
bool isSync_;
};
}
}

View File

@@ -0,0 +1,344 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "ModuleRegistryHolder.h"
#include <folly/json.h>
#include <fb/fbjni.h>
#include <Module/CxxModule.h>
#include <Module/JsArgumentHelpers.h>
#include <cxxreact/Instance.h>
#include <cxxreact/NativeModule.h>
#include <react/jni/ReadableNativeArray.h>
#include "MethodInvoker.h"
#include "CatalystInstanceImpl.h"
using facebook::xplat::module::CxxModule;
namespace facebook {
namespace react {
namespace {
class JavaNativeModule : public NativeModule {
public:
JavaNativeModule(jni::alias_ref<JavaModuleWrapper::javaobject> wrapper)
: wrapper_(make_global(wrapper)) {}
std::string getName() override {
static auto getNameMethod = wrapper_->getClass()->getMethod<jstring()>("getName");
return getNameMethod(wrapper_)->toStdString();
}
std::vector<MethodDescriptor> getMethods() override {
static auto getMDMethod =
wrapper_->getClass()->getMethod<jni::JList<JMethodDescriptor::javaobject>::javaobject()>(
"getMethodDescriptors");
std::vector<MethodDescriptor> ret;
auto descs = getMDMethod(wrapper_);
for (const auto& desc : *descs) {
static auto nameField =
JMethodDescriptor::javaClassStatic()->getField<jstring>("name");
static auto typeField =
JMethodDescriptor::javaClassStatic()->getField<jstring>("type");
ret.emplace_back(
desc->getFieldValue(nameField)->toStdString(),
desc->getFieldValue(typeField)->toStdString()
);
}
return ret;
}
folly::dynamic getConstants() override {
static auto constantsMethod =
wrapper_->getClass()->getMethod<NativeArray::javaobject()>("getConstants");
auto constants = constantsMethod(wrapper_);
if (!constants) {
return nullptr;
} else {
// See JavaModuleWrapper#getConstants for the other side of this hack.
return cthis(constants)->array[0];
}
}
virtual bool supportsWebWorkers() override {
static auto supportsWebWorkersMethod =
wrapper_->getClass()->getMethod<jboolean()>("supportsWebWorkers");
return supportsWebWorkersMethod(wrapper_);
}
void invoke(ExecutorToken token, unsigned int reactMethodId, folly::dynamic&& params) override {
static auto invokeMethod =
wrapper_->getClass()->getMethod<void(JExecutorToken::javaobject, jint, ReadableNativeArray::javaobject)>("invoke");
invokeMethod(wrapper_, JExecutorToken::extractJavaPartFromToken(token).get(), static_cast<jint>(reactMethodId),
ReadableNativeArray::newObjectCxxArgs(std::move(params)).get());
}
MethodCallResult callSerializableNativeHook(ExecutorToken token, unsigned int reactMethodId, folly::dynamic&& params) override {
throw std::runtime_error("Unsupported operation.");
}
private:
jni::global_ref<JavaModuleWrapper::javaobject> wrapper_;
};
class NewJavaNativeModule : public NativeModule {
public:
NewJavaNativeModule(std::weak_ptr<Instance> instance, jni::alias_ref<JavaModuleWrapper::javaobject> wrapper)
: instance_(std::move(instance)),
wrapper_(make_global(wrapper)),
module_(make_global(wrapper->getModule())) {
auto descs = wrapper_->getMethodDescriptors();
std::string moduleName = getName();
methods_.reserve(descs->size());
for (const auto& desc : *descs) {
auto type = desc->getType();
auto name = desc->getName();
methods_.emplace_back(
desc->getMethod(),
desc->getSignature(),
moduleName + "." + name,
type == "syncHook");
methodDescriptors_.emplace_back(name, type);
}
}
std::string getName() override {
static auto getNameMethod = wrapper_->getClass()->getMethod<jstring()>("getName");
return getNameMethod(wrapper_)->toStdString();
}
std::vector<MethodDescriptor> getMethods() override {
return methodDescriptors_;
}
folly::dynamic getConstants() override {
static auto constantsMethod =
wrapper_->getClass()->getMethod<NativeArray::javaobject()>("getConstants");
auto constants = constantsMethod(wrapper_);
if (!constants) {
return nullptr;
} else {
// See JavaModuleWrapper#getConstants for the other side of this hack.
return cthis(constants)->array[0];
}
}
virtual bool supportsWebWorkers() override {
static auto supportsWebWorkersMethod =
wrapper_->getClass()->getMethod<jboolean()>("supportsWebWorkers");
return supportsWebWorkersMethod(wrapper_);
}
void invoke(ExecutorToken token, unsigned int reactMethodId, folly::dynamic&& params) override {
if (reactMethodId >= methods_.size()) {
throw std::invalid_argument(
folly::to<std::string>("methodId ", reactMethodId, " out of range [0..", methods_.size(), "]"));
}
CHECK(!methods_[reactMethodId].isSyncHook()) << "Trying to invoke a synchronous hook asynchronously";
invokeInner(token, reactMethodId, std::move(params));
}
MethodCallResult callSerializableNativeHook(ExecutorToken token, unsigned int reactMethodId, folly::dynamic&& params) override {
if (reactMethodId >= methods_.size()) {
throw std::invalid_argument(
folly::to<std::string>("methodId ", reactMethodId, " out of range [0..", methods_.size(), "]"));
}
CHECK(methods_[reactMethodId].isSyncHook()) << "Trying to invoke a asynchronous method as synchronous hook";
return invokeInner(token, reactMethodId, std::move(params));
}
private:
std::weak_ptr<Instance> instance_;
jni::global_ref<JavaModuleWrapper::javaobject> wrapper_;
jni::global_ref<JBaseJavaModule::javaobject> module_;
std::vector<MethodInvoker> methods_;
std::vector<MethodDescriptor> methodDescriptors_;
MethodCallResult invokeInner(ExecutorToken token, unsigned int reactMethodId, folly::dynamic&& params) {
if (!params.isArray()) {
throw std::invalid_argument(
folly::to<std::string>("method parameters should be array, but are ", params.typeName()));
}
return methods_[reactMethodId].invoke(instance_, module_.get(), token, params);
}
};
class CxxNativeModule : public NativeModule {
public:
CxxNativeModule(std::weak_ptr<Instance> instance,
std::unique_ptr<CxxModule> module)
: instance_(instance)
, module_(std::move(module))
, methods_(module_->getMethods()) {}
std::string getName() override {
return module_->getName();
}
virtual std::vector<MethodDescriptor> getMethods() override {
// Same as MessageQueue.MethodTypes.remote
static const auto kMethodTypeRemote = "remote";
std::vector<MethodDescriptor> descs;
for (auto& method : methods_) {
descs.emplace_back(method.name, kMethodTypeRemote);
}
return descs;
}
virtual folly::dynamic getConstants() override {
folly::dynamic constants = folly::dynamic::object();
for (auto& pair : module_->getConstants()) {
constants.insert(std::move(pair.first), std::move(pair.second));
}
return constants;
}
virtual bool supportsWebWorkers() override {
// TODO(andrews): web worker support in cxxmodules
return true;
}
// TODO mhorowitz: do we need initialize()/onCatalystInstanceDestroy() in C++
// or only Java?
virtual void invoke(ExecutorToken token, unsigned int reactMethodId, folly::dynamic&& params) override {
if (reactMethodId >= methods_.size()) {
throw std::invalid_argument(
folly::to<std::string>("methodId ", reactMethodId, " out of range [0..", methods_.size(), "]"));
}
if (!params.isArray()) {
throw std::invalid_argument(
folly::to<std::string>("method parameters should be array, but are ", params.typeName()));
}
CxxModule::Callback first;
CxxModule::Callback second;
const auto& method = methods_[reactMethodId];
if (params.size() < method.callbacks) {
throw std::invalid_argument(
folly::to<std::string>("Expected ", method.callbacks, " callbacks, but only ",
params.size(), " parameters provided"));
}
if (method.callbacks == 1) {
first = makeCallback(instance_, token, params[params.size() - 1]);
} else if (method.callbacks == 2) {
first = makeCallback(instance_, token, params[params.size() - 2]);
second = makeCallback(instance_, token, params[params.size() - 1]);
}
params.resize(params.size() - method.callbacks);
// I've got a few flawed options here. I can let the C++ exception
// propogate, and the registry will log/convert them to java exceptions.
// This lets all the java and red box handling work ok, but the only info I
// can capture about the C++ exception is the what() string, not the stack.
// I can std::terminate() the app. This causes the full, accurate C++
// stack trace to be added to logcat by debuggerd. The java state is lost,
// but in practice, the java stack is always the same in this case since
// the javascript stack is not visible, and the crash is unfriendly to js
// developers, but crucial to C++ developers. The what() value is also
// lost. Finally, I can catch, log the java stack, then rethrow the C++
// exception. In this case I get java and C++ stack data, but the C++
// stack is as of the rethrow, not the original throw, both the C++ and
// java stacks always look the same.
//
// I am going with option 2, since that seems like the most useful
// choice. It would be nice to be able to get what() and the C++
// stack. I'm told that will be possible in the future. TODO
// mhorowitz #7128529: convert C++ exceptions to Java
try {
method.func(std::move(params), first, second);
} catch (const facebook::xplat::JsArgumentException& ex) {
// This ends up passed to the onNativeException callback.
throw;
} catch (...) {
// This means some C++ code is buggy. As above, we fail hard so the C++
// developer can debug and fix it.
std::terminate();
}
}
MethodCallResult callSerializableNativeHook(ExecutorToken token, unsigned int hookId, folly::dynamic&& args) override {
throw std::runtime_error("Not supported");
}
private:
std::weak_ptr<Instance> instance_;
std::unique_ptr<CxxModule> module_;
std::vector<CxxModule::Method> methods_;
};
}
jni::local_ref<JReflectMethod::javaobject> JMethodDescriptor::getMethod() const {
static auto method = javaClassStatic()->getField<JReflectMethod::javaobject>("method");
return getFieldValue(method);
}
std::string JMethodDescriptor::getSignature() const {
static auto signature = javaClassStatic()->getField<jstring>("signature");
return getFieldValue(signature)->toStdString();
}
std::string JMethodDescriptor::getName() const {
static auto name = javaClassStatic()->getField<jstring>("name");
return getFieldValue(name)->toStdString();
}
std::string JMethodDescriptor::getType() const {
static auto type = javaClassStatic()->getField<jstring>("type");
return getFieldValue(type)->toStdString();
}
void ModuleRegistryHolder::registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", ModuleRegistryHolder::initHybrid),
});
}
ModuleRegistryHolder::ModuleRegistryHolder(
CatalystInstanceImpl* catalystInstanceImpl,
jni::alias_ref<jni::JCollection<JavaModuleWrapper::javaobject>::javaobject> javaModules,
jni::alias_ref<jni::JCollection<CxxModuleWrapper::javaobject>::javaobject> cxxModules) {
std::vector<std::unique_ptr<NativeModule>> modules;
std::weak_ptr<Instance> winstance(catalystInstanceImpl->getInstance());
for (const auto& jm : *javaModules) {
modules.emplace_back(folly::make_unique<JavaNativeModule>(jm));
}
for (const auto& cm : *cxxModules) {
modules.emplace_back(
folly::make_unique<CxxNativeModule>(winstance, std::move(cthis(cm)->getModule())));
}
registry_ = std::make_shared<ModuleRegistry>(std::move(modules));
}
Callback makeCallback(std::weak_ptr<Instance> instance, ExecutorToken token, const folly::dynamic& callbackId) {
if (!callbackId.isInt()) {
throw std::invalid_argument("Expected callback(s) as final argument");
}
auto id = callbackId.getInt();
return [winstance = std::move(instance), token, id](folly::dynamic args) {
if (auto instance = winstance.lock()) {
jni::ThreadScope guard;
instance->callJSCallback(token, id, std::move(args));
}
};
}
}
}

View File

@@ -0,0 +1,94 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <fb/fbjni.h>
#include <cxxreact/ModuleRegistry.h>
#include <react/jni/CxxModuleWrapper.h>
namespace facebook {
namespace react {
class Instance;
class CatalystInstanceImpl;
struct JReflectMethod : public jni::JavaClass<JReflectMethod> {
static constexpr auto kJavaDescriptor = "Ljava/lang/reflect/Method;";
jmethodID getMethodID() {
auto id = jni::Environment::current()->FromReflectedMethod(self());
jni::throwPendingJniExceptionAsCppException();
return id;
}
};
struct JMethodDescriptor : public jni::JavaClass<JMethodDescriptor> {
static constexpr auto kJavaDescriptor =
"Lcom/facebook/react/cxxbridge/JavaModuleWrapper$MethodDescriptor;";
jni::local_ref<JReflectMethod::javaobject> getMethod() const;
std::string getSignature() const;
std::string getName() const;
std::string getType() const;
};
struct JBaseJavaModule : public jni::JavaClass<JBaseJavaModule> {
static constexpr auto kJavaDescriptor = "Lcom/facebook/react/bridge/BaseJavaModule;";
};
struct JavaModuleWrapper : jni::JavaClass<JavaModuleWrapper> {
static constexpr auto kJavaDescriptor = "Lcom/facebook/react/cxxbridge/JavaModuleWrapper;";
jni::local_ref<JBaseJavaModule::javaobject> getModule() {
static auto getModule = javaClassStatic()->getMethod<JBaseJavaModule::javaobject()>("getModule");
return getModule(self());
}
jni::local_ref<jni::JList<JMethodDescriptor::javaobject>::javaobject> getMethodDescriptors() {
static auto getMethods =
getClass()->getMethod<jni::JList<JMethodDescriptor::javaobject>::javaobject()>("getMethodDescriptors");
return getMethods(self());
}
jni::local_ref<jni::JList<JMethodDescriptor::javaobject>::javaobject> newGetMethodDescriptors() {
static auto getMethods =
getClass()->getMethod<jni::JList<JMethodDescriptor::javaobject>::javaobject()>("newGetMethodDescriptors");
return getMethods(self());
}
};
class ModuleRegistryHolder : public jni::HybridClass<ModuleRegistryHolder> {
public:
static constexpr auto kJavaDescriptor = "Lcom/facebook/react/cxxbridge/ModuleRegistryHolder;";
std::shared_ptr<ModuleRegistry> getModuleRegistry() {
return registry_;
}
static jni::local_ref<jhybriddata> initHybrid(
jni::alias_ref<jclass>,
CatalystInstanceImpl* catalystInstanceImpl,
jni::alias_ref<jni::JCollection<JavaModuleWrapper::javaobject>::javaobject> javaModules,
jni::alias_ref<jni::JCollection<CxxModuleWrapper::javaobject>::javaobject> cxxModules) {
return makeCxxInstance(catalystInstanceImpl, javaModules, cxxModules);
}
static void registerNatives();
private:
friend HybridBase;
ModuleRegistryHolder(
CatalystInstanceImpl* catalystInstanceImpl,
jni::alias_ref<jni::JCollection<JavaModuleWrapper::javaobject>::javaobject> javaModules,
jni::alias_ref<jni::JCollection<CxxModuleWrapper::javaobject>::javaobject> cxxModules);
facebook::xplat::module::CxxModule::Callback makeCallback(const folly::dynamic& callbackId);
std::shared_ptr<ModuleRegistry> registry_;
};
using Callback = std::function<void(folly::dynamic)>;
Callback makeCallback(std::weak_ptr<Instance> instance, ExecutorToken token, const folly::dynamic& callbackId);
}}

View File

@@ -0,0 +1,186 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include <folly/dynamic.h>
#include <fb/fbjni.h>
#include <fb/log.h>
#include <cxxreact/Executor.h>
#include <cxxreact/JSCExecutor.h>
#include <cxxreact/Platform.h>
#include <cxxreact/Value.h>
#include <react/jni/ReadableNativeArray.h>
#include "CatalystInstanceImpl.h"
#include "JavaScriptExecutorHolder.h"
#include "JSCPerfLogging.h"
#include "JSLoader.h"
#include "ModuleRegistryHolder.h"
#include "ProxyExecutor.h"
#include "WebWorkers.h"
#include "JCallback.h"
#include <string>
using namespace facebook::jni;
namespace facebook {
namespace react {
namespace {
static std::string getApplicationDir(const char* methodName) {
// Get the Application Context object
auto getApplicationClass = findClassLocal(
"com/facebook/react/common/ApplicationHolder");
auto getApplicationMethod = getApplicationClass->getStaticMethod<jobject()>(
"getApplication",
"()Landroid/app/Application;"
);
auto application = getApplicationMethod(getApplicationClass);
// Get getCacheDir() from the context
auto getDirMethod = findClassLocal("android/app/Application")
->getMethod<jobject()>(methodName,
"()Ljava/io/File;"
);
auto dirObj = getDirMethod(application);
// Call getAbsolutePath() on the returned File object
auto getAbsolutePathMethod = findClassLocal("java/io/File")
->getMethod<jstring()>("getAbsolutePath");
return getAbsolutePathMethod(dirObj)->toStdString();
}
static std::string getApplicationCacheDir() {
return getApplicationDir("getCacheDir");
}
static std::string getApplicationPersistentDir() {
return getApplicationDir("getFilesDir");
}
static JSValueRef nativeLoggingHook(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[], JSValueRef *exception) {
android_LogPriority logLevel = ANDROID_LOG_DEBUG;
if (argumentCount > 1) {
int level = (int) JSValueToNumber(ctx, arguments[1], NULL);
// The lowest log level we get from JS is 0. We shift and cap it to be
// in the range the Android logging method expects.
logLevel = std::min(
static_cast<android_LogPriority>(level + ANDROID_LOG_DEBUG),
ANDROID_LOG_FATAL);
}
if (argumentCount > 0) {
JSStringRef jsString = JSValueToStringCopy(ctx, arguments[0], NULL);
String message = String::adopt(jsString);
FBLOG_PRI(logLevel, "ReactNativeJS", "%s", message.str().c_str());
}
return JSValueMakeUndefined(ctx);
}
static JSValueRef nativePerformanceNow(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[], JSValueRef *exception) {
static const int64_t NANOSECONDS_IN_SECOND = 1000000000LL;
static const int64_t NANOSECONDS_IN_MILLISECOND = 1000000LL;
// This is equivalent to android.os.SystemClock.elapsedRealtime() in native
struct timespec now;
clock_gettime(CLOCK_MONOTONIC_RAW, &now);
int64_t nano = now.tv_sec * NANOSECONDS_IN_SECOND + now.tv_nsec;
return JSValueMakeNumber(ctx, (nano / (double)NANOSECONDS_IN_MILLISECOND));
}
class JSCJavaScriptExecutorHolder : public HybridClass<JSCJavaScriptExecutorHolder,
JavaScriptExecutorHolder> {
public:
static constexpr auto kJavaDescriptor = "Lcom/facebook/react/cxxbridge/JSCJavaScriptExecutor;";
static local_ref<jhybriddata> initHybrid(alias_ref<jclass>, ReadableNativeArray* jscConfigArray) {
// See JSCJavaScriptExecutor.Factory() for the other side of this hack.
folly::dynamic jscConfigMap = jscConfigArray->array[0];
jscConfigMap["PersistentDirectory"] = getApplicationPersistentDir();
return makeCxxInstance(
std::make_shared<JSCExecutorFactory>(getApplicationCacheDir(), std::move(jscConfigMap)));
}
static void registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", JSCJavaScriptExecutorHolder::initHybrid),
});
}
private:
friend HybridBase;
using HybridBase::HybridBase;
};
struct JavaJSExecutor : public JavaClass<JavaJSExecutor> {
static constexpr auto kJavaDescriptor = "Lcom/facebook/react/bridge/JavaJSExecutor;";
};
class ProxyJavaScriptExecutorHolder : public HybridClass<ProxyJavaScriptExecutorHolder,
JavaScriptExecutorHolder> {
public:
static constexpr auto kJavaDescriptor = "Lcom/facebook/react/cxxbridge/ProxyJavaScriptExecutor;";
static local_ref<jhybriddata> initHybrid(
alias_ref<jclass>, alias_ref<JavaJSExecutor::javaobject> executorInstance) {
return makeCxxInstance(
std::make_shared<ProxyExecutorOneTimeFactory>(
make_global(executorInstance)));
}
static void registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", ProxyJavaScriptExecutorHolder::initHybrid),
});
}
private:
friend HybridBase;
using HybridBase::HybridBase;
};
class JReactMarker : public JavaClass<JReactMarker> {
public:
static constexpr auto kJavaDescriptor = "Lcom/facebook/react/bridge/ReactMarker;";
static void logMarker(const std::string& marker) {
static auto cls = javaClassStatic();
static auto meth = cls->getStaticMethod<void(std::string)>("logMarker");
meth(cls, marker);
}
};
}
extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
return initialize(vm, [] {
// Inject some behavior into react/
ReactMarker::logMarker = JReactMarker::logMarker;
WebWorkerUtil::createWebWorkerThread = WebWorkers::createWebWorkerThread;
WebWorkerUtil::loadScriptFromAssets =
[] (const std::string& assetName) {
return loadScriptFromAssets(assetName);
};
WebWorkerUtil::loadScriptFromNetworkSync = WebWorkers::loadScriptFromNetworkSync;
PerfLogging::installNativeHooks = addNativePerfLoggingHooks;
JSNativeHooks::loggingHook = nativeLoggingHook;
JSNativeHooks::nowHook = nativePerformanceNow;
JSCJavaScriptExecutorHolder::registerNatives();
ProxyJavaScriptExecutorHolder::registerNatives();
CatalystInstanceImpl::registerNatives();
ModuleRegistryHolder::registerNatives();
CxxModuleWrapper::registerNatives();
JCallbackImpl::registerNatives();
registerJSLoaderNatives();
});
}
}}

View File

@@ -0,0 +1,14 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <jni.h>
#include <jni/Countable.h>
#include <cxxreact/Executor.h>
namespace facebook {
namespace react {
jmethodID getLogMarkerMethod();
} // namespace react
} // namespace facebook

View File

@@ -0,0 +1,116 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "ProxyExecutor.h"
#include <fb/assert.h>
#include <fb/Environment.h>
#include <jni/LocalReference.h>
#include <jni/LocalString.h>
#include <folly/json.h>
#include <folly/Memory.h>
#include <cxxreact/SystraceSection.h>
namespace facebook {
namespace react {
const auto EXECUTOR_BASECLASS = "com/facebook/react/bridge/JavaJSExecutor";
static std::string executeJSCallWithProxy(
jobject executor,
const std::string& methodName,
const std::vector<folly::dynamic>& arguments) {
static auto executeJSCall =
jni::findClassStatic(EXECUTOR_BASECLASS)->getMethod<jstring(jstring, jstring)>("executeJSCall");
auto result = executeJSCall(
executor,
jni::make_jstring(methodName).get(),
jni::make_jstring(folly::toJson(arguments).c_str()).get());
return result->toString();
}
std::unique_ptr<JSExecutor> ProxyExecutorOneTimeFactory::createJSExecutor(
std::shared_ptr<ExecutorDelegate> delegate, std::shared_ptr<MessageQueueThread>) {
return folly::make_unique<ProxyExecutor>(std::move(m_executor), delegate);
}
ProxyExecutor::ProxyExecutor(jni::global_ref<jobject>&& executorInstance,
std::shared_ptr<ExecutorDelegate> delegate)
: m_executor(std::move(executorInstance))
, m_delegate(delegate) {
folly::dynamic nativeModuleConfig = folly::dynamic::array;
{
SystraceSection s("collectNativeModuleDescriptions");
for (const auto& name : delegate->moduleNames()) {
nativeModuleConfig.push_back(delegate->getModuleConfig(name));
}
}
folly::dynamic config =
folly::dynamic::object
("remoteModuleConfig", std::move(nativeModuleConfig));
SystraceSection t("setGlobalVariable");
setGlobalVariable(
"__fbBatchedBridgeConfig",
folly::make_unique<JSBigStdString>(folly::toJson(config)));
}
ProxyExecutor::~ProxyExecutor() {
m_executor.reset();
}
void ProxyExecutor::loadApplicationScript(
std::unique_ptr<const JSBigString>,
std::string sourceURL) {
static auto loadApplicationScript =
jni::findClassStatic(EXECUTOR_BASECLASS)->getMethod<void(jstring)>("loadApplicationScript");
// The proxy ignores the script data passed in.
loadApplicationScript(
m_executor.get(),
jni::make_jstring(sourceURL).get());
executeJSCallWithProxy(m_executor.get(), "flushedQueue", std::vector<folly::dynamic>());
}
void ProxyExecutor::setJSModulesUnbundle(std::unique_ptr<JSModulesUnbundle>) {
jni::throwNewJavaException(
"java/lang/UnsupportedOperationException",
"Loading application unbundles is not supported for proxy executors");
}
void ProxyExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) {
std::vector<folly::dynamic> call{
moduleId,
methodId,
std::move(arguments),
};
std::string result = executeJSCallWithProxy(m_executor.get(), "callFunctionReturnFlushedQueue", std::move(call));
m_delegate->callNativeModules(*this, result, true);
}
void ProxyExecutor::invokeCallback(const double callbackId, const folly::dynamic& arguments) {
std::vector<folly::dynamic> call{
(double) callbackId,
std::move(arguments)
};
std::string result = executeJSCallWithProxy(m_executor.get(), "invokeCallbackAndReturnFlushedQueue", std::move(call));
m_delegate->callNativeModules(*this, result, true);
}
void ProxyExecutor::setGlobalVariable(std::string propName,
std::unique_ptr<const JSBigString> jsonValue) {
static auto setGlobalVariable =
jni::findClassStatic(EXECUTOR_BASECLASS)->getMethod<void(jstring, jstring)>("setGlobalVariable");
setGlobalVariable(
m_executor.get(),
jni::make_jstring(propName).get(),
jni::make_jstring(jsonValue->c_str()).get());
}
} }

View File

@@ -0,0 +1,56 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <cxxreact/Executor.h>
#include <fb/fbjni.h>
#include <jni.h>
#include <jni/GlobalReference.h>
#include "OnLoad.h"
namespace facebook {
namespace react {
/**
* This executor factory can only create a single executor instance because it moves
* executorInstance global reference to the executor instance it creates.
*/
class ProxyExecutorOneTimeFactory : public JSExecutorFactory {
public:
ProxyExecutorOneTimeFactory(jni::global_ref<jobject>&& executorInstance) :
m_executor(std::move(executorInstance)) {}
virtual std::unique_ptr<JSExecutor> createJSExecutor(
std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> queue) override;
private:
jni::global_ref<jobject> m_executor;
};
class ProxyExecutor : public JSExecutor {
public:
ProxyExecutor(jni::global_ref<jobject>&& executorInstance,
std::shared_ptr<ExecutorDelegate> delegate);
virtual ~ProxyExecutor() override;
virtual void loadApplicationScript(
std::unique_ptr<const JSBigString> script,
std::string sourceURL) override;
virtual void setJSModulesUnbundle(
std::unique_ptr<JSModulesUnbundle> bundle) override;
virtual void callFunction(
const std::string& moduleId,
const std::string& methodId,
const folly::dynamic& arguments) override;
virtual void invokeCallback(
const double callbackId,
const folly::dynamic& arguments) override;
virtual void setGlobalVariable(
std::string propName,
std::unique_ptr<const JSBigString> jsonValue) override;
private:
jni::global_ref<jobject> m_executor;
std::shared_ptr<ExecutorDelegate> m_delegate;
};
} }

View File

@@ -0,0 +1,50 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <fstream>
#include <memory>
#include <string>
#include <sstream>
#include <jni.h>
#include <folly/Memory.h>
#include "JMessageQueueThread.h"
using namespace facebook::jni;
namespace facebook {
namespace react {
class WebWorkers : public JavaClass<WebWorkers> {
public:
static constexpr auto kJavaDescriptor = "Lcom/facebook/react/bridge/webworkers/WebWorkers;";
static std::unique_ptr<JMessageQueueThread> createWebWorkerThread(int id, MessageQueueThread *ownerMessageQueueThread) {
static auto method = WebWorkers::javaClassStatic()->
getStaticMethod<JavaMessageQueueThread::javaobject(jint, JavaMessageQueueThread::javaobject)>("createWebWorkerThread");
auto res = method(WebWorkers::javaClassStatic(), id, static_cast<JMessageQueueThread*>(ownerMessageQueueThread)->jobj());
return folly::make_unique<JMessageQueueThread>(res);
}
static std::string loadScriptFromNetworkSync(const std::string& url, const std::string& tempfileName) {
static auto method = WebWorkers::javaClassStatic()->
getStaticMethod<void(jstring, jstring)>("downloadScriptToFileSync");
method(
WebWorkers::javaClassStatic(),
jni::make_jstring(url).get(),
jni::make_jstring(tempfileName).get());
std::ifstream tempFile(tempfileName);
if (!tempFile.good()) {
throw std::runtime_error("Didn't find worker script file at " + tempfileName);
}
std::stringstream buffer;
buffer << tempFile.rdbuf();
std::remove(tempfileName.c_str());
return buffer.str();
}
};
} }

View File

@@ -0,0 +1,21 @@
cxx_library(
name = 'perftests',
srcs = [ 'OnLoad.cpp' ],
soname = 'libnativereactperftests.so',
compiler_flags = [
'-fexceptions',
],
deps = [
'//native:base',
'//native/fb:fb',
'//xplat/folly:molly',
'//xplat/react/module:module',
],
visibility = [
'//instrumentation_tests/com/facebook/react/...',
],
)
project_config(
src_target = ':perftests',
)

View File

@@ -0,0 +1,156 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include <fb/log.h>
#include <fb/fbjni.h>
#include <Module/CxxModule.h>
#include <Module/JsArgumentHelpers.h>
#include <mutex>
#include <condition_variable>
namespace facebook {
namespace react {
using facebook::jni::alias_ref;
namespace {
// This is a wrapper around the Java proxy to the javascript module. This
// allows us to call functions on the js module from c++.
class JavaJSModule : public jni::JavaClass<JavaJSModule> {
public:
static constexpr auto kJavaDescriptor =
"Lcom/facebook/react/CatalystBridgeBenchmarks$BridgeBenchmarkModule;";
static void bounceCxx(alias_ref<javaobject> obj, int iters) {
static auto method = javaClassLocal()->getMethod<void(jint)>("bounceCxx");
method(obj, iters);
}
static void bounceArgsCxx(
alias_ref<javaobject> obj,
int iters,
int a, int b,
double x, double y,
const std::string& s, const std::string& t) {
static auto method =
javaClassLocal()->getMethod<void(jint, jint, jint, jdouble, jdouble, jstring, jstring)>("bounceArgsCxx");
method(obj, iters, a, b, x, y, jni::make_jstring(s).get(), jni::make_jstring(t).get());
}
};
// This is just the test instance itself. Used only to countdown the latch.
class CatalystBridgeBenchmarks : public jni::JavaClass<CatalystBridgeBenchmarks> {
public:
static constexpr auto kJavaDescriptor =
"Lcom/facebook/react/CatalystBridgeBenchmarks;";
static void countDown(alias_ref<javaobject> obj) {
static auto method = javaClassLocal()->getMethod<void()>("countDown");
method(obj);
}
};
// This is the shared data for two cxx bounce threads.
struct Data {
std::mutex m;
std::condition_variable cv;
bool leftActive;
Data() : leftActive(true) {}
};
Data data;
void runBounce(jni::alias_ref<jclass>, bool isLeft, int iters) {
for (int i = 0; i < iters; i++) {
std::unique_lock<std::mutex> lk(data.m);
data.cv.wait(lk, [&]{ return data.leftActive == isLeft; });
data.leftActive = !isLeft;
data.cv.notify_one();
}
}
static jni::global_ref<JavaJSModule::javaobject> jsModule;
static jni::global_ref<CatalystBridgeBenchmarks::javaobject> javaTestInstance;
class CxxBenchmarkModule : public xplat::module::CxxModule {
public:
virtual std::string getName() override {
return "CxxBenchmarkModule";
}
virtual auto getConstants() -> std::map<std::string, folly::dynamic> override {
return std::map<std::string, folly::dynamic>();
}
virtual auto getMethods() -> std::vector<Method> override {
return std::vector<Method>{
Method("bounce", [this] (folly::dynamic args) {
this->bounce(xplat::jsArgAsInt(args, 0));
}),
Method("bounceArgs", [this] (folly::dynamic args) {
this->bounceArgs(
xplat::jsArgAsInt(args, 0),
xplat::jsArgAsInt(args, 1),
xplat::jsArgAsInt(args, 2),
xplat::jsArgAsDouble(args, 3),
xplat::jsArgAsDouble(args, 4),
xplat::jsArgAsString(args, 5),
xplat::jsArgAsString(args, 6));
}),
};
}
void bounce(int iters) {
if (iters == 0) {
CatalystBridgeBenchmarks::countDown(javaTestInstance);
} else {
JavaJSModule::bounceCxx(jsModule, iters - 1);
}
}
void bounceArgs(
int iters,
int a, int b,
double x, double y,
const std::string& s, const std::string& t) {
if (iters == 0) {
CatalystBridgeBenchmarks::countDown(javaTestInstance);
} else {
JavaJSModule::bounceArgsCxx(jsModule, iters - 1, a, b, x, y, s, t);
}
}
};
void setUp(
alias_ref<CatalystBridgeBenchmarks::javaobject> obj,
alias_ref<JavaJSModule::javaobject> mod) {
javaTestInstance = jni::make_global(obj);
jsModule = jni::make_global(mod);
}
void tearDown(
alias_ref<CatalystBridgeBenchmarks::javaobject>) {
javaTestInstance.reset();
jsModule.reset();
}
}
}
}
extern "C" facebook::xplat::module::CxxModule* CxxBenchmarkModule() {
return new facebook::react::CxxBenchmarkModule();
}
extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
return facebook::jni::initialize(vm, [] {
facebook::jni::registerNatives(
"com/facebook/react/CatalystBridgeBenchmarks", {
makeNativeMethod("runNativeBounce", facebook::react::runBounce),
makeNativeMethod("nativeSetUp", facebook::react::setUp),
makeNativeMethod("nativeTearDown", facebook::react::tearDown),
});
});
}