feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,17 @@
---
InheritParentConfig: true
Checks: '
-cert-err60-cpp,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
-cppcoreguidelines-special-member-functions,
-fuchsia-default-arguments-calls,
-google-readability-casting,
-google-runtime-references,
-hicpp-special-member-functions,
-llvm-header-guard,
-misc-non-private-member-variables-in-classes,
-misc-unused-parameters,
-modernize-use-trailing-return-type,
-performance-unnecessary-value-param,
'
...

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.
*/
#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,29 @@
/*
* 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 "BridgelessJSCallInvoker.h"
#include <stdexcept>
namespace facebook::react {
BridgelessJSCallInvoker::BridgelessJSCallInvoker(
RuntimeExecutor runtimeExecutor)
: runtimeExecutor_(std::move(runtimeExecutor)) {}
void BridgelessJSCallInvoker::invokeAsync(
std::function<void()>&& func) noexcept {
runtimeExecutor_([func = std::move(func)](jsi::Runtime& runtime) { func(); });
}
void BridgelessJSCallInvoker::invokeSync(std::function<void()>&& func) {
// TODO: Implement this method. The TurboModule infra doesn't call invokeSync.
throw std::runtime_error(
"Synchronous native -> JS calls are currently not supported.");
}
} // 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 <ReactCommon/CallInvoker.h>
#include <ReactCommon/RuntimeExecutor.h>
#include <functional>
namespace facebook::react {
/**
* A native-to-JS call invoker that uses the RuntimeExecutor. It guarantees that
* any calls from any thread are queued on the right JS thread.
*/
class BridgelessJSCallInvoker : public CallInvoker {
public:
explicit BridgelessJSCallInvoker(RuntimeExecutor runtimeExecutor);
void invokeAsync(std::function<void()>&& func) noexcept override;
void invokeSync(std::function<void()>&& func) override;
private:
RuntimeExecutor runtimeExecutor_;
};
} // 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,
std::function<void()>&& func) noexcept {
messageQueueThread_->runOnQueue(std::move(func));
}
void BridgelessNativeMethodCallInvoker::invokeSync(
const std::string& methodName,
std::function<void()>&& func) {
messageQueueThread_->runOnQueueSync(std::move(func));
}
} // namespace facebook::react

View File

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

View File

@@ -0,0 +1,60 @@
/*
* 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"
#include <cxxreact/MessageQueueThread.h>
#include <algorithm>
namespace facebook::react {
BufferedRuntimeExecutor::BufferedRuntimeExecutor(
RuntimeExecutor runtimeExecutor)
: runtimeExecutor_(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_.size() > 0) {
const BufferedWork& bufferedWork = queue_.top();
Work work = std::move(bufferedWork.work_);
runtimeExecutor_(std::move(work));
queue_.pop();
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,49 @@
/*
* 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 <ReactCommon/RuntimeExecutor.h>
#include <jsi/jsi.h>
#include <atomic>
#include <queue>
#include "TimerManager.h"
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_;
bool isBufferingEnabled_;
std::mutex lock_;
std::atomic<uint64_t> lastIndex_;
std::priority_queue<BufferedWork> queue_;
};
} // 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)
file(GLOB bridgeless_SRC "*.cpp")
add_library(bridgeless
STATIC
${bridgeless_SRC}
)
target_compile_options(
bridgeless
PRIVATE
$<$<CONFIG:Debug>:-DHERMES_ENABLE_DEBUGGER=1>
-std=c++20
-fexceptions
)
target_include_directories(bridgeless PUBLIC .)
find_library(LIBHERMES NAMES hermes-engine::libhermes)
if (LIBHERMES)
target_link_libraries(bridgeless hermes-engine::libhermes)
endif ()
target_link_libraries(
bridgeless
jserrorhandler
fabricjni
react_featureflagsjni
turbomodulejsijni
fb
jsi
jsireact
react_utils
jsinspector
react_featureflags
react_utils
)

View File

@@ -0,0 +1,33 @@
/*
* 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 "JSRuntimeFactory.h"
namespace facebook::react {
jsi::Runtime& JSIRuntimeHolder::getRuntime() noexcept {
return *runtime_;
}
JSIRuntimeHolder::JSIRuntimeHolder(std::unique_ptr<jsi::Runtime> runtime)
: runtime_(std::move(runtime)) {
assert(runtime_ != nullptr);
}
std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate>
JSIRuntimeHolder::createAgentDelegate(
jsinspector_modern::FrontendChannel frontendChannel,
jsinspector_modern::SessionState& sessionState,
std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate::ExportedState>,
const jsinspector_modern::ExecutionContextDescription&
executionContextDescription) {
(void)executionContextDescription;
return std::make_unique<jsinspector_modern::FallbackRuntimeAgentDelegate>(
std::move(frontendChannel), sessionState, runtime_->description());
}
} // 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.
*/
#pragma once
#include <ReactCommon/RuntimeExecutor.h>
#include <cxxreact/MessageQueueThread.h>
#include <jsi/jsi.h>
#include <jsinspector-modern/ReactCdp.h>
namespace facebook::react {
/**
* An interface that represents an instance of a JS VM
*/
class JSRuntime : public jsinspector_modern::RuntimeTargetDelegate {
public:
virtual jsi::Runtime& getRuntime() noexcept = 0;
virtual ~JSRuntime() = default;
};
/**
* Interface for a class that creates instances of a JS VM
*/
class JSRuntimeFactory {
public:
virtual std::unique_ptr<JSRuntime> createJSRuntime(
std::shared_ptr<MessageQueueThread> msgQueueThread) noexcept = 0;
virtual ~JSRuntimeFactory() = default;
};
/**
* Utility class for creating a JSRuntime from a uniquely owned jsi::Runtime.
*/
class JSIRuntimeHolder : public JSRuntime {
public:
jsi::Runtime& getRuntime() noexcept override;
std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate> createAgentDelegate(
jsinspector_modern::FrontendChannel frontendChannel,
jsinspector_modern::SessionState& sessionState,
std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate::ExportedState>
previouslyExportedState,
const jsinspector_modern::ExecutionContextDescription&
executionContextDescription) override;
explicit JSIRuntimeHolder(std::unique_ptr<jsi::Runtime> runtime);
private:
std::unique_ptr<jsi::Runtime> runtime_;
};
} // namespace facebook::react

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,67 @@
# 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
folly_config = get_folly_config()
folly_compiler_flags = folly_config[:compiler_flags]
folly_version = folly_config[:version]
folly_dep_name = 'RCT-Folly/Fabric'
boost_compiler_flags = '-Wno-documentation'
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 = "*.{cpp,h}", "nativeviewconfig/*.{cpp,h}"
s.exclude_files = "iostests/*", "tests/**/*.{cpp,h}"
s.header_dir = "react/runtime"
s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/Headers/Private/React-Core\" \"${PODS_TARGET_SRCROOT}/../..\"",
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
"GCC_WARN_PEDANTIC" => "YES" }
s.compiler_flags = folly_compiler_flags + ' ' + boost_compiler_flags
if ENV['USE_FRAMEWORKS']
s.header_mappings_dir = '../../'
s.module_name = 'React_RuntimeCore'
end
s.dependency folly_dep_name, folly_version
s.dependency "React-jsiexecutor"
s.dependency "React-cxxreact"
s.dependency "React-runtimeexecutor"
s.dependency "glog"
s.dependency "React-jsi"
s.dependency "React-jserrorhandler"
s.dependency "React-runtimescheduler"
s.dependency "React-utils"
s.dependency "React-featureflags"
if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1"
s.dependency "hermes-engine"
else
s.dependency "React-jsc"
end
s.dependency "React-jsinspector"
end

View File

@@ -0,0 +1,63 @@
# 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
folly_config = get_folly_config()
folly_compiler_flags = folly_config[:compiler_flags]
folly_version = folly_config[:version]
folly_dep_name = 'RCT-Folly/Fabric'
boost_compiler_flags = '-Wno-documentation'
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 = "hermes/*.{cpp,h}"
s.header_dir = "react/runtime/hermes"
s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"${PODS_TARGET_SRCROOT}/../..\" \"${PODS_TARGET_SRCROOT}/../../hermes/executor\" \"$(PODS_ROOT)/boost\"",
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
"GCC_WARN_PEDANTIC" => "YES" }
s.compiler_flags = folly_compiler_flags + ' ' + boost_compiler_flags
if ENV['USE_FRAMEWORKS']
s.header_mappings_dir = './'
s.module_name = 'React_RuntimeHermes'
end
s.dependency folly_dep_name, folly_version
s.dependency "React-nativeconfig"
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')
if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1"
s.dependency "React-hermes"
s.dependency "hermes-engine"
else
s.dependency "React-jsc"
s.exclude_files = "hermes/*.{cpp,h}"
end
end

View File

@@ -0,0 +1,435 @@
/*
* 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 <cxxreact/ErrorUtils.h>
#include <cxxreact/JSBigString.h>
#include <cxxreact/JSExecutor.h>
#include <cxxreact/ReactMarker.h>
#include <cxxreact/SystraceSection.h>
#include <glog/logging.h>
#include <jsi/JSIDynamic.h>
#include <jsi/instrumentation.h>
#include <jsireact/JSIExecutor.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/runtimescheduler/RuntimeSchedulerBinding.h>
#include <react/utils/jsi.h>
#include <iostream>
#include <tuple>
#include <utility>
namespace facebook::react {
ReactInstance::ReactInstance(
std::unique_ptr<JSRuntime> runtime,
std::shared_ptr<MessageQueueThread> jsMessageQueueThread,
std::shared_ptr<TimerManager> timerManager,
JsErrorHandler::JsErrorHandlingFunc jsErrorHandlingFunc,
jsinspector_modern::PageTarget* parentInspectorTarget)
: runtime_(std::move(runtime)),
jsMessageQueueThread_(jsMessageQueueThread),
timerManager_(std::move(timerManager)),
jsErrorHandler_(jsErrorHandlingFunc),
hasFatalJsError_(std::make_shared<bool>(false)),
parentInspectorTarget_(parentInspectorTarget) {
auto runtimeExecutor = [weakRuntime = std::weak_ptr<JSRuntime>(runtime_),
weakTimerManager =
std::weak_ptr<TimerManager>(timerManager_),
weakJsMessageQueueThread =
std::weak_ptr<MessageQueueThread>(
jsMessageQueueThread_),
weakHasFatalJsError =
std::weak_ptr<bool>(hasFatalJsError_)](
std::function<void(jsi::Runtime & runtime)>&&
callback) {
if (std::shared_ptr<bool> sharedHasFatalJsError =
weakHasFatalJsError.lock()) {
if (*sharedHasFatalJsError) {
LOG(INFO)
<< "Calling into JS using runtimeExecutor but hasFatalJsError_ is true";
return;
}
}
if (weakRuntime.expired()) {
return;
}
if (std::shared_ptr<MessageQueueThread> sharedJsMessageQueueThread =
weakJsMessageQueueThread.lock()) {
sharedJsMessageQueueThread->runOnQueue(
[weakRuntime, weakTimerManager, callback = std::move(callback)]() {
if (auto strongRuntime = weakRuntime.lock()) {
jsi::Runtime& jsiRuntime = strongRuntime->getRuntime();
SystraceSection s("ReactInstance::_runtimeExecutor[Callback]");
try {
callback(jsiRuntime);
// If we have first-class support for microtasks,
// they would've been called as part of the previous callback.
if (!ReactNativeFeatureFlags::enableMicrotasks()) {
if (auto strongTimerManager = weakTimerManager.lock()) {
strongTimerManager->callReactNativeMicrotasks(jsiRuntime);
}
}
} catch (jsi::JSError& originalError) {
handleJSError(jsiRuntime, originalError, true);
}
}
});
}
};
if (parentInspectorTarget_) {
inspectorTarget_ = &parentInspectorTarget_->registerInstance(*this);
runtimeInspectorTarget_ =
&inspectorTarget_->registerRuntime(*runtime_, runtimeExecutor);
}
runtimeScheduler_ =
std::make_shared<RuntimeScheduler>(std::move(runtimeExecutor));
auto pipedRuntimeExecutor =
[runtimeScheduler = runtimeScheduler_.get()](
std::function<void(jsi::Runtime & runtime)>&& callback) {
runtimeScheduler->scheduleWork(std::move(callback));
};
bufferedRuntimeExecutor_ =
std::make_shared<BufferedRuntimeExecutor>(pipedRuntimeExecutor);
}
void ReactInstance::unregisterFromInspector() {
if (inspectorTarget_) {
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
// getBufferedRuntimeExecutor() 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));
}
};
}
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) {
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_)](jsi::Runtime& runtime) {
try {
SystraceSection s("ReactInstance::loadScript");
bool hasLogger(ReactMarker::logTaggedMarkerBridgelessImpl);
if (hasLogger) {
ReactMarker::logTaggedMarkerBridgeless(
ReactMarker::RUN_JS_BUNDLE_START, scriptName.c_str());
}
runtime.evaluateJavaScript(buffer, sourceURL);
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();
}
} catch (jsi::JSError& error) {
// Handle uncaught JS errors during loading JS bundle
*hasFatalJsError_ = true;
this->jsErrorHandler_.handleJsError(error, true);
}
});
}
/*
* 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,
const folly::dynamic& args) {
// TODO (C++ 20): This code previously implicitly captured `this` in a [=]
// capture group. Was it meaning to pass modules_ by value?
bufferedRuntimeExecutor_->execute([=, this](jsi::Runtime& runtime) {
SystraceSection s(
"ReactInstance::callFunctionOnModule",
"moduleName",
moduleName,
"methodName",
methodName);
if (modules_.find(moduleName) == modules_.end()) {
std::ostringstream knownModules;
int i = 0;
for (auto it = modules_.begin(); it != modules_.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(modules_.size()) + "):" + knownModules.str() +
". Did you forget to call `RN$registerCallableModule`?");
}
auto module = modules_[moduleName]->factory.call(runtime).asObject(runtime);
auto method = module.getProperty(runtime, methodName.c_str());
if (method.isUndefined()) {
throw jsi::JSError(
runtime,
"Failed to call into JavaScript module method " + moduleName + "." +
methodName + ". Module exists, but the method is undefined.");
}
std::vector<jsi::Value> jsArgs;
for (auto& arg : args) {
jsArgs.push_back(jsi::valueFromDynamic(runtime, arg));
}
method.asObject(runtime).asFunction(runtime).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) {
SystraceSection s("ReactInstance::registerSegment");
const auto tag = folly::to<std::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);
if (hasLogger) {
ReactMarker::logTaggedMarkerBridgeless(
ReactMarker::REGISTER_JS_SEGMENT_START, tag.c_str());
}
LOG(WARNING) << "Starting to evaluate segment " << segmentId
<< " in ReactInstance::registerSegment";
runtime.evaluateJavaScript(
buffer, JSExecutor::getSyntheticBundlePath(segmentId, segmentPath));
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,
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));
}
}
} // namespace
void ReactInstance::initializeRuntime(
JSRuntimeFlags options,
BindingsInstallFunc bindingsInstallFunc) noexcept {
runtimeScheduler_->scheduleWork([this, options, bindingsInstallFunc](
jsi::Runtime& runtime) {
SystraceSection s("ReactInstance::initializeRuntime");
bindNativePerformanceNow(runtime);
RuntimeSchedulerBinding::createAndInstallIfNeeded(
runtime, runtimeScheduler_);
defineReactInstanceFlags(runtime, options);
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].asObject(runtime).isFunction(runtime)) {
throw jsi::JSError(
runtime,
"The second argument to registerCallableModule must be a function that returns the JS module.");
}
modules_[name] = std::make_shared<CallableModule>(
args[1].getObject(runtime).asFunction(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;
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) {
SystraceSection 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,93 @@
/*
* 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/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 {
struct CallableModule {
explicit CallableModule(jsi::Function factory)
: factory(std::move(factory)) {}
jsi::Function factory;
};
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::JsErrorHandlingFunc JsErrorHandlingFunc,
jsinspector_modern::PageTarget* 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);
void registerSegment(uint32_t segmentId, const std::string& segmentPath);
void callFunctionOnModule(
const std::string& moduleName,
const std::string& methodName,
const 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::shared_ptr<CallableModule>> modules_;
std::shared_ptr<RuntimeScheduler> runtimeScheduler_;
JsErrorHandler jsErrorHandler_;
// Whether there are errors caught during bundle loading
std::shared_ptr<bool> hasFatalJsError_;
jsinspector_modern::InstanceTarget* inspectorTarget_{nullptr};
jsinspector_modern::RuntimeTarget* runtimeInspectorTarget_{nullptr};
jsinspector_modern::PageTarget* parentInspectorTarget_{nullptr};
};
} // namespace facebook::react

View File

@@ -0,0 +1,415 @@
/*
* 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/SystraceSection.h>
#include <utility>
namespace facebook::react {
TimerManager::TimerManager(
std::unique_ptr<PlatformTimerRegistry> platformTimerRegistry) noexcept
: platformTimerRegistry_(std::move(platformTimerRegistry)) {}
void TimerManager::setRuntimeExecutor(
RuntimeExecutor runtimeExecutor) noexcept {
runtimeExecutor_ = runtimeExecutor;
}
std::shared_ptr<TimerHandle> TimerManager::createReactNativeMicrotask(
jsi::Function&& callback,
std::vector<jsi::Value>&& args) {
auto sharedCallback = std::make_shared<TimerCallback>(
std::move(callback), std::move(args), /* repeat */ false);
// Get the id for the callback.
uint32_t timerID = timerIndex_++;
timers_[timerID] = std::move(sharedCallback);
reactNativeMicrotasksQueue_.push_back(timerID);
return std::make_shared<TimerHandle>(timerID);
}
void TimerManager::callReactNativeMicrotasks(jsi::Runtime& runtime) {
std::vector<uint32_t> reactNativeMicrotasksQueue;
while (!reactNativeMicrotasksQueue_.empty()) {
reactNativeMicrotasksQueue.clear();
reactNativeMicrotasksQueue.swap(reactNativeMicrotasksQueue_);
for (auto reactNativeMicrotaskID : reactNativeMicrotasksQueue) {
// ReactNativeMicrotasks can clear other scheduled reactNativeMicrotasks.
if (timers_.count(reactNativeMicrotaskID) > 0) {
timers_[reactNativeMicrotaskID]->invoke(runtime);
timers_.erase(reactNativeMicrotaskID);
}
}
}
}
std::shared_ptr<TimerHandle> TimerManager::createTimer(
jsi::Function&& callback,
std::vector<jsi::Value>&& args,
double delay) {
auto sharedCallback = std::make_shared<TimerCallback>(
std::move(callback), std::move(args), false);
// Get the id for the callback.
uint32_t timerID = timerIndex_++;
timers_[timerID] = std::move(sharedCallback);
platformTimerRegistry_->createTimer(timerID, delay);
return std::make_shared<TimerHandle>(timerID);
}
std::shared_ptr<TimerHandle> TimerManager::createRecurringTimer(
jsi::Function&& callback,
std::vector<jsi::Value>&& args,
double delay) {
auto sharedCallback = std::make_shared<TimerCallback>(
std::move(callback), std::move(args), true);
// Get the id for the callback.
uint32_t timerID = timerIndex_++;
timers_[timerID] = std::move(sharedCallback);
platformTimerRegistry_->createRecurringTimer(timerID, delay);
return std::make_shared<TimerHandle>(timerID);
}
void TimerManager::deleteReactNativeMicrotask(
jsi::Runtime& runtime,
std::shared_ptr<TimerHandle> timerHandle) {
if (timerHandle == nullptr) {
throw jsi::JSError(
runtime, "clearReactNativeMicrotask was called with an invalid handle");
}
for (auto it = reactNativeMicrotasksQueue_.begin();
it != reactNativeMicrotasksQueue_.end();
it++) {
if ((*it) == timerHandle->index()) {
reactNativeMicrotasksQueue_.erase(it);
break;
}
}
if (timers_.find(timerHandle->index()) != timers_.end()) {
timers_.erase(timerHandle->index());
}
}
void TimerManager::deleteTimer(
jsi::Runtime& runtime,
std::shared_ptr<TimerHandle> timerHandle) {
if (timerHandle == nullptr) {
throw jsi::JSError(runtime, "clearTimeout called with an invalid handle");
}
platformTimerRegistry_->deleteTimer(timerHandle->index());
if (timers_.find(timerHandle->index()) != timers_.end()) {
timers_.erase(timerHandle->index());
}
}
void TimerManager::deleteRecurringTimer(
jsi::Runtime& runtime,
std::shared_ptr<TimerHandle> timerHandle) {
if (timerHandle == nullptr) {
throw jsi::JSError(runtime, "clearInterval called with an invalid handle");
}
platformTimerRegistry_->deleteTimer(timerHandle->index());
if (timers_.find(timerHandle->index()) != timers_.end()) {
timers_.erase(timerHandle->index());
}
}
void TimerManager::callTimer(uint32_t timerID) {
runtimeExecutor_([this, timerID](jsi::Runtime& runtime) {
SystraceSection s("TimerManager::callTimer");
if (timers_.count(timerID) > 0) {
timers_[timerID]->invoke(runtime);
// Invoking a timer has the potential to delete it. Double check the timer
// still exists before accessing it again.
if (timers_.count(timerID) > 0 && !timers_[timerID]->repeat) {
timers_.erase(timerID);
}
}
});
}
void TimerManager::attachGlobals(jsi::Runtime& runtime) {
// Install host functions for timers.
// TODO (T45786383): Add missing timer functions from JSTimers
// TODO (T96212789): Remove when JSVM microtask queue is used everywhere in
// bridgeless mode. This is being overwritten in JS in that case.
runtime.global().setProperty(
runtime,
"setImmediate",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "setImmediate"),
2, // Function, ...args
[this](
jsi::Runtime& rt,
const jsi::Value& thisVal,
const jsi::Value* args,
size_t count) {
if (count == 0) {
throw jsi::JSError(
rt,
"setImmediate must be called with at least one argument (a function to call)");
}
if (!args[0].isObject() || !args[0].asObject(rt).isFunction(rt)) {
throw jsi::JSError(
rt, "The first argument to setImmediate must be a function.");
}
auto callback = args[0].getObject(rt).getFunction(rt);
// Package up the remaining argument values into one place.
std::vector<jsi::Value> moreArgs;
for (size_t extraArgNum = 1; extraArgNum < count; extraArgNum++) {
moreArgs.emplace_back(rt, args[extraArgNum]);
}
auto handle = createReactNativeMicrotask(
std::move(callback), std::move(moreArgs));
return jsi::Object::createFromHostObject(rt, handle);
}));
runtime.global().setProperty(
runtime,
"clearImmediate",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "clearImmediate"),
1, // handle
[this](
jsi::Runtime& rt,
const jsi::Value& thisVal,
const jsi::Value* args,
size_t count) {
if (count == 0 || !args[0].isObject() ||
!args[0].asObject(rt).isHostObject<TimerHandle>(rt)) {
return jsi::Value::undefined();
}
std::shared_ptr<TimerHandle> handle =
args[0].asObject(rt).asHostObject<TimerHandle>(rt);
deleteReactNativeMicrotask(rt, handle);
return jsi::Value::undefined();
}));
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)) {
throw jsi::JSError(
rt, "The first argument to setTimeout must be a function.");
}
auto callback = args[0].getObject(rt).getFunction(rt);
if (count > 1 && !args[1].isNumber() && !args[1].isUndefined()) {
throw jsi::JSError(
rt,
"The second argument to setTimeout must be a number or undefined.");
}
auto delay =
count > 1 && args[1].isNumber() ? args[1].getNumber() : 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]);
}
auto handle =
createTimer(std::move(callback), std::move(moreArgs), delay);
return jsi::Object::createFromHostObject(rt, handle);
}));
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].isObject() ||
!args[0].asObject(rt).isHostObject<TimerHandle>(rt)) {
return jsi::Value::undefined();
}
std::shared_ptr<TimerHandle> host =
args[0].asObject(rt).asHostObject<TimerHandle>(rt);
deleteTimer(rt, host);
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 < 2) {
throw jsi::JSError(
rt,
"setInterval must be called with at least two arguments (the function to call and the delay).");
}
if (!args[0].isObject() || !args[0].asObject(rt).isFunction(rt)) {
throw jsi::JSError(
rt, "The first argument to setInterval must be a function.");
}
auto callback = args[0].getObject(rt).getFunction(rt);
if (!args[1].isNumber()) {
throw jsi::JSError(
rt, "The second argument to setInterval must be a number.");
}
auto delay = args[1].getNumber();
// 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]);
}
auto handle = createRecurringTimer(
std::move(callback), std::move(moreArgs), delay);
return jsi::Object::createFromHostObject(rt, handle);
}));
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].isObject() ||
!args[0].asObject(rt).isHostObject<TimerHandle>(rt)) {
return jsi::Value::undefined();
}
std::shared_ptr<TimerHandle> host =
args[0].asObject(rt).asHostObject<TimerHandle>(rt);
deleteRecurringTimer(rt, host);
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.");
}
using CallbackContainer = std::tuple<jsi::Function>;
auto callback = jsi::Function::createFromHostFunction(
rt,
jsi::PropNameID::forAscii(rt, "RN$rafFn"),
0,
[callbackContainer = std::make_shared<CallbackContainer>(
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 std::get<0>(*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.
auto handle = createTimer(
std::move(callback),
std::vector<jsi::Value>(),
/* delay */ 0);
return jsi::Object::createFromHostObject(rt, handle);
}));
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].isObject() ||
!args[0].asObject(rt).isHostObject<TimerHandle>(rt)) {
return jsi::Value::undefined();
}
std::shared_ptr<TimerHandle> host =
args[0].asObject(rt).asHostObject<TimerHandle>(rt);
deleteTimer(rt, host);
return jsi::Value::undefined();
}));
}
} // namespace facebook::react

View File

@@ -0,0 +1,115 @@
/*
* 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 {
/*
* A HostObject subclass representing the result of a setTimeout call.
* Can be used as an argument to clearTimeout.
*/
class TimerHandle : public jsi::HostObject {
public:
explicit TimerHandle(uint32_t index) : index_(index) {}
uint32_t index() const {
return index_;
}
~TimerHandle() override = default;
private:
// Index in the timeouts_ map of the owning SetTimeoutQueue.
uint32_t index_;
};
/*
* Wraps a jsi::Function to make it copyable so we can pass it into a lambda.
*/
struct TimerCallback {
TimerCallback(TimerCallback&&) = default;
TimerCallback(
jsi::Function callback,
std::vector<jsi::Value> args,
bool repeat)
: callback_(std::move(callback)),
args_(std::move(args)),
repeat(repeat) {}
void invoke(jsi::Runtime& runtime) {
callback_.call(runtime, args_.data(), args_.size());
}
jsi::Function callback_;
const std::vector<jsi::Value> args_;
bool repeat;
};
class TimerManager {
public:
explicit TimerManager(
std::unique_ptr<PlatformTimerRegistry> platformTimerRegistry) noexcept;
void setRuntimeExecutor(RuntimeExecutor runtimeExecutor) noexcept;
void callReactNativeMicrotasks(jsi::Runtime& runtime);
void callTimer(uint32_t);
void attachGlobals(jsi::Runtime& runtime);
private:
std::shared_ptr<TimerHandle> createReactNativeMicrotask(
jsi::Function&& callback,
std::vector<jsi::Value>&& args);
void deleteReactNativeMicrotask(
jsi::Runtime& runtime,
std::shared_ptr<TimerHandle> handle);
std::shared_ptr<TimerHandle> createTimer(
jsi::Function&& callback,
std::vector<jsi::Value>&& args,
double delay);
void deleteTimer(jsi::Runtime& runtime, std::shared_ptr<TimerHandle> handle);
std::shared_ptr<TimerHandle> createRecurringTimer(
jsi::Function&& callback,
std::vector<jsi::Value>&& args,
double delay);
void deleteRecurringTimer(
jsi::Runtime& runtime,
std::shared_ptr<TimerHandle> handle);
RuntimeExecutor runtimeExecutor_;
std::unique_ptr<PlatformTimerRegistry> platformTimerRegistry_;
// A map (id => callback func) of the currently active JS timers
std::unordered_map<uint32_t, std::shared_ptr<TimerCallback>> timers_;
// Each timeout that is registered on this queue gets a sequential id. This
// is the global count from which those are assigned.
uint32_t timerIndex_{0};
// 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<uint32_t> reactNativeMicrotasksQueue_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,36 @@
# 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)
add_compile_options(-std=c++20)
file(GLOB_RECURSE bridgeless_hermes_SRC CONFIGURE_DEPENDS *.cpp)
add_library(
bridgelesshermes
STATIC
${bridgeless_hermes_SRC}
)
target_include_directories(bridgelesshermes PUBLIC .)
target_link_libraries(bridgelesshermes
jsireact
hermes-engine::libhermes
hermes_inspector_modern
jsi
hermes_executor_common
bridgeless
react_featureflags
jsinspector
)
if(${CMAKE_BUILD_TYPE} MATCHES Debug)
target_compile_options(
bridgelesshermes
PRIVATE
-DHERMES_ENABLE_DEBUGGER=1
)
endif()

View File

@@ -0,0 +1,196 @@
/*
* 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/HermesRuntimeAgentDelegate.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>
#include <hermes/inspector/RuntimeAdapter.h>
#include <jsi/decorator.h>
#endif
using namespace facebook::hermes;
using namespace facebook::jsi;
namespace facebook::react {
#ifdef HERMES_ENABLE_DEBUGGER
// 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
class HermesJSRuntime : public JSRuntime {
public:
HermesJSRuntime(
std::unique_ptr<HermesRuntime> runtime,
std::shared_ptr<MessageQueueThread> msgQueueThread)
: runtime_(std::move(runtime)),
msgQueueThread_(std::move(msgQueueThread)) {}
jsi::Runtime& getRuntime() noexcept override {
return *runtime_;
}
std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate> createAgentDelegate(
jsinspector_modern::FrontendChannel frontendChannel,
jsinspector_modern::SessionState& sessionState,
std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate::ExportedState>
previouslyExportedState,
const jsinspector_modern::ExecutionContextDescription&
executionContextDescription) override {
return std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate>(
new jsinspector_modern::HermesRuntimeAgentDelegate(
frontendChannel,
sessionState,
std::move(previouslyExportedState),
executionContextDescription,
runtime_,
[msgQueueThreadWeak = std::weak_ptr(msgQueueThread_),
runtimeWeak = std::weak_ptr(runtime_)](auto fn) {
auto msgQueueThread = msgQueueThreadWeak.lock();
if (!msgQueueThread) {
return;
}
msgQueueThread->runOnQueue([runtimeWeak, fn]() {
auto runtime = runtimeWeak.lock();
if (!runtime) {
return;
}
fn(*runtime);
});
}));
}
private:
std::shared_ptr<HermesRuntime> runtime_;
std::shared_ptr<MessageQueueThread> msgQueueThread_;
};
std::unique_ptr<JSRuntime> HermesInstance::createJSRuntime(
std::shared_ptr<const ReactNativeConfig> reactNativeConfig,
std::shared_ptr<::hermes::vm::CrashManager> cm,
std::shared_ptr<MessageQueueThread> msgQueueThread) noexcept {
assert(msgQueueThread != nullptr);
int64_t vmExperimentFlags = reactNativeConfig
? reactNativeConfig->getInt64("ios_hermes:vm_experiment_flags")
: 0;
int64_t heapSizeConfig = reactNativeConfig
? reactNativeConfig->getInt64("ios_hermes:rn_heap_size_mb")
: 0;
// Default to 3GB if MobileConfigs is not available
auto heapSizeMB = heapSizeConfig > 0
? static_cast<::hermes::vm::gcheapsize_t>(heapSizeConfig)
: 3072;
::hermes::vm::RuntimeConfig::Builder runtimeConfigBuilder =
::hermes::vm::RuntimeConfig::Builder()
.withGCConfig(::hermes::vm::GCConfig::Builder()
.withMaxHeapSize(heapSizeMB << 20)
.withName("RNBridgeless")
// 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.
.withAllocInYoung(false)
.withRevertToYGAtTTI(true)
.build())
.withEnableSampleProfiling(true)
.withMicrotaskQueue(ReactNativeFeatureFlags::enableMicrotasks())
.withVMExperimentFlags(vmExperimentFlags);
if (cm) {
runtimeConfigBuilder.withCrashMgr(cm);
}
std::unique_ptr<HermesRuntime> hermesRuntime =
hermes::makeHermesRuntime(runtimeConfigBuilder.build());
#ifdef HERMES_ENABLE_DEBUGGER
auto& inspectorFlags = jsinspector_modern::InspectorFlags::getInstance();
if (!inspectorFlags.getEnableModernCDPRegistry()) {
std::unique_ptr<DecoratedRuntime> decoratedRuntime =
std::make_unique<DecoratedRuntime>(
std::move(hermesRuntime), msgQueueThread);
return std::make_unique<JSIRuntimeHolder>(std::move(decoratedRuntime));
}
#endif
return std::make_unique<HermesJSRuntime>(
std::move(hermesRuntime), std::move(msgQueueThread));
}
} // 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 <cxxreact/MessageQueueThread.h>
#include <hermes/hermes.h>
#include <jsi/jsi.h>
#include <react/config/ReactNativeConfig.h>
#include <react/runtime/JSRuntimeFactory.h>
namespace facebook::react {
class HermesInstance {
public:
static std::unique_ptr<JSRuntime> createJSRuntime(
std::shared_ptr<const ReactNativeConfig> reactNativeConfig,
std::shared_ptr<::hermes::vm::CrashManager> cm,
std::shared_ptr<MessageQueueThread> msgQueueThread) noexcept;
};
} // namespace facebook::react

View File

@@ -0,0 +1,159 @@
/*
* 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 <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];
[instanceDelegate instance:[OCMArg any] didReceiveJSErrorStack:stack message:@"message" exceptionId:5 isFatal:YES];
OCMVerify(
OCMTimes(1),
[_mockHostDelegate host:_subject didReceiveJSErrorStack:stack message:@"message" exceptionId:5 isFatal:YES]);
}
- (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,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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(-std=c++20)
file(GLOB_RECURSE bridgeless_nativeviewconfig_SRC CONFIGURE_DEPENDS *.cpp)
add_library(
bridgelessnativeviewconfig
STATIC
${bridgeless_nativeviewconfig_SRC}
)
target_include_directories(bridgelessnativeviewconfig PUBLIC .)
target_link_libraries(bridgelessnativeviewconfig jsi)

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()>&& 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();
};
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(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(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,29 @@
/*
* 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 {
using ProviderType = std::function<jsi::Value()>;
/*
* 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()>&& provider);
void install(
jsi::Runtime& runtime,
const std::string& name,
std::function<jsi::Value(std::string)>&& provider);
} // namespace facebook::react::LegacyUIManagerConstantsProviderBinding

View File

@@ -0,0 +1,78 @@
# 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
folly_config = get_folly_config()
folly_compiler_flags = folly_config[:compiler_flags]
folly_version = folly_config[:version]
folly_dep_name = 'RCT-Folly/Fabric'
boost_compiler_flags = '-Wno-documentation'
header_search_paths = [
"$(PODS_ROOT)/boost",
"$(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 = "ReactCommon/*.{mm,h}"
s.header_dir = "ReactCommon"
s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => header_search_paths,
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
"GCC_WARN_PEDANTIC" => "YES" }
s.compiler_flags = folly_compiler_flags + ' ' + boost_compiler_flags
if ENV['USE_FRAMEWORKS']
s.header_mappings_dir = './'
s.module_name = 'React_RuntimeApple'
end
s.dependency folly_dep_name, folly_version
s.dependency "React-jsiexecutor"
s.dependency "React-cxxreact"
s.dependency "React-callinvoker"
s.dependency "React-runtimeexecutor"
s.dependency "React-utils"
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"
if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1"
s.dependency "hermes-engine"
s.dependency "React-RuntimeHermes"
s.exclude_files = "ReactCommon/RCTJscInstance.{mm,h}"
else
s.dependency "React-jsc"
s.exclude_files = "ReactCommon/RCTHermesInstance.{mm,h}"
end
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 <UIKit/UIKit.h>
#import <cxxreact/MessageQueueThread.h>
#import <hermes/Public/CrashManager.h>
#import <jsi/jsi.h>
#import <react/runtime/JSRuntimeFactory.h>
#import <react/runtime/hermes/HermesInstance.h>
namespace facebook {
namespace react {
using CrashManagerProvider =
std::function<std::shared_ptr<::hermes::vm::CrashManager>()>;
// ObjC++ wrapper for HermesInstance.cpp
class RCTHermesInstance : public JSRuntimeFactory {
public:
RCTHermesInstance();
RCTHermesInstance(
std::shared_ptr<const ReactNativeConfig> reactNativeConfig,
CrashManagerProvider crashManagerProvider);
std::unique_ptr<JSRuntime> createJSRuntime(
std::shared_ptr<MessageQueueThread> msgQueueThread) noexcept override;
~RCTHermesInstance(){};
private:
std::shared_ptr<const ReactNativeConfig> _reactNativeConfig;
CrashManagerProvider _crashManagerProvider;
std::unique_ptr<HermesInstance> _hermesInstance;
};
} // namespace react
} // namespace facebook

View File

@@ -0,0 +1,31 @@
/*
* 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"
namespace facebook {
namespace react {
RCTHermesInstance::RCTHermesInstance() : RCTHermesInstance(nullptr, nullptr) {}
RCTHermesInstance::RCTHermesInstance(
std::shared_ptr<const ReactNativeConfig> reactNativeConfig,
CrashManagerProvider crashManagerProvider)
: _reactNativeConfig(std::move(reactNativeConfig)),
_crashManagerProvider(std::move(crashManagerProvider)),
_hermesInstance(std::make_unique<HermesInstance>())
{
}
std::unique_ptr<JSRuntime> RCTHermesInstance::createJSRuntime(
std::shared_ptr<MessageQueueThread> msgQueueThread) noexcept
{
return _hermesInstance->createJSRuntime(
_reactNativeConfig, _crashManagerProvider ? _crashManagerProvider() : nullptr, msgQueueThread);
}
} // namespace react
} // namespace facebook

View File

@@ -0,0 +1,18 @@
/*
* 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;
@end

View File

@@ -0,0 +1,88 @@
/*
* 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;
@protocol RCTTurboModuleManagerDelegate;
typedef NSURL *_Nullable (^RCTHostBundleURLProvider)(void);
// Runtime API
@protocol RCTHostDelegate <NSObject>
- (void)host:(RCTHost *)host
didReceiveJSErrorStack:(NSArray<NSDictionary<NSString *, id> *> *)stack
message:(NSString *)message
exceptionId:(NSUInteger)exceptionId
isFatal:(BOOL)isFatal;
- (void)hostDidStart:(RCTHost *)host;
@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 NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
hostDelegate:(id<RCTHostDelegate>)hostDelegate
turboModuleManagerDelegate:(id<RCTTurboModuleManagerDelegate>)turboModuleManagerDelegate
jsEngineProvider:(RCTHostJSEngineProvider)jsEngineProvider
launchOptions:(nullable NSDictionary *)launchOptions __deprecated;
@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;
- (RCTSurfacePresenter *)getSurfacePresenter __attribute__((deprecated("Use `surfacePresenter` property instead.")));
// Native module API
- (RCTModuleRegistry *)getModuleRegistry __attribute__((deprecated("Use `moduleRegistry` property instead.")));
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,412 @@
/*
* 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/RCTFabricSurface.h>
#import <React/RCTInspectorDevServerHelper.h>
#import <React/RCTJSThread.h>
#import <React/RCTLog.h>
#import <React/RCTMockDef.h>
#import <React/RCTPerformanceLogger.h>
#import <React/RCTReloadCommand.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;
class RCTHostPageTargetDelegate : public facebook::react::jsinspector_modern::PageTargetDelegate {
public:
RCTHostPageTargetDelegate(RCTHost *host) : host_(host) {}
void onReload(const PageReloadRequest &request) override
{
RCTAssertMainQueue();
[static_cast<id<RCTReloadListener>>(host_) didReceiveReloadCommand];
}
private:
__weak RCTHost *host_;
};
@interface RCTHost () <RCTReloadListener, RCTInstanceDelegate>
@end
@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;
// All the surfaces that need to be started after main bundle execution
NSMutableArray<RCTFabricSurface *> *_surfaceStartBuffer;
std::mutex _surfaceStartBufferMutex;
RCTInstanceInitialBundleLoadCompletionBlock _onInitialBundleLoad;
std::vector<__weak RCTFabricSurface *> _attachedSurfaces;
RCTModuleRegistry *_moduleRegistry;
std::unique_ptr<RCTHostPageTargetDelegate> _inspectorPageDelegate;
std::shared_ptr<jsinspector_modern::PageTarget> _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
{
if (self = [super init]) {
_hostDelegate = hostDelegate;
_turboModuleManagerDelegate = turboModuleManagerDelegate;
_surfaceStartBuffer = [NSMutableArray new];
_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];
/**
* TODO (T108401473) Remove _onInitialBundleLoad, because it was initially
* introduced to start surfaces after the main JSBundle was fully executed.
* It is no longer needed because Fabric now dispatches all native-to-JS calls
* onto the JS thread, including JS calls to start Fabric surfaces.
* JS calls should be dispatched using the BufferedRuntimeExecutor, which can buffer
* JS calls before the main JSBundle finishes execution, and execute them after.
*/
_onInitialBundleLoad = ^{
RCTHost *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
NSArray<RCTFabricSurface *> *unstartedSurfaces = @[];
{
std::lock_guard<std::mutex> guard{strongSelf->_surfaceStartBufferMutex};
unstartedSurfaces = [NSArray arrayWithArray:strongSelf->_surfaceStartBuffer];
strongSelf->_surfaceStartBuffer = nil;
}
for (RCTFabricSurface *surface in unstartedSurfaces) {
[surface start];
}
};
// Listen to reload commands
dispatch_async(dispatch_get_main_queue(), ^{
RCTRegisterReloadCommandListener(self);
});
_inspectorPageDelegate = std::make_unique<RCTHostPageTargetDelegate>(self);
}
return self;
}
#pragma mark - Public
- (void)start
{
if (_bundleURLProvider) {
[self _setBundleURL:_bundleURLProvider()];
}
auto &inspectorFlags = jsinspector_modern::InspectorFlags::getInstance();
if (inspectorFlags.getEnableModernCDPRegistry() && !_inspectorPageId.has_value()) {
_inspectorTarget =
facebook::react::jsinspector_modern::PageTarget::create(*_inspectorPageDelegate, [](auto callback) {
RCTExecuteOnMainQueue(^{
callback();
});
});
__weak RCTHost *weakSelf = self;
_inspectorPageId = facebook::react::jsinspector_modern::getInspectorInstance().addPage(
"React Native Bridgeless (Experimental)",
/* 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),
{
.integrationName = "iOS Bridgeless (RCTHost)",
});
},
{.nativePageReloads = 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
onInitialBundleLoad:_onInitialBundleLoad
moduleRegistry:_moduleRegistry
parentInspectorTarget:_inspectorTarget.get()
launchOptions:_launchOptions];
[_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];
{
std::lock_guard<std::mutex> guard{_surfaceStartBufferMutex};
if (_surfaceStartBuffer) {
[_surfaceStartBuffer addObject:surface];
return surface;
}
}
[surface start];
return surface;
}
- (RCTFabricSurface *)createSurfaceWithModuleName:(NSString *)moduleName initialProperties:(NSDictionary *)properties
{
return [self createSurfaceWithModuleName:moduleName mode:DisplayMode::Visible initialProperties:properties];
}
- (RCTModuleRegistry *)moduleRegistry
{
return _moduleRegistry;
}
// Deprecated
- (RCTModuleRegistry *)getModuleRegistry
{
return self.moduleRegistry;
}
- (RCTSurfacePresenter *)surfacePresenter
{
return [_instance surfacePresenter];
}
// Deprecated
- (RCTSurfacePresenter *)getSurfacePresenter
{
return self.surfacePresenter;
}
- (void)callFunctionOnJSModule:(NSString *)moduleName method:(NSString *)method args:(NSArray *)args
{
[_instance callFunctionOnJSModule:moduleName method:method args:args];
}
#pragma mark - RCTReloadListener
- (void)didReceiveReloadCommand
{
[_instance invalidate];
_instance = nil;
if (_bundleURLProvider) {
[self _setBundleURL:_bundleURLProvider()];
}
// Ensure all attached surfaces are restarted after reload
{
std::lock_guard<std::mutex> guard{_surfaceStartBufferMutex};
_surfaceStartBuffer = [NSMutableArray arrayWithArray:[self _getAttachedSurfaces]];
}
_instance = [[RCTInstance alloc] initWithDelegate:self
jsRuntimeFactory:[self _provideJSEngine]
bundleManager:_bundleManager
turboModuleManagerDelegate:_turboModuleManagerDelegate
onInitialBundleLoad:_onInitialBundleLoad
moduleRegistry:_moduleRegistry
parentInspectorTarget:_inspectorTarget.get()
launchOptions:_launchOptions];
[_hostDelegate hostDidStart:self];
for (RCTFabricSurface *surface in [self _getAttachedSurfaces]) {
[surface resetWithSurfacePresenter:self.surfacePresenter];
}
}
- (void)dealloc
{
[_instance invalidate];
if (_inspectorPageId.has_value()) {
facebook::react::jsinspector_modern::getInspectorInstance().removePage(*_inspectorPageId);
_inspectorPageId.reset();
_inspectorTarget.reset();
}
}
#pragma mark - RCTInstanceDelegate
- (void)instance:(RCTInstance *)instance
didReceiveJSErrorStack:(NSArray<NSDictionary<NSString *, id> *> *)stack
message:(NSString *)message
exceptionId:(NSUInteger)exceptionId
isFatal:(BOOL)isFatal
{
[_hostDelegate host:self didReceiveJSErrorStack:stack message:message exceptionId:exceptionId isFatal:isFatal];
}
- (void)instance:(RCTInstance *)instance didInitializeRuntime:(facebook::jsi::Runtime &)runtime
{
[self.runtimeDelegate host:self didInitializeRuntime:runtime];
}
#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;
}
#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);
}
@end

View File

@@ -0,0 +1,78 @@
/*
* 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 <jsinspector-modern/ReactCdp.h>
#import <react/renderer/mapbuffer/MapBuffer.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;
@protocol RCTTurboModuleManagerDelegate;
@protocol RCTInstanceDelegate <RCTContextContainerHandling>
- (void)instance:(RCTInstance *)instance
didReceiveJSErrorStack:(NSArray<NSDictionary<NSString *, id> *> *)stack
message:(NSString *)message
exceptionId:(NSUInteger)exceptionId
isFatal:(BOOL)isFatal;
- (void)instance:(RCTInstance *)instance didInitializeRuntime:(facebook::jsi::Runtime &)runtime;
@end
typedef void (^_Null_unspecified RCTInstanceInitialBundleLoadCompletionBlock)();
/**
* 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
onInitialBundleLoad:(RCTInstanceInitialBundleLoadCompletionBlock)onInitialBundleLoad
moduleRegistry:(RCTModuleRegistry *)moduleRegistry
parentInspectorTarget:(facebook::react::jsinspector_modern::PageTarget *)parentInspectorTarget
launchOptions:(nullable NSDictionary *)launchOptions;
- (void)callFunctionOnJSModule:(NSString *)moduleName method:(NSString *)method args:(NSArray *)args;
- (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,520 @@
/*
* 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 <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/RCTDevSettings.h>
#import <React/RCTDisplayLink.h>
#import <React/RCTEventDispatcherProtocol.h>
#import <React/RCTFollyConvert.h>
#import <React/RCTJavaScriptLoader.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/RCTTurboModuleManager.h>
#import <ReactCommon/RuntimeExecutor.h>
#import <cxxreact/ReactMarker.h>
#import <jsinspector-modern/ReactCdp.h>
#import <jsireact/JSIExecutor.h>
#import <react/runtime/BridgelessJSCallInvoker.h>
#import <react/utils/ContextContainer.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 RCTInstance () <RCTTurboModuleManagerDelegate, RCTTurboModuleManagerRuntimeHandler>
@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;
RCTInstanceInitialBundleLoadCompletionBlock _onInitialBundleLoad;
RCTTurboModuleManager *_turboModuleManager;
std::mutex _invalidationMutex;
std::atomic<bool> _valid;
RCTJSThreadManager *_jsThreadManager;
NSDictionary *_launchOptions;
// APIs supporting interop with native modules and view managers
RCTBridgeModuleDecorator *_bridgeModuleDecorator;
jsinspector_modern::PageTarget *_parentInspectorTarget;
}
#pragma mark - Public
- (instancetype)initWithDelegate:(id<RCTInstanceDelegate>)delegate
jsRuntimeFactory:(std::shared_ptr<facebook::react::JSRuntimeFactory>)jsRuntimeFactory
bundleManager:(RCTBundleManager *)bundleManager
turboModuleManagerDelegate:(id<RCTTurboModuleManagerDelegate>)tmmDelegate
onInitialBundleLoad:(RCTInstanceInitialBundleLoadCompletionBlock)onInitialBundleLoad
moduleRegistry:(RCTModuleRegistry *)moduleRegistry
parentInspectorTarget:(jsinspector_modern::PageTarget *)parentInspectorTarget
launchOptions:(nullable NSDictionary *)launchOptions
{
if (self = [super init]) {
_performanceLogger = [RCTPerformanceLogger new];
registerPerformanceLoggerHooks(_performanceLogger);
[_performanceLogger markStartForTag:RCTPLReactInstanceInit];
_delegate = delegate;
_jsRuntimeFactory = jsRuntimeFactory;
_appTMMDelegate = tmmDelegate;
_jsThreadManager = [RCTJSThreadManager new];
_onInitialBundleLoad = onInitialBundleLoad;
_bridgeModuleDecorator = [[RCTBridgeModuleDecorator alloc] initWithViewRegistry:[RCTViewRegistry new]
moduleRegistry:moduleRegistry
bundleManager:bundleManager
callableJSModules:[RCTCallableJSModules new]];
_parentInspectorTarget = parentInspectorTarget;
{
__weak __typeof(self) weakSelf = self;
[_bridgeModuleDecorator.callableJSModules
setBridgelessJSModuleMethodInvoker:^(
NSString *moduleName, NSString *methodName, NSArray *args, dispatch_block_t onComplete) {
// TODO: Make RCTInstance call onComplete
[weakSelf callFunctionOnJSModule:moduleName method:methodName args:args];
}];
}
_launchOptions = launchOptions;
NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
[defaultCenter addObserver:self
selector:@selector(_notifyEventDispatcherObserversOfEvent_DEPRECATED:)
name:@"RCTNotifyEventDispatcherObserversOfEvent_DEPRECATED"
object:nil];
[self _start];
}
return self;
}
- (void)callFunctionOnJSModule:(NSString *)moduleName method:(NSString *)method args:(NSArray *)args
{
if (_valid) {
_reactInstance->callFunctionOnModule(
[moduleName UTF8String], [method UTF8String], convertIdToFollyDynamic(args ?: @[]));
}
}
- (void)invalidate
{
std::lock_guard<std::mutex> lock(_invalidationMutex);
_valid = false;
if (self->_reactInstance) {
self->_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 = 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
{
if ([_appTMMDelegate respondsToSelector:@selector(getModuleClassFromName:)]) {
return [_appTMMDelegate getModuleClassFromName:name];
}
return nil;
}
- (id<RCTTurboModule>)getModuleInstanceFromClass:(Class)moduleClass
{
if ([_appTMMDelegate respondsToSelector:@selector(getModuleInstanceFromClass:)]) {
id<RCTTurboModule> module = [_appTMMDelegate getModuleInstanceFromClass:moduleClass];
[self _attachBridgelessAPIsToModule:module];
return module;
}
return nil;
}
- (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;
}
#pragma mark - RCTTurboModuleManagerRuntimeHandler
- (RuntimeExecutor)runtimeExecutorForTurboModuleManager:(RCTTurboModuleManager *)turboModuleManager
{
if (_valid) {
return _reactInstance->getBufferedRuntimeExecutor();
}
return nullptr;
}
#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 jsErrorHandlingFunc = [=](MapBuffer errorMap) { [weakSelf _handleJSErrorMap:std::move(errorMap)]; };
// Create the React Instance
_reactInstance = std::make_unique<ReactInstance>(
_jsRuntimeFactory->createJSRuntime(_jsThreadManager.jsMessageThread),
_jsThreadManager.jsMessageThread,
timerManager,
jsErrorHandlingFunc,
_parentInspectorTarget);
_valid = true;
RuntimeExecutor bufferedRuntimeExecutor = _reactInstance->getBufferedRuntimeExecutor();
timerManager->setRuntimeExecutor(bufferedRuntimeExecutor);
auto jsCallInvoker = make_shared<BridgelessJSCallInvoker>(bufferedRuntimeExecutor);
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];
_turboModuleManager.runtimeHandler = self;
#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];
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("RuntimeScheduler", 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];
ReactInstance::JSRuntimeFlags options = {
.isProfiling = false, .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 (RCTGetUseNativeViewConfigsInBridgelessMode()) {
installLegacyUIManagerConstantsProviderBinding(runtime);
}
[strongSelf->_delegate instance:strongSelf didInitializeRuntime:runtime];
// Set up Display Link
RCTModuleData *timingModuleData = [[RCTModuleData alloc] initWithModuleInstance:timing
bridge:nil
moduleRegistry:nil
viewRegistry_DEPRECATED:nil
bundleManager:nil
callableJSModules:nil];
[strongSelf->_displayLink registerModuleForFrameUpdates:timing withModuleData:timingModuleData];
[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)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;
[RCTJavaScriptLoader 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];
[strongSelf invalidate];
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];
}];
}
- (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;
_reactInstance->loadScript(std::move(script), url);
[[NSNotificationCenter defaultCenter] postNotificationName:@"RCTInstanceDidLoadBundle" object:nil];
if (_onInitialBundleLoad) {
_onInitialBundleLoad();
_onInitialBundleLoad = nil;
}
}
- (void)_notifyEventDispatcherObserversOfEvent_DEPRECATED:(NSNotification *)notification
{
NSDictionary *userInfo = notification.userInfo;
id<RCTEvent> event = [userInfo objectForKey:@"event"];
RCTModuleRegistry *moduleRegistry = _bridgeModuleDecorator.moduleRegistry;
if (event && moduleRegistry) {
id<RCTEventDispatcherProtocol> legacyEventDispatcher = [moduleRegistry moduleForName:"EventDispatcher"
lazilyLoadIfNecessary:YES];
[legacyEventDispatcher notifyObserversOfEvent:event];
}
}
- (void)_handleJSErrorMap:(facebook::react::MapBuffer)errorMap
{
NSString *message = [NSString stringWithCString:errorMap.getString(JSErrorHandlerKey::kErrorMessage).c_str()
encoding:[NSString defaultCStringEncoding]];
std::vector<facebook::react::MapBuffer> frames = errorMap.getMapBufferList(JSErrorHandlerKey::kAllStackFrames);
NSMutableArray<NSDictionary<NSString *, id> *> *stack = [NSMutableArray new];
for (const facebook::react::MapBuffer &mapBuffer : frames) {
NSDictionary *frame = @{
@"file" : [NSString stringWithCString:mapBuffer.getString(JSErrorHandlerKey::kFrameFileName).c_str()
encoding:[NSString defaultCStringEncoding]],
@"methodName" : [NSString stringWithCString:mapBuffer.getString(JSErrorHandlerKey::kFrameMethodName).c_str()
encoding:[NSString defaultCStringEncoding]],
@"lineNumber" : [NSNumber numberWithInt:mapBuffer.getInt(JSErrorHandlerKey::kFrameLineNumber)],
@"column" : [NSNumber numberWithInt:mapBuffer.getInt(JSErrorHandlerKey::kFrameColumnNumber)],
};
[stack addObject:frame];
}
[_delegate instance:self
didReceiveJSErrorStack:stack
message:message
exceptionId:errorMap.getInt(JSErrorHandlerKey::kExceptionId)
isFatal:errorMap.getBool(JSErrorHandlerKey::kIsFatal)];
}
@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,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.
*/
#import <cxxreact/MessageQueueThread.h>
#import <jsi/jsi.h>
#import <react/runtime/JSRuntimeFactory.h>
namespace facebook {
namespace react {
class RCTJscInstance : public JSRuntimeFactory {
public:
RCTJscInstance();
std::unique_ptr<JSRuntime> createJSRuntime(
std::shared_ptr<MessageQueueThread> msgQueueThread) noexcept override;
~RCTJscInstance(){};
};
} // namespace react
} // namespace facebook

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

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,44 @@
/*
* 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)
{
static 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)
{
auto constantsProvider = [&runtime]() -> jsi::Value { return getConstants(runtime); };
LegacyUIManagerConstantsProviderBinding::install(runtime, "getConstants", std::move(constantsProvider));
}
} // 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,869 @@
/*
* 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 <gmock/gmock.h>
#include <gtest/gtest.h>
#include <ReactCommon/RuntimeExecutor.h>
#include <hermes/hermes.h>
#include <jsi/jsi.h>
#include <react/renderer/mapbuffer/MapBuffer.h>
#include <react/runtime/ReactInstance.h>
using ::testing::_;
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) {
callbackQueue_.push(func);
}
// Unused
void runOnQueueSync(std::function<void()>&&) {}
// Unused
void quitSynchronous() {}
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() {}
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 jsErrorHandlingFunc = [](MapBuffer errorMap) noexcept {
// Do nothing
};
instance_ = std::make_unique<ReactInstance>(
std::move(runtime),
messageQueueThread_,
timerManager_,
std::move(jsErrorHandlingFunc));
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(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(script);
}
jsi::Value eval(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) {
eval("RN$Bridgeless === true");
expectError();
initializeRuntimeWithScript("");
auto val = eval("RN$Bridgeless === true");
EXPECT_EQ(val.getBool(), true);
}
TEST_F(ReactInstanceTest, testProfilingFlag) {
eval("__RCTProfileIsProfiling === true");
expectError();
initializeRuntimeWithScript({.isProfiling = true}, "");
auto val = eval("__RCTProfileIsProfiling === true");
EXPECT_EQ(val.getBool(), true);
}
TEST_F(ReactInstanceTest, testPromiseIntegration) {
initializeRuntimeWithScript("");
eval(R"xyz123(
let called = 0;
function getResult() {
return called;
}
Promise.resolve().then(() => {
called++;
}).then(() => {
called++;
})
)xyz123");
auto result = runtime_->global()
.getPropertyAsFunction(*runtime_, "getResult")
.call(*runtime_);
EXPECT_EQ(result.getNumber(), 2);
}
TEST_F(ReactInstanceTest, testSetImmediate) {
initializeRuntimeWithScript("");
eval(R"xyz123(
let called = false;
function getResult() {
return called;
}
setImmediate(() => {
called = true;
});
)xyz123");
auto result = runtime_->global()
.getPropertyAsFunction(*runtime_, "getResult")
.call(*runtime_);
EXPECT_EQ(result.getBool(), true);
}
TEST_F(ReactInstanceTest, testNestedSetImmediate) {
initializeRuntimeWithScript("");
eval(R"xyz123(
let called = false;
function getResult() {
return called;
}
setImmediate(() => {
setImmediate(() => {
called = true;
})
});
)xyz123");
auto result = runtime_->global()
.getPropertyAsFunction(*runtime_, "getResult")
.call(*runtime_);
EXPECT_EQ(result.getBool(), true);
}
TEST_F(ReactInstanceTest, testSetImmediateWithInvalidArgs) {
initializeRuntimeWithScript("");
EXPECT_EQ(
getErrorMessage("setImmediate();"),
"setImmediate must be called with at least one argument (a function to call)");
EXPECT_EQ(
getErrorMessage("setImmediate('invalid');"),
"The first argument to setImmediate must be a function.");
EXPECT_EQ(
getErrorMessage("setImmediate({});"),
"The first argument to setImmediate must be a function.");
}
TEST_F(ReactInstanceTest, testClearImmediate) {
initializeRuntimeWithScript("");
eval(R"xyz123(
let called = false;
function getResult() {
return called;
}
const handle = setImmediate(() => {
called = true;
});
clearImmediate(handle);
)xyz123");
auto func = runtime_->global().getPropertyAsFunction(*runtime_, "getResult");
auto val = func.call(*runtime_);
EXPECT_EQ(val.getBool(), false);
}
TEST_F(ReactInstanceTest, testClearImmediateWithInvalidHandle) {
initializeRuntimeWithScript("");
auto js = R"xyz123(
let called = false;
const handle = setImmediate(() => {
called = true;
});
function getResult() {
return called;
}
function clearInvalidHandle() {
clearImmediate(handle);
}
)xyz123";
eval(js);
auto clear =
runtime_->global().getPropertyAsFunction(*runtime_, "clearInvalidHandle");
// Clearing an invalid handle should fail silently.
EXPECT_NO_THROW(clear.call((*runtime_)));
}
TEST_F(ReactInstanceTest, testClearImmediateWithInvalidArgs) {
initializeRuntimeWithScript("");
eval("clearImmediate();");
expectNoError();
eval("clearImmediate('invalid');");
expectNoError();
eval("clearImmediate({});");
expectNoError();
eval("clearImmediate(undefined);");
expectNoError();
}
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
eval("setTimeout(() => {});");
}
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).");
EXPECT_EQ(
getErrorMessage("setTimeout('invalid');"),
"The first argument to setTimeout must be a function.");
EXPECT_EQ(
getErrorMessage("setTimeout(() => {}, 'invalid');"),
"The second argument to setTimeout must be a number or undefined.");
}
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 two arguments (the function to call and the delay).");
EXPECT_EQ(
getErrorMessage("setInterval(() => {});"),
"setInterval must be called with at least two arguments (the function to call and the delay).");
EXPECT_EQ(
getErrorMessage("setInterval('invalid', 100);"),
"The first argument to setInterval must be a function.");
EXPECT_EQ(
getErrorMessage("setInterval(() => {}, 'invalid');"),
"The second argument to setInterval must be a number.");
}
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_EQ(
getLastErrorMessage(),
"Failed to call into JavaScript module method invalidModule.method(). Module has not been registered as callable. Registered callable JavaScript modules (n = 0):. Did you forget to call `RN$registerCallableModule`?");
}
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(),
"Failed to call into JavaScript module method foo.invalidMethod. Module exists, but the method is undefined.");
}
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