diff --git a/ReactCommon/cxxreact/JSCExecutor.cpp b/ReactCommon/cxxreact/JSCExecutor.cpp index cee2f2ece..810d0ecfc 100644 --- a/ReactCommon/cxxreact/JSCExecutor.cpp +++ b/ReactCommon/cxxreact/JSCExecutor.cpp @@ -361,7 +361,6 @@ void JSCExecutor::loadApplicationScript(std::unique_ptr scrip evaluateSourceCode(m_context, bcSourceCode, jsSourceURL); - bindBridge(); flush(); ReactMarker::logMarker("CREATE_REACT_CONTEXT_END"); @@ -412,7 +411,6 @@ void JSCExecutor::loadApplicationScript(std::unique_ptr scrip evaluateScript(m_context, jsScript, jsSourceURL); } - bindBridge(); flush(); ReactMarker::logMarker("CREATE_REACT_CONTEXT_END"); @@ -428,24 +426,32 @@ void JSCExecutor::setJSModulesUnbundle(std::unique_ptr unbund void JSCExecutor::bindBridge() throw(JSException) { SystraceSection s("JSCExecutor::bindBridge"); - if (!m_delegate || !m_delegate->getModuleRegistry()) { - return; - } - auto global = Object::getGlobalObject(m_context); - auto batchedBridgeValue = global.getProperty("__fbBatchedBridge"); - if (batchedBridgeValue.isUndefined()) { - throwJSExecutionException("Could not get BatchedBridge, make sure your bundle is packaged correctly"); - } + std::call_once(m_bindFlag, [this] { + auto global = Object::getGlobalObject(m_context); + auto batchedBridgeValue = global.getProperty("__fbBatchedBridge"); + if (batchedBridgeValue.isUndefined()) { + auto requireBatchedBridge = global.getProperty("__fbRequireBatchedBridge"); + if (!requireBatchedBridge.isUndefined()) { + batchedBridgeValue = requireBatchedBridge.asObject().callAsFunction({}); + } + if (batchedBridgeValue.isUndefined()) { + throwJSExecutionException("Could not get BatchedBridge, make sure your bundle is packaged correctly"); + } + } - auto batchedBridge = batchedBridgeValue.asObject(); - m_callFunctionReturnFlushedQueueJS = batchedBridge.getProperty("callFunctionReturnFlushedQueue").asObject(); - m_invokeCallbackAndReturnFlushedQueueJS = batchedBridge.getProperty("invokeCallbackAndReturnFlushedQueue").asObject(); - m_flushedQueueJS = batchedBridge.getProperty("flushedQueue").asObject(); - m_callFunctionReturnResultAndFlushedQueueJS = batchedBridge.getProperty("callFunctionReturnResultAndFlushedQueue").asObject(); + auto batchedBridge = batchedBridgeValue.asObject(); + 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) { SystraceSection s("JSCExecutor::callNativeModules"); + // If this fails, you need to pass a fully functional delegate with a + // module registry to the factory/ctor. + CHECK(m_delegate) << "Attempting to use native modules without a delegate"; try { auto calls = value.toJSONString(); m_delegate->callNativeModules(*this, folly::parseJson(calls), true); @@ -462,17 +468,31 @@ void JSCExecutor::callNativeModules(Value&& value) { void JSCExecutor::flush() { SystraceSection s("JSCExecutor::flush"); - if (!m_delegate) { - // do nothing - } else if (!m_delegate->getModuleRegistry()) { - callNativeModules(Value::makeNull(m_context)); - } else { - // If this is failing, chances are you have provided a delegate with a - // module registry, but haven't loaded the JS which enables native function - // queueing. Add BatchedBridge.js to your bundle, pass a nullptr delegate, - // or make delegate->getModuleRegistry() return nullptr. - CHECK(m_flushedQueueJS) << "Attempting to use native methods without loading BatchedBridge.js"; + + if (m_flushedQueueJS) { callNativeModules(m_flushedQueueJS->callAsFunction({})); + return; + } + + // When a native module is called from JS, BatchedBridge.enqueueNativeCall() + // is invoked. For that to work, require('BatchedBridge') has to be called, + // and when that happens, __fbBatchedBridge is set as a side effect. + auto global = Object::getGlobalObject(m_context); + auto batchedBridgeValue = global.getProperty("__fbBatchedBridge"); + // So here, if __fbBatchedBridge doesn't exist, then we know no native calls + // have happened, and we were able to determine this without forcing + // BatchedBridge to be loaded as a side effect. + if (!batchedBridgeValue.isUndefined()) { + // If calls were made, we bind to the JS bridge methods, and use them to + // get the pending queue of native calls. + bindBridge(); + callNativeModules(m_flushedQueueJS->callAsFunction({})); + } else if (m_delegate) { + // If we have a delegate, we need to call it; we pass a null list to + // callNativeModules, since we know there are no native calls, without + // calling into JS again. If no calls were made and there's no delegate, + // nothing happens, which is correct. + callNativeModules(Value::makeNull(m_context)); } } @@ -483,9 +503,9 @@ void JSCExecutor::callFunction(const std::string& moduleId, const std::string& m auto result = [&] { try { - // See flush() - CHECK(m_callFunctionReturnFlushedQueueJS) - << "Attempting to call native methods without loading BatchedBridge.js"; + if (!m_callFunctionReturnResultAndFlushedQueueJS) { + bindBridge(); + } return m_callFunctionReturnFlushedQueueJS->callAsFunction({ Value(m_context, String::createExpectingAscii(m_context, moduleId)), Value(m_context, String::createExpectingAscii(m_context, methodId)), @@ -504,6 +524,9 @@ void JSCExecutor::invokeCallback(const double callbackId, const folly::dynamic& SystraceSection s("JSCExecutor::invokeCallback"); auto result = [&] { try { + if (!m_invokeCallbackAndReturnFlushedQueueJS) { + bindBridge(); + } return m_invokeCallbackAndReturnFlushedQueueJS->callAsFunction({ Value::makeNumber(m_context, callbackId), Value::fromDynamic(m_context, std::move(arguments)) @@ -521,8 +544,9 @@ Value JSCExecutor::callFunctionSyncWithValue( const std::string& module, const std::string& method, Value args) { SystraceSection s("JSCExecutor::callFunction"); - // See flush() - CHECK(m_callFunctionReturnResultAndFlushedQueueJS); + if (!m_callFunctionReturnResultAndFlushedQueueJS) { + bindBridge(); + } Object result = m_callFunctionReturnResultAndFlushedQueueJS->callAsFunction({ Value(m_context, String::createExpectingAscii(m_context, module)), Value(m_context, String::createExpectingAscii(m_context, method)), diff --git a/ReactCommon/cxxreact/JSCExecutor.h b/ReactCommon/cxxreact/JSCExecutor.h index 3fbb03504..c3a1449e0 100644 --- a/ReactCommon/cxxreact/JSCExecutor.h +++ b/ReactCommon/cxxreact/JSCExecutor.h @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -112,6 +113,7 @@ private: std::unique_ptr m_unbundle; JSCNativeModules m_nativeModules; folly::dynamic m_jscConfig; + std::once_flag m_bindFlag; folly::Optional m_invokeCallbackAndReturnFlushedQueueJS; folly::Optional m_callFunctionReturnFlushedQueueJS; diff --git a/ReactCommon/jschelpers/Value.h b/ReactCommon/jschelpers/Value.h index b5d79c53d..ca8b86bf6 100644 --- a/ReactCommon/jschelpers/Value.h +++ b/ReactCommon/jschelpers/Value.h @@ -237,6 +237,13 @@ public: Value(JSContextRef context, JSStringRef value); Value(Value&&); + Value& operator=(Value&& other) { + m_context = other.m_context; + m_value = other.m_value; + other.m_value = NULL; + return *this; + }; + operator JSValueRef() const { return m_value; }