1 /*
2  * Copyright (c) Meta Platforms, Inc. and affiliates.
3  *
4  * This source code is licensed under the MIT license found in the
5  * LICENSE file in the root directory of this source tree.
6  */
7 
8 #include "ABI48_0_0NativeToJsBridge.h"
9 
10 #include <ABI48_0_0ReactCommon/ABI48_0_0CallInvoker.h>
11 #include <folly/MoveWrapper.h>
12 #include <folly/json.h>
13 #include <glog/logging.h>
14 #include <ABI48_0_0jsi/ABI48_0_0jsi.h>
15 #include <ABI48_0_0Reactperflogger/ABI48_0_0BridgeNativeModulePerfLogger.h>
16 
17 #include "ABI48_0_0ErrorUtils.h"
18 #include "ABI48_0_0Instance.h"
19 #include "ABI48_0_0JSBigString.h"
20 #include "ABI48_0_0MessageQueueThread.h"
21 #include "ABI48_0_0MethodCall.h"
22 #include "ABI48_0_0ModuleRegistry.h"
23 #include "ABI48_0_0RAMBundleRegistry.h"
24 #include "ABI48_0_0SystraceSection.h"
25 
26 #include <memory>
27 
28 #ifdef WITH_FBSYSTRACE
29 #include <fbsystrace.h>
30 #include <ABI48_0_0jsi/ABI48_0_0jsi/jsi.h>
31 
32 using fbsystrace::FbSystraceAsyncFlow;
33 #endif
34 
35 namespace ABI48_0_0facebook {
36 namespace ABI48_0_0React {
37 
38 // This class manages calls from JS to native code.
39 class JsToNativeBridge : public ABI48_0_0React::ExecutorDelegate {
40  public:
JsToNativeBridge(std::shared_ptr<ModuleRegistry> registry,std::shared_ptr<InstanceCallback> callback)41   JsToNativeBridge(
42       std::shared_ptr<ModuleRegistry> registry,
43       std::shared_ptr<InstanceCallback> callback)
44       : m_registry(registry), m_callback(callback) {}
45 
getModuleRegistry()46   std::shared_ptr<ModuleRegistry> getModuleRegistry() override {
47     return m_registry;
48   }
49 
isBatchActive()50   bool isBatchActive() {
51     return m_batchHadNativeModuleOrTurboModuleCalls;
52   }
53 
callNativeModules(JSExecutor & executor,folly::dynamic && calls,bool isEndOfBatch)54   void callNativeModules(
55       [[maybe_unused]] JSExecutor &executor,
56       folly::dynamic &&calls,
57       bool isEndOfBatch) override {
58     CHECK(m_registry || calls.empty())
59         << "native module calls cannot be completed with no native modules";
60     m_batchHadNativeModuleOrTurboModuleCalls =
61         m_batchHadNativeModuleOrTurboModuleCalls || !calls.empty();
62 
63     std::vector<MethodCall> methodCalls = parseMethodCalls(std::move(calls));
64     BridgeNativeModulePerfLogger::asyncMethodCallBatchPreprocessEnd(
65         (int)methodCalls.size());
66 
67     // An exception anywhere in here stops processing of the batch.  This
68     // was the behavior of the Android bridge, and since exception handling
69     // terminates the whole bridge, there's not much point in continuing.
70     for (auto &call : methodCalls) {
71       m_registry->callNativeMethod(
72           call.moduleId, call.methodId, std::move(call.arguments), call.callId);
73     }
74     if (isEndOfBatch) {
75       // onBatchComplete will be called on the native (module) queue, but
76       // decrementPendingJSCalls will be called sync. Be aware that the bridge
77       // may still be processing native calls when the bridge idle signaler
78       // fires.
79       if (m_batchHadNativeModuleOrTurboModuleCalls) {
80         m_callback->onBatchComplete();
81         m_batchHadNativeModuleOrTurboModuleCalls = false;
82       }
83       m_callback->decrementPendingJSCalls();
84     }
85   }
86 
callSerializableNativeHook(JSExecutor & executor,unsigned int moduleId,unsigned int methodId,folly::dynamic && args)87   MethodCallResult callSerializableNativeHook(
88       [[maybe_unused]] JSExecutor &executor,
89       unsigned int moduleId,
90       unsigned int methodId,
91       folly::dynamic &&args) override {
92     return m_registry->callSerializableNativeHook(
93         moduleId, methodId, std::move(args));
94   }
95 
recordTurboModuleAsyncMethodCall()96   void recordTurboModuleAsyncMethodCall() {
97     m_batchHadNativeModuleOrTurboModuleCalls = true;
98   }
99 
100  private:
101   // These methods are always invoked from an Executor.  The NativeToJsBridge
102   // keeps a reference to the executor, and when destroy() is called, the
103   // executor is destroyed synchronously on its queue.
104   std::shared_ptr<ModuleRegistry> m_registry;
105   std::shared_ptr<InstanceCallback> m_callback;
106   bool m_batchHadNativeModuleOrTurboModuleCalls = false;
107 };
108 
NativeToJsBridge(JSExecutorFactory * jsExecutorFactory,std::shared_ptr<ModuleRegistry> registry,std::shared_ptr<MessageQueueThread> jsQueue,std::shared_ptr<InstanceCallback> callback)109 NativeToJsBridge::NativeToJsBridge(
110     JSExecutorFactory *jsExecutorFactory,
111     std::shared_ptr<ModuleRegistry> registry,
112     std::shared_ptr<MessageQueueThread> jsQueue,
113     std::shared_ptr<InstanceCallback> callback)
114     : m_destroyed(std::make_shared<bool>(false)),
115       m_delegate(std::make_shared<JsToNativeBridge>(registry, callback)),
116       m_executor(jsExecutorFactory->createJSExecutor(m_delegate, jsQueue)),
117       m_executorMessageQueueThread(std::move(jsQueue)),
118       m_inspectable(m_executor->isInspectable()) {}
119 
120 // This must be called on the same thread on which the constructor was called.
~NativeToJsBridge()121 NativeToJsBridge::~NativeToJsBridge() {
122   CHECK(*m_destroyed)
123       << "NativeToJsBridge::destroy() must be called before deallocating the NativeToJsBridge!";
124 }
125 
initializeRuntime()126 void NativeToJsBridge::initializeRuntime() {
127   runOnExecutorQueue(
128       [](JSExecutor *executor) mutable { executor->initializeRuntime(); });
129 }
130 
loadBundle(std::unique_ptr<RAMBundleRegistry> bundleRegistry,std::unique_ptr<const JSBigString> startupScript,std::string startupScriptSourceURL)131 void NativeToJsBridge::loadBundle(
132     std::unique_ptr<RAMBundleRegistry> bundleRegistry,
133     std::unique_ptr<const JSBigString> startupScript,
134     std::string startupScriptSourceURL) {
135   runOnExecutorQueue(
136       [this,
137        bundleRegistryWrap = folly::makeMoveWrapper(std::move(bundleRegistry)),
138        startupScript = folly::makeMoveWrapper(std::move(startupScript)),
139        startupScriptSourceURL =
140            std::move(startupScriptSourceURL)](JSExecutor *executor) mutable {
141         auto bundleRegistry = bundleRegistryWrap.move();
142         if (bundleRegistry) {
143           executor->setBundleRegistry(std::move(bundleRegistry));
144         }
145         try {
146           executor->loadBundle(
147               std::move(*startupScript), std::move(startupScriptSourceURL));
148         } catch (...) {
149           m_applicationScriptHasFailure = true;
150           throw;
151         }
152       });
153 }
154 
loadBundleSync(std::unique_ptr<RAMBundleRegistry> bundleRegistry,std::unique_ptr<const JSBigString> startupScript,std::string startupScriptSourceURL)155 void NativeToJsBridge::loadBundleSync(
156     std::unique_ptr<RAMBundleRegistry> bundleRegistry,
157     std::unique_ptr<const JSBigString> startupScript,
158     std::string startupScriptSourceURL) {
159   if (bundleRegistry) {
160     m_executor->setBundleRegistry(std::move(bundleRegistry));
161   }
162   try {
163     m_executor->loadBundle(
164         std::move(startupScript), std::move(startupScriptSourceURL));
165   } catch (...) {
166     m_applicationScriptHasFailure = true;
167     throw;
168   }
169 }
170 
callFunction(std::string && module,std::string && method,folly::dynamic && arguments)171 void NativeToJsBridge::callFunction(
172     std::string &&module,
173     std::string &&method,
174     folly::dynamic &&arguments) {
175   int systraceCookie = -1;
176 #ifdef WITH_FBSYSTRACE
177   systraceCookie = m_systraceCookie++;
178   FbSystraceAsyncFlow::begin(
179       TRACE_TAG_REACT_CXX_BRIDGE, "JSCall", systraceCookie);
180 #endif
181 
182   runOnExecutorQueue([this,
183                       module = std::move(module),
184                       method = std::move(method),
185                       arguments = std::move(arguments),
186                       systraceCookie](JSExecutor *executor) {
187     if (m_applicationScriptHasFailure) {
188       LOG(ERROR)
189           << "Attempting to call JS function on a bad application bundle: "
190           << module.c_str() << "." << method.c_str() << "()";
191       throw std::runtime_error(
192           "Attempting to call JS function on a bad application bundle: " +
193           module + "." + method + "()");
194     }
195 
196 #ifdef WITH_FBSYSTRACE
197     FbSystraceAsyncFlow::end(
198         TRACE_TAG_REACT_CXX_BRIDGE, "JSCall", systraceCookie);
199     SystraceSection s(
200         "NativeToJsBridge::callFunction", "module", module, "method", method);
201 #else
202     (void)(systraceCookie);
203 #endif
204     // This is safe because we are running on the executor's thread: it won't
205     // destruct until after it's been unregistered (which we check above) and
206     // that will happen on this thread
207     executor->callFunction(module, method, arguments);
208   });
209 }
210 
invokeCallback(double callbackId,folly::dynamic && arguments)211 void NativeToJsBridge::invokeCallback(
212     double callbackId,
213     folly::dynamic &&arguments) {
214   int systraceCookie = -1;
215 #ifdef WITH_FBSYSTRACE
216   systraceCookie = m_systraceCookie++;
217   FbSystraceAsyncFlow::begin(
218       TRACE_TAG_REACT_CXX_BRIDGE, "<callback>", systraceCookie);
219 #endif
220 
221   runOnExecutorQueue(
222       [this, callbackId, arguments = std::move(arguments), systraceCookie](
223           JSExecutor *executor) {
224         if (m_applicationScriptHasFailure) {
225           LOG(ERROR)
226               << "Attempting to call JS callback on a bad application bundle: "
227               << callbackId;
228           throw std::runtime_error(
229               "Attempting to invoke JS callback on a bad application bundle.");
230         }
231 #ifdef WITH_FBSYSTRACE
232         FbSystraceAsyncFlow::end(
233             TRACE_TAG_REACT_CXX_BRIDGE, "<callback>", systraceCookie);
234         SystraceSection s("NativeToJsBridge::invokeCallback");
235 #else
236         (void)(systraceCookie);
237 #endif
238         executor->invokeCallback(callbackId, arguments);
239       });
240 }
241 
registerBundle(uint32_t bundleId,const std::string & bundlePath)242 void NativeToJsBridge::registerBundle(
243     uint32_t bundleId,
244     const std::string &bundlePath) {
245   runOnExecutorQueue([bundleId, bundlePath](JSExecutor *executor) {
246     executor->registerBundle(bundleId, bundlePath);
247   });
248 }
249 
setGlobalVariable(std::string propName,std::unique_ptr<const JSBigString> jsonValue)250 void NativeToJsBridge::setGlobalVariable(
251     std::string propName,
252     std::unique_ptr<const JSBigString> jsonValue) {
253   runOnExecutorQueue([propName = std::move(propName),
254                       jsonValue = folly::makeMoveWrapper(std::move(jsonValue))](
255                          JSExecutor *executor) mutable {
256     executor->setGlobalVariable(propName, jsonValue.move());
257   });
258 }
259 
getJavaScriptContext()260 void *NativeToJsBridge::getJavaScriptContext() {
261   // TODO(cjhopman): this seems unsafe unless we require that it is only called
262   // on the main js queue.
263   return m_executor->getJavaScriptContext();
264 }
265 
isInspectable()266 bool NativeToJsBridge::isInspectable() {
267   return m_inspectable;
268 }
269 
isBatchActive()270 bool NativeToJsBridge::isBatchActive() {
271   return m_delegate->isBatchActive();
272 }
273 
handleMemoryPressure(int pressureLevel)274 void NativeToJsBridge::handleMemoryPressure(int pressureLevel) {
275   runOnExecutorQueue([=](JSExecutor *executor) {
276     executor->handleMemoryPressure(pressureLevel);
277   });
278 }
279 
destroy()280 void NativeToJsBridge::destroy() {
281   // All calls made through runOnExecutorQueue have an early exit if
282   // m_destroyed is true. Setting this before the runOnQueueSync will cause
283   // pending work to be cancelled and we won't have to wait for it.
284   *m_destroyed = true;
285   m_executorMessageQueueThread->runOnQueueSync([this] {
286     m_executor->destroy();
287     m_executorMessageQueueThread->quitSynchronous();
288     m_executor = nullptr;
289   });
290 }
291 
runOnExecutorQueue(std::function<void (JSExecutor *)> task)292 void NativeToJsBridge::runOnExecutorQueue(
293     std::function<void(JSExecutor *)> task) {
294   if (*m_destroyed) {
295     return;
296   }
297 
298   std::shared_ptr<bool> isDestroyed = m_destroyed;
299   m_executorMessageQueueThread->runOnQueue(
300       [this, isDestroyed, task = std::move(task)] {
301         if (*isDestroyed) {
302           return;
303         }
304 
305         // The executor is guaranteed to be valid for the duration of the task
306         // because:
307         // 1. the executor is only destroyed after it is unregistered
308         // 2. the executor is unregistered on this queue
309         // 3. we just confirmed that the executor hasn't been unregistered above
310         task(m_executor.get());
311       });
312 }
313 
getDecoratedNativeCallInvoker(std::shared_ptr<CallInvoker> nativeInvoker)314 std::shared_ptr<CallInvoker> NativeToJsBridge::getDecoratedNativeCallInvoker(
315     std::shared_ptr<CallInvoker> nativeInvoker) {
316   class NativeCallInvoker : public CallInvoker {
317    private:
318     std::weak_ptr<JsToNativeBridge> m_jsToNativeBridge;
319     std::shared_ptr<CallInvoker> m_nativeInvoker;
320 
321    public:
322     NativeCallInvoker(
323         std::weak_ptr<JsToNativeBridge> jsToNativeBridge,
324         std::shared_ptr<CallInvoker> nativeInvoker)
325         : m_jsToNativeBridge(jsToNativeBridge),
326           m_nativeInvoker(nativeInvoker) {}
327 
328     void invokeAsync(std::function<void()> &&func) override {
329       if (auto strongJsToNativeBridge = m_jsToNativeBridge.lock()) {
330         strongJsToNativeBridge->recordTurboModuleAsyncMethodCall();
331       }
332       m_nativeInvoker->invokeAsync(std::move(func));
333     }
334 
335     void invokeSync(std::function<void()> &&func) override {
336       m_nativeInvoker->invokeSync(std::move(func));
337     }
338   };
339 
340   return std::make_shared<NativeCallInvoker>(m_delegate, nativeInvoker);
341 }
342 
343 } // namespace ABI48_0_0React
344 } // namespace ABI48_0_0facebook
345