first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
/*
* 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 <react/runtime/ReactInstance.h>
namespace facebook::react {
class BindingsInstaller {
public:
virtual ReactInstance::BindingsInstallFunc getBindingsInstallFunc()
{
return nullptr;
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,28 @@
/*
* 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.
*/
#include "BridgelessNativeMethodCallInvoker.h"
namespace facebook::react {
BridgelessNativeMethodCallInvoker::BridgelessNativeMethodCallInvoker(
std::shared_ptr<MessageQueueThread> messageQueueThread)
: messageQueueThread_(std::move(messageQueueThread)) {}
void BridgelessNativeMethodCallInvoker::invokeAsync(
const std::string& /*methodName*/,
NativeMethodCallFunc&& func) noexcept {
messageQueueThread_->runOnQueue(std::move(func));
}
void BridgelessNativeMethodCallInvoker::invokeSync(
const std::string& /*methodName*/,
NativeMethodCallFunc&& func) {
messageQueueThread_->runOnQueueSync(std::move(func));
}
} // namespace facebook::react

View File

@@ -0,0 +1,26 @@
/*
* 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 <ReactCommon/CallInvoker.h>
#include <cxxreact/MessageQueueThread.h>
#include <memory>
namespace facebook::react {
class BridgelessNativeMethodCallInvoker : public NativeMethodCallInvoker {
public:
explicit BridgelessNativeMethodCallInvoker(std::shared_ptr<MessageQueueThread> messageQueueThread);
void invokeAsync(const std::string &methodName, NativeMethodCallFunc &&func) noexcept override;
void invokeSync(const std::string &methodName, NativeMethodCallFunc &&func) override;
private:
std::shared_ptr<MessageQueueThread> messageQueueThread_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,58 @@
/*
* 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.
*/
#include "BufferedRuntimeExecutor.h"
namespace facebook::react {
BufferedRuntimeExecutor::BufferedRuntimeExecutor(
RuntimeExecutor runtimeExecutor)
: runtimeExecutor_(std::move(runtimeExecutor)),
isBufferingEnabled_(true),
lastIndex_(0) {}
void BufferedRuntimeExecutor::execute(Work&& callback) {
if (!isBufferingEnabled_) {
// Fast path: Schedule directly to RuntimeExecutor, without locking
runtimeExecutor_(std::move(callback));
return;
}
/**
* Note: std::mutex doesn't have a FIFO ordering.
* To preserve the order of the buffered work, use a priority queue and
* track the last known work index.
*/
uint64_t newIndex = lastIndex_++;
std::scoped_lock guard(lock_);
if (isBufferingEnabled_) {
queue_.push({.index_ = newIndex, .work_ = std::move(callback)});
return;
}
// Force flush the queue to maintain the execution order.
unsafeFlush();
runtimeExecutor_(std::move(callback));
}
void BufferedRuntimeExecutor::flush() {
std::scoped_lock guard(lock_);
unsafeFlush();
isBufferingEnabled_ = false;
}
void BufferedRuntimeExecutor::unsafeFlush() {
while (!queue_.empty()) {
const BufferedWork& bufferedWork = queue_.top();
Work work = bufferedWork.work_;
runtimeExecutor_(std::move(work));
queue_.pop();
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,52 @@
/*
* 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 <ReactCommon/RuntimeExecutor.h>
#include <jsi/jsi.h>
#include <atomic>
#include <mutex>
#include <queue>
namespace facebook::react {
class BufferedRuntimeExecutor {
public:
using Work = std::function<void(jsi::Runtime &runtime)>;
// A utility structure to track pending work in the order of when they arrive.
struct BufferedWork {
uint64_t index_;
Work work_;
bool operator<(const BufferedWork &rhs) const
{
// Higher index has lower priority, so this inverted comparison puts
// the smaller index on top of the queue.
return index_ > rhs.index_;
}
};
BufferedRuntimeExecutor(RuntimeExecutor runtimeExecutor);
void execute(Work &&callback);
// Flush buffered JS calls and then diable JS buffering
void flush();
private:
// Perform flushing without locking mechanism
void unsafeFlush();
RuntimeExecutor runtimeExecutor_;
std::atomic<bool> isBufferingEnabled_;
std::mutex lock_;
std::atomic<uint64_t> lastIndex_;
std::priority_queue<BufferedWork> queue_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,46 @@
# 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/internal/react-native-platform-selector.cmake)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB bridgeless_SRC "*.cpp")
add_library(bridgeless
OBJECT
${bridgeless_SRC}
)
target_compile_reactnative_options(bridgeless PRIVATE)
if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED)
target_compile_options(bridgeless PRIVATE -DHERMES_ENABLE_DEBUGGER=1)
if (HERMES_V1_ENABLED)
target_compile_options(bridgeless PRIVATE -DHERMES_V1_ENABLED=1)
endif()
endif ()
target_include_directories(bridgeless PUBLIC .)
react_native_android_selector(fabricjni fabricjni "")
react_native_android_selector(react_featureflagsjni react_featureflagsjni react_featureflags)
react_native_android_selector(turbomodulejsijni turbomodulejsijni "")
target_link_libraries(
bridgeless
jserrorhandler
${fabricjni}
${react_featureflagsjni}
${turbomodulejsijni}
jsi
jsitooling
jsireact
react_utils
jsinspector
react_featureflags
react_performance_timeline
react_utils
)

View File

@@ -0,0 +1,32 @@
/*
* 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 <cstdint>
namespace facebook::react {
/**
* This interface is implemented by each platform.
* Responsibility: Call into some platform API to register/schedule, or delete
* registered/scheduled timers.
*/
class PlatformTimerRegistry {
public:
virtual void createTimer(uint32_t timerID, double delayMS) = 0;
virtual void deleteTimer(uint32_t timerID) = 0;
virtual void createRecurringTimer(uint32_t timerID, double delayMS) = 0;
virtual ~PlatformTimerRegistry() noexcept = default;
};
using TimerManagerDelegate = PlatformTimerRegistry;
} // namespace facebook::react

View File

@@ -0,0 +1,3 @@
# Bridgeless Mode for iOS
This library is not ready for integration for production nor local experimentation. Expect breaking changes regularly if you use any of these APIs. Use at your own risk!

View File

@@ -0,0 +1,56 @@
# 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.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "../../..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
Pod::Spec.new do |s|
s.name = "React-RuntimeCore"
s.version = version
s.summary = "The React Native Runtime."
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = podspec_sources(["*.{cpp,h}", "nativeviewconfig/*.{cpp,h}"], ["*.h", "nativeviewconfig/*.h"])
s.exclude_files = "iostests/*", "tests/**/*.{cpp,h}"
s.header_dir = "react/runtime"
s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/Headers/Private/React-Core\" \"${PODS_TARGET_SRCROOT}/../..\"",
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"GCC_WARN_PEDANTIC" => "YES" }
resolve_use_frameworks(s, header_mappings_dir: "../..", module_name: "React_RuntimeCore")
s.dependency "React-jsiexecutor"
s.dependency "React-cxxreact"
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
s.dependency "React-jsi"
s.dependency "React-jserrorhandler"
s.dependency "React-performancetimeline"
s.dependency "React-runtimescheduler"
s.dependency "React-utils"
s.dependency "React-featureflags"
add_dependency(s, "React-Fabric")
depend_on_js_engine(s)
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
s.dependency "React-jsinspector"
add_dependency(s, "React-jsitooling", :framework_name => "JSITooling")
end

View File

@@ -0,0 +1,53 @@
# 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.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "../../..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
Pod::Spec.new do |s|
s.name = "React-RuntimeHermes"
s.version = version
s.summary = "The React Native Runtime."
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = podspec_sources("hermes/*.{cpp,h}", "hermes/*.h")
s.header_dir = "react/runtime/hermes"
s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"${PODS_TARGET_SRCROOT}/../..\" \"${PODS_TARGET_SRCROOT}/../../hermes/executor\"",
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"GCC_WARN_PEDANTIC" => "YES" }
resolve_use_frameworks(s, header_mappings_dir: "../../", module_name: "React_RuntimeHermes")
s.dependency "React-jsitracing"
s.dependency "React-jsi"
s.dependency "React-utils"
s.dependency "React-RuntimeCore"
s.dependency "React-featureflags"
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
add_dependency(s, "React-jsinspectortracing", :framework_name => 'jsinspector_moderntracing')
s.dependency "React-hermes"
s.dependency "hermes-engine"
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
add_dependency(s, "React-jsitooling", :framework_name => "JSITooling")
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
end

View File

@@ -0,0 +1,693 @@
/*
* 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.
*/
#include "ReactInstance.h"
#include <ReactCommon/RuntimeExecutor.h>
#include <cxxreact/ErrorUtils.h>
#include <cxxreact/JSBigString.h>
#include <cxxreact/JSExecutor.h>
#include <cxxreact/ReactMarker.h>
#include <cxxreact/TraceSection.h>
#include <glog/logging.h>
#include <jsi/JSIDynamic.h>
#include <jsi/hermes.h>
#include <jsi/instrumentation.h>
#include <jsinspector-modern/HostTarget.h>
#include <jsireact/JSIExecutor.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/core/ShadowNode.h>
#include <react/renderer/runtimescheduler/RuntimeSchedulerBinding.h>
#include <react/timing/primitives.h>
#include <react/utils/jsi-utils.h>
#include <iostream>
#include <memory>
#include <utility>
namespace facebook::react {
namespace {
std::shared_ptr<RuntimeScheduler> createRuntimeScheduler(
RuntimeExecutor runtimeExecutor,
RuntimeSchedulerTaskErrorHandler taskErrorHandler) {
std::shared_ptr<RuntimeScheduler> scheduler =
std::make_shared<RuntimeScheduler>(
std::move(runtimeExecutor),
HighResTimeStamp::now,
std::move(taskErrorHandler));
scheduler->setPerformanceEntryReporter(
// FIXME: Move creation of PerformanceEntryReporter to here and
// guarantee that its lifetime is the same as the runtime.
PerformanceEntryReporter::getInstance().get());
return scheduler;
}
} // namespace
ReactInstance::ReactInstance(
std::unique_ptr<JSRuntime> runtime,
std::shared_ptr<MessageQueueThread> jsMessageQueueThread,
std::shared_ptr<TimerManager> timerManager,
JsErrorHandler::OnJsError onJsError,
jsinspector_modern::HostTarget* parentInspectorTarget)
: runtime_(std::move(runtime)),
jsMessageQueueThread_(std::move(jsMessageQueueThread)),
timerManager_(std::move(timerManager)),
jsErrorHandler_(std::make_shared<JsErrorHandler>(std::move(onJsError))),
parentInspectorTarget_(parentInspectorTarget) {
RuntimeExecutor runtimeExecutor =
[weakRuntime = std::weak_ptr(runtime_),
weakTimerManager = std::weak_ptr(timerManager_),
weakJsThread = std::weak_ptr(jsMessageQueueThread_),
jsErrorHandler = jsErrorHandler_](auto callback) {
if (weakRuntime.expired()) {
return;
}
if (auto jsThread = weakJsThread.lock()) {
jsThread->runOnQueue([jsErrorHandler,
weakRuntime,
weakTimerManager,
callback = std::move(callback)]() {
auto runtime = weakRuntime.lock();
if (!runtime) {
return;
}
jsi::Runtime& jsiRuntime = runtime->getRuntime();
TraceSection s("ReactInstance::_runtimeExecutor[Callback]");
try {
ShadowNode::setUseRuntimeShadowNodeReferenceUpdateOnThread(true);
callback(jsiRuntime);
} catch (jsi::JSError& originalError) {
jsErrorHandler->handleError(jsiRuntime, originalError, true);
} catch (std::exception& ex) {
jsi::JSError error(
jsiRuntime, std::string("Non-js exception: ") + ex.what());
jsErrorHandler->handleError(jsiRuntime, error, true);
}
});
}
};
if (parentInspectorTarget_ != nullptr) {
auto executor = parentInspectorTarget_->executorFromThis();
auto bufferedRuntimeExecutorThatWaitsForInspectorSetup =
std::make_shared<BufferedRuntimeExecutor>(runtimeExecutor);
auto runtimeExecutorThatExecutesAfterInspectorSetup =
[bufferedRuntimeExecutorThatWaitsForInspectorSetup](
std::function<void(jsi::Runtime & runtime)>&& callback) {
bufferedRuntimeExecutorThatWaitsForInspectorSetup->execute(
std::move(callback));
};
runtimeScheduler_ = createRuntimeScheduler(
runtimeExecutorThatExecutesAfterInspectorSetup,
[jsErrorHandler = jsErrorHandler_](
jsi::Runtime& runtime, jsi::JSError& error) {
jsErrorHandler->handleError(runtime, error, true);
});
auto runtimeExecutorThatGoesThroughRuntimeScheduler =
[runtimeScheduler = runtimeScheduler_.get()](
std::function<void(jsi::Runtime & runtime)>&& callback) {
runtimeScheduler->scheduleWork(std::move(callback));
};
// This code can execute from any thread, so we need to make sure we set up
// the inspector logic in the right one. The callback executes immediately
// if we are already in the right thread.
executor([this,
runtimeExecutorThatGoesThroughRuntimeScheduler,
bufferedRuntimeExecutorThatWaitsForInspectorSetup](
jsinspector_modern::HostTarget& hostTarget) {
// Callbacks scheduled through the page target executor are generally
// not guaranteed to run (e.g.: if the page target is destroyed)
// but in this case it is because the page target cannot be destroyed
// before the instance finishes its setup:
// * On iOS it's because we do the setup synchronously.
// * On Android it's because we explicitly wait for the instance
// creation task to finish before starting the destruction.
inspectorTarget_ = &hostTarget.registerInstance(*this);
runtimeInspectorTarget_ = &inspectorTarget_->registerRuntime(
runtime_->getRuntimeTargetDelegate(),
runtimeExecutorThatGoesThroughRuntimeScheduler);
bufferedRuntimeExecutorThatWaitsForInspectorSetup->flush();
});
} else {
runtimeScheduler_ = createRuntimeScheduler(
runtimeExecutor,
[jsErrorHandler = jsErrorHandler_](
jsi::Runtime& runtime, jsi::JSError& error) {
jsErrorHandler->handleError(runtime, error, true);
});
}
bufferedRuntimeExecutor_ = std::make_shared<BufferedRuntimeExecutor>(
[runtimeScheduler = runtimeScheduler_.get()](
std::function<void(jsi::Runtime & runtime)>&& callback) {
runtimeScheduler->scheduleWork(std::move(callback));
});
}
void ReactInstance::unregisterFromInspector() {
if (inspectorTarget_ != nullptr) {
assert(runtimeInspectorTarget_);
inspectorTarget_->unregisterRuntime(*runtimeInspectorTarget_);
assert(parentInspectorTarget_);
parentInspectorTarget_->unregisterInstance(*inspectorTarget_);
inspectorTarget_ = nullptr;
}
}
RuntimeExecutor ReactInstance::getUnbufferedRuntimeExecutor() noexcept {
return [runtimeScheduler = runtimeScheduler_.get()](
std::function<void(jsi::Runtime & runtime)>&& callback) {
runtimeScheduler->scheduleWork(std::move(callback));
};
}
// This BufferedRuntimeExecutor ensures that the main JS bundle finished
// execution before any JS queued into it from C++ are executed. Use
// getUnbufferedRuntimeExecutor() instead if you do not need the main JS bundle
// to have finished. e.g. setting global variables into JS runtime.
RuntimeExecutor ReactInstance::getBufferedRuntimeExecutor() noexcept {
return [weakBufferedRuntimeExecutor_ =
std::weak_ptr<BufferedRuntimeExecutor>(bufferedRuntimeExecutor_)](
std::function<void(jsi::Runtime & runtime)>&& callback) {
if (auto strongBufferedRuntimeExecutor_ =
weakBufferedRuntimeExecutor_.lock()) {
strongBufferedRuntimeExecutor_->execute(std::move(callback));
}
};
}
// TODO(T184010230): Should the RuntimeScheduler returned from this method be
// buffered?
std::shared_ptr<RuntimeScheduler>
ReactInstance::getRuntimeScheduler() noexcept {
return runtimeScheduler_;
}
namespace {
// Copied from JSIExecutor.cpp
// basename_r isn't in all iOS SDKs, so use this simple version instead.
std::string simpleBasename(const std::string& path) {
size_t pos = path.rfind('/');
return (pos != std::string::npos) ? path.substr(pos) : path;
}
} // namespace
/**
* Load the JS bundle and flush buffered JS calls, future JS calls won't be
* buffered after calling this.
* Note that this method is asynchronous. However, a completion callback
* isn't needed because all calls into JS should be dispatched to the JSThread,
* preferably via the runtimeExecutor_.
*/
void ReactInstance::loadScript(
std::unique_ptr<const JSBigString> script,
const std::string& sourceURL,
std::function<void(jsi::Runtime& runtime)>&& beforeLoad,
std::function<void(jsi::Runtime& runtime)>&& afterLoad) {
auto buffer = std::make_shared<BigStringBuffer>(std::move(script));
std::string scriptName = simpleBasename(sourceURL);
runtimeScheduler_->scheduleWork([this,
scriptName,
sourceURL,
buffer = std::move(buffer),
weakBufferedRuntimeExecuter =
std::weak_ptr<BufferedRuntimeExecutor>(
bufferedRuntimeExecutor_),
beforeLoad,
afterLoad](jsi::Runtime& runtime) {
if (beforeLoad) {
beforeLoad(runtime);
}
TraceSection s("ReactInstance::loadScript");
bool hasLogger(ReactMarker::logTaggedMarkerBridgelessImpl != nullptr);
if (hasLogger) {
ReactMarker::logTaggedMarkerBridgeless(
ReactMarker::RUN_JS_BUNDLE_START, scriptName.c_str());
ReactMarker::logMarkerBridgeless(ReactMarker::INIT_REACT_RUNTIME_START);
ReactMarker::logMarkerBridgeless(ReactMarker::APP_STARTUP_START);
}
// Check if the shermes unit is avaliable.
auto* shUnitAPI = jsi::castInterface<hermes::IHermesSHUnit>(&runtime);
auto* shUnitCreator = shUnitAPI ? shUnitAPI->getSHUnitCreator() : nullptr;
if (shUnitCreator) {
LOG(WARNING) << "ReactInstance: evaluateSHUnit";
auto* hermesAPI = jsi::castInterface<hermes::IHermes>(&runtime);
hermesAPI->evaluateSHUnit(shUnitCreator);
} else {
LOG(WARNING) << "ReactInstance: evaluateJavaScript() with JS bundle";
runtime.evaluateJavaScript(buffer, sourceURL);
}
/**
* TODO(T183610671): We need a safe/reliable way to enable the js
* pipeline from javascript. Remove this after we figure that out, or
* after we just remove the js pipeline.
*/
if (!jsErrorHandler_->hasHandledFatalError()) {
jsErrorHandler_->setRuntimeReady();
}
if (hasLogger) {
ReactMarker::logTaggedMarkerBridgeless(
ReactMarker::RUN_JS_BUNDLE_STOP, scriptName.c_str());
ReactMarker::logMarkerBridgeless(ReactMarker::INIT_REACT_RUNTIME_STOP);
ReactMarker::logMarkerBridgeless(ReactMarker::APP_STARTUP_STOP);
}
if (auto strongBufferedRuntimeExecuter =
weakBufferedRuntimeExecuter.lock()) {
strongBufferedRuntimeExecuter->flush();
}
if (afterLoad) {
afterLoad(runtime);
}
});
}
/*
* Calls a method on a JS module that has been registered with
* `registerCallableModule`. Used to invoke a JS function from platform code.
*/
void ReactInstance::callFunctionOnModule(
const std::string& moduleName,
const std::string& methodName,
folly::dynamic&& args) {
if (bufferedRuntimeExecutor_ == nullptr) {
LOG(ERROR)
<< "Calling callFunctionOnModule with null BufferedRuntimeExecutor";
return;
}
bufferedRuntimeExecutor_->execute([this,
moduleName = moduleName,
methodName = methodName,
args = std::move(args)](
jsi::Runtime& runtime) {
TraceSection s(
"ReactInstance::callFunctionOnModule",
"moduleName",
moduleName,
"methodName",
methodName);
auto it = callableModules_.find(moduleName);
if (it == callableModules_.end()) {
std::ostringstream knownModules;
int i = 0;
for (it = callableModules_.begin(); it != callableModules_.end();
it++, i++) {
const char* space = (i > 0 ? ", " : " ");
knownModules << space << it->first;
}
throw jsi::JSError(
runtime,
"Failed to call into JavaScript module method " + moduleName + "." +
methodName +
"(). Module has not been registered as callable. Registered callable JavaScript modules (n = " +
std::to_string(callableModules_.size()) +
"):" + knownModules.str() +
". Did you forget to call `registerCallableModule`?");
}
if (std::holds_alternative<jsi::Function>(it->second)) {
auto module =
std::get<jsi::Function>(it->second).call(runtime).asObject(runtime);
it->second = std::move(module);
}
auto& module = std::get<jsi::Object>(it->second);
auto method = module.getPropertyAsFunction(runtime, methodName.c_str());
std::vector<jsi::Value> jsArgs;
for (auto& arg : args) {
jsArgs.push_back(jsi::valueFromDynamic(runtime, arg));
}
method.callWithThis(
runtime, module, (const jsi::Value*)jsArgs.data(), jsArgs.size());
});
}
void ReactInstance::registerSegment(
uint32_t segmentId,
const std::string& segmentPath) {
LOG(WARNING) << "Starting to run ReactInstance::registerSegment with segment "
<< segmentId;
runtimeScheduler_->scheduleWork([=](jsi::Runtime& runtime) {
TraceSection s("ReactInstance::registerSegment");
auto tag = std::to_string(segmentId);
auto script = JSBigFileString::fromPath(segmentPath);
if (script->size() == 0) {
throw std::invalid_argument(
"Empty segment registered with ID " + tag + " from " + segmentPath);
}
auto buffer = std::make_shared<BigStringBuffer>(std::move(script));
bool hasLogger(ReactMarker::logTaggedMarkerBridgelessImpl != nullptr);
if (hasLogger) {
ReactMarker::logTaggedMarkerBridgeless(
ReactMarker::REGISTER_JS_SEGMENT_START, tag.c_str());
}
LOG(WARNING) << "Starting to evaluate segment " << segmentId
<< " in ReactInstance::registerSegment";
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
runtime.evaluateJavaScript(
buffer, JSExecutor::getSyntheticBundlePath(segmentId, segmentPath));
#pragma clang diagnostic pop
LOG(WARNING) << "Finished evaluating segment " << segmentId
<< " in ReactInstance::registerSegment";
if (hasLogger) {
ReactMarker::logTaggedMarkerBridgeless(
ReactMarker::REGISTER_JS_SEGMENT_STOP, tag.c_str());
}
});
}
namespace {
void defineReactInstanceFlags(
jsi::Runtime& runtime,
const ReactInstance::JSRuntimeFlags& options) noexcept {
defineReadOnlyGlobal(runtime, "RN$Bridgeless", jsi::Value(true));
if (options.isProfiling) {
defineReadOnlyGlobal(runtime, "__RCTProfileIsProfiling", jsi::Value(true));
}
if (options.runtimeDiagnosticFlags.length() > 0) {
defineReadOnlyGlobal(
runtime,
"RN$DiagnosticFlags",
jsi::String::createFromUtf8(runtime, options.runtimeDiagnosticFlags));
}
}
bool isTruthy(jsi::Runtime& runtime, const jsi::Value& value) {
auto Boolean = runtime.global().getPropertyAsFunction(runtime, "Boolean");
return Boolean.call(runtime, value).getBool();
}
} // namespace
void ReactInstance::initializeRuntime(
JSRuntimeFlags options,
BindingsInstallFunc bindingsInstallFunc) noexcept {
runtimeScheduler_->scheduleWork([this,
options = std::move(options),
bindingsInstallFunc =
std::move(bindingsInstallFunc)](
jsi::Runtime& runtime) {
TraceSection s("ReactInstance::initializeRuntime");
bindNativePerformanceNow(runtime);
RuntimeSchedulerBinding::createAndInstallIfNeeded(
runtime, runtimeScheduler_);
runtime_->unstable_initializeOnJsThread();
defineReactInstanceFlags(runtime, options);
defineReadOnlyGlobal(
runtime,
"RN$useAlwaysAvailableJSErrorHandling",
jsi::Value(
ReactNativeFeatureFlags::useAlwaysAvailableJSErrorHandling()));
defineReadOnlyGlobal(
runtime,
"RN$isRuntimeReady",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "isRuntimeReady"),
0,
[jsErrorHandler = jsErrorHandler_](
jsi::Runtime& /*runtime*/,
const jsi::Value& /*unused*/,
const jsi::Value* /*args*/,
size_t /*count*/) {
return jsErrorHandler->isRuntimeReady();
}));
defineReadOnlyGlobal(
runtime,
"RN$hasHandledFatalException",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "hasHandledFatalException"),
0,
[jsErrorHandler = jsErrorHandler_](
jsi::Runtime& /*runtime*/,
const jsi::Value& /*unused*/,
const jsi::Value* /*args*/,
size_t /*count*/) {
return jsErrorHandler->hasHandledFatalError();
}));
defineReadOnlyGlobal(
runtime,
"RN$notifyOfFatalException",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "notifyOfFatalException"),
0,
[jsErrorHandler = jsErrorHandler_](
jsi::Runtime& /*runtime*/,
const jsi::Value& /*unused*/,
const jsi::Value* /*args*/,
size_t /*count*/) {
jsErrorHandler->notifyOfFatalError();
return jsi::Value::undefined();
}));
defineReadOnlyGlobal(
runtime,
"RN$inExceptionHandler",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "inExceptionHandler"),
0,
[jsErrorHandler = jsErrorHandler_](
jsi::Runtime& /*runtime*/,
const jsi::Value& /*unused*/,
const jsi::Value* /*args*/,
size_t /*count*/) {
return jsErrorHandler->inErrorHandler();
}));
// TODO(T196834299): We should really use a C++ turbomodule for this
defineReadOnlyGlobal(
runtime,
"RN$handleException",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "handleException"),
3,
[jsErrorHandler = jsErrorHandler_](
jsi::Runtime& runtime,
const jsi::Value& /*unused*/,
const jsi::Value* args,
size_t count) {
if (count < 2) {
throw jsi::JSError(
runtime,
"handleException requires 3 arguments: error, isFatal, logToConsole (optional)");
}
auto isFatal = isTruthy(runtime, args[1]);
if (!ReactNativeFeatureFlags::
useAlwaysAvailableJSErrorHandling()) {
if (jsErrorHandler->isRuntimeReady()) {
return jsi::Value(false);
}
}
auto jsError =
jsi::JSError(runtime, jsi::Value(runtime, args[0]));
if (count == 2) {
jsErrorHandler->handleError(runtime, jsError, isFatal);
} else {
auto logToConsole = isTruthy(runtime, args[2]);
jsErrorHandler->handleError(
runtime, jsError, isFatal, logToConsole);
}
return jsi::Value(true);
}));
defineReadOnlyGlobal(
runtime,
"RN$registerExceptionListener",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "registerExceptionListener"),
1,
[errorListeners = std::vector<std::shared_ptr<jsi::Function>>(),
jsErrorHandler = jsErrorHandler_](
jsi::Runtime& runtime,
const jsi::Value& /*unused*/,
const jsi::Value* args,
size_t count) mutable {
if (count < 1) {
throw jsi::JSError(
runtime,
"registerExceptionListener: requires 1 argument: fn");
}
if (!args[0].isObject() ||
!args[0].getObject(runtime).isFunction(runtime)) {
throw jsi::JSError(
runtime,
"registerExceptionListener: The first argument must be a function");
}
auto errorListener = std::make_shared<jsi::Function>(
args[0].getObject(runtime).getFunction(runtime));
errorListeners.emplace_back(errorListener);
jsErrorHandler->registerErrorListener(
[weakErrorListener = std::weak_ptr<jsi::Function>(
errorListener)](jsi::Runtime& runtime, jsi::Value data) {
if (auto strongErrorListener = weakErrorListener.lock()) {
strongErrorListener->call(runtime, data);
}
});
return jsi::Value::undefined();
}));
defineReadOnlyGlobal(
runtime,
"RN$registerCallableModule",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "registerCallableModule"),
2,
[this](
jsi::Runtime& runtime,
const jsi::Value& /*unused*/,
const jsi::Value* args,
size_t count) {
if (count != 2) {
throw jsi::JSError(
runtime,
"registerCallableModule requires exactly 2 arguments");
}
if (!args[0].isString()) {
throw jsi::JSError(
runtime,
"The first argument to registerCallableModule must be a string (the name of the JS module).");
}
auto name = args[0].asString(runtime).utf8(runtime);
if (!args[1].isObject() ||
!args[1].getObject(runtime).isFunction(runtime)) {
throw jsi::JSError(
runtime,
"The second argument to registerCallableModule must be a function that returns the JS module.");
}
callableModules_.emplace(
std::move(name),
args[1].getObject(runtime).getFunction(runtime));
return jsi::Value::undefined();
}));
timerManager_->attachGlobals(runtime);
bindingsInstallFunc(runtime);
});
}
void ReactInstance::handleMemoryPressureJs(int pressureLevel) {
// The level is an enum value passed by the Android OS to an onTrimMemory
// event callback. Defined in ComponentCallbacks2.
enum AndroidMemoryPressure {
TRIM_MEMORY_BACKGROUND = 40,
TRIM_MEMORY_COMPLETE = 80,
TRIM_MEMORY_MODERATE = 60,
TRIM_MEMORY_RUNNING_CRITICAL = 15,
TRIM_MEMORY_RUNNING_LOW = 10,
TRIM_MEMORY_RUNNING_MODERATE = 5,
TRIM_MEMORY_UI_HIDDEN = 20,
};
const char* levelName = nullptr;
switch (pressureLevel) {
case TRIM_MEMORY_BACKGROUND:
levelName = "TRIM_MEMORY_BACKGROUND";
break;
case TRIM_MEMORY_COMPLETE:
levelName = "TRIM_MEMORY_COMPLETE";
break;
case TRIM_MEMORY_MODERATE:
levelName = "TRIM_MEMORY_MODERATE";
break;
case TRIM_MEMORY_RUNNING_CRITICAL:
levelName = "TRIM_MEMORY_RUNNING_CRITICAL";
break;
case TRIM_MEMORY_RUNNING_LOW:
levelName = "TRIM_MEMORY_RUNNING_LOW";
break;
case TRIM_MEMORY_RUNNING_MODERATE:
levelName = "TRIM_MEMORY_RUNNING_MODERATE";
break;
case TRIM_MEMORY_UI_HIDDEN:
levelName = "TRIM_MEMORY_UI_HIDDEN";
break;
default:
levelName = "UNKNOWN";
break;
}
switch (pressureLevel) {
case TRIM_MEMORY_RUNNING_LOW:
case TRIM_MEMORY_RUNNING_MODERATE:
case TRIM_MEMORY_UI_HIDDEN:
// For non-severe memory trims, do nothing.
LOG(INFO) << "Memory warning (pressure level: " << levelName
<< ") received by JS VM, ignoring because it's non-severe";
break;
case TRIM_MEMORY_BACKGROUND:
case TRIM_MEMORY_COMPLETE:
case TRIM_MEMORY_MODERATE:
case TRIM_MEMORY_RUNNING_CRITICAL:
// For now, pressureLevel is unused by collectGarbage.
// This may change in the future if the JS GC has different styles of
// collections.
LOG(INFO) << "Memory warning (pressure level: " << levelName
<< ") received by JS VM, running a GC";
runtimeScheduler_->scheduleWork([=](jsi::Runtime& runtime) {
TraceSection s("ReactInstance::handleMemoryPressure");
runtime.instrumentation().collectGarbage(levelName);
});
break;
default:
// Use the raw number instead of the name here since the name is
// meaningless.
LOG(WARNING) << "Memory warning (pressure level: " << pressureLevel
<< ") received by JS VM, unrecognized pressure level";
break;
}
}
void* ReactInstance::getJavaScriptContext() {
return &runtime_->getRuntime();
}
} // namespace facebook::react

View File

@@ -0,0 +1,82 @@
/*
* 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 <ReactCommon/RuntimeExecutor.h>
#include <cxxreact/JSBigString.h>
#include <cxxreact/MessageQueueThread.h>
#include <jserrorhandler/JsErrorHandler.h>
#include <jsi/jsi.h>
#include <jsinspector-modern/ReactCdp.h>
#include <jsireact/JSIExecutor.h>
#include <react/renderer/runtimescheduler/RuntimeScheduler.h>
#include <react/runtime/BufferedRuntimeExecutor.h>
#include <react/runtime/JSRuntimeFactory.h>
#include <react/runtime/TimerManager.h>
namespace facebook::react {
class ReactInstance final : private jsinspector_modern::InstanceTargetDelegate {
public:
using BindingsInstallFunc = std::function<void(jsi::Runtime &runtime)>;
ReactInstance(
std::unique_ptr<JSRuntime> runtime,
std::shared_ptr<MessageQueueThread> jsMessageQueueThread,
std::shared_ptr<TimerManager> timerManager,
JsErrorHandler::OnJsError onJsError,
jsinspector_modern::HostTarget *parentInspectorTarget = nullptr);
RuntimeExecutor getUnbufferedRuntimeExecutor() noexcept;
RuntimeExecutor getBufferedRuntimeExecutor() noexcept;
std::shared_ptr<RuntimeScheduler> getRuntimeScheduler() noexcept;
struct JSRuntimeFlags {
bool isProfiling = false;
const std::string runtimeDiagnosticFlags = {};
};
void initializeRuntime(JSRuntimeFlags options, BindingsInstallFunc bindingsInstallFunc) noexcept;
void loadScript(
std::unique_ptr<const JSBigString> script,
const std::string &sourceURL,
std::function<void(jsi::Runtime &runtime)> &&beforeLoad = nullptr,
std::function<void(jsi::Runtime &runtime)> &&afterLoad = nullptr);
void registerSegment(uint32_t segmentId, const std::string &segmentPath);
void callFunctionOnModule(const std::string &moduleName, const std::string &methodName, folly::dynamic &&args);
void handleMemoryPressureJs(int pressureLevel);
/**
* Unregisters the instance from the inspector. This method must be called
* on the main (non-JS) thread.
*/
void unregisterFromInspector();
void *getJavaScriptContext();
private:
std::shared_ptr<JSRuntime> runtime_;
std::shared_ptr<MessageQueueThread> jsMessageQueueThread_;
std::shared_ptr<BufferedRuntimeExecutor> bufferedRuntimeExecutor_;
std::shared_ptr<TimerManager> timerManager_;
std::unordered_map<std::string, std::variant<jsi::Function, jsi::Object>> callableModules_;
std::shared_ptr<RuntimeScheduler> runtimeScheduler_;
std::shared_ptr<JsErrorHandler> jsErrorHandler_;
jsinspector_modern::InstanceTarget *inspectorTarget_{nullptr};
jsinspector_modern::RuntimeTarget *runtimeInspectorTarget_{nullptr};
jsinspector_modern::HostTarget *parentInspectorTarget_{nullptr};
};
} // namespace facebook::react

View File

@@ -0,0 +1,416 @@
/*
* 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.
*/
#include "TimerManager.h"
#include <cxxreact/TraceSection.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <cmath>
#include <utility>
namespace facebook::react {
namespace {
double coerceNumberTimeout(jsi::Runtime& rt, const jsi::Value& timeout) {
double delay = 0.0;
// fast-path
if (timeout.isNumber()) {
delay = timeout.getNumber();
} else {
// perform number coercion for timeout to be web spec compliant
auto numberCtor = rt.global().getPropertyAsObject(rt, "Number");
auto delayNumberObject =
numberCtor.getFunction(rt).callAsConstructor(rt, timeout).getObject(rt);
auto delayNumericValue =
delayNumberObject.getPropertyAsFunction(rt, "valueOf")
.callWithThis(rt, delayNumberObject);
delay = delayNumericValue.isNumber() ? delayNumericValue.getNumber() : 0;
}
return std::isnan(delay) ? 0.0 : std::max(0.0, delay);
}
inline const char* getTimerSourceName(TimerSource source) {
switch (source) {
case TimerSource::Unknown:
return "unknown";
case TimerSource::SetTimeout:
return "setTimeout";
case TimerSource::SetInterval:
return "setInterval";
case TimerSource::RequestAnimationFrame:
return "requestAnimationFrame";
}
return "unknown";
}
} // namespace
TimerManager::TimerManager(
std::unique_ptr<PlatformTimerRegistry> platformTimerRegistry) noexcept
: platformTimerRegistry_(std::move(platformTimerRegistry)) {}
void TimerManager::setRuntimeExecutor(
RuntimeExecutor runtimeExecutor) noexcept {
runtimeExecutor_ = std::move(runtimeExecutor);
}
TimerHandle TimerManager::createReactNativeMicrotask(
jsi::Function&& callback,
std::vector<jsi::Value>&& args) {
// Get the id for the callback.
TimerHandle timerID = timerIndex_++;
timers_.emplace(
std::piecewise_construct,
std::forward_as_tuple(timerID),
std::forward_as_tuple(
std::move(callback), std::move(args), /* repeat */ false));
reactNativeMicrotasksQueue_.push_back(timerID);
return timerID;
}
void TimerManager::callReactNativeMicrotasks(jsi::Runtime& runtime) {
std::vector<TimerHandle> reactNativeMicrotasksQueue;
while (!reactNativeMicrotasksQueue_.empty()) {
reactNativeMicrotasksQueue.clear();
reactNativeMicrotasksQueue.swap(reactNativeMicrotasksQueue_);
for (auto reactNativeMicrotaskID : reactNativeMicrotasksQueue) {
// ReactNativeMicrotasks can clear other scheduled reactNativeMicrotasks.
auto it = timers_.find(reactNativeMicrotaskID);
if (it != timers_.end()) {
it->second.invoke(runtime);
// Invoking a timer has the potential to delete it. Do not re-use the
// existing iterator to erase it from the map.
timers_.erase(reactNativeMicrotaskID);
}
}
}
}
TimerHandle TimerManager::createTimer(
jsi::Function&& callback,
std::vector<jsi::Value>&& args,
double delay,
TimerSource source) {
// Get the id for the callback.
TimerHandle timerID = timerIndex_++;
TraceSection s(
"TimerManager::createTimer",
"id",
timerID,
"type",
getTimerSourceName(source),
"delay",
delay);
timers_.emplace(
std::piecewise_construct,
std::forward_as_tuple(timerID),
std::forward_as_tuple(
std::move(callback),
std::move(args),
/* repeat */ false,
source));
platformTimerRegistry_->createTimer(timerID, delay);
return timerID;
}
TimerHandle TimerManager::createRecurringTimer(
jsi::Function&& callback,
std::vector<jsi::Value>&& args,
double delay,
TimerSource source) {
// Get the id for the callback.
TimerHandle timerID = timerIndex_++;
TraceSection s(
"TimerManager::createRecurringTimer",
"id",
timerID,
"type",
getTimerSourceName(source),
"delay",
delay);
timers_.emplace(
std::piecewise_construct,
std::forward_as_tuple(timerID),
std::forward_as_tuple(
std::move(callback), std::move(args), /* repeat */ true, source));
platformTimerRegistry_->createRecurringTimer(timerID, delay);
return timerID;
}
void TimerManager::deleteReactNativeMicrotask(
jsi::Runtime& runtime,
TimerHandle timerHandle) {
if (timerHandle < 0) {
throw jsi::JSError(
runtime, "clearReactNativeMicrotask was called with an invalid handle");
}
auto it = std::find(
reactNativeMicrotasksQueue_.begin(),
reactNativeMicrotasksQueue_.end(),
timerHandle);
if (it != reactNativeMicrotasksQueue_.end()) {
reactNativeMicrotasksQueue_.erase(it);
timers_.erase(timerHandle);
}
}
void TimerManager::deleteTimer(jsi::Runtime& runtime, TimerHandle timerHandle) {
if (timerHandle < 0) {
throw jsi::JSError(runtime, "clearTimeout called with an invalid handle");
}
platformTimerRegistry_->deleteTimer(timerHandle);
timers_.erase(timerHandle);
}
void TimerManager::deleteRecurringTimer(
jsi::Runtime& runtime,
TimerHandle timerHandle) {
if (timerHandle < 0) {
throw jsi::JSError(runtime, "clearInterval called with an invalid handle");
}
platformTimerRegistry_->deleteTimer(timerHandle);
timers_.erase(timerHandle);
}
void TimerManager::callTimer(TimerHandle timerHandle) {
runtimeExecutor_([this, timerHandle](jsi::Runtime& runtime) {
auto it = timers_.find(timerHandle);
if (it != timers_.end()) {
auto& timerCallback = it->second;
bool repeats = timerCallback.repeat;
{
TraceSection s(
"TimerManager::callTimer",
"id",
timerHandle,
"type",
getTimerSourceName(timerCallback.source));
timerCallback.invoke(runtime);
}
if (!repeats) {
// Invoking a timer has the potential to delete it. Do not re-use the
// existing iterator to erase it from the map.
timers_.erase(timerHandle);
}
}
});
}
void TimerManager::attachGlobals(jsi::Runtime& runtime) {
// Install host functions for timers.
// TODO (T45786383): Add missing timer functions from JSTimers
runtime.global().setProperty(
runtime,
"setTimeout",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "setTimeout"),
3, // Function, delay, ...args
[this](
jsi::Runtime& rt,
const jsi::Value& /*thisVal*/,
const jsi::Value* args,
size_t count) {
if (count == 0) {
throw jsi::JSError(
rt,
"setTimeout must be called with at least one argument (the function to call).");
}
if (!args[0].isObject() || !args[0].asObject(rt).isFunction(rt)) {
// Do not throw any error to match web spec; instead return 0, an
// invalid timer id
return 0;
}
auto callback = args[0].getObject(rt).getFunction(rt);
auto delay = count > 1 ? coerceNumberTimeout(rt, args[1]) : 0.0;
// Package up the remaining argument values into one place.
std::vector<jsi::Value> moreArgs;
for (size_t extraArgNum = 2; extraArgNum < count; extraArgNum++) {
moreArgs.emplace_back(rt, args[extraArgNum]);
}
return createTimer(
std::move(callback),
std::move(moreArgs),
delay,
TimerSource::SetTimeout);
}));
runtime.global().setProperty(
runtime,
"clearTimeout",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "clearTimeout"),
1, // timerID
[this](
jsi::Runtime& rt,
const jsi::Value& /*thisVal*/,
const jsi::Value* args,
size_t count) {
if (count > 0 && args[0].isNumber()) {
auto handle = (TimerHandle)args[0].asNumber();
deleteTimer(rt, handle);
}
return jsi::Value::undefined();
}));
runtime.global().setProperty(
runtime,
"setInterval",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "setInterval"),
3, // Function, delay, ...args
[this](
jsi::Runtime& rt,
const jsi::Value& /*thisVal*/,
const jsi::Value* args,
size_t count) {
if (count == 0) {
throw jsi::JSError(
rt,
"setInterval must be called with at least one argument (the function to call).");
}
if (!args[0].isObject() || !args[0].asObject(rt).isFunction(rt)) {
// Do not throw any error to match web spec; instead return 0, an
// invalid timer id
return 0;
}
auto callback = args[0].getObject(rt).getFunction(rt);
auto delay = count > 1
? coerceNumberTimeout(rt, jsi::Value{rt, args[1]})
: 0.0;
// Package up the remaining argument values into one place.
std::vector<jsi::Value> moreArgs;
for (size_t extraArgNum = 2; extraArgNum < count; extraArgNum++) {
moreArgs.emplace_back(rt, args[extraArgNum]);
}
return createRecurringTimer(
std::move(callback),
std::move(moreArgs),
delay,
TimerSource::SetInterval);
}));
runtime.global().setProperty(
runtime,
"clearInterval",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "clearInterval"),
1, // timerID
[this](
jsi::Runtime& rt,
const jsi::Value& /*thisVal*/,
const jsi::Value* args,
size_t count) {
if (count > 0 && args[0].isNumber()) {
auto handle = (TimerHandle)args[0].asNumber();
deleteRecurringTimer(rt, handle);
}
return jsi::Value::undefined();
}));
runtime.global().setProperty(
runtime,
"requestAnimationFrame",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "requestAnimationFrame"),
1, // callback
[this](
jsi::Runtime& rt,
const jsi::Value& /*thisVal*/,
const jsi::Value* args,
size_t count) {
if (count == 0) {
throw jsi::JSError(
rt,
"requestAnimationFrame must be called with at least one argument (i.e: a callback)");
}
if (!args[0].isObject() || !args[0].asObject(rt).isFunction(rt)) {
throw jsi::JSError(
rt,
"The first argument to requestAnimationFrame must be a function.");
}
auto callback = jsi::Function::createFromHostFunction(
rt,
jsi::PropNameID::forAscii(rt, "RN$rafFn"),
0,
[callbackContainer = std::make_shared<jsi::Function>(
args[0].getObject(rt).getFunction(rt))](
jsi::Runtime& rt,
const jsi::Value& /*thisVal*/,
const jsi::Value* /*args*/,
size_t /*count*/) {
auto performance =
rt.global().getPropertyAsObject(rt, "performance");
auto nowFn = performance.getPropertyAsFunction(rt, "now");
auto now = nowFn.callWithThis(rt, performance, {});
return callbackContainer->call(rt, {std::move(now)});
});
// The current implementation of requestAnimationFrame is the same
// as setTimeout(0). This isn't exactly how requestAnimationFrame
// is supposed to work on web, and may change in the future.
return createTimer(
std::move(callback),
std::vector<jsi::Value>(),
/* delay */ 0,
TimerSource::RequestAnimationFrame);
}));
runtime.global().setProperty(
runtime,
"cancelAnimationFrame",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "cancelAnimationFrame"),
1, // timerID
[this](
jsi::Runtime& rt,
const jsi::Value& /*thisVal*/,
const jsi::Value* args,
size_t count) {
if (count > 0 && args[0].isNumber()) {
auto handle = (TimerHandle)args[0].asNumber();
deleteTimer(rt, handle);
}
return jsi::Value::undefined();
}));
}
} // namespace facebook::react

View File

@@ -0,0 +1,98 @@
/*
* 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 <ReactCommon/RuntimeExecutor.h>
#include <cstdint>
#include <unordered_map>
#include <vector>
#include "PlatformTimerRegistry.h"
namespace facebook::react {
using TimerHandle = int;
enum class TimerSource { Unknown, SetTimeout, SetInterval, RequestAnimationFrame };
/*
* Wraps a jsi::Function to make it copyable so we can pass it into a lambda.
*/
struct TimerCallback {
TimerCallback(
jsi::Function callback,
std::vector<jsi::Value> args,
bool repeat,
TimerSource source = TimerSource::Unknown)
: callback_(std::move(callback)), args_(std::move(args)), repeat(repeat), source(source)
{
}
void invoke(jsi::Runtime &runtime)
{
callback_.call(runtime, args_.data(), args_.size());
}
jsi::Function callback_;
const std::vector<jsi::Value> args_;
bool repeat;
TimerSource source;
};
class TimerManager {
public:
explicit TimerManager(std::unique_ptr<PlatformTimerRegistry> platformTimerRegistry) noexcept;
void setRuntimeExecutor(RuntimeExecutor runtimeExecutor) noexcept;
void callReactNativeMicrotasks(jsi::Runtime &runtime);
void callTimer(TimerHandle handle);
void attachGlobals(jsi::Runtime &runtime);
private:
TimerHandle createReactNativeMicrotask(jsi::Function &&callback, std::vector<jsi::Value> &&args);
void deleteReactNativeMicrotask(jsi::Runtime &runtime, TimerHandle handle);
TimerHandle createTimer(
jsi::Function &&callback,
std::vector<jsi::Value> &&args,
double delay,
TimerSource source = TimerSource::Unknown);
void deleteTimer(jsi::Runtime &runtime, TimerHandle handle);
TimerHandle createRecurringTimer(
jsi::Function &&callback,
std::vector<jsi::Value> &&args,
double delay,
TimerSource source = TimerSource::Unknown);
void deleteRecurringTimer(jsi::Runtime &runtime, TimerHandle handle);
RuntimeExecutor runtimeExecutor_;
std::unique_ptr<PlatformTimerRegistry> platformTimerRegistry_;
// A map (id => callback func) of the currently active JS timers
std::unordered_map<TimerHandle, TimerCallback> timers_;
// Each timeout that is registered on this queue gets a sequential id. This
// is the global count from which those are assigned.
// As per WHATWG HTML 8.6.1 (Timers) ids must be greater than zero, i.e. start
// at 1
TimerHandle timerIndex_{1};
// The React Native microtask queue is used to back public APIs including
// `queueMicrotask`, `clearImmediate`, and `setImmediate` (which is used by
// the Promise polyfill) when the JSVM microtask mechanism is not used.
std::vector<TimerHandle> reactNativeMicrotasksQueue_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,42 @@
# 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/internal/react-native-platform-selector.cmake)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB_RECURSE bridgeless_hermes_SRC CONFIGURE_DEPENDS *.cpp)
add_library(
bridgelesshermes
OBJECT
${bridgeless_hermes_SRC}
)
target_include_directories(bridgelesshermes PUBLIC .)
react_native_android_selector(reactnative reactnative "")
target_link_libraries(bridgelesshermes
hermes-engine::hermesvm
hermes_executor_common
hermes_inspector_modern
jsi
jsitooling
jsinspector
${reactnative}
)
target_compile_reactnative_options(bridgelesshermes PRIVATE)
if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED)
target_compile_options(
bridgelesshermes
PRIVATE
-DHERMES_ENABLE_DEBUGGER=1
)
if (HERMES_V1_ENABLED)
target_compile_options(bridgelesshermes PRIVATE -DHERMES_V1_ENABLED=1)
endif()
endif()

View File

@@ -0,0 +1,179 @@
/*
* 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.
*/
#include "HermesInstance.h"
#include <hermes/inspector-modern/chrome/HermesRuntimeTargetDelegate.h>
#include <jsi/jsilib.h>
#include <jsinspector-modern/InspectorFlags.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#ifdef HERMES_ENABLE_DEBUGGER
#include <hermes/inspector-modern/chrome/Registration.h>
#ifndef HERMES_V1_ENABLED
#include <hermes/inspector/RuntimeAdapter.h>
#endif
#include <jsi/decorator.h>
#endif
using namespace facebook::hermes;
using namespace facebook::jsi;
namespace facebook::react {
#if defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED)
// Wrapper that strongly retains the HermesRuntime for on device debugging.
//
// HermesInstanceRuntimeAdapter needs to strongly retain the HermesRuntime. Why:
// - facebook::hermes::inspector_modern::chrome::Connection::Impl owns the
// Adapter
// - facebook::hermes::inspector_modern::chrome::Connection::Impl also owns
// jsi:: objects
// - jsi:: objects need to be deleted before the Runtime.
//
// If Adapter doesn't share ownership over jsi::Runtime, the runtime can be
// deleted before Connection::Impl cleans up all its jsi:: Objects. This will
// lead to a runtime crash.
class HermesInstanceRuntimeAdapter : public inspector_modern::RuntimeAdapter {
public:
HermesInstanceRuntimeAdapter(
std::shared_ptr<HermesRuntime> hermesRuntime,
std::shared_ptr<MessageQueueThread> msgQueueThread)
: hermesRuntime_(std::move(hermesRuntime)),
messageQueueThread_(std::move(msgQueueThread)) {}
virtual ~HermesInstanceRuntimeAdapter() = default;
HermesRuntime& getRuntime() override {
return *hermesRuntime_;
}
void tickleJs() override {
std::weak_ptr<HermesRuntime> weakRuntime(hermesRuntime_);
messageQueueThread_->runOnQueue([weakRuntime]() {
auto runtime = weakRuntime.lock();
if (!runtime) {
return;
}
jsi::Function func =
runtime->global().getPropertyAsFunction(*runtime, "__tickleJs");
func.call(*runtime);
});
}
private:
std::shared_ptr<HermesRuntime> hermesRuntime_;
std::shared_ptr<MessageQueueThread> messageQueueThread_;
};
class DecoratedRuntime : public jsi::RuntimeDecorator<jsi::Runtime> {
public:
DecoratedRuntime(
std::unique_ptr<HermesRuntime> runtime,
std::shared_ptr<MessageQueueThread> msgQueueThread)
: RuntimeDecorator<jsi::Runtime>(*runtime), runtime_(std::move(runtime)) {
auto adapter = std::make_unique<HermesInstanceRuntimeAdapter>(
runtime_, msgQueueThread);
debugToken_ = inspector_modern::chrome::enableDebugging(
std::move(adapter), "Hermes Bridgeless React Native");
}
~DecoratedRuntime() {
inspector_modern::chrome::disableDebugging(debugToken_);
}
private:
std::shared_ptr<HermesRuntime> runtime_;
inspector_modern::chrome::DebugSessionToken debugToken_;
};
#endif // defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED)
class HermesJSRuntime : public JSRuntime {
public:
HermesJSRuntime(std::unique_ptr<HermesRuntime> runtime)
: runtime_(std::move(runtime)) {}
jsi::Runtime& getRuntime() noexcept override {
return *runtime_;
}
jsinspector_modern::RuntimeTargetDelegate& getRuntimeTargetDelegate()
override {
if (!targetDelegate_) {
targetDelegate_.emplace(runtime_);
}
return *targetDelegate_;
}
void unstable_initializeOnJsThread() override {
runtime_->registerForProfiling();
}
private:
std::shared_ptr<HermesRuntime> runtime_;
std::optional<jsinspector_modern::HermesRuntimeTargetDelegate>
targetDelegate_;
};
std::unique_ptr<JSRuntime> HermesInstance::createJSRuntime(
std::shared_ptr<::hermes::vm::CrashManager> crashManager,
std::shared_ptr<MessageQueueThread> msgQueueThread,
bool allocInOldGenBeforeTTI) noexcept {
assert(msgQueueThread != nullptr);
auto gcConfig = ::hermes::vm::GCConfig::Builder()
// Default to 3GB
.withMaxHeapSize(3072 << 20)
.withName("RNBridgeless");
if (allocInOldGenBeforeTTI) {
// For the next two arguments: avoid GC before TTI
// by initializing the runtime to allocate directly
// in the old generation, but revert to normal
// operation when we reach the (first) TTI point.
gcConfig.withAllocInYoung(false).withRevertToYGAtTTI(true);
}
::hermes::vm::RuntimeConfig::Builder runtimeConfigBuilder =
::hermes::vm::RuntimeConfig::Builder()
.withGCConfig(gcConfig.build())
.withEnableSampleProfiling(true)
.withMicrotaskQueue(
ReactNativeFeatureFlags::enableBridgelessArchitecture());
if (crashManager) {
runtimeConfigBuilder.withCrashMgr(crashManager);
}
std::unique_ptr<HermesRuntime> hermesRuntime =
hermes::makeHermesRuntime(runtimeConfigBuilder.build());
auto errorPrototype = hermesRuntime->global()
.getPropertyAsObject(*hermesRuntime, "Error")
.getPropertyAsObject(*hermesRuntime, "prototype");
errorPrototype.setProperty(*hermesRuntime, "jsEngine", "hermes");
#if defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED)
auto& inspectorFlags = jsinspector_modern::InspectorFlags::getInstance();
if (!inspectorFlags.getFuseboxEnabled()) {
std::unique_ptr<DecoratedRuntime> decoratedRuntime =
std::make_unique<DecoratedRuntime>(
std::move(hermesRuntime), msgQueueThread);
return std::make_unique<JSIRuntimeHolder>(std::move(decoratedRuntime));
}
#else
(void)msgQueueThread;
#endif
return std::make_unique<HermesJSRuntime>(std::move(hermesRuntime));
}
} // namespace facebook::react

View File

@@ -0,0 +1,25 @@
/*
* 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 <cxxreact/MessageQueueThread.h>
#include <hermes/hermes.h>
#include <jsi/jsi.h>
#include <react/runtime/JSRuntimeFactory.h>
namespace facebook::react {
class HermesInstance {
public:
static std::unique_ptr<JSRuntime> createJSRuntime(
std::shared_ptr<::hermes::vm::CrashManager> crashManager,
std::shared_ptr<MessageQueueThread> msgQueueThread,
bool allocInOldGenBeforeTTI) noexcept;
};
} // namespace facebook::react

View File

@@ -0,0 +1,178 @@
/*
* 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.
*/
#import <XCTest/XCTest.h>
#import <RCTTestUtils/RCTMemoryUtils.h>
#import <RCTTestUtils/ShimRCTInstance.h>
#import <React/RCTLog.h>
#import <React/RCTMockDef.h>
#import <ReactCommon/RCTHermesInstance.h>
#import <ReactCommon/RCTHost.h>
#import <ReactCommon/RCTInstance.h>
#import <ReactCommon/RCTTurboModuleManager.h>
#import <hermes/hermes.h>
#import <OCMock/OCMock.h>
RCT_MOCK_REF(RCTHost, _RCTLogNativeInternal);
RCTLogLevel gLogLevel;
int gLogCalledTimes = 0;
NSString *gLogMessage = nil;
static void RCTLogNativeInternalMock(RCTLogLevel level, const char *fileName, int lineNumber, NSString *format, ...)
{
gLogLevel = level;
gLogCalledTimes++;
va_list args;
va_start(args, format);
gLogMessage = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
}
@interface RCTHostTests : XCTestCase
@end
@implementation RCTHostTests {
RCTHost *_subject;
id<RCTHostDelegate> _mockHostDelegate;
}
static ShimRCTInstance *shimmedRCTInstance;
- (void)setUp
{
[super setUp];
RCTAutoReleasePoolPush();
shimmedRCTInstance = [ShimRCTInstance new];
_mockHostDelegate = OCMProtocolMock(@protocol(RCTHostDelegate));
_subject = [[RCTHost alloc] initWithBundleURL:OCMClassMock([NSURL class])
hostDelegate:_mockHostDelegate
turboModuleManagerDelegate:OCMProtocolMock(@protocol(RCTTurboModuleManagerDelegate))
jsEngineProvider:^std::shared_ptr<facebook::react::JSRuntimeFactory>() {
return std::make_shared<facebook::react::RCTHermesInstance>();
}
launchOptions:nil];
}
- (void)tearDown
{
RCTAutoReleasePoolPop();
_subject = nil;
XCTAssertEqual(RCTGetRetainCount(_subject), 0);
_mockHostDelegate = nil;
XCTAssertEqual(RCTGetRetainCount(_mockHostDelegate), 0);
[shimmedRCTInstance reset];
gLogCalledTimes = 0;
gLogMessage = nil;
[super tearDown];
}
- (void)testStart
{
RCT_MOCK_SET(RCTHost, _RCTLogNativeInternal, RCTLogNativeInternalMock);
XCTAssertEqual(shimmedRCTInstance.initCount, 0);
[_subject start];
OCMVerify(OCMTimes(1), [_mockHostDelegate hostDidStart:_subject]);
XCTAssertEqual(shimmedRCTInstance.initCount, 1);
XCTAssertEqual(gLogCalledTimes, 0);
XCTAssertEqual(shimmedRCTInstance.invalidateCount, 0);
[_subject start];
XCTAssertEqual(shimmedRCTInstance.initCount, 2);
XCTAssertEqual(shimmedRCTInstance.invalidateCount, 1);
OCMVerify(OCMTimes(2), [_mockHostDelegate hostDidStart:_subject]);
XCTAssertEqual(gLogLevel, RCTLogLevelWarning);
XCTAssertEqual(gLogCalledTimes, 1);
XCTAssertEqualObjects(
gLogMessage,
@"RCTHost should not be creating a new instance if one already exists. This implies there is a bug with how/when this method is being called.");
RCT_MOCK_RESET(RCTHost, _RCTLogNativeInternal);
}
- (void)testCallFunctionOnJSModule
{
[_subject start];
NSArray *args = @[ @"hi", @(5), @(NO) ];
[_subject callFunctionOnJSModule:@"jsModule" method:@"method" args:args];
XCTAssertEqualObjects(shimmedRCTInstance.jsModuleName, @"jsModule");
XCTAssertEqualObjects(shimmedRCTInstance.method, @"method");
XCTAssertEqualObjects(shimmedRCTInstance.args, args);
}
- (void)testDidReceiveErrorStack
{
id<RCTInstanceDelegate> instanceDelegate = (id<RCTInstanceDelegate>)_subject;
NSMutableArray<NSDictionary<NSString *, id> *> *stack = [NSMutableArray array];
NSMutableDictionary<NSString *, id> *stackFrame0 = [NSMutableDictionary dictionary];
stackFrame0[@"linenumber"] = @(3);
stackFrame0[@"column"] = @(4);
stackFrame0[@"methodname"] = @"method1";
stackFrame0[@"file"] = @"file1.js";
[stack addObject:stackFrame0];
NSMutableDictionary<NSString *, id> *stackFrame1 = [NSMutableDictionary dictionary];
stackFrame0[@"linenumber"] = @(63);
stackFrame0[@"column"] = @(44);
stackFrame0[@"methodname"] = @"method2";
stackFrame0[@"file"] = @"file2.js";
[stack addObject:stackFrame1];
id extraData = [NSDictionary dictionary];
[instanceDelegate instance:[OCMArg any]
didReceiveJSErrorStack:stack
message:@"message"
originalMessage:nil
name:nil
componentStack:nil
exceptionId:5
isFatal:YES
extraData:extraData];
OCMVerify(
OCMTimes(1),
[_mockHostDelegate host:_subject
didReceiveJSErrorStack:stack
message:@"message"
originalMessage:nil
name:nil
componentStack:nil
exceptionId:5
isFatal:YES
extraData:extraData]);
}
- (void)testDidInitializeRuntime
{
id<RCTHostRuntimeDelegate> mockRuntimeDelegate = OCMProtocolMock(@protocol(RCTHostRuntimeDelegate));
_subject.runtimeDelegate = mockRuntimeDelegate;
auto hermesRuntime = facebook::hermes::makeHermesRuntime();
facebook::jsi::Runtime *rt = hermesRuntime.get();
id<RCTInstanceDelegate> instanceDelegate = (id<RCTInstanceDelegate>)_subject;
[instanceDelegate instance:[OCMArg any] didInitializeRuntime:*rt];
OCMVerify(OCMTimes(1), [mockRuntimeDelegate host:_subject didInitializeRuntime:*rt]);
}
@end

View File

@@ -0,0 +1,20 @@
# 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB_RECURSE bridgeless_nativeviewconfig_SRC CONFIGURE_DEPENDS *.cpp)
add_library(
bridgelessnativeviewconfig
OBJECT
${bridgeless_nativeviewconfig_SRC}
)
target_include_directories(bridgelessnativeviewconfig PUBLIC .)
target_link_libraries(bridgelessnativeviewconfig jsi)
target_compile_reactnative_options(bridgelessnativeviewconfig PRIVATE)

View File

@@ -0,0 +1,61 @@
/*
* 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.
*/
#include "LegacyUIManagerConstantsProviderBinding.h"
namespace facebook::react::LegacyUIManagerConstantsProviderBinding {
void install(
jsi::Runtime& runtime,
const std::string& name,
std::function<jsi::Value(jsi::Runtime&)>&& provider) {
auto methodName = "RN$LegacyInterop_UIManager_" + name;
auto hostFunction = [provider = std::move(provider)](
jsi::Runtime& runtime,
const jsi::Value& /*thisValue*/,
const jsi::Value* /*arguments*/,
size_t count) -> jsi::Value {
if (count != 0) {
throw new jsi::JSError(runtime, "0 arguments expected.");
}
return provider(runtime);
};
auto jsiFunction = jsi::Function::createFromHostFunction(
runtime, jsi::PropNameID::forAscii(runtime, name), 2, hostFunction);
runtime.global().setProperty(runtime, methodName.c_str(), jsiFunction);
}
void install(
jsi::Runtime& runtime,
const std::string& name,
std::function<jsi::Value(jsi::Runtime&, const std::string&)>&& provider) {
auto methodName = "RN$LegacyInterop_UIManager_" + name;
auto hostFunction = [provider = std::move(provider)](
jsi::Runtime& runtime,
const jsi::Value& /*thisValue*/,
const jsi::Value* args,
size_t count) -> jsi::Value {
if (count != 1) {
throw new jsi::JSError(runtime, "1 argument expected.");
}
if (!args[0].isString()) {
throw new jsi::JSError(runtime, "First argument must be string.");
}
return provider(runtime, args[0].asString(runtime).utf8(runtime));
};
auto jsiFunction = jsi::Function::createFromHostFunction(
runtime, jsi::PropNameID::forAscii(runtime, name), 2, hostFunction);
runtime.global().setProperty(runtime, methodName.c_str(), jsiFunction);
}
} // namespace facebook::react::LegacyUIManagerConstantsProviderBinding

View File

@@ -0,0 +1,24 @@
/*
* 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 <jsi/jsi.h>
namespace facebook::react::LegacyUIManagerConstantsProviderBinding {
/*
* Installs RN$LegacyInterop_UIManager_getConstants binding into JavaScript
* runtime. It is supposed to be used as a substitute to UIManager.getConstants
* in bridgeless mode.
*/
void install(jsi::Runtime &runtime, const std::string &name, std::function<jsi::Value(jsi::Runtime &)> &&provider);
void install(
jsi::Runtime &runtime,
const std::string &name,
std::function<jsi::Value(jsi::Runtime &, const std::string &)> &&provider);
} // namespace facebook::react::LegacyUIManagerConstantsProviderBinding

View File

@@ -0,0 +1,72 @@
# 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.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "../../../../..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
header_search_paths = [
"$(PODS_ROOT)/Headers/Private/React-Core",
"$(PODS_TARGET_SRCROOT)/../../../..",
"$(PODS_TARGET_SRCROOT)/../../../../..",
]
Pod::Spec.new do |s|
s.name = "React-RuntimeApple"
s.version = version
s.summary = "The React Native Runtime."
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = podspec_sources("ReactCommon/*.{mm,h}", "ReactCommon/*.{h}")
s.header_dir = "ReactCommon"
s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => header_search_paths,
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"GCC_WARN_PEDANTIC" => "YES" }
resolve_use_frameworks(s, header_mappings_dir: "./", module_name: "React_RuntimeApple")
s.dependency "React-jsiexecutor"
s.dependency "React-cxxreact"
s.dependency "React-callinvoker"
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
s.dependency "React-runtimescheduler"
s.dependency "React-jsi"
s.dependency "React-Core/Default"
s.dependency "React-CoreModules"
s.dependency "React-NativeModulesApple"
s.dependency "React-RCTFabric"
s.dependency "React-RuntimeCore"
s.dependency "React-Mapbuffer"
s.dependency "React-jserrorhandler"
s.dependency "React-jsinspector"
s.dependency "React-featureflags"
add_dependency(s, "React-jsitooling", :framework_name => "JSITooling")
add_dependency(s, "React-RCTFBReactNativeSpec")
add_dependency(s, "React-utils", :additional_framework_paths => ["react/utils/platform/ios"])
if use_third_party_jsc()
s.exclude_files = ["ReactCommon/RCTHermesInstance.{mm,h}", "ReactCommon/RCTJscInstance.{mm,h}"]
else
s.dependency "hermes-engine"
add_dependency(s, "React-RuntimeHermes")
s.exclude_files = "ReactCommon/RCTJscInstance.{mm,h}"
end
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
end

View File

@@ -0,0 +1,32 @@
/*
* 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.
*/
#import <UIKit/UIKit.h>
#import <React/RCTTiming.h>
#import <react/runtime/PlatformTimerRegistry.h>
#import <react/runtime/TimerManager.h>
@interface RCTJSTimerExecutor : NSObject <RCTTimingDelegate>
- (void)setTimerManager:(std::weak_ptr<facebook::react::TimerManager>)timerManager;
@end
class ObjCTimerRegistry : public facebook::react::PlatformTimerRegistry {
public:
ObjCTimerRegistry();
void createTimer(uint32_t timerID, double delayMS) override;
void deleteTimer(uint32_t timerID) override;
void createRecurringTimer(uint32_t timerID, double delayMS) override;
void setTimerManager(std::weak_ptr<facebook::react::TimerManager> timerManager);
RCTTiming *_Null_unspecified timing;
private:
RCTJSTimerExecutor *_Null_unspecified jsTimerExecutor_;
double toSeconds(double ms);
};

View File

@@ -0,0 +1,73 @@
/*
* 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.
*/
#import "ObjCTimerRegistry.h"
@implementation RCTJSTimerExecutor {
std::weak_ptr<facebook::react::TimerManager> _timerManager;
}
- (void)setTimerManager:(std::weak_ptr<facebook::react::TimerManager>)timerManager
{
_timerManager = timerManager;
}
- (void)callTimers:(NSArray<NSNumber *> *)timers
{
if (auto timerManager = _timerManager.lock()) {
for (NSNumber *timer in timers) {
timerManager->callTimer([timer unsignedIntValue]);
}
}
}
- (void)immediatelyCallTimer:(nonnull NSNumber *)callbackID
{
if (auto timerManager = _timerManager.lock()) {
timerManager->callTimer([callbackID unsignedIntValue]);
}
}
- (void)callIdleCallbacks:(nonnull NSNumber *)absoluteFrameStartMS
{
// TODO(T53992765)(petetheheat) - Implement this
}
@end
ObjCTimerRegistry::ObjCTimerRegistry()
{
jsTimerExecutor_ = [RCTJSTimerExecutor new];
timing = [[RCTTiming alloc] initWithDelegate:jsTimerExecutor_];
}
void ObjCTimerRegistry::createTimer(uint32_t timerID, double delayMS)
{
[timing createTimerForNextFrame:@(timerID) duration:toSeconds(delayMS) jsSchedulingTime:nil repeats:NO];
}
void ObjCTimerRegistry::deleteTimer(uint32_t timerID)
{
[timing deleteTimer:(double)timerID];
}
void ObjCTimerRegistry::createRecurringTimer(uint32_t timerID, double delayMS)
{
[timing createTimerForNextFrame:@(timerID) duration:toSeconds(delayMS) jsSchedulingTime:nil repeats:YES];
}
void ObjCTimerRegistry::setTimerManager(std::weak_ptr<facebook::react::TimerManager> timerManager)
{
[jsTimerExecutor_ setTimerManager:timerManager];
}
// ObjC timing native module expects a NSTimeInterval which is always specified in seconds. JS expresses timer delay
// in ms. Perform a simple conversion here.
double ObjCTimerRegistry::toSeconds(double ms)
{
return ms / 1000.0;
}

View File

@@ -0,0 +1,16 @@
/*
* 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.
*/
#import <Foundation/Foundation.h>
#import <react/utils/ContextContainer.h>
@protocol RCTContextContainerHandling <NSObject>
- (void)didCreateContextContainer:(std::shared_ptr<facebook::react::ContextContainer>)contextContainer;
@end

View File

@@ -0,0 +1,40 @@
/*
* 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.
*/
#import <hermes/Public/CrashManager.h>
#import <jsi/jsi.h>
#import <react/runtime/JSRuntimeFactory.h>
namespace facebook::react {
class MessageQueueThread;
using CrashManagerProvider = std::function<std::shared_ptr<::hermes::vm::CrashManager>()>;
// ObjC++ wrapper for HermesInstance.cpp
class RCTHermesInstance : public JSRuntimeFactory {
public:
RCTHermesInstance() : RCTHermesInstance(nullptr, false) {}
RCTHermesInstance(CrashManagerProvider crashManagerProvider)
: RCTHermesInstance(std::move(crashManagerProvider), false)
{
}
RCTHermesInstance(CrashManagerProvider crashManagerProvider, bool allocInOldGenBeforeTTI)
: _crashManagerProvider(std::move(crashManagerProvider)), _allocInOldGenBeforeTTI(allocInOldGenBeforeTTI)
{
}
std::unique_ptr<JSRuntime> createJSRuntime(std::shared_ptr<MessageQueueThread> msgQueueThread) noexcept override;
~RCTHermesInstance() override {};
private:
CrashManagerProvider _crashManagerProvider;
bool _allocInOldGenBeforeTTI;
};
} // namespace facebook::react

View File

@@ -0,0 +1,21 @@
/*
* 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.
*/
#import "RCTHermesInstance.h"
#import <react/runtime/hermes/HermesInstance.h>
namespace facebook::react {
std::unique_ptr<JSRuntime> RCTHermesInstance::createJSRuntime(
std::shared_ptr<MessageQueueThread> msgQueueThread) noexcept
{
return HermesInstance::createJSRuntime(
_crashManagerProvider ? _crashManagerProvider() : nullptr, std::move(msgQueueThread), _allocInOldGenBeforeTTI);
}
} // namespace facebook::react

View File

@@ -0,0 +1,21 @@
/*
* 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.
*/
#import "RCTHost.h"
#import "RCTContextContainerHandling.h"
@interface RCTHost (Internal)
- (void)registerSegmentWithId:(NSNumber *)segmentId path:(NSString *)path;
- (void)setBundleURLProvider:(RCTHostBundleURLProvider)bundleURLProvider;
- (void)setContextContainerHandler:(id<RCTContextContainerHandling>)contextContainerHandler;
- (void)reload;
@property (nonatomic, readonly) RCTBundleManager *bundleManager;
@end

View File

@@ -0,0 +1,106 @@
/*
* 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.
*/
#import <Foundation/Foundation.h>
#import <React/RCTDefines.h>
#import <react/renderer/core/ReactPrimitives.h>
#import <react/runtime/JSRuntimeFactory.h>
#import "RCTInstance.h"
NS_ASSUME_NONNULL_BEGIN
@class RCTFabricSurface;
@class RCTHost;
@class RCTModuleRegistry;
@class RCTDevMenuConfiguration;
@protocol RCTTurboModuleManagerDelegate;
typedef NSURL *_Nullable (^RCTHostBundleURLProvider)(void);
// Runtime API
@protocol RCTHostDelegate <NSObject>
- (void)hostDidStart:(RCTHost *)host;
@optional
- (NSArray<NSString *> *)unstableModulesRequiringMainQueueSetup;
- (void)loadBundleAtURL:(NSURL *)sourceURL
onProgress:(RCTSourceLoadProgressBlock)onProgress
onComplete:(RCTSourceLoadBlock)loadCallback;
// TODO(T205780509): Remove this api in react native v0.78
// The bridgeless js error handling api will just call into exceptionsmanager directly
- (void)host:(RCTHost *)host
didReceiveJSErrorStack:(NSArray<NSDictionary<NSString *, id> *> *)stack
message:(NSString *)message
originalMessage:(NSString *_Nullable)originalMessage
name:(NSString *_Nullable)name
componentStack:(NSString *_Nullable)componentStack
exceptionId:(NSUInteger)exceptionId
isFatal:(BOOL)isFatal
extraData:(NSDictionary<NSString *, id> *)extraData __attribute__((deprecated));
@end
@protocol RCTHostRuntimeDelegate <NSObject>
- (void)host:(RCTHost *)host didInitializeRuntime:(facebook::jsi::Runtime &)runtime;
@end
typedef std::shared_ptr<facebook::react::JSRuntimeFactory> (^RCTHostJSEngineProvider)(void);
@interface RCTHost : NSObject
- (instancetype)initWithBundleURLProvider:(RCTHostBundleURLProvider)provider
hostDelegate:(id<RCTHostDelegate>)hostDelegate
turboModuleManagerDelegate:(id<RCTTurboModuleManagerDelegate>)turboModuleManagerDelegate
jsEngineProvider:(RCTHostJSEngineProvider)jsEngineProvider
launchOptions:(nullable NSDictionary *)launchOptions
devMenuConfiguration:(RCTDevMenuConfiguration *__nullable)devMenuConfiguration
NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithBundleURLProvider:(RCTHostBundleURLProvider)provider
hostDelegate:(id<RCTHostDelegate>)hostDelegate
turboModuleManagerDelegate:(id<RCTTurboModuleManagerDelegate>)turboModuleManagerDelegate
jsEngineProvider:(RCTHostJSEngineProvider)jsEngineProvider
launchOptions:(nullable NSDictionary *)launchOptions;
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
hostDelegate:(id<RCTHostDelegate>)hostDelegate
turboModuleManagerDelegate:(id<RCTTurboModuleManagerDelegate>)turboModuleManagerDelegate
jsEngineProvider:(RCTHostJSEngineProvider)jsEngineProvider
launchOptions:(nullable NSDictionary *)launchOptions __deprecated;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
@property (nonatomic, weak, nullable) id<RCTHostRuntimeDelegate> runtimeDelegate;
@property (nonatomic, readonly) RCTSurfacePresenter *surfacePresenter;
@property (nonatomic, readonly) RCTModuleRegistry *moduleRegistry;
- (void)start;
- (void)callFunctionOnJSModule:(NSString *)moduleName method:(NSString *)method args:(NSArray *)args;
// Renderer API
- (RCTFabricSurface *)createSurfaceWithModuleName:(NSString *)moduleName
mode:(facebook::react::DisplayMode)displayMode
initialProperties:(NSDictionary *)properties;
- (RCTFabricSurface *)createSurfaceWithModuleName:(NSString *)moduleName initialProperties:(NSDictionary *)properties;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,490 @@
/*
* 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.
*/
#import "RCTHost.h"
#import "RCTHost+Internal.h"
#import <React/RCTAssert.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTConvert.h>
#import <React/RCTDevMenu.h>
#import <React/RCTFabricSurface.h>
#import <React/RCTInspectorDevServerHelper.h>
#import <React/RCTInspectorNetworkHelper.h>
#import <React/RCTInspectorUtils.h>
#import <React/RCTJSThread.h>
#import <React/RCTLog.h>
#import <React/RCTMockDef.h>
#import <React/RCTPausedInDebuggerOverlayController.h>
#import <React/RCTPerformanceLogger.h>
#import <React/RCTReloadCommand.h>
#import <React/RCTUtils.h>
#import <jsinspector-modern/InspectorFlags.h>
#import <jsinspector-modern/InspectorInterfaces.h>
#import <jsinspector-modern/ReactCdp.h>
#import <optional>
RCT_MOCK_DEF(RCTHost, _RCTLogNativeInternal);
#define _RCTLogNativeInternal RCT_MOCK_USE(RCTHost, _RCTLogNativeInternal)
using namespace facebook::react;
@interface RCTHost () <RCTReloadListener, RCTInstanceDelegate>
@property (nonatomic, readonly) jsinspector_modern::HostTarget *inspectorTarget;
@end
class RCTHostHostTargetDelegate : public facebook::react::jsinspector_modern::HostTargetDelegate {
public:
RCTHostHostTargetDelegate(RCTHost *host)
: host_(host),
pauseOverlayController_([[RCTPausedInDebuggerOverlayController alloc] init]),
networkHelper_([[RCTInspectorNetworkHelper alloc] init])
{
}
jsinspector_modern::HostTargetMetadata getMetadata() override
{
auto metadata = [RCTInspectorUtils getHostMetadata];
#if TARGET_OS_IPHONE
NSString *osName = [[UIDevice currentDevice] systemName];
#else
NSString *osName = @"macOS";
#endif
return {
.appDisplayName = [metadata.appDisplayName UTF8String],
.appIdentifier = [metadata.appIdentifier UTF8String],
.deviceName = [metadata.deviceName UTF8String],
.integrationName = [[NSString stringWithFormat:@"%@ Bridgeless (RCTHost)", osName] UTF8String],
.platform = [metadata.platform UTF8String],
.reactNativeVersion = [metadata.reactNativeVersion UTF8String],
};
}
void onReload(const PageReloadRequest &request) override
{
RCTAssertMainQueue();
[static_cast<id<RCTReloadListener>>(host_) didReceiveReloadCommand];
}
void onSetPausedInDebuggerMessage(const OverlaySetPausedInDebuggerMessageRequest &request) override
{
RCTAssertMainQueue();
if (!request.message.has_value()) {
[pauseOverlayController_ hide];
} else {
__weak RCTHost *hostWeak = host_;
[pauseOverlayController_
showWithMessage:@(request.message.value().c_str())
onResume:^{
RCTAssertMainQueue();
RCTHost *hostStrong = hostWeak;
if (!hostStrong) {
return;
}
if (!hostStrong.inspectorTarget) {
return;
}
hostStrong.inspectorTarget->sendCommand(jsinspector_modern::HostCommand::DebuggerResume);
}];
}
}
void loadNetworkResource(const RCTInspectorLoadNetworkResourceRequest &params, RCTInspectorNetworkExecutor executor)
override
{
[networkHelper_ loadNetworkResourceWithParams:params executor:executor];
}
private:
__weak RCTHost *host_;
RCTPausedInDebuggerOverlayController *pauseOverlayController_;
RCTInspectorNetworkHelper *networkHelper_;
};
@implementation RCTHost {
RCTInstance *_instance;
__weak id<RCTHostDelegate> _hostDelegate;
__weak id<RCTTurboModuleManagerDelegate> _turboModuleManagerDelegate;
__weak id<RCTContextContainerHandling> _contextContainerHandler;
NSURL *_oldDelegateBundleURL;
NSURL *_bundleURL;
RCTBundleManager *_bundleManager;
RCTHostBundleURLProvider _bundleURLProvider;
RCTHostJSEngineProvider _jsEngineProvider;
NSDictionary *_launchOptions;
std::vector<__weak RCTFabricSurface *> _attachedSurfaces;
RCTModuleRegistry *_moduleRegistry;
RCTDevMenuConfiguration *_devMenuConfiguration;
std::unique_ptr<RCTHostHostTargetDelegate> _inspectorHostDelegate;
std::shared_ptr<jsinspector_modern::HostTarget> _inspectorTarget;
std::optional<int> _inspectorPageId;
}
+ (void)initialize
{
_RCTInitializeJSThreadConstantInternal();
}
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
hostDelegate:(id<RCTHostDelegate>)hostDelegate
turboModuleManagerDelegate:(id<RCTTurboModuleManagerDelegate>)turboModuleManagerDelegate
jsEngineProvider:(RCTHostJSEngineProvider)jsEngineProvider
launchOptions:(nullable NSDictionary *)launchOptions
{
return [self
initWithBundleURLProvider:^{
return bundleURL;
}
hostDelegate:hostDelegate
turboModuleManagerDelegate:turboModuleManagerDelegate
jsEngineProvider:jsEngineProvider
launchOptions:launchOptions];
}
/**
Host initialization should not be resource intensive. A host may be created before any intention of using React Native
has been expressed.
*/
- (instancetype)initWithBundleURLProvider:(RCTHostBundleURLProvider)provider
hostDelegate:(id<RCTHostDelegate>)hostDelegate
turboModuleManagerDelegate:(id<RCTTurboModuleManagerDelegate>)turboModuleManagerDelegate
jsEngineProvider:(RCTHostJSEngineProvider)jsEngineProvider
launchOptions:(nullable NSDictionary *)launchOptions
{
return [self initWithBundleURLProvider:provider
hostDelegate:hostDelegate
turboModuleManagerDelegate:turboModuleManagerDelegate
jsEngineProvider:jsEngineProvider
launchOptions:launchOptions
devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]];
}
- (instancetype)initWithBundleURLProvider:(RCTHostBundleURLProvider)provider
hostDelegate:(id<RCTHostDelegate>)hostDelegate
turboModuleManagerDelegate:(id<RCTTurboModuleManagerDelegate>)turboModuleManagerDelegate
jsEngineProvider:(RCTHostJSEngineProvider)jsEngineProvider
launchOptions:(nullable NSDictionary *)launchOptions
devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration
{
if (self = [super init]) {
_hostDelegate = hostDelegate;
_turboModuleManagerDelegate = turboModuleManagerDelegate;
_bundleManager = [RCTBundleManager new];
_moduleRegistry = [RCTModuleRegistry new];
_jsEngineProvider = [jsEngineProvider copy];
_launchOptions = [launchOptions copy];
__weak RCTHost *weakSelf = self;
auto bundleURLGetter = ^NSURL *() {
RCTHost *strongSelf = weakSelf;
if (!strongSelf) {
return nil;
}
return strongSelf->_bundleURL;
};
auto bundleURLSetter = ^(NSURL *bundleURL_) {
[weakSelf _setBundleURL:bundleURL_];
};
auto defaultBundleURLGetter = ^NSURL *() {
RCTHost *strongSelf = weakSelf;
if (!strongSelf || !strongSelf->_bundleURLProvider) {
return nil;
}
return strongSelf->_bundleURLProvider();
};
[_bundleManager setBridgelessBundleURLGetter:bundleURLGetter
andSetter:bundleURLSetter
andDefaultGetter:defaultBundleURLGetter];
RCTExecuteOnMainQueue(^{
// Listen to reload commands
RCTRegisterReloadCommandListener(self);
});
_inspectorHostDelegate = std::make_unique<RCTHostHostTargetDelegate>(self);
_devMenuConfiguration = devMenuConfiguration;
}
return self;
}
#pragma mark - Public
- (void)start
{
if (_bundleURLProvider) {
[self _setBundleURL:_bundleURLProvider()];
}
auto &inspectorFlags = jsinspector_modern::InspectorFlags::getInstance();
if (inspectorFlags.getFuseboxEnabled() && !_inspectorPageId.has_value()) {
_inspectorTarget =
facebook::react::jsinspector_modern::HostTarget::create(*_inspectorHostDelegate, [](auto callback) {
RCTExecuteOnMainQueue(^{
callback();
});
});
__weak RCTHost *weakSelf = self;
_inspectorPageId = facebook::react::jsinspector_modern::getInspectorInstance().addPage(
"React Native Bridgeless",
/* vm */ "",
[weakSelf](std::unique_ptr<facebook::react::jsinspector_modern::IRemoteConnection> remote)
-> std::unique_ptr<facebook::react::jsinspector_modern::ILocalConnection> {
RCTHost *strongSelf = weakSelf;
if (!strongSelf) {
// This can happen if we're about to be dealloc'd. Reject the connection.
return nullptr;
}
return strongSelf->_inspectorTarget->connect(std::move(remote));
},
{.nativePageReloads = true, .prefersFuseboxFrontend = true});
}
if (_instance) {
RCTLogWarn(
@"RCTHost should not be creating a new instance if one already exists. This implies there is a bug with how/when this method is being called.");
[_instance invalidate];
}
_instance = [[RCTInstance alloc] initWithDelegate:self
jsRuntimeFactory:[self _provideJSEngine]
bundleManager:_bundleManager
turboModuleManagerDelegate:_turboModuleManagerDelegate
moduleRegistry:_moduleRegistry
parentInspectorTarget:_inspectorTarget.get()
launchOptions:_launchOptions
devMenuConfiguration:_devMenuConfiguration];
[_hostDelegate hostDidStart:self];
}
- (RCTFabricSurface *)createSurfaceWithModuleName:(NSString *)moduleName
mode:(DisplayMode)displayMode
initialProperties:(NSDictionary *)properties
{
RCTFabricSurface *surface = [[RCTFabricSurface alloc] initWithSurfacePresenter:self.surfacePresenter
moduleName:moduleName
initialProperties:properties];
surface.surfaceHandler.setDisplayMode(displayMode);
[self _attachSurface:surface];
__weak RCTFabricSurface *weakSurface = surface;
// Use the BufferedRuntimeExecutor to start the surface after the main JS bundle was fully executed.
[_instance callFunctionOnBufferedRuntimeExecutor:[weakSurface](facebook::jsi::Runtime &_) { [weakSurface start]; }];
return surface;
}
- (RCTFabricSurface *)createSurfaceWithModuleName:(NSString *)moduleName initialProperties:(NSDictionary *)properties
{
return [self createSurfaceWithModuleName:moduleName mode:DisplayMode::Visible initialProperties:properties];
}
- (RCTModuleRegistry *)moduleRegistry
{
return _moduleRegistry;
}
- (RCTSurfacePresenter *)surfacePresenter
{
return [_instance surfacePresenter];
}
- (RCTBundleManager *)bundleManager
{
return _bundleManager;
}
- (void)callFunctionOnJSModule:(NSString *)moduleName method:(NSString *)method args:(NSArray *)args
{
[_instance callFunctionOnJSModule:moduleName method:method args:args];
}
#pragma mark - RCTReloadListener
- (void)didReceiveReloadCommand
{
[self _reloadWithShouldRestartSurfaces:YES];
}
- (void)dealloc
{
[_instance invalidate];
if (_inspectorPageId.has_value()) {
facebook::react::jsinspector_modern::getInspectorInstance().removePage(*_inspectorPageId);
_inspectorPageId.reset();
_inspectorTarget.reset();
}
}
#pragma mark - RCTInstanceDelegate
- (NSArray<NSString *> *)unstableModulesRequiringMainQueueSetup
{
if ([_hostDelegate respondsToSelector:@selector(unstableModulesRequiringMainQueueSetup)]) {
return [_hostDelegate unstableModulesRequiringMainQueueSetup];
}
return @[];
}
- (BOOL)instance:(RCTInstance *)instance
didReceiveJSErrorStack:(NSArray<NSDictionary<NSString *, id> *> *)stack
message:(NSString *)message
originalMessage:(NSString *_Nullable)originalMessage
name:(NSString *_Nullable)name
componentStack:(NSString *_Nullable)componentStack
exceptionId:(NSUInteger)exceptionId
isFatal:(BOOL)isFatal
extraData:(NSDictionary<NSString *, id> *)extraData
{
if (![_hostDelegate respondsToSelector:@selector(host:
didReceiveJSErrorStack:message:originalMessage:name:componentStack
:exceptionId:isFatal:extraData:)]) {
return NO;
}
[_hostDelegate host:self
didReceiveJSErrorStack:stack
message:message
originalMessage:originalMessage
name:name
componentStack:componentStack
exceptionId:exceptionId
isFatal:isFatal
extraData:extraData];
return YES;
}
- (void)instance:(RCTInstance *)instance didInitializeRuntime:(facebook::jsi::Runtime &)runtime
{
[self.runtimeDelegate host:self didInitializeRuntime:runtime];
}
- (void)loadBundleAtURL:(NSURL *)sourceURL
onProgress:(RCTSourceLoadProgressBlock)onProgress
onComplete:(RCTSourceLoadBlock)loadCallback
{
if ([_hostDelegate respondsToSelector:@selector(loadBundleAtURL:onProgress:onComplete:)]) {
[_hostDelegate loadBundleAtURL:sourceURL onProgress:onProgress onComplete:loadCallback];
} else {
[RCTJavaScriptLoader loadBundleAtURL:sourceURL onProgress:onProgress onComplete:loadCallback];
}
}
#pragma mark - RCTContextContainerHandling
- (void)didCreateContextContainer:(std::shared_ptr<facebook::react::ContextContainer>)contextContainer
{
[_contextContainerHandler didCreateContextContainer:contextContainer];
}
#pragma mark - Internal
- (void)registerSegmentWithId:(NSNumber *)segmentId path:(NSString *)path
{
[_instance registerSegmentWithId:segmentId path:path];
}
- (void)setBundleURLProvider:(RCTHostBundleURLProvider)bundleURLProvider
{
_bundleURLProvider = [bundleURLProvider copy];
}
- (void)setContextContainerHandler:(id<RCTContextContainerHandling>)contextContainerHandler
{
_contextContainerHandler = contextContainerHandler;
}
- (void)reload
{
[self _reloadWithShouldRestartSurfaces:NO];
}
#pragma mark - Private
- (void)_attachSurface:(RCTFabricSurface *)surface
{
_attachedSurfaces.push_back(surface);
}
- (NSArray<RCTFabricSurface *> *)_getAttachedSurfaces
{
NSMutableArray<RCTFabricSurface *> *surfaces = [NSMutableArray new];
for (RCTFabricSurface *surface : _attachedSurfaces) {
if (surface) {
[surfaces addObject:surface];
}
}
return surfaces;
}
- (std::shared_ptr<facebook::react::JSRuntimeFactory>)_provideJSEngine
{
RCTAssert(_jsEngineProvider, @"_jsEngineProvider must be non-nil");
std::shared_ptr<facebook::react::JSRuntimeFactory> jsEngine = _jsEngineProvider();
RCTAssert(jsEngine != nullptr, @"_jsEngineProvider must return a nonnull pointer");
return jsEngine;
}
- (void)_setBundleURL:(NSURL *)bundleURL
{
// Reset the _bundleURL ivar if the RCTHost delegate presents a new bundleURL
NSURL *newDelegateBundleURL = bundleURL;
if (newDelegateBundleURL && ![newDelegateBundleURL isEqual:_oldDelegateBundleURL]) {
_oldDelegateBundleURL = newDelegateBundleURL;
_bundleURL = newDelegateBundleURL;
}
// Sanitize the bundle URL
_bundleURL = [RCTConvert NSURL:_bundleURL.absoluteString];
// Update the global bundle URLq
RCTReloadCommandSetBundleURL(_bundleURL);
}
- (void)_reloadWithShouldRestartSurfaces:(BOOL)shouldRestartSurfaces
{
[_instance invalidate];
_instance = nil;
if (_bundleURLProvider) {
[self _setBundleURL:_bundleURLProvider()];
}
_instance = [[RCTInstance alloc] initWithDelegate:self
jsRuntimeFactory:[self _provideJSEngine]
bundleManager:_bundleManager
turboModuleManagerDelegate:_turboModuleManagerDelegate
moduleRegistry:_moduleRegistry
parentInspectorTarget:_inspectorTarget.get()
launchOptions:_launchOptions
devMenuConfiguration:_devMenuConfiguration];
[_hostDelegate hostDidStart:self];
for (RCTFabricSurface *surface in [self _getAttachedSurfaces]) {
[surface resetWithSurfacePresenter:self.surfacePresenter];
if (shouldRestartSurfaces) {
[_instance callFunctionOnBufferedRuntimeExecutor:[surface](facebook::jsi::Runtime &_) { [surface start]; }];
}
}
}
#pragma mark - jsinspector_modern
- (jsinspector_modern::HostTarget *)inspectorTarget
{
return _inspectorTarget.get();
}
@end

View File

@@ -0,0 +1,97 @@
/*
* 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.
*/
#import <UIKit/UIKit.h>
#import <React/RCTDefines.h>
#import <React/RCTJavaScriptLoader.h>
#import <jsinspector-modern/ReactCdp.h>
#import <react/runtime/JSRuntimeFactory.h>
#import <react/runtime/ReactInstance.h>
#import "RCTContextContainerHandling.h"
NS_ASSUME_NONNULL_BEGIN
/**
* A utility to enable diagnostics mode at runtime. Useful for test runs.
* The flags are comma-separated string tokens, or an empty string when
* nothing is enabled.
*/
RCT_EXTERN NSString *RCTInstanceRuntimeDiagnosticFlags(void);
RCT_EXTERN void RCTInstanceSetRuntimeDiagnosticFlags(NSString *_Nullable flags);
@class RCTBundleManager;
@class RCTInstance;
@class RCTJSThreadManager;
@class RCTModuleRegistry;
@class RCTPerformanceLogger;
@class RCTSource;
@class RCTSurfacePresenter;
@class RCTDevMenuConfiguration;
@protocol RCTTurboModuleManagerDelegate;
@protocol RCTInstanceDelegate <RCTContextContainerHandling>
- (void)instance:(RCTInstance *)instance didInitializeRuntime:(facebook::jsi::Runtime &)runtime;
- (void)loadBundleAtURL:(NSURL *)sourceURL
onProgress:(RCTSourceLoadProgressBlock)onProgress
onComplete:(RCTSourceLoadBlock)loadCallback;
- (NSArray<NSString *> *)unstableModulesRequiringMainQueueSetup;
// TODO(T205780509): Remove this api in react native v0.78
// The bridgeless js error handling api will just call into exceptionsmanager directly
- (BOOL)instance:(RCTInstance *)instance
didReceiveJSErrorStack:(NSArray<NSDictionary<NSString *, id> *> *)stack
message:(NSString *)message
originalMessage:(NSString *_Nullable)originalMessage
name:(NSString *_Nullable)name
componentStack:(NSString *_Nullable)componentStack
exceptionId:(NSUInteger)exceptionId
isFatal:(BOOL)isFatal
extraData:(NSDictionary<NSString *, id> *)extraData __attribute__((deprecated));
@end
/**
* RCTInstance owns and manages most of the pieces of infrastructure required to display a screen powered by React
* Native. RCTInstance should never be instantiated in product code, but rather accessed through RCTHost. The host
* ensures that any access to the instance is safe, and manages instance lifecycle.
*/
@interface RCTInstance : NSObject
- (instancetype)initWithDelegate:(id<RCTInstanceDelegate>)delegate
jsRuntimeFactory:(std::shared_ptr<facebook::react::JSRuntimeFactory>)jsRuntimeFactory
bundleManager:(RCTBundleManager *)bundleManager
turboModuleManagerDelegate:(id<RCTTurboModuleManagerDelegate>)turboModuleManagerDelegate
moduleRegistry:(RCTModuleRegistry *)moduleRegistry
parentInspectorTarget:(facebook::react::jsinspector_modern::HostTarget *)parentInspectorTarget
launchOptions:(nullable NSDictionary *)launchOptions;
- (instancetype)initWithDelegate:(id<RCTInstanceDelegate>)delegate
jsRuntimeFactory:(std::shared_ptr<facebook::react::JSRuntimeFactory>)jsRuntimeFactory
bundleManager:(RCTBundleManager *)bundleManager
turboModuleManagerDelegate:(id<RCTTurboModuleManagerDelegate>)turboModuleManagerDelegate
moduleRegistry:(RCTModuleRegistry *)moduleRegistry
parentInspectorTarget:(facebook::react::jsinspector_modern::HostTarget *)parentInspectorTarget
launchOptions:(nullable NSDictionary *)launchOptions
devMenuConfiguration:(RCTDevMenuConfiguration *__nullable)devMenuConfiguration;
- (void)callFunctionOnJSModule:(NSString *)moduleName method:(NSString *)method args:(NSArray *)args;
- (void)callFunctionOnBufferedRuntimeExecutor:(std::function<void(facebook::jsi::Runtime &runtime)> &&)executor;
- (void)registerSegmentWithId:(NSNumber *)segmentId path:(NSString *)path;
- (void)invalidate;
@property (nonatomic, readonly, strong) RCTSurfacePresenter *surfacePresenter;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,682 @@
/*
* 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.
*/
#import "RCTInstance.h"
#import <memory>
#import <FBReactNativeSpec/FBReactNativeSpec.h>
#import <React/NSDataBigString.h>
#import <React/RCTAssert.h>
#import <React/RCTBridge+Inspector.h>
#import <React/RCTBridge+Private.h>
#import <React/RCTBridge.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTBridgeModuleDecorator.h>
#import <React/RCTBridgeProxy+Cxx.h>
#import <React/RCTBridgeProxy.h>
#import <React/RCTComponentViewFactory.h>
#import <React/RCTConstants.h>
#import <React/RCTCxxUtils.h>
#import <React/RCTDevMenu.h>
#import <React/RCTDevMenuConfigurationDecorator.h>
#import <React/RCTDevSettings.h>
#import <React/RCTDisplayLink.h>
#import <React/RCTEventDispatcherProtocol.h>
#import <React/RCTLog.h>
#import <React/RCTLogBox.h>
#import <React/RCTModuleData.h>
#import <React/RCTPerformanceLogger.h>
#import <React/RCTRedBox.h>
#import <React/RCTSurfacePresenter.h>
#import <ReactCommon/RCTTurboModule.h>
#import <ReactCommon/RCTTurboModuleManager.h>
#import <ReactCommon/RuntimeExecutor.h>
#import <cxxreact/ReactMarker.h>
#import <jsinspector-modern/InspectorFlags.h>
#import <jsinspector-modern/ReactCdp.h>
#import <jsireact/JSIExecutor.h>
#import <react/featureflags/ReactNativeFeatureFlags.h>
#import <react/renderer/runtimescheduler/RuntimeSchedulerCallInvoker.h>
#import <react/utils/ContextContainer.h>
#import <react/utils/FollyConvert.h>
#import <react/utils/ManagedObjectWrapper.h>
#import "ObjCTimerRegistry.h"
#import "RCTJSThreadManager.h"
#import "RCTLegacyUIManagerConstantsProvider.h"
#import "RCTPerformanceLoggerUtils.h"
#if RCT_DEV_MENU && __has_include(<React/RCTDevLoadingViewProtocol.h>)
#import <React/RCTDevLoadingViewProtocol.h>
#endif
using namespace facebook;
using namespace facebook::react;
static NSString *sRuntimeDiagnosticFlags = nil;
NSString *RCTInstanceRuntimeDiagnosticFlags(void)
{
return sRuntimeDiagnosticFlags ? [sRuntimeDiagnosticFlags copy] : [NSString new];
}
void RCTInstanceSetRuntimeDiagnosticFlags(NSString *flags)
{
if (!flags) {
return;
}
sRuntimeDiagnosticFlags = [flags copy];
}
@interface RCTBridgelessDisplayLinkModuleHolder : NSObject <RCTDisplayLinkModuleHolder>
- (instancetype)initWithModule:(id<RCTBridgeModule>)module;
@end
@implementation RCTBridgelessDisplayLinkModuleHolder {
id<RCTBridgeModule> _module;
}
- (instancetype)initWithModule:(id<RCTBridgeModule>)module
{
_module = module;
return self;
}
- (id<RCTBridgeModule>)instance
{
return _module;
}
- (Class)moduleClass
{
return [_module class];
}
- (dispatch_queue_t)methodQueue
{
return _module.methodQueue;
}
@end
@interface RCTInstance () <RCTTurboModuleManagerDelegate>
@end
@implementation RCTInstance {
std::unique_ptr<ReactInstance> _reactInstance;
std::shared_ptr<JSRuntimeFactory> _jsRuntimeFactory;
__weak id<RCTTurboModuleManagerDelegate> _appTMMDelegate;
__weak id<RCTInstanceDelegate> _delegate;
RCTSurfacePresenter *_surfacePresenter;
RCTPerformanceLogger *_performanceLogger;
RCTDisplayLink *_displayLink;
RCTTurboModuleManager *_turboModuleManager;
std::mutex _invalidationMutex;
std::atomic<bool> _valid;
RCTJSThreadManager *_jsThreadManager;
NSDictionary *_launchOptions;
void (^_waitUntilModuleSetupComplete)();
// APIs supporting interop with native modules and view managers
RCTBridgeModuleDecorator *_bridgeModuleDecorator;
RCTDevMenuConfigurationDecorator *_devMenuConfigurationDecorator;
jsinspector_modern::HostTarget *_parentInspectorTarget;
}
#pragma mark - Public
- (instancetype)initWithDelegate:(id<RCTInstanceDelegate>)delegate
jsRuntimeFactory:(std::shared_ptr<facebook::react::JSRuntimeFactory>)jsRuntimeFactory
bundleManager:(RCTBundleManager *)bundleManager
turboModuleManagerDelegate:(id<RCTTurboModuleManagerDelegate>)tmmDelegate
moduleRegistry:(RCTModuleRegistry *)moduleRegistry
parentInspectorTarget:(jsinspector_modern::HostTarget *)parentInspectorTarget
launchOptions:(nullable NSDictionary *)launchOptions
{
return [self initWithDelegate:delegate
jsRuntimeFactory:jsRuntimeFactory
bundleManager:bundleManager
turboModuleManagerDelegate:tmmDelegate
moduleRegistry:moduleRegistry
parentInspectorTarget:parentInspectorTarget
launchOptions:launchOptions
devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]];
}
- (instancetype)initWithDelegate:(id<RCTInstanceDelegate>)delegate
jsRuntimeFactory:(std::shared_ptr<facebook::react::JSRuntimeFactory>)jsRuntimeFactory
bundleManager:(RCTBundleManager *)bundleManager
turboModuleManagerDelegate:(id<RCTTurboModuleManagerDelegate>)tmmDelegate
moduleRegistry:(RCTModuleRegistry *)moduleRegistry
parentInspectorTarget:(jsinspector_modern::HostTarget *)parentInspectorTarget
launchOptions:(nullable NSDictionary *)launchOptions
devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration
{
if (self = [super init]) {
_performanceLogger = [RCTPerformanceLogger new];
registerPerformanceLoggerHooks(_performanceLogger);
[_performanceLogger markStartForTag:RCTPLReactInstanceInit];
_delegate = delegate;
_jsRuntimeFactory = jsRuntimeFactory;
_appTMMDelegate = tmmDelegate;
_jsThreadManager = [RCTJSThreadManager new];
_bridgeModuleDecorator = [[RCTBridgeModuleDecorator alloc] initWithViewRegistry:[RCTViewRegistry new]
moduleRegistry:moduleRegistry
bundleManager:bundleManager
callableJSModules:[RCTCallableJSModules new]];
_devMenuConfigurationDecorator =
#if RCT_DEV_MENU
[[RCTDevMenuConfigurationDecorator alloc] initWithDevMenuConfiguration:devMenuConfiguration];
#else
nil;
#endif
_parentInspectorTarget = parentInspectorTarget;
{
__weak __typeof(self) weakSelf = self;
[_bridgeModuleDecorator.callableJSModules
setBridgelessJSModuleMethodInvoker:^(
NSString *moduleName, NSString *methodName, NSArray *args, dispatch_block_t onComplete) {
[weakSelf callFunctionOnJSModule:moduleName method:methodName args:args];
if (onComplete) {
[weakSelf
callFunctionOnBufferedRuntimeExecutor:[onComplete](facebook::jsi::Runtime &_) { onComplete(); }];
}
}];
}
_launchOptions = launchOptions;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(_handleMemoryWarning)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
[self _start];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
}
- (void)callFunctionOnJSModule:(NSString *)moduleName method:(NSString *)method args:(NSArray *)args
{
if (_valid) {
_reactInstance->callFunctionOnModule(
[moduleName UTF8String], [method UTF8String], convertIdToFollyDynamic(args ? args : @[]));
}
}
- (void)invalidate
{
std::lock_guard<std::mutex> lock(_invalidationMutex);
_valid = false;
if (_reactInstance) {
_reactInstance->unregisterFromInspector();
}
[_surfacePresenter suspend];
[_jsThreadManager dispatchToJSThread:^{
/**
* Every TurboModule is invalidated on its own method queue.
* TurboModuleManager invalidate blocks the calling thread until all TurboModules are invalidated.
*/
[self->_turboModuleManager invalidate];
// Clean up all the Resources
self->_reactInstance = nullptr;
self->_jsRuntimeFactory = nullptr;
self->_appTMMDelegate = nil;
self->_delegate = nil;
[self->_displayLink invalidate];
self->_displayLink = nil;
self->_turboModuleManager = nil;
self->_performanceLogger = nil;
// Terminate the JavaScript thread, so that no other work executes after this block.
self->_jsThreadManager = nil;
}];
}
- (void)registerSegmentWithId:(NSNumber *)segmentId path:(NSString *)path
{
if (_valid) {
_reactInstance->registerSegment(static_cast<uint32_t>([segmentId unsignedIntValue]), path.UTF8String);
}
}
#pragma mark - RCTTurboModuleManagerDelegate
- (Class)getModuleClassFromName:(const char *)name
{
return [_appTMMDelegate getModuleClassFromName:name];
}
- (id<RCTTurboModule>)getModuleInstanceFromClass:(Class)moduleClass
{
id<RCTTurboModule> module = [_appTMMDelegate getModuleInstanceFromClass:moduleClass];
if (!module) {
module = [moduleClass new];
}
[self _attachBridgelessAPIsToModule:module];
return module;
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
{
if ([_appTMMDelegate respondsToSelector:@selector(getTurboModule:jsInvoker:)]) {
return [_appTMMDelegate getTurboModule:name jsInvoker:jsInvoker];
}
return nullptr;
}
- (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge
{
if ([_appTMMDelegate respondsToSelector:@selector(extraModulesForBridge:)]) {
return [_appTMMDelegate extraModulesForBridge:nil];
}
return @[];
}
- (nullable id<RCTModuleProvider>)getModuleProvider:(const char *)name
{
if ([_appTMMDelegate respondsToSelector:@selector(getModuleProvider:)]) {
return [_appTMMDelegate getModuleProvider:name];
}
return nil;
}
#pragma mark - Private
- (void)_start
{
// Set up timers
auto objCTimerRegistry = std::make_unique<ObjCTimerRegistry>();
auto timing = objCTimerRegistry->timing;
auto *objCTimerRegistryRawPtr = objCTimerRegistry.get();
auto timerManager = std::make_shared<TimerManager>(std::move(objCTimerRegistry));
objCTimerRegistryRawPtr->setTimerManager(timerManager);
__weak __typeof(self) weakSelf = self;
auto onJsError = [=](jsi::Runtime &runtime, const JsErrorHandler::ProcessedError &error) {
[weakSelf _handleJSError:error withRuntime:runtime];
};
// Create the React Instance
_reactInstance = std::make_unique<ReactInstance>(
_jsRuntimeFactory->createJSRuntime(_jsThreadManager.jsMessageThread),
_jsThreadManager.jsMessageThread,
timerManager,
onJsError,
_parentInspectorTarget);
_valid = true;
RuntimeExecutor bufferedRuntimeExecutor = _reactInstance->getBufferedRuntimeExecutor();
timerManager->setRuntimeExecutor(bufferedRuntimeExecutor);
auto jsCallInvoker = make_shared<RuntimeSchedulerCallInvoker>(_reactInstance->getRuntimeScheduler());
RCTBridgeProxy *bridgeProxy =
[[RCTBridgeProxy alloc] initWithViewRegistry:_bridgeModuleDecorator.viewRegistry_DEPRECATED
moduleRegistry:_bridgeModuleDecorator.moduleRegistry
bundleManager:_bridgeModuleDecorator.bundleManager
callableJSModules:_bridgeModuleDecorator.callableJSModules
dispatchToJSThread:^(dispatch_block_t block) {
__strong __typeof(self) strongSelf = weakSelf;
if (strongSelf && strongSelf->_valid) {
strongSelf->_reactInstance->getBufferedRuntimeExecutor()([=](jsi::Runtime &runtime) { block(); });
}
}
registerSegmentWithId:^(NSNumber *segmentId, NSString *path) {
__strong __typeof(self) strongSelf = weakSelf;
if (strongSelf && strongSelf->_valid) {
[strongSelf registerSegmentWithId:segmentId path:path];
}
}
runtime:_reactInstance->getJavaScriptContext()
launchOptions:_launchOptions];
bridgeProxy.jsCallInvoker = jsCallInvoker;
[RCTBridge setCurrentBridge:(RCTBridge *)bridgeProxy];
// Set up TurboModules
_turboModuleManager = [[RCTTurboModuleManager alloc] initWithBridgeProxy:bridgeProxy
bridgeModuleDecorator:_bridgeModuleDecorator
delegate:self
jsInvoker:jsCallInvoker
devMenuConfigurationDecorator:_devMenuConfigurationDecorator];
#if RCT_DEV
/**
* Instantiating DevMenu has the side-effect of registering
* shortcuts for CMD + d, CMD + i, and CMD + n via RCTDevMenu.
* Therefore, when TurboModules are enabled, we must manually create this
* NativeModule.
*/
[_turboModuleManager moduleForName:"RCTDevMenu"];
#endif // end RCT_DEV
// Initialize RCTModuleRegistry so that TurboModules can require other TurboModules.
[_bridgeModuleDecorator.moduleRegistry setTurboModuleRegistry:_turboModuleManager];
if (ReactNativeFeatureFlags::enableEagerMainQueueModulesOnIOS()) {
/**
* Some native modules need to capture uikit objects on the main thread.
* Start initializing those modules on the main queue here. The JavaScript thread
* will wait until this module init finishes, before executing the js bundle.
*/
NSArray<NSString *> *modulesRequiringMainQueueSetup = [_delegate unstableModulesRequiringMainQueueSetup];
std::shared_ptr<std::mutex> mutex = std::make_shared<std::mutex>();
std::shared_ptr<std::condition_variable> cv = std::make_shared<std::condition_variable>();
std::shared_ptr<bool> isReady = std::make_shared<bool>(false);
_waitUntilModuleSetupComplete = ^{
std::unique_lock<std::mutex> lock(*mutex);
cv->wait(lock, [isReady] { return *isReady; });
};
// TODO(T218039767): Integrate perf logging into main queue module init
RCTExecuteOnMainQueue(^{
for (NSString *moduleName in modulesRequiringMainQueueSetup) {
[self->_bridgeModuleDecorator.moduleRegistry moduleForName:[moduleName UTF8String]];
}
RCTScreenSize();
RCTScreenScale();
RCTSwitchSize();
std::lock_guard<std::mutex> lock(*mutex);
*isReady = true;
cv->notify_all();
});
}
RCTLogSetBridgelessModuleRegistry(_bridgeModuleDecorator.moduleRegistry);
RCTLogSetBridgelessCallableJSModules(_bridgeModuleDecorator.callableJSModules);
auto contextContainer = std::make_shared<ContextContainer>();
[_delegate didCreateContextContainer:contextContainer];
contextContainer->insert(
"RCTImageLoader", facebook::react::wrapManagedObject([_turboModuleManager moduleForName:"RCTImageLoader"]));
contextContainer->insert(
"RCTEventDispatcher",
facebook::react::wrapManagedObject([_turboModuleManager moduleForName:"RCTEventDispatcher"]));
contextContainer->insert("RCTBridgeModuleDecorator", facebook::react::wrapManagedObject(_bridgeModuleDecorator));
contextContainer->insert(RuntimeSchedulerKey, std::weak_ptr<RuntimeScheduler>(_reactInstance->getRuntimeScheduler()));
contextContainer->insert("RCTBridgeProxy", facebook::react::wrapManagedObject(bridgeProxy));
_surfacePresenter = [[RCTSurfacePresenter alloc]
initWithContextContainer:contextContainer
runtimeExecutor:bufferedRuntimeExecutor
bridgelessBindingsExecutor:std::optional(_reactInstance->getUnbufferedRuntimeExecutor())];
// This enables RCTViewRegistry in modules to return UIViews from its viewForReactTag method
__weak RCTSurfacePresenter *weakSurfacePresenter = _surfacePresenter;
[_bridgeModuleDecorator.viewRegistry_DEPRECATED setBridgelessComponentViewProvider:^UIView *(NSNumber *reactTag) {
RCTSurfacePresenter *strongSurfacePresenter = weakSurfacePresenter;
if (strongSurfacePresenter == nil) {
return nil;
}
return [strongSurfacePresenter findComponentViewWithTag_DO_NOT_USE_DEPRECATED:reactTag.integerValue];
}];
// DisplayLink is used to call timer callbacks.
_displayLink = [RCTDisplayLink new];
auto &inspectorFlags = jsinspector_modern::InspectorFlags::getInstance();
ReactInstance::JSRuntimeFlags options = {
.isProfiling = inspectorFlags.getIsProfilingBuild(),
.runtimeDiagnosticFlags = [RCTInstanceRuntimeDiagnosticFlags() UTF8String]};
_reactInstance->initializeRuntime(options, [=](jsi::Runtime &runtime) {
__strong __typeof(self) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
[strongSelf->_turboModuleManager installJSBindings:runtime];
facebook::react::bindNativeLogger(runtime, [](const std::string &message, unsigned int logLevel) {
_RCTLogJavaScriptInternal(static_cast<RCTLogLevel>(logLevel), [NSString stringWithUTF8String:message.c_str()]);
});
RCTInstallNativeComponentRegistryBinding(runtime);
if (ReactNativeFeatureFlags::useNativeViewConfigsInBridgelessMode()) {
installLegacyUIManagerConstantsProviderBinding(runtime);
}
[strongSelf->_delegate instance:strongSelf didInitializeRuntime:runtime];
// Set up Display Link
id<RCTDisplayLinkModuleHolder> moduleHolder = [[RCTBridgelessDisplayLinkModuleHolder alloc] initWithModule:timing];
[strongSelf->_displayLink registerModuleForFrameUpdates:timing withModuleHolder:moduleHolder];
[strongSelf->_displayLink addToRunLoop:[NSRunLoop currentRunLoop]];
// Attempt to load bundle synchronously, fallback to asynchronously.
[strongSelf->_performanceLogger markStartForTag:RCTPLScriptDownload];
[strongSelf _loadJSBundle:[strongSelf->_bridgeModuleDecorator.bundleManager bundleURL]];
});
[_performanceLogger markStopForTag:RCTPLReactInstanceInit];
}
- (void)_attachBridgelessAPIsToModule:(id<RCTTurboModule>)module
{
__weak RCTInstance *weakSelf = self;
if ([module respondsToSelector:@selector(setDispatchToJSThread:)]) {
((id<RCTJSDispatcherModule>)module).dispatchToJSThread = ^(dispatch_block_t block) {
__strong __typeof(self) strongSelf = weakSelf;
if (strongSelf && strongSelf->_valid) {
strongSelf->_reactInstance->getBufferedRuntimeExecutor()([=](jsi::Runtime &runtime) { block(); });
}
};
}
if ([module respondsToSelector:@selector(setSurfacePresenter:)]) {
[module performSelector:@selector(setSurfacePresenter:) withObject:_surfacePresenter];
}
// Replaces bridge.isInspectable
if ([module respondsToSelector:@selector(setIsInspectable:)]) {
#if RCT_DEV_MENU
if (_valid) {
_reactInstance->getBufferedRuntimeExecutor()([module](jsi::Runtime &runtime) {
((id<RCTDevSettingsInspectable>)module).isInspectable = runtime.isInspectable();
});
}
#endif
}
}
- (void)callFunctionOnBufferedRuntimeExecutor:(std::function<void(facebook::jsi::Runtime &)> &&)executor
{
_reactInstance->getBufferedRuntimeExecutor()([=](jsi::Runtime &runtime) {
if (executor) {
executor(runtime);
}
});
}
- (void)handleBundleLoadingError:(NSError *)error
{
if (!_valid) {
return;
}
RCTRedBox *redBox = [_turboModuleManager moduleForName:"RedBox"];
RCTExecuteOnMainQueue(^{
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidFailToLoadNotification
object:self
userInfo:@{@"error" : error}];
[redBox showErrorMessage:[error localizedDescription]];
RCTFatal(error);
});
}
- (void)_loadJSBundle:(NSURL *)sourceURL
{
#if RCT_DEV_MENU && __has_include(<React/RCTDevLoadingViewProtocol.h>)
{
id<RCTDevLoadingViewProtocol> loadingView =
(id<RCTDevLoadingViewProtocol>)[_turboModuleManager moduleForName:"DevLoadingView"];
[loadingView showWithURL:sourceURL];
}
#endif
__weak __typeof(self) weakSelf = self;
[_delegate loadBundleAtURL:sourceURL
onProgress:^(RCTLoadingProgress *progressData) {
__typeof(self) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
#if RCT_DEV_MENU && __has_include(<React/RCTDevLoadingViewProtocol.h>)
id<RCTDevLoadingViewProtocol> loadingView =
(id<RCTDevLoadingViewProtocol>)[strongSelf->_turboModuleManager moduleForName:"DevLoadingView"];
[loadingView updateProgress:progressData];
#endif
}
onComplete:^(NSError *error, RCTSource *source) {
__typeof(self) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
if (error) {
[strongSelf handleBundleLoadingError:error];
return;
}
// DevSettings module is needed by _loadScriptFromSource's callback so prior initialization is required
RCTDevSettings *const devSettings =
(RCTDevSettings *)[strongSelf->_turboModuleManager moduleForName:"DevSettings"];
[strongSelf _loadScriptFromSource:source];
// Set up hot module reloading in Dev only.
[strongSelf->_performanceLogger markStopForTag:RCTPLScriptDownload];
[devSettings setupHMRClientWithBundleURL:sourceURL];
#if RCT_DEV
[strongSelf _logOldArchitectureWarnings];
#endif
}];
}
- (void)_logOldArchitectureWarnings
{
if (!RCTAreLegacyLogsEnabled()) {
return;
}
NSArray<NSString *> *modulesInOldArchMode = [getModulesLoadedWithOldArch() copy];
if (modulesInOldArchMode.count > 0) {
NSMutableString *moduleList = [NSMutableString new];
for (NSString *moduleName in modulesInOldArchMode) {
[moduleList appendFormat:@"- %@\n", moduleName];
}
RCTLogWarn(
@"The following modules have been registered using a RCT_EXPORT_MODULE. That's a Legacy Architecture API. Please migrate to the new approach as described in the https://reactnative.dev/docs/next/turbo-native-modules-introduction#register-the-native-module-in-your-app website or open a PR in the library repository:\n%@",
moduleList);
}
}
- (void)_loadScriptFromSource:(RCTSource *)source
{
std::lock_guard<std::mutex> lock(_invalidationMutex);
if (!_valid) {
return;
}
auto script = std::make_unique<NSDataBigString>(source.data);
const auto *url = deriveSourceURL(source.url).UTF8String;
auto beforeLoad = [waitUntilModuleSetupComplete = self->_waitUntilModuleSetupComplete](jsi::Runtime &_) {
if (waitUntilModuleSetupComplete) {
waitUntilModuleSetupComplete();
}
};
auto afterLoad = [](jsi::Runtime &_) {
[[NSNotificationCenter defaultCenter] postNotificationName:@"RCTInstanceDidLoadBundle" object:nil];
};
_reactInstance->loadScript(std::move(script), url, beforeLoad, afterLoad);
}
- (void)_handleJSError:(const JsErrorHandler::ProcessedError &)error withRuntime:(jsi::Runtime &)runtime
{
NSMutableDictionary<NSString *, id> *errorData = [NSMutableDictionary new];
errorData[@"message"] = @(error.message.c_str());
if (error.originalMessage) {
errorData[@"originalMessage"] = @(error.originalMessage->c_str());
}
if (error.name) {
errorData[@"name"] = @(error.name->c_str());
}
if (error.componentStack) {
errorData[@"componentStack"] = @(error.componentStack->c_str());
}
NSMutableArray<NSDictionary<NSString *, id> *> *stack = [NSMutableArray new];
for (const JsErrorHandler::ProcessedError::StackFrame &frame : error.stack) {
NSMutableDictionary<NSString *, id> *stackFrame = [NSMutableDictionary new];
if (frame.file) {
stackFrame[@"file"] = @(frame.file->c_str());
}
stackFrame[@"methodName"] = @(frame.methodName.c_str());
if (frame.lineNumber) {
stackFrame[@"lineNumber"] = @(*frame.lineNumber);
}
if (frame.column) {
stackFrame[@"column"] = @(*frame.column);
}
[stack addObject:stackFrame];
}
errorData[@"stack"] = stack;
errorData[@"id"] = @(error.id);
errorData[@"isFatal"] = @(error.isFatal);
id extraData =
TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsi::Value(runtime, error.extraData), nullptr);
if (extraData) {
errorData[@"extraData"] = extraData;
}
if (![_delegate instance:self
didReceiveJSErrorStack:errorData[@"stack"]
message:errorData[@"message"]
originalMessage:errorData[@"originalMessage"]
name:errorData[@"name"]
componentStack:errorData[@"componentStack"]
exceptionId:error.id
isFatal:[errorData[@"isFatal"] boolValue]
extraData:errorData[@"extraData"]]) {
JS::NativeExceptionsManager::ExceptionData jsErrorData{errorData};
id<NativeExceptionsManagerSpec> exceptionsManager = [_turboModuleManager moduleForName:"ExceptionsManager"];
[exceptionsManager reportException:jsErrorData];
}
}
- (void)_handleMemoryWarning
{
if (_valid) {
// Memory Pressure Unloading Level 15 represents TRIM_MEMORY_RUNNING_CRITICAL.
static constexpr int unloadLevel = 15;
_reactInstance->handleMemoryPressureJs(unloadLevel);
}
}
@end

View File

@@ -0,0 +1,21 @@
/*
* 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.
*/
#import <Foundation/Foundation.h>
#import <React/RCTMessageThread.h>
NS_ASSUME_NONNULL_BEGIN
@interface RCTJSThreadManager : NSObject
- (void)dispatchToJSThread:(dispatch_block_t)block;
- (std::shared_ptr<facebook::react::RCTMessageThread>)jsMessageThread;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,124 @@
/*
* 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.
*/
#import "RCTJSThreadManager.h"
#import <React/RCTAssert.h>
#import <React/RCTCxxUtils.h>
static NSString *const RCTJSThreadName = @"com.facebook.react.runtime.JavaScript";
#define RCTAssertJSThread() \
RCTAssert(self->_jsThread == [NSThread currentThread], @"This method must be called on JS thread")
@implementation RCTJSThreadManager {
NSThread *_jsThread;
std::shared_ptr<facebook::react::RCTMessageThread> _jsMessageThread;
}
- (instancetype)init
{
if (self = [super init]) {
[self startJSThread];
__weak RCTJSThreadManager *weakSelf = self;
dispatch_block_t captureJSThreadRunLoop = ^(void) {
__strong RCTJSThreadManager *strongSelf = weakSelf;
strongSelf->_jsMessageThread =
std::make_shared<facebook::react::RCTMessageThread>([NSRunLoop currentRunLoop], ^(NSError *error) {
if (error) {
[weakSelf _handleError:error];
}
});
};
[self performSelector:@selector(_tryAndHandleError:)
onThread:_jsThread
withObject:captureJSThreadRunLoop
waitUntilDone:YES];
}
return self;
}
- (std::shared_ptr<facebook::react::RCTMessageThread>)jsMessageThread
{
return _jsMessageThread;
}
- (void)dealloc
{
// This avoids a race condition, where work can be executed on JS thread after
// other peices of infra are cleaned up.
_jsMessageThread->quitSynchronous();
}
#pragma mark - JSThread Management
- (void)startJSThread
{
_jsThread = [[NSThread alloc] initWithTarget:[self class] selector:@selector(runRunLoop) object:nil];
_jsThread.name = RCTJSThreadName;
_jsThread.qualityOfService = NSOperationQualityOfServiceUserInteractive;
#if RCT_DEBUG
_jsThread.stackSize *= 2;
#endif
[_jsThread start];
}
/**
* Ensure block is run on the JS thread. If we're already on the JS thread, the block will execute synchronously.
* If we're not on the JS thread, the block is dispatched to that thread.
*/
- (void)dispatchToJSThread:(dispatch_block_t)block
{
RCTAssert(_jsThread, @"This method must not be called before the JS thread is created");
if ([NSThread currentThread] == _jsThread) {
[self _tryAndHandleError:block];
} else {
__weak __typeof(self) weakSelf = self;
_jsMessageThread->runOnQueue([weakSelf, block] { [weakSelf _tryAndHandleError:block]; });
}
}
+ (void)runRunLoop
{
@autoreleasepool {
// copy thread name to pthread name
pthread_setname_np([NSThread currentThread].name.UTF8String);
// Set up a dummy runloop source to avoid spinning
CFRunLoopSourceContext noSpinCtx = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
CFRunLoopSourceRef noSpinSource = CFRunLoopSourceCreate(NULL, 0, &noSpinCtx);
CFRunLoopAddSource(CFRunLoopGetCurrent(), noSpinSource, kCFRunLoopDefaultMode);
CFRelease(noSpinSource);
// run the run loop
while (kCFRunLoopRunStopped !=
CFRunLoopRunInMode(
kCFRunLoopDefaultMode, ((NSDate *)[NSDate distantFuture]).timeIntervalSinceReferenceDate, NO)) {
RCTAssert(NO, @"not reached assertion"); // runloop spun. that's bad.
}
}
}
#pragma mark - Private
- (void)_handleError:(NSError *)error
{
RCTFatal(error);
}
- (void)_tryAndHandleError:(dispatch_block_t)block
{
NSError *error = facebook::react::tryAndReturnError(block);
if (error) {
[self _handleError:error];
}
}
@end

View File

@@ -0,0 +1,22 @@
/*
* 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.
*/
#import <cxxreact/MessageQueueThread.h>
#import <jsi/jsi.h>
#import <react/runtime/JSRuntimeFactory.h>
namespace facebook::react {
class RCTJscInstance : public JSRuntimeFactory {
public:
RCTJscInstance();
std::unique_ptr<JSRuntime> createJSRuntime(std::shared_ptr<MessageQueueThread> msgQueueThread) noexcept override;
~RCTJscInstance() {};
};
} // namespace facebook::react

View File

@@ -0,0 +1,20 @@
/*
* 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.
*/
#import "RCTJscInstance.h"
#include <jsc/JSCRuntime.h>
namespace facebook::react {
RCTJscInstance::RCTJscInstance() {}
std::unique_ptr<JSRuntime> RCTJscInstance::createJSRuntime(std::shared_ptr<MessageQueueThread> msgQueueThread) noexcept
{
return std::make_unique<JSIRuntimeHolder>(jsc::makeJSCRuntime());
}
} // namespace facebook::react

View File

@@ -0,0 +1,19 @@
/*
* 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 <jsi/jsi.h>
namespace facebook::react {
/*
* Installs UIManger constants provider into JavaScript runtime. This is needed
* to implement UIManager.getConstants in bridgeless mode. The constants object
* contains view configs for every legacy native component.
*/
void installLegacyUIManagerConstantsProviderBinding(jsi::Runtime &runtime);
} // namespace facebook::react

View File

@@ -0,0 +1,43 @@
/*
* 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.
*/
#include "RCTLegacyUIManagerConstantsProvider.h"
#import <React/RCTBridge+Private.h>
#import <React/RCTComponentData.h>
#import <React/RCTUIManager.h>
#import <React/RCTViewManager.h>
#import <ReactCommon/RCTTurboModule.h>
#import <react/runtime/nativeviewconfig/LegacyUIManagerConstantsProviderBinding.h>
namespace facebook::react {
namespace {
jsi::Value getConstants(facebook::jsi::Runtime &runtime)
{
NSMutableDictionary<NSString *, NSObject *> *result = [NSMutableDictionary new];
auto directEvents = [NSMutableDictionary new];
auto bubblingEvents = [NSMutableDictionary new];
for (Class moduleClass in RCTGetModuleClasses()) {
if ([moduleClass isSubclassOfClass:RCTViewManager.class]) {
auto name = RCTViewManagerModuleNameForClass(moduleClass);
auto viewConfig = [RCTComponentData viewConfigForViewMangerClass:moduleClass];
auto moduleConstants =
RCTModuleConstantsForDestructuredComponent(directEvents, bubblingEvents, moduleClass, name, viewConfig);
result[name] = moduleConstants;
}
}
return TurboModuleConvertUtils::convertObjCObjectToJSIValue(runtime, result);
};
} // namespace
void installLegacyUIManagerConstantsProviderBinding(jsi::Runtime &runtime)
{
LegacyUIManagerConstantsProviderBinding::install(runtime, "getConstants", getConstants);
}
} // namespace facebook::react

View File

@@ -0,0 +1,16 @@
/*
* 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.
*/
#import <Foundation/Foundation.h>
@class RCTPerformanceLogger;
NS_ASSUME_NONNULL_BEGIN
extern void registerPerformanceLoggerHooks(RCTPerformanceLogger *performanceLogger);
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,74 @@
/*
* 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.
*/
#import "RCTPerformanceLoggerUtils.h"
#import <React/RCTPerformanceLogger.h>
#import <cxxreact/ReactMarker.h>
using namespace facebook::react;
static void mapReactMarkerToPerformanceLogger(
const ReactMarker::ReactMarkerId markerId,
RCTPerformanceLogger *performanceLogger)
{
switch (markerId) {
case ReactMarker::APP_STARTUP_START:
[performanceLogger markStartForTag:RCTPLAppStartup];
break;
case ReactMarker::APP_STARTUP_STOP:
[performanceLogger markStopForTag:RCTPLAppStartup];
break;
case ReactMarker::INIT_REACT_RUNTIME_START:
[performanceLogger markStartForTag:RCTPLInitReactRuntime];
break;
case ReactMarker::INIT_REACT_RUNTIME_STOP:
[performanceLogger markStopForTag:RCTPLInitReactRuntime];
break;
case ReactMarker::RUN_JS_BUNDLE_START:
[performanceLogger markStartForTag:RCTPLScriptExecution];
break;
case ReactMarker::RUN_JS_BUNDLE_STOP:
[performanceLogger markStopForTag:RCTPLScriptExecution];
break;
case ReactMarker::NATIVE_REQUIRE_START:
[performanceLogger appendStartForTag:RCTPLRAMNativeRequires];
break;
case ReactMarker::NATIVE_REQUIRE_STOP:
[performanceLogger appendStopForTag:RCTPLRAMNativeRequires];
[performanceLogger addValue:1 forTag:RCTPLRAMNativeRequiresCount];
break;
case ReactMarker::NATIVE_MODULE_SETUP_START:
[performanceLogger markStartForTag:RCTPLNativeModuleSetup];
break;
case ReactMarker::NATIVE_MODULE_SETUP_STOP:
[performanceLogger markStopForTag:RCTPLNativeModuleSetup];
break;
case ReactMarker::REACT_INSTANCE_INIT_START:
[performanceLogger markStartForTag:RCTPLReactInstanceInit];
break;
case ReactMarker::REACT_INSTANCE_INIT_STOP:
[performanceLogger markStopForTag:RCTPLReactInstanceInit];
break;
case ReactMarker::CREATE_REACT_CONTEXT_STOP:
case ReactMarker::JS_BUNDLE_STRING_CONVERT_START:
case ReactMarker::JS_BUNDLE_STRING_CONVERT_STOP:
case ReactMarker::REGISTER_JS_SEGMENT_START:
case ReactMarker::REGISTER_JS_SEGMENT_STOP:
// These are not used on iOS.
break;
}
}
void registerPerformanceLoggerHooks(RCTPerformanceLogger *performanceLogger)
{
__weak RCTPerformanceLogger *weakPerformanceLogger = performanceLogger;
ReactMarker::logTaggedMarkerBridgelessImpl = [weakPerformanceLogger](
const ReactMarker::ReactMarkerId markerId, const char *tag) {
mapReactMarkerToPerformanceLogger(markerId, weakPerformanceLogger);
};
}

View File

@@ -0,0 +1,749 @@
/*
* 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.
*/
#include <memory>
#include <queue>
#include <utility>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <ReactCommon/RuntimeExecutor.h>
#include <hermes/hermes.h>
#include <jserrorhandler/JsErrorHandler.h>
#include <jsi/jsi.h>
#include <react/runtime/ReactInstance.h>
using ::testing::_;
using ::testing::HasSubstr;
using ::testing::SaveArg;
namespace facebook::react {
class MockTimerRegistry : public PlatformTimerRegistry {
public:
MOCK_METHOD2(createTimer, void(uint32_t, double));
MOCK_METHOD2(createRecurringTimer, void(uint32_t, double));
MOCK_METHOD1(deleteTimer, void(uint32_t));
};
class MockMessageQueueThread : public MessageQueueThread {
public:
void runOnQueue(std::function<void()>&& func) override {
callbackQueue_.push(func);
}
// Unused
void runOnQueueSync(std::function<void()>&& /*unused*/) override {}
// Unused
void quitSynchronous() override {}
void tick() {
if (!callbackQueue_.empty()) {
auto callback = callbackQueue_.front();
callback();
callbackQueue_.pop();
}
}
void guardedTick() {
try {
tick();
} catch (const std::exception& e) {
// For easier debugging
FAIL() << e.what();
}
}
size_t size() {
return callbackQueue_.size();
}
private:
std::queue<std::function<void()>> callbackQueue_;
};
class ErrorUtils : public jsi::HostObject {
public:
jsi::Value get(jsi::Runtime& rt, const jsi::PropNameID& name) override {
auto methodName = name.utf8(rt);
if (methodName == "reportFatalError") {
return jsi::Function::createFromHostFunction(
rt,
name,
1,
[this](
jsi::Runtime& runtime,
const jsi::Value& /*thisValue*/,
const jsi::Value* arguments,
size_t count) {
if (count >= 1) {
auto value = jsi::Value(runtime, std::move(arguments[0]));
auto error = jsi::JSError(runtime, std::move(value));
reportFatalError(std::move(error));
}
return jsi::Value::undefined();
});
} else {
throw std::runtime_error("Unknown method: " + methodName);
}
}
void reportFatalError(jsi::JSError&& error) {
errors_.push_back(std::move(error));
}
size_t size() {
return errors_.size();
}
jsi::JSError getLastError() {
auto error = errors_.back();
errors_.pop_back();
return error;
}
private:
std::vector<jsi::JSError> errors_;
};
class ReactInstanceTest : public ::testing::Test {
protected:
ReactInstanceTest() = default;
void SetUp() override {
auto runtime =
std::make_unique<JSIRuntimeHolder>(hermes::makeHermesRuntime());
runtime_ = &runtime->getRuntime();
messageQueueThread_ = std::make_shared<MockMessageQueueThread>();
auto mockRegistry = std::make_unique<MockTimerRegistry>();
mockRegistry_ = mockRegistry.get();
timerManager_ = std::make_shared<TimerManager>(std::move(mockRegistry));
auto onJsError =
[](jsi::Runtime& /*runtime*/,
const JsErrorHandler::ProcessedError& /*error*/) noexcept {
// Do nothing
};
instance_ = std::make_unique<ReactInstance>(
std::move(runtime),
messageQueueThread_,
timerManager_,
std::move(onJsError));
timerManager_->setRuntimeExecutor(instance_->getBufferedRuntimeExecutor());
// Install a C++ error handler
errorHandler_ = std::make_shared<ErrorUtils>();
runtime_->global().setProperty(
*runtime_,
"ErrorUtils",
jsi::Object::createFromHostObject(*runtime_, errorHandler_));
}
void initializeRuntimeWithScript(
ReactInstance::JSRuntimeFlags jsRuntimeFlags,
std::string script) {
instance_->initializeRuntime(jsRuntimeFlags, [](jsi::Runtime& runtime) {});
step();
// Run the main bundle, so that native -> JS calls no longer get buffered.
loadScript(std::move(script));
}
void initializeRuntimeWithScript(std::string script) {
instance_->initializeRuntime(
{.isProfiling = false}, [](jsi::Runtime& runtime) {});
step();
// Run the main bundle, so that native -> JS calls no longer get buffered.
loadScript(std::move(script));
}
jsi::Value tryEval(const std::string& js, const std::string& defaultVal) {
return eval(
"(function() { try { return " + js + "; } catch { return " +
defaultVal + "; } })()");
}
jsi::Value eval(const std::string& js) {
RuntimeExecutor runtimeExecutor = instance_->getUnbufferedRuntimeExecutor();
jsi::Value ret = jsi::Value::undefined();
runtimeExecutor([js, &ret](jsi::Runtime& runtime) {
ret = runtime.evaluateJavaScript(
std::make_unique<jsi::StringBuffer>(js), "");
});
step();
return ret;
}
// Call instance_->loadScript() to evaluate JS script and flush buffered JS
// calls
jsi::Value loadScript(std::string js) {
jsi::Value ret = jsi::Value::undefined();
instance_->loadScript(std::make_unique<JSBigStdString>(std::move(js)), "");
step();
return ret;
}
void expectError() {
EXPECT_NE(errorHandler_->size(), 0)
<< "Expected an error to have been thrown, but it wasn't.";
}
void expectNoError() {
EXPECT_EQ(errorHandler_->size(), 0)
<< "Expected no error to have been thrown, but one was.";
}
std::string getLastErrorMessage() {
auto error = errorHandler_->getLastError();
return error.getMessage();
}
std::string getErrorMessage(std::string js) {
eval(js);
return getLastErrorMessage();
}
void step() {
messageQueueThread_->guardedTick();
}
jsi::Runtime* runtime_{};
std::shared_ptr<MockMessageQueueThread> messageQueueThread_;
std::unique_ptr<ReactInstance> instance_;
std::shared_ptr<TimerManager> timerManager_;
MockTimerRegistry* mockRegistry_{};
std::shared_ptr<ErrorUtils> errorHandler_;
};
TEST_F(ReactInstanceTest, testBridgelessFlagIsSet) {
auto valBefore = tryEval("RN$Bridgeless === true", "false");
EXPECT_EQ(valBefore.getBool(), false);
initializeRuntimeWithScript("");
auto val = eval("RN$Bridgeless === true");
EXPECT_EQ(val.getBool(), true);
}
TEST_F(ReactInstanceTest, testProfilingFlag) {
auto valBefore = tryEval("__RCTProfileIsProfiling === true", "false");
EXPECT_EQ(valBefore.getBool(), false);
initializeRuntimeWithScript({.isProfiling = true}, "");
auto val = eval("__RCTProfileIsProfiling === true");
EXPECT_EQ(val.getBool(), true);
}
TEST_F(ReactInstanceTest, testSetTimeout) {
initializeRuntimeWithScript("");
uint32_t timerID{0};
EXPECT_CALL(*mockRegistry_, createTimer(_, 100))
.WillOnce(SaveArg<0>(&timerID));
eval(R"xyz123(
let called = false;
setTimeout(() => {
called = true;
}, 100);
function getResult() {
return called;
}
)xyz123");
timerManager_->callTimer(timerID);
step();
auto called = runtime_->global()
.getPropertyAsFunction(*runtime_, "getResult")
.call(*runtime_);
EXPECT_EQ(called.getBool(), true);
}
TEST_F(ReactInstanceTest, testSetTimeoutWithoutDelay) {
initializeRuntimeWithScript("");
EXPECT_CALL(
*mockRegistry_,
createTimer(_, 0)); // If delay is not provided, it should use 0
auto val = eval("setTimeout(() => {});");
expectNoError();
EXPECT_EQ(val.asNumber(), 1); // First timer id should start at 1
}
TEST_F(ReactInstanceTest, testSetTimeoutWithPassThroughArgs) {
initializeRuntimeWithScript("");
uint32_t timerID{0};
EXPECT_CALL(*mockRegistry_, createTimer(_, 0)).WillOnce(SaveArg<0>(&timerID));
eval(R"xyz123(
let result;
setTimeout(arg => {
result = arg;
}, undefined, 'foo');
function getResult() {
return result;
}
)xyz123");
timerManager_->callTimer(timerID);
step();
auto result = runtime_->global()
.getPropertyAsFunction(*runtime_, "getResult")
.call(*runtime_);
EXPECT_EQ(result.asString(*runtime_).utf8(*runtime_), "foo");
}
TEST_F(ReactInstanceTest, testSetTimeoutWithInvalidArgs) {
initializeRuntimeWithScript("");
EXPECT_EQ(
getErrorMessage("setTimeout();"),
"setTimeout must be called with at least one argument (the function to call).");
auto val = eval("setTimeout('invalid')");
expectNoError();
EXPECT_EQ(val.asNumber(), 0);
eval("setTimeout(() => {}, 'invalid');");
expectNoError();
}
TEST_F(ReactInstanceTest, testClearTimeout) {
initializeRuntimeWithScript("");
uint32_t timerID{0};
EXPECT_CALL(*mockRegistry_, createTimer(_, 100))
.WillOnce(SaveArg<0>(&timerID));
eval(R"xyz123(
const handle = setTimeout(() => {}, 100);
function clear() {
clearTimeout(handle);
}
)xyz123");
EXPECT_CALL(*mockRegistry_, deleteTimer(timerID));
runtime_->global().getPropertyAsFunction(*runtime_, "clear").call(*runtime_);
}
TEST_F(ReactInstanceTest, testClearTimeoutWithInvalidArgs) {
initializeRuntimeWithScript("");
eval("clearTimeout();");
expectNoError();
eval("clearTimeout('invalid');");
expectNoError();
eval("clearTimeout({});");
expectNoError();
eval("clearTimeout(undefined);");
expectNoError();
}
TEST_F(ReactInstanceTest, testClearTimeoutForExpiredTimer) {
initializeRuntimeWithScript("");
uint32_t timerID{0};
EXPECT_CALL(*mockRegistry_, createTimer(_, 100))
.WillOnce(SaveArg<0>(&timerID));
eval(R"xyz123(
const handle = setTimeout(() => {}, 100);
function clear() {
clearTimeout(handle);
}
)xyz123");
// Call the timer
timerManager_->callTimer(timerID);
step();
// Now clear the called timer
EXPECT_CALL(*mockRegistry_, deleteTimer(timerID));
auto clear = runtime_->global().getPropertyAsFunction(*runtime_, "clear");
EXPECT_NO_THROW(clear.call(*runtime_));
}
TEST_F(ReactInstanceTest, testSetInterval) {
initializeRuntimeWithScript("");
uint32_t timerID{0};
EXPECT_CALL(*mockRegistry_, createRecurringTimer(_, 100))
.WillOnce(SaveArg<0>(&timerID));
eval(R"xyz123(
let result = 0;
setInterval(() => {
result++;
}, 100);
function getResult() {
return result;
}
)xyz123");
timerManager_->callTimer(timerID);
step();
auto getResult =
runtime_->global().getPropertyAsFunction(*runtime_, "getResult");
EXPECT_EQ(getResult.call(*runtime_).asNumber(), 1.0);
// Should be able to call the same callback again.
timerManager_->callTimer(timerID);
step();
EXPECT_EQ(getResult.call(*runtime_).asNumber(), 2.0);
}
TEST_F(ReactInstanceTest, testSetIntervalWithPassThroughArgs) {
initializeRuntimeWithScript("");
uint32_t timerID{0};
EXPECT_CALL(*mockRegistry_, createRecurringTimer(_, 100))
.WillOnce(SaveArg<0>(&timerID));
eval(R"xyz123(
let result;
setInterval(arg => {
result = arg;
}, 100, 'foo');
function getResult() {
return result;
}
)xyz123");
timerManager_->callTimer(timerID);
step();
auto getResult =
runtime_->global().getPropertyAsFunction(*runtime_, "getResult");
EXPECT_EQ(
getResult.call(*runtime_).asString(*runtime_).utf8(*runtime_), "foo");
}
TEST_F(ReactInstanceTest, testSetIntervalWithInvalidArgs) {
initializeRuntimeWithScript("");
EXPECT_EQ(
getErrorMessage("setInterval();"),
"setInterval must be called with at least one argument (the function to call).");
auto val = eval("setInterval('invalid', 100)");
expectNoError();
EXPECT_EQ(val.asNumber(), 0);
}
TEST_F(ReactInstanceTest, testClearInterval) {
initializeRuntimeWithScript("");
uint32_t timerID{0};
EXPECT_CALL(*mockRegistry_, createRecurringTimer(_, 100))
.WillOnce(SaveArg<0>(&timerID));
eval(R"xyz123(
let result = 0;
const handle = setInterval(() => {
result++;
}, 100);
function clear() {
clearInterval(handle);
}
function getResult() {
return result;
}
)xyz123");
timerManager_->callTimer(timerID);
step();
auto getResult =
runtime_->global().getPropertyAsFunction(*runtime_, "getResult");
EXPECT_EQ(getResult.call(*runtime_).asNumber(), 1.0);
EXPECT_CALL(*mockRegistry_, deleteTimer(timerID));
runtime_->global().getPropertyAsFunction(*runtime_, "clear").call(*runtime_);
step();
timerManager_->callTimer(timerID);
step();
// Callback should not have been invoked again.
EXPECT_EQ(getResult.call(*runtime_).asNumber(), 1.0);
}
TEST_F(ReactInstanceTest, testClearIntervalWithInvalidArgs) {
initializeRuntimeWithScript("");
eval("clearInterval();");
expectNoError();
eval("clearInterval(false);");
expectNoError();
eval("clearInterval({});");
expectNoError();
eval("clearInterval(undefined);");
expectNoError();
}
TEST_F(ReactInstanceTest, testRequestAnimationFrame) {
initializeRuntimeWithScript("");
uint32_t timerID{0};
EXPECT_CALL(*mockRegistry_, createTimer(_, 0)).WillOnce(SaveArg<0>(&timerID));
eval(R"xyz123(
let called = false;
performance = {
now: () => 0
}
requestAnimationFrame(() => {
called = true;
});
function getResult() {
return called;
}
)xyz123");
auto getResult =
runtime_->global().getPropertyAsFunction(*runtime_, "getResult");
EXPECT_EQ(getResult.call(*runtime_).getBool(), false);
timerManager_->callTimer(timerID);
step();
EXPECT_EQ(getResult.call(*runtime_).getBool(), true);
}
TEST_F(
ReactInstanceTest,
testRequestAnimationFrameCallbackArgIsPerformanceNow) {
initializeRuntimeWithScript("");
uint32_t timerID{0};
EXPECT_CALL(*mockRegistry_, createTimer(_, 0)).WillOnce(SaveArg<0>(&timerID));
eval(R"xyz123(
let now = 0;
performance = {
now: () => 123456
}
requestAnimationFrame(($now) => {
now = $now;
});
function getResult() {
return now;
}
)xyz123");
auto getResult =
runtime_->global().getPropertyAsFunction(*runtime_, "getResult");
EXPECT_EQ(getResult.call(*runtime_).getNumber(), 0);
timerManager_->callTimer(timerID);
step();
EXPECT_EQ(getResult.call(*runtime_).getNumber(), 123456);
}
TEST_F(ReactInstanceTest, testRequestAnimationFrameWithInvalidArgs) {
initializeRuntimeWithScript("");
eval(R"xyz123(
performance = {
now: () => 0
}
)xyz123");
EXPECT_EQ(
getErrorMessage("requestAnimationFrame();"),
"requestAnimationFrame must be called with at least one argument (i.e: a callback)");
EXPECT_EQ(
getErrorMessage("requestAnimationFrame('invalid');"),
"The first argument to requestAnimationFrame must be a function.");
EXPECT_EQ(
getErrorMessage("requestAnimationFrame({});"),
"The first argument to requestAnimationFrame must be a function.");
}
TEST_F(ReactInstanceTest, testCancelAnimationFrame) {
initializeRuntimeWithScript("");
uint32_t timerID{0};
EXPECT_CALL(*mockRegistry_, createTimer(_, 0)).WillOnce(SaveArg<0>(&timerID));
eval(R"xyz123(
let called = false;
performance = {
now: () => 0
}
const handle = requestAnimationFrame(() => {
called = true;
});
function clear() {
cancelAnimationFrame(handle);
}
function getResult() {
return called;
}
)xyz123");
EXPECT_CALL(*mockRegistry_, deleteTimer(timerID));
runtime_->global().getPropertyAsFunction(*runtime_, "clear").call(*runtime_);
// Attempt to call timer; should fail silently.
timerManager_->callTimer(timerID);
step();
// Verify the callback was not called.
auto called = runtime_->global()
.getPropertyAsFunction(*runtime_, "getResult")
.call(*runtime_);
EXPECT_EQ(called.getBool(), false);
}
TEST_F(ReactInstanceTest, testCancelAnimationFrameWithInvalidArgs) {
initializeRuntimeWithScript("");
eval("cancelAnimationFrame();");
expectNoError();
eval("cancelAnimationFrame(false);");
expectNoError();
eval("cancelAnimationFrame({});");
expectNoError();
eval("cancelAnimationFrame(undefined);");
expectNoError();
}
TEST_F(ReactInstanceTest, testCancelAnimationFrameWithExpiredTimer) {
initializeRuntimeWithScript("");
uint32_t timerID{0};
EXPECT_CALL(*mockRegistry_, createTimer(_, 0)).WillOnce(SaveArg<0>(&timerID));
eval(R"xyz123(
let called = false;
performance = {
now: () => 0
}
const handle = requestAnimationFrame(() => {
called = true;
});
function clear() {
cancelAnimationFrame(handle);
}
function getResult() {
return called;
}
)xyz123");
timerManager_->callTimer(timerID);
step();
auto called = runtime_->global()
.getPropertyAsFunction(*runtime_, "getResult")
.call(*runtime_);
EXPECT_EQ(called.getBool(), true);
EXPECT_CALL(*mockRegistry_, deleteTimer(timerID));
auto clear = runtime_->global().getPropertyAsFunction(*runtime_, "clear");
// Canceling an expired timer should fail silently.
EXPECT_NO_THROW(clear.call(*runtime_));
}
TEST_F(ReactInstanceTest, testRegisterCallableModule) {
initializeRuntimeWithScript(R"xyz123(
let called = false;
const module = {
bar: () => {
called = true;
},
};
function getResult() {
return called;
}
RN$registerCallableModule('foo', () => module);
)xyz123");
auto args = folly::dynamic::array(0);
instance_->callFunctionOnModule("foo", "bar", std::move(args));
step();
auto called = runtime_->global()
.getPropertyAsFunction(*runtime_, "getResult")
.call(*runtime_);
EXPECT_EQ(called.getBool(), true);
}
TEST_F(ReactInstanceTest, testRegisterCallableModule_invalidArgs) {
initializeRuntimeWithScript("");
EXPECT_EQ(
getErrorMessage("RN$registerCallableModule();"),
"registerCallableModule requires exactly 2 arguments");
EXPECT_EQ(
getErrorMessage("RN$registerCallableModule('foo');"),
"registerCallableModule requires exactly 2 arguments");
EXPECT_EQ(
getErrorMessage("RN$registerCallableModule(1, () => ({}));"),
"The first argument to registerCallableModule must be a string (the name of the JS module).");
EXPECT_EQ(
getErrorMessage("RN$registerCallableModule('foo', false);"),
"The second argument to registerCallableModule must be a function that returns the JS module.");
}
TEST_F(ReactInstanceTest, testCallFunctionOnModule_invalidModule) {
initializeRuntimeWithScript("");
auto args = folly::dynamic::array(0);
instance_->callFunctionOnModule("invalidModule", "method", std::move(args));
step();
expectError();
EXPECT_THAT(
getLastErrorMessage(),
HasSubstr(
"Failed to call into JavaScript module method invalidModule.method()"));
}
TEST_F(ReactInstanceTest, testCallFunctionOnModule_undefinedMethod) {
initializeRuntimeWithScript(R"xyz123(
const module = {
bar: () => {},
};
RN$registerCallableModule('foo', () => module);
)xyz123");
auto args = folly::dynamic::array(0);
instance_->callFunctionOnModule("foo", "invalidMethod", std::move(args));
step();
expectError();
EXPECT_EQ(
getLastErrorMessage(),
"getPropertyAsObject: property 'invalidMethod' is undefined, expected an Object");
}
TEST_F(ReactInstanceTest, testCallFunctionOnModule_invalidMethod) {
initializeRuntimeWithScript(R"xyz123(
const module = {
bar: false,
};
RN$registerCallableModule('foo', () => module);
)xyz123");
auto args = folly::dynamic::array(0);
instance_->callFunctionOnModule("foo", "bar", std::move(args));
step();
expectError();
}
TEST_F(ReactInstanceTest, testRegisterCallableModule_withArgs) {
initializeRuntimeWithScript(R"xyz123(
let result;
const module = {
bar: thing => {
result = thing;
},
};
RN$registerCallableModule('foo', () => module);
function getResult() {
return result;
}
)xyz123");
auto args = folly::dynamic::array(1);
instance_->callFunctionOnModule("foo", "bar", std::move(args));
step();
auto result = runtime_->global()
.getPropertyAsFunction(*runtime_, "getResult")
.call(*runtime_);
EXPECT_EQ(result.getNumber(), 1);
}
} // namespace facebook::react