react-native/ReactCommon/cxxreact/CxxNativeModule.cpp

166 lines
5.6 KiB
C++
Raw Normal View History

// Copyright 2004-present Facebook. All Rights Reserved.
#include "CxxNativeModule.h"
#include "Instance.h"
#include <folly/json.h>
#include <cxxreact/JsArgumentHelpers.h>
using facebook::xplat::module::CxxModule;
namespace facebook {
namespace react {
std::function<void(folly::dynamic)> 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()) {
instance->callJSCallback(token, id, std::move(args));
}
};
}
CxxNativeModule::CxxNativeModule(std::weak_ptr<Instance> instance,
std::unique_ptr<CxxModule> module)
: instance_(instance)
, module_(std::move(module))
, methods_(module_->getMethods()) {}
std::string CxxNativeModule::getName() {
return module_->getName();
}
std::vector<MethodDescriptor> CxxNativeModule::getMethods() {
// Same as MessageQueue.MethodTypes.remote
static const auto kMethodTypeRemote = "remote";
static const auto kMethodTypeSyncHook = "syncHook";
std::vector<MethodDescriptor> descs;
for (auto& method : methods_) {
assert(method.func || method.syncFunc);
descs.emplace_back(method.name, method.func ? kMethodTypeRemote : kMethodTypeSyncHook);
}
return descs;
}
folly::dynamic CxxNativeModule::getConstants() {
folly::dynamic constants = folly::dynamic::object();
for (auto& pair : module_->getConstants()) {
constants.insert(std::move(pair.first), std::move(pair.second));
}
return constants;
}
bool CxxNativeModule::supportsWebWorkers() {
// TODO(andrews): web worker support in cxxmodules
return false;
}
void CxxNativeModule::invoke(ExecutorToken token, unsigned int reactMethodId, folly::dynamic&& params) {
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 (!method.func) {
throw std::runtime_error(
folly::to<std::string>("Method ", method.name,
" is synchronous but invoked asynchronously"));
}
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 CxxNativeModule::callSerializableNativeHook(
ExecutorToken token, unsigned int hookId, folly::dynamic&& args) {
if (hookId >= methods_.size()) {
throw std::invalid_argument(
folly::to<std::string>("methodId ", hookId, " out of range [0..", methods_.size(), "]"));
}
const auto& method = methods_[hookId];
if (!method.syncFunc) {
throw std::runtime_error(
folly::to<std::string>("Method ", method.name,
" is asynchronous but invoked synchronously"));
}
if (!args.isString()) {
throw std::invalid_argument(
folly::to<std::string>("method parameters should be string, but are ", args.typeName()));
}
folly::dynamic params = folly::parseJson(args.stringPiece());
if (!params.isArray()) {
throw std::invalid_argument(
folly::to<std::string>("parsed method parameters should be array, but are ",
args.typeName()));
}
return { method.syncFunc(std::move(params)), false };
}
}
}