/* * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include "NativeToJsBridge.h" #include #include #include #include #include "Instance.h" #include "JSBigString.h" #include "MessageQueueThread.h" #include "MethodCall.h" #include "ModuleRegistry.h" #include "RAMBundleRegistry.h" #include "SystraceSection.h" #include #ifdef WITH_FBSYSTRACE #include using fbsystrace::FbSystraceAsyncFlow; #endif namespace facebook { namespace react { // This class manages calls from JS to native code. class JsToNativeBridge : public react::ExecutorDelegate { public: JsToNativeBridge( std::shared_ptr registry, std::shared_ptr callback) : m_registry(registry), m_callback(callback) {} std::shared_ptr getModuleRegistry() override { return m_registry; } bool isBatchActive() { return m_batchHadNativeModuleOrTurboModuleCalls; } void callNativeModules( __unused JSExecutor &executor, folly::dynamic &&calls, bool isEndOfBatch) override { CHECK(m_registry || calls.empty()) << "native module calls cannot be completed with no native modules"; m_batchHadNativeModuleOrTurboModuleCalls = m_batchHadNativeModuleOrTurboModuleCalls || !calls.empty(); // An exception anywhere in here stops processing of the batch. This // was the behavior of the Android bridge, and since exception handling // terminates the whole bridge, there's not much point in continuing. for (auto &call : parseMethodCalls(std::move(calls))) { m_registry->callNativeMethod( call.moduleId, call.methodId, std::move(call.arguments), call.callId); } if (isEndOfBatch) { // onBatchComplete will be called on the native (module) queue, but // decrementPendingJSCalls will be called sync. Be aware that the bridge // may still be processing native calls when the bridge idle signaler // fires. if (m_batchHadNativeModuleOrTurboModuleCalls) { m_callback->onBatchComplete(); m_batchHadNativeModuleOrTurboModuleCalls = false; } m_callback->decrementPendingJSCalls(); } } MethodCallResult callSerializableNativeHook( __unused JSExecutor &executor, unsigned int moduleId, unsigned int methodId, folly::dynamic &&args) override { return m_registry->callSerializableNativeHook( moduleId, methodId, std::move(args)); } void recordTurboModuleAsyncMethodCall() { m_batchHadNativeModuleOrTurboModuleCalls = true; } private: // These methods are always invoked from an Executor. The NativeToJsBridge // keeps a reference to the executor, and when destroy() is called, the // executor is destroyed synchronously on its queue. std::shared_ptr m_registry; std::shared_ptr m_callback; bool m_batchHadNativeModuleOrTurboModuleCalls = false; }; NativeToJsBridge::NativeToJsBridge( JSExecutorFactory *jsExecutorFactory, std::shared_ptr registry, std::shared_ptr jsQueue, std::shared_ptr callback) : m_destroyed(std::make_shared(false)), m_delegate(std::make_shared(registry, callback)), m_executor(jsExecutorFactory->createJSExecutor(m_delegate, jsQueue)), m_executorMessageQueueThread(std::move(jsQueue)), m_inspectable(m_executor->isInspectable()) {} // This must be called on the same thread on which the constructor was called. NativeToJsBridge::~NativeToJsBridge() { CHECK(*m_destroyed) << "NativeToJsBridge::destroy() must be called before deallocating the NativeToJsBridge!"; } void NativeToJsBridge::initializeRuntime() { runOnExecutorQueue( [](JSExecutor *executor) mutable { executor->initializeRuntime(); }); } void NativeToJsBridge::loadBundle( std::unique_ptr bundleRegistry, std::unique_ptr startupScript, std::string startupScriptSourceURL) { runOnExecutorQueue( [this, bundleRegistryWrap = folly::makeMoveWrapper(std::move(bundleRegistry)), startupScript = folly::makeMoveWrapper(std::move(startupScript)), startupScriptSourceURL = std::move(startupScriptSourceURL)](JSExecutor *executor) mutable { auto bundleRegistry = bundleRegistryWrap.move(); if (bundleRegistry) { executor->setBundleRegistry(std::move(bundleRegistry)); } try { executor->loadBundle( std::move(*startupScript), std::move(startupScriptSourceURL)); } catch (...) { m_applicationScriptHasFailure = true; throw; } }); } void NativeToJsBridge::loadBundleSync( std::unique_ptr bundleRegistry, std::unique_ptr startupScript, std::string startupScriptSourceURL) { if (bundleRegistry) { m_executor->setBundleRegistry(std::move(bundleRegistry)); } try { m_executor->loadBundle( std::move(startupScript), std::move(startupScriptSourceURL)); } catch (...) { m_applicationScriptHasFailure = true; throw; } } void NativeToJsBridge::callFunction( std::string &&module, std::string &&method, folly::dynamic &&arguments) { int systraceCookie = -1; #ifdef WITH_FBSYSTRACE systraceCookie = m_systraceCookie++; FbSystraceAsyncFlow::begin( TRACE_TAG_REACT_CXX_BRIDGE, "JSCall", systraceCookie); #endif runOnExecutorQueue([this, module = std::move(module), method = std::move(method), arguments = std::move(arguments), systraceCookie](JSExecutor *executor) { if (m_applicationScriptHasFailure) { LOG(ERROR) << "Attempting to call JS function on a bad application bundle: " << module.c_str() << "." << method.c_str() << "()"; throw std::runtime_error( "Attempting to call JS function on a bad application bundle: " + module + "." + method + "()"); } #ifdef WITH_FBSYSTRACE FbSystraceAsyncFlow::end( TRACE_TAG_REACT_CXX_BRIDGE, "JSCall", systraceCookie); SystraceSection s( "NativeToJsBridge::callFunction", "module", module, "method", method); #else (void)(systraceCookie); #endif // This is safe because we are running on the executor's thread: it won't // destruct until after it's been unregistered (which we check above) and // that will happen on this thread executor->callFunction(module, method, arguments); }); } void NativeToJsBridge::invokeCallback( double callbackId, folly::dynamic &&arguments) { int systraceCookie = -1; #ifdef WITH_FBSYSTRACE systraceCookie = m_systraceCookie++; FbSystraceAsyncFlow::begin( TRACE_TAG_REACT_CXX_BRIDGE, "", systraceCookie); #endif runOnExecutorQueue( [this, callbackId, arguments = std::move(arguments), systraceCookie]( JSExecutor *executor) { if (m_applicationScriptHasFailure) { LOG(ERROR) << "Attempting to call JS callback on a bad application bundle: " << callbackId; throw std::runtime_error( "Attempting to invoke JS callback on a bad application bundle."); } #ifdef WITH_FBSYSTRACE FbSystraceAsyncFlow::end( TRACE_TAG_REACT_CXX_BRIDGE, "", systraceCookie); SystraceSection s("NativeToJsBridge::invokeCallback"); #else (void)(systraceCookie); #endif executor->invokeCallback(callbackId, arguments); }); } void NativeToJsBridge::registerBundle( uint32_t bundleId, const std::string &bundlePath) { runOnExecutorQueue([bundleId, bundlePath](JSExecutor *executor) { executor->registerBundle(bundleId, bundlePath); }); } void NativeToJsBridge::setGlobalVariable( std::string propName, std::unique_ptr jsonValue) { runOnExecutorQueue([propName = std::move(propName), jsonValue = folly::makeMoveWrapper(std::move(jsonValue))]( JSExecutor *executor) mutable { executor->setGlobalVariable(propName, jsonValue.move()); }); } void *NativeToJsBridge::getJavaScriptContext() { // TODO(cjhopman): this seems unsafe unless we require that it is only called // on the main js queue. return m_executor->getJavaScriptContext(); } bool NativeToJsBridge::isInspectable() { return m_inspectable; } bool NativeToJsBridge::isBatchActive() { return m_delegate->isBatchActive(); } void NativeToJsBridge::handleMemoryPressure(int pressureLevel) { runOnExecutorQueue([=](JSExecutor *executor) { executor->handleMemoryPressure(pressureLevel); }); } void NativeToJsBridge::destroy() { // All calls made through runOnExecutorQueue have an early exit if // m_destroyed is true. Setting this before the runOnQueueSync will cause // pending work to be cancelled and we won't have to wait for it. *m_destroyed = true; m_executorMessageQueueThread->runOnQueueSync([this] { m_executor->destroy(); m_executorMessageQueueThread->quitSynchronous(); m_executor = nullptr; }); } void NativeToJsBridge::runOnExecutorQueue( std::function task) { if (*m_destroyed) { return; } std::shared_ptr isDestroyed = m_destroyed; m_executorMessageQueueThread->runOnQueue( [this, isDestroyed, task = std::move(task)] { if (*isDestroyed) { return; } // The executor is guaranteed to be valid for the duration of the task // because: // 1. the executor is only destroyed after it is unregistered // 2. the executor is unregistered on this queue // 3. we just confirmed that the executor hasn't been unregistered above task(m_executor.get()); }); } std::shared_ptr NativeToJsBridge::getDecoratedNativeCallInvoker( std::shared_ptr nativeInvoker) { class NativeCallInvoker : public CallInvoker { private: std::weak_ptr m_jsToNativeBridge; std::shared_ptr m_nativeInvoker; public: NativeCallInvoker( std::weak_ptr jsToNativeBridge, std::shared_ptr nativeInvoker) : m_jsToNativeBridge(jsToNativeBridge), m_nativeInvoker(nativeInvoker) {} void invokeAsync(std::function &&func) override { if (auto strongJsToNativeBridge = m_jsToNativeBridge.lock()) { strongJsToNativeBridge->recordTurboModuleAsyncMethodCall(); } m_nativeInvoker->invokeAsync(std::move(func)); } void invokeSync(std::function &&func) override { m_nativeInvoker->invokeSync(std::move(func)); } }; return std::make_shared(m_delegate, nativeInvoker); } } // namespace react } // namespace facebook