Files
react-native/packages/react-native/ReactCommon/cxxreact/NativeToJsBridge.h
T
Ramanpreet Nara b70f186b53 Migrate from native CallInvoker to NativeMethodCallInvoker (#37473)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/37473

## Context
The TurboModule system uses a CallInvoker interface to schedule JavaScript → native, and native → JavaScript calls.

## Problem
JavaScript → native and native → JavaScript calls are different. They can have different behaviours/properties. And, in the future, we might want to evolve them differently. e.g:
- JavaScript → native **always** have a method name. Native → JavaScript calls don't.
- Native → JavaScript can have priorities, since D41492849. JavaScript → native don't.

## Changes
Instead of tying both types of calls to the CallInvoker abstraction, this diff creates a **separate** abstraction for Native → JavaScript calls: NativeMethodCallInvoker.

This way, we can evolve both abstractions separately over time:
- We can evolve the CallInvoker abstraction to suit the needs of JavaScript → native calls.
- We can evolve the NativeMethodCallInvoker abstraction to suite the needs of native → JavaScript calls.

This ultimately makes TurboModule system more extensible.

## Motivation
For the TurboModule interop layer on iOS, React Native needs to execute the "constantsToExport" method on the main queue, when the module requires main queue setup. (implementation: D45924977).

The simplest way to implement this behaviour is to introduce a `methodName` to CallInvoker, and customize the legacy module's CallInvoker::invokeSync method, like so:

```
  void invokeSync(std::string methodName, std::function<void()> &&work) override
  {
    if (requiresMainQueueSetup_ && methodName == "getConstants") {
      __block auto retainedWork = std::move(work);
      RCTUnsafeExecuteOnMainQueueSync(^{
        retainedWork();
      });
      return;
    }

    work();
  }
```

But, customizing CallInvoker to introduce a `methodName` parameter doesn't make sense: Native → JavaScript calls don't necessarily have method names. So, this diff forks CallInvoker into NativeMethodCallInvoker. That way, we can customize NativeMethodCallInvoker to introduce a method name (which does make sense) and resolve this problem. For the full solution, see D45924977.

NOTE: Now that NativeMethodCallInvoker is different from CallInvoker, it might make sense to re-name CallInvoker back to JSCallInvoker.

Changelog:
[Category][Breaking] - Introduce NativeMethodCallInvoker to replace the TurboModule system's native CallInvoker

Reviewed By: javache

Differential Revision: D45891627

fbshipit-source-id: 39c3f450a290ad396b715288a50858d33ce78441
2023-05-22 17:32:01 -07:00

135 lines
4.0 KiB
C++

/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <atomic>
#include <functional>
#include <map>
#include <vector>
#include <ReactCommon/CallInvoker.h>
#include <ReactCommon/RuntimeExecutor.h>
#include <cxxreact/JSExecutor.h>
namespace folly {
struct dynamic;
}
namespace facebook::react {
struct InstanceCallback;
class JsToNativeBridge;
class MessageQueueThread;
class ModuleRegistry;
class RAMBundleRegistry;
// This class manages calls from native code to JS. It also manages
// executors and their threads. All functions here can be called from
// any thread.
//
// Except for loadBundleSync(), all void methods will queue
// work to run on the jsQueue passed to the ctor, and return
// immediately.
class NativeToJsBridge {
public:
friend class JsToNativeBridge;
/**
* This must be called on the main JS thread.
*/
NativeToJsBridge(
JSExecutorFactory *jsExecutorFactory,
std::shared_ptr<ModuleRegistry> registry,
std::shared_ptr<MessageQueueThread> jsQueue,
std::shared_ptr<InstanceCallback> callback);
virtual ~NativeToJsBridge();
/**
* Executes a function with the module ID and method ID and any additional
* arguments in JS.
*/
void callFunction(
std::string &&module,
std::string &&method,
folly::dynamic &&args);
/**
* Invokes a callback with the cbID, and optional additional arguments in JS.
*/
void invokeCallback(double callbackId, folly::dynamic &&args);
/**
* Sets global variables in the JS Context.
*/
void initializeRuntime();
/**
* Starts the JS application. If bundleRegistry is non-null, then it is
* used to fetch JavaScript modules as individual scripts.
* Otherwise, the script is assumed to include all the modules.
*/
void loadBundle(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupCode,
std::string sourceURL);
void loadBundleSync(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupCode,
std::string sourceURL);
void registerBundle(uint32_t bundleId, const std::string &bundlePath);
void setGlobalVariable(
std::string propName,
std::unique_ptr<const JSBigString> jsonValue);
void *getJavaScriptContext();
bool isInspectable();
bool isBatchActive();
void handleMemoryPressure(int pressureLevel);
/**
* Synchronously tears down the bridge and the main executor.
*/
void destroy();
void runOnExecutorQueue(std::function<void(JSExecutor *)> task);
/**
* NativeMethodCallInvoker is used by TurboModules to schedule work on the
* NativeModule thread(s).
*/
std::shared_ptr<NativeMethodCallInvoker> getDecoratedNativeMethodCallInvoker(
std::shared_ptr<NativeMethodCallInvoker> nativeInvoker) const;
private:
// This is used to avoid a race condition where a proxyCallback gets queued
// after ~NativeToJsBridge(), on the same thread. In that case, the callback
// will try to run the task on m_callback which will have been destroyed
// within ~NativeToJsBridge(), thus causing a SIGSEGV.
std::shared_ptr<bool> m_destroyed;
std::shared_ptr<JsToNativeBridge> m_delegate;
std::unique_ptr<JSExecutor> m_executor;
std::shared_ptr<MessageQueueThread> m_executorMessageQueueThread;
// Memoize this on the JS thread, so that it can be inspected from
// any thread later. This assumes inspectability doesn't change for
// a JSExecutor instance, which is true for all existing implementations.
bool m_inspectable;
// Keep track of whether the JS bundle containing the application logic causes
// exception when evaluated initially. If so, more calls to JS will very
// likely fail as well, so this flag can help prevent them.
bool m_applicationScriptHasFailure = false;
#ifdef WITH_FBSYSTRACE
std::atomic_uint_least32_t m_systraceCookie = ATOMIC_VAR_INIT(0);
#endif
};
} // namespace facebook::react