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 "ABI49_0_0NativeToJsBridge.h"
9
10 #include <ABI49_0_0ReactCommon/ABI49_0_0CallInvoker.h>
11 #include <folly/MoveWrapper.h>
12 #include <folly/json.h>
13 #include <glog/logging.h>
14 #include <ABI49_0_0jsi/ABI49_0_0jsi.h>
15 #include <ABI49_0_0Reactperflogger/ABI49_0_0BridgeNativeModulePerfLogger.h>
16
17 #include "ABI49_0_0ErrorUtils.h"
18 #include "ABI49_0_0Instance.h"
19 #include "ABI49_0_0JSBigString.h"
20 #include "ABI49_0_0MessageQueueThread.h"
21 #include "ABI49_0_0MethodCall.h"
22 #include "ABI49_0_0ModuleRegistry.h"
23 #include "ABI49_0_0RAMBundleRegistry.h"
24 #include "ABI49_0_0SystraceSection.h"
25
26 #include <memory>
27
28 #ifdef WITH_FBSYSTRACE
29 #include <fbsystrace.h>
30 #include <ABI49_0_0jsi/ABI49_0_0jsi/jsi.h>
31
32 using fbsystrace::FbSystraceAsyncFlow;
33 #endif
34
35 namespace ABI49_0_0facebook {
36 namespace ABI49_0_0React {
37
38 // This class manages calls from JS to native code.
39 class JsToNativeBridge : public ABI49_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 ABI49_0_0React
344 } // namespace ABI49_0_0facebook
345