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,7 @@
---
InheritParentConfig: true
CheckOptions:
# clang 15 does not support reverse_view
- key: modernize-loop-convert.UseCxx20ReverseRanges
value: false
...

View File

@@ -0,0 +1,311 @@
# 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'
react_native_path = ".."
Pod::Spec.new do |s|
s.name = "React-Fabric"
s.version = version
s.summary = "Fabric for React Native."
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 = "dummyFile.cpp"
s.pod_target_xcconfig = { "USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
"DEFINES_MODULE" => "YES" }
if ENV['USE_FRAMEWORKS']
s.header_mappings_dir = './'
s.module_name = 'React_Fabric'
end
s.dependency folly_dep_name, folly_version
s.dependency "React-jsiexecutor"
s.dependency "RCTRequired"
s.dependency "RCTTypeSafety"
s.dependency "ReactCommon/turbomodule/core"
s.dependency "React-jsi"
s.dependency "React-logger"
s.dependency "glog"
s.dependency "DoubleConversion"
s.dependency "fmt", "9.1.0"
s.dependency "React-Core"
s.dependency "React-debug"
s.dependency "React-utils"
s.dependency "React-runtimescheduler"
s.dependency "React-cxxreact"
add_dependency(s, "React-rendererdebug")
add_dependency(s, "React-graphics", :additional_framework_paths => ["react/renderer/graphics/platform/ios"])
if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1"
s.dependency "hermes-engine"
else
s.dependency "React-jsc"
end
s.subspec "animations" do |ss|
ss.dependency folly_dep_name, folly_version
ss.compiler_flags = folly_compiler_flags
ss.source_files = "react/renderer/animations/**/*.{m,mm,cpp,h}"
ss.exclude_files = "react/renderer/animations/tests"
ss.header_dir = "react/renderer/animations"
end
s.subspec "attributedstring" do |ss|
ss.dependency folly_dep_name, folly_version
ss.compiler_flags = folly_compiler_flags
ss.source_files = "react/renderer/attributedstring/**/*.{m,mm,cpp,h}"
ss.exclude_files = "react/renderer/attributedstring/tests"
ss.header_dir = "react/renderer/attributedstring"
end
s.subspec "core" do |ss|
header_search_path = [
"\"$(PODS_ROOT)/boost\"",
"\"$(PODS_TARGET_SRCROOT)/ReactCommon\"",
"\"$(PODS_ROOT)/RCT-Folly\"",
"\"$(PODS_ROOT)/Headers/Private/Yoga\"",
"\"$(PODS_TARGET_SRCROOT)\"",
"\"$(PODS_ROOT)/DoubleConversion\"",
"\"$(PODS_ROOT)/fmt/include\"",
]
if ENV['USE_FRAMEWORKS']
header_search_path = header_search_path + [
"\"$(PODS_TARGET_SRCROOT)/react/renderer/textlayoutmanager/platform/ios\"",
"\"$(PODS_TARGET_SRCROOT)/react/renderer/components/textinput/platform/ios\"",
"\"$(PODS_TARGET_SRCROOT)/react/renderer/components/view/platform/cxx\"",
]
end
ss.dependency folly_dep_name, folly_version
ss.compiler_flags = folly_compiler_flags + ' ' + boost_compiler_flags
ss.source_files = "react/renderer/core/**/*.{m,mm,cpp,h}"
ss.exclude_files = "react/renderer/core/tests"
ss.header_dir = "react/renderer/core"
ss.pod_target_xcconfig = {
"HEADER_SEARCH_PATHS" => header_search_path.join(" ")
}
end
s.subspec "componentregistry" do |ss|
ss.dependency folly_dep_name, folly_version
ss.compiler_flags = folly_compiler_flags
ss.source_files = "react/renderer/componentregistry/*.{m,mm,cpp,h}"
ss.header_dir = "react/renderer/componentregistry"
end
s.subspec "componentregistrynative" do |ss|
ss.dependency folly_dep_name, folly_version
ss.compiler_flags = folly_compiler_flags
ss.source_files = "react/renderer/componentregistry/native/**/*.{m,mm,cpp,h}"
ss.header_dir = "react/renderer/componentregistry/native"
end
s.subspec "components" do |ss|
ss.subspec "inputaccessory" do |sss|
sss.dependency folly_dep_name, folly_version
sss.compiler_flags = folly_compiler_flags
sss.source_files = "react/renderer/components/inputaccessory/**/*.{m,mm,cpp,h}"
sss.exclude_files = "react/renderer/components/inputaccessory/tests"
sss.header_dir = "react/renderer/components/inputaccessory"
end
ss.subspec "legacyviewmanagerinterop" do |sss|
sss.dependency folly_dep_name, folly_version
sss.compiler_flags = folly_compiler_flags
sss.source_files = "react/renderer/components/legacyviewmanagerinterop/**/*.{m,mm,cpp,h}"
sss.exclude_files = "react/renderer/components/legacyviewmanagerinterop/tests"
sss.header_dir = "react/renderer/components/legacyviewmanagerinterop"
sss.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/Headers/Private/React-Core\"" }
end
ss.subspec "modal" do |sss|
sss.dependency folly_dep_name, folly_version
sss.compiler_flags = folly_compiler_flags
sss.source_files = "react/renderer/components/modal/**/*.{m,mm,cpp,h}"
sss.exclude_files = "react/renderer/components/modal/tests"
sss.header_dir = "react/renderer/components/modal"
end
ss.subspec "rncore" do |sss|
sss.dependency folly_dep_name, folly_version
sss.compiler_flags = folly_compiler_flags
sss.source_files = "react/renderer/components/rncore/**/*.{m,mm,cpp,h}"
sss.header_dir = "react/renderer/components/rncore"
end
ss.subspec "root" do |sss|
sss.dependency folly_dep_name, folly_version
sss.compiler_flags = folly_compiler_flags
sss.source_files = "react/renderer/components/root/**/*.{m,mm,cpp,h}"
sss.exclude_files = "react/renderer/components/root/tests"
sss.header_dir = "react/renderer/components/root"
end
ss.subspec "safeareaview" do |sss|
sss.dependency folly_dep_name, folly_version
sss.compiler_flags = folly_compiler_flags
sss.source_files = "react/renderer/components/safeareaview/**/*.{m,mm,cpp,h}"
sss.exclude_files = "react/renderer/components/safeareaview/tests"
sss.header_dir = "react/renderer/components/safeareaview"
end
ss.subspec "scrollview" do |sss|
sss.dependency folly_dep_name, folly_version
sss.compiler_flags = folly_compiler_flags
sss.source_files = "react/renderer/components/scrollview/**/*.{m,mm,cpp,h}"
sss.exclude_files = "react/renderer/components/scrollview/tests"
sss.header_dir = "react/renderer/components/scrollview"
end
ss.subspec "text" do |sss|
sss.dependency folly_dep_name, folly_version
sss.compiler_flags = folly_compiler_flags
sss.source_files = "react/renderer/components/text/**/*.{m,mm,cpp,h}"
sss.exclude_files = "react/renderer/components/text/tests"
sss.header_dir = "react/renderer/components/text"
end
ss.subspec "textinput" do |sss|
sss.dependency folly_dep_name, folly_version
sss.compiler_flags = folly_compiler_flags
sss.source_files = "react/renderer/components/textinput/platform/ios/**/*.{m,mm,cpp,h}"
sss.header_dir = "react/renderer/components/iostextinput"
end
ss.subspec "unimplementedview" do |sss|
sss.dependency folly_dep_name, folly_version
sss.compiler_flags = folly_compiler_flags
sss.source_files = "react/renderer/components/unimplementedview/**/*.{m,mm,cpp,h}"
sss.exclude_files = "react/renderer/components/unimplementedview/tests"
sss.header_dir = "react/renderer/components/unimplementedview"
end
ss.subspec "view" do |sss|
sss.dependency folly_dep_name, folly_version
sss.dependency "Yoga"
sss.compiler_flags = folly_compiler_flags
sss.source_files = "react/renderer/components/view/**/*.{m,mm,cpp,h}"
sss.exclude_files = "react/renderer/components/view/tests", "react/renderer/components/view/platform/android"
sss.header_dir = "react/renderer/components/view"
sss.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/Headers/Private/Yoga\"" }
end
end
s.subspec "imagemanager" do |ss|
ss.dependency folly_dep_name, folly_version
ss.compiler_flags = folly_compiler_flags
ss.source_files = "react/renderer/imagemanager/*.{m,mm,cpp,h}"
ss.header_dir = "react/renderer/imagemanager"
end
s.subspec "mounting" do |ss|
ss.dependency folly_dep_name, folly_version
ss.compiler_flags = folly_compiler_flags
ss.source_files = "react/renderer/mounting/**/*.{m,mm,cpp,h}"
ss.exclude_files = "react/renderer/mounting/tests"
ss.header_dir = "react/renderer/mounting"
end
s.subspec "scheduler" do |ss|
ss.dependency folly_dep_name, folly_version
ss.compiler_flags = folly_compiler_flags
ss.source_files = "react/renderer/scheduler/**/*.{m,mm,cpp,h}"
ss.header_dir = "react/renderer/scheduler"
end
s.subspec "templateprocessor" do |ss|
ss.dependency folly_dep_name, folly_version
ss.compiler_flags = folly_compiler_flags
ss.source_files = "react/renderer/templateprocessor/**/*.{m,mm,cpp,h}"
ss.exclude_files = "react/renderer/templateprocessor/tests"
ss.header_dir = "react/renderer/templateprocessor"
end
s.subspec "textlayoutmanager" do |ss|
ss.dependency folly_dep_name, folly_version
ss.dependency "React-Fabric/uimanager"
ss.compiler_flags = folly_compiler_flags
ss.source_files = "react/renderer/textlayoutmanager/platform/ios/**/*.{m,mm,cpp,h}",
"react/renderer/textlayoutmanager/*.{m,mm,cpp,h}"
ss.exclude_files = "react/renderer/textlayoutmanager/tests",
"react/renderer/textlayoutmanager/platform/android",
"react/renderer/textlayoutmanager/platform/cxx"
ss.header_dir = "react/renderer/textlayoutmanager"
end
s.subspec "uimanager" do |ss|
ss.dependency folly_dep_name, folly_version
ss.compiler_flags = folly_compiler_flags
ss.source_files = "react/renderer/uimanager/**/*.{m,mm,cpp,h}"
ss.exclude_files = "react/renderer/uimanager/tests"
ss.header_dir = "react/renderer/uimanager"
end
s.subspec "telemetry" do |ss|
ss.dependency folly_dep_name, folly_version
ss.compiler_flags = folly_compiler_flags
ss.source_files = "react/renderer/telemetry/**/*.{m,mm,cpp,h}"
ss.exclude_files = "react/renderer/telemetry/tests"
ss.header_dir = "react/renderer/telemetry"
end
s.subspec "leakchecker" do |ss|
ss.dependency folly_dep_name, folly_version
ss.compiler_flags = folly_compiler_flags
ss.source_files = "react/renderer/leakchecker/**/*.{cpp,h}"
ss.exclude_files = "react/renderer/leakchecker/tests"
ss.header_dir = "react/renderer/leakchecker"
ss.pod_target_xcconfig = { "GCC_WARN_PEDANTIC" => "YES" }
end
s.script_phases = [
{
:name => '[RN]Check rncore',
:execution_position => :before_compile,
:script => <<-EOS
echo "Checking whether Codegen has run..."
rncorePath="$REACT_NATIVE_PATH/ReactCommon/react/renderer/components/rncore"
if [[ ! -d "$rncorePath" ]]; then
echo 'error: Codegen did not run properly in your project. Please reinstall cocoapods with `bundle exec pod install`.'
exit 1
fi
EOS
}
]
end

View File

@@ -0,0 +1,95 @@
# 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'
react_native_path = ".."
header_search_path = [
"\"$(PODS_ROOT)/boost\"",
"\"$(PODS_TARGET_SRCROOT)/ReactCommon\"",
"\"$(PODS_ROOT)/RCT-Folly\"",
"\"$(PODS_ROOT)/Headers/Private/Yoga\"",
"\"$(PODS_ROOT)/DoubleConversion\"",
"\"$(PODS_ROOT)/fmt/include\"",
]
if ENV['USE_FRAMEWORKS']
header_search_path = header_search_path + [
"\"$(PODS_TARGET_SRCROOT)\"",
"\"$(PODS_TARGET_SRCROOT)/react/renderer/textlayoutmanager/platform/ios\"",
"\"$(PODS_TARGET_SRCROOT)/react/renderer/components/textinput/platform/ios\"",
# "\"$(PODS_CONFIGURATION_BUILD_DIR)/React-Codegen/React_Codegen.framework/Headers\"",
]
end
Pod::Spec.new do |s|
s.name = "React-FabricImage"
s.version = version
s.summary = "Image Component for Fabric for React Native."
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 = "react/renderer/components/image/**/*.{m,mm,cpp,h}"
s.exclude_files = "react/renderer/components/image/tests"
s.header_dir = "react/renderer/components/image"
s.compiler_flags = folly_compiler_flags
s.pod_target_xcconfig = { "USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
"HEADER_SEARCH_PATHS" => header_search_path.join(" ")
}
if ENV['USE_FRAMEWORKS']
s.header_mappings_dir = './'
s.module_name = 'React_FabricImage'
end
s.dependency folly_dep_name, folly_version
s.dependency "React-jsiexecutor", version
s.dependency "RCTRequired", version
s.dependency "RCTTypeSafety", version
s.dependency "React-jsi"
s.dependency "React-logger"
s.dependency "glog"
s.dependency "DoubleConversion"
s.dependency "fmt", "9.1.0"
s.dependency "React-ImageManager"
s.dependency "React-utils"
s.dependency "Yoga"
add_dependency(s, "ReactCommon", :subspec => "turbomodule/core")
add_dependency(s, "React-graphics", :additional_framework_paths => ["react/renderer/graphics/platform/ios"])
add_dependency(s, "React-Fabric", :additional_framework_paths => [
"react/renderer/components/view/platform/cxx",
"react/renderer/imagemanager/platform/ios"
])
add_dependency(s, "React-rendererdebug")
if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1"
s.dependency "hermes-engine"
else
s.dependency "React-jsc"
end
end

View File

@@ -0,0 +1,43 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
Pod::Spec.new do |s|
s.name = "React-Mapbuffer"
s.version = version
s.summary = "-"
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 = "react/renderer/mapbuffer/*.{cpp,h}"
s.exclude_files = "react/renderer/mapbuffer/tests"
s.public_header_files = 'react/renderer/mapbuffer/*.h'
s.header_dir = "react/renderer/mapbuffer"
s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)\"", "USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20" }
if ENV['USE_FRAMEWORKS']
s.header_mappings_dir = './'
s.module_name = 'React_Mapbuffer'
end
s.dependency "glog"
add_dependency(s, "React-debug")
end

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.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
Pod::Spec.new do |s|
s.name = "React-nativeconfig"
s.version = version
s.summary = "-"
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 = "react/config/*.{m,mm,cpp,h}"
s.header_dir = "react/config"
s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => "c++20" }
if ENV['USE_FRAMEWORKS']
s.header_mappings_dir = './'
s.module_name = 'React_nativeconfig'
end
end

View File

@@ -0,0 +1,52 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
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
# We are using two different paths for react native because of the way how js_srcs_dir and output_dir options are used
# output_dir option usage was introduced in https://github.com/facebook/react-native/pull/36210
# React-rncore.podspec is the only podspec in the project that uses this option
# We should rethink this approach in T148704916
# Relative path to react native from iOS project root (e.g. <ios-project-root>/../node_modules/react-native)
react_native_dependency_path = ENV['REACT_NATIVE_PATH']
# Relative path to react native from current podspec
react_native_sources_path = '..'
header_search_paths = [
"\"$(PODS_TARGET_SRCROOT)\"",
"\"$(PODS_TARGET_SRCROOT)/ReactCommon\"",
]
if ENV["USE_FRAMEWORKS"]
create_header_search_path_for_frameworks("ReactCommon", :additional_framework_paths => ["react/nativemodule/core"], :include_base_folder => false)
.each { |search_path| header_search_paths << "\"#{search_path}\"" }
end
Pod::Spec.new do |s|
s.name = "React-rncore"
s.version = version
s.summary = "Fabric for React Native."
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 = "dummyFile.cpp"
s.pod_target_xcconfig = { "USE_HEADERMAP" => "YES",
"HEADER_SEARCH_PATHS" => header_search_paths.join(' '),
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20" }
end

View File

@@ -0,0 +1,79 @@
# 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]
boost_compiler_flags = '-Wno-documentation'
using_hermes = ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == "1"
Pod::Spec.new do |s|
s.name = "ReactCommon"
s.module_name = "ReactCommon"
s.version = version
s.summary = "-" # TODO
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.header_dir = "ReactCommon" # Use global header_dir for all subspecs for use_frameworks! compatibility
s.compiler_flags = folly_compiler_flags + ' ' + boost_compiler_flags
s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/RCT-Folly\" \"$(PODS_ROOT)/DoubleConversion\" \"$(PODS_ROOT)/fmt/include\" \"$(PODS_ROOT)/Headers/Private/React-Core\"",
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
"GCC_WARN_PEDANTIC" => "YES" }
if ENV['USE_FRAMEWORKS']
s.header_mappings_dir = './'
end
# TODO (T48588859): Restructure this target to align with dir structure: "react/nativemodule/..."
# Note: Update this only when ready to minimize breaking changes.
s.subspec "turbomodule" do |ss|
ss.dependency "React-callinvoker", version
ss.dependency "React-perflogger", version
ss.dependency "React-cxxreact", version
ss.dependency "React-jsi", version
ss.dependency "RCT-Folly", folly_version
ss.dependency "React-logger", version
ss.dependency "DoubleConversion"
ss.dependency "fmt", "9.1.0"
ss.dependency "glog"
if using_hermes
ss.dependency "hermes-engine"
end
ss.subspec "bridging" do |sss|
sss.dependency "React-jsi", version
sss.source_files = "react/bridging/**/*.{cpp,h}"
sss.exclude_files = "react/bridging/tests"
sss.header_dir = "react/bridging"
sss.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/ReactCommon\" \"$(PODS_ROOT)/RCT-Folly\"" }
if using_hermes
sss.dependency "hermes-engine"
end
end
ss.subspec "core" do |sss|
sss.source_files = "react/nativemodule/core/ReactCommon/**/*.{cpp,h}"
sss.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/ReactCommon\" \"$(PODS_CONFIGURATION_BUILD_DIR)/React-debug/React_debug.framework/Headers\" \"$(PODS_CONFIGURATION_BUILD_DIR)/React-utils/React_utils.framework/Headers\"" }
sss.dependency "React-debug", version
sss.dependency "React-utils", version
end
end
end

View File

@@ -0,0 +1,6 @@
---
InheritParentConfig: true
Checks: '>
clang-diagnostic-*,
'
...

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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-DLOG_TAG=\"Butter\"
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic)
add_library(butter INTERFACE)
target_include_directories(butter INTERFACE .)
target_link_libraries(butter INTERFACE glog)

View File

@@ -0,0 +1,76 @@
/*
* 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
namespace facebook {
namespace butter {
/*
* `Butter` is a minimal collection of basic tools borrowed from other low-level
* general purpose libraries (like Folly, Abseil or Boost). The main goals of
* Butter:
* - Make the codebase more portable;
* - Make the dependency list explicit (by decoupling it as a dependency list of
* Butter);
* - Make relying on modern C++ patterns and tools in code simple and easy.
* - Make executing experiments with different dependencies easier.
* - Limit reliance on third-party libraries eventually.
*
* Target C++ Version
* ------------------
* Currently, Butter targets c++20.
*
* The Scope
* ---------
* What should be part of Butter and what should not? Should I add some piece of
* functionality in the Butter? Here is a quick checklist.
*
* As of now, Butter is relatively permissive per the guidance below:
*
* If one of the following is true, yes, go for it:
* - If some feature is already in some future C++ standard (possibly in draft
* stage) and it's already implemented in some 3rd party library.
* - If some standardized feature of C++ is implemented in the standard not in
* the most efficient way (because the standard enforces some tricky
* constraints, like always-valid iterators, which nobody uses and should use),
* but you have a library that does it right providing exact same interface.
*
* If one of the following is true, please do *NOT* do it (at least as part of
* the library):
* - You want to use some very fancy pattern that your favorite library (but
* nothing else) provides, and You want to make this pattern very command in the
* code base. Your hope is that this pattern will conquer the world and be
* a part of the C++ standard eventually.
* - You favorite library provides some general purpose container that 10x times
* faster than the standard one, so You want to use that in the code base. That
* container does not have compatible API though (because it's a clear trade-off
* with efficiency, of course).
*
* Note that eventually Butter will restrict the API collection to reduce
* reliance on non-standard C++ builtin libraries. That way, the API footprint
* stays small and is limited to just the essential APIs. This restriction is
* currently a work in progress.
*/
/*
* Configuration
*/
/*
* Enables using Folly containers instead of standard ones (such as map, vector,
* small_vector, optional and etc.)
* Custom containers are only enabled in release mode. Using custom stuff
* complicates debugging process because it breaks embedded into IDE
* introspections mechanisms.
*/
#ifndef DEBUG
#define BUTTER_USE_FOLLY_CONTAINERS
#endif
} // namespace butter
} // namespace facebook

View File

@@ -0,0 +1,45 @@
/*
* 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 <butter/butter.h>
#ifdef BUTTER_USE_FOLLY_CONTAINERS
#include <folly/container/F14Map.h>
#else
#include <unordered_map>
#endif
namespace facebook {
namespace butter {
/*
* Note: In Butter, `map` aliases to `unordered_map` because everyone agrees
* that an *ordered* map is nonsense and was a huge mistake for standardization.
* If you need an *ordered* map, feel free to introduce that as
* `butter::ordered_map`.
*/
#ifdef BUTTER_USE_FOLLY_CONTAINERS
template <typename... Ts>
using map = folly::F14FastMap<Ts...>;
#else
template <typename... Ts>
using map = std::unordered_map<Ts...>;
#endif
} // namespace butter
} // namespace facebook

View File

@@ -0,0 +1,6 @@
---
Checks: '>
clang-diagnostic-*,
'
InheritParentConfig: true
...

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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic)
add_library(callinvoker INTERFACE)
target_include_directories(callinvoker INTERFACE .)

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.
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]
boost_compiler_flags = '-Wno-documentation'
Pod::Spec.new do |s|
s.name = "React-callinvoker"
s.version = version
s.summary = "-" # TODO
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}"
s.header_dir = "ReactCommon"
end

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <functional>
#include <memory>
#include "SchedulerPriority.h"
namespace facebook::react {
using CallFunc = std::function<void()>;
/**
* An interface for a generic native-to-JS call invoker. See BridgeJSCallInvoker
* for an implementation.
*/
class CallInvoker {
public:
virtual void invokeAsync(CallFunc&& func) noexcept = 0;
virtual void invokeAsync(
SchedulerPriority /*priority*/,
CallFunc&& func) noexcept {
// When call with priority is not implemented, fall back to a regular async
// execution
invokeAsync(std::move(func));
}
virtual void invokeSync(CallFunc&& func) = 0;
virtual ~CallInvoker() {}
};
class NativeMethodCallInvoker {
public:
virtual void invokeAsync(
const std::string& methodName,
CallFunc&& func) noexcept = 0;
virtual void invokeSync(const std::string& methodName, CallFunc&& func) = 0;
virtual ~NativeMethodCallInvoker() {}
};
} // namespace facebook::react

View File

@@ -0,0 +1,22 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <chrono>
namespace facebook::react {
enum class SchedulerPriority : int {
ImmediatePriority = 1,
UserBlockingPriority = 2,
NormalPriority = 3,
LowPriority = 4,
IdlePriority = 5,
};
} // namespace facebook::react

View File

@@ -0,0 +1,6 @@
---
Checks: '>
clang-diagnostic-*,
'
InheritParentConfig: true
...

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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wno-unused-lambda-capture
-DLOG_TAG=\"ReactNative\")
file(GLOB reactnative_SRC CONFIGURE_DEPENDS *.cpp)
add_library(reactnative STATIC ${reactnative_SRC})
target_include_directories(reactnative PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(reactnative
boost
callinvoker
folly_runtime
glog
jsi
jsinspector
logger
reactperflogger
runtimeexecutor
react_debug)

View File

@@ -0,0 +1,256 @@
/*
* 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 <functional>
#include <map>
#include <tuple>
#include <vector>
#include <folly/dynamic.h>
using namespace std::placeholders;
namespace facebook::react {
class Instance;
} // namespace facebook::react
namespace facebook {
namespace xplat {
namespace module {
/**
* Base class for Catalyst native modules whose implementations are
* written in C++. Native methods are represented by instances of the
* Method struct. Generally, a derived class will manage an instance
* which represents the data for the module, and non-Catalyst-specific
* methods can be wrapped in lambdas which convert between
* folly::dynamic and native C++ objects. The Callback arguments will
* pass through to js functions passed to the analogous javascript
* methods. At most two callbacks will be converted. Results should
* be passed to the first callback, and errors to the second callback.
* Exceptions thrown by a method will be converted to platform
* exceptions, and handled however they are handled on that platform.
* (TODO mhorowitz #7128529: this exception behavior is not yet
* implemented.)
*
* There are two sets of constructors here. The first set initializes
* a Method using a name and anything convertible to a std::function.
* This is most useful for registering a lambda as a RN method. There
* are overloads to support functions which take no arguments,
* arguments only, and zero, one, or two callbacks.
*
* The second set of methods is similar, but instead of taking a
* function, takes the method name, an object, and a pointer to a
* method on that object.
*/
class CxxModule {
class AsyncTagType {};
class SyncTagType {};
public:
typedef std::function<std::unique_ptr<CxxModule>()> Provider;
typedef std::function<void(std::vector<folly::dynamic>)> Callback;
constexpr static AsyncTagType AsyncTag = AsyncTagType();
constexpr static SyncTagType SyncTag = SyncTagType();
struct Method {
std::string name;
size_t callbacks;
bool isPromise;
std::function<void(folly::dynamic, Callback, Callback)> func;
std::function<folly::dynamic(folly::dynamic)> syncFunc;
const char* getType() {
assert(func || syncFunc);
return func ? (isPromise ? "promise" : "async") : "sync";
}
// std::function/lambda ctors
Method(std::string aname, std::function<void()>&& afunc)
: name(std::move(aname)),
callbacks(0),
isPromise(false),
func(std::bind(std::move(afunc))) {}
Method(std::string aname, std::function<void(folly::dynamic)>&& afunc)
: name(std::move(aname)),
callbacks(0),
isPromise(false),
func(std::bind(std::move(afunc), std::placeholders::_1)) {}
Method(
std::string aname,
std::function<void(folly::dynamic, Callback)>&& afunc)
: name(std::move(aname)),
callbacks(1),
isPromise(false),
func(std::bind(
std::move(afunc),
std::placeholders::_1,
std::placeholders::_2)) {}
Method(
std::string aname,
std::function<void(folly::dynamic, Callback, Callback)>&& afunc)
: name(std::move(aname)),
callbacks(2),
isPromise(true),
func(std::move(afunc)) {}
Method(
std::string aname,
std::function<void(folly::dynamic, Callback, Callback)>&& afunc,
AsyncTagType)
: name(std::move(aname)),
callbacks(2),
isPromise(false),
func(std::move(afunc)) {}
// method pointer ctors
template <typename T>
Method(std::string aname, T* t, void (T::*method)())
: name(std::move(aname)),
callbacks(0),
isPromise(false),
func(std::bind(method, t)) {}
template <typename T>
Method(std::string aname, T* t, void (T::*method)(folly::dynamic))
: name(std::move(aname)),
callbacks(0),
isPromise(false),
func(std::bind(method, t, std::placeholders::_1)) {}
template <typename T>
Method(std::string aname, T* t, void (T::*method)(folly::dynamic, Callback))
: name(std::move(aname)),
callbacks(1),
isPromise(false),
func(std::bind(
method,
t,
std::placeholders::_1,
std::placeholders::_2)) {}
template <typename T>
Method(
std::string aname,
T* t,
void (T::*method)(folly::dynamic, Callback, Callback))
: name(std::move(aname)),
callbacks(2),
isPromise(true),
func(std::bind(
method,
t,
std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3)) {}
template <typename T>
Method(
std::string aname,
T* t,
void (T::*method)(folly::dynamic, Callback, Callback),
AsyncTagType)
: name(std::move(aname)),
callbacks(2),
isPromise(false),
func(std::bind(
method,
t,
std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3)) {}
// sync std::function/lambda ctors
// Overloads for functions returning void give ambiguity errors.
// I am not sure if this is a runtime/compiler bug, or a
// limitation I do not understand.
Method(
std::string aname,
std::function<folly::dynamic()>&& afunc,
SyncTagType)
: name(std::move(aname)),
callbacks(0),
isPromise(false),
syncFunc([afunc = std::move(afunc)](const folly::dynamic&) {
return afunc();
}) {}
Method(
std::string aname,
std::function<folly::dynamic(folly::dynamic)>&& afunc,
SyncTagType)
: name(std::move(aname)),
callbacks(0),
isPromise(false),
syncFunc(std::move(afunc)) {}
};
/**
* This may block, if necessary to complete cleanup before the
* object is destroyed.
*/
virtual ~CxxModule() {}
/**
* @return the name of this module. This will be the name used to {@code
* require()} this module from javascript.
*/
virtual std::string getName() = 0;
/**
* Each entry in the map will be exported as a property to JS. The
* key is the property name, and the value can be anything.
*/
virtual auto getConstants() -> std::map<std::string, folly::dynamic> {
return {};
};
/**
* @return a list of methods this module exports to JS.
*/
virtual auto getMethods() -> std::vector<Method> = 0;
/**
* Called during the construction of CxxNativeModule.
*/
void setInstance(std::weak_ptr<react::Instance> instance) {
instance_ = instance;
}
/**
* @return a weak_ptr to the current instance of the bridge.
* When used with CxxNativeModule, this gives Cxx modules access to functions
* such as `callJSFunction`, allowing them to communicate back to JS outside
* of the regular callbacks.
*/
std::weak_ptr<react::Instance> getInstance() {
return instance_;
}
private:
std::weak_ptr<react::Instance> instance_;
};
} // namespace module
} // namespace xplat
} // namespace facebook

View File

@@ -0,0 +1,259 @@
/*
* 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 "CxxNativeModule.h"
#include "Instance.h"
#include <folly/json.h>
#include <glog/logging.h>
#include <iterator>
#include "JsArgumentHelpers.h"
#include "MessageQueueThread.h"
#include "SystraceSection.h"
#include <logger/react_native_log.h>
using facebook::xplat::module::CxxModule;
namespace facebook::react {
std::function<void(folly::dynamic)> makeCallback(
std::weak_ptr<Instance> instance,
const folly::dynamic& callbackId) {
if (!callbackId.isNumber()) {
throw std::invalid_argument("Expected callback(s) as final argument");
}
auto id = callbackId.asInt();
return [winstance = std::move(instance), id](folly::dynamic args) {
if (auto instance = winstance.lock()) {
instance->callJSCallback(id, std::move(args));
}
};
}
namespace {
/**
* CxxModule::Callback accepts a vector<dynamic>, makeCallback returns
* a callback that accepts a dynamic, adapt the second into the first.
* TODO: Callback types should be made equal (preferably
* function<void(dynamic)>) to avoid the extra copy and indirect call.
*/
CxxModule::Callback convertCallback(
std::function<void(folly::dynamic)> callback) {
return [callback = std::move(callback)](std::vector<folly::dynamic> args) {
callback(folly::dynamic(
std::make_move_iterator(args.begin()),
std::make_move_iterator(args.end())));
};
}
} // namespace
bool CxxNativeModule::shouldWarnOnUse_ = false;
void CxxNativeModule::setShouldWarnOnUse(bool value) {
shouldWarnOnUse_ = value;
}
void CxxNativeModule::emitWarnIfWarnOnUsage(
const std::string& method_name,
const std::string& module_name) {
if (shouldWarnOnUse_) {
std::string message = folly::to<std::string>(
"Calling ",
method_name,
" on Cxx NativeModule (name = \"",
module_name,
"\").");
react_native_log_warn(message.c_str());
}
}
std::string CxxNativeModule::getName() {
return name_;
}
std::string CxxNativeModule::getSyncMethodName(unsigned int reactMethodId) {
if (reactMethodId >= methods_.size()) {
throw std::invalid_argument(folly::to<std::string>(
"methodId ",
reactMethodId,
" out of range [0..",
methods_.size(),
"]"));
}
return methods_[reactMethodId].name;
}
std::vector<MethodDescriptor> CxxNativeModule::getMethods() {
lazyInit();
std::vector<MethodDescriptor> descs;
for (auto& method : methods_) {
descs.emplace_back(method.name, method.getType());
}
return descs;
}
folly::dynamic CxxNativeModule::getConstants() {
lazyInit();
if (!module_) {
return nullptr;
}
emitWarnIfWarnOnUsage("getConstants()", getName());
folly::dynamic constants = folly::dynamic::object();
for (auto& pair : module_->getConstants()) {
constants.insert(std::move(pair.first), std::move(pair.second));
}
return constants;
}
void CxxNativeModule::invoke(
unsigned int reactMethodId,
folly::dynamic&& params,
int callId) {
if (reactMethodId >= methods_.size()) {
throw std::invalid_argument(folly::to<std::string>(
"methodId ",
reactMethodId,
" out of range [0..",
methods_.size(),
"]"));
}
if (!params.isArray()) {
throw std::invalid_argument(folly::to<std::string>(
"method parameters should be array, but are ", params.typeName()));
}
CxxModule::Callback first;
CxxModule::Callback second;
const auto& method = methods_[reactMethodId];
if (!method.func) {
throw std::runtime_error(folly::to<std::string>(
"Method ", method.name, " is synchronous but invoked asynchronously"));
}
emitWarnIfWarnOnUsage(method.name, getName());
if (params.size() < method.callbacks) {
throw std::invalid_argument(folly::to<std::string>(
"Expected ",
method.callbacks,
" callbacks, but only ",
params.size(),
" parameters provided"));
}
if (method.callbacks == 1) {
first = convertCallback(makeCallback(instance_, params[params.size() - 1]));
} else if (method.callbacks == 2) {
first = convertCallback(makeCallback(instance_, params[params.size() - 2]));
second =
convertCallback(makeCallback(instance_, params[params.size() - 1]));
}
params.resize(params.size() - method.callbacks);
// I've got a few flawed options here. I can let the C++ exception
// propagate, and the registry will log/convert them to java exceptions.
// This lets all the java and red box handling work ok, but the only info I
// can capture about the C++ exception is the what() string, not the stack.
// I can std::terminate() the app. This causes the full, accurate C++
// stack trace to be added to logcat by debuggerd. The java state is lost,
// but in practice, the java stack is always the same in this case since
// the javascript stack is not visible, and the crash is unfriendly to js
// developers, but crucial to C++ developers. The what() value is also
// lost. Finally, I can catch, log the java stack, then rethrow the C++
// exception. In this case I get java and C++ stack data, but the C++
// stack is as of the rethrow, not the original throw, both the C++ and
// java stacks always look the same.
//
// I am going with option 2, since that seems like the most useful
// choice. It would be nice to be able to get what() and the C++
// stack. I'm told that will be possible in the future. TODO
// mhorowitz #7128529: convert C++ exceptions to Java
const auto& moduleName = name_;
SystraceSection s(
"CxxMethodCallQueue", "module", moduleName, "method", method.name);
messageQueueThread_->runOnQueue([method,
moduleName,
params = std::move(params),
first,
second,
callId]() {
#ifdef WITH_FBSYSTRACE
if (callId != -1) {
fbsystrace_end_async_flow(TRACE_TAG_REACT_APPS, "native", callId);
}
#else
(void)(callId);
#endif
SystraceSection s(
"CxxMethodCallDispatch", "module", moduleName, "method", method.name);
try {
method.func(std::move(params), first, second);
} catch (const facebook::xplat::JsArgumentException& ex) {
throw;
} catch (std::exception& e) {
LOG(ERROR) << "std::exception. Method call " << method.name.c_str()
<< " failed: " << e.what();
std::terminate();
} catch (std::string& error) {
LOG(ERROR) << "std::string. Method call " << method.name.c_str()
<< " failed: " << error.c_str();
std::terminate();
} catch (...) {
LOG(ERROR) << "Method call " << method.name.c_str()
<< " failed. unknown error";
std::terminate();
}
});
}
MethodCallResult CxxNativeModule::callSerializableNativeHook(
unsigned int hookId,
folly::dynamic&& args) {
if (hookId >= methods_.size()) {
throw std::invalid_argument(folly::to<std::string>(
"methodId ", hookId, " out of range [0..", methods_.size(), "]"));
}
const auto& method = methods_[hookId];
if (!method.syncFunc) {
throw std::runtime_error(folly::to<std::string>(
"Method ", method.name, " is asynchronous but invoked synchronously"));
}
emitWarnIfWarnOnUsage(method.name, getName());
return method.syncFunc(std::move(args));
}
void CxxNativeModule::lazyInit() {
if (module_ || !provider_) {
return;
}
// TODO 17216751: providers should never return null modules
module_ = provider_();
provider_ = nullptr;
if (module_) {
module_->setInstance(instance_);
methods_ = module_->getMethods();
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,68 @@
/*
* 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/CxxModule.h>
#include <cxxreact/NativeModule.h>
#ifndef RN_EXPORT
#define RN_EXPORT __attribute__((visibility("default")))
#endif
namespace facebook::react {
class Instance;
class MessageQueueThread;
typedef void (*WarnOnUsageLogger)(std::string message);
std::function<void(folly::dynamic)> makeCallback(
std::weak_ptr<Instance> instance,
const folly::dynamic& callbackId);
class RN_EXPORT CxxNativeModule : public NativeModule {
public:
CxxNativeModule(
std::weak_ptr<Instance> instance,
std::string name,
xplat::module::CxxModule::Provider provider,
std::shared_ptr<MessageQueueThread> messageQueueThread)
: instance_(instance),
name_(std::move(name)),
provider_(provider),
messageQueueThread_(messageQueueThread) {}
std::string getName() override;
std::string getSyncMethodName(unsigned int methodId) override;
std::vector<MethodDescriptor> getMethods() override;
folly::dynamic getConstants() override;
void invoke(unsigned int reactMethodId, folly::dynamic&& params, int callId)
override;
MethodCallResult callSerializableNativeHook(
unsigned int hookId,
folly::dynamic&& args) override;
static void setShouldWarnOnUse(bool value);
private:
void lazyInit();
std::weak_ptr<Instance> instance_;
std::string name_;
xplat::module::CxxModule::Provider provider_;
std::shared_ptr<MessageQueueThread> messageQueueThread_;
std::unique_ptr<xplat::module::CxxModule> module_;
std::vector<xplat::module::CxxModule::Method> methods_;
void emitWarnIfWarnOnUsage(
const std::string& method_name,
const std::string& module_name);
static bool shouldWarnOnUse_;
};
} // 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.
*/
#include <jsi/jsi.h>
namespace facebook::react {
inline static void
handleJSError(jsi::Runtime& runtime, const jsi::JSError& error, bool isFatal) {
auto errorUtils = runtime.global().getProperty(runtime, "ErrorUtils");
if (errorUtils.isUndefined() || !errorUtils.isObject() ||
!errorUtils.getObject(runtime).hasProperty(runtime, "reportFatalError") ||
!errorUtils.getObject(runtime).hasProperty(runtime, "reportError")) {
// ErrorUtils was not set up. This probably means the bundle didn't
// load properly.
throw jsi::JSError(
runtime,
"ErrorUtils is not set up properly. Something probably went wrong trying to load the JS bundle. Trying to report error " +
error.getMessage(),
error.getStack());
}
// TODO(janzer): Rewrite this function to return the processed error
// instead of just reporting it through the native module
if (isFatal) {
auto func = errorUtils.asObject(runtime).getPropertyAsFunction(
runtime, "reportFatalError");
func.call(runtime, error.value());
} else {
auto func = errorUtils.asObject(runtime).getPropertyAsFunction(
runtime, "reportError");
func.call(runtime, error.value());
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,325 @@
/*
* 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 "Instance.h"
#include "ErrorUtils.h"
#include "JSBigString.h"
#include "JSBundleType.h"
#include "JSExecutor.h"
#include "MessageQueueThread.h"
#include "MethodCall.h"
#include "NativeToJsBridge.h"
#include "RAMBundleRegistry.h"
#include "RecoverableError.h"
#include "SystraceSection.h"
#include <cxxreact/JSIndexedRAMBundle.h>
#include <folly/json.h>
#include <react/debug/react_native_assert.h>
#include <glog/logging.h>
#include <condition_variable>
#include <exception>
#include <memory>
#include <mutex>
#include <string>
namespace facebook::react {
Instance::~Instance() {
if (nativeToJsBridge_) {
nativeToJsBridge_->destroy();
}
}
void Instance::unregisterFromInspector() {
if (inspectorTarget_) {
assert(runtimeInspectorTarget_);
inspectorTarget_->unregisterRuntime(*runtimeInspectorTarget_);
assert(parentInspectorTarget_);
parentInspectorTarget_->unregisterInstance(*inspectorTarget_);
parentInspectorTarget_ = nullptr;
inspectorTarget_ = nullptr;
}
}
void Instance::initializeBridge(
std::unique_ptr<InstanceCallback> callback,
std::shared_ptr<JSExecutorFactory> jsef,
std::shared_ptr<MessageQueueThread> jsQueue,
std::shared_ptr<ModuleRegistry> moduleRegistry,
jsinspector_modern::PageTarget* parentInspectorTarget) {
callback_ = std::move(callback);
moduleRegistry_ = std::move(moduleRegistry);
parentInspectorTarget_ = parentInspectorTarget;
jsQueue->runOnQueueSync(
[this, &jsef, jsQueue, parentInspectorTarget]() mutable {
nativeToJsBridge_ = std::make_shared<NativeToJsBridge>(
jsef.get(), moduleRegistry_, jsQueue, callback_);
nativeToJsBridge_->initializeRuntime();
if (parentInspectorTarget) {
inspectorTarget_ = &parentInspectorTarget->registerInstance(*this);
RuntimeExecutor runtimeExecutorIfJsi = getRuntimeExecutor();
runtimeInspectorTarget_ = &inspectorTarget_->registerRuntime(
nativeToJsBridge_->getInspectorTargetDelegate(),
runtimeExecutorIfJsi ? runtimeExecutorIfJsi : [](auto) {});
}
/**
* After NativeToJsBridge is created, the jsi::Runtime should exist.
* Also, the JS message queue thread exists. So, it's safe to
* schedule all queued up js Calls.
*/
jsCallInvoker_->setNativeToJsBridgeAndFlushCalls(nativeToJsBridge_);
std::scoped_lock lock(m_syncMutex);
m_syncReady = true;
m_syncCV.notify_all();
});
CHECK(nativeToJsBridge_);
}
void Instance::loadBundle(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> string,
std::string sourceURL) {
callback_->incrementPendingJSCalls();
SystraceSection s("Instance::loadBundle", "sourceURL", sourceURL);
nativeToJsBridge_->loadBundle(
std::move(bundleRegistry), std::move(string), std::move(sourceURL));
}
void Instance::loadBundleSync(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> string,
std::string sourceURL) {
std::unique_lock<std::mutex> lock(m_syncMutex);
m_syncCV.wait(lock, [this] { return m_syncReady; });
SystraceSection s("Instance::loadBundleSync", "sourceURL", sourceURL);
nativeToJsBridge_->loadBundleSync(
std::move(bundleRegistry), std::move(string), std::move(sourceURL));
}
void Instance::setSourceURL(std::string sourceURL) {
callback_->incrementPendingJSCalls();
SystraceSection s("Instance::setSourceURL", "sourceURL", sourceURL);
nativeToJsBridge_->loadBundle(nullptr, nullptr, std::move(sourceURL));
}
void Instance::loadScriptFromString(
std::unique_ptr<const JSBigString> string,
std::string sourceURL,
bool loadSynchronously) {
SystraceSection s("Instance::loadScriptFromString", "sourceURL", sourceURL);
if (loadSynchronously) {
loadBundleSync(nullptr, std::move(string), std::move(sourceURL));
} else {
loadBundle(nullptr, std::move(string), std::move(sourceURL));
}
}
void Instance::loadRAMBundleFromString(
std::unique_ptr<const JSBigString> script,
const std::string& sourceURL) {
auto bundle = std::make_unique<JSIndexedRAMBundle>(std::move(script));
auto startupScript = bundle->getStartupCode();
auto registry = RAMBundleRegistry::singleBundleRegistry(std::move(bundle));
loadRAMBundle(std::move(registry), std::move(startupScript), sourceURL, true);
}
void Instance::loadRAMBundleFromFile(
const std::string& sourcePath,
const std::string& sourceURL,
bool loadSynchronously) {
auto bundle = std::make_unique<JSIndexedRAMBundle>(sourcePath.c_str());
auto startupScript = bundle->getStartupCode();
auto registry = RAMBundleRegistry::multipleBundlesRegistry(
std::move(bundle), JSIndexedRAMBundle::buildFactory());
loadRAMBundle(
std::move(registry),
std::move(startupScript),
sourceURL,
loadSynchronously);
}
void Instance::loadRAMBundle(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL,
bool loadSynchronously) {
if (loadSynchronously) {
loadBundleSync(
std::move(bundleRegistry),
std::move(startupScript),
std::move(startupScriptSourceURL));
} else {
loadBundle(
std::move(bundleRegistry),
std::move(startupScript),
std::move(startupScriptSourceURL));
}
}
void Instance::setGlobalVariable(
std::string propName,
std::unique_ptr<const JSBigString> jsonValue) {
nativeToJsBridge_->setGlobalVariable(
std::move(propName), std::move(jsonValue));
}
void* Instance::getJavaScriptContext() {
return nativeToJsBridge_ ? nativeToJsBridge_->getJavaScriptContext()
: nullptr;
}
bool Instance::isInspectable() {
return nativeToJsBridge_ ? nativeToJsBridge_->isInspectable() : false;
}
bool Instance::isBatchActive() {
return nativeToJsBridge_ ? nativeToJsBridge_->isBatchActive() : false;
}
void Instance::callJSFunction(
std::string&& module,
std::string&& method,
folly::dynamic&& params) {
callback_->incrementPendingJSCalls();
nativeToJsBridge_->callFunction(
std::move(module), std::move(method), std::move(params));
}
void Instance::callJSCallback(uint64_t callbackId, folly::dynamic&& params) {
SystraceSection s("Instance::callJSCallback");
callback_->incrementPendingJSCalls();
nativeToJsBridge_->invokeCallback((double)callbackId, std::move(params));
}
void Instance::registerBundle(
uint32_t bundleId,
const std::string& bundlePath) {
nativeToJsBridge_->registerBundle(bundleId, bundlePath);
}
const ModuleRegistry& Instance::getModuleRegistry() const {
return *moduleRegistry_;
}
ModuleRegistry& Instance::getModuleRegistry() {
return *moduleRegistry_;
}
void Instance::handleMemoryPressure(int pressureLevel) {
if (nativeToJsBridge_) {
// This class resets `nativeToJsBridge_` only in the destructor,
// hence a race is not possible there.
nativeToJsBridge_->handleMemoryPressure(pressureLevel);
}
}
std::shared_ptr<CallInvoker> Instance::getJSCallInvoker() {
return std::static_pointer_cast<CallInvoker>(jsCallInvoker_);
}
RuntimeExecutor Instance::getRuntimeExecutor() {
// HACK: RuntimeExecutor is not compatible with non-JSIExecutor, we return
// a null callback, which the caller should handle.
if (!getJavaScriptContext()) {
return nullptr;
}
std::weak_ptr<NativeToJsBridge> weakNativeToJsBridge = nativeToJsBridge_;
return [weakNativeToJsBridge](
std::function<void(jsi::Runtime & runtime)>&& callback) {
if (auto strongNativeToJsBridge = weakNativeToJsBridge.lock()) {
strongNativeToJsBridge->runOnExecutorQueue(
[callback = std::move(callback)](JSExecutor* executor) {
// Assumes the underlying executor is a JSIExecutor
jsi::Runtime* runtime =
(jsi::Runtime*)executor->getJavaScriptContext();
try {
react_native_assert(runtime != nullptr);
callback(*runtime);
executor->flush();
} catch (jsi::JSError& originalError) {
handleJSError(*runtime, originalError, true);
}
});
}
};
}
std::shared_ptr<NativeMethodCallInvoker>
Instance::getDecoratedNativeMethodCallInvoker(
std::shared_ptr<NativeMethodCallInvoker> nativeMethodCallInvoker) {
return nativeToJsBridge_->getDecoratedNativeMethodCallInvoker(
nativeMethodCallInvoker);
}
void Instance::JSCallInvoker::setNativeToJsBridgeAndFlushCalls(
std::weak_ptr<NativeToJsBridge> nativeToJsBridge) {
std::scoped_lock guard(m_mutex);
m_shouldBuffer = false;
m_nativeToJsBridge = nativeToJsBridge;
while (m_workBuffer.size() > 0) {
scheduleAsync(std::move(m_workBuffer.front()));
m_workBuffer.pop_front();
}
}
void Instance::JSCallInvoker::invokeSync(std::function<void()>&& work) {
// TODO: Replace JS Callinvoker with RuntimeExecutor.
throw std::runtime_error(
"Synchronous native -> JS calls are currently not supported.");
}
void Instance::JSCallInvoker::invokeAsync(
std::function<void()>&& work) noexcept {
std::scoped_lock guard(m_mutex);
/**
* Why is is necessary to queue up async work?
*
* 1. TurboModuleManager must be created synchronously after the Instance,
* before we load the source code. This is when the NativeModule system
* is initialized. RCTDevLoadingView shows bundle download progress.
* 2. TurboModuleManager requires a JS CallInvoker.
* 3. The JS CallInvoker requires the NativeToJsBridge, which is created on
* the JS thread in Instance::initializeBridge.
*
* Therefore, although we don't call invokeAsync before the JS bundle is
* executed, this buffering is implemented anyways to ensure that work
* isn't discarded.
*/
if (m_shouldBuffer) {
m_workBuffer.push_back(std::move(work));
return;
}
scheduleAsync(std::move(work));
}
void Instance::JSCallInvoker::scheduleAsync(
std::function<void()>&& work) noexcept {
if (auto strongNativeToJsBridge = m_nativeToJsBridge.lock()) {
strongNativeToJsBridge->runOnExecutorQueue(
[work = std::move(work)](JSExecutor* executor) {
work();
executor->flush();
});
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,186 @@
/*
* 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 <condition_variable>
#include <list>
#include <memory>
#include <mutex>
#include <ReactCommon/RuntimeExecutor.h>
#include <cxxreact/NativeToJsBridge.h>
#include <jsinspector-modern/ReactCdp.h>
#ifndef RN_EXPORT
#define RN_EXPORT __attribute__((visibility("default")))
#endif
namespace folly {
struct dynamic;
}
namespace facebook::react {
class JSBigString;
class JSExecutorFactory;
class MessageQueueThread;
class ModuleRegistry;
class RAMBundleRegistry;
struct InstanceCallback {
virtual ~InstanceCallback() {}
virtual void onBatchComplete() {}
virtual void incrementPendingJSCalls() {}
virtual void decrementPendingJSCalls() {}
};
class RN_EXPORT Instance : private jsinspector_modern::InstanceTargetDelegate {
public:
~Instance() override;
void initializeBridge(
std::unique_ptr<InstanceCallback> callback,
std::shared_ptr<JSExecutorFactory> jsef,
std::shared_ptr<MessageQueueThread> jsQueue,
std::shared_ptr<ModuleRegistry> moduleRegistry,
jsinspector_modern::PageTarget* inspectorTarget = nullptr);
void initializeRuntime();
void setSourceURL(std::string sourceURL);
void loadScriptFromString(
std::unique_ptr<const JSBigString> string,
std::string sourceURL,
bool loadSynchronously);
void loadRAMBundleFromString(
std::unique_ptr<const JSBigString> script,
const std::string& sourceURL);
void loadRAMBundleFromFile(
const std::string& sourcePath,
const std::string& sourceURL,
bool loadSynchronously);
void loadRAMBundle(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL,
bool loadSynchronously);
bool supportsProfiling();
void setGlobalVariable(
std::string propName,
std::unique_ptr<const JSBigString> jsonValue);
void* getJavaScriptContext();
bool isInspectable();
bool isBatchActive();
void callJSFunction(
std::string&& module,
std::string&& method,
folly::dynamic&& params);
void callJSCallback(uint64_t callbackId, folly::dynamic&& params);
// This method is experimental, and may be modified or removed.
void registerBundle(uint32_t bundleId, const std::string& bundlePath);
const ModuleRegistry& getModuleRegistry() const;
ModuleRegistry& getModuleRegistry();
void handleMemoryPressure(int pressureLevel);
/**
* JS CallInvoker is used by TurboModules to schedule work on the JS thread.
*
* Why is the bridge creating JS CallInvoker?
*
* - After every Native -> JS call in the TurboModule system, the bridge
* needs to flush all queued NativeModule method calls. The bridge must
* also dispatch onBatchComplete if the queue of NativeModule method calls
* was not empty.
*/
std::shared_ptr<CallInvoker> getJSCallInvoker();
/**
* NativeMethodCallInvoker is used by TurboModules to schedule work on the
* NativeModule thread(s).
*
* Why is the bridge decorating NativeMethodCallInvoker?
*
* - The bridge must be informed of all TurboModule async method calls. Why?
* When all queued NativeModule method calls are flushed by a call from
* Native -> JS, if that queue was non-zero in size, JsToNativeBridge
* dispatches onBatchComplete. When we turn our NativeModules to
* TurboModules, there will be less and less pending NativeModule method
* calls, so onBatchComplete will not fire as often. Therefore, the bridge
* needs to know how many TurboModule async method calls have been completed
* since the last time the bridge was flushed. If this number is non-zero,
* we fire onBatchComplete.
*
* Why can't we just create and return a new NativeMethodCallInvoker?
*
* - On Android, we have one NativeModule thread. That thread is created and
* managed outside of NativeToJsBridge. On iOS, we have one MethodQueue per
* module. Those MethodQueues are also created and managed outside of
* NativeToJsBridge. Therefore, we need to pass in a
* NativeMethodCallInvoker that schedules work on the respective thread.
*/
std::shared_ptr<NativeMethodCallInvoker> getDecoratedNativeMethodCallInvoker(
std::shared_ptr<NativeMethodCallInvoker> nativeInvoker);
/**
* RuntimeExecutor is used by Fabric to access the jsi::Runtime.
*/
RuntimeExecutor getRuntimeExecutor();
/**
* Unregisters the instance from the inspector. This method must be called
* on the main (non-JS) thread.
*/
void unregisterFromInspector();
private:
void callNativeModules(folly::dynamic&& calls, bool isEndOfBatch);
void loadBundle(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL);
void loadBundleSync(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL);
std::shared_ptr<InstanceCallback> callback_;
std::shared_ptr<NativeToJsBridge> nativeToJsBridge_;
std::shared_ptr<ModuleRegistry> moduleRegistry_;
std::mutex m_syncMutex;
std::condition_variable m_syncCV;
bool m_syncReady = false;
class JSCallInvoker : public CallInvoker {
private:
std::weak_ptr<NativeToJsBridge> m_nativeToJsBridge;
std::mutex m_mutex;
bool m_shouldBuffer = true;
std::list<std::function<void()>> m_workBuffer;
void scheduleAsync(std::function<void()>&& work) noexcept;
public:
void setNativeToJsBridgeAndFlushCalls(
std::weak_ptr<NativeToJsBridge> nativeToJsBridge);
void invokeAsync(std::function<void()>&& work) noexcept override;
void invokeSync(std::function<void()>&& work) override;
};
std::shared_ptr<JSCallInvoker> jsCallInvoker_ =
std::make_shared<JSCallInvoker>();
jsinspector_modern::PageTarget* parentInspectorTarget_{nullptr};
jsinspector_modern::InstanceTarget* inspectorTarget_{nullptr};
jsinspector_modern::RuntimeTarget* runtimeInspectorTarget_{nullptr};
};
} // namespace facebook::react

View File

@@ -0,0 +1,117 @@
/*
* 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 "JSBigString.h"
#include <glog/logging.h>
#include <folly/Memory.h>
#include <folly/portability/Fcntl.h>
#include <folly/portability/SysMman.h>
#include <folly/portability/SysStat.h>
#include <folly/portability/Unistd.h>
#include <memory>
namespace facebook::react {
JSBigFileString::JSBigFileString(int fd, size_t size, off_t offset /*= 0*/)
: m_fd{-1}, m_data{nullptr} {
m_fd = dup(fd);
if (m_fd == -1) {
const char* message =
"JSBigFileString::JSBigFileString - Could not duplicate file descriptor";
LOG(ERROR) << message;
throw std::runtime_error(message);
}
// Offsets given to mmap must be page aligned. We abstract away that
// restriction by sending a page aligned offset to mmap, and keeping track
// of the offset within the page that we must alter the mmap pointer by to
// get the final desired offset.
if (offset != 0) {
const static auto ps = sysconf(_SC_PAGESIZE);
auto d = lldiv(offset, ps);
m_mapOff = static_cast<off_t>(d.quot);
m_pageOff = static_cast<off_t>(d.rem);
m_size = size + m_pageOff;
} else {
m_mapOff = 0;
m_pageOff = 0;
m_size = size;
}
}
JSBigFileString::~JSBigFileString() {
if (m_data) {
munmap((void*)m_data, m_size);
}
close(m_fd);
}
const char* JSBigFileString::c_str() const {
if (m_size == 0) {
return "";
}
if (!m_data) {
m_data =
(const char*)mmap(0, m_size, PROT_READ, MAP_PRIVATE, m_fd, m_mapOff);
CHECK(m_data != MAP_FAILED)
<< " fd: " << m_fd << " size: " << m_size << " offset: " << m_mapOff
<< " error: " << std::strerror(errno);
}
static const size_t kMinPageSize = 4096;
CHECK(!(reinterpret_cast<uintptr_t>(m_data) & (kMinPageSize - 1)))
<< "mmap address misaligned, likely corrupted"
<< " m_data: " << (const void*)m_data;
CHECK(m_pageOff <= m_size)
<< "offset impossibly large, likely corrupted"
<< " m_pageOff: " << m_pageOff << " m_size: " << m_size;
return m_data + m_pageOff;
}
size_t JSBigFileString::size() const {
// Ensure mapping has been initialized.
c_str();
return m_size - m_pageOff;
}
int JSBigFileString::fd() const {
return m_fd;
}
std::unique_ptr<const JSBigFileString> JSBigFileString::fromPath(
const std::string& sourceURL) {
int fd = ::open(sourceURL.c_str(), O_RDONLY);
if (fd == -1) {
const std::string message =
std::string("JSBigFileString::fromPath - Could not open file: ") +
sourceURL;
LOG(ERROR) << message;
throw std::runtime_error(message.c_str());
}
struct stat fileInfo {};
int res = ::fstat(fd, &fileInfo);
if (res == -1) {
const std::string message =
"JSBigFileString::fromPath - fstat on bundle failed: " + sourceURL;
LOG(ERROR) << message;
::close(fd);
throw std::runtime_error(message.c_str());
}
auto ptr = std::make_unique<const JSBigFileString>(fd, fileInfo.st_size);
CHECK(::close(fd) == 0);
return ptr;
}
} // namespace facebook::react

View File

@@ -0,0 +1,135 @@
/*
* 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 <folly/Exception.h>
#ifndef RN_EXPORT
#ifdef _MSC_VER
#define RN_EXPORT
#else
#define RN_EXPORT __attribute__((visibility("default")))
#endif
#endif
namespace facebook::react {
// JSExecutor functions sometimes take large strings, on the order of
// megabytes. Copying these can be expensive. Introducing a
// move-only, non-CopyConstructible type will let the compiler ensure
// that no copies occur. folly::MoveWrapper should be used when a
// large string needs to be curried into a std::function<>, which must
// by CopyConstructible.
class JSBigString {
public:
JSBigString() = default;
// Not copyable
JSBigString(const JSBigString&) = delete;
JSBigString& operator=(const JSBigString&) = delete;
virtual ~JSBigString() {}
virtual bool isAscii() const = 0;
// This needs to be a \0 terminated string
virtual const char* c_str() const = 0;
// Length of the c_str without the NULL byte.
virtual size_t size() const = 0;
};
// Concrete JSBigString implementation which holds a std::string
// instance.
class JSBigStdString : public JSBigString {
public:
JSBigStdString(std::string str, bool isAscii = false)
: m_isAscii(isAscii), m_str(std::move(str)) {}
bool isAscii() const override {
return m_isAscii;
}
const char* c_str() const override {
return m_str.c_str();
}
size_t size() const override {
return m_str.size();
}
private:
bool m_isAscii;
std::string m_str;
};
// Concrete JSBigString implementation which holds a heap-allocated
// buffer, and provides an accessor for writing to it. This can be
// used to construct a JSBigString in place, such as by reading from a
// file.
class RN_EXPORT JSBigBufferString : public JSBigString {
public:
JSBigBufferString(size_t size) : m_data(new char[size + 1]), m_size(size) {
// Guarantee nul-termination. The caller is responsible for
// filling in the rest of m_data.
m_data[m_size] = '\0';
}
~JSBigBufferString() override {
delete[] m_data;
}
bool isAscii() const override {
return true;
}
const char* c_str() const override {
return m_data;
}
size_t size() const override {
return m_size;
}
char* data() {
return m_data;
}
private:
char* m_data;
size_t m_size;
};
// JSBigString interface implemented by a file-backed mmap region.
class RN_EXPORT JSBigFileString : public JSBigString {
public:
JSBigFileString(int fd, size_t size, off_t offset = 0);
~JSBigFileString() override;
bool isAscii() const override {
return true;
}
const char* c_str() const override;
size_t size() const override;
int fd() const;
static std::unique_ptr<const JSBigFileString> fromPath(
const std::string& sourceURL);
private:
int m_fd; // The file descriptor being mmapped
size_t m_size; // The size of the mmapped region
mutable off_t m_pageOff; // The offset in the mmapped region to the data.
off_t m_mapOff; // The offset in the file to the mmapped region.
mutable const char* m_data; // Pointer to the mmapped region.
};
} // namespace facebook::react

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.
*/
#include "JSBundleType.h"
namespace facebook::react {
static uint32_t constexpr RAMBundleMagicNumber = 0xFB0BD1E5;
// "Hermes" in ancient Greek encoded in UTF-16BE and truncated to 8 bytes.
static uint64_t constexpr HermesBCBundleMagicNumber = 0x1F1903C103BC1FC6;
ScriptTag parseTypeFromHeader(const BundleHeader& header) {
switch (header.magic32.value) {
case RAMBundleMagicNumber:
return ScriptTag::RAMBundle;
default:
return ScriptTag::String;
}
}
const char* stringForScriptTag(const ScriptTag& tag) {
switch (tag) {
case ScriptTag::String:
return "String";
case ScriptTag::RAMBundle:
return "RAM Bundle";
}
return "";
}
bool isHermesBytecodeBundle(const BundleHeader& header) {
return header.magic64 == HermesBCBundleMagicNumber;
}
} // namespace facebook::react

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.
*/
#pragma once
#include <folly/Portability.h>
#include <cstdint>
#include <cstring>
#ifndef RN_EXPORT
#define RN_EXPORT __attribute__((visibility("default")))
#endif
namespace facebook::react {
/*
* Scripts given to the JS Executors to run could be in any of the following
* formats. They are tagged so the executor knows how to run them.
* Hermes bytecode bundles (as encoded by hermesc, not metro) are not treated
* in a special way, they will be identified as ScriptTag::String.
*/
enum struct ScriptTag {
String = 0,
RAMBundle,
};
/**
* RAM bundles and BC bundles begin with headers. For RAM bundles this is
* 4 bytes, for BC bundles this is 12 bytes. This structure holds the first 12
* bytes from a bundle in a way that gives access to that information.
*/
FOLLY_PACK_PUSH
struct FOLLY_PACK_ATTR Magic32 {
uint32_t value;
uint32_t reserved_;
};
struct FOLLY_PACK_ATTR BundleHeader {
BundleHeader() {
std::memset(this, 0, sizeof(BundleHeader));
}
union {
Magic32 magic32;
uint64_t magic64;
};
uint32_t version;
};
FOLLY_PACK_POP
/**
* Takes the first 8 bytes of a bundle, and returns a tag describing the
* bundle's format.
*/
RN_EXPORT ScriptTag parseTypeFromHeader(const BundleHeader& header);
/**
* Convert an `ScriptTag` enum into a string, useful for emitting in errors
* and diagnostic messages.
*/
RN_EXPORT const char* stringForScriptTag(const ScriptTag& tag);
/**
* Check whether a given bundle is hermesc-generated bytecode
*/
RN_EXPORT bool isHermesBytecodeBundle(const BundleHeader& header);
} // namespace facebook::react

View File

@@ -0,0 +1,50 @@
/*
* 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 "JSExecutor.h"
#include "RAMBundleRegistry.h"
#include <folly/Conv.h>
#include <jsinspector-modern/ReactCdp.h>
#include <chrono>
namespace facebook::react {
std::string JSExecutor::getSyntheticBundlePath(
uint32_t bundleId,
const std::string& bundlePath) {
if (bundleId == RAMBundleRegistry::MAIN_BUNDLE_ID) {
return bundlePath;
}
return folly::to<std::string>("seg-", bundleId, ".js");
}
double JSExecutor::performanceNow() {
auto time = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(
time.time_since_epoch())
.count();
constexpr double NANOSECONDS_IN_MILLISECOND = 1000000.0;
return duration / NANOSECONDS_IN_MILLISECOND;
}
std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate>
JSExecutor::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, getDescription());
}
} // namespace facebook::react

View File

@@ -0,0 +1,156 @@
/*
* 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 <memory>
#include <string>
#include <cxxreact/NativeModule.h>
#include <folly/dynamic.h>
#include <jsinspector-modern/InspectorInterfaces.h>
#include <jsinspector-modern/ReactCdp.h>
#ifndef RN_EXPORT
#define RN_EXPORT __attribute__((visibility("default")))
#endif
namespace facebook::react {
class JSBigString;
class JSExecutor;
class JSModulesUnbundle;
class MessageQueueThread;
class ModuleRegistry;
class RAMBundleRegistry;
// This interface describes the delegate interface required by
// Executor implementations to call from JS into native code.
class ExecutorDelegate {
public:
virtual ~ExecutorDelegate() {}
virtual std::shared_ptr<ModuleRegistry> getModuleRegistry() = 0;
virtual void callNativeModules(
JSExecutor& executor,
folly::dynamic&& calls,
bool isEndOfBatch) = 0;
virtual MethodCallResult callSerializableNativeHook(
JSExecutor& executor,
unsigned int moduleId,
unsigned int methodId,
folly::dynamic&& args) = 0;
};
class JSExecutorFactory {
public:
virtual std::unique_ptr<JSExecutor> createJSExecutor(
std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> jsQueue) = 0;
virtual ~JSExecutorFactory() {}
};
class RN_EXPORT JSExecutor : public jsinspector_modern::RuntimeTargetDelegate {
public:
/**
* Prepares the JS runtime for React Native by installing global variables.
* Called once before any JS is evaluated.
*/
virtual void initializeRuntime() = 0;
/**
* Execute an application script bundle in the JS context.
*/
virtual void loadBundle(
std::unique_ptr<const JSBigString> script,
std::string sourceURL) = 0;
/**
* Add an application "RAM" bundle registry
*/
virtual void setBundleRegistry(
std::unique_ptr<RAMBundleRegistry> bundleRegistry) = 0;
/**
* Register a file path for an additional "RAM" bundle
*/
virtual void registerBundle(
uint32_t bundleId,
const std::string& bundlePath) = 0;
/**
* Executes BatchedBridge.callFunctionReturnFlushedQueue with the module ID,
* method ID and optional additional arguments in JS. The executor is
* responsible for using Bridge->callNativeModules to invoke any necessary
* native modules methods.
*/
virtual void callFunction(
const std::string& moduleId,
const std::string& methodId,
const folly::dynamic& arguments) = 0;
/**
* Executes BatchedBridge.invokeCallbackAndReturnFlushedQueue with the cbID,
* and optional additional arguments in JS and returns the next queue. The
* executor is responsible for using Bridge->callNativeModules to invoke any
* necessary native modules methods.
*/
virtual void invokeCallback(
const double callbackId,
const folly::dynamic& arguments) = 0;
virtual void setGlobalVariable(
std::string propName,
std::unique_ptr<const JSBigString> jsonValue) = 0;
virtual void* getJavaScriptContext() {
return nullptr;
}
/**
* Returns whether or not the underlying executor supports debugging via the
* Chrome remote debugging protocol. If true, the executor should also
* override the \c createAgentDelegate method.
*/
virtual bool isInspectable() {
return false;
}
/**
* The description is displayed in the dev menu, if there is one in
* this build. There is a default, but if this method returns a
* non-empty string, it will be used instead.
*/
virtual std::string getDescription() = 0;
virtual void handleMemoryPressure([[maybe_unused]] int pressureLevel) {}
virtual void destroy() {}
virtual ~JSExecutor() override {}
virtual void flush() {}
static std::string getSyntheticBundlePath(
uint32_t bundleId,
const std::string& bundlePath);
static double performanceNow();
/**
* Create a RuntimeAgentDelegate that can be used to debug the JS VM instance.
*/
virtual 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;
};
} // namespace facebook::react

View File

@@ -0,0 +1,128 @@
/*
* 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 "JSIndexedRAMBundle.h"
#include <glog/logging.h>
#include <fstream>
#include <memory>
#include <sstream>
namespace facebook::react {
std::function<std::unique_ptr<JSModulesUnbundle>(std::string)>
JSIndexedRAMBundle::buildFactory() {
return [](const std::string& bundlePath) {
return std::make_unique<JSIndexedRAMBundle>(bundlePath.c_str());
};
}
JSIndexedRAMBundle::JSIndexedRAMBundle(const char* sourcePath) {
m_bundle = std::make_unique<std::ifstream>(sourcePath, std::ifstream::binary);
if (!m_bundle) {
throw std::ios_base::failure(folly::to<std::string>(
"Bundle ", sourcePath, "cannot be opened: ", m_bundle->rdstate()));
}
init();
}
JSIndexedRAMBundle::JSIndexedRAMBundle(
std::unique_ptr<const JSBigString> script) {
// tmpStream is needed because m_bundle is std::istream type
// which has no member 'write'
std::unique_ptr<std::stringstream> tmpStream =
std::make_unique<std::stringstream>();
tmpStream->write(script->c_str(), script->size());
m_bundle = std::move(tmpStream);
if (!m_bundle) {
throw std::ios_base::failure(folly::to<std::string>(
"Bundle from string cannot be opened: ", m_bundle->rdstate()));
}
init();
}
void JSIndexedRAMBundle::init() {
// read in magic header, number of entries, and length of the startup section
uint32_t header[3];
static_assert(
sizeof(header) == 12,
"header size must exactly match the input file format");
readBundle(reinterpret_cast<char*>(header), sizeof(header));
const size_t numTableEntries = folly::Endian::little(header[1]);
const size_t startupCodeSize = folly::Endian::little(header[2]);
// allocate memory for meta data and lookup table.
m_table = ModuleTable(numTableEntries);
m_baseOffset = sizeof(header) + m_table.byteLength();
// read the lookup table from the file
readBundle(reinterpret_cast<char*>(m_table.data.get()), m_table.byteLength());
// read the startup code
m_startupCode = std::unique_ptr<JSBigBufferString>(
new JSBigBufferString{startupCodeSize - 1});
readBundle(m_startupCode->data(), startupCodeSize - 1);
}
JSIndexedRAMBundle::Module JSIndexedRAMBundle::getModule(
uint32_t moduleId) const {
Module ret;
ret.name = folly::to<std::string>(moduleId, ".js");
ret.code = getModuleCode(moduleId);
return ret;
}
std::unique_ptr<const JSBigString> JSIndexedRAMBundle::getStartupCode() {
CHECK(m_startupCode)
<< "startup code for a RAM Bundle can only be retrieved once";
return std::move(m_startupCode);
}
std::string JSIndexedRAMBundle::getModuleCode(const uint32_t id) const {
const auto moduleData = id < m_table.numEntries ? &m_table.data[id] : nullptr;
// entries without associated code have offset = 0 and length = 0
const uint32_t length =
moduleData ? folly::Endian::little(moduleData->length) : 0;
if (length == 0) {
throw std::ios_base::failure(
folly::to<std::string>("Error loading module", id, "from RAM Bundle"));
}
std::string ret(length - 1, '\0');
readBundle(
&ret.front(),
length - 1,
m_baseOffset + folly::Endian::little(moduleData->offset));
return ret;
}
void JSIndexedRAMBundle::readBundle(char* buffer, const std::streamsize bytes)
const {
if (!m_bundle->read(buffer, bytes)) {
if (m_bundle->rdstate() & std::ios::eofbit) {
throw std::ios_base::failure("Unexpected end of RAM Bundle file");
}
throw std::ios_base::failure(folly::to<std::string>(
"Error reading RAM Bundle: ", m_bundle->rdstate()));
}
}
void JSIndexedRAMBundle::readBundle(
char* buffer,
const std::streamsize bytes,
const std::ifstream::pos_type position) const {
if (!m_bundle->seekg(position)) {
throw std::ios_base::failure(folly::to<std::string>(
"Error reading RAM Bundle: ", m_bundle->rdstate()));
}
readBundle(buffer, bytes);
}
} // namespace facebook::react

View File

@@ -0,0 +1,71 @@
/*
* 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 <istream>
#include <memory>
#include <cxxreact/JSBigString.h>
#include <cxxreact/JSModulesUnbundle.h>
#ifndef RN_EXPORT
#define RN_EXPORT __attribute__((visibility("default")))
#endif
namespace facebook::react {
class RN_EXPORT JSIndexedRAMBundle : public JSModulesUnbundle {
public:
static std::function<std::unique_ptr<JSModulesUnbundle>(std::string)>
buildFactory();
// Throws std::runtime_error on failure.
JSIndexedRAMBundle(const char* sourceURL);
JSIndexedRAMBundle(std::unique_ptr<const JSBigString> script);
// Throws std::runtime_error on failure.
std::unique_ptr<const JSBigString> getStartupCode();
// Throws std::runtime_error on failure.
Module getModule(uint32_t moduleId) const override;
private:
struct ModuleData {
uint32_t offset;
uint32_t length;
};
static_assert(
sizeof(ModuleData) == 8,
"ModuleData must not have any padding and use sizes matching input files");
struct ModuleTable {
size_t numEntries;
std::unique_ptr<ModuleData[]> data;
ModuleTable() : numEntries(0){};
ModuleTable(size_t entries)
: numEntries(entries),
data(std::unique_ptr<ModuleData[]>(new ModuleData[numEntries])){};
size_t byteLength() const {
return numEntries * sizeof(ModuleData);
}
};
void init();
std::string getModuleCode(const uint32_t id) const;
void readBundle(char* buffer, const std::streamsize bytes) const;
void readBundle(
char* buffer,
const std::streamsize bytes,
const std::istream::pos_type position) const;
mutable std::unique_ptr<std::istream> m_bundle;
ModuleTable m_table;
size_t m_baseOffset;
std::unique_ptr<JSBigBufferString> m_startupCode;
};
} // namespace facebook::react

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <cstdint>
#include <stdexcept>
#include <string>
#include <folly/Conv.h>
namespace facebook::react {
class JSModulesUnbundle {
/**
* Represents the set of JavaScript modules that the application consists of.
* The source code of each module can be retrieved by module ID.
*
* The class is non-copyable because copying instances might involve copying
* several megabytes of memory.
*/
public:
class ModuleNotFound : public std::out_of_range {
public:
using std::out_of_range::out_of_range;
ModuleNotFound(uint32_t moduleId)
: std::out_of_range::out_of_range(
folly::to<std::string>("Module not found: ", moduleId)) {}
};
struct Module {
std::string name;
std::string code;
};
JSModulesUnbundle() {}
virtual ~JSModulesUnbundle() {}
virtual Module getModule(uint32_t moduleId) const = 0;
private:
JSModulesUnbundle(const JSModulesUnbundle&) = delete;
};
} // namespace facebook::react

View File

@@ -0,0 +1,120 @@
/*
* 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 <folly/dynamic.h>
namespace facebook {
namespace xplat {
namespace detail {
template <typename R, typename M, typename... T>
R jsArg1(const folly::dynamic& arg, M asFoo, const T&... desc) {
try {
return (arg.*asFoo)();
} catch (const folly::TypeError& ex) {
throw JsArgumentException(folly::to<std::string>(
"Error converting javascript arg ", desc..., " to C++: ", ex.what()));
} catch (const std::range_error& ex) {
throw JsArgumentException(folly::to<std::string>(
"Could not convert argument ",
desc...,
" to required type: ",
ex.what()));
}
}
} // namespace detail
template <typename R, typename... T>
R jsArg(
const folly::dynamic& arg,
R (folly::dynamic::*asFoo)() const,
const T&... desc) {
return detail::jsArg1<R>(arg, asFoo, desc...);
}
template <typename R, typename... T>
R jsArg(
const folly::dynamic& arg,
R (folly::dynamic::*asFoo)() const&,
const T&... desc) {
return detail::jsArg1<R>(arg, asFoo, desc...);
}
template <typename T>
// NOLINTNEXTLINE (T62192316)
typename detail::is_dynamic<T>::type& jsArgAsDynamic(T&& args, size_t n) {
try {
return args[n];
} catch (const std::out_of_range& ex) {
// Use 1-base counting for argument description.
throw JsArgumentException(folly::to<std::string>(
"JavaScript provided ",
args.size(),
" arguments for C++ method which references at least ",
n + 1,
" arguments: ",
ex.what()));
}
}
template <typename R>
R jsArgN(
const folly::dynamic& args,
size_t n,
R (folly::dynamic::*asFoo)() const) {
return jsArg(jsArgAsDynamic(args, n), asFoo, n);
}
template <typename R>
R jsArgN(
const folly::dynamic& args,
size_t n,
R (folly::dynamic::*asFoo)() const&) {
return jsArg(jsArgAsDynamic(args, n), asFoo, n);
}
namespace detail {
// This is a helper for jsArgAsArray and jsArgAsObject.
template <typename T>
typename detail::is_dynamic<T>::type& jsArgAsType(
T&& args,
size_t n,
const char* required,
bool (folly::dynamic::*isFoo)() const) {
T& ret = jsArgAsDynamic(args, n);
if ((ret.*isFoo)()) {
return ret;
}
// Use 1-base counting for argument description.
throw JsArgumentException(folly::to<std::string>(
"Argument ",
n + 1,
" of type ",
ret.typeName(),
" is not required type ",
required));
}
} // end namespace detail
template <typename T>
typename detail::is_dynamic<T>::type& jsArgAsArray(T&& args, size_t n) {
return detail::jsArgAsType(args, n, "Array", &folly::dynamic::isArray);
}
template <typename T>
typename detail::is_dynamic<T>::type& jsArgAsObject(T&& args, size_t n) {
return detail::jsArgAsType(args, n, "Object", &folly::dynamic::isObject);
}
} // namespace xplat
} // namespace facebook

View File

@@ -0,0 +1,125 @@
/*
* 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 <exception>
#include <string>
#include <folly/Conv.h>
#include <folly/dynamic.h>
// When building a cross-platform module for React Native, arguments passed
// from JS are represented as a folly::dynamic. This class provides helpers to
// extract arguments from the folly::dynamic to concrete types usable by
// cross-platform code, and converting exceptions to a JsArgumentException so
// they can be caught and reported to RN consistently. The goal is to make the
// jsArgAs... methods at the end simple to use should be most common, but any
// non-detail method can be used when needed.
namespace facebook {
namespace xplat {
class JsArgumentException : public std::logic_error {
public:
JsArgumentException(const std::string& msg) : std::logic_error(msg) {}
};
// This extracts a single argument by calling the given method pointer on it.
// If an exception is thrown, the additional arguments are passed to
// folly::to<> to be included in the exception string. This will be most
// commonly used when extracting values from non-scalar argument. The second
// overload accepts ref-qualified member functions.
template <typename R, typename... T>
R jsArg(
const folly::dynamic& arg,
R (folly::dynamic::*asFoo)() const,
const T&... desc);
template <typename R, typename... T>
R jsArg(
const folly::dynamic& arg,
R (folly::dynamic::*asFoo)() const&,
const T&... desc);
// This is like jsArg, but a operates on a dynamic representing an array of
// arguments. The argument n is used both to index the array and build the
// exception message, if any. It can be used directly, but will more often be
// used by the type-specific methods following.
template <typename R>
R jsArgN(
const folly::dynamic& args,
size_t n,
R (folly::dynamic::*asFoo)() const);
template <typename R>
R jsArgN(
const folly::dynamic& args,
size_t n,
R (folly::dynamic::*asFoo)() const&);
namespace detail {
// This is a type helper to implement functions which should work on both const
// and non-const folly::dynamic arguments, and return a type with the same
// constness. Basically, it causes the templates which use it to be defined
// only for types compatible with folly::dynamic.
template <typename T>
struct is_dynamic {
typedef typename std::
enable_if<std::is_assignable<folly::dynamic, T>::value, T>::type type;
};
} // end namespace detail
// Easy to use conversion helpers are here:
// Extract the n'th arg from the given dynamic, as a dynamic. Throws a
// JsArgumentException if there is no n'th arg in the input.
template <typename T>
typename detail::is_dynamic<T>::type& jsArgAsDynamic(T&& args, size_t n);
// Extract the n'th arg from the given dynamic, as a dynamic Array. Throws a
// JsArgumentException if there is no n'th arg in the input, or it is not an
// Array.
template <typename T>
typename detail::is_dynamic<T>::type& jsArgAsArray(T&& args, size_t n);
// Extract the n'th arg from the given dynamic, as a dynamic Object. Throws a
// JsArgumentException if there is no n'th arg in the input, or it is not an
// Object.
template <typename T>
typename detail::is_dynamic<T>::type& jsArgAsObject(T&& args, size_t n);
// Extract the n'th arg from the given dynamic, as a bool. Throws a
// JsArgumentException if this fails for some reason.
inline bool jsArgAsBool(const folly::dynamic& args, size_t n) {
return jsArgN(args, n, &folly::dynamic::asBool);
}
// Extract the n'th arg from the given dynamic, as an integer. Throws a
// JsArgumentException if this fails for some reason.
inline int64_t jsArgAsInt(const folly::dynamic& args, size_t n) {
return jsArgN(args, n, &folly::dynamic::asInt);
}
// Extract the n'th arg from the given dynamic, as a double. Throws a
// JsArgumentException if this fails for some reason.
inline double jsArgAsDouble(const folly::dynamic& args, size_t n) {
return jsArgN(args, n, &folly::dynamic::asDouble);
}
// Extract the n'th arg from the given dynamic, as a string. Throws a
// JsArgumentException if this fails for some reason.
inline std::string jsArgAsString(const folly::dynamic& args, size_t n) {
return jsArgN(args, n, &folly::dynamic::asString);
}
} // namespace xplat
} // namespace facebook
#include <cxxreact/JsArgumentHelpers-inl.h>

View File

@@ -0,0 +1,27 @@
/*
* 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 <condition_variable>
#include <functional>
#include <mutex>
namespace facebook::react {
class MessageQueueThread {
public:
virtual ~MessageQueueThread() {}
virtual void runOnQueue(std::function<void()>&&) = 0;
// runOnQueueSync and quitSynchronous are dangerous. They should only be
// used for initialization and cleanup.
virtual void runOnQueueSync(std::function<void()>&&) = 0;
// Once quitSynchronous() returns, no further work should run on the queue.
virtual void quitSynchronous() = 0;
};
} // namespace facebook::react

View File

@@ -0,0 +1,87 @@
/*
* 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 "MethodCall.h"
#include <folly/json.h>
#include <stdexcept>
namespace facebook::react {
#define REQUEST_MODULE_IDS 0
#define REQUEST_METHOD_IDS 1
#define REQUEST_PARAMS 2
#define REQUEST_CALLID 3
static const char* errorPrefix = "Malformed calls from JS: ";
std::vector<MethodCall> parseMethodCalls(folly::dynamic&& jsonData) {
if (jsonData.isNull()) {
return {};
}
if (!jsonData.isArray()) {
throw std::invalid_argument(folly::to<std::string>(
errorPrefix, "input isn't array but ", jsonData.typeName()));
}
if (jsonData.size() < REQUEST_PARAMS + 1) {
throw std::invalid_argument(
folly::to<std::string>(errorPrefix, "size == ", jsonData.size()));
}
auto& moduleIds = jsonData[REQUEST_MODULE_IDS];
auto& methodIds = jsonData[REQUEST_METHOD_IDS];
auto& params = jsonData[REQUEST_PARAMS];
int callId = -1;
if (!moduleIds.isArray() || !methodIds.isArray() || !params.isArray()) {
throw std::invalid_argument(folly::to<std::string>(
errorPrefix,
"not all fields are arrays.\n\n",
folly::toJson(jsonData)));
}
if (moduleIds.size() != methodIds.size() ||
moduleIds.size() != params.size()) {
throw std::invalid_argument(folly::to<std::string>(
errorPrefix,
"field sizes are different.\n\n",
folly::toJson(jsonData)));
}
if (jsonData.size() > REQUEST_CALLID) {
if (!jsonData[REQUEST_CALLID].isNumber()) {
throw std::invalid_argument(folly::to<std::string>(
errorPrefix, "invalid callId", jsonData[REQUEST_CALLID].typeName()));
}
callId = (int)jsonData[REQUEST_CALLID].asInt();
}
std::vector<MethodCall> methodCalls;
for (size_t i = 0; i < moduleIds.size(); i++) {
if (!params[i].isArray()) {
throw std::invalid_argument(folly::to<std::string>(
errorPrefix,
"method arguments isn't array but ",
params[i].typeName()));
}
methodCalls.emplace_back(
static_cast<int>(moduleIds[i].asInt()),
static_cast<int>(methodIds[i].asInt()),
std::move(params[i]),
callId);
// only increment callid if contains valid callid as callid is optional
callId += (callId != -1) ? 1 : 0;
}
return methodCalls;
}
} // namespace facebook::react

View File

@@ -0,0 +1,34 @@
/*
* 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 <map>
#include <string>
#include <vector>
#include <folly/dynamic.h>
namespace facebook::react {
struct MethodCall {
int moduleId;
int methodId;
folly::dynamic arguments;
int callId;
MethodCall(int mod, int meth, folly::dynamic&& args, int cid)
: moduleId(mod),
methodId(meth),
arguments(std::move(args)),
callId(cid) {}
};
/// \throws std::invalid_argument
std::vector<MethodCall> parseMethodCalls(folly::dynamic&& calls);
} // namespace facebook::react

View File

@@ -0,0 +1,238 @@
/*
* 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 "ModuleRegistry.h"
#include <glog/logging.h>
#include <reactperflogger/BridgeNativeModulePerfLogger.h>
#include "NativeModule.h"
#include "SystraceSection.h"
namespace facebook::react {
namespace {
std::string normalizeName(std::string name) {
// TODO mhorowitz #10487027: This is super ugly. We should just
// change iOS to emit normalized names, drop the "RK..." from
// names hardcoded in Android, and then delete this and the
// similar hacks in js.
if (name.compare(0, 3, "RCT") == 0) {
return name.substr(3);
} else if (name.compare(0, 2, "RK") == 0) {
return name.substr(2);
}
return name;
}
} // namespace
ModuleRegistry::ModuleRegistry(
std::vector<std::unique_ptr<NativeModule>> modules,
ModuleNotFoundCallback callback)
: modules_{std::move(modules)}, moduleNotFoundCallback_{callback} {}
void ModuleRegistry::updateModuleNamesFromIndex(size_t index) {
for (; index < modules_.size(); index++) {
std::string name = normalizeName(modules_[index]->getName());
modulesByName_[name] = index;
}
}
void ModuleRegistry::registerModules(
std::vector<std::unique_ptr<NativeModule>> modules) {
SystraceSection s_("ModuleRegistry::registerModules");
// Noop if there are no NativeModules to add
if (modules.empty()) {
return;
}
if (modules_.empty() && unknownModules_.empty()) {
modules_ = std::move(modules);
} else {
size_t modulesSize = modules_.size();
size_t addModulesSize = modules.size();
bool addToNames = !modulesByName_.empty();
modules_.reserve(modulesSize + addModulesSize);
std::move(modules.begin(), modules.end(), std::back_inserter(modules_));
if (!unknownModules_.empty()) {
for (size_t index = modulesSize; index < modulesSize + addModulesSize;
index++) {
std::string name = normalizeName(modules_[index]->getName());
auto it = unknownModules_.find(name);
if (it != unknownModules_.end()) {
throw std::runtime_error(folly::to<std::string>(
"module ",
name,
" was required without being registered and is now being registered."));
} else if (addToNames) {
modulesByName_[name] = index;
}
}
} else if (addToNames) {
updateModuleNamesFromIndex(modulesSize);
}
}
}
std::vector<std::string> ModuleRegistry::moduleNames() {
SystraceSection s_("ModuleRegistry::moduleNames");
std::vector<std::string> names;
for (size_t i = 0; i < modules_.size(); i++) {
std::string name = normalizeName(modules_[i]->getName());
modulesByName_[name] = i;
names.push_back(std::move(name));
}
return names;
}
std::optional<ModuleConfig> ModuleRegistry::getConfig(const std::string& name) {
SystraceSection s("ModuleRegistry::getConfig", "module", name);
// Initialize modulesByName_
if (modulesByName_.empty() && !modules_.empty()) {
moduleNames();
}
auto it = modulesByName_.find(name);
if (it == modulesByName_.end()) {
if (unknownModules_.find(name) != unknownModules_.end()) {
BridgeNativeModulePerfLogger::moduleJSRequireBeginningFail(name.c_str());
BridgeNativeModulePerfLogger::moduleJSRequireEndingStart(name.c_str());
return std::nullopt;
}
if (!moduleNotFoundCallback_) {
unknownModules_.insert(name);
BridgeNativeModulePerfLogger::moduleJSRequireBeginningFail(name.c_str());
BridgeNativeModulePerfLogger::moduleJSRequireEndingStart(name.c_str());
return std::nullopt;
}
BridgeNativeModulePerfLogger::moduleJSRequireBeginningEnd(name.c_str());
bool wasModuleLazilyLoaded = moduleNotFoundCallback_(name);
it = modulesByName_.find(name);
bool wasModuleRegisteredWithRegistry =
wasModuleLazilyLoaded && it != modulesByName_.end();
if (!wasModuleRegisteredWithRegistry) {
BridgeNativeModulePerfLogger::moduleJSRequireEndingStart(name.c_str());
unknownModules_.insert(name);
return std::nullopt;
}
} else {
BridgeNativeModulePerfLogger::moduleJSRequireBeginningEnd(name.c_str());
}
// If we've gotten this far, then we've signaled moduleJSRequireBeginningEnd
size_t index = it->second;
CHECK(index < modules_.size());
NativeModule* module = modules_[index].get();
// string name, object constants, array methodNames (methodId is index),
// [array promiseMethodIds], [array syncMethodIds]
folly::dynamic config = folly::dynamic::array(name);
{
SystraceSection s_("ModuleRegistry::getConstants", "module", name);
/**
* In the case that there are constants, we'll initialize the NativeModule,
* and signal moduleJSRequireEndingStart. Otherwise, we'll simply signal the
* event. The Module will be initialized when we invoke one of its
* NativeModule methods.
*/
config.push_back(module->getConstants());
}
{
SystraceSection s_("ModuleRegistry::getMethods", "module", name);
std::vector<MethodDescriptor> methods = module->getMethods();
folly::dynamic methodNames = folly::dynamic::array;
folly::dynamic promiseMethodIds = folly::dynamic::array;
folly::dynamic syncMethodIds = folly::dynamic::array;
for (auto& descriptor : methods) {
// TODO: #10487027 compare tags instead of doing string comparison?
methodNames.push_back(std::move(descriptor.name));
if (descriptor.type == "promise") {
promiseMethodIds.push_back(methodNames.size() - 1);
} else if (descriptor.type == "sync") {
syncMethodIds.push_back(methodNames.size() - 1);
}
}
if (!methodNames.empty()) {
config.push_back(std::move(methodNames));
if (!promiseMethodIds.empty() || !syncMethodIds.empty()) {
config.push_back(std::move(promiseMethodIds));
if (!syncMethodIds.empty()) {
config.push_back(std::move(syncMethodIds));
}
}
}
}
if (config.size() == 2 && config[1].empty()) {
// no constants or methods
return std::nullopt;
} else {
return ModuleConfig{index, std::move(config)};
}
}
std::string ModuleRegistry::getModuleName(unsigned int moduleId) {
if (moduleId >= modules_.size()) {
throw std::runtime_error(folly::to<std::string>(
"moduleId ", moduleId, " out of range [0..", modules_.size(), ")"));
}
return modules_[moduleId]->getName();
}
std::string ModuleRegistry::getModuleSyncMethodName(
unsigned int moduleId,
unsigned int methodId) {
if (moduleId >= modules_.size()) {
throw std::runtime_error(folly::to<std::string>(
"moduleId ", moduleId, " out of range [0..", modules_.size(), ")"));
}
return modules_[moduleId]->getSyncMethodName(methodId);
}
void ModuleRegistry::callNativeMethod(
unsigned int moduleId,
unsigned int methodId,
folly::dynamic&& params,
int callId) {
if (moduleId >= modules_.size()) {
throw std::runtime_error(folly::to<std::string>(
"moduleId ", moduleId, " out of range [0..", modules_.size(), ")"));
}
modules_[moduleId]->invoke(methodId, std::move(params), callId);
}
MethodCallResult ModuleRegistry::callSerializableNativeHook(
unsigned int moduleId,
unsigned int methodId,
folly::dynamic&& params) {
if (moduleId >= modules_.size()) {
throw std::runtime_error(folly::to<std::string>(
"moduleId ", moduleId, "out of range [0..", modules_.size(), ")"));
}
return modules_[moduleId]->callSerializableNativeHook(
methodId, std::move(params));
}
} // namespace facebook::react

View File

@@ -0,0 +1,90 @@
/*
* 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 <memory>
#include <unordered_set>
#include <vector>
#include <cxxreact/JSExecutor.h>
#include <folly/dynamic.h>
#include <optional>
#ifndef RN_EXPORT
#define RN_EXPORT __attribute__((visibility("default")))
#endif
namespace facebook::react {
class NativeModule;
struct ModuleConfig {
size_t index;
folly::dynamic config;
};
class RN_EXPORT ModuleRegistry {
public:
// not implemented:
// onBatchComplete: see
// https://our.intern.facebook.com/intern/tasks/?t=5279396 getModule: only
// used by views getAllModules: only used for cleanup; use RAII instead
// notifyCatalystInstanceInitialized: this is really only used by view-related
// code notifyCatalystInstanceDestroy: use RAII instead
using ModuleNotFoundCallback = std::function<bool(const std::string& name)>;
ModuleRegistry(
std::vector<std::unique_ptr<NativeModule>> modules,
ModuleNotFoundCallback callback = nullptr);
void registerModules(std::vector<std::unique_ptr<NativeModule>> modules);
std::vector<std::string> moduleNames();
std::optional<ModuleConfig> getConfig(const std::string& name);
void callNativeMethod(
unsigned int moduleId,
unsigned int methodId,
folly::dynamic&& params,
int callId);
MethodCallResult callSerializableNativeHook(
unsigned int moduleId,
unsigned int methodId,
folly::dynamic&& args);
std::string getModuleName(unsigned int moduleId);
std::string getModuleSyncMethodName(
unsigned int moduleId,
unsigned int methodName);
private:
// This is always populated
std::vector<std::unique_ptr<NativeModule>> modules_;
// This is used to extend the population of modulesByName_ if registerModules
// is called after moduleNames
void updateModuleNamesFromIndex(size_t size);
// This is only populated if moduleNames() is called. Values are indices into
// modules_.
std::unordered_map<std::string, size_t> modulesByName_;
// This is populated with modules that are requested via getConfig but are
// unknown. An error will be thrown if they are subsequently added to the
// registry.
std::unordered_set<std::string> unknownModules_;
// Function will be called if a module was requested but was not found.
// If the function returns true, ModuleRegistry will try to find the module
// again (assuming it's registered) If the function returns false,
// ModuleRegistry will not try to find the module and return nullptr instead.
ModuleNotFoundCallback moduleNotFoundCallback_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,125 @@
/*
* 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 <memory>
namespace facebook::react {
/*
NOTE: we keep this internal copy of folly/MoveWrapper.h to unblock
the the workstream of dropping the dependency on folly in RN!
For a technical explanation on why we still need this we defer
to the doc in folly/Function.h:
"There are some limitations in std::function that folly::Function tries to
avoid. std::function is copy-constructible and requires that the callable that
it wraps is copy-constructible as well, which is a constraint that is often
inconvenient. In most cases when using a std::function you don't make use of
its copy-constructibility, so you might sometimes feel like you get back very
little in return for a noticeable restriction. This restriction becomes
apparent when trying to use a lambda capturing a unique_ptr (or any
non-copyable type) as a callback for a folly::Future.
std::unique_ptr<Foo> foo_ptr = new Foo;
some_future.then(
[foo_ptr = std::move(foo_ptr)] mutable
(int x)
{ foo_ptr->setX(x); }
);
This piece of code did not compile before folly::Future started using
folly::Function instead of std::function to store the callback. Because the
lambda captures something non-copyable (the unique_ptr), it is not copyable
itself. And std::function can only store copyable callables.
The implementation of folly::Future did not make use of the
copy-constructibility of std::function at any point. There was no benefit from
the fact that the std::function is copy-constructible, but the fact that it can
only wrap copy-constructible callables posed a restriction.
A workaround was available: folly::MoveWrapper, which wraps an object that may
be non-copyable and implements copy operations by moving the embedded object.
Using a folly::MoveWrapper, you can capture non-copyable objects in a lambda,
and the lambda itself is still copyable and may be wrapped in a std::function.
It is a pragmatic solution for the above problem, but you have to be a little
careful. The problem is that you cant use a MoveWrapper anywhere where copy
operations are assumed to behave like actual copy operations. Also, a
folly::MoveWrapper<std::unique_ptr<T>> essentially behaves like auto_ptr<T>. Ask
yourself whether youd want to use lots of auto_ptrs in your codebase. And the
original question still persists: we very often dont benefit from
copy-constructibility of std::function, so why do we have to live with this
restriction? I.e. why do we have to use MoveWrapper?"
*/
/** C++11 closures don't support move-in capture. Nor does std::bind.
facepalm.
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3610.html
"[...] a work-around that should make people's stomach crawl:
write a wrapper that performs move-on-copy, much like the deprecated
auto_ptr"
Unlike auto_ptr, this doesn't require a heap allocation.
*/
template <class T>
class MoveWrapper {
public:
/** If value can be default-constructed, why not?
Then we don't have to move it in */
MoveWrapper() = default;
/// Move a value in.
explicit MoveWrapper(T&& t) : value(std::move(t)) {}
/// copy is move
MoveWrapper(const MoveWrapper& other) : value(std::move(other.value)) {}
/// move is also move
MoveWrapper(MoveWrapper&& other) noexcept : value(std::move(other.value)) {}
const T& operator*() const {
return value;
}
T& operator*() {
return value;
}
const T* operator->() const {
return &value;
}
T* operator->() {
return &value;
}
/// move the value out (sugar for std::move(*moveWrapper))
T&& move() {
return std::move(value);
}
// If you want these you're probably doing it wrong, though they'd be
// easy enough to implement
MoveWrapper& operator=(MoveWrapper const&) = delete;
MoveWrapper& operator=(MoveWrapper&&) = delete;
private:
mutable T value;
};
/// Make a MoveWrapper from the argument. Because the name "makeMoveWrapper"
/// is already quite transparent in its intent, this will work for lvalues as
/// if you had wrapped them in std::move.
template <class T, class T0 = typename std::remove_reference<T>::type>
MoveWrapper<T0> makeMoveWrapper(T&& t) {
return MoveWrapper<T0>(std::forward<T0>(t));
}
} // namespace facebook::react

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <string>
#include <vector>
#include <folly/dynamic.h>
#include <optional>
namespace facebook::react {
struct MethodDescriptor {
std::string name;
// type is one of js MessageQueue.MethodTypes
std::string type;
MethodDescriptor(std::string n, std::string t)
: name(std::move(n)), type(std::move(t)) {}
};
using MethodCallResult = std::optional<folly::dynamic>;
class NativeModule {
public:
virtual ~NativeModule() {}
virtual std::string getName() = 0;
virtual std::string getSyncMethodName(unsigned int methodId) = 0;
virtual std::vector<MethodDescriptor> getMethods() = 0;
virtual folly::dynamic getConstants() = 0;
virtual void
invoke(unsigned int reactMethodId, folly::dynamic&& params, int callId) = 0;
virtual MethodCallResult callSerializableNativeHook(
unsigned int reactMethodId,
folly::dynamic&& args) = 0;
};
} // namespace facebook::react

View File

@@ -0,0 +1,351 @@
/*
* 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 "NativeToJsBridge.h"
#include <ReactCommon/CallInvoker.h>
#include <folly/json.h>
#include <glog/logging.h>
#include <jsi/jsi.h>
#include <reactperflogger/BridgeNativeModulePerfLogger.h>
#include "ErrorUtils.h"
#include "Instance.h"
#include "JSBigString.h"
#include "MessageQueueThread.h"
#include "MethodCall.h"
#include "ModuleRegistry.h"
#include "MoveWrapper.h"
#include "RAMBundleRegistry.h"
#include "SystraceSection.h"
#include <memory>
#ifdef WITH_FBSYSTRACE
#include <fbsystrace.h>
using fbsystrace::FbSystraceAsyncFlow;
#endif
namespace facebook::react {
// This class manages calls from JS to native code.
class JsToNativeBridge : public react::ExecutorDelegate {
public:
JsToNativeBridge(
std::shared_ptr<ModuleRegistry> registry,
std::shared_ptr<InstanceCallback> callback)
: m_registry(registry), m_callback(callback) {}
std::shared_ptr<ModuleRegistry> getModuleRegistry() override {
return m_registry;
}
bool isBatchActive() {
return m_batchHadNativeModuleOrTurboModuleCalls;
}
void callNativeModules(
[[maybe_unused]] JSExecutor& executor,
folly::dynamic&& calls,
bool isEndOfBatch) override {
CHECK(m_registry || calls.empty())
<< "native module calls cannot be completed with no native modules";
m_batchHadNativeModuleOrTurboModuleCalls =
m_batchHadNativeModuleOrTurboModuleCalls || !calls.empty();
std::vector<MethodCall> methodCalls = parseMethodCalls(std::move(calls));
BridgeNativeModulePerfLogger::asyncMethodCallBatchPreprocessEnd(
(int)methodCalls.size());
// An exception anywhere in here stops processing of the batch. This
// was the behavior of the Android bridge, and since exception handling
// terminates the whole bridge, there's not much point in continuing.
for (auto& call : methodCalls) {
m_registry->callNativeMethod(
call.moduleId, call.methodId, std::move(call.arguments), call.callId);
}
if (isEndOfBatch) {
// onBatchComplete will be called on the native (module) queue, but
// decrementPendingJSCalls will be called sync. Be aware that the bridge
// may still be processing native calls when the bridge idle signaler
// fires.
if (m_batchHadNativeModuleOrTurboModuleCalls) {
m_callback->onBatchComplete();
m_batchHadNativeModuleOrTurboModuleCalls = false;
}
m_callback->decrementPendingJSCalls();
}
}
MethodCallResult callSerializableNativeHook(
[[maybe_unused]] JSExecutor& executor,
unsigned int moduleId,
unsigned int methodId,
folly::dynamic&& args) override {
return m_registry->callSerializableNativeHook(
moduleId, methodId, std::move(args));
}
void recordTurboModuleAsyncMethodCall() noexcept {
m_batchHadNativeModuleOrTurboModuleCalls = true;
}
private:
// These methods are always invoked from an Executor. The NativeToJsBridge
// keeps a reference to the executor, and when destroy() is called, the
// executor is destroyed synchronously on its queue.
std::shared_ptr<ModuleRegistry> m_registry;
std::shared_ptr<InstanceCallback> m_callback;
bool m_batchHadNativeModuleOrTurboModuleCalls = false;
};
NativeToJsBridge::NativeToJsBridge(
JSExecutorFactory* jsExecutorFactory,
std::shared_ptr<ModuleRegistry> registry,
std::shared_ptr<MessageQueueThread> jsQueue,
std::shared_ptr<InstanceCallback> callback)
: m_destroyed(std::make_shared<bool>(false)),
m_delegate(std::make_shared<JsToNativeBridge>(registry, callback)),
m_executor(jsExecutorFactory->createJSExecutor(m_delegate, jsQueue)),
m_executorMessageQueueThread(std::move(jsQueue)),
m_inspectable(m_executor->isInspectable()) {}
// This must be called on the same thread on which the constructor was called.
NativeToJsBridge::~NativeToJsBridge() {
CHECK(*m_destroyed)
<< "NativeToJsBridge::destroy() must be called before deallocating the NativeToJsBridge!";
}
void NativeToJsBridge::initializeRuntime() {
runOnExecutorQueue(
[](JSExecutor* executor) mutable { executor->initializeRuntime(); });
}
void NativeToJsBridge::loadBundle(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL) {
runOnExecutorQueue(
[this,
bundleRegistryWrap = makeMoveWrapper(std::move(bundleRegistry)),
startupScript = makeMoveWrapper(std::move(startupScript)),
startupScriptSourceURL =
std::move(startupScriptSourceURL)](JSExecutor* executor) mutable {
auto bundleRegistry = bundleRegistryWrap.move();
if (bundleRegistry) {
executor->setBundleRegistry(std::move(bundleRegistry));
}
try {
executor->loadBundle(
std::move(*startupScript), std::move(startupScriptSourceURL));
} catch (...) {
m_applicationScriptHasFailure = true;
throw;
}
});
}
void NativeToJsBridge::loadBundleSync(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL) {
if (bundleRegistry) {
m_executor->setBundleRegistry(std::move(bundleRegistry));
}
try {
m_executor->loadBundle(
std::move(startupScript), std::move(startupScriptSourceURL));
} catch (...) {
m_applicationScriptHasFailure = true;
throw;
}
}
void NativeToJsBridge::callFunction(
std::string&& module,
std::string&& method,
folly::dynamic&& arguments) {
int systraceCookie = -1;
#ifdef WITH_FBSYSTRACE
systraceCookie = m_systraceCookie++;
FbSystraceAsyncFlow::begin(
TRACE_TAG_REACT_CXX_BRIDGE, "JSCall", systraceCookie);
#endif
runOnExecutorQueue([this,
module = std::move(module),
method = std::move(method),
arguments = std::move(arguments),
systraceCookie](JSExecutor* executor) {
if (m_applicationScriptHasFailure) {
LOG(ERROR)
<< "Attempting to call JS function on a bad application bundle: "
<< module.c_str() << "." << method.c_str() << "()";
throw std::runtime_error(
"Attempting to call JS function on a bad application bundle: " +
module + "." + method + "()");
}
#ifdef WITH_FBSYSTRACE
FbSystraceAsyncFlow::end(
TRACE_TAG_REACT_CXX_BRIDGE, "JSCall", systraceCookie);
SystraceSection s(
"NativeToJsBridge::callFunction", "module", module, "method", method);
#else
(void)(systraceCookie);
#endif
// This is safe because we are running on the executor's thread: it won't
// destruct until after it's been unregistered (which we check above) and
// that will happen on this thread
executor->callFunction(module, method, arguments);
});
}
void NativeToJsBridge::invokeCallback(
double callbackId,
folly::dynamic&& arguments) {
int systraceCookie = -1;
#ifdef WITH_FBSYSTRACE
systraceCookie = m_systraceCookie++;
FbSystraceAsyncFlow::begin(
TRACE_TAG_REACT_CXX_BRIDGE, "<callback>", systraceCookie);
#endif
runOnExecutorQueue(
[this, callbackId, arguments = std::move(arguments), systraceCookie](
JSExecutor* executor) {
if (m_applicationScriptHasFailure) {
LOG(ERROR)
<< "Attempting to call JS callback on a bad application bundle: "
<< callbackId;
throw std::runtime_error(
"Attempting to invoke JS callback on a bad application bundle.");
}
#ifdef WITH_FBSYSTRACE
FbSystraceAsyncFlow::end(
TRACE_TAG_REACT_CXX_BRIDGE, "<callback>", systraceCookie);
SystraceSection s("NativeToJsBridge::invokeCallback");
#else
(void)(systraceCookie);
#endif
executor->invokeCallback(callbackId, arguments);
});
}
void NativeToJsBridge::registerBundle(
uint32_t bundleId,
const std::string& bundlePath) {
runOnExecutorQueue([bundleId, bundlePath](JSExecutor* executor) {
executor->registerBundle(bundleId, bundlePath);
});
}
void NativeToJsBridge::setGlobalVariable(
std::string propName,
std::unique_ptr<const JSBigString> jsonValue) {
runOnExecutorQueue([propName = std::move(propName),
jsonValue = makeMoveWrapper(std::move(jsonValue))](
JSExecutor* executor) mutable {
executor->setGlobalVariable(propName, jsonValue.move());
});
}
void* NativeToJsBridge::getJavaScriptContext() {
// TODO(cjhopman): this seems unsafe unless we require that it is only called
// on the main js queue.
return m_executor->getJavaScriptContext();
}
bool NativeToJsBridge::isInspectable() {
return m_inspectable;
}
bool NativeToJsBridge::isBatchActive() {
return m_delegate->isBatchActive();
}
void NativeToJsBridge::handleMemoryPressure(int pressureLevel) {
runOnExecutorQueue([=](JSExecutor* executor) {
executor->handleMemoryPressure(pressureLevel);
});
}
void NativeToJsBridge::destroy() {
// All calls made through runOnExecutorQueue have an early exit if
// m_destroyed is true. Setting this before the runOnQueueSync will cause
// pending work to be cancelled and we won't have to wait for it.
*m_destroyed = true;
m_executorMessageQueueThread->runOnQueueSync([this] {
m_executor->destroy();
m_executorMessageQueueThread->quitSynchronous();
m_executor = nullptr;
});
}
void NativeToJsBridge::runOnExecutorQueue(
std::function<void(JSExecutor*)>&& task) noexcept {
if (*m_destroyed) {
return;
}
std::shared_ptr<bool> isDestroyed = m_destroyed;
m_executorMessageQueueThread->runOnQueue(
[this, isDestroyed, task = std::move(task)] {
if (*isDestroyed) {
return;
}
// The executor is guaranteed to be valid for the duration of the task
// because:
// 1. the executor is only destroyed after it is unregistered
// 2. the executor is unregistered on this queue
// 3. we just confirmed that the executor hasn't been unregistered above
task(m_executor.get());
});
}
std::shared_ptr<NativeMethodCallInvoker>
NativeToJsBridge::getDecoratedNativeMethodCallInvoker(
std::shared_ptr<NativeMethodCallInvoker> nativeMethodCallInvoker) const {
class NativeMethodCallInvokerImpl : public NativeMethodCallInvoker {
private:
std::weak_ptr<JsToNativeBridge> m_jsToNativeBridge;
std::shared_ptr<NativeMethodCallInvoker> m_nativeInvoker;
public:
NativeMethodCallInvokerImpl(
std::weak_ptr<JsToNativeBridge> jsToNativeBridge,
std::shared_ptr<NativeMethodCallInvoker> nativeInvoker)
: m_jsToNativeBridge(std::move(jsToNativeBridge)),
m_nativeInvoker(std::move(nativeInvoker)) {}
void invokeAsync(
const std::string& methodName,
std::function<void()>&& func) noexcept override {
if (auto strongJsToNativeBridge = m_jsToNativeBridge.lock()) {
strongJsToNativeBridge->recordTurboModuleAsyncMethodCall();
}
m_nativeInvoker->invokeAsync(methodName, std::move(func));
}
void invokeSync(const std::string& methodName, std::function<void()>&& func)
override {
m_nativeInvoker->invokeSync(methodName, std::move(func));
}
};
return std::make_shared<NativeMethodCallInvokerImpl>(
m_delegate, std::move(nativeMethodCallInvoker));
}
jsinspector_modern::RuntimeTargetDelegate&
NativeToJsBridge::getInspectorTargetDelegate() {
return *m_executor;
}
} // namespace facebook::react

View File

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

View File

@@ -0,0 +1,79 @@
/*
* 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 "RAMBundleRegistry.h"
#include <folly/String.h>
#include <memory>
namespace facebook::react {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
constexpr uint32_t RAMBundleRegistry::MAIN_BUNDLE_ID;
#pragma clang diagnostic pop
std::unique_ptr<RAMBundleRegistry> RAMBundleRegistry::singleBundleRegistry(
std::unique_ptr<JSModulesUnbundle> mainBundle) {
return std::make_unique<RAMBundleRegistry>(std::move(mainBundle));
}
std::unique_ptr<RAMBundleRegistry> RAMBundleRegistry::multipleBundlesRegistry(
std::unique_ptr<JSModulesUnbundle> mainBundle,
std::function<std::unique_ptr<JSModulesUnbundle>(std::string)> factory) {
return std::make_unique<RAMBundleRegistry>(
std::move(mainBundle), std::move(factory));
}
RAMBundleRegistry::RAMBundleRegistry(
std::unique_ptr<JSModulesUnbundle> mainBundle,
std::function<std::unique_ptr<JSModulesUnbundle>(std::string)> factory)
: m_factory(std::move(factory)) {
m_bundles.emplace(MAIN_BUNDLE_ID, std::move(mainBundle));
}
void RAMBundleRegistry::registerBundle(
uint32_t bundleId,
std::string bundlePath) {
m_bundlePaths.emplace(bundleId, std::move(bundlePath));
}
JSModulesUnbundle::Module RAMBundleRegistry::getModule(
uint32_t bundleId,
uint32_t moduleId) {
if (m_bundles.find(bundleId) == m_bundles.end()) {
if (!m_factory) {
throw std::runtime_error(
"You need to register factory function in order to "
"support multiple RAM bundles.");
}
auto bundlePath = m_bundlePaths.find(bundleId);
if (bundlePath == m_bundlePaths.end()) {
throw std::runtime_error(
"In order to fetch RAM bundle from the registry, its file "
"path needs to be registered first.");
}
m_bundles.emplace(bundleId, m_factory(bundlePath->second));
}
auto module = getBundle(bundleId)->getModule(moduleId);
if (bundleId == MAIN_BUNDLE_ID) {
return module;
}
return {
folly::to<std::string>("seg-", bundleId, '_', std::move(module.name)),
std::move(module.code),
};
}
JSModulesUnbundle* RAMBundleRegistry::getBundle(uint32_t bundleId) const {
return m_bundles.at(bundleId).get();
}
} // namespace facebook::react

View File

@@ -0,0 +1,54 @@
/*
* 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>
#include <functional>
#include <memory>
#include <unordered_map>
#include <utility>
#include <cxxreact/JSModulesUnbundle.h>
#ifndef RN_EXPORT
#define RN_EXPORT __attribute__((visibility("default")))
#endif
namespace facebook::react {
class RN_EXPORT RAMBundleRegistry {
public:
constexpr static uint32_t MAIN_BUNDLE_ID = 0;
static std::unique_ptr<RAMBundleRegistry> singleBundleRegistry(
std::unique_ptr<JSModulesUnbundle> mainBundle);
static std::unique_ptr<RAMBundleRegistry> multipleBundlesRegistry(
std::unique_ptr<JSModulesUnbundle> mainBundle,
std::function<std::unique_ptr<JSModulesUnbundle>(std::string)> factory);
explicit RAMBundleRegistry(
std::unique_ptr<JSModulesUnbundle> mainBundle,
std::function<std::unique_ptr<JSModulesUnbundle>(std::string)> factory =
nullptr);
RAMBundleRegistry(RAMBundleRegistry&&) = default;
RAMBundleRegistry& operator=(RAMBundleRegistry&&) = default;
void registerBundle(uint32_t bundleId, std::string bundlePath);
JSModulesUnbundle::Module getModule(uint32_t bundleId, uint32_t moduleId);
virtual ~RAMBundleRegistry(){};
private:
JSModulesUnbundle* getBundle(uint32_t bundleId) const;
std::function<std::unique_ptr<JSModulesUnbundle>(std::string)> m_factory;
std::unordered_map<uint32_t, std::string> m_bundlePaths;
std::unordered_map<uint32_t, std::unique_ptr<JSModulesUnbundle>> m_bundles;
};
} // namespace facebook::react

View File

@@ -0,0 +1,59 @@
# coding: utf-8
# 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]
boost_compiler_flags = '-Wno-documentation'
Pod::Spec.new do |s|
s.name = "React-cxxreact"
s.version = version
s.summary = "-" # TODO
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}"
s.exclude_files = "SampleCxxModule.*"
s.compiler_flags = folly_compiler_flags + ' ' + boost_compiler_flags
s.pod_target_xcconfig = {
"HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/RCT-Folly\" \"$(PODS_ROOT)/DoubleConversion\" \"$(PODS_ROOT)/fmt/include\" \"$(PODS_CONFIGURATION_BUILD_DIR)/React-debug/React_debug.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers\"",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20"
}
s.header_dir = "cxxreact"
s.dependency "boost", "1.83.0"
s.dependency "DoubleConversion"
s.dependency "fmt", "9.1.0"
s.dependency "RCT-Folly", folly_version
s.dependency "glog"
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
s.dependency "React-callinvoker", version
s.dependency "React-runtimeexecutor", version
s.dependency "React-perflogger", version
s.dependency "React-jsi", version
s.dependency "React-logger", version
s.dependency "React-debug", version
if ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == "1"
s.dependency 'hermes-engine'
end
end

View File

@@ -0,0 +1,134 @@
/*
* 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 "ReactMarker.h"
#include <cxxreact/JSExecutor.h>
namespace facebook::react {
namespace ReactMarker {
#if __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wglobal-constructors"
#endif
LogTaggedMarker logTaggedMarkerImpl = nullptr;
LogTaggedMarker logTaggedMarkerBridgelessImpl = nullptr;
#if __clang__
#pragma clang diagnostic pop
#endif
void logMarker(const ReactMarkerId markerId) {
logTaggedMarker(markerId, nullptr);
}
void logTaggedMarker(const ReactMarkerId markerId, const char* tag) {
logTaggedMarkerImpl(markerId, tag);
}
void logMarkerBridgeless(const ReactMarkerId markerId) {
logTaggedMarkerBridgeless(markerId, nullptr);
}
void logTaggedMarkerBridgeless(const ReactMarkerId markerId, const char* tag) {
logTaggedMarkerBridgelessImpl(markerId, tag);
}
void logMarkerDone(const ReactMarkerId markerId, double markerTime) {
StartupLogger::getInstance().logStartupEvent(markerId, markerTime);
}
StartupLogger& StartupLogger::getInstance() {
static StartupLogger instance;
return instance;
}
void StartupLogger::logStartupEvent(
const ReactMarkerId markerId,
double markerTime) {
switch (markerId) {
case ReactMarkerId::APP_STARTUP_START:
if (!std::isnan(appStartupStartTime)) {
// We had a startup start time, which indicates a warm start (user
// closed the app and start again). In this case we need to invalidate
// all other startup timings.
reset();
}
appStartupStartTime = markerTime;
return;
case ReactMarkerId::APP_STARTUP_STOP:
if (std::isnan(appStartupEndTime)) {
appStartupEndTime = markerTime;
}
return;
case ReactMarkerId::INIT_REACT_RUNTIME_START:
if (std::isnan(initReactRuntimeStartTime)) {
initReactRuntimeStartTime = markerTime;
}
return;
case ReactMarkerId::INIT_REACT_RUNTIME_STOP:
if (std::isnan(initReactRuntimeEndTime)) {
initReactRuntimeEndTime = markerTime;
}
return;
case ReactMarkerId::RUN_JS_BUNDLE_START:
if (std::isnan(runJSBundleStartTime)) {
runJSBundleStartTime = markerTime;
}
return;
case ReactMarkerId::RUN_JS_BUNDLE_STOP:
if (std::isnan(runJSBundleEndTime)) {
runJSBundleEndTime = markerTime;
}
return;
default:
return;
}
}
void StartupLogger::reset() {
appStartupStartTime = std::nan("");
appStartupEndTime = std::nan("");
initReactRuntimeStartTime = std::nan("");
initReactRuntimeEndTime = std::nan("");
runJSBundleStartTime = std::nan("");
runJSBundleEndTime = std::nan("");
}
double StartupLogger::getAppStartupStartTime() {
return appStartupStartTime;
}
double StartupLogger::getInitReactRuntimeStartTime() {
return initReactRuntimeStartTime;
}
double StartupLogger::getInitReactRuntimeEndTime() {
return initReactRuntimeEndTime;
}
double StartupLogger::getRunJSBundleStartTime() {
return runJSBundleStartTime;
}
double StartupLogger::getRunJSBundleEndTime() {
return runJSBundleEndTime;
}
double StartupLogger::getAppStartupEndTime() {
return appStartupEndTime;
}
} // namespace ReactMarker
} // namespace facebook::react

View File

@@ -0,0 +1,106 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <cmath>
#ifdef __APPLE__
#include <functional>
#endif
namespace facebook::react {
namespace ReactMarker {
enum ReactMarkerId {
APP_STARTUP_START,
APP_STARTUP_STOP,
INIT_REACT_RUNTIME_START,
INIT_REACT_RUNTIME_STOP,
NATIVE_REQUIRE_START,
NATIVE_REQUIRE_STOP,
RUN_JS_BUNDLE_START,
RUN_JS_BUNDLE_STOP,
CREATE_REACT_CONTEXT_STOP,
JS_BUNDLE_STRING_CONVERT_START,
JS_BUNDLE_STRING_CONVERT_STOP,
NATIVE_MODULE_SETUP_START,
NATIVE_MODULE_SETUP_STOP,
REGISTER_JS_SEGMENT_START,
REGISTER_JS_SEGMENT_STOP,
REACT_INSTANCE_INIT_START,
REACT_INSTANCE_INIT_STOP
};
#ifdef __APPLE__
using LogTaggedMarker =
std::function<void(const ReactMarkerId, const char* tag)>; // Bridge only
using LogTaggedMarkerBridgeless =
std::function<void(const ReactMarkerId, const char* tag)>;
#else
typedef void (
*LogTaggedMarker)(const ReactMarkerId, const char* tag); // Bridge only
typedef void (*LogTaggedMarkerBridgeless)(const ReactMarkerId, const char* tag);
#endif
#ifndef RN_EXPORT
#define RN_EXPORT __attribute__((visibility("default")))
#endif
extern RN_EXPORT LogTaggedMarker logTaggedMarkerImpl; // Bridge only
extern RN_EXPORT LogTaggedMarker logTaggedMarkerBridgelessImpl;
extern RN_EXPORT void logMarker(const ReactMarkerId markerId); // Bridge only
extern RN_EXPORT void logTaggedMarker(
const ReactMarkerId markerId,
const char* tag); // Bridge only
extern RN_EXPORT void logMarkerBridgeless(const ReactMarkerId markerId);
extern RN_EXPORT void logTaggedMarkerBridgeless(
const ReactMarkerId markerId,
const char* tag);
struct ReactMarkerEvent {
const ReactMarkerId markerId;
const char* tag;
double time;
};
class RN_EXPORT StartupLogger {
public:
static StartupLogger& getInstance();
void logStartupEvent(const ReactMarkerId markerName, double markerTime);
void reset();
double getAppStartupStartTime();
double getInitReactRuntimeStartTime();
double getInitReactRuntimeEndTime();
double getRunJSBundleStartTime();
double getRunJSBundleEndTime();
double getAppStartupEndTime();
private:
StartupLogger() = default;
StartupLogger(const StartupLogger&) = delete;
StartupLogger& operator=(const StartupLogger&) = delete;
double appStartupStartTime = std::nan("");
double appStartupEndTime = std::nan("");
double initReactRuntimeStartTime = std::nan("");
double initReactRuntimeEndTime = std::nan("");
double runJSBundleStartTime = std::nan("");
double runJSBundleEndTime = std::nan("");
};
// When the marker got logged from the platform, it will notify here. This is
// used to collect react markers that are logged in the platform instead of in
// C++.
extern RN_EXPORT void logMarkerDone(
const ReactMarkerId markerId,
double markerTime);
} // namespace ReactMarker
} // namespace facebook::react

View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated by scripts/releases/set-rn-version.js
*/
#pragma once
#include <cstdint>
#include <string_view>
namespace facebook::react {
constexpr struct {
int32_t Major = 0;
int32_t Minor = 74;
int32_t Patch = 0;
std::string_view Prerelease = "";
} ReactNativeVersion;
} // namespace facebook::react

View File

@@ -0,0 +1,48 @@
/*
* 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 <exception>
#include <functional>
#include <string>
namespace facebook::react {
/**
* RecoverableError
*
* An exception that it is expected we should be able to recover from.
*/
struct RecoverableError : public std::exception {
explicit RecoverableError(const std::string& what_)
: m_what{"facebook::react::Recoverable: " + what_} {}
virtual const char* what() const noexcept override {
return m_what.c_str();
}
/**
* runRethrowingAsRecoverable
*
* Helper function that converts any exception of type `E`, thrown within the
* `act` routine into a recoverable error with the same message.
*/
template <typename E>
inline static void runRethrowingAsRecoverable(std::function<void()> act) {
try {
act();
} catch (const E& err) {
throw RecoverableError(err.what());
}
}
private:
std::string m_what;
};
} // namespace facebook::react

View File

@@ -0,0 +1,182 @@
/*
* 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 "SampleCxxModule.h"
#include <cxxreact/JsArgumentHelpers.h>
#include <glog/logging.h>
#include <memory>
#include <thread>
using namespace folly;
namespace facebook {
namespace xplat {
namespace samples {
std::string Sample::hello() {
LOG(WARNING) << "glog: hello, world";
return "hello";
}
double Sample::add(double a, double b) {
return a + b;
}
std::string Sample::concat(const std::string& a, const std::string& b) {
return a + b;
}
std::string Sample::repeat(int count, const std::string& str) {
std::string ret;
for (int i = 0; i < count; i++) {
ret += str;
}
return ret;
}
void Sample::save(std::map<std::string, std::string> dict) {
state_ = std::move(dict);
}
std::map<std::string, std::string> Sample::load() {
return state_;
}
void Sample::except() {
// TODO mhorowitz #7128529: There's no way to automatically test this
// right now.
// throw std::runtime_error("oops");
}
void Sample::call_later(int msec, std::function<void()> f) {
std::thread t([=] {
std::this_thread::sleep_for(std::chrono::milliseconds(msec));
f();
});
t.detach();
}
double Sample::twice(double n) {
return n * 2;
}
SampleCxxModule::SampleCxxModule(std::unique_ptr<Sample> sample)
: sample_(std::move(sample)) {}
std::string SampleCxxModule::getName() {
return "Sample";
}
auto SampleCxxModule::getConstants() -> std::map<std::string, folly::dynamic> {
return {
{"one", 1},
{"two", 2},
{"animal", "fox"},
};
}
auto SampleCxxModule::getMethods() -> std::vector<Method> {
return {
Method("hello", [this] { sample_->hello(); }),
Method(
"add",
[this](dynamic args, Callback cb) {
LOG(WARNING) << "Sample: add => "
<< sample_->add(
jsArgAsDouble(args, 0), jsArgAsDouble(args, 1));
cb({sample_->add(jsArgAsDouble(args, 0), jsArgAsDouble(args, 1))});
}),
Method(
"concat",
[this](dynamic args, Callback cb) {
cb({sample_->concat(
jsArgAsString(args, 0), jsArgAsString(args, 1))});
}),
Method(
"repeat",
[this](dynamic args, Callback cb) {
cb({sample_->repeat(
(int)jsArgAsInt(args, 0), jsArgAsString(args, 1))});
}),
Method("save", this, &SampleCxxModule::save),
Method("load", this, &SampleCxxModule::load),
Method(
"call_later",
[this](dynamic args, Callback cb) {
sample_->call_later((int)jsArgAsInt(args, 0), [cb] { cb({}); });
}),
Method("except", [this] { sample_->except(); }),
Method(
"twice",
[this](dynamic args) -> dynamic {
return sample_->twice(jsArgAsDouble(args, 0));
},
SyncTag),
Method(
"syncHello",
[this]() -> dynamic {
sample_->hello();
return nullptr;
},
SyncTag),
Method(
"addIfPositiveAsPromise",
[](dynamic args, Callback cb, Callback cbError) {
auto a = jsArgAsDouble(args, 0);
auto b = jsArgAsDouble(args, 1);
if (a < 0 || b < 0) {
cbError({"Negative number!"});
} else {
cb({a + b});
}
}),
Method(
"addIfPositiveAsAsync",
[](dynamic args, Callback cb, Callback cbError) {
auto a = jsArgAsDouble(args, 0);
auto b = jsArgAsDouble(args, 1);
if (a < 0 || b < 0) {
cbError({"Negative number!"});
} else {
cb({a + b});
}
},
AsyncTag),
};
}
void SampleCxxModule::save(folly::dynamic args) {
std::map<std::string, std::string> m;
for (const auto& p : jsArgN(args, 0, &dynamic::items)) {
m.emplace(
jsArg(p.first, &dynamic::asString, "map key"),
jsArg(p.second, &dynamic::asString, "map value"));
}
sample_->save(std::move(m));
}
void SampleCxxModule::load([[maybe_unused]] folly::dynamic args, Callback cb) {
dynamic d = dynamic::object;
for (const auto& p : sample_->load()) {
d.insert(p.first, p.second);
}
cb({d});
}
} // namespace samples
} // namespace xplat
} // namespace facebook
// By convention, the function name should be the same as the class name.
facebook::xplat::module::CxxModule* SampleCxxModule() {
return new facebook::xplat::samples::SampleCxxModule(
std::make_unique<facebook::xplat::samples::Sample>());
}

View File

@@ -0,0 +1,59 @@
/*
* 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 <memory>
#include <vector>
#include <cxxreact/CxxModule.h>
namespace facebook {
namespace xplat {
namespace samples {
// In a less contrived example, Sample would be part of a traditional
// C++ library.
class Sample {
public:
std::string hello();
double add(double a, double b);
std::string concat(const std::string& a, const std::string& b);
std::string repeat(int count, const std::string& str);
void save(std::map<std::string, std::string> dict);
std::map<std::string, std::string> load();
void call_later(int msec, std::function<void()> f);
void except();
double twice(double n);
private:
std::map<std::string, std::string> state_;
};
class SampleCxxModule : public module::CxxModule {
public:
SampleCxxModule(std::unique_ptr<Sample> sample);
std::string getName();
virtual auto getConstants() -> std::map<std::string, folly::dynamic>;
virtual auto getMethods() -> std::vector<Method>;
private:
void save(folly::dynamic args);
void load(folly::dynamic args, Callback cb);
std::unique_ptr<Sample> sample_;
};
} // namespace samples
} // namespace xplat
} // namespace facebook
extern "C" facebook::xplat::module::CxxModule* SampleCxxModule();

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.
*/
#include <memory>
#include <cxxreact/CxxModule.h>
namespace facebook {
namespace xplat {
namespace module {
// Allows a Cxx-module to be shared or reused across multiple React instances
// Caveat: the setInstance call is not forwarded, so usages of getInstance
// inside your module (e.g. dispatching events) will always be nullptr.
class SharedProxyCxxModule : public CxxModule {
public:
explicit SharedProxyCxxModule(std::shared_ptr<CxxModule> shared)
: shared_(shared) {}
std::string getName() override {
return shared_->getName();
}
auto getConstants() -> std::map<std::string, folly::dynamic> override {
return shared_->getConstants();
}
auto getMethods() -> std::vector<Method> override {
return shared_->getMethods();
}
private:
std::shared_ptr<CxxModule> shared_;
};
} // namespace module
} // namespace xplat
} // namespace facebook

View File

@@ -0,0 +1,59 @@
/*
* 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
#ifdef WITH_FBSYSTRACE
#include <fbsystrace.h>
#endif
namespace facebook::react {
/**
* Allow providing an fbsystrace implementation that can short-circuit out
* quickly and can throttle too frequent events so we can get useful traces even
* if rendering etc. is spinning. For throttling we'll need file/line info so we
* use a macro.
*/
#if defined(WITH_LOOM_TRACE)
#define SystraceSection \
static constexpr const char systraceSectionFile[] = __FILE__; \
fbsystrace::FbSystraceSection<systraceSectionFile, __LINE__>
/**
* This is a convenience class to avoid lots of verbose profiling
* #ifdefs. If WITH_FBSYSTRACE is not defined, the optimizer will
* remove this completely. If it is defined, it will behave as
* FbSystraceSection, with the right tag provided. Use two separate classes to
* to ensure that the ODR rule isn't violated, that is, if WITH_FBSYSTRACE has
* different values in different files, there is no inconsistency in the sizes
* of defined symbols.
*/
#elif defined(WITH_FBSYSTRACE)
struct ConcreteSystraceSection {
public:
template <typename... ConvertsToStringPiece>
explicit ConcreteSystraceSection(
const char* name,
ConvertsToStringPiece&&... args)
: m_section(TRACE_TAG_REACT_CXX_BRIDGE, name, args...) {}
private:
fbsystrace::FbSystraceSection m_section;
};
using SystraceSection = ConcreteSystraceSection;
#else
struct DummySystraceSection {
public:
template <typename... ConvertsToStringPiece>
explicit DummySystraceSection(
__unused const char* name,
__unused ConvertsToStringPiece&&... args) {}
};
using SystraceSection = DummySystraceSection;
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,39 @@
/*
* 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 <gtest/gtest.h>
#include <exception>
#include <stdexcept>
#include <cxxreact/RecoverableError.h>
using namespace facebook::react;
TEST(RecoverableError, RunRethrowingAsRecoverableRecoverTest) {
try {
RecoverableError::runRethrowingAsRecoverable<std::runtime_error>(
[]() { throw std::runtime_error("catch me"); });
FAIL() << "Unthrown exception";
} catch (const RecoverableError& err) {
ASSERT_STREQ(err.what(), "facebook::react::Recoverable: catch me");
} catch (...) {
FAIL() << "Uncaught exception";
}
}
TEST(RecoverableError, RunRethrowingAsRecoverableFallthroughTest) {
try {
RecoverableError::runRethrowingAsRecoverable<std::runtime_error>(
[]() { throw std::logic_error("catch me"); });
FAIL() << "Unthrown exception";
} catch (const RecoverableError& err) {
FAIL() << "Recovered exception that should have fallen through";
} catch (const std::exception& err) {
ASSERT_STREQ(err.what(), "catch me");
}
}

View File

@@ -0,0 +1,113 @@
/*
* 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 <cxxreact/JsArgumentHelpers.h>
#include <folly/dynamic.h>
#include <gtest/gtest.h>
#include <algorithm>
using namespace facebook::xplat;
using dynamic = folly::dynamic;
#define EXPECT_JSAE(statement, exstr) \
do { \
try { \
statement; \
FAIL() << "Expected JsArgumentException(" << (exstr) << ") not thrown"; \
} catch (const JsArgumentException& ex) { \
EXPECT_EQ(ex.what(), std::string(exstr)); \
} \
} while (0) // let any other exception escape, gtest will deal.
TEST(JsArgumentHelpersTest, args) {
const bool aBool = true;
const int64_t anInt = 17;
const double aDouble = 3.14;
const std::string aString = "word";
const dynamic anArray = dynamic::array("a", "b", "c");
const dynamic anObject = dynamic::object("k1", "v1")("k2", "v2");
const std::string aNumericString = folly::to<std::string>(anInt);
folly::dynamic args = dynamic::array(
aBool, anInt, aDouble, aString, anArray, anObject, aNumericString);
EXPECT_EQ(jsArgAsBool(args, 0), aBool);
EXPECT_EQ(jsArgAsInt(args, 1), anInt);
EXPECT_EQ(jsArgAsDouble(args, 2), aDouble);
EXPECT_EQ(jsArgAsString(args, 3), aString);
EXPECT_EQ(jsArgAsArray(args, 4), anArray);
EXPECT_EQ(jsArgAsObject(args, 5), anObject);
// const args
const folly::dynamic& cargs = args;
const folly::dynamic& a4 = jsArgAsArray(cargs, 4);
EXPECT_EQ(a4, anArray);
EXPECT_EQ(jsArgAsObject(cargs, 5), anObject);
// helpers returning dynamic should return same object without copying
EXPECT_EQ(&jsArgAsArray(args, 4), &(args[4]));
EXPECT_EQ(&jsArgAsArray(cargs, 4), &(args[4]));
// dynamics returned for mutable args should be mutable. The test is that
// this compiles.
jsArgAsArray(args, 4)[2] = "d";
jsArgAsArray(args, 4)[2] = "c";
// These fail to compile due to constness.
// jsArgAsArray(cargs, 4)[2] = "d";
// jsArgAsArray(cargs, 4)[2] = "c";
// ref-qualified member function tests
EXPECT_EQ(jsArgN(args, 3, &folly::dynamic::getString), aString);
EXPECT_EQ(jsArg(args[3], &folly::dynamic::getString), aString);
// conversions
EXPECT_EQ(jsArgAsDouble(args, 1), anInt * 1.0);
EXPECT_EQ(jsArgAsString(args, 1), aNumericString);
EXPECT_EQ(jsArgAsInt(args, 6), anInt);
// Test exception messages.
// out_of_range
EXPECT_JSAE(
jsArgAsBool(args, 7),
"JavaScript provided 7 arguments for C++ method which references at least "
"8 arguments: out of range in dynamic array");
// Conv range_error (invalid value conversion)
const std::string exhead = "Could not convert argument 3 to required type: ";
const std::string extail = ": Invalid leading character: \"word\"";
try {
jsArgAsInt(args, 3);
FAIL() << "Expected JsArgumentException(" << exhead << "..." << extail
<< ") not thrown";
} catch (const JsArgumentException& ex) {
const std::string exwhat = ex.what();
EXPECT_GT(exwhat.size(), exhead.size());
EXPECT_GT(exwhat.size(), extail.size());
EXPECT_TRUE(std::equal(exhead.cbegin(), exhead.cend(), exwhat.cbegin()))
<< "JsArgumentException('" << exwhat << "') does not begin with '"
<< exhead << "'";
EXPECT_TRUE(std::equal(extail.crbegin(), extail.crend(), exwhat.crbegin()))
<< "JsArgumentException('" << exwhat << "') does not end with '"
<< extail << "'";
}
// inconvertible types
EXPECT_JSAE(
jsArgAsArray(args, 2),
"Argument 3 of type double is not required type Array");
EXPECT_JSAE(
jsArgAsInt(args, 4),
"Error converting javascript arg 4 to C++: "
"TypeError: expected dynamic type `int/double/bool/string', but had type `array'");
// type predicate failure
EXPECT_JSAE(
jsArgAsObject(args, 4),
"Argument 5 of type array is not required type Object");
}

View File

@@ -0,0 +1,62 @@
/*
* 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 <fcntl.h>
#include <sys/mman.h>
#include <cxxreact/JSBigString.h>
#include <gtest/gtest.h>
using namespace facebook::react;
namespace {
int tempFileFromString(std::string contents) {
const char* tmpDir = getenv("TMPDIR");
if (tmpDir == nullptr)
tmpDir = "/tmp";
std::string tmp{tmpDir};
tmp += "/temp.XXXXXX";
std::vector<char> tmpBuf{tmp.begin(), tmp.end()};
tmpBuf.push_back('\0');
const int fd = mkstemp(tmpBuf.data());
write(fd, contents.c_str(), contents.size() + 1);
return fd;
}
}; // namespace
TEST(JSBigFileString, MapWholeFileTest) {
std::string data{"Hello, world"};
const auto size = data.length() + 1;
// Initialise Big String
int fd = tempFileFromString("Hello, world");
JSBigFileString bigStr{fd, size};
// Test
ASSERT_STREQ(data.c_str(), bigStr.c_str());
}
TEST(JSBigFileString, MapPartTest) {
std::string data{"Hello, world"};
// Sub-string to actually map
std::string needle{"or"};
off_t offset = data.find(needle);
// Initialise Big String
int fd = tempFileFromString(data);
JSBigFileString bigStr{fd, needle.size(), offset};
// Test
EXPECT_EQ(needle.length(), bigStr.size());
for (unsigned int i = 0; i < needle.length(); ++i) {
EXPECT_EQ(needle[i], bigStr.c_str()[i]);
}
}

View File

@@ -0,0 +1,15 @@
/*
* 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 <folly/init/Init.h>
#include <gtest/gtest.h>
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
folly::init(&argc, &argv);
return RUN_ALL_TESTS();
}

View File

@@ -0,0 +1,155 @@
/*
* 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 <cxxreact/MethodCall.h>
#include <folly/json.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsign-compare"
#include <gtest/gtest.h>
#pragma GCC diagnostic pop
using namespace facebook::react;
using dynamic = folly::dynamic;
TEST(parseMethodCalls, SingleReturnCallNoArgs) {
auto jsText = "[[7],[3],[[]]]";
auto returnedCalls = parseMethodCalls(folly::parseJson(jsText));
EXPECT_EQ(1, returnedCalls.size());
auto returnedCall = returnedCalls[0];
EXPECT_EQ(0, returnedCall.arguments.size());
EXPECT_EQ(7, returnedCall.moduleId);
EXPECT_EQ(3, returnedCall.methodId);
}
TEST(parseMethodCalls, InvalidReturnFormat) {
try {
auto input = dynamic::object("foo", 1);
parseMethodCalls(std::move(input));
ADD_FAILURE();
} catch (const std::invalid_argument&) {
// ignored
}
try {
auto input = dynamic::array(dynamic::object("foo", 1));
parseMethodCalls(std::move(input));
ADD_FAILURE();
} catch (const std::invalid_argument&) {
// ignored
}
try {
auto input = dynamic::array(1, 4, dynamic::object("foo", 2));
parseMethodCalls(std::move(input));
ADD_FAILURE();
} catch (const std::invalid_argument&) {
// ignored
}
try {
auto input = dynamic::array(
dynamic::array(1), dynamic::array(4), dynamic::object("foo", 2));
parseMethodCalls(std::move(input));
ADD_FAILURE();
} catch (const std::invalid_argument&) {
// ignored
}
try {
auto input =
dynamic::array(dynamic::array(1), dynamic::array(4), dynamic::array());
parseMethodCalls(std::move(input));
ADD_FAILURE();
} catch (const std::invalid_argument&) {
// ignored
}
}
TEST(parseMethodCalls, NumberReturn) {
auto jsText = "[[0],[0],[[\"foobar\"]]]";
auto returnedCalls = parseMethodCalls(folly::parseJson(jsText));
EXPECT_EQ(1, returnedCalls.size());
auto returnedCall = returnedCalls[0];
EXPECT_EQ(1, returnedCall.arguments.size());
EXPECT_EQ(folly::dynamic::STRING, returnedCall.arguments[0].type());
EXPECT_EQ("foobar", returnedCall.arguments[0].asString());
}
TEST(parseMethodCalls, StringReturn) {
auto jsText = "[[0],[0],[[42.16]]]";
auto returnedCalls = parseMethodCalls(folly::parseJson(jsText));
EXPECT_EQ(1, returnedCalls.size());
auto returnedCall = returnedCalls[0];
EXPECT_EQ(1, returnedCall.arguments.size());
EXPECT_EQ(folly::dynamic::DOUBLE, returnedCall.arguments[0].type());
EXPECT_EQ(42.16, returnedCall.arguments[0].asDouble());
}
TEST(parseMethodCalls, BooleanReturn) {
auto jsText = "[[0],[0],[[false]]]";
auto returnedCalls = parseMethodCalls(folly::parseJson(jsText));
EXPECT_EQ(1, returnedCalls.size());
auto returnedCall = returnedCalls[0];
EXPECT_EQ(1, returnedCall.arguments.size());
EXPECT_EQ(folly::dynamic::BOOL, returnedCall.arguments[0].type());
ASSERT_FALSE(returnedCall.arguments[0].asBool());
}
TEST(parseMethodCalls, NullReturn) {
auto jsText = "[[0],[0],[[null]]]";
auto returnedCalls = parseMethodCalls(folly::parseJson(jsText));
EXPECT_EQ(1, returnedCalls.size());
auto returnedCall = returnedCalls[0];
EXPECT_EQ(1, returnedCall.arguments.size());
EXPECT_EQ(folly::dynamic::NULLT, returnedCall.arguments[0].type());
}
TEST(parseMethodCalls, MapReturn) {
auto jsText =
"[[0],[0],[[{\"foo\": \"hello\", \"bar\": 4.0, \"baz\": true}]]]";
auto returnedCalls = parseMethodCalls(folly::parseJson(jsText));
EXPECT_EQ(1, returnedCalls.size());
auto returnedCall = returnedCalls[0];
EXPECT_EQ(1, returnedCall.arguments.size());
EXPECT_EQ(folly::dynamic::OBJECT, returnedCall.arguments[0].type());
auto& returnedMap = returnedCall.arguments[0];
auto foo = returnedMap.at("foo");
EXPECT_EQ(folly::dynamic("hello"), foo);
auto bar = returnedMap.at("bar");
EXPECT_EQ(folly::dynamic(4.0), bar);
auto baz = returnedMap.at("baz");
EXPECT_EQ(folly::dynamic(true), baz);
}
TEST(parseMethodCalls, ArrayReturn) {
auto jsText = "[[0],[0],[[[\"foo\", 42.0, false]]]]";
auto returnedCalls = parseMethodCalls(folly::parseJson(jsText));
EXPECT_EQ(1, returnedCalls.size());
auto returnedCall = returnedCalls[0];
EXPECT_EQ(1, returnedCall.arguments.size());
EXPECT_EQ(folly::dynamic::ARRAY, returnedCall.arguments[0].type());
auto& returnedArray = returnedCall.arguments[0];
EXPECT_EQ(3, returnedArray.size());
EXPECT_EQ(folly::dynamic("foo"), returnedArray[0]);
EXPECT_EQ(folly::dynamic(42.0), returnedArray[1]);
EXPECT_EQ(folly::dynamic(false), returnedArray[2]);
}
TEST(parseMethodCalls, ReturnMultipleParams) {
auto jsText = "[[0],[0],[[\"foo\", 14, null, false]]]";
auto returnedCalls = parseMethodCalls(folly::parseJson(jsText));
EXPECT_EQ(1, returnedCalls.size());
auto returnedCall = returnedCalls[0];
EXPECT_EQ(4, returnedCall.arguments.size());
EXPECT_EQ(folly::dynamic::STRING, returnedCall.arguments[0].type());
EXPECT_EQ(folly::dynamic::INT64, returnedCall.arguments[1].type());
EXPECT_EQ(folly::dynamic::NULLT, returnedCall.arguments[2].type());
EXPECT_EQ(folly::dynamic::BOOL, returnedCall.arguments[3].type());
}
TEST(parseMethodCalls, ParseTwoCalls) {
auto jsText = "[[0,0],[1,1],[[],[]]]";
auto returnedCalls = parseMethodCalls(folly::parseJson(jsText));
EXPECT_EQ(2, returnedCalls.size());
}

View File

@@ -0,0 +1,6 @@
---
Checks: '>
clang-diagnostic-*,
'
InheritParentConfig: true
...

View File

@@ -0,0 +1,54 @@
# 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
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]
boost_compiler_flags = '-Wno-documentation'
Pod::Spec.new do |s|
s.name = "React-hermes"
s.version = version
s.summary = "Hermes engine for React Native"
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 = "executor/*.{cpp,h}",
"inspector-modern/chrome/*.{cpp,h}",
s.public_header_files = "executor/HermesExecutorFactory.h"
s.compiler_flags = folly_compiler_flags + ' ' + boost_compiler_flags
s.pod_target_xcconfig = {
"HEADER_SEARCH_PATHS" => "\"${PODS_ROOT}/hermes-engine/destroot/include\" \"$(PODS_TARGET_SRCROOT)/..\" \"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/RCT-Folly\" \"$(PODS_ROOT)/DoubleConversion\" \"$(PODS_ROOT)/fmt/include\"",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20"
}
s.header_dir = "reacthermes"
s.dependency "React-cxxreact", version
s.dependency "React-jsiexecutor", version
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
s.dependency "React-perflogger", version
s.dependency "RCT-Folly", folly_version
s.dependency "DoubleConversion"
s.dependency "fmt", "9.1.0"
s.dependency "glog"
s.dependency "hermes-engine"
s.dependency "React-jsi"
s.dependency "React-runtimeexecutor"
end

View File

@@ -0,0 +1,37 @@
# 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 hermes_executor_SRC CONFIGURE_DEPENDS *.cpp)
add_library(
hermes_executor_common
STATIC
${hermes_executor_SRC}
)
target_include_directories(hermes_executor_common PUBLIC .)
target_link_libraries(hermes_executor_common
jsireact
hermes-engine::libhermes
jsi
hermes_inspector_modern
)
if(${CMAKE_BUILD_TYPE} MATCHES Debug)
target_compile_options(
hermes_executor_common
PRIVATE
-DHERMES_ENABLE_DEBUGGER=1
)
else()
target_compile_options(
hermes_executor_common
PRIVATE
-DNDEBUG
)
endif()

View File

@@ -0,0 +1,292 @@
/*
* 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 "HermesExecutorFactory.h"
#include <thread>
#include <cxxreact/MessageQueueThread.h>
#include <cxxreact/SystraceSection.h>
#include <hermes/hermes.h>
#include <jsi/decorator.h>
#include <jsinspector-modern/InspectorFlags.h>
#include <hermes/inspector-modern/chrome/HermesRuntimeAgentDelegate.h>
#include <hermes/inspector-modern/chrome/Registration.h>
#include <hermes/inspector/RuntimeAdapter.h>
using namespace facebook::hermes;
using namespace facebook::jsi;
namespace facebook::react {
namespace {
#ifdef HERMES_ENABLE_DEBUGGER
class HermesExecutorRuntimeAdapter
: public facebook::hermes::inspector_modern::RuntimeAdapter {
public:
HermesExecutorRuntimeAdapter(
std::shared_ptr<HermesRuntime> runtime,
std::shared_ptr<MessageQueueThread> thread)
: runtime_(runtime), thread_(std::move(thread)) {}
virtual ~HermesExecutorRuntimeAdapter() = default;
HermesRuntime& getRuntime() override {
return *runtime_;
}
void tickleJs() override {
thread_->runOnQueue(
[weakRuntime = std::weak_ptr<HermesRuntime>(runtime_)]() {
auto runtime = weakRuntime.lock();
if (!runtime) {
return;
}
jsi::Function func =
runtime->global().getPropertyAsFunction(*runtime, "__tickleJs");
func.call(*runtime);
});
}
private:
std::shared_ptr<HermesRuntime> runtime_;
std::shared_ptr<MessageQueueThread> thread_;
};
#endif // HERMES_ENABLE_DEBUGGER
struct ReentrancyCheck {
// This is effectively a very subtle and complex assert, so only
// include it in builds which would include asserts.
#ifndef NDEBUG
ReentrancyCheck() : tid(std::thread::id()), depth(0) {}
void before() {
std::thread::id this_id = std::this_thread::get_id();
std::thread::id expected = std::thread::id();
// A note on memory ordering: the main purpose of these checks is
// to observe a before/before race, without an intervening after.
// This will be detected by the compare_exchange_strong atomicity
// properties, regardless of memory order.
//
// For everything else, it is easiest to think of 'depth' as a
// proxy for any access made inside the VM. If access to depth
// are reordered incorrectly, the same could be true of any other
// operation made by the VM. In fact, using acquire/release
// memory ordering could create barriers which mask a programmer
// error. So, we use relaxed memory order, to avoid masking
// actual ordering errors. Although, in practice, ordering errors
// of this sort would be surprising, because the decorator would
// need to call after() without before().
if (tid.compare_exchange_strong(
expected, this_id, std::memory_order_relaxed)) {
// Returns true if tid and expected were the same. If they
// were, then the stored tid referred to no thread, and we
// atomically saved this thread's tid. Now increment depth.
assert(depth == 0 && "No thread id, but depth != 0");
++depth;
} else if (expected == this_id) {
// If the stored tid referred to a thread, expected was set to
// that value. If that value is this thread's tid, that's ok,
// just increment depth again.
assert(depth != 0 && "Thread id was set, but depth == 0");
++depth;
} else {
// The stored tid was some other thread. This indicates a bad
// programmer error, where VM methods were called on two
// different threads unsafely. Fail fast (and hard) so the
// crash can be analyzed.
__builtin_trap();
}
}
void after() {
assert(
tid.load(std::memory_order_relaxed) == std::this_thread::get_id() &&
"No thread id in after()");
if (--depth == 0) {
// If we decremented depth to zero, store no-thread into tid.
std::thread::id expected = std::this_thread::get_id();
bool didWrite = tid.compare_exchange_strong(
expected, std::thread::id(), std::memory_order_relaxed);
assert(didWrite && "Decremented to zero, but no tid write");
}
}
std::atomic<std::thread::id> tid;
// This is not atomic, as it is only written or read from the owning
// thread.
unsigned int depth;
#endif
};
// This adds ReentrancyCheck and debugger enable/teardown to the given
// Runtime.
class DecoratedRuntime : public jsi::WithRuntimeDecorator<ReentrancyCheck> {
public:
// The first argument may be another decorater which itself
// decorates the real HermesRuntime, depending on the build config.
// The second argument is the real HermesRuntime as well to
// manage the debugger registration.
DecoratedRuntime(
std::unique_ptr<Runtime> runtime,
HermesRuntime& hermesRuntime,
std::shared_ptr<MessageQueueThread> jsQueue,
bool enableDebugger,
const std::string& debuggerName)
: jsi::WithRuntimeDecorator<ReentrancyCheck>(*runtime, reentrancyCheck_),
runtime_(std::move(runtime)) {
#ifdef HERMES_ENABLE_DEBUGGER
enableDebugger_ = enableDebugger;
if (enableDebugger_) {
std::shared_ptr<HermesRuntime> rt(runtime_, &hermesRuntime);
auto adapter =
std::make_unique<HermesExecutorRuntimeAdapter>(rt, jsQueue);
debugToken_ = facebook::hermes::inspector_modern::chrome::enableDebugging(
std::move(adapter), debuggerName);
}
#endif // HERMES_ENABLE_DEBUGGER
}
~DecoratedRuntime() {
#ifdef HERMES_ENABLE_DEBUGGER
if (enableDebugger_) {
facebook::hermes::inspector_modern::chrome::disableDebugging(debugToken_);
}
#endif // HERMES_ENABLE_DEBUGGER
}
private:
// runtime_ is a potentially decorated Runtime.
// hermesRuntime is a reference to a HermesRuntime managed by runtime_.
//
// HermesExecutorRuntimeAdapter requirements are kept, because the
// dtor will disable debugging on the HermesRuntime before the
// member managing it is destroyed.
std::shared_ptr<Runtime> runtime_;
ReentrancyCheck reentrancyCheck_;
#ifdef HERMES_ENABLE_DEBUGGER
bool enableDebugger_;
facebook::hermes::inspector_modern::chrome::DebugSessionToken debugToken_;
#endif // HERMES_ENABLE_DEBUGGER
};
} // namespace
void HermesExecutorFactory::setEnableDebugger(bool enableDebugger) {
enableDebugger_ = enableDebugger;
}
void HermesExecutorFactory::setDebuggerName(const std::string& debuggerName) {
debuggerName_ = debuggerName;
}
std::unique_ptr<JSExecutor> HermesExecutorFactory::createJSExecutor(
std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> jsQueue) {
std::unique_ptr<HermesRuntime> hermesRuntime;
{
SystraceSection s("makeHermesRuntime");
hermesRuntime = hermes::makeHermesRuntime(runtimeConfig_);
}
HermesRuntime& hermesRuntimeRef = *hermesRuntime;
auto& inspectorFlags = jsinspector_modern::InspectorFlags::getInstance();
bool enableDebugger =
!inspectorFlags.getEnableModernCDPRegistry() && enableDebugger_;
auto decoratedRuntime = std::make_shared<DecoratedRuntime>(
std::move(hermesRuntime),
hermesRuntimeRef,
jsQueue,
enableDebugger,
debuggerName_);
// So what do we have now?
// DecoratedRuntime -> HermesRuntime
//
// DecoratedRuntime is held by JSIExecutor. When it gets used, it
// will check that it's on the right thread, do any necessary trace
// logging, then call the real HermesRuntime. When it is destroyed,
// it will shut down the debugger before the HermesRuntime is. In
// the normal case where debugging is not compiled in,
// all that's left is the thread checking.
// Add js engine information to Error.prototype so in error reporting we
// can send this information.
auto errorPrototype =
decoratedRuntime->global()
.getPropertyAsObject(*decoratedRuntime, "Error")
.getPropertyAsObject(*decoratedRuntime, "prototype");
errorPrototype.setProperty(*decoratedRuntime, "jsEngine", "hermes");
return std::make_unique<HermesExecutor>(
decoratedRuntime,
delegate,
jsQueue,
timeoutInvoker_,
runtimeInstaller_,
hermesRuntimeRef);
}
::hermes::vm::RuntimeConfig HermesExecutorFactory::defaultRuntimeConfig() {
return ::hermes::vm::RuntimeConfig::Builder()
.withEnableSampleProfiling(true)
.build();
}
HermesExecutor::HermesExecutor(
std::shared_ptr<jsi::Runtime> runtime,
std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> jsQueue,
const JSIScopedTimeoutInvoker& timeoutInvoker,
RuntimeInstaller runtimeInstaller,
HermesRuntime& hermesRuntime)
: JSIExecutor(runtime, delegate, timeoutInvoker, runtimeInstaller),
jsQueue_(jsQueue),
runtime_(runtime),
hermesRuntime_(hermesRuntime) {}
std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate>
HermesExecutor::createAgentDelegate(
jsinspector_modern::FrontendChannel frontendChannel,
jsinspector_modern::SessionState& sessionState,
std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate::ExportedState>
previouslyExportedState,
const jsinspector_modern::ExecutionContextDescription&
executionContextDescription) {
std::shared_ptr<HermesRuntime> hermesRuntimeShared(runtime_, &hermesRuntime_);
return std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate>(
new jsinspector_modern::HermesRuntimeAgentDelegate(
frontendChannel,
sessionState,
std::move(previouslyExportedState),
executionContextDescription,
hermesRuntimeShared,
[jsQueueWeak = std::weak_ptr(jsQueue_),
runtimeWeak = std::weak_ptr(runtime_)](auto fn) {
auto jsQueue = jsQueueWeak.lock();
if (!jsQueue) {
return;
}
jsQueue->runOnQueue([runtimeWeak, fn]() {
auto runtime = runtimeWeak.lock();
if (!runtime) {
return;
}
fn(*runtime);
});
}));
}
} // namespace facebook::react

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.
*/
#pragma once
#include <hermes/hermes.h>
#include <jsireact/JSIExecutor.h>
#include <utility>
namespace facebook::react {
class HermesExecutorFactory : public JSExecutorFactory {
public:
explicit HermesExecutorFactory(
JSIExecutor::RuntimeInstaller runtimeInstaller,
const JSIScopedTimeoutInvoker& timeoutInvoker =
JSIExecutor::defaultTimeoutInvoker,
::hermes::vm::RuntimeConfig runtimeConfig = defaultRuntimeConfig())
: runtimeInstaller_(runtimeInstaller),
timeoutInvoker_(timeoutInvoker),
runtimeConfig_(std::move(runtimeConfig)) {
assert(timeoutInvoker_ && "Should not have empty timeoutInvoker");
}
void setEnableDebugger(bool enableDebugger);
void setDebuggerName(const std::string& debuggerName);
std::unique_ptr<JSExecutor> createJSExecutor(
std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> jsQueue) override;
private:
static ::hermes::vm::RuntimeConfig defaultRuntimeConfig();
JSIExecutor::RuntimeInstaller runtimeInstaller_;
JSIScopedTimeoutInvoker timeoutInvoker_;
::hermes::vm::RuntimeConfig runtimeConfig_;
bool enableDebugger_ = true;
std::string debuggerName_ = "Hermes React Native";
};
class HermesExecutor : public JSIExecutor {
public:
HermesExecutor(
std::shared_ptr<jsi::Runtime> runtime,
std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> jsQueue,
const JSIScopedTimeoutInvoker& timeoutInvoker,
RuntimeInstaller runtimeInstaller,
hermes::HermesRuntime& hermesRuntime);
virtual 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;
private:
JSIScopedTimeoutInvoker timeoutInvoker_;
std::shared_ptr<MessageQueueThread> jsQueue_;
std::shared_ptr<jsi::Runtime> runtime_;
hermes::HermesRuntime& hermesRuntime_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,41 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "../../..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
Pod::Spec.new do |s|
s.name = "React-jsitracing"
s.version = version
s.summary = "Internal library for JSI debugging."
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 = "JSITracing.{cpp,h}"
s.header_dir = "."
s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"${PODS_TARGET_SRCROOT}/../..\"",
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
"GCC_WARN_PEDANTIC" => "YES" }
if ENV['USE_FRAMEWORKS']
s.header_mappings_dir = './'
s.module_name = 'React_jsitracing'
end
s.dependency "React-jsi"
end

View File

@@ -0,0 +1,37 @@
# 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 hermesinspectormodern_SRC CONFIGURE_DEPENDS chrome/*.cpp)
add_library(hermes_inspector_modern
STATIC
${hermesinspectormodern_SRC})
target_compile_options(
hermes_inspector_modern
PRIVATE
-std=c++20
-fexceptions
)
if(${CMAKE_BUILD_TYPE} MATCHES Debug)
target_compile_options(
hermes_inspector_modern
PRIVATE
-DHERMES_ENABLE_DEBUGGER=1
)
endif()
target_include_directories(hermes_inspector_modern PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(hermes_inspector_modern
jsinspector
fb
glog
hermes-engine::libhermes
jsi
runtimeexecutor)

View File

@@ -0,0 +1,147 @@
/*
* 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 "ConnectionDemux.h"
#ifdef HERMES_ENABLE_DEBUGGER
#include <hermes/inspector/RuntimeAdapter.h>
#include <hermes/inspector/chrome/CDPHandler.h>
#include <jsinspector-modern/InspectorInterfaces.h>
namespace facebook {
namespace hermes {
namespace inspector_modern {
namespace chrome {
using ::facebook::react::jsinspector_modern::IInspector;
using ::facebook::react::jsinspector_modern::ILocalConnection;
using ::facebook::react::jsinspector_modern::IRemoteConnection;
namespace {
class LocalConnection : public ILocalConnection {
public:
LocalConnection(
std::shared_ptr<hermes::inspector_modern::chrome::CDPHandler> conn,
std::shared_ptr<std::unordered_set<std::string>> inspectedContexts);
~LocalConnection();
void sendMessage(std::string message) override;
void disconnect() override;
private:
std::shared_ptr<hermes::inspector_modern::chrome::CDPHandler> conn_;
std::shared_ptr<std::unordered_set<std::string>> inspectedContexts_;
};
LocalConnection::LocalConnection(
std::shared_ptr<hermes::inspector_modern::chrome::CDPHandler> conn,
std::shared_ptr<std::unordered_set<std::string>> inspectedContexts)
: conn_(conn), inspectedContexts_(inspectedContexts) {
inspectedContexts_->insert(conn->getTitle());
}
LocalConnection::~LocalConnection() = default;
void LocalConnection::sendMessage(std::string str) {
conn_->handle(std::move(str));
}
void LocalConnection::disconnect() {
inspectedContexts_->erase(conn_->getTitle());
conn_->unregisterCallbacks();
}
} // namespace
ConnectionDemux::ConnectionDemux(
facebook::react::jsinspector_modern::IInspector& inspector)
: globalInspector_(inspector),
inspectedContexts_(std::make_shared<std::unordered_set<std::string>>()) {}
ConnectionDemux::~ConnectionDemux() = default;
DebugSessionToken ConnectionDemux::enableDebugging(
std::unique_ptr<RuntimeAdapter> adapter,
const std::string& title) {
std::scoped_lock lock(mutex_);
// TODO(#22976087): workaround for ComponentScript contexts never being
// destroyed.
//
// After a reload, the old ComponentScript VM instance stays alive. When we
// register the new CS VM instance, check for any previous CS VM (via strcmp
// of title) and remove them.
std::vector<int> pagesToDelete;
for (auto it = conns_.begin(); it != conns_.end(); ++it) {
if (it->second->getTitle() == title) {
pagesToDelete.push_back(it->first);
}
}
for (auto pageId : pagesToDelete) {
removePage(pageId);
}
auto waitForDebugger =
(inspectedContexts_->find(title) != inspectedContexts_->end());
return addPage(hermes::inspector_modern::chrome::CDPHandler::create(
std::move(adapter), title, waitForDebugger));
}
void ConnectionDemux::disableDebugging(DebugSessionToken session) {
std::scoped_lock lock(mutex_);
if (conns_.find(session) == conns_.end()) {
return;
}
removePage(session);
}
int ConnectionDemux::addPage(
std::shared_ptr<hermes::inspector_modern::chrome::CDPHandler> conn) {
auto connectFunc = [conn, this](std::unique_ptr<IRemoteConnection> remoteConn)
-> std::unique_ptr<ILocalConnection> {
// This cannot be unique_ptr as std::function is copyable but unique_ptr
// isn't. TODO: Change the CDPHandler API to accommodate this and not
// require a copyable callback?
std::shared_ptr<IRemoteConnection> sharedConn = std::move(remoteConn);
if (!conn->registerCallbacks(
[sharedConn](const std::string& message) {
sharedConn->onMessage(message);
},
[sharedConn]() { sharedConn->onDisconnect(); })) {
return nullptr;
}
return std::make_unique<LocalConnection>(conn, inspectedContexts_);
};
int pageId = globalInspector_.addPage(
conn->getTitle(), "Hermes", std::move(connectFunc));
conns_[pageId] = std::move(conn);
return pageId;
}
void ConnectionDemux::removePage(int pageId) {
globalInspector_.removePage(pageId);
auto conn = conns_.at(pageId);
std::string title = conn->getTitle();
inspectedContexts_->erase(title);
conn->unregisterCallbacks();
conns_.erase(pageId);
}
} // namespace chrome
} // namespace inspector_modern
} // namespace hermes
} // namespace facebook
#endif // HERMES_ENABLE_DEBUGGER

View File

@@ -0,0 +1,68 @@
/*
* 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
#ifdef HERMES_ENABLE_DEBUGGER
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <hermes/hermes.h>
#include <hermes/inspector-modern/chrome/Registration.h>
#include <hermes/inspector/RuntimeAdapter.h>
#include <hermes/inspector/chrome/CDPHandler.h>
#include <jsinspector-modern/InspectorInterfaces.h>
namespace facebook {
namespace hermes {
namespace inspector_modern {
namespace chrome {
/*
* ConnectionDemux keeps track of all debuggable Hermes runtimes (called
* "pages" in the higher-level React Native API) in this process. See
* Registration.h for documentation of the public API.
*/
class ConnectionDemux {
public:
explicit ConnectionDemux(
facebook::react::jsinspector_modern::IInspector& inspector);
~ConnectionDemux();
ConnectionDemux(const ConnectionDemux&) = delete;
ConnectionDemux& operator=(const ConnectionDemux&) = delete;
DebugSessionToken enableDebugging(
std::unique_ptr<RuntimeAdapter> adapter,
const std::string& title);
void disableDebugging(DebugSessionToken session);
private:
int addPage(
std::shared_ptr<hermes::inspector_modern::chrome::CDPHandler> conn);
void removePage(int pageId);
facebook::react::jsinspector_modern::IInspector& globalInspector_;
std::mutex mutex_;
std::unordered_map<
int,
std::shared_ptr<hermes::inspector_modern::chrome::CDPHandler>>
conns_;
std::shared_ptr<std::unordered_set<std::string>> inspectedContexts_;
};
} // namespace chrome
} // namespace inspector_modern
} // namespace hermes
} // namespace facebook
#endif // HERMES_ENABLE_DEBUGGER

View File

@@ -0,0 +1,228 @@
/*
* 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 "HermesRuntimeAgentDelegate.h"
// If HERMES_ENABLE_DEBUGGER isn't defined, we can't access any Hermes
// CDPHandler headers or types.
#ifdef HERMES_ENABLE_DEBUGGER
#include <hermes/inspector/RuntimeAdapter.h>
#include <hermes/inspector/chrome/CDPHandler.h>
#else // HERMES_ENABLE_DEBUGGER
#include <jsinspector-modern/FallbackRuntimeAgentDelegate.h>
#endif // HERMES_ENABLE_DEBUGGER
#include <hermes/hermes.h>
#include <jsinspector-modern/ReactCdp.h>
using namespace facebook::hermes;
namespace facebook::react::jsinspector_modern {
#ifdef HERMES_ENABLE_DEBUGGER
namespace {
/**
* An implementation of the Hermes RuntimeAdapter interface (part of
* Hermes's CDPHandler API) for use within a React Native RuntimeAgentDelegate.
*/
class HermesRuntimeAgentDelegateAdapter
: public hermes::inspector_modern::RuntimeAdapter {
public:
HermesRuntimeAgentDelegateAdapter(
std::shared_ptr<hermes::HermesRuntime> runtime,
RuntimeExecutor runtimeExecutor)
: runtime_(runtime), runtimeExecutor_(runtimeExecutor) {}
HermesRuntime& getRuntime() override {
return *runtime_;
}
void tickleJs() override {
runtimeExecutor_([](jsi::Runtime& runtime) {
jsi::Function func =
runtime.global().getPropertyAsFunction(runtime, "__tickleJs");
func.call(runtime);
});
}
private:
std::shared_ptr<hermes::HermesRuntime> runtime_;
RuntimeExecutor runtimeExecutor_;
};
} // namespace
/**
* A RuntimeAgentDelegate that handles requests from the Chrome DevTools
* Protocol for an instance of Hermes.
*/
class HermesRuntimeAgentDelegate::Impl final : public RuntimeAgentDelegate {
using HermesCDPHandler = hermes::inspector_modern::chrome::CDPHandler;
using HermesExecutionContextDescription =
hermes::inspector_modern::chrome::CDPHandlerExecutionContextDescription;
using HermesState = hermes::inspector_modern::chrome::State;
struct HermesStateWrapper : public ExportedState {
explicit HermesStateWrapper(std::unique_ptr<HermesState> state)
: state_(std::move(state)) {}
static std::unique_ptr<HermesState> unwrapDestructively(
ExportedState* wrapper) {
if (!wrapper) {
return nullptr;
}
if (auto* typedWrapper = dynamic_cast<HermesStateWrapper*>(wrapper)) {
return std::move(typedWrapper->state_);
}
return nullptr;
}
private:
std::unique_ptr<HermesState> state_;
};
public:
/**
* \param frontendChannel A channel used to send responses and events to the
* frontend.
* \param sessionState The state of the current CDP session. This will only
* be accessed on the main thread (during the constructor, in handleRequest,
* etc).
* \param previouslyExportedState The exported state from a previous instance
* of RuntimeAgentDelegate (NOT necessarily HermesRuntimeAgentDelegate). This
* may be nullptr, and if not nullptr it may be of any concrete type that
* implements RuntimeAgentDelegate::ExportedState.
* \param executionContextDescription A description of the execution context
* represented by this runtime. This is used for disambiguating the
* source/destination of CDP messages when there are multiple runtimes
* (concurrently or over the life of a Page).
* \param runtime The HermesRuntime that this agent is attached to.
* \param runtimeExecutor A callback for scheduling work on the JS thread.
* \c runtimeExecutor may drop scheduled work if the runtime is destroyed
* first.
*/
Impl(
FrontendChannel frontendChannel,
SessionState& sessionState,
std::unique_ptr<RuntimeAgentDelegate::ExportedState>
previouslyExportedState,
const ExecutionContextDescription& executionContextDescription,
std::shared_ptr<hermes::HermesRuntime> runtime,
RuntimeExecutor runtimeExecutor)
: hermes_(HermesCDPHandler::create(
std::make_unique<HermesRuntimeAgentDelegateAdapter>(
runtime,
runtimeExecutor),
/* waitForDebugger */ false,
/* enableConsoleAPICapturing */ false,
/* state */
HermesStateWrapper::unwrapDestructively(
previouslyExportedState.get()),
{.isRuntimeDomainEnabled = sessionState.isRuntimeDomainEnabled},
HermesExecutionContextDescription{
.id = executionContextDescription.id,
.origin = executionContextDescription.origin,
.name = executionContextDescription.name,
.auxData = std::nullopt,
.shouldSendNotifications = false})) {
hermes_->registerCallbacks(
/* msgCallback */
[frontendChannel =
std::move(frontendChannel)](const std::string& messageFromHermes) {
frontendChannel(messageFromHermes);
;
},
/* onUnregister */
[]() {});
}
/**
* Handle a CDP request. The response will be sent over the provided
* \c FrontendChannel synchronously or asynchronously.
* \param req The parsed request.
* \returns true if this agent has responded, or will respond asynchronously,
* to the request (with either a success or error message). False if the
* agent expects another agent to respond to the request instead.
*/
bool handleRequest(const cdp::PreparsedRequest& req) override {
// TODO: Change to string::starts_with when we're on C++20.
if (req.method.rfind("Log.", 0) == 0) {
// Since we know Hermes doesn't do anything useful with Log messages, but
// our containing PageAgent will, just bail out early.
// TODO: We need a way to negotiate this more dynamically with Hermes
// through the API.
return false;
}
// Forward everything else to Hermes's CDPHandler.
hermes_->handle(req.toJson());
// Let the call know that this request is handled (i.e. it is Hermes's
// responsibility to respond with either success or an error).
return true;
}
virtual std::unique_ptr<ExportedState> getExportedState() override {
return std::make_unique<HermesStateWrapper>(hermes_->getState());
}
private:
std::shared_ptr<HermesCDPHandler> hermes_;
};
#else // !HERMES_ENABLE_DEBUGGER
/**
* A stub for HermesRuntimeAgentDelegate when Hermes is compiled without
* debugging support.
*/
class HermesRuntimeAgentDelegate::Impl final
: public FallbackRuntimeAgentDelegate {
public:
Impl(
FrontendChannel frontendChannel,
SessionState& sessionState,
std::unique_ptr<RuntimeAgentDelegate::ExportedState>,
const ExecutionContextDescription&,
std::shared_ptr<hermes::HermesRuntime> runtime,
RuntimeExecutor)
: FallbackRuntimeAgentDelegate(
std::move(frontendChannel),
sessionState,
runtime->description()) {}
};
#endif // HERMES_ENABLE_DEBUGGER
HermesRuntimeAgentDelegate::HermesRuntimeAgentDelegate(
FrontendChannel frontendChannel,
SessionState& sessionState,
std::unique_ptr<RuntimeAgentDelegate::ExportedState>
previouslyExportedState,
const ExecutionContextDescription& executionContextDescription,
std::shared_ptr<hermes::HermesRuntime> runtime,
RuntimeExecutor runtimeExecutor)
: impl_(std::make_unique<Impl>(
std::move(frontendChannel),
sessionState,
std::move(previouslyExportedState),
executionContextDescription,
std::move(runtime),
std::move(runtimeExecutor))) {}
bool HermesRuntimeAgentDelegate::handleRequest(
const cdp::PreparsedRequest& req) {
return impl_->handleRequest(req);
}
std::unique_ptr<HermesRuntimeAgentDelegate::ExportedState>
HermesRuntimeAgentDelegate::getExportedState() {
return impl_->getExportedState();
}
} // namespace facebook::react::jsinspector_modern

View File

@@ -0,0 +1,71 @@
/*
* 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 <hermes/hermes.h>
#include <jsinspector-modern/ReactCdp.h>
namespace facebook::react::jsinspector_modern {
/**
* A RuntimeAgentDelegate that handles requests from the Chrome DevTools
* Protocol for an instance of Hermes.
*/
class HermesRuntimeAgentDelegate : public RuntimeAgentDelegate {
public:
/**
* \param frontendChannel A channel used to send responses and events to the
* frontend.
* \param sessionState The state of the current CDP session. This will only
* be accessed on the main thread (during the constructor, in handleRequest,
* etc).
* \param previouslyExportedState The exported state from a previous instance
* of RuntimeAgentDelegate (NOT necessarily HermesRuntimeAgentDelegate). This
* may be nullptr, and if not nullptr it may be of any concrete type that
* implements RuntimeAgentDelegate::ExportedState.
* \param executionContextDescription A description of the execution context
* represented by this runtime. This is used for disambiguating the
* source/destination of CDP messages when there are multiple runtimes
* (concurrently or over the life of a Page).
* \param runtime The HermesRuntime that this agent is attached to.
* \param runtimeExecutor A callback for scheduling work on the JS thread.
* \c runtimeExecutor may drop scheduled work if the runtime is destroyed
* first.
*/
HermesRuntimeAgentDelegate(
FrontendChannel frontendChannel,
SessionState& sessionState,
std::unique_ptr<RuntimeAgentDelegate::ExportedState>
previouslyExportedState,
const ExecutionContextDescription& executionContextDescription,
std::shared_ptr<hermes::HermesRuntime> runtime,
RuntimeExecutor runtimeExecutor);
/**
* Handle a CDP request. The response will be sent over the provided
* \c FrontendChannel synchronously or asynchronously.
* \param req The parsed request.
* \returns true if this agent has responded, or will respond asynchronously,
* to the request (with either a success or error message). False if the
* agent expects another agent to respond to the request instead.
*/
bool handleRequest(const cdp::PreparsedRequest& req) override;
virtual std::unique_ptr<ExportedState> getExportedState() override;
private:
// We use the private implementation idiom to keep HERMES_ENABLE_DEBUGGER
// checks out of the header.
class Impl;
const std::unique_ptr<Impl> impl_;
};
} // namespace facebook::react::jsinspector_modern

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "Registration.h"
#include "ConnectionDemux.h"
#ifdef HERMES_ENABLE_DEBUGGER
namespace facebook {
namespace hermes {
namespace inspector_modern {
namespace chrome {
namespace {
ConnectionDemux& demux() {
static ConnectionDemux instance{
facebook::react::jsinspector_modern::getInspectorInstance()};
return instance;
}
} // namespace
DebugSessionToken enableDebugging(
std::unique_ptr<RuntimeAdapter> adapter,
const std::string& title) {
return demux().enableDebugging(std::move(adapter), title);
}
void disableDebugging(DebugSessionToken session) {
demux().disableDebugging(session);
}
} // namespace chrome
} // namespace inspector_modern
} // namespace hermes
} // namespace facebook
#endif // HERMES_ENABLE_DEBUGGER

View File

@@ -0,0 +1,47 @@
/*
* 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
#ifdef HERMES_ENABLE_DEBUGGER
#include <memory>
#include <string>
#include <hermes/hermes.h>
#include <hermes/inspector/RuntimeAdapter.h>
namespace facebook {
namespace hermes {
namespace inspector_modern {
namespace chrome {
using DebugSessionToken = int;
/*
* enableDebugging adds this runtime to the list of debuggable JS targets
* (called "pages" in the higher-level React Native API) in this process. It
* should be called before any JS runs in the runtime. The returned token
* can be used to disable debugging for this runtime.
*/
extern DebugSessionToken enableDebugging(
std::unique_ptr<RuntimeAdapter> adapter,
const std::string& title);
/*
* disableDebugging removes this runtime from the list of debuggable JS targets
* in this process. The runtime to remove is identified by the token returned
* from enableDebugging.
*/
extern void disableDebugging(DebugSessionToken session);
} // namespace chrome
} // namespace inspector_modern
} // namespace hermes
} // namespace facebook
#endif // HERMES_ENABLE_DEBUGGER

View File

@@ -0,0 +1,148 @@
/*
* 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 <chrono>
#include <condition_variable>
#include <mutex>
#include <string>
#include <unordered_map>
#include <vector>
#include <gtest/gtest.h>
#include <hermes/hermes.h>
#include <hermes/inspector-modern/chrome/ConnectionDemux.h>
#include <jsinspector-modern/InspectorInterfaces.h>
namespace facebook {
namespace hermes {
namespace inspector_modern {
namespace chrome {
using ::facebook::react::jsinspector_modern::IInspector;
using ::facebook::react::jsinspector_modern::InspectorPageDescription;
using ::facebook::react::jsinspector_modern::IRemoteConnection;
namespace {
std::unordered_map<int, std::string> makePageMap(
const std::vector<InspectorPageDescription>& pages) {
std::unordered_map<int, std::string> pageMap;
for (auto& page : pages) {
pageMap[page.id] = page.title;
}
return pageMap;
}
void expectPages(
IInspector& inspector,
const std::unordered_map<int, std::string>& expected) {
auto pages = makePageMap(inspector.getPages());
EXPECT_EQ(pages, expected);
}
class TestRemoteConnection : public IRemoteConnection {
public:
class Data {
public:
void expectDisconnected() {
std::unique_lock<std::mutex> lock(mutex_);
cv_.wait_for(
lock, std::chrono::milliseconds(2500), [&] { return !connected_; });
EXPECT_FALSE(connected_);
}
void setDisconnected() {
std::scoped_lock lock(mutex_);
connected_ = false;
cv_.notify_one();
}
private:
std::mutex mutex_;
std::condition_variable cv_;
bool connected_{true};
};
TestRemoteConnection() : data_(std::make_shared<Data>()) {}
~TestRemoteConnection() {}
void onMessage(std::string message) override {}
void onDisconnect() override {
data_->setDisconnected();
}
std::shared_ptr<Data> getData() {
return data_;
}
private:
std::shared_ptr<Data> data_;
};
}; // namespace
TEST(ConnectionDemuxTests, TestEnableDisable) {
std::shared_ptr<HermesRuntime> runtime1(
facebook::hermes::makeHermesRuntime());
std::shared_ptr<HermesRuntime> runtime2(
facebook::hermes::makeHermesRuntime());
auto inspector =
facebook::react::jsinspector_modern::makeTestInspectorInstance();
ConnectionDemux demux{*inspector};
int id1 = demux.enableDebugging(
std::make_unique<SharedRuntimeAdapter>(runtime1), "page1");
int id2 = demux.enableDebugging(
std::make_unique<SharedRuntimeAdapter>(runtime2), "page2");
expectPages(*inspector, {{id1, "page1"}, {id2, "page2"}});
auto remoteConn1 = std::make_unique<TestRemoteConnection>();
auto remoteData1 = remoteConn1->getData();
auto localConn1 = inspector->connect(id1, std::move(remoteConn1));
EXPECT_NE(localConn1.get(), nullptr);
{
// If we connect to the same page id again without disconnecting, we should
// get null
auto remoteConn = std::make_unique<TestRemoteConnection>();
auto localConn = inspector->connect(id1, std::move(remoteConn));
EXPECT_EQ(localConn.get(), nullptr);
}
auto remoteConn2 = std::make_unique<TestRemoteConnection>();
auto remoteData2 = remoteConn2->getData();
auto localConn2 = inspector->connect(id2, std::move(remoteConn2));
EXPECT_NE(localConn2.get(), nullptr);
// Disable debugging on runtime2. This should remove its page from the list
// and call onDisconnect on its remoteConn
demux.disableDebugging(id2);
expectPages(*inspector, {{id1, "page1"}});
remoteData2->expectDisconnected();
// Disconnect conn1. Its page should still be in the page list and
// onDisconnect should be called.
localConn1->disconnect();
remoteData1->expectDisconnected();
{
// Should still be able to reconnect after disconnecting
auto remoteConn = std::make_unique<TestRemoteConnection>();
auto localConn = inspector->connect(id1, std::move(remoteConn));
EXPECT_NE(localConn.get(), nullptr);
}
}
} // namespace chrome
} // namespace inspector_modern
} // namespace hermes
} // namespace facebook

View File

@@ -0,0 +1,6 @@
---
Checks: '>
clang-diagnostic-*,
'
InheritParentConfig: true
...

View File

@@ -0,0 +1,35 @@
# 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.
##################
### jscruntime ###
##################
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fexceptions
-frtti
-O3
-Wno-unused-lambda-capture
-DLOG_TAG=\"ReactNative\")
add_library(jscruntime
STATIC
JSCRuntime.h
JSCRuntime.cpp)
target_include_directories(jscruntime PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(jscruntime
folly_runtime
jsc
jsi
glog)
if(NOT ${CMAKE_BUILD_TYPE} MATCHES Debug)
target_compile_options(jscruntime PRIVATE -DNDEBUG)
endif()

File diff suppressed because it is too large Load Diff

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>
#include <memory.h>
namespace facebook {
namespace jsc {
std::unique_ptr<jsi::Runtime> makeJSCRuntime();
} // namespace jsc
} // namespace facebook

View File

@@ -0,0 +1,37 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
Pod::Spec.new do |s|
s.name = "React-jsc"
s.version = version
s.summary = "JavaScriptCore engine for React Native"
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 = "JSCRuntime.{cpp,h}"
s.exclude_files = "**/test/*"
s.framework = "JavaScriptCore"
s.dependency "React-jsi", version
s.subspec "Fabric" do |ss|
ss.pod_target_xcconfig = { "OTHER_CFLAGS" => "$(inherited)" }
end
end

View File

@@ -0,0 +1,6 @@
---
Checks: '>
clang-diagnostic-*,
'
InheritParentConfig: true
...

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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(-std=c++20)
file(GLOB_RECURSE js_error_handler_SRC CONFIGURE_DEPENDS *.cpp)
add_library(
jserrorhandler
STATIC
${js_error_handler_SRC}
)
target_include_directories(jserrorhandler PUBLIC .)
target_link_libraries(jserrorhandler
jsi
folly_runtime
mapbufferjni
)

View File

@@ -0,0 +1,105 @@
/*
* 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 "JsErrorHandler.h"
#include <react/renderer/mapbuffer/MapBufferBuilder.h>
#include <regex>
#include <sstream>
#include <string>
#include <vector>
namespace facebook::react {
using facebook::react::JSErrorHandlerKey;
static MapBuffer
parseErrorStack(const jsi::JSError& error, bool isFatal, bool isHermes) {
/**
* This parses the different stack traces and puts them into one format
* This borrows heavily from TraceKit (https://github.com/occ/TraceKit)
* This is the same regex from stacktrace-parser.js.
*/
const std::regex REGEX_CHROME(
R"(^\s*at (?:(?:(?:Anonymous function)?|((?:\[object object\])?\S+(?: \[as \S+\])?)) )?\(?((?:file|http|https):.*?):(\d+)(?::(\d+))?\)?\s*$)");
const std::regex REGEX_GECKO(
R"(^(?:\s*([^@]*)(?:\((.*?)\))?@)?(\S.*?):(\d+)(?::(\d+))?\s*$)");
const std::regex REGEX_NODE(
R"(^\s*at (?:((?:\[object object\])?\S+(?: \[as \S+\])?) )?\(?(.*?):(\d+)(?::(\d+))?\)?\s*$)");
// Capture groups for Hermes (from parseHermesStack.js):
// 1. function name
// 2. is this a native stack frame?
// 3. is this a bytecode address or a source location?
// 4. source URL (filename)
// 5. line number (1 based)
// 6. column number (1 based) or virtual offset (0 based)
const std::regex REGEX_HERMES(
R"(^ {4}at (.+?)(?: \((native)\)?| \((address at )?(.*?):(\d+):(\d+)\))$)");
std::string line;
std::stringstream strStream(error.getStack());
auto errorObj = MapBufferBuilder();
std::vector<MapBuffer> frames;
while (std::getline(strStream, line, '\n')) {
auto frame = MapBufferBuilder();
auto searchResults = std::smatch{};
if (isHermes) {
if (std::regex_search(line, searchResults, REGEX_HERMES)) {
std::string str2 = std::string(searchResults[2]);
if (str2.compare("native")) {
frame.putString(kFrameFileName, std::string(searchResults[4]));
frame.putString(kFrameMethodName, std::string(searchResults[1]));
frame.putInt(kFrameLineNumber, std::stoi(searchResults[5]));
frame.putInt(kFrameColumnNumber, std::stoi(searchResults[6]));
frames.push_back(frame.build());
}
}
} else {
if (std::regex_search(line, searchResults, REGEX_GECKO)) {
frame.putString(kFrameFileName, std::string(searchResults[3]));
frame.putString(kFrameMethodName, std::string(searchResults[1]));
frame.putInt(kFrameLineNumber, std::stoi(searchResults[4]));
frame.putInt(kFrameColumnNumber, std::stoi(searchResults[5]));
} else if (
std::regex_search(line, searchResults, REGEX_CHROME) ||
std::regex_search(line, searchResults, REGEX_NODE)) {
frame.putString(kFrameFileName, std::string(searchResults[2]));
frame.putString(kFrameMethodName, std::string(searchResults[1]));
frame.putInt(kFrameLineNumber, std::stoi(searchResults[3]));
frame.putInt(kFrameColumnNumber, std::stoi(searchResults[4]));
} else {
continue;
}
frames.push_back(frame.build());
}
}
errorObj.putMapBufferList(kAllStackFrames, std::move(frames));
errorObj.putString(kErrorMessage, "EarlyJsError: " + error.getMessage());
// TODO: If needed, can increment exceptionId by 1 each time
errorObj.putInt(kExceptionId, 0);
errorObj.putBool(kIsFatal, isFatal);
return errorObj.build();
}
JsErrorHandler::JsErrorHandler(
JsErrorHandler::JsErrorHandlingFunc jsErrorHandlingFunc) {
this->_jsErrorHandlingFunc = jsErrorHandlingFunc;
};
JsErrorHandler::~JsErrorHandler() {}
void JsErrorHandler::handleJsError(const jsi::JSError& error, bool isFatal) {
// TODO: Current error parsing works and is stable. Can investigate using
// REGEX_HERMES to get additional Hermes data, though it requires JS setup.
MapBuffer errorMap = parseErrorStack(error, isFatal, false);
_jsErrorHandlingFunc(std::move(errorMap));
}
} // namespace facebook::react

View File

@@ -0,0 +1,39 @@
/*
* 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>
#include <react/renderer/mapbuffer/MapBuffer.h>
namespace facebook::react {
enum JSErrorHandlerKey : uint16_t {
kFrameFileName = 0,
kFrameMethodName = 1,
kFrameLineNumber = 2,
kFrameColumnNumber = 3,
kAllStackFrames = 4,
kErrorMessage = 5,
kExceptionId = 6,
kIsFatal = 7
};
class JsErrorHandler {
public:
using JsErrorHandlingFunc = std::function<void(MapBuffer errorMap)>;
JsErrorHandler(JsErrorHandlingFunc jsErrorHandlingFunc);
~JsErrorHandler();
void handleJsError(const jsi::JSError& error, bool isFatal);
private:
JsErrorHandlingFunc _jsErrorHandlingFunc;
};
} // namespace facebook::react

View File

@@ -0,0 +1,53 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
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'
react_native_path = ".."
Pod::Spec.new do |s|
s.name = "React-jserrorhandler"
s.version = version
s.summary = "-"
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.header_dir = "jserrorhandler"
s.source_files = "JsErrorHandler.{cpp,h}"
s.pod_target_xcconfig = {
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20"
}
s.compiler_flags = folly_compiler_flags + ' ' + boost_compiler_flags
if ENV['USE_FRAMEWORKS']
s.header_mappings_dir = '../'
s.module_name = 'React_jserrorhandler'
end
s.dependency folly_dep_name, folly_version
s.dependency "React-jsi"
add_dependency(s, "React-debug")
add_dependency(s, "React-Mapbuffer")
end

View File

@@ -0,0 +1,6 @@
---
Checks: '>
clang-diagnostic-*,
'
InheritParentConfig: true
...

View File

@@ -0,0 +1,27 @@
# 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.
##################
### jsi ###
##################
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fexceptions
-frtti
-O3
-Wno-unused-lambda-capture
-DLOG_TAG=\"ReactNative\")
file(GLOB jsi_SRC CONFIGURE_DEPENDS jsi/*.cpp)
add_library(jsi SHARED ${jsi_SRC})
target_include_directories(jsi PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(jsi
folly_runtime
glog)

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.
require "json"
js_engine = ENV['USE_HERMES'] == "0" ?
:jsc :
:hermes
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]
boost_compiler_flags = '-Wno-documentation'
Pod::Spec.new do |s|
s.name = "React-jsi"
s.version = version
s.summary = "JavaScript Interface layer for React Native"
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.header_dir = "jsi"
s.compiler_flags = folly_compiler_flags + ' ' + boost_compiler_flags
s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/RCT-Folly\" \"$(PODS_ROOT)/DoubleConversion\" \"$(PODS_ROOT)/fmt/include\"",
"DEFINES_MODULE" => "YES" }
s.dependency "boost", "1.83.0"
s.dependency "DoubleConversion"
s.dependency "fmt", "9.1.0"
s.dependency "RCT-Folly", folly_version
s.dependency "glog"
s.source_files = "**/*.{cpp,h}"
files_to_exclude = [
"jsi/jsilib-posix.cpp",
"jsi/jsilib-windows.cpp",
"**/test/*"
]
if js_engine == :hermes
# JSI is a part of hermes-engine. Including them also in react-native will violate the One Definition Rulle.
files_to_exclude += [ "jsi/jsi.cpp" ]
s.dependency "hermes-engine"
end
s.exclude_files = files_to_exclude
end

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.
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_library(jsi
jsi.cpp)
target_include_directories(jsi PUBLIC ..)
set(jsi_compile_flags "")
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR
"${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU")
list(APPEND jsi_compile_flags "-Wno-non-virtual-dtor")
elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "MSVC")
# Turn on Error Handling in MSVC, otherwise objects are not destructed
# when they go out of scope due to exceptions.
list(APPEND jsi_compile_flags "/EHsc")
endif()
if (HERMES_ENABLE_BITCODE)
list(APPEND jsi_compile_flags "-fembed-bitcode")
endif ()
target_compile_options(jsi PRIVATE ${jsi_compile_flags})
install(DIRECTORY "${PROJECT_SOURCE_DIR}/API/jsi/" DESTINATION include
FILES_MATCHING PATTERN "*.h"
PATTERN "test" EXCLUDE)

View File

@@ -0,0 +1,212 @@
/*
* 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 "JSIDynamic.h"
#include <glog/logging.h>
#include <folly/dynamic.h>
#include <jsi/jsi.h>
using namespace facebook::jsi;
namespace facebook {
namespace jsi {
namespace {
struct FromDynamic {
FromDynamic(const folly::dynamic* dynArg, Object objArg)
: dyn(dynArg), obj(std::move(objArg)) {}
const folly::dynamic* dyn;
Object obj;
};
// This converts one element. If it's a collection, it gets pushed onto
// the stack for later processing.
Value valueFromDynamicShallow(
Runtime& runtime,
std::vector<FromDynamic>& stack,
const folly::dynamic& dyn) {
switch (dyn.type()) {
case folly::dynamic::NULLT:
return Value::null();
case folly::dynamic::ARRAY: {
Object arr = Array(runtime, dyn.size());
Value ret = Value(runtime, arr);
stack.emplace_back(&dyn, std::move(arr));
return ret;
}
case folly::dynamic::BOOL:
return Value(dyn.getBool());
case folly::dynamic::DOUBLE:
return dyn.getDouble();
case folly::dynamic::INT64:
return Value((double)dyn.getInt());
case folly::dynamic::OBJECT: {
auto obj = Object(runtime);
Value ret = Value(runtime, obj);
stack.emplace_back(&dyn, std::move(obj));
return ret;
}
case folly::dynamic::STRING:
return Value(String::createFromUtf8(runtime, dyn.getString()));
}
CHECK(false);
}
} // namespace
Value valueFromDynamic(Runtime& runtime, const folly::dynamic& dynInput) {
std::vector<FromDynamic> stack;
Value ret = valueFromDynamicShallow(runtime, stack, dynInput);
while (!stack.empty()) {
auto top = std::move(stack.back());
stack.pop_back();
switch (top.dyn->type()) {
case folly::dynamic::ARRAY: {
Array arr = std::move(top.obj).getArray(runtime);
for (size_t i = 0; i < top.dyn->size(); ++i) {
arr.setValueAtIndex(
runtime,
i,
valueFromDynamicShallow(runtime, stack, (*top.dyn)[i]));
}
break;
}
case folly::dynamic::OBJECT: {
Object obj = std::move(top.obj);
for (const auto& element : top.dyn->items()) {
if (element.first.isNumber() || element.first.isString()) {
obj.setProperty(
runtime,
PropNameID::forUtf8(runtime, element.first.asString()),
valueFromDynamicShallow(runtime, stack, element.second));
}
}
break;
}
default:
CHECK(false);
}
}
return ret;
}
namespace {
struct FromValue {
FromValue(folly::dynamic* dynArg, Object objArg)
: dyn(dynArg), obj(std::move(objArg)) {}
folly::dynamic* dyn;
Object obj;
};
// This converts one element. If it's a collection, it gets pushed
// onto the stack for later processing. The output is created by
// mutating the output argument, because we need its actual pointer to
// push onto the stack.
void dynamicFromValueShallow(
Runtime& runtime,
std::vector<FromValue>& stack,
const jsi::Value& value,
folly::dynamic& output) {
if (value.isUndefined() || value.isNull()) {
output = nullptr;
} else if (value.isBool()) {
output = value.getBool();
} else if (value.isNumber()) {
output = value.getNumber();
} else if (value.isString()) {
output = value.getString(runtime).utf8(runtime);
} else if (value.isObject()) {
Object obj = value.getObject(runtime);
if (obj.isArray(runtime)) {
output = folly::dynamic::array();
} else if (obj.isFunction(runtime)) {
throw JSError(runtime, "JS Functions are not convertible to dynamic");
} else {
output = folly::dynamic::object();
}
stack.emplace_back(&output, std::move(obj));
} else if (value.isBigInt()) {
throw JSError(runtime, "JS BigInts are not convertible to dynamic");
} else if (value.isSymbol()) {
throw JSError(runtime, "JS Symbols are not convertible to dynamic");
} else {
throw JSError(runtime, "Value is not convertible to dynamic");
}
}
} // namespace
folly::dynamic dynamicFromValue(
Runtime& runtime,
const Value& valueInput,
std::function<bool(const std::string&)> filterObjectKeys) {
std::vector<FromValue> stack;
folly::dynamic ret;
dynamicFromValueShallow(runtime, stack, valueInput, ret);
while (!stack.empty()) {
auto top = std::move(stack.back());
stack.pop_back();
if (top.obj.isArray(runtime)) {
// Inserting into a dyn can invalidate references into it, so we
// need to insert new elements up front, then push stuff onto
// the stack.
Array array = top.obj.getArray(runtime);
size_t arraySize = array.size(runtime);
for (size_t i = 0; i < arraySize; ++i) {
top.dyn->push_back(nullptr);
}
for (size_t i = 0; i < arraySize; ++i) {
dynamicFromValueShallow(
runtime, stack, array.getValueAtIndex(runtime, i), top.dyn->at(i));
}
} else {
Array names = top.obj.getPropertyNames(runtime);
std::vector<std::pair<std::string, jsi::Value>> props;
for (size_t i = 0; i < names.size(runtime); ++i) {
String name = names.getValueAtIndex(runtime, i).getString(runtime);
Value prop = top.obj.getProperty(runtime, name);
if (prop.isUndefined()) {
continue;
}
auto nameStr = name.utf8(runtime);
if (filterObjectKeys && filterObjectKeys(nameStr)) {
continue;
}
// The JSC conversion uses JSON.stringify, which substitutes
// null for a function, so we do the same here. Just dropping
// the pair might also work, but would require more testing.
if (prop.isObject() && prop.getObject(runtime).isFunction(runtime)) {
prop = Value::null();
}
props.emplace_back(std::move(nameStr), std::move(prop));
top.dyn->insert(props.back().first, nullptr);
}
for (const auto& prop : props) {
dynamicFromValueShallow(
runtime, stack, prop.second, (*top.dyn)[prop.first]);
}
}
}
return ret;
}
} // namespace jsi
} // namespace facebook

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 <folly/dynamic.h>
#include <jsi/jsi.h>
namespace facebook {
namespace jsi {
facebook::jsi::Value valueFromDynamic(
facebook::jsi::Runtime& runtime,
const folly::dynamic& dyn);
folly::dynamic dynamicFromValue(
facebook::jsi::Runtime& runtime,
const facebook::jsi::Value& value,
std::function<bool(const std::string&)> filterObjectKeys = nullptr);
} // namespace jsi
} // namespace facebook

View File

@@ -0,0 +1,810 @@
/*
* 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 <tuple>
#include <jsi/instrumentation.h>
#include <jsi/jsi.h>
// This file contains objects to help API users create their own
// runtime adapters, i.e. if you want to compose runtimes to add your
// own behavior.
namespace facebook {
namespace jsi {
// Use this to wrap host functions. It will pass the member runtime as
// the first arg to the callback. The first argument to the ctor
// should be the decorated runtime, not the plain one.
class DecoratedHostFunction {
public:
DecoratedHostFunction(Runtime& drt, HostFunctionType plainHF)
: drt_(drt), plainHF_(std::move(plainHF)) {}
Runtime& decoratedRuntime() {
return drt_;
}
Value
operator()(Runtime&, const Value& thisVal, const Value* args, size_t count) {
return plainHF_(decoratedRuntime(), thisVal, args, count);
}
private:
template <typename Plain, typename Base>
friend class RuntimeDecorator;
Runtime& drt_;
HostFunctionType plainHF_;
};
// From the perspective of the caller, a plain HostObject is passed to
// the decorated Runtime, and the HostObject methods expect to get
// passed that Runtime. But the plain Runtime will pass itself to its
// callback, so we need a helper here which curries the decorated
// Runtime, and calls the plain HostObject with it.
//
// If the concrete RuntimeDecorator derives DecoratedHostObject, it
// should call the base class get() and set() to invoke the plain
// HostObject functionality. The Runtime& it passes does not matter,
// as it is not used.
class DecoratedHostObject : public HostObject {
public:
DecoratedHostObject(Runtime& drt, std::shared_ptr<HostObject> plainHO)
: drt_(drt), plainHO_(plainHO) {}
// The derived class methods can call this to get a reference to the
// decorated runtime, since the rt passed to the callback will be
// the plain runtime.
Runtime& decoratedRuntime() {
return drt_;
}
Value get(Runtime&, const PropNameID& name) override {
return plainHO_->get(decoratedRuntime(), name);
}
void set(Runtime&, const PropNameID& name, const Value& value) override {
plainHO_->set(decoratedRuntime(), name, value);
}
std::vector<PropNameID> getPropertyNames(Runtime&) override {
return plainHO_->getPropertyNames(decoratedRuntime());
}
private:
template <typename Plain, typename Base>
friend class RuntimeDecorator;
Runtime& drt_;
std::shared_ptr<HostObject> plainHO_;
};
/// C++ variant on a standard Decorator pattern, using template
/// parameters. The \c Plain template parameter type is the
/// undecorated Runtime type. You can usually use \c Runtime here,
/// but if you know the concrete type ahead of time and it's final,
/// the compiler can devirtualize calls to the decorated
/// implementation. The \c Base template parameter type will be used
/// as the base class of the decorated type. Here, too, you can
/// usually use \c Runtime, but if you want the decorated type to
/// implement a derived class of Runtime, you can specify that here.
/// For an example, see threadsafe.h.
template <typename Plain = Runtime, typename Base = Runtime>
class RuntimeDecorator : public Base, private jsi::Instrumentation {
public:
Plain& plain() {
static_assert(
std::is_base_of<Runtime, Plain>::value,
"RuntimeDecorator's Plain type must derive from jsi::Runtime");
static_assert(
std::is_base_of<Runtime, Base>::value,
"RuntimeDecorator's Base type must derive from jsi::Runtime");
return plain_;
}
const Plain& plain() const {
return plain_;
}
Value evaluateJavaScript(
const std::shared_ptr<const Buffer>& buffer,
const std::string& sourceURL) override {
return plain().evaluateJavaScript(buffer, sourceURL);
}
std::shared_ptr<const PreparedJavaScript> prepareJavaScript(
const std::shared_ptr<const Buffer>& buffer,
std::string sourceURL) override {
return plain().prepareJavaScript(buffer, std::move(sourceURL));
}
Value evaluatePreparedJavaScript(
const std::shared_ptr<const PreparedJavaScript>& js) override {
return plain().evaluatePreparedJavaScript(js);
}
bool drainMicrotasks(int maxMicrotasksHint) override {
return plain().drainMicrotasks(maxMicrotasksHint);
}
Object global() override {
return plain().global();
}
std::string description() override {
return plain().description();
};
bool isInspectable() override {
return plain().isInspectable();
};
Instrumentation& instrumentation() override {
return *this;
}
protected:
// plain is generally going to be a reference to an object managed
// by a derived class. We cache it here so this class can be
// concrete, and avoid making virtual calls to find the plain
// Runtime. Note that the ctor and dtor do not access through the
// reference, so passing a reference to an object before its
// lifetime has started is ok.
RuntimeDecorator(Plain& plain) : plain_(plain) {}
Runtime::PointerValue* cloneSymbol(const Runtime::PointerValue* pv) override {
return plain_.cloneSymbol(pv);
};
Runtime::PointerValue* cloneBigInt(const Runtime::PointerValue* pv) override {
return plain_.cloneBigInt(pv);
};
Runtime::PointerValue* cloneString(const Runtime::PointerValue* pv) override {
return plain_.cloneString(pv);
};
Runtime::PointerValue* cloneObject(const Runtime::PointerValue* pv) override {
return plain_.cloneObject(pv);
};
Runtime::PointerValue* clonePropNameID(
const Runtime::PointerValue* pv) override {
return plain_.clonePropNameID(pv);
};
PropNameID createPropNameIDFromAscii(const char* str, size_t length)
override {
return plain_.createPropNameIDFromAscii(str, length);
};
PropNameID createPropNameIDFromUtf8(const uint8_t* utf8, size_t length)
override {
return plain_.createPropNameIDFromUtf8(utf8, length);
};
PropNameID createPropNameIDFromString(const String& str) override {
return plain_.createPropNameIDFromString(str);
};
PropNameID createPropNameIDFromSymbol(const Symbol& sym) override {
return plain_.createPropNameIDFromSymbol(sym);
};
std::string utf8(const PropNameID& id) override {
return plain_.utf8(id);
};
bool compare(const PropNameID& a, const PropNameID& b) override {
return plain_.compare(a, b);
};
std::string symbolToString(const Symbol& sym) override {
return plain_.symbolToString(sym);
}
BigInt createBigIntFromInt64(int64_t value) override {
return plain_.createBigIntFromInt64(value);
}
BigInt createBigIntFromUint64(uint64_t value) override {
return plain_.createBigIntFromUint64(value);
}
bool bigintIsInt64(const BigInt& b) override {
return plain_.bigintIsInt64(b);
}
bool bigintIsUint64(const BigInt& b) override {
return plain_.bigintIsUint64(b);
}
uint64_t truncate(const BigInt& b) override {
return plain_.truncate(b);
}
String bigintToString(const BigInt& bigint, int radix) override {
return plain_.bigintToString(bigint, radix);
}
String createStringFromAscii(const char* str, size_t length) override {
return plain_.createStringFromAscii(str, length);
};
String createStringFromUtf8(const uint8_t* utf8, size_t length) override {
return plain_.createStringFromUtf8(utf8, length);
};
std::string utf8(const String& s) override {
return plain_.utf8(s);
}
Object createObject() override {
return plain_.createObject();
};
Object createObject(std::shared_ptr<HostObject> ho) override {
return plain_.createObject(
std::make_shared<DecoratedHostObject>(*this, std::move(ho)));
};
std::shared_ptr<HostObject> getHostObject(const jsi::Object& o) override {
std::shared_ptr<HostObject> dho = plain_.getHostObject(o);
return static_cast<DecoratedHostObject&>(*dho).plainHO_;
};
HostFunctionType& getHostFunction(const jsi::Function& f) override {
HostFunctionType& dhf = plain_.getHostFunction(f);
// This will fail if a cpp file including this header is not compiled
// with RTTI.
return dhf.target<DecoratedHostFunction>()->plainHF_;
};
bool hasNativeState(const Object& o) override {
return plain_.hasNativeState(o);
}
std::shared_ptr<NativeState> getNativeState(const Object& o) override {
return plain_.getNativeState(o);
}
void setNativeState(const Object& o, std::shared_ptr<NativeState> state)
override {
plain_.setNativeState(o, state);
}
void setExternalMemoryPressure(const Object& obj, size_t amt) override {
plain_.setExternalMemoryPressure(obj, amt);
}
Value getProperty(const Object& o, const PropNameID& name) override {
return plain_.getProperty(o, name);
};
Value getProperty(const Object& o, const String& name) override {
return plain_.getProperty(o, name);
};
bool hasProperty(const Object& o, const PropNameID& name) override {
return plain_.hasProperty(o, name);
};
bool hasProperty(const Object& o, const String& name) override {
return plain_.hasProperty(o, name);
};
void setPropertyValue(
const Object& o,
const PropNameID& name,
const Value& value) override {
plain_.setPropertyValue(o, name, value);
};
void setPropertyValue(const Object& o, const String& name, const Value& value)
override {
plain_.setPropertyValue(o, name, value);
};
bool isArray(const Object& o) const override {
return plain_.isArray(o);
};
bool isArrayBuffer(const Object& o) const override {
return plain_.isArrayBuffer(o);
};
bool isFunction(const Object& o) const override {
return plain_.isFunction(o);
};
bool isHostObject(const jsi::Object& o) const override {
return plain_.isHostObject(o);
};
bool isHostFunction(const jsi::Function& f) const override {
return plain_.isHostFunction(f);
};
Array getPropertyNames(const Object& o) override {
return plain_.getPropertyNames(o);
};
WeakObject createWeakObject(const Object& o) override {
return plain_.createWeakObject(o);
};
Value lockWeakObject(const WeakObject& wo) override {
return plain_.lockWeakObject(wo);
};
Array createArray(size_t length) override {
return plain_.createArray(length);
};
ArrayBuffer createArrayBuffer(
std::shared_ptr<MutableBuffer> buffer) override {
return plain_.createArrayBuffer(std::move(buffer));
};
size_t size(const Array& a) override {
return plain_.size(a);
};
size_t size(const ArrayBuffer& ab) override {
return plain_.size(ab);
};
uint8_t* data(const ArrayBuffer& ab) override {
return plain_.data(ab);
};
Value getValueAtIndex(const Array& a, size_t i) override {
return plain_.getValueAtIndex(a, i);
};
void setValueAtIndexImpl(const Array& a, size_t i, const Value& value)
override {
plain_.setValueAtIndexImpl(a, i, value);
};
Function createFunctionFromHostFunction(
const PropNameID& name,
unsigned int paramCount,
HostFunctionType func) override {
return plain_.createFunctionFromHostFunction(
name, paramCount, DecoratedHostFunction(*this, std::move(func)));
};
Value call(
const Function& f,
const Value& jsThis,
const Value* args,
size_t count) override {
return plain_.call(f, jsThis, args, count);
};
Value callAsConstructor(const Function& f, const Value* args, size_t count)
override {
return plain_.callAsConstructor(f, args, count);
};
// Private data for managing scopes.
Runtime::ScopeState* pushScope() override {
return plain_.pushScope();
}
void popScope(Runtime::ScopeState* ss) override {
plain_.popScope(ss);
}
bool strictEquals(const Symbol& a, const Symbol& b) const override {
return plain_.strictEquals(a, b);
};
bool strictEquals(const BigInt& a, const BigInt& b) const override {
return plain_.strictEquals(a, b);
};
bool strictEquals(const String& a, const String& b) const override {
return plain_.strictEquals(a, b);
};
bool strictEquals(const Object& a, const Object& b) const override {
return plain_.strictEquals(a, b);
};
bool instanceOf(const Object& o, const Function& f) override {
return plain_.instanceOf(o, f);
};
// jsi::Instrumentation methods
std::string getRecordedGCStats() override {
return plain().instrumentation().getRecordedGCStats();
}
std::unordered_map<std::string, int64_t> getHeapInfo(
bool includeExpensive) override {
return plain().instrumentation().getHeapInfo(includeExpensive);
}
void collectGarbage(std::string cause) override {
plain().instrumentation().collectGarbage(std::move(cause));
}
void startTrackingHeapObjectStackTraces(
std::function<void(
uint64_t,
std::chrono::microseconds,
std::vector<HeapStatsUpdate>)> callback) override {
plain().instrumentation().startTrackingHeapObjectStackTraces(
std::move(callback));
}
void stopTrackingHeapObjectStackTraces() override {
plain().instrumentation().stopTrackingHeapObjectStackTraces();
}
void startHeapSampling(size_t samplingInterval) override {
plain().instrumentation().startHeapSampling(samplingInterval);
}
void stopHeapSampling(std::ostream& os) override {
plain().instrumentation().stopHeapSampling(os);
}
void createSnapshotToFile(const std::string& path) override {
plain().instrumentation().createSnapshotToFile(path);
}
void createSnapshotToStream(std::ostream& os) override {
plain().instrumentation().createSnapshotToStream(os);
}
std::string flushAndDisableBridgeTrafficTrace() override {
return const_cast<Plain&>(plain())
.instrumentation()
.flushAndDisableBridgeTrafficTrace();
}
void writeBasicBlockProfileTraceToFile(
const std::string& fileName) const override {
const_cast<Plain&>(plain())
.instrumentation()
.writeBasicBlockProfileTraceToFile(fileName);
}
/// Dump external profiler symbols to the given file name.
void dumpProfilerSymbolsToFile(const std::string& fileName) const override {
const_cast<Plain&>(plain()).instrumentation().dumpProfilerSymbolsToFile(
fileName);
}
private:
Plain& plain_;
};
namespace detail {
// This metaprogramming allows the With type's methods to be
// optional.
template <typename T, typename U = void>
struct BeforeCaller {
static void before(T&) {}
};
template <typename T, typename U = void>
struct AfterCaller {
static void after(T&) {}
};
// decltype((void)&...) is either SFINAE, or void.
// So, if SFINAE does not happen for T, then this specialization exists
// for BeforeCaller<T, void>, and always applies. If not, only the
// default above exists, and that is used instead.
template <typename T>
struct BeforeCaller<T, decltype((void)&T::before)> {
static void before(T& t) {
t.before();
}
};
template <typename T>
struct AfterCaller<T, decltype((void)&T::after)> {
static void after(T& t) {
t.after();
}
};
// It's possible to use multiple decorators by nesting
// WithRuntimeDecorator<...>, but this specialization allows use of
// std::tuple of decorator classes instead. See testlib.cpp for an
// example.
template <typename... T>
struct BeforeCaller<std::tuple<T...>> {
static void before(std::tuple<T...>& tuple) {
all_before<0, T...>(tuple);
}
private:
template <size_t N, typename U, typename... Rest>
static void all_before(std::tuple<T...>& tuple) {
detail::BeforeCaller<U>::before(std::get<N>(tuple));
all_before<N + 1, Rest...>(tuple);
}
template <size_t N>
static void all_before(std::tuple<T...>&) {}
};
template <typename... T>
struct AfterCaller<std::tuple<T...>> {
static void after(std::tuple<T...>& tuple) {
all_after<0, T...>(tuple);
}
private:
template <size_t N, typename U, typename... Rest>
static void all_after(std::tuple<T...>& tuple) {
all_after<N + 1, Rest...>(tuple);
detail::AfterCaller<U>::after(std::get<N>(tuple));
}
template <size_t N>
static void all_after(std::tuple<T...>&) {}
};
} // namespace detail
// A decorator which implements an around idiom. A With instance is
// RAII constructed before each call to the undecorated class; the
// ctor is passed a single argument of type WithArg&. Plain and Base
// are used as in the base class.
template <typename With, typename Plain = Runtime, typename Base = Runtime>
class WithRuntimeDecorator : public RuntimeDecorator<Plain, Base> {
public:
using RD = RuntimeDecorator<Plain, Base>;
// The reference arguments to the ctor are stored, but not used by
// the ctor, and there is no ctor, so they can be passed members of
// the derived class.
WithRuntimeDecorator(Plain& plain, With& with) : RD(plain), with_(with) {}
Value evaluateJavaScript(
const std::shared_ptr<const Buffer>& buffer,
const std::string& sourceURL) override {
Around around{with_};
return RD::evaluateJavaScript(buffer, sourceURL);
}
std::shared_ptr<const PreparedJavaScript> prepareJavaScript(
const std::shared_ptr<const Buffer>& buffer,
std::string sourceURL) override {
Around around{with_};
return RD::prepareJavaScript(buffer, std::move(sourceURL));
}
Value evaluatePreparedJavaScript(
const std::shared_ptr<const PreparedJavaScript>& js) override {
Around around{with_};
return RD::evaluatePreparedJavaScript(js);
}
bool drainMicrotasks(int maxMicrotasksHint) override {
Around around{with_};
return RD::drainMicrotasks(maxMicrotasksHint);
}
Object global() override {
Around around{with_};
return RD::global();
}
std::string description() override {
Around around{with_};
return RD::description();
};
bool isInspectable() override {
Around around{with_};
return RD::isInspectable();
};
// The jsi:: prefix is necessary because MSVC compiler complains C2247:
// Instrumentation is not accessible because RuntimeDecorator uses private
// to inherit from Instrumentation.
// TODO(T40821815) Consider removing this workaround when updating MSVC
jsi::Instrumentation& instrumentation() override {
Around around{with_};
return RD::instrumentation();
}
protected:
Runtime::PointerValue* cloneSymbol(const Runtime::PointerValue* pv) override {
Around around{with_};
return RD::cloneSymbol(pv);
};
Runtime::PointerValue* cloneString(const Runtime::PointerValue* pv) override {
Around around{with_};
return RD::cloneString(pv);
};
Runtime::PointerValue* cloneObject(const Runtime::PointerValue* pv) override {
Around around{with_};
return RD::cloneObject(pv);
};
Runtime::PointerValue* clonePropNameID(
const Runtime::PointerValue* pv) override {
Around around{with_};
return RD::clonePropNameID(pv);
};
PropNameID createPropNameIDFromAscii(const char* str, size_t length)
override {
Around around{with_};
return RD::createPropNameIDFromAscii(str, length);
};
PropNameID createPropNameIDFromUtf8(const uint8_t* utf8, size_t length)
override {
Around around{with_};
return RD::createPropNameIDFromUtf8(utf8, length);
};
PropNameID createPropNameIDFromString(const String& str) override {
Around around{with_};
return RD::createPropNameIDFromString(str);
};
std::string utf8(const PropNameID& id) override {
Around around{with_};
return RD::utf8(id);
};
bool compare(const PropNameID& a, const PropNameID& b) override {
Around around{with_};
return RD::compare(a, b);
};
std::string symbolToString(const Symbol& sym) override {
Around around{with_};
return RD::symbolToString(sym);
};
String createStringFromAscii(const char* str, size_t length) override {
Around around{with_};
return RD::createStringFromAscii(str, length);
};
String createStringFromUtf8(const uint8_t* utf8, size_t length) override {
Around around{with_};
return RD::createStringFromUtf8(utf8, length);
};
std::string utf8(const String& s) override {
Around around{with_};
return RD::utf8(s);
}
Object createObject() override {
Around around{with_};
return RD::createObject();
};
Object createObject(std::shared_ptr<HostObject> ho) override {
Around around{with_};
return RD::createObject(std::move(ho));
};
std::shared_ptr<HostObject> getHostObject(const jsi::Object& o) override {
Around around{with_};
return RD::getHostObject(o);
};
HostFunctionType& getHostFunction(const jsi::Function& f) override {
Around around{with_};
return RD::getHostFunction(f);
};
Value getProperty(const Object& o, const PropNameID& name) override {
Around around{with_};
return RD::getProperty(o, name);
};
Value getProperty(const Object& o, const String& name) override {
Around around{with_};
return RD::getProperty(o, name);
};
bool hasProperty(const Object& o, const PropNameID& name) override {
Around around{with_};
return RD::hasProperty(o, name);
};
bool hasProperty(const Object& o, const String& name) override {
Around around{with_};
return RD::hasProperty(o, name);
};
void setPropertyValue(
const Object& o,
const PropNameID& name,
const Value& value) override {
Around around{with_};
RD::setPropertyValue(o, name, value);
};
void setPropertyValue(const Object& o, const String& name, const Value& value)
override {
Around around{with_};
RD::setPropertyValue(o, name, value);
};
bool isArray(const Object& o) const override {
Around around{with_};
return RD::isArray(o);
};
bool isArrayBuffer(const Object& o) const override {
Around around{with_};
return RD::isArrayBuffer(o);
};
bool isFunction(const Object& o) const override {
Around around{with_};
return RD::isFunction(o);
};
bool isHostObject(const jsi::Object& o) const override {
Around around{with_};
return RD::isHostObject(o);
};
bool isHostFunction(const jsi::Function& f) const override {
Around around{with_};
return RD::isHostFunction(f);
};
Array getPropertyNames(const Object& o) override {
Around around{with_};
return RD::getPropertyNames(o);
};
WeakObject createWeakObject(const Object& o) override {
Around around{with_};
return RD::createWeakObject(o);
};
Value lockWeakObject(const WeakObject& wo) override {
Around around{with_};
return RD::lockWeakObject(wo);
};
Array createArray(size_t length) override {
Around around{with_};
return RD::createArray(length);
};
ArrayBuffer createArrayBuffer(
std::shared_ptr<MutableBuffer> buffer) override {
return RD::createArrayBuffer(std::move(buffer));
};
size_t size(const Array& a) override {
Around around{with_};
return RD::size(a);
};
size_t size(const ArrayBuffer& ab) override {
Around around{with_};
return RD::size(ab);
};
uint8_t* data(const ArrayBuffer& ab) override {
Around around{with_};
return RD::data(ab);
};
Value getValueAtIndex(const Array& a, size_t i) override {
Around around{with_};
return RD::getValueAtIndex(a, i);
};
void setValueAtIndexImpl(const Array& a, size_t i, const Value& value)
override {
Around around{with_};
RD::setValueAtIndexImpl(a, i, value);
};
Function createFunctionFromHostFunction(
const PropNameID& name,
unsigned int paramCount,
HostFunctionType func) override {
Around around{with_};
return RD::createFunctionFromHostFunction(
name, paramCount, std::move(func));
};
Value call(
const Function& f,
const Value& jsThis,
const Value* args,
size_t count) override {
Around around{with_};
return RD::call(f, jsThis, args, count);
};
Value callAsConstructor(const Function& f, const Value* args, size_t count)
override {
Around around{with_};
return RD::callAsConstructor(f, args, count);
};
// Private data for managing scopes.
Runtime::ScopeState* pushScope() override {
Around around{with_};
return RD::pushScope();
}
void popScope(Runtime::ScopeState* ss) override {
Around around{with_};
RD::popScope(ss);
}
bool strictEquals(const Symbol& a, const Symbol& b) const override {
Around around{with_};
return RD::strictEquals(a, b);
};
bool strictEquals(const String& a, const String& b) const override {
Around around{with_};
return RD::strictEquals(a, b);
};
bool strictEquals(const Object& a, const Object& b) const override {
Around around{with_};
return RD::strictEquals(a, b);
};
bool instanceOf(const Object& o, const Function& f) override {
Around around{with_};
return RD::instanceOf(o, f);
};
private:
// Wrap an RAII type around With& to guarantee after always happens.
struct Around {
Around(With& with) : with_(with) {
detail::BeforeCaller<With>::before(with_);
}
~Around() {
detail::AfterCaller<With>::after(with_);
}
With& with_;
};
With& with_;
};
} // namespace jsi
} // namespace facebook

View File

@@ -0,0 +1,117 @@
/*
* 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 <chrono>
#include <iosfwd>
#include <string>
#include <tuple>
#include <unordered_map>
#include <jsi/jsi.h>
namespace facebook {
namespace jsi {
/// Methods for starting and collecting instrumentation, an \c Instrumentation
/// instance is associated with a particular \c Runtime instance, which it
/// controls the instrumentation of.
/// None of these functions should return newly created jsi values, nor should
/// it modify the values of any jsi values in the heap (although GCs are fine).
class JSI_EXPORT Instrumentation {
public:
virtual ~Instrumentation() = default;
/// Returns GC statistics as a JSON-encoded string, with an object containing
/// "type" and "version" fields outermost. "type" is a string, unique to a
/// particular implementation of \c jsi::Instrumentation, and "version" is a
/// number to indicate any revision to that implementation and its output
/// format.
///
/// \pre This call can only be made on the instrumentation instance of a
/// runtime initialised to collect GC statistics.
///
/// \post All cumulative measurements mentioned in the output are accumulated
/// across the entire lifetime of the Runtime.
///
/// \return the GC statistics collected so far, as a JSON-encoded string.
virtual std::string getRecordedGCStats() = 0;
/// Request statistics about the current state of the runtime's heap. This
/// function can be called at any time, and should produce information that is
/// correct at the instant it is called (i.e, not stale).
///
/// \return a map from a string key to a number associated with that
/// statistic.
virtual std::unordered_map<std::string, int64_t> getHeapInfo(
bool includeExpensive) = 0;
/// Perform a full garbage collection.
/// \param cause The cause of this collection, as it should be reported in
/// logs.
virtual void collectGarbage(std::string cause) = 0;
/// A HeapStatsUpdate is a tuple of the fragment index, the number of objects
/// in that fragment, and the number of bytes used by those objects.
/// A "fragment" is a view of all objects allocated within a time slice.
using HeapStatsUpdate = std::tuple<uint64_t, uint64_t, uint64_t>;
/// Start capturing JS stack-traces for all JS heap allocated objects. These
/// can be accessed via \c ::createSnapshotToFile().
/// \param fragmentCallback If present, invoke this callback every so often
/// with the most recently seen object ID, and a list of fragments that have
/// been updated. This callback will be invoked on the same thread that the
/// runtime is using.
virtual void startTrackingHeapObjectStackTraces(
std::function<void(
uint64_t lastSeenObjectID,
std::chrono::microseconds timestamp,
std::vector<HeapStatsUpdate> stats)> fragmentCallback) = 0;
/// Stop capture JS stack-traces for JS heap allocated objects.
virtual void stopTrackingHeapObjectStackTraces() = 0;
/// Start a heap sampling profiler that will sample heap allocations, and the
/// stack trace they were allocated at. Reports a summary of which functions
/// allocated the most.
/// \param samplingInterval The number of bytes allocated to wait between
/// samples. This will be used as the expected value of a poisson
/// distribution.
virtual void startHeapSampling(size_t samplingInterval) = 0;
/// Turns off the heap sampling profiler previously enabled via
/// \c startHeapSampling. Writes the output of the sampling heap profiler to
/// \p os. The output is a JSON formatted string.
virtual void stopHeapSampling(std::ostream& os) = 0;
/// Captures the heap to a file
///
/// \param path to save the heap capture
virtual void createSnapshotToFile(const std::string& path) = 0;
/// Captures the heap to an output stream
///
/// \param os output stream to write to.
virtual void createSnapshotToStream(std::ostream& os) = 0;
/// If the runtime has been created to trace to a temp file, flush
/// any unwritten parts of the trace of bridge traffic to the file,
/// and return the name of the file. Otherwise, return the empty string.
/// Tracing is disabled after this call.
virtual std::string flushAndDisableBridgeTrafficTrace() = 0;
/// Write basic block profile trace to the given file name.
virtual void writeBasicBlockProfileTraceToFile(
const std::string& fileName) const = 0;
/// Dump external profiler symbols to the given file name.
virtual void dumpProfilerSymbolsToFile(const std::string& fileName) const = 0;
};
} // namespace jsi
} // namespace facebook

View File

@@ -0,0 +1,356 @@
/*
* 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
namespace facebook {
namespace jsi {
namespace detail {
inline Value toValue(Runtime&, std::nullptr_t) {
return Value::null();
}
inline Value toValue(Runtime&, bool b) {
return Value(b);
}
inline Value toValue(Runtime&, double d) {
return Value(d);
}
inline Value toValue(Runtime&, float f) {
return Value(static_cast<double>(f));
}
inline Value toValue(Runtime&, int i) {
return Value(i);
}
inline Value toValue(Runtime& runtime, const char* str) {
return String::createFromAscii(runtime, str);
}
inline Value toValue(Runtime& runtime, const std::string& str) {
return String::createFromUtf8(runtime, str);
}
template <typename T>
inline Value toValue(Runtime& runtime, const T& other) {
static_assert(
std::is_base_of<Pointer, T>::value,
"This type cannot be converted to Value");
return Value(runtime, other);
}
inline Value toValue(Runtime& runtime, const Value& value) {
return Value(runtime, value);
}
inline Value&& toValue(Runtime&, Value&& value) {
return std::move(value);
}
inline PropNameID toPropNameID(Runtime& runtime, const char* name) {
return PropNameID::forAscii(runtime, name);
}
inline PropNameID toPropNameID(Runtime& runtime, const std::string& name) {
return PropNameID::forUtf8(runtime, name);
}
inline PropNameID&& toPropNameID(Runtime&, PropNameID&& name) {
return std::move(name);
}
/// Helper to throw while still compiling with exceptions turned off.
template <typename E, typename... Args>
[[noreturn]] inline void throwOrDie(Args&&... args) {
std::rethrow_exception(
std::make_exception_ptr(E{std::forward<Args>(args)...}));
}
} // namespace detail
template <typename T>
inline T Runtime::make(Runtime::PointerValue* pv) {
return T(pv);
}
inline Runtime::PointerValue* Runtime::getPointerValue(jsi::Pointer& pointer) {
return pointer.ptr_;
}
inline const Runtime::PointerValue* Runtime::getPointerValue(
const jsi::Pointer& pointer) {
return pointer.ptr_;
}
inline const Runtime::PointerValue* Runtime::getPointerValue(
const jsi::Value& value) {
return value.data_.pointer.ptr_;
}
inline Value Object::getProperty(Runtime& runtime, const char* name) const {
return getProperty(runtime, String::createFromAscii(runtime, name));
}
inline Value Object::getProperty(Runtime& runtime, const String& name) const {
return runtime.getProperty(*this, name);
}
inline Value Object::getProperty(Runtime& runtime, const PropNameID& name)
const {
return runtime.getProperty(*this, name);
}
inline bool Object::hasProperty(Runtime& runtime, const char* name) const {
return hasProperty(runtime, String::createFromAscii(runtime, name));
}
inline bool Object::hasProperty(Runtime& runtime, const String& name) const {
return runtime.hasProperty(*this, name);
}
inline bool Object::hasProperty(Runtime& runtime, const PropNameID& name)
const {
return runtime.hasProperty(*this, name);
}
template <typename T>
void Object::setProperty(Runtime& runtime, const char* name, T&& value) const {
setProperty(
runtime, String::createFromAscii(runtime, name), std::forward<T>(value));
}
template <typename T>
void Object::setProperty(Runtime& runtime, const String& name, T&& value)
const {
setPropertyValue(
runtime, name, detail::toValue(runtime, std::forward<T>(value)));
}
template <typename T>
void Object::setProperty(Runtime& runtime, const PropNameID& name, T&& value)
const {
setPropertyValue(
runtime, name, detail::toValue(runtime, std::forward<T>(value)));
}
inline Array Object::getArray(Runtime& runtime) const& {
assert(runtime.isArray(*this));
(void)runtime; // when assert is disabled we need to mark this as used
return Array(runtime.cloneObject(ptr_));
}
inline Array Object::getArray(Runtime& runtime) && {
assert(runtime.isArray(*this));
(void)runtime; // when assert is disabled we need to mark this as used
Runtime::PointerValue* value = ptr_;
ptr_ = nullptr;
return Array(value);
}
inline ArrayBuffer Object::getArrayBuffer(Runtime& runtime) const& {
assert(runtime.isArrayBuffer(*this));
(void)runtime; // when assert is disabled we need to mark this as used
return ArrayBuffer(runtime.cloneObject(ptr_));
}
inline ArrayBuffer Object::getArrayBuffer(Runtime& runtime) && {
assert(runtime.isArrayBuffer(*this));
(void)runtime; // when assert is disabled we need to mark this as used
Runtime::PointerValue* value = ptr_;
ptr_ = nullptr;
return ArrayBuffer(value);
}
inline Function Object::getFunction(Runtime& runtime) const& {
assert(runtime.isFunction(*this));
return Function(runtime.cloneObject(ptr_));
}
inline Function Object::getFunction(Runtime& runtime) && {
assert(runtime.isFunction(*this));
(void)runtime; // when assert is disabled we need to mark this as used
Runtime::PointerValue* value = ptr_;
ptr_ = nullptr;
return Function(value);
}
template <typename T>
inline bool Object::isHostObject(Runtime& runtime) const {
return runtime.isHostObject(*this) &&
std::dynamic_pointer_cast<T>(runtime.getHostObject(*this));
}
template <>
inline bool Object::isHostObject<HostObject>(Runtime& runtime) const {
return runtime.isHostObject(*this);
}
template <typename T>
inline std::shared_ptr<T> Object::getHostObject(Runtime& runtime) const {
assert(isHostObject<T>(runtime));
return std::static_pointer_cast<T>(runtime.getHostObject(*this));
}
template <typename T>
inline std::shared_ptr<T> Object::asHostObject(Runtime& runtime) const {
if (!isHostObject<T>(runtime)) {
detail::throwOrDie<JSINativeException>(
"Object is not a HostObject of desired type");
}
return std::static_pointer_cast<T>(runtime.getHostObject(*this));
}
template <>
inline std::shared_ptr<HostObject> Object::getHostObject<HostObject>(
Runtime& runtime) const {
assert(runtime.isHostObject(*this));
return runtime.getHostObject(*this);
}
template <typename T>
inline bool Object::hasNativeState(Runtime& runtime) const {
return runtime.hasNativeState(*this) &&
std::dynamic_pointer_cast<T>(runtime.getNativeState(*this));
}
template <>
inline bool Object::hasNativeState<NativeState>(Runtime& runtime) const {
return runtime.hasNativeState(*this);
}
template <typename T>
inline std::shared_ptr<T> Object::getNativeState(Runtime& runtime) const {
assert(hasNativeState<T>(runtime));
return std::static_pointer_cast<T>(runtime.getNativeState(*this));
}
inline void Object::setNativeState(
Runtime& runtime,
std::shared_ptr<NativeState> state) const {
runtime.setNativeState(*this, state);
}
inline void Object::setExternalMemoryPressure(Runtime& runtime, size_t amt)
const {
runtime.setExternalMemoryPressure(*this, amt);
}
inline Array Object::getPropertyNames(Runtime& runtime) const {
return runtime.getPropertyNames(*this);
}
inline Value WeakObject::lock(Runtime& runtime) const {
return runtime.lockWeakObject(*this);
}
template <typename T>
void Array::setValueAtIndex(Runtime& runtime, size_t i, T&& value) const {
setValueAtIndexImpl(
runtime, i, detail::toValue(runtime, std::forward<T>(value)));
}
inline Value Array::getValueAtIndex(Runtime& runtime, size_t i) const {
return runtime.getValueAtIndex(*this, i);
}
inline Function Function::createFromHostFunction(
Runtime& runtime,
const jsi::PropNameID& name,
unsigned int paramCount,
jsi::HostFunctionType func) {
return runtime.createFunctionFromHostFunction(
name, paramCount, std::move(func));
}
inline Value Function::call(Runtime& runtime, const Value* args, size_t count)
const {
return runtime.call(*this, Value::undefined(), args, count);
}
inline Value Function::call(Runtime& runtime, std::initializer_list<Value> args)
const {
return call(runtime, args.begin(), args.size());
}
template <typename... Args>
inline Value Function::call(Runtime& runtime, Args&&... args) const {
// A more awesome version of this would be able to create raw values
// which can be used directly without wrapping and unwrapping, but
// this will do for now.
return call(runtime, {detail::toValue(runtime, std::forward<Args>(args))...});
}
inline Value Function::callWithThis(
Runtime& runtime,
const Object& jsThis,
const Value* args,
size_t count) const {
return runtime.call(*this, Value(runtime, jsThis), args, count);
}
inline Value Function::callWithThis(
Runtime& runtime,
const Object& jsThis,
std::initializer_list<Value> args) const {
return callWithThis(runtime, jsThis, args.begin(), args.size());
}
template <typename... Args>
inline Value Function::callWithThis(
Runtime& runtime,
const Object& jsThis,
Args&&... args) const {
// A more awesome version of this would be able to create raw values
// which can be used directly without wrapping and unwrapping, but
// this will do for now.
return callWithThis(
runtime, jsThis, {detail::toValue(runtime, std::forward<Args>(args))...});
}
template <typename... Args>
inline Array Array::createWithElements(Runtime& runtime, Args&&... args) {
return createWithElements(
runtime, {detail::toValue(runtime, std::forward<Args>(args))...});
}
template <typename... Args>
inline std::vector<PropNameID> PropNameID::names(
Runtime& runtime,
Args&&... args) {
return names({detail::toPropNameID(runtime, std::forward<Args>(args))...});
}
template <size_t N>
inline std::vector<PropNameID> PropNameID::names(
PropNameID(&&propertyNames)[N]) {
std::vector<PropNameID> result;
result.reserve(N);
for (auto& name : propertyNames) {
result.push_back(std::move(name));
}
return result;
}
inline Value Function::callAsConstructor(
Runtime& runtime,
const Value* args,
size_t count) const {
return runtime.callAsConstructor(*this, args, count);
}
inline Value Function::callAsConstructor(
Runtime& runtime,
std::initializer_list<Value> args) const {
return callAsConstructor(runtime, args.begin(), args.size());
}
template <typename... Args>
inline Value Function::callAsConstructor(Runtime& runtime, Args&&... args)
const {
return callAsConstructor(
runtime, {detail::toValue(runtime, std::forward<Args>(args))...});
}
String BigInt::toString(Runtime& runtime, int radix) const {
return runtime.bigintToString(*this, radix);
}
} // namespace jsi
} // namespace facebook

View File

@@ -0,0 +1,534 @@
/*
* 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 <cassert>
#include <cmath>
#include <cstdlib>
#include <stdexcept>
#include <jsi/instrumentation.h>
#include <jsi/jsi.h>
namespace facebook {
namespace jsi {
namespace {
// This is used for generating short exception strings.
std::string kindToString(const Value& v, Runtime* rt = nullptr) {
if (v.isUndefined()) {
return "undefined";
} else if (v.isNull()) {
return "null";
} else if (v.isBool()) {
return v.getBool() ? "true" : "false";
} else if (v.isNumber()) {
return "a number";
} else if (v.isString()) {
return "a string";
} else if (v.isSymbol()) {
return "a symbol";
} else if (v.isBigInt()) {
return "a bigint";
} else {
assert(v.isObject() && "Expecting object.");
return rt != nullptr && v.getObject(*rt).isFunction(*rt) ? "a function"
: "an object";
}
}
// getPropertyAsFunction() will try to create a JSError. If the
// failure is in building a JSError, this will lead to infinite
// recursion. This function is used in place of getPropertyAsFunction
// when building JSError, to avoid that infinite recursion.
Value callGlobalFunction(Runtime& runtime, const char* name, const Value& arg) {
Value v = runtime.global().getProperty(runtime, name);
if (!v.isObject()) {
throw JSINativeException(
std::string("callGlobalFunction: JS global property '") + name +
"' is " + kindToString(v, &runtime) + ", expected a Function");
}
Object o = v.getObject(runtime);
if (!o.isFunction(runtime)) {
throw JSINativeException(
std::string("callGlobalFunction: JS global property '") + name +
"' is a non-callable Object, expected a Function");
}
Function f = std::move(o).getFunction(runtime);
return f.call(runtime, arg);
}
} // namespace
Buffer::~Buffer() = default;
MutableBuffer::~MutableBuffer() = default;
PreparedJavaScript::~PreparedJavaScript() = default;
Value HostObject::get(Runtime&, const PropNameID&) {
return Value();
}
void HostObject::set(Runtime& rt, const PropNameID& name, const Value&) {
std::string msg("TypeError: Cannot assign to property '");
msg += name.utf8(rt);
msg += "' on HostObject with default setter";
throw JSError(rt, msg);
}
HostObject::~HostObject() {}
NativeState::~NativeState() {}
Runtime::~Runtime() {}
Instrumentation& Runtime::instrumentation() {
class NoInstrumentation : public Instrumentation {
std::string getRecordedGCStats() override {
return "";
}
std::unordered_map<std::string, int64_t> getHeapInfo(bool) override {
return std::unordered_map<std::string, int64_t>{};
}
void collectGarbage(std::string) override {}
void startTrackingHeapObjectStackTraces(
std::function<void(
uint64_t,
std::chrono::microseconds,
std::vector<HeapStatsUpdate>)>) override {}
void stopTrackingHeapObjectStackTraces() override {}
void startHeapSampling(size_t) override {}
void stopHeapSampling(std::ostream&) override {}
void createSnapshotToFile(const std::string&) override {
throw JSINativeException(
"Default instrumentation cannot create a heap snapshot");
}
void createSnapshotToStream(std::ostream&) override {
throw JSINativeException(
"Default instrumentation cannot create a heap snapshot");
}
std::string flushAndDisableBridgeTrafficTrace() override {
std::abort();
}
void writeBasicBlockProfileTraceToFile(const std::string&) const override {
std::abort();
}
void dumpProfilerSymbolsToFile(const std::string&) const override {
std::abort();
}
};
static NoInstrumentation sharedInstance;
return sharedInstance;
}
Value Runtime::createValueFromJsonUtf8(const uint8_t* json, size_t length) {
Function parseJson = global()
.getPropertyAsObject(*this, "JSON")
.getPropertyAsFunction(*this, "parse");
return parseJson.call(*this, String::createFromUtf8(*this, json, length));
}
Pointer& Pointer::operator=(Pointer&& other) {
if (ptr_) {
ptr_->invalidate();
}
ptr_ = other.ptr_;
other.ptr_ = nullptr;
return *this;
}
Object Object::getPropertyAsObject(Runtime& runtime, const char* name) const {
Value v = getProperty(runtime, name);
if (!v.isObject()) {
throw JSError(
runtime,
std::string("getPropertyAsObject: property '") + name + "' is " +
kindToString(v, &runtime) + ", expected an Object");
}
return v.getObject(runtime);
}
Function Object::getPropertyAsFunction(Runtime& runtime, const char* name)
const {
Object obj = getPropertyAsObject(runtime, name);
if (!obj.isFunction(runtime)) {
throw JSError(
runtime,
std::string("getPropertyAsFunction: property '") + name + "' is " +
kindToString(std::move(obj), &runtime) + ", expected a Function");
};
return std::move(obj).getFunction(runtime);
}
Array Object::asArray(Runtime& runtime) const& {
if (!isArray(runtime)) {
throw JSError(
runtime,
"Object is " + kindToString(Value(runtime, *this), &runtime) +
", expected an array");
}
return getArray(runtime);
}
Array Object::asArray(Runtime& runtime) && {
if (!isArray(runtime)) {
throw JSError(
runtime,
"Object is " + kindToString(Value(runtime, *this), &runtime) +
", expected an array");
}
return std::move(*this).getArray(runtime);
}
Function Object::asFunction(Runtime& runtime) const& {
if (!isFunction(runtime)) {
throw JSError(
runtime,
"Object is " + kindToString(Value(runtime, *this), &runtime) +
", expected a function");
}
return getFunction(runtime);
}
Function Object::asFunction(Runtime& runtime) && {
if (!isFunction(runtime)) {
throw JSError(
runtime,
"Object is " + kindToString(Value(runtime, *this), &runtime) +
", expected a function");
}
return std::move(*this).getFunction(runtime);
}
Value::Value(Value&& other) : Value(other.kind_) {
if (kind_ == BooleanKind) {
data_.boolean = other.data_.boolean;
} else if (kind_ == NumberKind) {
data_.number = other.data_.number;
} else if (kind_ >= PointerKind) {
new (&data_.pointer) Pointer(std::move(other.data_.pointer));
}
// when the other's dtor runs, nothing will happen.
other.kind_ = UndefinedKind;
}
Value::Value(Runtime& runtime, const Value& other) : Value(other.kind_) {
// data_ is uninitialized, so use placement new to create non-POD
// types in it. Any other kind of initialization will call a dtor
// first, which is incorrect.
if (kind_ == BooleanKind) {
data_.boolean = other.data_.boolean;
} else if (kind_ == NumberKind) {
data_.number = other.data_.number;
} else if (kind_ == SymbolKind) {
new (&data_.pointer) Pointer(runtime.cloneSymbol(other.data_.pointer.ptr_));
} else if (kind_ == BigIntKind) {
new (&data_.pointer) Pointer(runtime.cloneBigInt(other.data_.pointer.ptr_));
} else if (kind_ == StringKind) {
new (&data_.pointer) Pointer(runtime.cloneString(other.data_.pointer.ptr_));
} else if (kind_ >= ObjectKind) {
new (&data_.pointer) Pointer(runtime.cloneObject(other.data_.pointer.ptr_));
}
}
Value::~Value() {
if (kind_ >= PointerKind) {
data_.pointer.~Pointer();
}
}
bool Value::strictEquals(Runtime& runtime, const Value& a, const Value& b) {
if (a.kind_ != b.kind_) {
return false;
}
switch (a.kind_) {
case UndefinedKind:
case NullKind:
return true;
case BooleanKind:
return a.data_.boolean == b.data_.boolean;
case NumberKind:
return a.data_.number == b.data_.number;
case SymbolKind:
return runtime.strictEquals(
static_cast<const Symbol&>(a.data_.pointer),
static_cast<const Symbol&>(b.data_.pointer));
case BigIntKind:
return runtime.strictEquals(
static_cast<const BigInt&>(a.data_.pointer),
static_cast<const BigInt&>(b.data_.pointer));
case StringKind:
return runtime.strictEquals(
static_cast<const String&>(a.data_.pointer),
static_cast<const String&>(b.data_.pointer));
case ObjectKind:
return runtime.strictEquals(
static_cast<const Object&>(a.data_.pointer),
static_cast<const Object&>(b.data_.pointer));
}
return false;
}
bool Value::asBool() const {
if (!isBool()) {
throw JSINativeException(
"Value is " + kindToString(*this) + ", expected a boolean");
}
return getBool();
}
double Value::asNumber() const {
if (!isNumber()) {
throw JSINativeException(
"Value is " + kindToString(*this) + ", expected a number");
}
return getNumber();
}
Object Value::asObject(Runtime& rt) const& {
if (!isObject()) {
throw JSError(
rt, "Value is " + kindToString(*this, &rt) + ", expected an Object");
}
return getObject(rt);
}
Object Value::asObject(Runtime& rt) && {
if (!isObject()) {
throw JSError(
rt, "Value is " + kindToString(*this, &rt) + ", expected an Object");
}
auto ptr = data_.pointer.ptr_;
data_.pointer.ptr_ = nullptr;
return static_cast<Object>(ptr);
}
Symbol Value::asSymbol(Runtime& rt) const& {
if (!isSymbol()) {
throw JSError(
rt, "Value is " + kindToString(*this, &rt) + ", expected a Symbol");
}
return getSymbol(rt);
}
Symbol Value::asSymbol(Runtime& rt) && {
if (!isSymbol()) {
throw JSError(
rt, "Value is " + kindToString(*this, &rt) + ", expected a Symbol");
}
return std::move(*this).getSymbol(rt);
}
BigInt Value::asBigInt(Runtime& rt) const& {
if (!isBigInt()) {
throw JSError(
rt, "Value is " + kindToString(*this, &rt) + ", expected a BigInt");
}
return getBigInt(rt);
}
BigInt Value::asBigInt(Runtime& rt) && {
if (!isBigInt()) {
throw JSError(
rt, "Value is " + kindToString(*this, &rt) + ", expected a BigInt");
}
return std::move(*this).getBigInt(rt);
}
String Value::asString(Runtime& rt) const& {
if (!isString()) {
throw JSError(
rt, "Value is " + kindToString(*this, &rt) + ", expected a String");
}
return getString(rt);
}
String Value::asString(Runtime& rt) && {
if (!isString()) {
throw JSError(
rt, "Value is " + kindToString(*this, &rt) + ", expected a String");
}
return std::move(*this).getString(rt);
}
String Value::toString(Runtime& runtime) const {
Function toString = runtime.global().getPropertyAsFunction(runtime, "String");
return toString.call(runtime, *this).getString(runtime);
}
uint64_t BigInt::asUint64(Runtime& runtime) const {
if (!isUint64(runtime)) {
throw JSError(runtime, "Lossy truncation in BigInt64::asUint64");
}
return getUint64(runtime);
}
int64_t BigInt::asInt64(Runtime& runtime) const {
if (!isInt64(runtime)) {
throw JSError(runtime, "Lossy truncation in BigInt64::asInt64");
}
return getInt64(runtime);
}
Array Array::createWithElements(
Runtime& rt,
std::initializer_list<Value> elements) {
Array result(rt, elements.size());
size_t index = 0;
for (const auto& element : elements) {
result.setValueAtIndex(rt, index++, element);
}
return result;
}
std::vector<PropNameID> HostObject::getPropertyNames(Runtime&) {
return {};
}
Runtime::ScopeState* Runtime::pushScope() {
return nullptr;
}
void Runtime::popScope(ScopeState*) {}
JSError::JSError(Runtime& rt, Value&& value) {
setValue(rt, std::move(value));
}
JSError::JSError(Runtime& rt, std::string msg) : message_(std::move(msg)) {
try {
setValue(
rt,
callGlobalFunction(rt, "Error", String::createFromUtf8(rt, message_)));
} catch (const JSIException& ex) {
message_ = std::string(ex.what()) + " (while raising " + message_ + ")";
setValue(rt, String::createFromUtf8(rt, message_));
}
}
JSError::JSError(Runtime& rt, std::string msg, std::string stack)
: message_(std::move(msg)), stack_(std::move(stack)) {
try {
Object e(rt);
e.setProperty(rt, "message", String::createFromUtf8(rt, message_));
e.setProperty(rt, "stack", String::createFromUtf8(rt, stack_));
setValue(rt, std::move(e));
} catch (const JSIException& ex) {
setValue(rt, String::createFromUtf8(rt, ex.what()));
}
}
JSError::JSError(std::string what, Runtime& rt, Value&& value)
: JSIException(std::move(what)) {
setValue(rt, std::move(value));
}
JSError::JSError(Value&& value, std::string message, std::string stack)
: JSIException(message + "\n\n" + stack),
value_(std::make_shared<Value>(std::move(value))),
message_(std::move(message)),
stack_(std::move(stack)) {}
void JSError::setValue(Runtime& rt, Value&& value) {
value_ = std::make_shared<Value>(std::move(value));
if ((message_.empty() || stack_.empty()) && value_->isObject()) {
auto obj = value_->getObject(rt);
if (message_.empty()) {
try {
Value message = obj.getProperty(rt, "message");
if (!message.isUndefined() && !message.isString()) {
message = callGlobalFunction(rt, "String", message);
}
if (message.isString()) {
message_ = message.getString(rt).utf8(rt);
} else if (!message.isUndefined()) {
message_ = "String(e.message) is a " + kindToString(message, &rt);
}
} catch (const JSIException& ex) {
message_ = std::string("[Exception while creating message string: ") +
ex.what() + "]";
}
}
if (stack_.empty()) {
try {
Value stack = obj.getProperty(rt, "stack");
if (!stack.isUndefined() && !stack.isString()) {
stack = callGlobalFunction(rt, "String", stack);
}
if (stack.isString()) {
stack_ = stack.getString(rt).utf8(rt);
} else if (!stack.isUndefined()) {
stack_ = "String(e.stack) is a " + kindToString(stack, &rt);
}
} catch (const JSIException& ex) {
message_ = std::string("[Exception while creating stack string: ") +
ex.what() + "]";
}
}
}
if (message_.empty()) {
try {
if (value_->isString()) {
message_ = value_->getString(rt).utf8(rt);
} else {
Value message = callGlobalFunction(rt, "String", *value_);
if (message.isString()) {
message_ = message.getString(rt).utf8(rt);
} else {
message_ = "String(e) is a " + kindToString(message, &rt);
}
}
} catch (const JSIException& ex) {
message_ = std::string("[Exception while creating message string: ") +
ex.what() + "]";
}
}
if (stack_.empty()) {
stack_ = "no stack";
}
if (what_.empty()) {
what_ = message_ + "\n\n" + stack_;
}
}
JSIException::~JSIException() {}
JSINativeException::~JSINativeException() {}
JSError::~JSError() {}
} // namespace jsi
} // namespace facebook

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,108 @@
/*
* 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.
*/
#ifndef _WINDOWS
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <cerrno>
#include <cstdarg>
#include <stdexcept>
#include <jsi/jsilib.h>
namespace facebook {
namespace jsi {
namespace {
constexpr size_t kErrorBufferSize = 512;
__attribute__((format(printf, 1, 2))) void throwFormattedError(
const char* fmt,
...) {
char logBuffer[kErrorBufferSize];
va_list va_args;
va_start(va_args, fmt);
int result = vsnprintf(logBuffer, sizeof(logBuffer), fmt, va_args);
va_end(va_args);
if (result < 0) {
throw JSINativeException(
std::string("Failed to format error message: ") + fmt);
}
throw JSINativeException(logBuffer);
}
class ScopedFile {
public:
ScopedFile(const std::string& path)
: path_(path), fd_(::open(path.c_str(), O_RDONLY)) {
if (fd_ == -1) {
throwFormattedError(
"Could not open %s: %s", path.c_str(), strerror(errno));
}
}
~ScopedFile() {
::close(fd_);
}
size_t size() {
struct stat fileInfo;
if (::fstat(fd_, &fileInfo) == -1) {
throwFormattedError(
"Could not stat %s: %s", path_.c_str(), strerror(errno));
}
return fileInfo.st_size;
}
uint8_t* mmap(size_t size) {
void* result = ::mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd_, 0);
if (result == MAP_FAILED) {
throwFormattedError(
"Could not mmap %s: %s", path_.c_str(), strerror(errno));
}
return reinterpret_cast<uint8_t*>(result);
}
const std::string& path_;
const int fd_;
};
} // namespace
FileBuffer::FileBuffer(const std::string& path) {
ScopedFile file(path);
size_ = file.size();
data_ = file.mmap(size_);
}
FileBuffer::~FileBuffer() {
if (::munmap(data_, size_)) {
// terminate the program with pending exception
try {
throwFormattedError(
"Could not unmap memory (%p, %zu bytes): %s",
data_,
size_,
strerror(errno));
} catch (...) {
std::terminate();
}
}
}
} // namespace jsi
} // namespace facebook
#endif // !defined(_WINDOWS)

View File

@@ -0,0 +1,27 @@
/*
* 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.
*/
#ifdef _WINDOWS
#include <jsi/jsilib.h>
namespace facebook {
namespace jsi {
FileBuffer::FileBuffer(const std::string&) {
// TODO(T41045067) Implement this on Windows
throw new JSINativeException("FileBuffer is not implemented on Windows");
}
FileBuffer::~FileBuffer() {
assert(false && "FileBuffer is not implemented on Windows");
}
} // namespace jsi
} // namespace facebook
#endif //_WINDOWS

View File

@@ -0,0 +1,59 @@
/*
* 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 {
namespace jsi {
class FileBuffer : public Buffer {
public:
FileBuffer(const std::string& path);
~FileBuffer() override;
size_t size() const override {
return size_;
}
const uint8_t* data() const override {
return data_;
}
private:
size_t size_;
uint8_t* data_;
};
// A trivial implementation of PreparedJavaScript that simply stores the source
// buffer and URL.
class SourceJavaScriptPreparation final : public jsi::PreparedJavaScript,
public jsi::Buffer {
std::shared_ptr<const jsi::Buffer> buf_;
std::string sourceURL_;
public:
SourceJavaScriptPreparation(
std::shared_ptr<const jsi::Buffer> buf,
std::string sourceURL)
: buf_(std::move(buf)), sourceURL_(std::move(sourceURL)) {}
const std::string& sourceURL() const {
return sourceURL_;
}
size_t size() const override {
return buf_->size();
}
const uint8_t* data() const override {
return buf_->data();
}
};
} // namespace jsi
} // namespace facebook

File diff suppressed because it is too large Load Diff

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.
*/
#pragma once
#include <functional>
#include <memory>
#include <vector>
#include <gtest/gtest.h>
#include <jsi/jsi.h>
namespace facebook {
namespace jsi {
class Runtime;
using RuntimeFactory = std::function<std::unique_ptr<Runtime>()>;
std::vector<RuntimeFactory> runtimeGenerators();
class JSITestBase : public ::testing::TestWithParam<RuntimeFactory> {
public:
JSITestBase() : factory(GetParam()), runtime(factory()), rt(*runtime) {}
Value eval(const char* code) {
return rt.global().getPropertyAsFunction(rt, "eval").call(rt, code);
}
Function function(const std::string& code) {
return eval(("(" + code + ")").c_str()).getObject(rt).getFunction(rt);
}
bool checkValue(const Value& value, const std::string& jsValue) {
return function("function(value) { return value == " + jsValue + "; }")
.call(rt, std::move(value))
.getBool();
}
RuntimeFactory factory;
std::unique_ptr<Runtime> runtime;
Runtime& rt;
};
} // namespace jsi
} // namespace facebook

Some files were not shown because too many files have changed in this diff Show More