diff --git a/ReactCommon/cxxreact/Instance.h b/ReactCommon/cxxreact/Instance.h index 2a6035659..c9ebe5f6a 100644 --- a/ReactCommon/cxxreact/Instance.h +++ b/ReactCommon/cxxreact/Instance.h @@ -55,6 +55,11 @@ class Instance { void callJSCallback(ExecutorToken token, uint64_t callbackId, folly::dynamic&& params); MethodCallResult callSerializableNativeHook(ExecutorToken token, unsigned int moduleId, unsigned int methodId, folly::dynamic&& args); + // This method is experimental, and may be modified or removed. + template + Value callFunctionSync(const std::string& module, const std::string& method, T&& args) { + return nativeToJsBridge_->callFunctionSync(module, method, std::forward(args)); + } ExecutorToken getMainExecutorToken(); void handleMemoryPressureUiHidden(); diff --git a/ReactCommon/cxxreact/JSCExecutor.cpp b/ReactCommon/cxxreact/JSCExecutor.cpp index e6fe99d0a..9f2b4c1c8 100644 --- a/ReactCommon/cxxreact/JSCExecutor.cpp +++ b/ReactCommon/cxxreact/JSCExecutor.cpp @@ -118,7 +118,6 @@ JSCExecutor::JSCExecutor(std::shared_ptr delegate, folly::dynamic::object ("remoteModuleConfig", std::move(nativeModuleConfig)); - SystraceSection t("setGlobalVariable"); setGlobalVariable( "__fbBatchedBridgeConfig", @@ -344,6 +343,7 @@ void JSCExecutor::bindBridge() throw(JSException) { m_callFunctionReturnFlushedQueueJS = batchedBridge.getProperty("callFunctionReturnFlushedQueue").asObject(); m_invokeCallbackAndReturnFlushedQueueJS = batchedBridge.getProperty("invokeCallbackAndReturnFlushedQueue").asObject(); m_flushedQueueJS = batchedBridge.getProperty("flushedQueue").asObject(); + m_callFunctionReturnResultAndFlushedQueueJS = batchedBridge.getProperty("callFunctionReturnResultAndFlushedQueue").asObject(); } void JSCExecutor::callNativeModules(Value&& value) { @@ -405,6 +405,26 @@ void JSCExecutor::invokeCallback(const double callbackId, const folly::dynamic& callNativeModules(std::move(result)); } +Value JSCExecutor::callFunctionSyncWithValue( + const std::string& module, const std::string& method, Value args) { + SystraceSection s("JSCExecutor::callFunction"); + + Object result = m_callFunctionReturnResultAndFlushedQueueJS->callAsFunction({ + Value(m_context, String::createExpectingAscii(module)), + Value(m_context, String::createExpectingAscii(method)), + std::move(args), + }).asObject(); + + Value length = result.getProperty("length"); + + if (!length.isNumber() || length.asInteger() != 2) { + std::runtime_error("Return value of a callFunction must be an array of size 2"); + } + + callNativeModules(result.getPropertyAtIndex(1)); + return result.getPropertyAtIndex(0); +} + void JSCExecutor::setGlobalVariable(std::string propName, std::unique_ptr jsonValue) { try { SystraceSection s("JSCExecutor.setGlobalVariable", diff --git a/ReactCommon/cxxreact/JSCExecutor.h b/ReactCommon/cxxreact/JSCExecutor.h index 3d6d366d7..63ea21b63 100644 --- a/ReactCommon/cxxreact/JSCExecutor.h +++ b/ReactCommon/cxxreact/JSCExecutor.h @@ -74,6 +74,12 @@ public: virtual void invokeCallback( const double callbackId, const folly::dynamic& arguments) override; + template + Value callFunctionSync( + const std::string& module, const std::string& method, T&& args) { + return callFunctionSyncWithValue(module, method, + toValue(m_context, std::forward(args))); + } virtual void setGlobalVariable( std::string propName, std::unique_ptr jsonValue) override; @@ -101,6 +107,7 @@ private: folly::Optional m_invokeCallbackAndReturnFlushedQueueJS; folly::Optional m_callFunctionReturnFlushedQueueJS; folly::Optional m_flushedQueueJS; + folly::Optional m_callFunctionReturnResultAndFlushedQueueJS; /** * WebWorker constructor. Must be invoked from thread this Executor will run on. @@ -115,6 +122,9 @@ private: const folly::dynamic& jscConfig); void initOnJSVMThread() throw(JSException); + // This method is experimental, and may be modified or removed. + Value callFunctionSyncWithValue( + const std::string& module, const std::string& method, Value value); void terminateOnJSVMThread(); void bindBridge() throw(JSException); void callNativeModules(Value&&); diff --git a/ReactCommon/cxxreact/NativeToJsBridge.h b/ReactCommon/cxxreact/NativeToJsBridge.h index 6deb038e5..226e0b470 100644 --- a/ReactCommon/cxxreact/NativeToJsBridge.h +++ b/ReactCommon/cxxreact/NativeToJsBridge.h @@ -11,6 +11,7 @@ #include "Executor.h" #include "ExecutorToken.h" +#include "JSCExecutor.h" #include "JSModulesUnbundle.h" #include "MessageQueueThread.h" #include "MethodCall.h" @@ -80,6 +81,35 @@ public: */ void invokeCallback(ExecutorToken executorToken, double callbackId, folly::dynamic&& args); + /** + * Executes a JS method on the given executor synchronously, returning its + * return value. JSException will be thrown if JS throws an exception; + * another standard exception may be thrown for C++ bridge failures, or if + * the executor is not capable of synchronous calls. + * + * This method is experimental, and may be modified or removed. + * + * loadApplicationScriptSync() must be called and finished executing + * before callFunctionSync(). + */ + template + Value callFunctionSync(const std::string& module, const std::string& method, T&& args) { + if (*m_destroyed) { + throw std::logic_error( + folly::to("Synchronous call to ", module, ".", method, + " after bridge is destroyed")); + } + + JSCExecutor *jscExecutor = dynamic_cast(m_mainExecutor); + if (!jscExecutor) { + throw std::invalid_argument( + folly::to("Executor type ", typeid(*m_mainExecutor).name(), + " does not support synchronous calls")); + } + + return jscExecutor->callFunctionSync(module, method, std::forward(args)); + } + /** * Starts the JS application. If unbundle is non-null, then it is * used to fetch JavaScript modules as individual scripts.