first commit

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

2
node_modules/expo-modules-core/.eslintrc.js generated vendored Normal file
View File

@@ -0,0 +1,2 @@
// @generated by expo-module-scripts
module.exports = require('expo-module-scripts/eslintrc.base.js');

1955
node_modules/expo-modules-core/CHANGELOG.md generated vendored Normal file

File diff suppressed because it is too large Load Diff

172
node_modules/expo-modules-core/ExpoModulesCore.podspec generated vendored Normal file
View File

@@ -0,0 +1,172 @@
require 'json'
absolute_react_native_path = ''
if !ENV['REACT_NATIVE_PATH'].nil?
absolute_react_native_path = File.expand_path(ENV['REACT_NATIVE_PATH'], Pod::Config.instance.project_root)
else
absolute_react_native_path = File.dirname(`node --print "require.resolve('react-native/package.json')"`)
end
unless defined?(install_modules_dependencies)
# `install_modules_dependencies` and `add_dependency` are defined in react_native_pods.rb.
# When running with `pod ipc spec`, these methods are not defined and we have to require manually.
require File.join(absolute_react_native_path, "scripts/react_native_pods")
end
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
reactNativeVersion = '0.0.0'
begin
reactNativeVersion = `node --print "require('#{absolute_react_native_path}/package.json').version"`
rescue
reactNativeVersion = '0.0.0'
end
reactNativeTargetVersion = reactNativeVersion.split('.')[1].to_i
use_hermes = ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == '1'
new_arch_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1'
new_arch_compiler_flags = '-DRCT_NEW_ARCH_ENABLED'
compiler_flags = get_folly_config()[:compiler_flags] + ' ' + "-DREACT_NATIVE_TARGET_VERSION=#{reactNativeTargetVersion}"
if use_hermes
compiler_flags << ' -DUSE_HERMES'
end
if new_arch_enabled
compiler_flags << ' ' << new_arch_compiler_flags
end
# List of features that are required by linked modules
coreFeatures = []
if defined?(Expo::PackagesConfig)
coreFeatures = Expo::PackagesConfig.instance.coreFeatures
end
# During resolution phase, it will always false as Pod::Config.instance.podfile is not yet set.
# However, for our use case, we only need to check this value during installation phase.
def Pod::hasWorklets()
begin
# Safely access Pod::Config.instance.podfile without initiating it
if Pod::Config.instance_variable_defined?(:@instance) && !Pod::Config.instance_variable_get(:@instance).nil?
config = Pod::Config.instance
# Saefly access podfile and its dependencies
if config.instance_variable_defined?(:@podfile)
podfile = config.instance_variable_get(:@podfile)
if podfile && podfile.respond_to?(:dependencies)
dependencies = podfile.dependencies.map(&:name)
return dependencies.include?('RNWorklets')
end
end
end
rescue
end
return false
end
shouldEnableWorkletsIntegration = hasWorklets()
workletsCppFlags = 'WORKLETS_ENABLED=0'
if shouldEnableWorkletsIntegration
workletsCppFlags = "WORKLETS_ENABLED=1 REACT_NATIVE_MINOR_VERSION=#{reactNativeTargetVersion}"
end
Pod::Spec.new do |s|
s.name = 'ExpoModulesCore'
s.version = package['version']
s.summary = package['description']
s.description = package['description']
s.license = package['license']
s.author = package['author']
s.homepage = package['homepage']
s.platforms = {
:ios => '15.1',
:osx => '11.0',
:tvos => '15.1'
}
s.swift_version = '6.0'
s.source = { git: 'https://github.com/expo/expo.git' }
s.static_framework = true
s.header_dir = 'ExpoModulesCore'
header_search_paths = []
if ENV['USE_FRAMEWORKS']
header_search_paths.concat([
# Transitive dependency of React-Core
'"${PODS_CONFIGURATION_BUILD_DIR}/React-jsinspectortracing/jsinspector_moderntracing.framework/Headers"',
'"${PODS_CONFIGURATION_BUILD_DIR}/React-jsinspectorcdp/jsinspector_moderncdp.framework/Headers"',
# Transitive dependencies of React-runtimescheduler
'"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimescheduler/React_runtimescheduler.framework/Headers"',
'"${PODS_CONFIGURATION_BUILD_DIR}/React-performancetimeline/React_performancetimeline.framework/Headers"',
'"${PODS_CONFIGURATION_BUILD_DIR}/React-rendererconsistency/React_rendererconsistency.framework/Headers"',
'"${PODS_CONFIGURATION_BUILD_DIR}/React-timing/React_timing.framework/Headers"',
'"${PODS_CONFIGURATION_BUILD_DIR}/React-debug/React_debug.framework/Headers"',
'"${PODS_CONFIGURATION_BUILD_DIR}/RCT-Folly/folly.framework/Headers"',
'"$(PODS_ROOT)/DoubleConversion"', # Folly includes double-conversion/double-conversion.h
# RCTHost.h is in React-RuntimeApple under ReactCommon subdirectory
'"${PODS_CONFIGURATION_BUILD_DIR}/React-RuntimeApple/React_RuntimeApple.framework/Headers"',
'"${PODS_CONFIGURATION_BUILD_DIR}/React-RuntimeCore/React_RuntimeCore.framework/Headers"',
'"${PODS_CONFIGURATION_BUILD_DIR}/React-jsitooling/JSITooling.framework/Headers"',
'"${PODS_CONFIGURATION_BUILD_DIR}/React-jserrorhandler/React_jserrorhandler.framework/Headers"',
])
if shouldEnableWorkletsIntegration
pods_root = Pod::Config.instance.project_pods_root
react_native_worklets_node_modules_dir = File.join(File.dirname(`cd "#{Pod::Config.instance.installation_root.to_s}" && node --print "require.resolve('react-native-worklets/package.json')"`), '..')
react_native_worklets_dir_absolute = File.join(react_native_worklets_node_modules_dir, 'react-native-worklets')
workletsPath = Pathname.new(react_native_worklets_dir_absolute).relative_path_from(pods_root).to_s
header_search_paths.concat([
"\"$(PODS_ROOT)/#{workletsPath}/apple\"",
"\"$(PODS_ROOT)/#{workletsPath}/Common/cpp\"",
])
end
end
# Swift/Objective-C compatibility
s.pod_target_xcconfig = {
'USE_HEADERMAP' => 'YES',
'DEFINES_MODULE' => 'YES',
'CLANG_CXX_LANGUAGE_STANDARD' => 'c++20',
'SWIFT_COMPILATION_MODE' => 'wholemodule',
'OTHER_SWIFT_FLAGS' => "$(inherited) #{new_arch_enabled ? new_arch_compiler_flags : ''}",
'HEADER_SEARCH_PATHS' => header_search_paths.join(' '),
'GCC_PREPROCESSOR_DEFINITIONS' => "$(inherited) #{workletsCppFlags} EXPO_MODULES_CORE_VERSION=" + package['version'],
}
s.user_target_xcconfig = {
"HEADER_SEARCH_PATHS" => [
'"${PODS_CONFIGURATION_BUILD_DIR}/ExpoModulesCore/Swift Compatibility Header"',
'"$(PODS_ROOT)/Headers/Private/Yoga"', # Expo.h -> ExpoModulesCore-umbrella.h -> Fabric ViewProps.h -> Private Yoga headers
],
}
if use_hermes
s.dependency 'hermes-engine'
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
else
s.dependency 'React-jsc'
end
s.dependency 'ExpoModulesJSI'
s.dependency 'React-Core'
s.dependency 'ReactCommon/turbomodule/core'
s.dependency 'React-NativeModulesApple'
s.dependency 'React-RCTFabric'
if shouldEnableWorkletsIntegration
s.dependency 'RNWorklets'
end
install_modules_dependencies(s)
s.source_files = 'ios/**/*.{h,m,mm,swift,cpp}', 'common/cpp/**/*.{h,cpp}'
s.exclude_files = ['ios/JSI', 'ios/Tests', 'common/cpp/JSI']
s.compiler_flags = compiler_flags
s.private_header_files = ['ios/**/*+Private.h', 'ios/**/Swift.h']
s.test_spec 'Tests' do |test_spec|
test_spec.dependency 'ExpoModulesTestCore'
test_spec.source_files = 'ios/Tests/**/*.{m,swift}'
end
end

68
node_modules/expo-modules-core/ExpoModulesJSI.podspec generated vendored Normal file
View File

@@ -0,0 +1,68 @@
require 'json'
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
use_hermes = ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == '1'
Pod::Spec.new do |s|
s.name = 'ExpoModulesJSI'
s.version = package['version']
s.summary = package['description']
s.description = package['description']
s.license = package['license']
s.author = package['author']
s.homepage = package['homepage']
s.platforms = {
:ios => '15.1',
:osx => '11.0',
:tvos => '15.1'
}
s.swift_version = '6.0'
s.source = { git: 'https://github.com/expo/expo.git' }
s.static_framework = true
s.header_dir = 'ExpoModulesJSI'
header_search_paths = []
if ENV['USE_FRAMEWORKS']
header_search_paths.concat([
# Transitive dependency of React-Core
'"${PODS_CONFIGURATION_BUILD_DIR}/React-jsinspectortracing/jsinspector_moderntracing.framework/Headers"',
'"${PODS_CONFIGURATION_BUILD_DIR}/React-jsinspectorcdp/jsinspector_moderncdp.framework/Headers"',
# Transitive dependencies of React-runtimescheduler
'"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimescheduler/React_runtimescheduler.framework/Headers"',
'"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers"',
'"${PODS_CONFIGURATION_BUILD_DIR}/React-performancetimeline/React_performancetimeline.framework/Headers"',
'"${PODS_CONFIGURATION_BUILD_DIR}/React-rendererconsistency/React_rendererconsistency.framework/Headers"',
'"${PODS_CONFIGURATION_BUILD_DIR}/React-timing/React_timing.framework/Headers"',
'"${PODS_CONFIGURATION_BUILD_DIR}/React-debug/React_debug.framework/Headers"',
'"${PODS_CONFIGURATION_BUILD_DIR}/RCT-Folly/folly.framework/Headers"',
'"${PODS_CONFIGURATION_BUILD_DIR}/fmt/fmt.framework/Headers"',
'"$(PODS_ROOT)/DoubleConversion"',
])
end
# Swift/Objective-C compatibility
s.pod_target_xcconfig = {
'USE_HEADERMAP' => 'YES',
'DEFINES_MODULE' => 'YES',
'HEADER_SEARCH_PATHS' => header_search_paths.join(' '),
}
if use_hermes
s.dependency 'hermes-engine'
else
s.dependency 'React-jsc'
end
s.dependency 'React-Core'
s.dependency 'ReactCommon'
s.dependency 'React-runtimescheduler'
s.source_files = ['ios/JSI/**/*.{h,m,mm,swift,cpp}', 'common/cpp/JSI/**/*.{h,cpp}']
s.exclude_files = ['ios/JSI/Tests']
s.private_header_files = ['ios/JSI/**/*+Private.h', 'ios/JSI/**/Swift.h']
s.test_spec 'Tests' do |test_spec|
test_spec.source_files = 'ios/JSI/Tests/**/*.{m,swift}'
end
end

98
node_modules/expo-modules-core/README.md generated vendored Normal file
View File

@@ -0,0 +1,98 @@
<p>
<a href="https://docs.expo.dev/modules/">
<img
src="../../.github/resources/expo-modules-core.svg"
alt="expo-modules-core"
height="64" />
</a>
</p>
The core of Expo Modules architecture.
# Installation in managed Expo projects
For [managed](https://docs.expo.dev/archive/managed-vs-bare/) Expo projects, please follow the installation instructions in the [API documentation for the latest stable release](#api-documentation). If you follow the link and there is no documentation available then this library is not yet usable within managed projects &mdash; it is likely to be included in an upcoming Expo SDK release.
# Installation in bare React Native projects
For bare React Native projects, you must ensure that you have [installed and configured the `expo` package](https://docs.expo.dev/bare/installing-expo-modules/) before continuing.
### Add the package to your npm dependencies
```
npm install expo-modules-core
```
### Configure for iOS
Run `npx pod-install` after installing the npm package.
### Configure for Android
No additional set up necessary.
# Importing native dependencies - autolinking
Many React Native libraries come with platform-specific (native) code. This native code has to be linked into the project and, in some cases, configured further. These actions require some modifications to the native project files. One of the steps that has to be done with the native configuration is to enable the autolinking mechanism that takes care of including any supported module's native code into the project. The following configuration is required:
### iOS
> Caution! After you have made the following changes you will need to run `pod install` again.
```ruby
# Podfile
require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods")
require File.join(File.dirname(`node --print "require.resolve('expo-modules-core/package.json')"`), "cocoapods.rb")
require File.join(File.dirname(`node --print "require.resolve('expo-modules-core/package.json')"`), "scripts/autolinking")
# ...
target "TargetName" do
use_unimodules!
config = use_native_modules!
use_react_native!(:path => config["reactNativePath"])
# ...
end
```
### Android
```groovy
// app/build.gradle
apply from: new File(["node", "--print", "require.resolve('expo-modules-core/package.json')"].execute(null, rootDir).text.trim(), "../gradle.groovy")
apply from: new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), "../react.gradle")
apply from: new File(["node", "--print", "require.resolve('expo-updates/package.json')"].execute(null, rootDir).text.trim(), "../scripts/create-manifest-android.gradle")
// ...
apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json')"].execute(null, rootDir).text.trim(), "../native_modules.gradle");
applyNativeModulesAppBuildGradle(project)
```
```groovy
// settings.gradle
apply from: new File(["node", "--print", "require.resolve('expo-modules-core/package.json')"].execute(null, rootDir).text.trim(), "../gradle.groovy");
includeUnimodulesProjects()
apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json')"].execute(null, rootDir).text.trim(), "../native_modules.gradle");
applyNativeModulesSettingsGradle(settings)
```
### Explanation
The scripts that are referenced in Gradle and Cocoapods are usually found the related package inside the project's `node_modules` directory. In the case of monorepos (such as Yarn workspaces) the project directory may not contain `node_modules` at all; instead, the modules are likely to be located at the root of the repository. In order to ensure that both cases are supported, we take advantage of the Node dependency resolution strategy. We invoke a subprocess that spawns the simple JavaScript snippet that tries to locate the desired npm package containing the script we need.
- On iOS, we use [`` ` ` `` (backtick)](https://stackoverflow.com/questions/3159945/running-command-line-commands-within-ruby-script) ([alternative reference](https://ruby-doc.org/core-3.0.2/Kernel.html#method-i-60)).
- On Android, we use [String[]#execute](<http://docs.groovy-lang.org/latest/html/groovy-jdk/java/lang/String[].html#execute()>) to obtain the results from the subprocess' stdout.
The Node process that is spawned runs [`require.resolve`](https://nodejs.org/dist/latest-v14.x/docs/api/modules.html#modules_require_resolve_request_options) to obtain the path to a module's `package.json`(if you look for the module location using solely module name, you'll be given the path to the file pointed by the `main` attribute from the `package.json` and we need to know the location of the module's root directory). We then assemble the path to the desired script and execute it.
You can read more about the scripts and libraries used in the examples above in their official READMEs.
# Contributing
Contributions are very welcome! Please refer to guidelines described in the [contributing guide](https://github.com/expo/expo#contributing).

14
node_modules/expo-modules-core/android/CMakeLists.txt generated vendored Normal file
View File

@@ -0,0 +1,14 @@
cmake_minimum_required(VERSION 3.16)
set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_CXX_STANDARD 20)
project(expo-modules-core)
include(${CMAKE_SOURCE_DIR}/cmake/variables.cmake)
include(${CMAKE_SOURCE_DIR}/cmake/common.cmake)
# defines expo-modules-jsi target
include(${CMAKE_SOURCE_DIR}/cmake/jsi.cmake)
# defines expo-modules-core target
include(${CMAKE_SOURCE_DIR}/cmake/main.cmake)

View File

@@ -0,0 +1,123 @@
class KotlinExpoModulesCorePlugin implements Plugin<Project> {
void apply(Project project) {
// For compatibility reasons the plugin needs to declare that it provides common build.gradle
// options for the modules
project.rootProject.ext.expoProvidesDefaultConfig = {
true
}
project.ext.safeExtGet = { prop, fallback ->
project.rootProject.ext.has(prop) ? project.rootProject.ext.get(prop) : fallback
}
project.buildscript {
project.ext.kotlinVersion = {
project.rootProject.ext.has("kotlinVersion")
? project.rootProject.ext.get("kotlinVersion")
: "2.0.21"
}
project.ext.kspVersion = {
def kspVersionsMap = [
"1.6.10": "1.6.10-1.0.4",
"1.6.21": "1.6.21-1.0.6",
"1.7.22": "1.7.22-1.0.8",
"1.8.0": "1.8.0-1.0.9",
"1.8.10": "1.8.10-1.0.9",
"1.8.20": "1.8.20-1.0.11",
"1.8.22": "1.8.22-1.0.11",
"1.9.23": "1.9.23-1.0.20",
"1.9.24": "1.9.24-1.0.20",
"2.0.21": "2.0.21-1.0.28"
]
project.rootProject.ext.has("kspVersion")
? project.rootProject.ext.get("kspVersion")
: kspVersionsMap.containsKey(project.ext.kotlinVersion())
? kspVersionsMap.get(project.ext.kotlinVersion())
: "2.0.21-1.0.28"
}
}
}
}
ext.applyKotlinExpoModulesCorePlugin = {
try {
// Tries to apply the kotlin-android plugin if the client project does not apply yet.
// On previous `applyKotlinExpoModulesCorePlugin`, it is inside the `project.buildscript` block.
// We cannot use `project.plugins.hasPlugin()` yet but only to try-catch instead.
apply plugin: 'kotlin-android'
} catch (e) {}
apply plugin: KotlinExpoModulesCorePlugin
}
// Apply JVM Toolchain version for KSP
ext.applyKspJvmToolchain = {
project.ksp {
kotlin.jvmToolchain(17)
}
}
// Setup build options that are common for all modules
ext.useDefaultAndroidSdkVersions = {
project.android {
compileSdkVersion project.ext.safeExtGet("compileSdkVersion", 36)
defaultConfig {
minSdkVersion project.ext.safeExtGet("minSdkVersion", 24)
targetSdkVersion project.ext.safeExtGet("targetSdkVersion", 36)
}
lintOptions {
abortOnError false
}
}
}
ext.useExpoPublishing = {
if (!project.plugins.hasPlugin('maven-publish')) {
apply plugin: 'maven-publish'
}
if (components.findByName("release") == null) {
return
}
project.android {
publishing {
singleVariant("release") {
withSourcesJar()
}
}
}
project.afterEvaluate {
publishing {
publications {
release(MavenPublication) {
from components.release
}
}
repositories {
maven {
url = mavenLocal().url
}
}
}
}
}
ext.useCoreDependencies = {
dependencies {
// Avoids cyclic dependencies
if (!project.project.name.startsWith("expo-modules-core")) {
implementation project.project(':expo-modules-core')
}
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${project.ext.kotlinVersion()}"
}
}
ext.boolish = { value ->
return value.toString().toBoolean()
}

317
node_modules/expo-modules-core/android/build.gradle generated vendored Normal file
View File

@@ -0,0 +1,317 @@
import com.android.build.gradle.tasks.ExternalNativeBuildJsonTask
import expo.modules.plugin.gradle.ExpoModuleExtension
import groovy.json.JsonSlurper
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
buildscript {
// List of features that are required by linked modules
def coreFeatures = project.findProperty("coreFeatures") ?: []
ext.shouldIncludeCompose = coreFeatures.contains("compose")
repositories {
mavenCentral()
}
dependencies {
if (shouldIncludeCompose) {
classpath("org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin:${kotlinVersion}")
}
classpath("org.apache.commons:commons-text:1.14.0")
}
}
apply plugin: 'com.android.library'
apply plugin: 'expo-module-gradle-plugin'
if (shouldIncludeCompose) {
apply plugin: 'org.jetbrains.kotlin.plugin.compose'
}
group = 'host.exp.exponent'
version = '55.0.13'
def isExpoModulesCoreTests = {
Gradle gradle = getGradle()
String tskReqStr = gradle.getStartParameter().getTaskRequests().toString()
if (tskReqStr =~ /:expo-modules-core:connected\w*AndroidTest/) {
def androidTests = project.file("src/androidTest")
return androidTests.exists() && androidTests.isDirectory()
}
return false
}.call()
def isTests = {
Gradle gradle = getGradle()
String tskReqStr = gradle.getStartParameter().getTaskRequests().toString()
return tskReqStr =~ /connected\w*AndroidTest/
}.call()
def expoModuleExtension = project.extensions.getByType(ExpoModuleExtension)
def reactNativeArchitectures() {
def value = project.getProperties().get("reactNativeArchitectures")
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}
// HERMES
def USE_HERMES = findProperty("hermesEnabled") ?: true
// Currently the needs for hermes/jsc are only for androidTest, so we turn on this flag only when `isExpoModulesCoreTests` is true
USE_HERMES = USE_HERMES && isExpoModulesCoreTests
// END HERMES
def shouldTurnWarningsIntoErrors = findProperty("EXPO_TURN_WARNINGS_INTO_ERRORS") == "true"
def enableWorkletsIntegration = !isTests
def workletsProject = enableWorkletsIntegration ? findProject(":react-native-worklets") : null
if (workletsProject != null) {
evaluationDependsOn(workletsProject.path)
afterEvaluate {
println("Linking react-native-worklets native libs into expo-modules-core build tasks")
println(workletsProject.tasks.getByName("mergeDebugNativeLibs"))
println(workletsProject.tasks.getByName("mergeReleaseNativeLibs"))
tasks.getByName("buildCMakeDebug").dependsOn(workletsProject.tasks.getByName("mergeDebugNativeLibs"))
tasks.getByName("buildCMakeRelWithDebInfo").dependsOn(workletsProject.tasks.getByName("mergeReleaseNativeLibs"))
}
}
def reactNativeWorkletsRootDir = workletsProject?.projectDir?.parentFile
expoModule {
canBePublished false
}
android {
if (rootProject.hasProperty("ndkPath")) {
ndkPath rootProject.ext.ndkPath
}
if (rootProject.hasProperty("ndkVersion")) {
ndkVersion rootProject.ext.ndkVersion
}
namespace "expo.modules"
defaultConfig {
consumerProguardFiles 'proguard-rules.pro'
versionCode 1
versionName "55.0.13"
buildConfigField "String", "EXPO_MODULES_CORE_VERSION", "\"${versionName}\""
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", "true"
testInstrumentationRunner "expo.modules.TestRunner"
externalNativeBuild {
cmake {
def cppArguments = [
"-DANDROID_STL=c++_shared",
"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON",
"-DREACT_NATIVE_DIR=${expoModuleExtension.reactNativeDir}",
"-DREACT_NATIVE_TARGET_VERSION=${expoModuleExtension.reactNativeVersion.minor}",
"-DUSE_HERMES=${USE_HERMES}",
"-DUNIT_TEST=${isExpoModulesCoreTests}"
]
if (reactNativeWorkletsRootDir != null) {
cppArguments += "-DREACT_NATIVE_WORKLETS_DIR=${reactNativeWorkletsRootDir.absolutePath}"
}
abiFilters(*reactNativeArchitectures())
arguments(*cppArguments)
}
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
buildFeatures {
buildConfig true
prefab true
compose shouldIncludeCompose
}
packagingOptions {
// Gradle will add cmake target dependencies into packaging.
// Theses files are intermediated linking files to build modules-core and should not be in final package.
def sharedLibraries = [
"**/libc++_shared.so",
"**/libfabricjni.so",
"**/libfbjni.so",
"**/libfolly_json.so",
"**/libfolly_runtime.so",
"**/libglog.so",
"**/libhermesvm.so",
"**/libjscexecutor.so",
"**/libjsi.so",
"**/libreactnative.so",
"**/libreactnativejni.so",
"**/libreact_debug.so",
"**/libreact_nativemodule_core.so",
"**/libreact_utils.so",
"**/libreact_render_debug.so",
"**/libreact_render_graphics.so",
"**/libreact_render_core.so",
"**/libreact_render_componentregistry.so",
"**/libreact_render_mapbuffer.so",
"**/librrc_view.so",
"**/libruntimeexecutor.so",
"**/libyoga.so",
]
// Required or mockk will crash
resources {
excludes += [
"META-INF/LICENSE.md",
"META-INF/LICENSE-notice.md"
]
}
// In android (instrumental) tests, we want to package all so files to enable our JSI functionality.
// Otherwise, those files should be excluded, because will be loaded by the application.
if (isExpoModulesCoreTests) {
pickFirsts += sharedLibraries
} else {
excludes += sharedLibraries
}
}
sourceSets {
main {
java {
if (shouldIncludeCompose) {
srcDirs += 'src/compose'
} else {
srcDirs += 'src/withoutCompose'
}
}
}
}
testOptions {
unitTests.includeAndroidResources = true
unitTests.all { test ->
testLogging {
outputs.upToDateWhen { false }
events "passed", "failed", "skipped", "standardError"
showCauses true
showExceptions true
showStandardStreams true
}
}
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${kotlinVersion}"
implementation "org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}"
implementation 'androidx.annotation:annotation:1.9.1'
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2"
api "androidx.core:core-ktx:1.17.0"
if (shouldIncludeCompose) {
implementation 'androidx.compose.foundation:foundation-android:1.7.6'
}
implementation("androidx.tracing:tracing-ktx:1.2.0")
implementation 'com.facebook.react:react-android'
compileOnly 'com.facebook.fbjni:fbjni:0.5.1'
if (workletsProject != null) {
implementation workletsProject
}
testImplementation 'androidx.test:core:1.7.0'
testImplementation 'junit:junit:4.13.2'
testImplementation 'io.mockk:mockk:1.14.9'
testImplementation "com.google.truth:truth:1.4.5"
testImplementation "org.robolectric:robolectric:4.16.1"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2"
testImplementation "org.json:json:20250517"
androidTestImplementation 'androidx.test:runner:1.7.0'
androidTestImplementation 'androidx.test:core:1.7.0'
androidTestImplementation 'androidx.test:rules:1.7.0'
androidTestImplementation "io.mockk:mockk-android:1.14.9"
androidTestImplementation "com.google.truth:truth:1.4.5"
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2"
if (isExpoModulesCoreTests) {
if (USE_HERMES) {
compileOnly "com.facebook.react:hermes-android"
} else {
compileOnly "org.webkit:android-jsc:+"
}
}
}
if (shouldTurnWarningsIntoErrors) {
tasks.withType(JavaCompile) configureEach {
options.compilerArgs << "-Werror" << "-Xlint:all" << '-Xlint:-serial' << '-Xlint:-rawtypes'
}
tasks.withType(KotlinCompile) configureEach {
compilerOptions.allWarningsAsErrors = true
}
}
// Generates the PCH file during sync if it doesn't exist yet
def generatePCHTask = tasks.register("generatePCH") {
def configureTaskName = "configureCMakeDebug"
dependsOn(configureTaskName)
doLast {
reactNativeArchitectures().each { abi ->
def configureTaskNameForAbi = configureTaskName + "[" + abi + "]"
ExternalNativeBuildJsonTask configureTask = tasks.named(configureTaskNameForAbi).get() as ExternalNativeBuildJsonTask
// Gets CxxModel for the given ABI
File cxxBuildFolder = configureTask.abi.cxxBuildFolder
// Gets compile_commands.json file to find the command to generate the PCH file
File compileCommandsFile = new File(cxxBuildFolder, "compile_commands.json")
if (!compileCommandsFile.exists()) {
return
}
def parsedJson = new JsonSlurper().parseText(compileCommandsFile.text)
for (int i = 0; i < parsedJson.size(); i++) {
def commandObj = parsedJson[i]
def path = commandObj.file
if (!path.endsWith("cmake_pch.hxx.cxx")) {
continue
}
def generatedFilePath = path.substring(0, path.length() - ".cxx".length()) + ".pch"
// Checks if the file already exists, and skip if so
if (new File(generatedFilePath).exists()) {
continue
}
def tokenizer = new org.apache.commons.text.StringTokenizer(commandObj.command, " ")
def tokens = tokenizer.tokenList
def workingDirFile = new File(commandObj.directory)
providers.exec {
workingDir(providers.provider { workingDirFile }.get())
commandLine(tokens)
}.getResult().get().assertNormalExitValue()
}
}
}
}
// This task will run on the IDE project sync, ensuring the PCH file is generated early enough
tasks.register("prepareKotlinBuildScriptModel") {
dependsOn(generatePCHTask)
}

View File

@@ -0,0 +1,37 @@
add_library(EXPO_COMMON INTERFACE)
target_precompile_headers(
EXPO_COMMON
INTERFACE
${CMAKE_SOURCE_DIR}/src/main/cpp/ExpoHeader.pch
)
target_compile_options(
EXPO_COMMON
INTERFACE
--std=c++20
${OPTIMIZATION_FLAGS}
-frtti
-fexceptions
-Wall
-fstack-protector-all
-DUSE_HERMES=${USE_HERMES_INT}
-DUNIT_TEST=${UNIT_TEST_INT}
-DIS_NEW_ARCHITECTURE_ENABLED=1
-DRN_FABRIC_ENABLED=1
-DRN_SERIALIZABLE_STATE=1
${folly_FLAGS}
${ADDITIONAL_CXX_FLAGS}
)
target_link_libraries(
EXPO_COMMON
INTERFACE
ReactAndroid::jsi
fbjni::fbjni
ReactAndroid::reactnative
)
function(use_expo_common target_name)
target_link_libraries(${target_name} PRIVATE EXPO_COMMON)
endfunction()

18
node_modules/expo-modules-core/android/cmake/jsi.cmake generated vendored Normal file
View File

@@ -0,0 +1,18 @@
file(GLOB common_sources_jsi "${COMMON_DIR}/JSI/*.cpp")
file(GLOB android_sources_jsi "${ANDROID_SRC_DIR}/jsi/*.cpp")
add_library(
expo-modules-jsi
STATIC
${common_sources_jsi}
${android_sources_jsi}
)
use_expo_common(expo-modules-jsi)
target_include_directories(
expo-modules-jsi
PUBLIC
"${COMMON_DIR}/JSI"
"${ANDROID_SRC_DIR}/jsi"
)

View File

@@ -0,0 +1,92 @@
file(
GLOB
common_sources
"${COMMON_DIR}/*.cpp"
"${COMMON_DIR}/fabric/*.cpp"
)
set(main_dir ${ANDROID_SRC_DIR}/main/cpp)
file(
GLOB
sources_android
"${main_dir}/*.cpp"
"${main_dir}/types/*.cpp"
"${main_dir}/javaclasses/*.cpp"
"${main_dir}/decorators/*.cpp"
"${main_dir}/installers/*.cpp"
"${main_dir}/worklets/*.cpp"
)
file(GLOB fabric_andorid_sources "${ANDROID_SRC_DIR}/fabric/*.cpp")
add_library(
expo-modules-core
SHARED
${common_sources}
${sources_android}
${fabric_andorid_sources}
)
use_expo_common(expo-modules-core)
target_include_directories(
expo-modules-core
PRIVATE
${REACT_NATIVE_INTERFACE_INCLUDE_DIRECTORIES}/react
${REACT_NATIVE_INTERFACE_INCLUDE_DIRECTORIES}/react/fabric
# header only imports from turbomodule, e.g. CallInvokerHolder.h
"${REACT_NATIVE_DIR}/ReactAndroid/src/main/jni/react/turbomodule"
"${ANDROID_SRC_DIR}/fabric"
"${COMMON_DIR}"
"${COMMON_DIR}/fabric"
)
target_compile_options(
expo-modules-core
PRIVATE
${WORKLETS_INTEGRATION_COMPILE_OPTIONS}
)
if (REACT_NATIVE_WORKLETS_DIR)
target_include_directories(
expo-modules-core
PRIVATE
"${REACT_NATIVE_DIR}/ReactCommon"
"${REACT_NATIVE_DIR}/ReactCommon/jsiexecutor"
"${REACT_NATIVE_WORKLETS_DIR}/Common/cpp"
"${REACT_NATIVE_WORKLETS_DIR}/android/src/main/cpp"
)
endif ()
target_link_libraries(
expo-modules-core
PRIVATE
${LOG_LIB}
android
${JSEXECUTOR_LIB}
${NEW_ARCHITECTURE_DEPENDENCIES}
expo-modules-jsi
)
if (REACT_NATIVE_WORKLETS_DIR)
add_library(worklets SHARED IMPORTED)
if (${CMAKE_BUILD_TYPE} MATCHES "Debug")
set(BUILD_TYPE "debug")
else ()
set(BUILD_TYPE "release")
endif ()
set_target_properties(
worklets
PROPERTIES
IMPORTED_LOCATION
"${REACT_NATIVE_WORKLETS_DIR}/android/build/intermediates/cmake/${BUILD_TYPE}/obj/${ANDROID_ABI}/libworklets.so"
)
target_link_libraries(
expo-modules-core
PRIVATE
worklets
)
endif ()

View File

@@ -0,0 +1,52 @@
macro(createVarAsBoolToInt name value)
if (${value})
set(${name} "1")
else ()
set(${name} "0")
endif ()
endmacro()
createVarAsBoolToInt(USE_HERMES_INT ${USE_HERMES})
createVarAsBoolToInt(UNIT_TEST_INT ${UNIT_TEST})
set(ANDROID_SRC_DIR ${CMAKE_SOURCE_DIR}/src)
set(COMMON_DIR ${CMAKE_SOURCE_DIR}/../common/cpp)
include("${REACT_NATIVE_DIR}/ReactAndroid/cmake-utils/folly-flags.cmake")
find_package(ReactAndroid REQUIRED CONFIG)
find_package(fbjni REQUIRED CONFIG)
find_library(LOG_LIB log)
get_target_property(
REACT_NATIVE_INTERFACE_INCLUDE_DIRECTORIES
ReactAndroid::reactnative
INTERFACE_INCLUDE_DIRECTORIES
)
set(ADDITIONAL_CXX_FLAGS -DREACT_NATIVE_TARGET_VERSION=${REACT_NATIVE_TARGET_VERSION})
# REACT_NATIVE_MINOR_VERSION is used by worklets headers
set(ADDITIONAL_CXX_FLAGS ${ADDITIONAL_CXX_FLAGS} -DREACT_NATIVE_MINOR_VERSION=${REACT_NATIVE_TARGET_VERSION})
set(OPTIMIZATION_FLAGS "-O2")
if (${NATIVE_DEBUG})
set(ADDITIONAL_CXX_FLAGS "${ADDITIONAL_CXX_FLAGS} -g")
set(OPTIMIZATION_FLAGS "-O0")
endif ()
if (${UNIT_TEST})
if (${USE_HERMES})
find_package(hermes-engine REQUIRED CONFIG)
set(JSEXECUTOR_LIB hermes-engine::hermesvm)
else ()
set(JSEXECUTOR_LIB ReactAndroid::jscexecutor)
endif ()
else ()
set(JSEXECUTOR_LIB "")
endif ()
if (REACT_NATIVE_WORKLETS_DIR)
set(WORKLETS_INTEGRATION_COMPILE_OPTIONS "-DWORKLETS_ENABLED=1")
else ()
set(WORKLETS_INTEGRATION_COMPILE_OPTIONS "")
endif ()

View File

@@ -0,0 +1,45 @@
-keep @expo.modules.core.interfaces.DoNotStrip class *
-keepclassmembers class * {
@expo.modules.core.interfaces.DoNotStrip *;
}
-keep class * implements expo.modules.kotlin.records.Record {
*;
}
-keep class * extends expo.modules.kotlin.sharedobjects.SharedObject
-keep enum * implements expo.modules.kotlin.types.Enumerable {
*;
}
-keepnames class kotlin.Pair
-keep,allowoptimization,allowobfuscation class * extends expo.modules.kotlin.modules.Module {
public <init>();
public expo.modules.kotlin.modules.ModuleDefinitionData definition();
}
-keepclassmembers class * implements expo.modules.kotlin.views.ExpoView {
public <init>(android.content.Context);
public <init>(android.content.Context, expo.modules.kotlin.AppContext);
}
-keepclassmembers class * {
expo.modules.kotlin.viewevent.ViewEventCallback *;
}
-keepclassmembers class * {
expo.modules.kotlin.viewevent.ViewEventDelegate *;
}
-keep class * implements expo.modules.kotlin.views.ComposeProps {
*;
}
-keepnames class * implements expo.modules.kotlin.views.ExpoView {
*;
}
-keep interface expo.modules.kotlin.services.Service
-keep class * implements expo.modules.kotlin.services.Service {
<init>(...);
}

View File

@@ -0,0 +1,37 @@
package expo.modules.kotlin.views
import android.content.res.Resources
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import java.util.EnumSet
enum class Direction {
HORIZONTAL,
VERTICAL
}
@Composable
fun AutoSizingComposable(shadowNodeProxy: ShadowNodeProxy, axis: EnumSet<Direction> = EnumSet.allOf(Direction::class.java), content: @Composable () -> Unit) {
Layout(
content = content,
modifier = Modifier.fillMaxSize(),
measurePolicy = { measurables, constraints ->
val measurable = measurables.first()
val minIntrinsicWidth = measurable.maxIntrinsicWidth(constraints.minHeight)
val minIntrinsicHeight = measurable.minIntrinsicHeight(minIntrinsicWidth)
val intrinsicWidth = minIntrinsicWidth.toDouble() / Resources.getSystem().displayMetrics.density
val intrinsicHeight = minIntrinsicHeight.toDouble() / Resources.getSystem().displayMetrics.density
val width: Double = if (axis.contains(Direction.HORIZONTAL)) intrinsicWidth else Double.NaN
val height: Double = if (axis.contains(Direction.VERTICAL)) intrinsicHeight else Double.NaN
shadowNodeProxy.setViewSize(width, height)
val placeable = measurable.measure(constraints)
layout(placeable.measuredWidth, placeable.measuredHeight) {
placeable.place(0, 0)
}
}
)
}

View File

@@ -0,0 +1,7 @@
package expo.modules.kotlin.views
/**
* A marker interface for props classes that are used to pass data to Compose views.
* Needed for the R8 to not remove needed signatures that are used to receive prop types.
*/
interface ComposeProps

View File

@@ -0,0 +1,54 @@
package expo.modules.kotlin.views
import android.view.View
import androidx.compose.runtime.MutableState
import com.facebook.react.bridge.Dynamic
import expo.modules.kotlin.AppContext
import expo.modules.kotlin.exception.PropSetException
import expo.modules.kotlin.exception.exceptionDecorator
import expo.modules.kotlin.logger
import expo.modules.kotlin.types.AnyType
import kotlin.reflect.KProperty1
import kotlin.reflect.full.instanceParameter
import kotlin.reflect.full.memberFunctions
class ComposeViewProp(
name: String,
anyType: AnyType,
val property: KProperty1<*, *>
) : AnyViewProp(name, anyType) {
@Suppress("UNCHECKED_CAST")
override fun set(prop: Dynamic, onView: View, appContext: AppContext?) {
exceptionDecorator({
PropSetException(name, onView::class, it)
}) {
val props = (onView as ExpoComposeView<*>).props ?: return@exceptionDecorator
if (onView is ComposeFunctionHolder<*>) {
// Use current props state, not the initial props instance
val currentProps = onView.propsMutableState.value
val copy = currentProps::class.memberFunctions.firstOrNull { it.name == "copy" }
if (copy == null) {
logger.warn("⚠️ Props are not a data class with default values for all properties, cannot set prop $name dynamically.")
return@exceptionDecorator
}
val instanceParam = copy.instanceParameter!!
val newPropParam = copy.parameters.firstOrNull { it.name == name } ?: return@exceptionDecorator
val result = copy.callBy(mapOf(instanceParam to currentProps, newPropParam to type.convert(prop, appContext)))
// Set the new props instance back to the onView
(onView.propsMutableState as MutableState<Any?>).value = result
return@exceptionDecorator
}
val mutableState = property.getter.call(props)
if (mutableState is MutableState<*>) {
(mutableState as MutableState<Any?>).value = type.convert(prop, appContext)
} else {
logger.warn("⚠️ Property $name is not a MutableState in ${onView::class.java}")
}
}
}
override val isNullable: Boolean = anyType.kType.isMarkedNullable
}

View File

@@ -0,0 +1,40 @@
package expo.modules.kotlin.views
import android.annotation.SuppressLint
import android.view.View
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.facebook.react.uimanager.PixelUtil.pxToDp
import expo.modules.kotlin.AppContext
/**
* Marks a view as capable of crossing the Jetpack Compose -> React Native boundary.
*/
interface RNHostViewInterface {
var matchContents: Boolean
}
/**
* An ExpoComposeView for [AndroidView] wrapping with existing view
*/
@SuppressLint("ViewConstructor")
internal class ExpoComposeAndroidView(
private val view: View,
appContext: AppContext
) : ExpoComposeView<ComposeProps>(view.context, appContext), RNHostViewInterface {
override var matchContents = false
@Composable
override fun ComposableScope.Content() {
AndroidView(
factory = { view },
modifier = Modifier.size(
view.width.toFloat().pxToDp().dp,
view.height.toFloat().pxToDp().dp
)
)
}
}

View File

@@ -0,0 +1,254 @@
package expo.modules.kotlin.views
import android.annotation.SuppressLint
import android.content.Context
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.RowScope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.RecomposeScope
import androidx.compose.runtime.currentRecomposeScope
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.core.view.size
import expo.modules.kotlin.AppContext
import expo.modules.kotlin.viewevent.CoalescingKey
import expo.modules.kotlin.viewevent.EventDispatcher
import expo.modules.kotlin.viewevent.ViewEvent
import expo.modules.kotlin.viewevent.ViewEventDelegate
data class ComposableScope(
val rowScope: RowScope? = null,
val columnScope: ColumnScope? = null,
val boxScope: BoxScope? = null,
val nestedScrollConnection: NestedScrollConnection? = null
)
inline fun ComposableScope.withIf(
condition: Boolean,
block: ComposableScope.() -> ComposableScope
): ComposableScope {
return if (condition) block() else this
}
fun ComposableScope.with(rowScope: RowScope?): ComposableScope {
return this.copy(rowScope = rowScope)
}
fun ComposableScope.with(columnScope: ColumnScope?): ComposableScope {
return this.copy(columnScope = columnScope)
}
fun ComposableScope.with(boxScope: BoxScope?): ComposableScope {
return this.copy(boxScope = boxScope)
}
fun ComposableScope.with(nestedScrollConnection: NestedScrollConnection?): ComposableScope {
return this.copy(nestedScrollConnection = nestedScrollConnection)
}
/**
* A base class that should be used by compose views.
*/
abstract class ExpoComposeView<T : ComposeProps>(
context: Context,
appContext: AppContext,
private val withHostingView: Boolean = false
) : ExpoView(context, appContext) {
open val props: T? = null
protected var recomposeScope: RecomposeScope? = null
private val globalEvent = ViewEvent<Pair<String, Map<String, Any?>>>(GLOBAL_EVENT_NAME, this, null)
/**
* A global event dispatcher
*/
val globalEventDispatcher: (String, Map<String, Any?>) -> Unit = { name, params ->
globalEvent.invoke(Pair(name, params))
}
@Composable
abstract fun ComposableScope.Content()
override val shouldUseAndroidLayout = withHostingView
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
// In case of issues there's an alternative solution in previous commits at https://github.com/expo/expo/pull/33759
if (shouldUseAndroidLayout && !isAttachedToWindow) {
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec)
return
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
// Makes sure the child ComposeView is sticky with the current hosting view
if (withHostingView) {
for (i in 0 until childCount) {
val child = getChildAt(i)
if (child is ComposeView) {
val offsetX = paddingLeft
val offsetY = paddingRight
child.layout(offsetX, offsetY, offsetX + width, offsetY + height)
}
}
}
}
@Composable
fun Children(composableScope: ComposableScope?) {
recomposeScope = currentRecomposeScope
for (index in 0..<this.size) {
val child = getChildAt(index) as? ExpoComposeView<*> ?: continue
with(composableScope ?: ComposableScope()) {
with(child) {
Content()
}
}
}
}
@Composable
fun Children(composableScope: ComposableScope?, filter: (child: ExpoComposeView<*>) -> Boolean) {
recomposeScope = currentRecomposeScope
for (index in 0..<this.size) {
val child = getChildAt(index) as? ExpoComposeView<*> ?: continue
if (!filter(child)) {
continue
}
with(composableScope ?: ComposableScope()) {
with(child) {
Content()
}
}
}
}
@Composable
fun Child(composableScope: ComposableScope, index: Int) {
recomposeScope = currentRecomposeScope
val child = getChildAt(index) as? ExpoComposeView<*> ?: return
with(composableScope) {
with(child) {
Content()
}
}
}
@Composable
fun Child(index: Int) {
Child(ComposableScope(), index)
}
init {
if (withHostingView) {
addComposeView()
} else {
this.visibility = GONE
this.setWillNotDraw(true)
}
}
private fun addComposeView() {
val composeView = ComposeView(context).also {
it.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
it.setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
it.setContent {
with(ComposableScope()) {
Content()
}
}
it.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
it.disposeComposition()
}
override fun onViewDetachedFromWindow(v: View) = Unit
})
}
addView(composeView)
}
override fun addView(child: View, index: Int, params: ViewGroup.LayoutParams) {
val view = if (child !is ExpoComposeView<*> && child !is ComposeView && this !is RNHostViewInterface) {
ExpoComposeAndroidView(child, appContext)
} else {
child
}
super.addView(view, index, params)
}
override fun onViewAdded(child: View?) {
super.onViewAdded(child)
recomposeScope?.invalidate()
}
override fun onViewRemoved(child: View?) {
super.onViewRemoved(child)
recomposeScope?.invalidate()
}
}
/**
* A composable DSL scope that wraps an [ExpoComposeView] to provide syntax sugar.
*
* This scope allows defining view content using a functional, DSL-style API
* without creating a dedicated subclass of [ExpoComposeView].
*/
class FunctionalComposableScope(
val view: ComposeFunctionHolder<*>,
val composableScope: ComposableScope
) {
val appContext = view.appContext
val globalEventDispatcher = view.globalEventDispatcher
@Composable
fun Child(composableScope: ComposableScope, index: Int) {
view.Child(composableScope, index)
}
@Composable
fun Child(index: Int) {
view.Child(index)
}
@Composable
fun Children(composableScope: ComposableScope?) {
view.Children(composableScope)
}
@Composable
fun Children(composableScope: ComposableScope?, filter: (child: ExpoComposeView<*>) -> Boolean) {
view.Children(composableScope, filter)
}
inline fun <reified T> EventDispatcher(noinline coalescingKey: CoalescingKey<T>? = null): ViewEventDelegate<T> {
return view.EventDispatcher<T>(coalescingKey)
}
}
@SuppressLint("ViewConstructor")
class ComposeFunctionHolder<Props : ComposeProps>(
context: Context,
appContext: AppContext,
override val name: String,
private val composableContent: @Composable FunctionalComposableScope.(props: Props) -> Unit,
override val props: Props
) : ExpoComposeView<Props>(context, appContext), ViewFunctionHolder {
val propsMutableState = mutableStateOf(props)
@Composable
override fun ComposableScope.Content() {
val props by propsMutableState
with(FunctionalComposableScope(this@ComposeFunctionHolder, this@Content)) {
composableContent(props)
}
}
}

View File

@@ -0,0 +1,102 @@
@file:Suppress("FunctionName")
package expo.modules.kotlin.views
import androidx.compose.runtime.Composable
import expo.modules.kotlin.modules.DefinitionMarker
import expo.modules.kotlin.modules.InternalModuleDefinitionBuilder
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.types.AnyType
import expo.modules.kotlin.types.LazyKType
import expo.modules.kotlin.views.decorators.UseCSSProps
import kotlin.reflect.KClass
import kotlin.reflect.full.createInstance
import kotlin.reflect.full.memberProperties
import kotlin.reflect.typeOf
/**
* The name for the global event dispatcher
*/
internal const val GLOBAL_EVENT_NAME = "onGlobalEvent"
open class ModuleDefinitionBuilderWithCompose(
module: Module? = null
) : InternalModuleDefinitionBuilder(module) {
/**
* Creates the view manager definition that scopes other view-related definitions.
* Also collects all compose view props and generates setters.
*/
@JvmName("ComposeView")
inline fun <reified T : ExpoComposeView<P>, reified P : Any> View(viewClass: KClass<T>, body: ViewDefinitionBuilder<T>.() -> Unit = {}) {
val viewDefinitionBuilder = ViewDefinitionBuilder(viewClass, LazyKType(classifier = T::class, kTypeProvider = { typeOf<T>() }))
P::class.memberProperties.forEach { prop ->
val kType = prop.returnType.arguments.first().type
if (kType != null && viewDefinitionBuilder.props[prop.name] == null) {
viewDefinitionBuilder.props[prop.name] = ComposeViewProp(prop.name, AnyType(kType), prop)
}
}
viewDefinitionBuilder.UseCSSProps()
body.invoke(viewDefinitionBuilder)
registerViewDefinition(viewDefinitionBuilder.build())
}
@JvmName("ComposeView")
inline fun <reified Props : ComposeProps> View(
name: String,
events: ComposeViewFunctionDefinitionBuilder<Props>.() -> Unit = {},
noinline viewFunction: @Composable FunctionalComposableScope.(props: Props) -> Unit
) {
val definitionBuilder = ComposeViewFunctionDefinitionBuilder(name, Props::class, viewFunction)
events.invoke(definitionBuilder)
registerViewDefinition(definitionBuilder.build())
}
}
@DefinitionMarker
class ComposeViewFunctionDefinitionBuilder<Props : ComposeProps>(
val name: String,
val propsClass: KClass<Props>,
val viewFunction: @Composable FunctionalComposableScope.(props: Props) -> Unit
) {
private var callbacksDefinition: CallbacksDefinition = CallbacksDefinition(arrayOf(GLOBAL_EVENT_NAME))
fun build(): ViewManagerDefinition {
return ViewManagerDefinition(
name = name,
viewFactory = { context, appContext ->
val instance: Props = try {
propsClass.createInstance()
} catch (e: Exception) {
throw IllegalStateException("Could not instantiate props instance of $name compose component.", e)
}
ComposeFunctionHolder(context, appContext, name, viewFunction, instance)
},
callbacksDefinition = callbacksDefinition,
viewType = ComposeFunctionHolder::class.java,
props = propsClass.memberProperties.associate { prop ->
val kType = prop.returnType
prop.name to ComposeViewProp(prop.name, AnyType(kType), prop)
}
)
}
/**
* Defines prop names that should be treated as callbacks.
*/
fun Events(vararg callbacks: String) {
callbacksDefinition = CallbacksDefinition(
arrayOf(GLOBAL_EVENT_NAME, *callbacks)
)
}
/**
* Defines prop names that should be treated as callbacks.
*/
@JvmName("EventsWithArray")
fun Events(callbacks: Array<String>) {
callbacksDefinition = CallbacksDefinition(
arrayOf(GLOBAL_EVENT_NAME, *callbacks)
)
}
}

View File

@@ -0,0 +1,47 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#include "FabricComponentsRegistry.h"
#include <react/renderer/componentregistry/ComponentDescriptorProvider.h>
#include <CoreComponentsRegistry.h>
#include "ExpoViewComponentDescriptor.h"
namespace jni = facebook::jni;
namespace react = facebook::react;
namespace expo {
// static
void FabricComponentsRegistry::registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", FabricComponentsRegistry::initHybrid),
makeNativeMethod("registerComponentsRegistry", FabricComponentsRegistry::registerComponentsRegistry),
});
}
// static
jni::local_ref<FabricComponentsRegistry::jhybriddata>
FabricComponentsRegistry::initHybrid(jni::alias_ref<jhybridobject> jThis) {
return makeCxxInstance();
}
void FabricComponentsRegistry::registerComponentsRegistry(
jni::alias_ref<jni::JArrayClass<jni::JString>> componentNames) {
// Inject the component to the CoreComponentsRegistry because we don't want to touch the MainApplicationReactNativeHost
auto providerRegistry = react::CoreComponentsRegistry::sharedProviderRegistry();
size_t size = componentNames->size();
for (size_t i = 0; i < size; ++i) {
auto flavor = std::make_shared<std::string const>(componentNames->getElement(i)->toStdString());
auto componentName = react::ComponentName{flavor->c_str()};
providerRegistry->add(react::ComponentDescriptorProvider {
reinterpret_cast<react::ComponentHandle>(componentName),
componentName,
flavor,
&facebook::react::concreteComponentDescriptorConstructor<expo::ExpoViewComponentDescriptor>
});
}
}
} // namespace expo

View File

@@ -0,0 +1,25 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#pragma once
#include <fbjni/fbjni.h>
namespace expo {
class FabricComponentsRegistry : public facebook::jni::HybridClass<FabricComponentsRegistry> {
public:
static auto constexpr
kJavaDescriptor = "Lexpo/modules/adapters/react/FabricComponentsRegistry;";
static void registerNatives();
FabricComponentsRegistry() {};
void registerComponentsRegistry(
facebook::jni::alias_ref<facebook::jni::JArrayClass<facebook::jni::JString>> componentNames);
private:
static facebook::jni::local_ref<jhybriddata> initHybrid(facebook::jni::alias_ref<jhybridobject> jThis);
};
} // namespace expo

View File

@@ -0,0 +1,44 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include <jsi/jsi.h>
#include <memory>
namespace jsi = facebook::jsi;
namespace expo {
/**
* An interface for classes which wrap `jsi::Object`.
*/
class JSIObjectWrapper {
public:
/**
* @return a pointer to the underlying `jsi::Object`.
*/
virtual std::shared_ptr<jsi::Object> get() = 0;
};
/**
* An interface for classes which wrap `jsi::Value`.
*/
class JSIValueWrapper {
public:
/**
* @return a pointer to the underlying `jsi::Value`.
*/
virtual std::shared_ptr<jsi::Value> get() = 0;
};
/**
* An interface for classes which wrap `jsi::Function`.
*/
class JSIFunctionWrapper {
public:
/**
* @return a pointer to the underlying `jsi::Function`.
*/
virtual std::shared_ptr<jsi::Function> get() = 0;
};
} // namespace expo

View File

@@ -0,0 +1,26 @@
#include "JSReferencesCache.h"
namespace expo {
JSReferencesCache::JSReferencesCache(jsi::Runtime &runtime) {
jsObjectRegistry.emplace(
JSKeys::PROMISE,
std::make_unique<jsi::Object>(
runtime.global().getPropertyAsFunction(runtime, "Promise")
)
);
}
jsi::PropNameID &JSReferencesCache::getPropNameID(
jsi::Runtime &runtime,
const std::string &name
) {
auto propName = propNameIDRegistry.find(name);
if (propName == propNameIDRegistry.end()) {
auto propNameID = std::make_unique<jsi::PropNameID>(jsi::PropNameID::forAscii(runtime, name));
auto [result, _] = propNameIDRegistry.emplace(name, std::move(propNameID));
return *result->second;
}
return *propName->second;
}
} // namespace expo

View File

@@ -0,0 +1,56 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include <jsi/jsi.h>
#include <memory>
#include <unordered_map>
namespace jsi = facebook::jsi;
namespace expo {
/**
* Registry used to store references to often used JS objects like Promise.
* The object lifetime should be bound with the JS runtime.
*/
class JSReferencesCache {
public:
enum class JSKeys {
PROMISE
};
explicit JSReferencesCache(jsi::Runtime &runtime);
/**
* Gets a cached object.
*/
template<class T, typename std::enable_if_t<std::is_base_of_v<jsi::Object, T>, int> = 0>
T &getObject(JSKeys key) {
return static_cast<T &>(*jsObjectRegistry.at(key));
}
/**
* Gets a cached object if present. Otherwise, returns nullptr.
*/
template<class T, typename std::enable_if_t<std::is_base_of_v<jsi::Object, T>, int> = 0>
T *getOptionalObject(JSKeys key) {
auto result = jsObjectRegistry.find(key);
if (result == jsObjectRegistry.end()) {
return nullptr;
}
jsi::Object &object = *result->second;
return &static_cast<T &>(object);
}
/**
* Gets a cached jsi::PropNameID or creates a new one for the provided string.
*/
jsi::PropNameID &getPropNameID(jsi::Runtime &runtime, const std::string &name);
private:
std::unordered_map<JSKeys, std::unique_ptr<jsi::Object>> jsObjectRegistry;
std::unordered_map<std::string, std::unique_ptr<jsi::PropNameID>> propNameIDRegistry;
};
} // namespace expo

View File

@@ -0,0 +1,14 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
<meta-data
android:name="org.unimodules.core.AppLoader#react-native-headless"
android:value="expo.modules.adapters.react.apploader.RNHeadlessAppLoader" />
<meta-data
android:name="com.facebook.soloader.enabled"
android:value="true"
tools:replace="android:value" />
</application>
</manifest>

View File

@@ -0,0 +1,122 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#include "Exceptions.h"
#include "JSIContext.h"
#include "JSReferencesCache.h"
namespace jni = facebook::jni;
namespace expo {
jni::local_ref<CodedException> CodedException::create(const std::string &message) {
return CodedException::newInstance(jni::make_jstring(message));
}
std::string CodedException::getCode() noexcept {
const auto getCode = this->getClass()->getMethod<jni::JString()>("getCode");
const auto code = getCode(this->self());
return code->toStdString();
}
std::optional<std::string> CodedException::getLocalizedMessage() noexcept {
const auto getLocalizedMessage = this->getClass()
->getMethod<jni::JString()>("getLocalizedMessage");
const auto message = getLocalizedMessage(this->self());
if (message != nullptr) {
return message->toStdString();
}
return std::nullopt;
}
jni::local_ref<JavaScriptEvaluateException> JavaScriptEvaluateException::create(
const std::string &message,
const std::string &jsStack
) {
return JavaScriptEvaluateException::newInstance(
jni::make_jstring(message),
jni::make_jstring(jsStack)
);
}
jni::local_ref<UnexpectedException> UnexpectedException::create(const std::string &message) {
return UnexpectedException::newInstance(
jni::make_jstring(message)
);
}
jni::local_ref<InvalidArgsNumberException> InvalidArgsNumberException::create(int received, int expected) {
return InvalidArgsNumberException::newInstance(
received,
expected,
expected // number of required arguments
);
}
jsi::Value makeCodedError(
jsi::Runtime &rt,
jsi::String code,
jsi::String message
) {
auto codedErrorConstructor = rt
.global()
.getProperty(rt, "ExpoModulesCore_CodedError")
.asObject(rt)
.asFunction(rt);
return codedErrorConstructor.callAsConstructor(
rt, {
jsi::Value(rt, code),
jsi::Value(rt, message)
}
);
}
void rethrowAsCodedError(
jsi::Runtime &rt,
jni::JniException &jniException
) {
jni::local_ref<jni::JThrowable> unboxedThrowable = jniException.getThrowable();
if (unboxedThrowable->isInstanceOf(CodedException::javaClassLocal())) {
auto codedException = jni::static_ref_cast<CodedException>(unboxedThrowable);
auto code = codedException->getCode();
auto message = codedException->getLocalizedMessage();
auto codedError = makeCodedError(
rt,
jsi::String::createFromUtf8(rt, code),
jsi::String::createFromUtf8(rt, message.value_or(""))
);
throw jsi::JSError(
message.value_or(""),
rt,
std::move(codedError)
);
}
// Rethrow error if we can't wrap it.
throw;
}
void throwPendingJniExceptionAsCppException() {
JNIEnv* env = jni::Environment::current();
if (env->ExceptionCheck() == JNI_FALSE) {
return;
}
auto throwable = env->ExceptionOccurred();
if (!throwable) {
throw std::runtime_error("Unable to get pending JNI exception.");
}
env->ExceptionClear();
throw jni::JniException(jni::adopt_local(throwable));
}
void throwNewJavaException(jthrowable throwable) {
throw jni::JniException(jni::wrap_alias(throwable));
}
} // namespace expo

View File

@@ -0,0 +1,98 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
#include <optional>
namespace jni = facebook::jni;
namespace jsi = facebook::jsi;
namespace expo {
class JSIContext;
/**
* A convenient wrapper for the Kotlin CodedException.
* It can be used with the `jni::throwNewJavaException` function to throw a cpp exception that
* will be automatically changed to the corresponding Java/Kotlin exception.
* `jni::throwNewJavaException` creates and throws a C++ exception which wraps a Java exception,
* so the C++ flow is interrupted. Then, when translatePendingCppExceptionToJavaException
* is called at the topmost level of the native stack, the wrapped Java exception is thrown to the java caller.
*/
class CodedException : public jni::JavaClass<CodedException, jni::JThrowable> {
public:
static auto constexpr kJavaDescriptor = "Lexpo/modules/kotlin/exception/CodedException;";
static jni::local_ref<CodedException> create(const std::string &message);
std::string getCode() noexcept;
std::optional<std::string> getLocalizedMessage() noexcept;
};
/**
* A convenient wrapper for the Kotlin JavaScriptEvaluateException.
*/
class JavaScriptEvaluateException
: public jni::JavaClass<JavaScriptEvaluateException, CodedException> {
public:
static auto constexpr kJavaDescriptor = "Lexpo/modules/kotlin/exception/JavaScriptEvaluateException;";
static jni::local_ref<JavaScriptEvaluateException> create(
const std::string &message,
const std::string &jsStack
);
};
/**
* A convenient wrapper for the Kotlin UnexpectedException.
*/
class UnexpectedException
: public jni::JavaClass<UnexpectedException, CodedException> {
public:
static auto constexpr kJavaDescriptor = "Lexpo/modules/kotlin/exception/UnexpectedException;";
static jni::local_ref<UnexpectedException> create(
const std::string &message
);
};
class InvalidArgsNumberException
: public jni::JavaClass<InvalidArgsNumberException, CodedException> {
public:
static auto constexpr kJavaDescriptor = "Lexpo/modules/kotlin/exception/InvalidArgsNumberException;";
static jni::local_ref<InvalidArgsNumberException> create(
int received,
int expected
);
};
/**
* Tries to rethrow an jni::JniException as a js version of the CodedException
*/
[[noreturn]] void rethrowAsCodedError(
jsi::Runtime &rt,
jni::JniException &jniException
);
jsi::Value makeCodedError(
jsi::Runtime &runtime,
jsi::String code,
jsi::String message
);
/**
* fbjni@0.2.2 is built by ndk r21, its exceptions are not catchable by expo-modules-core built by ndk r23+.
* To catch these excetptions, we copy the `facebook::jni::throwPendingJniExceptionAsCppException` here and throw exceptions on our own.
*/
void throwPendingJniExceptionAsCppException();
/**
* Same as `facebook::jni::throwNewJavaException` but throwing exceptions on our own.
*/
[[noreturn]] void throwNewJavaException(jthrowable throwable);
} // namespace expo

View File

@@ -0,0 +1,22 @@
#pragma once
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <type_traits>
#include <unistd.h>
#include <unordered_map>
#include <vector>
#include <fbjni/fbjni.h>
#include <folly/dynamic.h>
#include <jsi/jsi.h>
#include <jsi/JSIDynamic.h>
#include <react/jni/ReadableNativeArray.h>
#include <react/jni/ReadableNativeMap.h>
#include <react/jni/WritableNativeArray.h>
#include <react/jni/WritableNativeMap.h>

View File

@@ -0,0 +1,83 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#include "ExpoModulesHostObject.h"
#include "LazyObject.h"
#include <folly/dynamic.h>
#include <jsi/JSIDynamic.h>
#include <react/bridging/LongLivedObject.h>
namespace jsi = facebook::jsi;
namespace expo {
ExpoModulesHostObject::ExpoModulesHostObject(JSIContext *installer)
: installer(installer) {}
/**
* Clears jsi references held by JSRegistry and JavaScriptRuntime.
*/
ExpoModulesHostObject::~ExpoModulesHostObject() {
auto &runtime = installer->runtimeHolder->get();
facebook::react::LongLivedObjectCollection::get(runtime).clear();
installer->prepareForDeallocation();
}
jsi::Value ExpoModulesHostObject::get(jsi::Runtime &runtime, const jsi::PropNameID &name) {
if (installer->wasDeallocated()) {
return jsi::Value::undefined();
}
auto cName = name.utf8(runtime);
if (UniqueJSIObject &cachedObject = modulesCache[cName]) {
return jsi::Value(runtime, *cachedObject);
}
if (!installer->hasModule(cName)) {
return jsi::Value::undefined();
}
// Create a lazy object for the specific module. It defers initialization of the final module object.
LazyObject::Shared moduleLazyObject = std::make_shared<LazyObject>(
[this, cName](jsi::Runtime &rt) {
// Check if the installer has been deallocated.
// If so, return nullptr to avoid a "field operation on NULL object" crash.
// As it's probably the best we can do in this case.
if (installer->wasDeallocated()) {
return std::shared_ptr<jsi::Object>(nullptr);
}
auto module = installer->getModule(cName);
return module->cthis()->getJSIObject(rt);
}
);
// Save the module's lazy host object for later use.
modulesCache[cName] = std::make_unique<jsi::Object>(
jsi::Object::createFromHostObject(runtime, moduleLazyObject));
return jsi::Value(runtime, *modulesCache[cName]);
}
void ExpoModulesHostObject::set(jsi::Runtime &runtime, const jsi::PropNameID &name,
const jsi::Value &value) {
throw jsi::JSError(
runtime,
"RuntimeError: Cannot override the host object for expo module '" + name.utf8(runtime) + "'"
);
}
std::vector<jsi::PropNameID> ExpoModulesHostObject::getPropertyNames(jsi::Runtime &rt) {
auto names = installer->getModulesName();
size_t size = names->size();
std::vector<jsi::PropNameID> result;
result.reserve(size);
for (int i = 0; i < size; i++) {
result.push_back(
jsi::PropNameID::forUtf8(rt, names->getElement(i)->toStdString())
);
}
return result;
}
} // namespace expo

View File

@@ -0,0 +1,39 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include "JSIContext.h"
#include <jsi/jsi.h>
#include <vector>
#import <unordered_map>
namespace jsi = facebook::jsi;
namespace expo {
using UniqueJSIObject = std::unique_ptr<jsi::Object>;
/**
* An entry point to all exported functionalities like modules.
*
* An instance of this class will be added to the JS global object.
*/
class ExpoModulesHostObject : public jsi::HostObject {
public:
ExpoModulesHostObject(JSIContext *installer);
~ExpoModulesHostObject() override;
jsi::Value get(jsi::Runtime &, const jsi::PropNameID &name) override;
void set(jsi::Runtime &, const jsi::PropNameID &name, const jsi::Value &value) override;
std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime &rt) override;
private:
JSIContext *installer;
std::unordered_map<std::string, UniqueJSIObject> modulesCache;
};
} // namespace expo

View File

@@ -0,0 +1,17 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#include "JNIDeallocator.h"
namespace expo {
void JNIDeallocator::addReference(
jni::local_ref<Destructible::javaobject> jniObject
) noexcept {
const static auto method = JNIDeallocator::javaClassLocal()
->getMethod<void(jni::local_ref<Destructible>)>(
"addReference"
);
method(self(), std::move(jniObject));
}
} // namespace expo

View File

@@ -0,0 +1,25 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include <fbjni/fbjni.h>
namespace jni = facebook::jni;
namespace expo {
class Destructible : public jni::JavaClass<Destructible> {
public:
static auto constexpr kJavaDescriptor = "Lexpo/modules/kotlin/jni/Destructible;";
};
class JNIDeallocator : public jni::JavaClass<JNIDeallocator> {
public:
static auto constexpr kJavaDescriptor = "Lexpo/modules/kotlin/jni/JNIDeallocator;";
void addReference(
jni::local_ref<Destructible::javaobject> jniObject
) noexcept;
};
} // namespace expo

View File

@@ -0,0 +1,78 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#include "JNIFunctionBody.h"
#include "Exceptions.h"
#include "JavaReferencesCache.h"
namespace jni = facebook::jni;
namespace react = facebook::react;
namespace expo {
jni::local_ref<jni::JObject>
JNINoArgsFunctionBody::invoke(
jobject self
) {
// Do NOT use getClass here!
// Method obtained from `getClass` will point to the overridden version of the method.
// Because of that, it can't be cached - we will try to invoke the nonexistent method
// if we receive an object of a different class than the one used to obtain the method id.
// The only cacheable method id can be obtain from the base class.
static const auto method = jni::findClassLocal("expo/modules/kotlin/jni/JNINoArgsFunctionBody")
->getMethod<jni::local_ref<jni::JObject>()>(
"invoke"
);
auto result = jni::Environment::current()->CallObjectMethod(self, method.getId());
throwPendingJniExceptionAsCppException();
return jni::adopt_local(static_cast<jni::JniType<jni::JObject>>(result));
}
jni::local_ref<jni::JObject>
JNIFunctionBody::invoke(
jobject self,
jobjectArray args
) {
// Do NOT use getClass here!
// Method obtained from `getClass` will point to the overridden version of the method.
// Because of that, it can't be cached - we will try to invoke the nonexistent method
// if we receive an object of a different class than the one used to obtain the method id.
// The only cacheable method id can be obtain from the base class.
static const auto method = jni::findClassLocal("expo/modules/kotlin/jni/JNIFunctionBody")
->getMethod<jni::local_ref<jni::JObject>(jobjectArray)>(
"invoke",
"([Ljava/lang/Object;)Ljava/lang/Object;"
);
jvalue jValue{
.l = args
};
auto result = jni::Environment::current()->CallObjectMethodA(self, method.getId(), &jValue);
throwPendingJniExceptionAsCppException();
return jni::adopt_local(static_cast<jni::JniType<jni::JObject>>(result));
}
void JNIAsyncFunctionBody::invoke(
jobject self,
jobjectArray args,
jobject promise
) {
// Do NOT use getClass here!
// Method obtained from `getClass` will point to the overridden version of the method.
// Because of that, it can't be cached - we will try to invoke the nonexistent method
// if we receive an object of a different class than the one used to obtain the method id.
// The only cacheable method id can be obtain from the base class.
static const auto method = jni::findClassLocal("expo/modules/kotlin/jni/JNIAsyncFunctionBody")
->getMethod<
void(jobjectArray, jobject)
>(
"invoke",
"([Ljava/lang/Object;Lexpo/modules/kotlin/jni/PromiseImpl;)V"
);
// TODO(@lukmccall): consider using CallVoidMethodV
jni::Environment::current()->CallVoidMethod(self, method.getId(), args, promise);
throwPendingJniExceptionAsCppException();
}
} // namespace expo

View File

@@ -0,0 +1,70 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include <fbjni/fbjni.h>
#include <react/jni/ReadableNativeArray.h>
namespace jni = facebook::jni;
namespace react = facebook::react;
namespace expo {
/**
* A CPP part of the expo.modules.kotlin.jni.JNINoArgsFunctionBody class.
* It represents the Kotlin's promise-less function.
*/
class JNINoArgsFunctionBody : public jni::JavaClass<JNINoArgsFunctionBody> {
public:
static auto constexpr kJavaDescriptor = "Lexpo/modules/kotlin/jni/JNINoArgsFunctionBody;";
/**
* Invokes a Kotlin's implementation of this function.
*
* @return result of the Kotlin function
*/
static jni::local_ref<jni::JObject> invoke(
jobject self
);
};
/**
* A CPP part of the expo.modules.kotlin.jni.JNIFunctionBody class.
* It represents the Kotlin's promise-less function.
*/
class JNIFunctionBody : public jni::JavaClass<JNIFunctionBody> {
public:
static auto constexpr kJavaDescriptor = "Lexpo/modules/kotlin/jni/JNIFunctionBody;";
/**
* Invokes a Kotlin's implementation of this function.
*
* @param args
* @return result of the Kotlin function
*/
static jni::local_ref<jni::JObject> invoke(
jobject self,
jobjectArray args
);
};
/**
* A CPP part of the expo.modules.kotlin.jni.JNIAsyncFunctionBody class.
* It represents the Kotlin's promise function.
*/
class JNIAsyncFunctionBody : public jni::JavaClass<JNIAsyncFunctionBody> {
public:
static auto constexpr kJavaDescriptor = "Lexpo/modules/kotlin/jni/JNIAsyncFunctionBody;";
/**
* Invokes a Kotlin's implementation of this async function.
*
* @param args
* @param promise that will be resolve or rejected in the Kotlin's implementation
*/
static void invoke(
jobject self,
jobjectArray args,
jobject promise
);
};
} // namespace expo

View File

@@ -0,0 +1,77 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#include "RuntimeHolder.h"
#include "JSIContext.h"
#include "JavaScriptModuleObject.h"
#include "JavaScriptValue.h"
#include "JavaScriptObject.h"
#include "JavaScriptWeakObject.h"
#include "JavaScriptFunction.h"
#include "JavaScriptArrayBuffer.h"
#include "JavaScriptTypedArray.h"
#include "NativeArrayBuffer.h"
#include "JavaReferencesCache.h"
#include "JavaCallback.h"
#include "JNIUtils.h"
#include "types/FrontendConverterProvider.h"
#include "decorators/JSDecoratorsBridgingObject.h"
#include "installers/MainRuntimeInstaller.h"
#include "installers/WorkletRuntimeInstaller.h"
#include "worklets/Worklet.h"
#include "worklets/WorkletNativeRuntime.h"
#if RN_FABRIC_ENABLED
#include "FabricComponentsRegistry.h"
#endif
#include <jni.h>
#include <fbjni/fbjni.h>
// Install all jni bindings
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
return facebook::jni::initialize(vm, [] {
// Loads references to often use Java classes
expo::JCacheHolder::init(jni::Environment::current());
expo::FrontendConverterProvider::instance()->createConverters();
#if UNIT_TEST
expo::RuntimeHolder::registerNatives();
#endif
expo::MainRuntimeInstaller::registerNatives();
expo::WorkletNativeRuntime::registerNatives();
expo::WorkletRuntimeInstaller::registerNatives();
expo::JSIContext::registerNatives();
expo::JavaScriptModuleObject::registerNatives();
expo::JavaScriptValue::registerNatives();
expo::JavaScriptObject::registerNatives();
expo::JavaScriptWeakObject::registerNatives();
expo::JavaScriptFunction::registerNatives();
expo::JavaScriptArrayBuffer::registerNatives();
expo::JavaScriptTypedArray::registerNatives();
expo::NativeArrayBuffer::registerNatives();
expo::JavaCallback::registerNatives();
expo::JNIUtils::registerNatives();
#if WORKLETS_ENABLED
expo::Worklet::registerNatives();
#endif
// Decorators
expo::JSDecoratorsBridgingObject::registerNatives();
#if RN_FABRIC_ENABLED
expo::FabricComponentsRegistry::registerNatives();
#endif
});
}
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *) {
JNIEnv *env = nullptr;
jint ret = vm->GetEnv((void**)env, JNI_VERSION_1_6);
if (ret != JNI_OK) {
return;
}
expo::JCacheHolder::unLoad(env);
}

View File

@@ -0,0 +1,229 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#include "JNIUtils.h"
#include "EventEmitter.h"
#include "JSIUtils.h"
#include "types/JNIToJSIConverter.h"
#include <jsi/JSIDynamic.h>
#include "JSIContext.h"
#include "Exceptions.h"
namespace expo {
jsi::Value convertSharedObject(
jni::local_ref<JSharedObject::javaobject> sharedObject,
jsi::Runtime &rt,
JSIContext *jsiContext
) {
int id = sharedObject->getId();
if (id != 0) {
return jsi::Value(rt, *jsiContext->getSharedObject(id)->cthis()->get());
}
auto jsClass = jsiContext->getJavascriptClass(sharedObject->getClass());
if (jsClass == nullptr) {
// If the shared object is an instance of `ShareRef` and the class was not found,
// we can create a new JavaScript object with the empty prototype.
// User didn't register SharedRef using Class component.
if (sharedObject->isInstanceOf(JSharedRef::javaClassStatic())) {
auto jsObject = std::make_shared<jsi::Object>(jsi::Object(rt));
auto jsObjectRef = JavaScriptObject::newInstance(
jsiContext,
jsiContext->runtimeHolder,
jsObject
);
jsiContext->registerSharedObject(sharedObject, jsObjectRef);
return jsi::Value(rt, *jsObject);
}
throwNewJavaException(
UnexpectedException::create(
"Could not find JavaScript class for shared object: " + sharedObject->toString()
).get()
);
}
auto prototype = jsClass
->cthis()
->get()
->getProperty(rt, "prototype")
.asObject(rt);
auto objSharedPtr = std::make_shared<jsi::Object>(
expo::common::createObjectWithPrototype(rt, &prototype)
);
auto jsObjectInstance = JavaScriptObject::newInstance(
jsiContext,
jsiContext->runtimeHolder,
objSharedPtr
);
jni::local_ref<JavaScriptObject::javaobject> jsRef = jni::make_local(
jsObjectInstance
);
jsiContext->registerSharedObject(sharedObject, jsRef);
return jsi::Value(rt, *objSharedPtr);
}
void JNIUtils::registerNatives() {
javaClassStatic()->registerNatives({
makeNativeMethod("emitEvent",
JNIUtils::emitEventOnJavaScriptObject),
makeNativeMethod("emitEvent",
JNIUtils::emitEventOnJavaScriptModule),
makeNativeMethod("emitEvent",
JNIUtils::emitEventOnWeakJavaScriptObject)
});
}
void JNIUtils::emitEventOnWeakJavaScriptObject(
[[maybe_unused]] jni::alias_ref<jni::JClass> clazz,
jni::alias_ref<JavaScriptWeakObject::javaobject> jsiThis,
jni::alias_ref<jni::HybridClass<JSIContext>::javaobject> jsiContextRef,
jni::alias_ref<jstring> eventName,
jni::alias_ref<jni::JArrayClass<jobject>> args
) {
jni::global_ref<jni::JArrayClass<jobject>> globalArgs = jni::make_global(args);
JNIUtils::emitEventOnJSIObject(
jsiThis->cthis()->getWeak(),
jsiContextRef,
eventName,
[args = globalArgs](jsi::Runtime &rt) -> std::vector<jsi::Value> {
auto localArgs = jni::static_ref_cast<jni::JArrayClass<jobject>>(args);
JNIEnv *env = jni::Environment::current();
size_t size = localArgs->size();
std::vector<jsi::Value> convertedArgs;
convertedArgs.reserve(size);
for (size_t i = 0; i < size; i++) {
jni::local_ref<jobject> arg = localArgs->getElement(i);
convertedArgs.push_back(convert(env, rt, arg));
}
return convertedArgs;
}
);
}
void JNIUtils::emitEventOnJavaScriptObject(
[[maybe_unused]] jni::alias_ref<jni::JClass> clazz,
jni::alias_ref<JavaScriptObject::javaobject> jsiThis,
jni::alias_ref<jni::HybridClass<JSIContext>::javaobject> jsiContextRef,
jni::alias_ref<jstring> eventName,
jni::alias_ref<jni::JArrayClass<jobject>> args
) {
jni::global_ref<jni::JArrayClass<jobject>> globalArgs = jni::make_global(args);
JNIUtils::emitEventOnJSIObject(
jsiThis->cthis()->get(),
jsiContextRef,
eventName,
[args = globalArgs](jsi::Runtime &rt) -> std::vector<jsi::Value> {
auto localArgs = jni::static_ref_cast<jni::JArrayClass<jobject>>(args);
JNIEnv *env = jni::Environment::current();
size_t size = localArgs->size();
std::vector<jsi::Value> convertedArgs;
convertedArgs.reserve(size);
for (size_t i = 0; i < size; i++) {
jni::local_ref<jobject> arg = localArgs->getElement(i);
convertedArgs.push_back(convert(env, rt, arg));
}
return convertedArgs;
}
);
}
void JNIUtils::emitEventOnJavaScriptModule(
[[maybe_unused]] jni::alias_ref<jni::JClass> clazz,
jni::alias_ref<JavaScriptModuleObject::javaobject> jsiThis,
jni::alias_ref<jni::HybridClass<JSIContext>::javaobject> jsiContextRef,
jni::alias_ref<jstring> eventName,
jni::alias_ref<jni::JMap<jstring, jobject>> eventBody
) {
auto globalEventBody = jni::make_global(eventBody);
JNIUtils::emitEventOnJSIObject(
jsiThis->cthis()->getCachedJSIObject(),
jsiContextRef,
eventName,
[args = std::move(globalEventBody)](jsi::Runtime &rt) -> std::vector<jsi::Value> {
JNIEnv *env = jni::Environment::current();
auto localArgs = jni::static_ref_cast<jni::JMap<jstring, jobject>>(args);
std::vector<jsi::Value> result;
result.push_back(convertToJS(env, rt, localArgs));
return result;
}
);
}
void JNIUtils::emitEventOnJSIObject(
std::weak_ptr<jsi::WeakObject> jsiThis,
jni::alias_ref<jni::HybridClass<JSIContext>::javaobject> jsiContextRef,
jni::alias_ref<jstring> eventName,
ArgsProvider argsProvider
) {
const std::string name = eventName->toStdString();
const JSIContext *jsiContext = jsiContextRef->cthis();
jsiContext->runtimeHolder->jsInvoker->invokeAsync([
jsiContext,
name = name,
argsProvider = std::move(argsProvider),
weakThis = std::move(jsiThis)
]() {
std::shared_ptr<jsi::WeakObject> jsWeakThis = weakThis.lock();
if (!jsWeakThis) {
return;
}
// TODO(@lukmccall): refactor when jsInvoker receives a runtime as a parameter
jsi::Runtime &rt = jsiContext->runtimeHolder->get();
jsi::Value unpackedValue = jsWeakThis->lock(rt);
if (unpackedValue.isUndefined()) {
// The JS object was deallocated - we can ignore emitting an event
return;
}
jsi::Object jsThis = unpackedValue.asObject(rt);
EventEmitter::emitEvent(rt, jsThis, name, argsProvider(rt));
});
}
void JNIUtils::emitEventOnJSIObject(
std::weak_ptr<jsi::Object> jsiThis,
jni::alias_ref<jni::HybridClass<JSIContext>::javaobject> jsiContextRef,
jni::alias_ref<jstring> eventName,
ArgsProvider argsProvider
) {
const std::string name = eventName->toStdString();
const JSIContext *jsiContext = jsiContextRef->cthis();
jsiContext->runtimeHolder->jsInvoker->invokeAsync([
jsiContext,
name = name,
weakThis = std::move(jsiThis),
argsProvider = std::move(argsProvider)
]() {
std::shared_ptr<jsi::Object> jsThis = weakThis.lock();
if (!jsThis) {
return;
}
// TODO(@lukmccall): refactor when jsInvoker receives a runtime as a parameter
jsi::Runtime &rt = jsiContext->runtimeHolder->get();
EventEmitter::emitEvent(rt, *jsThis, name, argsProvider(rt));
});
}
} // namespace expo

View File

@@ -0,0 +1,81 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include <vector>
#include <functional>
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
#include <react/jni/ReadableNativeMap.h>
#include "JSIContext.h"
#include "JavaScriptObject.h"
#include "JavaScriptModuleObject.h"
#include "JavaScriptWeakObject.h"
#include "JSharedObject.h"
namespace jni = facebook::jni;
namespace jsi = facebook::jsi;
namespace react = facebook::react;
namespace expo {
class JSIContext;
jsi::Value convertSharedObject(
jni::local_ref<JSharedObject::javaobject> sharedObject,
jsi::Runtime &rt,
JSIContext *context
);
class JNIUtils : public jni::JavaClass<JNIUtils> {
public:
static auto constexpr kJavaDescriptor = "Lexpo/modules/kotlin/jni/JNIUtils;";
static auto constexpr TAG = "JNIUtils";
static void registerNatives();
static void emitEventOnWeakJavaScriptObject(
jni::alias_ref<jni::JClass> clazz,
jni::alias_ref<JavaScriptWeakObject::javaobject> jsiThis,
jni::alias_ref<jni::HybridClass<JSIContext>::javaobject> jsiContextRef,
jni::alias_ref<jstring> eventName,
jni::alias_ref<jni::JArrayClass<jobject>> args
);
static void emitEventOnJavaScriptObject(
jni::alias_ref<jni::JClass> clazz,
jni::alias_ref<JavaScriptObject::javaobject> jsiThis,
jni::alias_ref<jni::HybridClass<JSIContext>::javaobject> jsiContextRef,
jni::alias_ref<jstring> eventName,
jni::alias_ref<jni::JArrayClass<jobject>> args
);
static void emitEventOnJavaScriptModule(
jni::alias_ref<jni::JClass> clazz,
jni::alias_ref<JavaScriptModuleObject::javaobject> jsiThis,
jni::alias_ref<jni::HybridClass<JSIContext>::javaobject> jsiContextRef,
jni::alias_ref<jstring> eventName,
jni::alias_ref<jni::JMap<jstring, jobject>> eventBody
);
private:
using ArgsProvider = std::function<std::vector<jsi::Value>(jsi::Runtime &rt)>;
static void emitEventOnJSIObject(
std::weak_ptr<jsi::Object> jsiThis,
jni::alias_ref<jni::HybridClass<JSIContext>::javaobject> jsiContextRef,
jni::alias_ref<jstring> eventName,
ArgsProvider argsProvider
);
static void emitEventOnJSIObject(
std::weak_ptr<jsi::WeakObject> jsiThis,
jni::alias_ref<jni::HybridClass<JSIContext>::javaobject> jsiContextRef,
jni::alias_ref<jstring> eventName,
ArgsProvider argsProvider
);
};
} // namespace expo

View File

@@ -0,0 +1,288 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#include "JSIContext.h"
#include "Exceptions.h"
#include "ExpoModulesHostObject.h"
#include "JavaReferencesCache.h"
#include "JSReferencesCache.h"
#include "SharedObject.h"
#include "SharedRef.h"
#include "NativeModule.h"
#include <fbjni/detail/Meta.h>
#include <fbjni/fbjni.h>
#include <memory>
#include <shared_mutex>
namespace jni = facebook::jni;
namespace jsi = facebook::jsi;
namespace expo {
void JSIContext::registerNatives() {
registerHybrid({
makeNativeMethod("evaluateScript", JSIContext::evaluateScript),
makeNativeMethod("evaluateVoidScript", JSIContext::evaluateVoidScript),
makeNativeMethod("global", JSIContext::global),
makeNativeMethod("createObject", JSIContext::createObject),
makeNativeMethod("drainJSEventLoop", JSIContext::drainJSEventLoop),
makeNativeMethod("setNativeStateForSharedObject",
JSIContext::jniSetNativeStateForSharedObject),
});
}
JSIContext::JSIContext(
jlong jsRuntimePointer,
jni::alias_ref<JNIDeallocator::javaobject> jniDeallocator,
std::shared_ptr<react::CallInvoker> callInvoker
) : jniDeallocator(jni::make_global(jniDeallocator)) {
auto runtime = reinterpret_cast<jsi::Runtime *>(jsRuntimePointer);
jsRegistry = std::make_unique<JSReferencesCache>(*runtime);
runtimeHolder = std::make_shared<JavaScriptRuntime>(
runtime,
std::move(callInvoker)
);
}
jni::local_ref<JSIContext::javaobject> JSIContext::newJavaInstance(
jni::local_ref<jni::detail::HybridData> hybridData,
jni::alias_ref<jni::JWeakReference<jobject>::javaobject> runtimeContextHolder
) {
return JSIContext::newObjectJavaArgs(
std::move(hybridData),
std::move(runtimeContextHolder)
);
}
void JSIContext::bindToJavaPart(
jni::local_ref<JSIContext::javaobject> jThis
) {
javaPart_ = jni::make_global(jThis);
threadSafeJThis = std::make_shared<ThreadSafeJNIGlobalRef<JSIContext::javaobject>>(
jni::Environment::current()->NewGlobalRef(javaPart_.get())
);
}
jni::local_ref<JavaScriptModuleObject::javaobject>
JSIContext::callGetJavaScriptModuleObjectMethod(const std::string &moduleName) const {
if (javaPart_ == nullptr) {
throw std::runtime_error(
"getJavaScriptModuleObject: JSIContext was prepared to be deallocated.");
}
const static auto method = expo::JSIContext::javaClassLocal()
->getMethod<jni::local_ref<JavaScriptModuleObject::javaobject>(
std::string)>(
"getJavaScriptModuleObject"
);
auto jniString = jni::make_jstring(moduleName);
auto result = jni::Environment::current()->CallObjectMethod(javaPart_.get(), method.getId(),
jniString.get());
throwPendingJniExceptionAsCppException();
return jni::adopt_local(static_cast<JavaScriptModuleObject::javaobject>(result));
}
jni::local_ref<jni::JArrayClass<jni::JString>>
JSIContext::callGetJavaScriptModulesNames() const {
if (javaPart_ == nullptr) {
throw std::runtime_error("getJavaScriptModules: JSIContext was prepared to be deallocated.");
}
const static auto method = expo::JSIContext::javaClassLocal()
->getMethod<jni::local_ref<jni::JArrayClass<jni::JString>>()>(
"getJavaScriptModulesName"
);
auto result = jni::Environment::current()->CallObjectMethod(javaPart_.get(), method.getId());
throwPendingJniExceptionAsCppException();
return jni::adopt_local(static_cast<jni::JniType<jni::JArrayClass<jni::JString>>>(result));
}
bool JSIContext::callHasModule(const std::string &moduleName) const {
if (javaPart_ == nullptr) {
throw std::runtime_error("hasModule: JSIContext was prepared to be deallocated.");
}
const static auto method = expo::JSIContext::javaClassLocal()
->getMethod<jboolean(std::string)>(
"hasModule"
);
auto jniString = jni::make_jstring(moduleName);
auto result = jni::Environment::current()->CallBooleanMethod(javaPart_.get(), method.getId(),
jniString.get());
throwPendingJniExceptionAsCppException();
return static_cast<bool>(result);
}
jni::local_ref<JavaScriptModuleObject::javaobject>
JSIContext::getModule(const std::string &moduleName) const {
return callGetJavaScriptModuleObjectMethod(moduleName);
}
bool JSIContext::hasModule(const std::string &moduleName) const {
return callHasModule(moduleName);
}
jni::local_ref<jni::JArrayClass<jni::JString>> JSIContext::getModulesName() const {
return callGetJavaScriptModulesNames();
}
jni::local_ref<JavaScriptValue::javaobject> JSIContext::evaluateScript(
jni::JString script
) {
return runtimeHolder->evaluateScript(script.toStdString());
}
void JSIContext::evaluateVoidScript(
jni::JString script
) {
runtimeHolder->evaluateVoidScript(script.toStdString());
}
jni::local_ref<JavaScriptObject::javaobject> JSIContext::global() noexcept {
return runtimeHolder->global();
}
jni::local_ref<JavaScriptObject::javaobject> JSIContext::createObject() noexcept {
return runtimeHolder->createObject();
}
void JSIContext::drainJSEventLoop() {
runtimeHolder->drainJSEventLoop();
}
void JSIContext::registerSharedObject(
jni::local_ref<jobject> native,
jni::local_ref<JavaScriptObject::javaobject> js
) {
if (javaPart_ == nullptr) {
throw std::runtime_error("registerSharedObject: JSIContext was prepared to be deallocated.");
}
const static auto method = expo::JSIContext::javaClassLocal()
->getMethod<void(jni::local_ref<jobject>, jni::local_ref<JavaScriptObject::javaobject>)>(
"registerSharedObject"
);
method(javaPart_, std::move(native), std::move(js));
}
jni::local_ref<JavaScriptObject::javaobject> JSIContext::getSharedObject(
int objectId
) {
if (javaPart_ == nullptr) {
throw std::runtime_error("getSharedObject: JSIContext was prepared to be deallocated.");
}
const static auto method = expo::JSIContext::javaClassLocal()
->getMethod<jni::local_ref<JavaScriptObject::javaobject>(int)>(
"getSharedObject"
);
return method(javaPart_, objectId);
}
void JSIContext::deleteSharedObject(
jni::alias_ref<JSIContext::javaobject> javaObject,
int objectId
) {
if (javaObject == nullptr) {
throw std::runtime_error("deleteSharedObject: JSIContext is invalid.");
}
const static auto method = expo::JSIContext::javaClassLocal()
->getMethod<void(int)>(
"deleteSharedObject"
);
method(javaObject, objectId);
}
void JSIContext::registerClass(
jni::local_ref<jclass> native,
jni::local_ref<JavaScriptObject::javaobject> jsClass
) {
if (javaPart_ == nullptr) {
throw std::runtime_error("registerClass: JSIContext was prepared to be deallocated.");
}
const static auto method = expo::JSIContext::javaClassLocal()
->getMethod<void(jni::local_ref<jclass>, jni::local_ref<JavaScriptObject::javaobject>)>(
"registerClass"
);
method(javaPart_, std::move(native), std::move(jsClass));
}
jni::local_ref<JavaScriptObject::javaobject> JSIContext::getJavascriptClass(
jni::local_ref<jclass> native
) {
if (javaPart_ == nullptr) {
throw std::runtime_error("getJavascriptClass: JSIContext was prepared to be deallocated.");
}
const static auto method = expo::JSIContext::javaClassLocal()
->getMethod<jni::local_ref<JavaScriptObject::javaobject>(jni::local_ref<jclass>)>(
"getJavascriptClass"
);
return method(javaPart_, std::move(native));
}
void JSIContext::prepareForDeallocation() noexcept {
jsRegistry.reset();
if (runtimeHolder) {
unbindJSIContext(runtimeHolder->get());
runtimeHolder.reset();
}
jniDeallocator.reset();
wasDeallocated_ = true;
}
void JSIContext::jniSetNativeStateForSharedObject(
int id,
jni::alias_ref<JavaScriptObject::javaobject> jsObject
) noexcept {
auto nativeState = std::make_shared<expo::SharedObject::NativeState>(
id,
// We can't predict the order of deallocation of the JSIContext and the SharedObject.
// So we need to pass a new ref to retain the JSIContext to make sure it's not deallocated before the SharedObject.
[threadSafeRef = threadSafeJThis](const SharedObject::ObjectId objectId) {
threadSafeRef->use([objectId](jni::alias_ref<JSIContext::javaobject> globalRef) {
JSIContext::deleteSharedObject(globalRef, objectId);
});
}
);
jsObject
->cthis()
->get()
->setNativeState(runtimeHolder->get(), std::move(nativeState));
}
bool JSIContext::wasDeallocated() const noexcept {
return wasDeallocated_;
}
static std::unordered_map<uintptr_t, JSIContext *> jsiContexts;
static std::shared_mutex jsiContextsMutex;
void bindJSIContext(const jsi::Runtime &runtime, JSIContext *jsiContext) {
std::unique_lock lock(jsiContextsMutex);
jsiContexts[reinterpret_cast<uintptr_t>(&runtime)] = jsiContext;
}
void unbindJSIContext(const jsi::Runtime &runtime) {
std::unique_lock lock(jsiContextsMutex);
jsiContexts.erase(reinterpret_cast<uintptr_t>(&runtime));
}
JSIContext *getJSIContext(const jsi::Runtime &runtime) {
std::shared_lock lock(jsiContextsMutex);
const auto iterator = jsiContexts.find(reinterpret_cast<uintptr_t>(&runtime));
if (iterator == jsiContexts.end()) {
throw std::invalid_argument("JSIContext for the given runtime doesn't exist");
}
return iterator->second;
}
} // namespace expo

View File

@@ -0,0 +1,194 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include "JavaScriptRuntime.h"
#include "JavaScriptModuleObject.h"
#include "JavaScriptValue.h"
#include "JavaScriptObject.h"
#include "JavaReferencesCache.h"
#include "JSReferencesCache.h"
#include "JNIDeallocator.h"
#include "ThreadSafeJNIGlobalRef.h"
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
#include <ReactCommon/CallInvokerHolder.h>
#include <ReactCommon/CallInvoker.h>
#if IS_NEW_ARCHITECTURE_ENABLED
#include <ReactCommon/RuntimeExecutor.h>
#include <react/jni/JRuntimeExecutor.h>
#endif
#include <ReactCommon/NativeMethodCallInvokerHolder.h>
#include <memory>
namespace jni = facebook::jni;
namespace jsi = facebook::jsi;
namespace react = facebook::react;
namespace expo {
/**
* A JNI wrapper to initialize CPP part of modules and access all data from the module registry.
*/
class JSIContext : public jni::HybridClass<JSIContext> {
public:
static auto constexpr kJavaDescriptor = "Lexpo/modules/kotlin/jni/JSIContext;";
static auto constexpr TAG = "JSIContext";
static void registerNatives();
static jni::local_ref<JSIContext::javaobject> newJavaInstance(
jni::local_ref<jni::detail::HybridData> hybridData,
jni::alias_ref<jni::JWeakReference<jobject>::javaobject> runtimeContextHolder
);
JSIContext(
jlong jsRuntimePointer,
jni::alias_ref<JNIDeallocator::javaobject> jniDeallocator,
std::shared_ptr<react::CallInvoker> callInvoker
);
void bindToJavaPart(
jni::local_ref<JSIContext::javaobject> jThis
);
/**
* Gets a module for a given name. It will throw an exception if the module doesn't exist.
*
* @param moduleName
* @return An instance of `JavaScriptModuleObject`
*/
[[nodiscard]] jni::local_ref<JavaScriptModuleObject::javaobject> getModule(const std::string &moduleName) const;
[[nodiscard]] bool hasModule(const std::string &moduleName) const;
/**
* Gets names of all available modules.
*/
[[nodiscard]] jni::local_ref<jni::JArrayClass<jni::JString>> getModulesName() const;
/**
* Exposes a `JavaScriptRuntime::evaluateScript` function to Kotlin
*/
jni::local_ref<JavaScriptValue::javaobject> evaluateScript(jni::JString script);
void evaluateVoidScript(jni::JString script);
/**
* Exposes a `JavaScriptRuntime::global` function to Kotlin
*/
jni::local_ref<JavaScriptObject::javaobject> global() noexcept;
/**
* Exposes a `JavaScriptRuntime::createObject` function to Kotlin
*/
jni::local_ref<JavaScriptObject::javaobject> createObject() noexcept;
/**
* Adds a shared object to the internal registry
* @param native part of the shared object
* @param js part of the shared object
*/
void registerSharedObject(
jni::local_ref<jobject> native,
jni::local_ref<JavaScriptObject::javaobject> js
);
/**
* Gets a shared object from the internal registry
* @param objectId
* @return
*/
jni::local_ref<JavaScriptObject::javaobject> getSharedObject(
int objectId
);
static void deleteSharedObject(
jni::alias_ref<JSIContext::javaobject> javaObject,
int objectId
);
/**
* Exposes a `JavaScriptRuntime::drainJSEventLoop` function to Kotlin
*/
void drainJSEventLoop();
std::shared_ptr<JavaScriptRuntime> runtimeHolder;
std::unique_ptr<JSReferencesCache> jsRegistry;
jni::global_ref<JNIDeallocator::javaobject> jniDeallocator;
void registerClass(jni::local_ref<jclass> native,
jni::local_ref<JavaScriptObject::javaobject> jsClass);
jni::local_ref<JavaScriptObject::javaobject> getJavascriptClass(jni::local_ref<jclass> native);
void prepareForDeallocation() noexcept;
[[nodiscard]] bool wasDeallocated() const noexcept;
/*
* We store two global references to the Java part of the JSIContext.registerClass
* However, one is wrapped in additional abstraction to make it thread-safe,
* which increase the access time. For most operations, we should use the bare reference.
* Only for operations that are executed on different threads that aren't attached to JVM,
* we should use the thread-safe reference.
*/
jni::global_ref<JSIContext::javaobject> javaPart_;
std::shared_ptr<ThreadSafeJNIGlobalRef<JSIContext::javaobject>> threadSafeJThis;
private:
friend HybridBase;
bool wasDeallocated_ = false;
[[nodiscard]] inline jni::local_ref<JavaScriptModuleObject::javaobject>
callGetJavaScriptModuleObjectMethod(const std::string &moduleName) const;
[[nodiscard]] inline jni::local_ref<jni::JArrayClass<jni::JString>> callGetJavaScriptModulesNames() const;
[[nodiscard]] inline bool callHasModule(const std::string &moduleName) const;
void prepareJSIContext(
jlong jsRuntimePointer,
jni::alias_ref<JNIDeallocator::javaobject> jniDeallocator,
std::shared_ptr<react::CallInvoker> callInvoker
) noexcept;
void prepareRuntime() noexcept;
void jniSetNativeStateForSharedObject(
int id,
jni::alias_ref<JavaScriptObject::javaobject> jsObject
) noexcept;
};
/**
* Binds the JSIContext to the runtime.
* Thread-safe: uses exclusive lock.
* @param runtime
* @param jsiContext
*/
void bindJSIContext(const jsi::Runtime &runtime, JSIContext *jsiContext);
/**
* Unbinds the JSIContext from the runtime.
* Thread-safe: uses exclusive lock.
* @param runtime
*/
void unbindJSIContext(const jsi::Runtime &runtime);
/**
* Gets the JSIContext for the given runtime.
* Thread-safe: uses exclusive lock.
* @param runtime
* @return JSIContext * - it should never be stored when received from this function.
* @throws std::invalid_argument if the JSIContext for the given runtime doesn't exist.
*/
JSIContext *getJSIContext(const jsi::Runtime &runtime);
} // namespace expo

View File

@@ -0,0 +1,85 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include "JSIObjectWrapper.h"
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
#include <type_traits>
namespace jni = facebook::jni;
namespace jsi = facebook::jsi;
namespace expo {
/**
* A base template of the jni to jsi types converter.
* To make this conversion as fast and easy as possible we used the type trait technic.
*/
template<class T, typename = void>
struct jsi_type_converter {
static const bool isDefined = false;
};
/**
* Conversion from jni::alias_ref<T::javaobject> to jsi::Value where T extends JSIValueWrapper, JSIObjectWrapper or JSIFunctionWrapper.
*/
template<class T>
struct jsi_type_converter<
jni::alias_ref<T>,
std::enable_if_t<
// jni::ReprType<T>::HybridType>::value if T looks like `R::javaobject`, it will return R
std::is_base_of<JSIValueWrapper, typename jni::ReprType<T>::HybridType>::value ||
std::is_base_of<JSIObjectWrapper, typename jni::ReprType<T>::HybridType>::value ||
std::is_base_of<JSIValueWrapper, typename jni::ReprType<T>::HybridType>::value
>
> {
static const bool isDefined = true;
inline static jsi::Value convert(
jsi::Runtime &runtime,
jni::alias_ref<T> &value) {
if (value == nullptr) {
return jsi::Value::undefined();
}
return jsi::Value(runtime, *value->cthis()->get());
}
};
/**
* Conversion from primitive types from which jsi::Value can be constructed (like bool, double) to jsi::Value.
*/
template<class T>
struct jsi_type_converter<
T,
std::enable_if_t<std::is_fundamental_v<T> && std::is_constructible_v<jsi::Value, T>>
> {
static const bool isDefined = true;
inline static jsi::Value convert(jsi::Runtime &runtime, T value) {
return jsi::Value(value);
}
};
/**
* Conversion from jni::alias_ref<jstring> to jsi::Value.
*/
template<>
struct jsi_type_converter<jni::alias_ref<jstring>> {
static const bool isDefined = true;
inline static jsi::Value convert(jsi::Runtime &runtime, jni::alias_ref<jstring> &value) {
if (value == nullptr) {
return jsi::Value::undefined();
}
return jsi::Value(jsi::String::createFromUtf8(runtime, value->toStdString()));
}
};
/**
* Helper that checks if the type converter was defined for the given type.
*/
template<class T>
inline constexpr bool is_jsi_type_converter_defined = jsi_type_converter<T>::isDefined;
} // namespace expo

View File

@@ -0,0 +1,12 @@
// Copyright 2021-present 650 Industries. All rights reserved.
#include "JSharedObject.h"
namespace expo {
int JSharedObject::getId() noexcept {
static const auto method = javaClassStatic()->getMethod<int()>("getSharedObjectId");
return method(self());
}
} // namespace expo

View File

@@ -0,0 +1,25 @@
// Copyright 2021-present 650 Industries. All rights reserved.
#pragma once
#include <fbjni/fbjni.h>
namespace jni = facebook::jni;
namespace expo {
class JSharedObject : public jni::JavaClass<JSharedObject> {
public:
static constexpr const char *kJavaDescriptor = "Lexpo/modules/kotlin/sharedobjects/SharedObject;";
static auto constexpr TAG = "SharedObject";
int getId() noexcept;
};
class JSharedRef : public jni::JavaClass<JSharedRef, JSharedObject> {
public:
static constexpr const char *kJavaDescriptor = "Lexpo/modules/kotlin/sharedobjects/SharedRef;";
static auto constexpr TAG = "SharedRef";
};
} // namespace expo

View File

@@ -0,0 +1,274 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#include "JavaCallback.h"
#include "JSIContext.h"
#include "types/JNIToJSIConverter.h"
#include "Exceptions.h"
#include "JSIUtils.h"
#include "JNIUtils.h"
#include <fbjni/fbjni.h>
#include <fbjni/fbjni.h>
#include <folly/dynamic.h>
#include <jsi/JSIDynamic.h>
#include <functional>
namespace expo {
JavaCallback::CallbackContext::CallbackContext(
jsi::Runtime &rt,
std::weak_ptr<react::CallInvoker> jsCallInvokerHolder,
std::optional<jsi::Function> resolveHolder,
std::optional<jsi::Function> rejectHolder
) : react::LongLivedObject(rt),
rt(rt),
jsCallInvokerHolder(std::move(jsCallInvokerHolder)),
resolveHolder(std::move(resolveHolder)),
rejectHolder(std::move(rejectHolder)) {}
void JavaCallback::CallbackContext::invalidate() {
resolveHolder.reset();
rejectHolder.reset();
allowRelease();
}
JavaCallback::JavaCallback(std::shared_ptr<CallbackContext> callbackContext)
: callbackContext(std::move(callbackContext)) {}
void JavaCallback::registerNatives() {
registerHybrid({
makeNativeMethod("invokeNative", JavaCallback::invoke),
makeNativeMethod("invokeNative", JavaCallback::invokeBool),
makeNativeMethod("invokeNative", JavaCallback::invokeInt),
makeNativeMethod("invokeNative", JavaCallback::invokeDouble),
makeNativeMethod("invokeNative", JavaCallback::invokeFloat),
makeNativeMethod("invokeNative", JavaCallback::invokeString),
makeNativeMethod("invokeNative", JavaCallback::invokeCollection),
makeNativeMethod("invokeNative", JavaCallback::invokeMap),
makeNativeMethod("invokeNative", JavaCallback::invokeWritableArray),
makeNativeMethod("invokeNative", JavaCallback::invokeWritableMap),
makeNativeMethod("invokeNative", JavaCallback::invokeSharedObject),
makeNativeMethod("invokeNative", JavaCallback::invokeJavaScriptArrayBuffer),
makeNativeMethod("invokeNative", JavaCallback::invokeNativeArrayBuffer),
makeNativeMethod("invokeNative", JavaCallback::invokeError),
makeNativeMethod("invokeIntArray", JavaCallback::invokeIntArray),
makeNativeMethod("invokeLongArray", JavaCallback::invokeLongArray),
makeNativeMethod("invokeFloatArray", JavaCallback::invokeFloatArray),
makeNativeMethod("invokeDoubleArray", JavaCallback::invokeDoubleArray),
});
}
jni::local_ref<JavaCallback::javaobject> JavaCallback::newInstance(
JSIContext *jsiContext,
std::shared_ptr<CallbackContext> callbackContext
) {
auto object = JavaCallback::newObjectCxxArgs(std::move(callbackContext));
jsiContext->jniDeallocator->addReference(object);
return object;
}
template<typename T>
void JavaCallback::invokeJSFunction(
ArgsConverter<typename std::remove_const<T>::type> argsConverter,
T arg
) {
const auto strongCallbackContext = this->callbackContext.lock();
// The context were deallocated before the callback was invoked.
if (strongCallbackContext == nullptr) {
return;
}
const auto jsInvoker = strongCallbackContext->jsCallInvokerHolder.lock();
// Call invoker is already released, so we cannot invoke the callback.
if (jsInvoker == nullptr) {
return;
}
jsInvoker->invokeAsync(
[
context = callbackContext,
argsConverter = std::move(argsConverter),
arg = std::move(arg)
]() -> void {
auto strongContext = context.lock();
// The context were deallocated before the callback was invoked.
if (strongContext == nullptr) {
return;
}
if (!strongContext->resolveHolder.has_value()) {
throw std::runtime_error(
"JavaCallback was already settled. Cannot invoke it again"
);
}
jsi::Function &jsFunction = strongContext->resolveHolder.value();
jsi::Runtime &rt = strongContext->rt;
argsConverter(rt, jsFunction, std::move(arg));
strongContext->invalidate();
});
}
template<class T>
void JavaCallback::invokeJSFunction(T arg) {
invokeJSFunction<T>(
[](
jsi::Runtime &rt,
jsi::Function &jsFunction,
T arg
) {
jsFunction.call(rt, convertToJS(jni::Environment::current(), rt, std::forward<T>(arg)));
},
arg
);
}
template<class T>
void JavaCallback::invokeJSFunctionForArray(T &arg) {
size_t size = arg->size();
auto region = arg->getRegion((jsize) 0, size);
RawArray<typename decltype(region)::element_type> rawArray;
rawArray.size = size;
rawArray.data = std::move(region);
invokeJSFunction<decltype(rawArray)>(
std::move(rawArray)
);
}
void JavaCallback::invoke() {
invokeJSFunction<nullptr_t>(
[](
jsi::Runtime &rt,
jsi::Function &jsFunction,
nullptr_t arg
) {
jsFunction.call(rt, {jsi::Value::null()});
},
nullptr
);
}
void JavaCallback::invokeBool(bool result) {
invokeJSFunction(result);
}
void JavaCallback::invokeInt(int result) {
invokeJSFunction(result);
}
void JavaCallback::invokeDouble(double result) {
invokeJSFunction(result);
}
void JavaCallback::invokeFloat(float result) {
invokeJSFunction(result);
}
void JavaCallback::invokeString(jni::alias_ref<jstring> result) {
invokeJSFunction(result->toStdString());
}
void JavaCallback::invokeCollection(jni::alias_ref<jni::JCollection<jobject>> result) {
invokeJSFunction<
jni::global_ref<jni::JCollection<jobject>>
>(jni::make_global(result));
}
void JavaCallback::invokeMap(jni::alias_ref<jni::JMap<jstring, jobject>> result) {
invokeJSFunction<
jni::global_ref<jni::JMap<jstring, jobject>>
>(jni::make_global(result));
}
void
JavaCallback::invokeWritableArray(jni::alias_ref<react::WritableNativeArray::javaobject> result) {
invokeJSFunction(result->cthis()->consume());
}
void JavaCallback::invokeWritableMap(jni::alias_ref<react::WritableNativeMap::javaobject> result) {
invokeJSFunction(result->cthis()->consume());
}
void JavaCallback::invokeSharedObject(jni::alias_ref<JSharedObject::javaobject> result) {
invokeJSFunction(jni::make_global(result));
}
void JavaCallback::invokeJavaScriptArrayBuffer(jni::alias_ref<JavaScriptArrayBuffer::javaobject> result) {
invokeJSFunction(jni::make_global(result));
}
void JavaCallback::invokeNativeArrayBuffer(jni::alias_ref<NativeArrayBuffer::javaobject> result) {
invokeJSFunction(jni::make_global(result));
}
void JavaCallback::invokeIntArray(jni::alias_ref<jni::JArrayInt> result) {
invokeJSFunctionForArray(result);
}
void JavaCallback::invokeLongArray(jni::alias_ref<jni::JArrayLong> result) {
invokeJSFunctionForArray(result);
}
void JavaCallback::invokeDoubleArray(jni::alias_ref<jni::JArrayDouble> result) {
invokeJSFunctionForArray(result);
}
void JavaCallback::invokeFloatArray(jni::alias_ref<jni::JArrayFloat> result) {
invokeJSFunctionForArray(result);
}
void JavaCallback::invokeError(jni::alias_ref<jstring> code, jni::alias_ref<jstring> errorMessage) {
const auto strongCallbackContext = this->callbackContext.lock();
// The context were deallocated before the callback was invoked.
if (strongCallbackContext == nullptr) {
return;
}
const auto jsInvoker = strongCallbackContext->jsCallInvokerHolder.lock();
// Call invoker is already released, so we cannot invoke the callback.
if (jsInvoker == nullptr) {
return;
}
jsInvoker->invokeAsync(
[
context = callbackContext,
code = code->toStdString(),
errorMessage = errorMessage->toStdString()
]() -> void {
auto strongContext = context.lock();
// The context were deallocated before the callback was invoked.
if (strongContext == nullptr) {
return;
}
if (!strongContext->rejectHolder.has_value()) {
throw std::runtime_error(
"JavaCallback was already settled. Cannot invoke it again"
);
}
jsi::Function &jsFunction = strongContext->rejectHolder.value();
jsi::Runtime &rt = strongContext->rt;
auto codedError = makeCodedError(
rt,
jsi::String::createFromUtf8(rt, code),
jsi::String::createFromUtf8(rt, errorMessage)
);
jsFunction.call(
rt,
(const jsi::Value *) &codedError,
(size_t) 1
);
strongContext->invalidate();
});
}
} // namespace expo

View File

@@ -0,0 +1,114 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include "JNIDeallocator.h"
#include "JSharedObject.h"
#include "JavaScriptArrayBuffer.h"
#include "NativeArrayBuffer.h"
#include <jsi/jsi.h>
#include <fbjni/fbjni.h>
#include <folly/dynamic.h>
#include <variant>
#include <react/jni/WritableNativeArray.h>
#include <react/jni/WritableNativeMap.h>
#include <fbjni/detail/CoreClasses.h>
#include <ReactCommon/CallInvoker.h>
#include <react/bridging/LongLivedObject.h>
namespace jni = facebook::jni;
namespace react = facebook::react;
namespace jsi = facebook::jsi;
namespace expo {
class JSIContext;
class JavaCallback : public jni::HybridClass<JavaCallback, Destructible> {
public:
static auto constexpr
kJavaDescriptor = "Lexpo/modules/kotlin/jni/JavaCallback;";
static auto constexpr TAG = "JavaCallback";
class CallbackContext : public react::LongLivedObject {
public:
CallbackContext(
jsi::Runtime &rt,
std::weak_ptr<react::CallInvoker> jsCallInvokerHolder,
std::optional<jsi::Function> resolveHolder,
std::optional<jsi::Function> rejectHolder
);
jsi::Runtime &rt;
std::weak_ptr<react::CallInvoker> jsCallInvokerHolder;
std::optional<jsi::Function> resolveHolder;
std::optional<jsi::Function> rejectHolder;
void invalidate();
};
static void registerNatives();
static jni::local_ref<JavaCallback::javaobject> newInstance(
JSIContext *jsiContext,
std::shared_ptr<CallbackContext> callbackContext
);
private:
std::weak_ptr<CallbackContext> callbackContext;
friend HybridBase;
JavaCallback(std::shared_ptr<CallbackContext> callback);
void invoke();
void invokeBool(bool result);
void invokeInt(int result);
void invokeDouble(double result);
void invokeFloat(float result);
void invokeString(jni::alias_ref<jstring> result);
void invokeWritableArray(jni::alias_ref<react::WritableNativeArray::javaobject> result);
void invokeWritableMap(jni::alias_ref<react::WritableNativeMap::javaobject> result);
void invokeCollection(jni::alias_ref<jni::JCollection<jobject>> result);
void invokeMap(jni::alias_ref<jni::JMap<jstring, jobject>> result);
void invokeSharedObject(jni::alias_ref<JSharedObject::javaobject> result);
void invokeJavaScriptArrayBuffer(jni::alias_ref<JavaScriptArrayBuffer::javaobject> result);
void invokeNativeArrayBuffer(jni::alias_ref<NativeArrayBuffer::javaobject> result);
void invokeError(jni::alias_ref<jstring> code, jni::alias_ref<jstring> errorMessage);
void invokeIntArray(jni::alias_ref<jni::JArrayInt> result);
void invokeLongArray(jni::alias_ref<jni::JArrayLong> result);
void invokeDoubleArray(jni::alias_ref<jni::JArrayDouble> result);
void invokeFloatArray(jni::alias_ref<jni::JArrayFloat> result);
template<class T>
using ArgsConverter = std::function<void(jsi::Runtime &rt, jsi::Function &jsFunction, T arg)>;
template<class T>
inline void invokeJSFunction(
ArgsConverter<typename std::remove_const<T>::type> argsConverter,
T arg
);
template<class T>
void invokeJSFunctionForArray(T &arg);
template<class T>
inline void invokeJSFunction(T arg);
};
} // namespace expo

View File

@@ -0,0 +1,120 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#include "JavaReferencesCache.h"
#include <vector>
namespace expo {
JCache::JCache(JNIEnv *env) {
#define REGISTER_CLASS_WITH_CONSTRUCTOR(variable, name, signature) \
{ \
auto clazz = (jclass) env->NewGlobalRef(env->FindClass(name)); \
variable = { \
.clazz = clazz, \
.constructor = env->GetMethodID(clazz, "<init>", signature) \
}; \
}
REGISTER_CLASS_WITH_CONSTRUCTOR(jDouble, "java/lang/Double", "(D)V")
REGISTER_CLASS_WITH_CONSTRUCTOR(jBoolean, "java/lang/Boolean", "(Z)V")
REGISTER_CLASS_WITH_CONSTRUCTOR(jInteger, "java/lang/Integer", "(I)V")
REGISTER_CLASS_WITH_CONSTRUCTOR(jLong, "java/lang/Long", "(J)V")
REGISTER_CLASS_WITH_CONSTRUCTOR(jFloat, "java/lang/Float", "(F)V")
REGISTER_CLASS_WITH_CONSTRUCTOR(jPromise, "expo/modules/kotlin/jni/PromiseImpl",
"(Lexpo/modules/kotlin/jni/JavaCallback;)V")
#undef REGISTER_CLASS_WITH_CONSTRUCTOR
#define REGISTER_CLASS(name) (jclass) env->NewGlobalRef(env->FindClass(name))
jDoubleArray = REGISTER_CLASS("[D");
jBooleanArray = REGISTER_CLASS("[Z");
jIntegerArray = REGISTER_CLASS("[I");
jLongArray = REGISTER_CLASS("[J");
jFloatArray = REGISTER_CLASS("[F");
jCollection = REGISTER_CLASS("java/util/Collection");
jMap = REGISTER_CLASS("java/util/Map");
jObject = REGISTER_CLASS("java/lang/Object");
jString = REGISTER_CLASS("java/lang/String");
jJavaScriptObject = REGISTER_CLASS("expo/modules/kotlin/jni/JavaScriptObject");
jJavaScriptValue = REGISTER_CLASS("expo/modules/kotlin/jni/JavaScriptValue");
jJavaScriptTypedArray = REGISTER_CLASS("expo/modules/kotlin/jni/JavaScriptTypedArray");
jJavaScriptArrayBuffer = REGISTER_CLASS("expo/modules/kotlin/jni/JavaScriptArrayBuffer");
jNativeArrayBuffer = REGISTER_CLASS("expo/modules/kotlin/jni/NativeArrayBuffer");
jReadableNativeArray = REGISTER_CLASS("com/facebook/react/bridge/ReadableNativeArray");
jReadableNativeMap = REGISTER_CLASS("com/facebook/react/bridge/ReadableNativeMap");
jWritableNativeArray = REGISTER_CLASS("com/facebook/react/bridge/WritableNativeArray");
jWritableNativeMap = REGISTER_CLASS("com/facebook/react/bridge/WritableNativeMap");
jSharedObject = REGISTER_CLASS("expo/modules/kotlin/sharedobjects/SharedObject");
jJavaScriptModuleObject = REGISTER_CLASS("expo/modules/kotlin/jni/JavaScriptModuleObject");
#undef REGISTER_CLASS
jUndefined = getJUndefined(env);
}
std::shared_ptr<JCache> JCacheHolder::jCache = nullptr;
void JCacheHolder::init(JNIEnv *env) {
jCache = std::make_shared<JCache>(env);
}
void JCacheHolder::unLoad(JNIEnv *env) {
jCache->unLoad(env);
jCache.reset();
}
JCache &JCacheHolder::get() {
return *jCache;
}
jclass JCache::getOrLoadJClass(
JNIEnv *env,
const std::string &className
) {
auto result = jClassRegistry.find(className);
if (result == jClassRegistry.end()) {
auto clazz = (jclass) env->NewGlobalRef(env->FindClass(className.c_str()));
jClassRegistry.insert({className, clazz});
return clazz;
}
return result->second;
}
void JCache::unLoad(JNIEnv *env) {
env->DeleteGlobalRef(jDoubleArray);
env->DeleteGlobalRef(jBooleanArray);
env->DeleteGlobalRef(jIntegerArray);
env->DeleteGlobalRef(jLongArray);
env->DeleteGlobalRef(jFloatArray);
env->DeleteGlobalRef(jCollection);
env->DeleteGlobalRef(jMap);
env->DeleteGlobalRef(jObject);
env->DeleteGlobalRef(jString);
env->DeleteGlobalRef(jJavaScriptObject);
env->DeleteGlobalRef(jJavaScriptValue);
env->DeleteGlobalRef(jJavaScriptTypedArray);
env->DeleteGlobalRef(jReadableNativeArray);
env->DeleteGlobalRef(jReadableNativeMap);
env->DeleteGlobalRef(jWritableNativeArray);
env->DeleteGlobalRef(jWritableNativeMap);
env->DeleteGlobalRef(jSharedObject);
env->DeleteGlobalRef(jJavaScriptModuleObject);
}
jobject JCache::getJUndefined(JNIEnv *env) {
jclass clazz = env->FindClass("expo/modules/kotlin/types/ValueOrUndefined");
jmethodID methodId = env->GetStaticMethodID(clazz, "getUndefined", "()Ljava/lang/Object;");
return env->NewGlobalRef(
env->CallStaticObjectMethod(clazz, methodId)
);
}
} // namespace expo

View File

@@ -0,0 +1,85 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include <fbjni/fbjni.h>
#include <memory>
#include <unordered_map>
namespace jni = facebook::jni;
namespace expo {
class JCache {
public:
struct ConstructableJClass {
jclass clazz;
jmethodID constructor;
};
JCache(JNIEnv *env);
/**
* Gets a cached Java class entry or loads it to the registry.
*/
jclass getOrLoadJClass(JNIEnv *env, const std::string &className);
ConstructableJClass jDouble;
ConstructableJClass jBoolean;
ConstructableJClass jInteger;
ConstructableJClass jLong;
ConstructableJClass jFloat;
ConstructableJClass jPromise;
jclass jDoubleArray;
jclass jBooleanArray;
jclass jIntegerArray;
jclass jLongArray;
jclass jFloatArray;
jclass jCollection;
jclass jMap;
jclass jObject;
jclass jString;
jclass jJavaScriptObject;
jclass jJavaScriptValue;
jclass jJavaScriptTypedArray;
jclass jJavaScriptArrayBuffer;
jclass jNativeArrayBuffer;
jclass jReadableNativeArray;
jclass jReadableNativeMap;
jclass jWritableNativeArray;
jclass jWritableNativeMap;
jclass jSharedObject;
jclass jJavaScriptModuleObject;
jobject jUndefined;
void unLoad(JNIEnv *env);
private:
static jobject getJUndefined(JNIEnv *env);
std::unordered_map<std::string, jclass> jClassRegistry;
};
class JCacheHolder {
public:
static void init(JNIEnv *env);
static void unLoad(JNIEnv *env);
/**
* Gets a singleton instance
*/
static JCache &get();
private:
static std::shared_ptr<JCache> jCache;
};
} // namespace expo

View File

@@ -0,0 +1,67 @@
#include "JavaScriptArrayBuffer.h"
#include "JavaScriptRuntime.h"
#include "JSIContext.h"
namespace expo {
void JavaScriptArrayBuffer::registerNatives() {
registerHybrid({
makeNativeMethod("size", JavaScriptArrayBuffer::size),
makeNativeMethod("readByte", JavaScriptArrayBuffer::read<int8_t>),
makeNativeMethod("read2Byte", JavaScriptArrayBuffer::read<int16_t>),
makeNativeMethod("read4Byte", JavaScriptArrayBuffer::read<int32_t>),
makeNativeMethod("read8Byte", JavaScriptArrayBuffer::read<int64_t>),
makeNativeMethod("readFloat", JavaScriptArrayBuffer::read<float>),
makeNativeMethod("readDouble", JavaScriptArrayBuffer::read<double>),
makeNativeMethod("toDirectBuffer", JavaScriptArrayBuffer::toDirectBuffer),
});
}
JavaScriptArrayBuffer::JavaScriptArrayBuffer(
std::weak_ptr<JavaScriptRuntime> runtime,
std::shared_ptr<jsi::ArrayBuffer> jsObject
) : runtimeHolder(std::move(runtime)), arrayBuffer(std::move(jsObject)) {
assert((!runtimeHolder.expired()) && "JS Runtime was used after deallocation");
}
jni::local_ref<JavaScriptArrayBuffer::javaobject> JavaScriptArrayBuffer::newInstance(
JSIContext *jsiContext,
std::weak_ptr<JavaScriptRuntime> runtime,
std::shared_ptr<jsi::ArrayBuffer> jsValue
) {
auto value = JavaScriptArrayBuffer::newObjectCxxArgs(
std::move(runtime),
std::move(jsValue)
);
jsiContext->jniDeallocator->addReference(value);
return value;
}
int JavaScriptArrayBuffer::size() {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
return (int) arrayBuffer->size(rawRuntime);
}
uint8_t *JavaScriptArrayBuffer::data() {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
return arrayBuffer->data(rawRuntime);
}
jni::local_ref<jni::JByteBuffer> JavaScriptArrayBuffer::toDirectBuffer() {
auto buffer = jni::JByteBuffer::wrapBytes(data(), size());
buffer->order(jni::JByteOrder::nativeOrder());
return buffer;
}
std::shared_ptr<jsi::ArrayBuffer> JavaScriptArrayBuffer::jsiArrayBuffer() {
return this->arrayBuffer;
}
}

View File

@@ -0,0 +1,55 @@
#pragma once
#include "JNIDeallocator.h"
#include "JavaScriptRuntime.h"
#include <fbjni/ByteBuffer.h>
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
#include <memory>
namespace expo {
class JavaScriptRuntime;
namespace jni = facebook::jni;
namespace jsi = facebook::jsi;
class JavaScriptArrayBuffer : public jni::HybridClass<JavaScriptArrayBuffer, Destructible> {
public:
static auto constexpr
kJavaDescriptor = "Lexpo/modules/kotlin/jni/JavaScriptArrayBuffer;";
static auto constexpr TAG = "JavaScriptArrayBuffer";
static void registerNatives();
static jni::local_ref<JavaScriptArrayBuffer::javaobject> newInstance(
JSIContext *jSIContext,
std::weak_ptr<JavaScriptRuntime> runtime,
std::shared_ptr<jsi::ArrayBuffer> arrayBuffer
);
JavaScriptArrayBuffer(
std::weak_ptr<JavaScriptRuntime> runtime,
std::shared_ptr<jsi::ArrayBuffer> arrayBuffer
);
[[nodiscard]] int size();
[[nodiscard]] uint8_t* data();
[[nodiscard]] jni::local_ref<jni::JByteBuffer> toDirectBuffer();
[[nodiscard]] std::shared_ptr<jsi::ArrayBuffer> jsiArrayBuffer();
template<class T>
T read(int position) {
return *reinterpret_cast<T *>(this->data() + position);
}
private:
std::weak_ptr<JavaScriptRuntime> runtimeHolder;
std::shared_ptr<jsi::ArrayBuffer> arrayBuffer;
};
} // namespace expo

View File

@@ -0,0 +1,71 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#include "JavaScriptFunction.h"
#include "types/JNIToJSIConverter.h"
#include "types/AnyType.h"
#include "JavaScriptObject.h"
namespace expo {
void JavaScriptFunction::registerNatives() {
registerHybrid({
makeNativeMethod("invoke", JavaScriptFunction::invoke),
});
}
JavaScriptFunction::JavaScriptFunction(
std::weak_ptr<JavaScriptRuntime> runtime,
std::shared_ptr<jsi::Function> jsFunction
) : runtimeHolder(std::move(runtime)), jsFunction(std::move(jsFunction)) {
assert((!runtimeHolder.expired()) && "JS Runtime was used after deallocation");
}
std::shared_ptr<jsi::Function> JavaScriptFunction::get() {
return jsFunction;
}
jobject JavaScriptFunction::invoke(
jni::alias_ref<JavaScriptObject::javaobject> jsThis,
jni::alias_ref<jni::JArrayClass<jobject>> args,
jni::alias_ref<ExpectedType::javaobject> expectedReturnType
) {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
JNIEnv *env = jni::Environment::current();
std::vector<jsi::Value> convertedArgs = convertArray(env, rawRuntime, args);
// TODO(@lukmccall): add better error handling
jsi::Value result = jsThis == nullptr ?
jsFunction->call(
rawRuntime,
(const jsi::Value *) convertedArgs.data(),
convertedArgs.size()
) :
jsFunction->callWithThis(
rawRuntime,
*(jsThis->cthis()->get()),
(const jsi::Value *) convertedArgs.data(),
convertedArgs.size()
);
auto converter = AnyType(jni::make_local(expectedReturnType)).converter;
auto convertedResult = converter->convert(rawRuntime, env, result);
return convertedResult;
}
jni::local_ref<JavaScriptFunction::javaobject> JavaScriptFunction::newInstance(
JSIContext *jsiContext,
std::weak_ptr<JavaScriptRuntime> runtime,
std::shared_ptr<jsi::Function> jsFunction
) {
auto function = JavaScriptFunction::newObjectCxxArgs(
std::move(runtime),
std::move(jsFunction)
);
jsiContext->jniDeallocator->addReference(function);
return function;
}
} // namespace expo

View File

@@ -0,0 +1,57 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include "JSIObjectWrapper.h"
#include "JavaScriptRuntime.h"
#include "types/ExpectedType.h"
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
namespace jni = facebook::jni;
namespace jsi = facebook::jsi;
namespace expo {
class JavaScriptObject;
/**
* Represents any JavaScript function. Its purpose is to expose the `jsi::Function` API back to Kotlin.
*/
class JavaScriptFunction : public jni::HybridClass<JavaScriptFunction, Destructible>, JSIFunctionWrapper {
public:
static auto constexpr
kJavaDescriptor = "Lexpo/modules/kotlin/jni/JavaScriptFunction;";
static auto constexpr TAG = "JavaScriptFunction";
static void registerNatives();
static jni::local_ref<JavaScriptFunction::javaobject> newInstance(
JSIContext *jsiContext,
std::weak_ptr<JavaScriptRuntime> runtime,
std::shared_ptr<jsi::Function> jsFunction
);
JavaScriptFunction(
std::weak_ptr<JavaScriptRuntime> runtime,
std::shared_ptr<jsi::Function> jsFunction
);
std::shared_ptr<jsi::Function> get() override;
private:
friend HybridBase;
std::weak_ptr<JavaScriptRuntime> runtimeHolder;
std::shared_ptr<jsi::Function> jsFunction;
jobject invoke(
jni::alias_ref<jni::HybridClass<JavaScriptObject, Destructible>::javaobject> jsThis,
jni::alias_ref<jni::JArrayClass<jobject>> args,
jni::alias_ref<ExpectedType::javaobject> expectedReturnType
);
};
} // namespace expo

View File

@@ -0,0 +1,45 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#include "JavaScriptModuleObject.h"
#include "NativeModule.h"
#include "decorators/JSDecoratorsBridgingObject.h"
namespace expo {
jni::local_ref<jni::HybridClass<JavaScriptModuleObject>::jhybriddata>
JavaScriptModuleObject::initHybrid(jni::alias_ref<jhybridobject> jThis) {
return makeCxxInstance();
}
void JavaScriptModuleObject::registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", JavaScriptModuleObject::initHybrid),
makeNativeMethod("decorate", JavaScriptModuleObject::decorate)
});
}
std::shared_ptr<jsi::Object> JavaScriptModuleObject::getJSIObject(jsi::Runtime &runtime) {
if (auto object = jsiObject.lock()) {
return object;
}
auto moduleObject = std::make_shared<jsi::Object>(NativeModule::createInstance(runtime));
for (const auto& decorator : this->decorators) {
decorator->decorate(runtime, *moduleObject);
}
jsiObject = moduleObject;
return moduleObject;
}
void JavaScriptModuleObject::decorate(jni::alias_ref<JSDecoratorsBridgingObject::javaobject> jsDecoratorsBridgingObject) noexcept {
this->decorators = jsDecoratorsBridgingObject->cthis()->bridge();
}
std::weak_ptr<jsi::Object> JavaScriptModuleObject::getCachedJSIObject() {
return jsiObject;
}
} // namespace expo

View File

@@ -0,0 +1,69 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
#include <vector>
#include <memory>
#include "decorators/JSDecorator.h"
namespace jni = facebook::jni;
namespace jsi = facebook::jsi;
namespace expo {
class JSIContext;
class JavaScriptModuleObject;
class JSDecoratorsBridgingObject;
class MainRuntimeInstaller;
/**
* A CPP part of the module.
*
* Right now objects of this class are stored by the ModuleHolder to ensure they will live
* as long as the RN context.
*/
class JavaScriptModuleObject : public jni::HybridClass<JavaScriptModuleObject> {
public:
static auto constexpr
kJavaDescriptor = "Lexpo/modules/kotlin/jni/JavaScriptModuleObject;";
static auto constexpr TAG = "JavaScriptModuleObject";
static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis);
static void registerNatives();
/**
* Returns a cached instance of jsi::Object representing this module.
* @param runtime
* @return Wrapped instance of JavaScriptModuleObject::HostObject
*/
std::shared_ptr<jsi::Object> getJSIObject(jsi::Runtime &runtime);
std::weak_ptr<jsi::Object> getCachedJSIObject();
/**
* Decorates the given object with properties and functions provided in the module definition.
*/
void decorate(jni::alias_ref<jni::HybridClass<JSDecoratorsBridgingObject>::javaobject> jsDecoratorsBridgingObject) noexcept;
private:
friend HybridBase;
friend MainRuntimeInstaller;
/**
* A reference to the `jsi::Object`.
* Simple we cached that value to return the same object each time.
* It's a weak reference because the JS runtime holds the actual object.
* Doing that allows the runtime to deallocate jsi::Object if it's not needed anymore.
*/
std::weak_ptr<jsi::Object> jsiObject;
std::vector<std::unique_ptr<JSDecorator>> decorators;
};
} // namespace expo

View File

@@ -0,0 +1,279 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#include "JavaScriptObject.h"
#include "JavaScriptValue.h"
#include "JavaScriptFunction.h"
#include "JavaScriptRuntime.h"
#include "JavaScriptWeakObject.h"
#include "JSITypeConverter.h"
#include "ObjectDeallocator.h"
#include "JavaReferencesCache.h"
#include "JSIContext.h"
#include "JavaScriptArrayBuffer.h"
namespace expo {
void JavaScriptObject::registerNatives() {
registerHybrid({
makeNativeMethod("hasProperty", JavaScriptObject::jniHasProperty),
makeNativeMethod("getProperty", JavaScriptObject::jniGetProperty),
makeNativeMethod("getPropertyNames", JavaScriptObject::jniGetPropertyNames),
makeNativeMethod("createWeak", JavaScriptObject::createWeak),
makeNativeMethod("setBoolProperty", JavaScriptObject::setProperty<bool>),
makeNativeMethod("setDoubleProperty", JavaScriptObject::setProperty<double>),
makeNativeMethod("setStringProperty",
JavaScriptObject::setProperty<jni::alias_ref<jstring>>),
makeNativeMethod("setJSValueProperty",
JavaScriptObject::setProperty<jni::alias_ref<JavaScriptValue::javaobject>>),
makeNativeMethod("setJSObjectProperty",
JavaScriptObject::setProperty<jni::alias_ref<JavaScriptObject::javaobject>>),
makeNativeMethod("unsetProperty", JavaScriptObject::unsetProperty),
makeNativeMethod("defineBoolProperty", JavaScriptObject::defineProperty<bool>),
makeNativeMethod("defineDoubleProperty",
JavaScriptObject::defineProperty<double>),
makeNativeMethod("defineStringProperty",
JavaScriptObject::defineProperty<jni::alias_ref<jstring>>),
makeNativeMethod("defineJSValueProperty",
JavaScriptObject::defineProperty<jni::alias_ref<JavaScriptValue::javaobject>>),
makeNativeMethod("defineJSObjectProperty",
JavaScriptObject::defineProperty<jni::alias_ref<JavaScriptObject::javaobject>>),
makeNativeMethod("defineNativeDeallocator",
JavaScriptObject::defineNativeDeallocator),
makeNativeMethod("setExternalMemoryPressure",
JavaScriptObject::setExternalMemoryPressure),
makeNativeMethod("isArray", JavaScriptObject::isArray),
makeNativeMethod("getArray", JavaScriptObject::getArray),
makeNativeMethod("isArrayBuffer", JavaScriptObject::isArrayBuffer),
makeNativeMethod("getArrayBuffer", JavaScriptObject::getArrayBuffer),
});
}
JavaScriptObject::JavaScriptObject(
std::weak_ptr<JavaScriptRuntime> runtime,
std::shared_ptr<jsi::Object> jsObject
) : runtimeHolder(std::move(runtime)), jsObject(std::move(jsObject)) {
assert((!runtimeHolder.expired()) && "JS Runtime was used after deallocation");
}
std::shared_ptr<jsi::Object> JavaScriptObject::get() {
return jsObject;
}
bool JavaScriptObject::hasProperty(const std::string &name) {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
return jsObject->hasProperty(rawRuntime, name.c_str());
}
jsi::Value JavaScriptObject::getProperty(const std::string &name) {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
return jsObject->getProperty(rawRuntime, name.c_str());
}
bool JavaScriptObject::jniHasProperty(jni::alias_ref<jstring> name) {
return hasProperty(name->toStdString());
}
jni::local_ref<JavaScriptValue::javaobject> JavaScriptObject::jniGetProperty(
jni::alias_ref<jstring> name
) {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
auto result = std::make_shared<jsi::Value>(getProperty(name->toStdString()));
return JavaScriptValue::newInstance(
expo::getJSIContext(rawRuntime),
runtimeHolder,
result
);
}
std::vector<std::string> JavaScriptObject::getPropertyNames() {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
jsi::Array properties = jsObject->getPropertyNames(rawRuntime);
auto size = properties.size(rawRuntime);
std::vector<std::string> names(size);
for (size_t i = 0; i < size; i++) {
auto propertyName = properties
.getValueAtIndex(rawRuntime, i)
.asString(rawRuntime)
.utf8(rawRuntime);
names[i] = propertyName;
}
return names;
}
jni::local_ref<jni::JArrayClass<jstring>> JavaScriptObject::jniGetPropertyNames() {
std::vector<std::string> cResult = getPropertyNames();
auto paredResult = jni::JArrayClass<jstring>::newArray(cResult.size());
for (size_t i = 0; i < cResult.size(); i++) {
paredResult->setElement(i, jni::make_jstring(cResult[i]).get());
}
return paredResult;
}
jni::local_ref<jni::HybridClass<JavaScriptWeakObject, Destructible>::javaobject>
JavaScriptObject::createWeak() {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
return JavaScriptWeakObject::newInstance(
expo::getJSIContext(rawRuntime),
runtimeHolder,
get()
);
}
jni::local_ref<JavaScriptFunction::javaobject> JavaScriptObject::jniAsFunction() {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
auto jsFuncion = std::make_shared<jsi::Function>(jsObject->asFunction(rawRuntime));
return JavaScriptFunction::newInstance(
expo::getJSIContext(rawRuntime),
runtimeHolder,
jsFuncion
);
}
void JavaScriptObject::setProperty(const std::string &name, jsi::Value value) {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
jsObject->setProperty(rawRuntime, name.c_str(), value);
}
void JavaScriptObject::unsetProperty(jni::alias_ref<jstring> name) {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
auto cName = name->toStdString();
jsObject->setProperty(
rawRuntime,
cName.c_str(),
jsi::Value::undefined()
);
}
jsi::Object JavaScriptObject::preparePropertyDescriptor(
jsi::Runtime &jsRuntime,
int options
) {
jsi::Object descriptor(jsRuntime);
descriptor.setProperty(jsRuntime, "configurable", (bool) ((1 << 0) & options));
descriptor.setProperty(jsRuntime, "enumerable", (bool) ((1 << 1) & options));
if ((bool) (1 << 2 & options)) {
descriptor.setProperty(jsRuntime, "writable", true);
}
return descriptor;
}
jni::local_ref<JavaScriptObject::javaobject> JavaScriptObject::newInstance(
JSIContext *jsiContext,
std::weak_ptr<JavaScriptRuntime> runtime,
std::shared_ptr<jsi::Object> jsObject
) {
auto object = JavaScriptObject::newObjectCxxArgs(std::move(runtime), std::move(jsObject));
jsiContext->jniDeallocator->addReference(object);
return object;
}
void JavaScriptObject::defineNativeDeallocator(
jni::alias_ref<JNIFunctionBody::javaobject> deallocator
) {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
jni::global_ref<JNIFunctionBody::javaobject> globalRef = jni::make_global(deallocator);
common::setDeallocator(
rawRuntime,
jsObject,
[globalRef = std::move(globalRef)]() mutable {
auto args = jni::Environment::ensureCurrentThreadIsAttached()->NewObjectArray(
0,
JCacheHolder::get().jObject,
nullptr
);
JNIFunctionBody::invoke(globalRef.get(), args);
globalRef.reset();
}
);
}
void JavaScriptObject::setExternalMemoryPressure(int size) {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
jsObject->setExternalMemoryPressure(rawRuntime, size);
}
bool JavaScriptObject::isArray() {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
return jsObject->isArray(rawRuntime);
}
jni::local_ref<jni::JArrayClass<JavaScriptValue::javaobject>> JavaScriptObject::getArray() {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
auto jsiContext = expo::getJSIContext(rawRuntime);
auto jsArray = jsObject->getArray(rawRuntime);
size_t size = jsArray.size(rawRuntime);
auto result = jni::JArrayClass<JavaScriptValue::javaobject>::newArray(size);
for (size_t i = 0; i < size; i++) {
auto element = JavaScriptValue::newInstance(
jsiContext,
runtimeHolder,
std::make_shared<jsi::Value>(jsArray.getValueAtIndex(rawRuntime, i))
);
result->setElement(i, element.release());
}
return result;
}
bool JavaScriptObject::isArrayBuffer() {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
return jsObject->isArrayBuffer(rawRuntime);
}
jni::local_ref<JavaScriptArrayBuffer::javaobject> JavaScriptObject::getArrayBuffer() {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
auto jsiContext = expo::getJSIContext(rawRuntime);
return JavaScriptArrayBuffer::newInstance(
jsiContext,
runtimeHolder,
std::make_shared<jsi::ArrayBuffer>(jsObject->getArrayBuffer(rawRuntime))
);
}
} // namespace expo

View File

@@ -0,0 +1,151 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include "JSIObjectWrapper.h"
#include "JSITypeConverter.h"
#include "JavaScriptRuntime.h"
#include "JNIFunctionBody.h"
#include "JNIDeallocator.h"
#include "JSIUtils.h"
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
#include <memory>
namespace jni = facebook::jni;
namespace jsi = facebook::jsi;
namespace expo {
class JavaScriptFunction;
class JavaScriptValue;
class JavaScriptWeakObject;
class JavaScriptArrayBuffer;
/**
* Represents any JavaScript object. Its purpose is to exposes `jsi::Object` API back to Kotlin.
*/
class JavaScriptObject : public jni::HybridClass<JavaScriptObject, Destructible>, JSIObjectWrapper {
public:
static auto constexpr
kJavaDescriptor = "Lexpo/modules/kotlin/jni/JavaScriptObject;";
static auto constexpr TAG = "JavaScriptObject";
static void registerNatives();
static jni::local_ref<JavaScriptObject::javaobject> newInstance(
JSIContext *jsiContext,
std::weak_ptr<JavaScriptRuntime> runtime,
std::shared_ptr<jsi::Object> jsObject
);
JavaScriptObject(
std::weak_ptr<JavaScriptRuntime> runtime,
std::shared_ptr<jsi::Object> jsObject
);
std::shared_ptr<jsi::Object> get() override;
/**
* @return a bool whether the object has a property with the given name
*/
bool hasProperty(const std::string &name);
/**
* @return the property of the object with the given name.
* If the name isn't a property on the object, returns the `jsi::Value::undefined` value.
*/
jsi::Value getProperty(const std::string &name);
/**
* @return a vector consisting of all enumerable property names in the object and its prototype chain.
*/
std::vector<std::string> getPropertyNames();
void setProperty(const std::string &name, jsi::Value value);
static jsi::Object preparePropertyDescriptor(jsi::Runtime &jsRuntime, int options);
void defineNativeDeallocator(
jni::alias_ref<JNIFunctionBody::javaobject> deallocator
);
/**
* Sets the memory pressure to inform the GC about how much external memory is associated with that specific JS object.
*/
void setExternalMemoryPressure(int size);
[[nodiscard]] bool isArray();
[[nodiscard]] jni::local_ref<jni::JArrayClass<jni::HybridClass<JavaScriptValue, Destructible>::javaobject>> getArray();
[[nodiscard]] bool isArrayBuffer();
[[nodiscard]] jni::local_ref<jni::HybridClass<JavaScriptArrayBuffer, Destructible>::javaobject> getArrayBuffer();
protected:
std::weak_ptr<JavaScriptRuntime> runtimeHolder;
std::shared_ptr<jsi::Object> jsObject;
private:
friend HybridBase;
bool jniHasProperty(jni::alias_ref<jstring> name);
jni::local_ref<jni::HybridClass<JavaScriptValue, Destructible>::javaobject> jniGetProperty(
jni::alias_ref<jstring> name
);
jni::local_ref<jni::JArrayClass<jstring>> jniGetPropertyNames();
jni::local_ref<jni::HybridClass<JavaScriptWeakObject, Destructible>::javaobject> createWeak();
jni::local_ref<jni::HybridClass<JavaScriptFunction, Destructible>::javaobject> jniAsFunction();
/**
* Unsets property with the given name.
*/
void unsetProperty(jni::alias_ref<jstring> name);
/**
* A template to generate different versions of the `setProperty` method based on the `jsi_type_converter` trait.
* Those generated methods will be exported and visible in the Kotlin codebase.
* On the other hand, we could just make one function that would take a generic Java Object,
* but then we would have to decide what to do with it and how to convert it to jsi::Value
* in cpp. That would be expensive. So it's easier to ensure that
* we call the correct version of `setProperty` in the Kotlin code.
*
* This template will work only if the jsi_type_converter exists for a given type.
*/
template<
class T,
typename = std::enable_if_t<is_jsi_type_converter_defined<T>>
>
void setProperty(jni::alias_ref<jstring> name, T value) {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
auto cName = name->toStdString();
jsObject->setProperty(
rawRuntime,
cName.c_str(),
jsi_type_converter<T>::convert(rawRuntime, value)
);
}
template<
class T,
typename = std::enable_if_t<is_jsi_type_converter_defined<T>>
>
void defineProperty(jni::alias_ref<jstring> name, T value, int options) {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
auto cName = name->toStdString();
jsi::Object descriptor = preparePropertyDescriptor(rawRuntime, options);
descriptor.setProperty(rawRuntime, "value", jsi_type_converter<T>::convert(rawRuntime, value));
common::defineProperty(rawRuntime, jsObject.get(), cName.c_str(), std::move(descriptor));
}
};
} // namespace expo

View File

@@ -0,0 +1,85 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#include "JavaScriptRuntime.h"
#include "JavaScriptValue.h"
#include "JavaScriptObject.h"
#include "Exceptions.h"
#include "JSIContext.h"
#include "JSIUtils.h"
namespace jsi = facebook::jsi;
namespace expo {
JavaScriptRuntime::JavaScriptRuntime(
jsi::Runtime *runtime,
std::shared_ptr<react::CallInvoker> jsInvoker
) : jsInvoker(std::move(jsInvoker)), runtime(runtime) {
}
jsi::Runtime &JavaScriptRuntime::get() const noexcept {
return *runtime;
}
jni::local_ref<JavaScriptValue::javaobject>
JavaScriptRuntime::evaluateScript(const std::string &script) {
auto scriptBuffer = std::make_shared<jsi::StringBuffer>(script);
try {
return JavaScriptValue::newInstance(
getJSIContext(get()),
weak_from_this(),
std::make_shared<jsi::Value>(runtime->evaluateJavaScript(scriptBuffer, "<<evaluated>>"))
);
} catch (const jsi::JSError &error) {
jni::throwNewJavaException(
JavaScriptEvaluateException::create(
error.getMessage(),
error.getStack()
).get()
);
} catch (const jsi::JSIException &error) {
jni::throwNewJavaException(
JavaScriptEvaluateException::create(
error.what(),
""
).get()
);
}
}
void JavaScriptRuntime::evaluateVoidScript(const std::string &script) {
auto scriptBuffer = std::make_shared<jsi::StringBuffer>(script);
try {
runtime->evaluateJavaScript(scriptBuffer, "<<evaluated>>");
} catch (const jsi::JSError &error) {
jni::throwNewJavaException(
JavaScriptEvaluateException::create(
error.getMessage(),
error.getStack()
).get()
);
} catch (const jsi::JSIException &error) {
jni::throwNewJavaException(
JavaScriptEvaluateException::create(
error.what(),
""
).get()
);
}
}
jni::local_ref<JavaScriptObject::javaobject> JavaScriptRuntime::global() noexcept {
auto global = std::make_shared<jsi::Object>(runtime->global());
return JavaScriptObject::newInstance(getJSIContext(get()), weak_from_this(), global);
}
jni::local_ref<JavaScriptObject::javaobject> JavaScriptRuntime::createObject() noexcept {
auto newObject = std::make_shared<jsi::Object>(*runtime);
return JavaScriptObject::newInstance(getJSIContext(get()), weak_from_this(), newObject);
}
void JavaScriptRuntime::drainJSEventLoop() {
while (!runtime->drainMicrotasks()) {}
}
} // namespace expo

View File

@@ -0,0 +1,87 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include "JNIDeallocator.h"
#include <jsi/jsi.h>
#include <fbjni/fbjni.h>
#include <ReactCommon/CallInvoker.h>
namespace jsi = facebook::jsi;
namespace jni = facebook::jni;
namespace react = facebook::react;
namespace expo {
class JavaScriptValue;
class JavaScriptObject;
class JSIContext;
/**
* A wrapper for the jsi::Runtime.
* This class is used as a bridge between CPP and Kotlin and to encapsulate common runtime helper functions.
*
* Instances of this class should be managed using a shared smart pointer.
* To pass runtime information to all of `JavaScriptValue` and `JavaScriptObject` we use `weak_from_this()`
* that requires that the object is held via a smart pointer. Otherwise, `weak_from_this()` returns `nullptr`.
*/
class JavaScriptRuntime : public std::enable_shared_from_this<JavaScriptRuntime> {
public:
JavaScriptRuntime(
jsi::Runtime *runtime,
std::shared_ptr<react::CallInvoker> jsInvoker
);
/**
* Returns the underlying runtime object.
*/
jsi::Runtime &get() const noexcept;
/**
* Evaluates given JavaScript source code.
* @throws if the input format is unknown, or evaluation causes an error,
* a jni::JniException<JavaScriptEvaluateException> will be thrown.
*/
jni::local_ref<jni::HybridClass<JavaScriptValue, Destructible>::javaobject> evaluateScript(
const std::string &script
);
/**
* Evaluates given JavaScript source code.
* @throws if the input format is unknown, or evaluation causes an error,
* a jni::JniException<JavaScriptEvaluateException> will be thrown.
*/
void evaluateVoidScript(
const std::string &script
);
/**
* Returns the runtime global object for use in Kotlin.
*/
jni::local_ref<jni::HybridClass<JavaScriptObject, Destructible>::javaobject> global() noexcept;
/**
* Creates a new object for use in Kotlin.
*/
jni::local_ref<jni::HybridClass<JavaScriptObject, Destructible>::javaobject> createObject() noexcept;
/**
* Drains the JavaScript VM internal Microtask (a.k.a. event loop) queue.
*/
void drainJSEventLoop();
std::shared_ptr<react::CallInvoker> jsInvoker;
private:
/**
* Raw pointer to the runtime. We do not own this - it's managed by React Native.
* The runtime's lifetime is guaranteed to exceed JavaScriptRuntime's lifetime,
* as JSIContext::prepareForDeallocation() invalidates all weak references before
* the runtime is deallocated.
*/
jsi::Runtime *runtime;
};
} // namespace expo

View File

@@ -0,0 +1,102 @@
#include "JavaScriptTypedArray.h"
#include "JavaScriptRuntime.h"
#include "JSIContext.h"
namespace expo {
JavaScriptTypedArray::JavaScriptTypedArray(
std::weak_ptr<JavaScriptRuntime> runtime,
std::shared_ptr<jsi::Object> jsObject
) : jni::HybridClass<JavaScriptTypedArray, JavaScriptObject>(std::move(runtime),
std::move(jsObject)) {
auto jsRuntime = runtimeHolder.lock();
assert((jsRuntime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = jsRuntime->get();
typedArrayWrapper = std::make_shared<expo::TypedArray>(rawRuntime, *JavaScriptObject::get());
rawPointer = static_cast<char *>(typedArrayWrapper->getRawPointer(rawRuntime));
}
void JavaScriptTypedArray::registerNatives() {
registerHybrid({
makeNativeMethod("getRawKind", JavaScriptTypedArray::getRawKind),
makeNativeMethod("toDirectBuffer", JavaScriptTypedArray::toDirectBuffer),
makeNativeMethod("read", JavaScriptTypedArray::readBuffer),
makeNativeMethod("write", JavaScriptTypedArray::writeBuffer),
makeNativeMethod("readByte", JavaScriptTypedArray::read<int8_t>),
makeNativeMethod("read2Byte", JavaScriptTypedArray::read<int16_t>),
makeNativeMethod("read4Byte", JavaScriptTypedArray::read<int32_t>),
makeNativeMethod("read8Byte", JavaScriptTypedArray::read<int64_t>),
makeNativeMethod("readFloat", JavaScriptTypedArray::read<float>),
makeNativeMethod("readDouble", JavaScriptTypedArray::read<double>),
makeNativeMethod("writeByte", JavaScriptTypedArray::write<int8_t>),
makeNativeMethod("write2Byte", JavaScriptTypedArray::write<int16_t>),
makeNativeMethod("write4Byte", JavaScriptTypedArray::write<int32_t>),
makeNativeMethod("write8Byte", JavaScriptTypedArray::write<int64_t>),
makeNativeMethod("writeFloat", JavaScriptTypedArray::write<float>),
makeNativeMethod("writeDouble", JavaScriptTypedArray::write<double>),
});
}
int JavaScriptTypedArray::getRawKind() {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
return (int) typedArrayWrapper->getKind(rawRuntime);
}
jni::local_ref<jni::JByteBuffer> JavaScriptTypedArray::toDirectBuffer() {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
auto byteLength = typedArrayWrapper->byteLength(rawRuntime);
auto byteBuffer = jni::JByteBuffer::wrapBytes(
static_cast<uint8_t *>(typedArrayWrapper->getRawPointer(rawRuntime)),
byteLength
);
byteBuffer->order(jni::JByteOrder::nativeOrder());
return byteBuffer;
}
void JavaScriptTypedArray::readBuffer(
jni::alias_ref<jni::JArrayByte> buffer,
int position,
int size
) {
buffer->setRegion(
0,
size,
reinterpret_cast<const signed char *>(rawPointer + position)
);
}
void JavaScriptTypedArray::writeBuffer(
jni::alias_ref<jni::JArrayByte> buffer,
int position,
int size
) {
auto region = buffer->getRegion(0, size);
memcpy(rawPointer + position, region.get(), size);
}
jni::local_ref<JavaScriptTypedArray::javaobject> JavaScriptTypedArray::newInstance(
JSIContext *jSIContext,
std::weak_ptr<JavaScriptRuntime> runtime,
std::shared_ptr<jsi::Object> jsObject
) {
auto object = JavaScriptTypedArray::newObjectCxxArgs(
std::move(runtime),
std::move(jsObject)
);
jSIContext->jniDeallocator->addReference(object);
return object;
}
}

View File

@@ -0,0 +1,70 @@
#pragma once
#include "TypedArray.h"
#include "JavaScriptObject.h"
#include <fbjni/fbjni.h>
#include <fbjni/ByteBuffer.h>
#include <jsi/jsi.h>
#include <memory>
namespace expo {
class JavaScriptRuntime;
namespace jni = facebook::jni;
namespace jsi = facebook::jsi;
class JavaScriptTypedArray : public jni::HybridClass<JavaScriptTypedArray, JavaScriptObject> {
public:
static auto constexpr
kJavaDescriptor = "Lexpo/modules/kotlin/jni/JavaScriptTypedArray;";
static auto constexpr TAG = "JavaScriptTypedArray";
static void registerNatives();
static jni::local_ref<JavaScriptTypedArray::javaobject> newInstance(
JSIContext *jSIContext,
std::weak_ptr<JavaScriptRuntime> runtime,
std::shared_ptr<jsi::Object> jsObject
);
JavaScriptTypedArray(
std::weak_ptr<JavaScriptRuntime> runtime,
std::shared_ptr<jsi::Object> jsObject
);
/**
* Gets a raw kind of the underlying typed array.
*/
int getRawKind();
/**
* Converts typed array into a direct byte buffer.
*/
jni::local_ref<jni::JByteBuffer> toDirectBuffer();
private:
std::shared_ptr<expo::TypedArray> typedArrayWrapper;
/**
* Cached pointer to the beginning of the byte buffer.
*/
char *rawPointer;
void readBuffer(jni::alias_ref<jni::JArrayByte> buffer, int position, int size);
void writeBuffer(jni::alias_ref<jni::JArrayByte> buffer, int position, int size);
template<class T>
T read(int position) {
return *reinterpret_cast<T *>(rawPointer + position);
}
template<class T>
void write(int position, T value) {
*reinterpret_cast<T *>(rawPointer + position) = value;
}
};
} // namespace expo

View File

@@ -0,0 +1,248 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#include "JavaScriptValue.h"
#include "JavaScriptRuntime.h"
#include "JavaScriptObject.h"
#include "JavaScriptTypedArray.h"
#include "JavaScriptArrayBuffer.h"
#include "JavaScriptFunction.h"
#include "TypedArray.h"
#include "Exceptions.h"
#include "JSIContext.h"
namespace expo {
void JavaScriptValue::registerNatives() {
registerHybrid({
makeNativeMethod("kind", JavaScriptValue::jniKind),
makeNativeMethod("isNull", JavaScriptValue::isNull),
makeNativeMethod("isUndefined", JavaScriptValue::isUndefined),
makeNativeMethod("isBool", JavaScriptValue::isBool),
makeNativeMethod("isNumber", JavaScriptValue::isNumber),
makeNativeMethod("isString", JavaScriptValue::isString),
makeNativeMethod("isSymbol", JavaScriptValue::isSymbol),
makeNativeMethod("isFunction", JavaScriptValue::isFunction),
makeNativeMethod("isArray", JavaScriptValue::isArray),
makeNativeMethod("isTypedArray", JavaScriptValue::isTypedArray),
makeNativeMethod("isObject", JavaScriptValue::isObject),
makeNativeMethod("getBool", JavaScriptValue::getBool),
makeNativeMethod("getDouble", JavaScriptValue::getDouble),
makeNativeMethod("getString", JavaScriptValue::jniGetString),
makeNativeMethod("getObject", JavaScriptValue::getObject),
makeNativeMethod("getArray", JavaScriptValue::getArray),
makeNativeMethod("getTypedArray", JavaScriptValue::getTypedArray),
makeNativeMethod("jniGetFunction", JavaScriptValue::jniGetFunction),
});
}
JavaScriptValue::JavaScriptValue(
std::weak_ptr<JavaScriptRuntime> runtime,
std::shared_ptr<jsi::Value> jsValue
) : runtimeHolder(std::move(runtime)), jsValue(std::move(jsValue)) {
assert((!runtimeHolder.expired()) && "JS Runtime was used after deallocation");
}
std::shared_ptr<jsi::Value> JavaScriptValue::get() {
return jsValue;
}
std::string JavaScriptValue::kind() {
if (isNull()) {
return "null";
}
if (isUndefined()) {
return "undefined";
}
if (isBool()) {
return "boolean";
}
if (isNumber()) {
return "number";
}
if (isString()) {
return "string";
}
if (isSymbol()) {
return "symbol";
}
if (isFunction()) {
return "function";
}
if (isArray()) {
return "array";
}
if (isObject()) {
return "object";
}
throwNewJavaException(
UnexpectedException::create("Unknown type").get()
);
}
bool JavaScriptValue::isNull() {
return jsValue->isNull();
}
bool JavaScriptValue::isUndefined() {
return jsValue->isUndefined();
}
bool JavaScriptValue::isBool() {
return jsValue->isBool();
}
bool JavaScriptValue::isNumber() {
return jsValue->isNumber();
}
bool JavaScriptValue::isString() {
return jsValue->isString();
}
bool JavaScriptValue::isSymbol() {
return jsValue->isSymbol();
}
bool JavaScriptValue::isFunction() {
if (jsValue->isObject()) {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
return jsValue->asObject(rawRuntime).isFunction(rawRuntime);
}
return false;
}
bool JavaScriptValue::isArray() {
if (jsValue->isObject()) {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
return jsValue->asObject(rawRuntime).isArray(rawRuntime);
}
return false;
}
bool JavaScriptValue::isObject() {
return jsValue->isObject();
}
bool JavaScriptValue::isTypedArray() {
if (jsValue->isObject()) {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
return expo::isTypedArray(rawRuntime, jsValue->getObject(rawRuntime));
}
return false;
}
bool JavaScriptValue::getBool() {
return jsValue->getBool();
}
double JavaScriptValue::getDouble() {
return jsValue->getNumber();
}
std::string JavaScriptValue::getString() {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
return jsValue->getString(rawRuntime).utf8(rawRuntime);
}
jni::local_ref<JavaScriptObject::javaobject> JavaScriptValue::getObject() {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
auto jsObject = std::make_shared<jsi::Object>(jsValue->getObject(rawRuntime));
return JavaScriptObject::newInstance(
expo::getJSIContext(rawRuntime),
runtimeHolder,
jsObject
);
}
jni::local_ref<JavaScriptFunction::javaobject> JavaScriptValue::jniGetFunction() {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
auto jsFunction = std::make_shared<jsi::Function>(
jsValue->getObject(rawRuntime).asFunction(rawRuntime));
return JavaScriptFunction::newInstance(
expo::getJSIContext(rawRuntime),
runtimeHolder,
jsFunction
);
}
jni::local_ref<jni::JArrayClass<JavaScriptValue::javaobject>> JavaScriptValue::getArray() {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
auto jsiContext = expo::getJSIContext(rawRuntime);
auto jsArray = jsValue
->getObject(rawRuntime)
.asArray(rawRuntime);
size_t size = jsArray.size(rawRuntime);
auto result = jni::JArrayClass<JavaScriptValue::javaobject>::newArray(size);
for (size_t i = 0; i < size; i++) {
auto element = JavaScriptValue::newInstance(
jsiContext,
runtimeHolder,
std::make_shared<jsi::Value>(jsArray.getValueAtIndex(rawRuntime, i))
);
result->setElement(i, element.release());
}
return result;
}
jni::local_ref<jstring> JavaScriptValue::jniKind() {
auto result = kind();
return jni::make_jstring(result);
}
jni::local_ref<jstring> JavaScriptValue::jniGetString() {
auto result = getString();
return jni::make_jstring(result);
}
jni::local_ref<JavaScriptTypedArray::javaobject> JavaScriptValue::getTypedArray() {
auto runtime = runtimeHolder.lock();
assert((runtime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = runtime->get();
auto jsObject = std::make_shared<jsi::Object>(jsValue->getObject(rawRuntime));
return JavaScriptTypedArray::newInstance(
expo::getJSIContext(rawRuntime),
runtimeHolder,
jsObject
);
}
jni::local_ref<JavaScriptValue::javaobject> JavaScriptValue::newInstance(
JSIContext *jsiContext,
std::weak_ptr<JavaScriptRuntime> runtime,
std::shared_ptr<jsi::Value> jsValue
) {
auto value = JavaScriptValue::newObjectCxxArgs(
std::move(runtime),
std::move(jsValue)
);
jsiContext->jniDeallocator->addReference(value);
return value;
}
} // namespace expo

View File

@@ -0,0 +1,99 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include "JSIObjectWrapper.h"
#include "JavaScriptTypedArray.h"
#include "JavaScriptArrayBuffer.h"
#include "JNIDeallocator.h"
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
#include <memory>
namespace jni = facebook::jni;
namespace jsi = facebook::jsi;
namespace expo {
class JavaScriptRuntime;
class JavaScriptObject;
class JavaScriptTypedArray;
class JavaScriptArrayBuffer;
class JavaScriptFunction;
/**
* Represents any JavaScript value. Its purpose is to expose the `jsi::Value` API back to Kotlin.
*/
class JavaScriptValue : public jni::HybridClass<JavaScriptValue, Destructible>, JSIValueWrapper {
public:
static auto constexpr
kJavaDescriptor = "Lexpo/modules/kotlin/jni/JavaScriptValue;";
static auto constexpr TAG = "JavaScriptValue";
static void registerNatives();
static jni::local_ref<JavaScriptValue::javaobject> newInstance(
JSIContext *jsiContext,
std::weak_ptr<JavaScriptRuntime> runtime,
std::shared_ptr<jsi::Value> jsValue
);
JavaScriptValue(
std::weak_ptr<JavaScriptRuntime> runtime,
std::shared_ptr<jsi::Value> jsValue
);
std::shared_ptr<jsi::Value> get() override;
std::string kind();
bool isNull();
bool isUndefined();
bool isBool();
bool isNumber();
bool isString();
bool isSymbol();
bool isFunction();
bool isArray();
bool isObject();
bool isTypedArray();
bool getBool();
double getDouble();
std::string getString();
jni::local_ref<jni::HybridClass<JavaScriptObject, Destructible>::javaobject> getObject();
jni::local_ref<jni::JArrayClass<JavaScriptValue::javaobject>> getArray();
jni::local_ref<JavaScriptTypedArray::javaobject> getTypedArray();
jni::local_ref<jni::HybridClass<JavaScriptFunction, Destructible>::javaobject> jniGetFunction();
private:
friend HybridBase;
std::weak_ptr<JavaScriptRuntime> runtimeHolder;
std::shared_ptr<jsi::Value> jsValue;
jni::local_ref<jstring> jniKind();
jni::local_ref<jstring> jniGetString();
};
} // namespace expo

View File

@@ -0,0 +1,60 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#include "JavaScriptWeakObject.h"
#include "JSIContext.h"
namespace expo {
void JavaScriptWeakObject::registerNatives() {
registerHybrid({
makeNativeMethod("lock", JavaScriptWeakObject::lock),
});
}
std::shared_ptr<jsi::WeakObject> JavaScriptWeakObject::getWeak() {
return _weakObject;
}
jni::local_ref<JavaScriptObject::javaobject> JavaScriptWeakObject::lock() {
auto jsRuntime = _runtimeHolder.lock();
assert((jsRuntime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = jsRuntime->get();
jsi::Value value = _weakObject->lock(rawRuntime);
if (value.isUndefined()) {
return nullptr;
}
std::shared_ptr<jsi::Object> objectPtr =
std::make_shared<jsi::Object>(value.asObject(rawRuntime));
if (!objectPtr) {
return nullptr;
}
return JavaScriptObject::newInstance(
expo::getJSIContext(rawRuntime),
_runtimeHolder, objectPtr
);
}
jni::local_ref<jni::HybridClass<JavaScriptWeakObject, Destructible>::javaobject>
JavaScriptWeakObject::newInstance(
JSIContext *jSIContext,
std::weak_ptr<JavaScriptRuntime> runtime,
std::shared_ptr<jsi::Object> jsObject) {
auto weakObject = JavaScriptWeakObject::newObjectCxxArgs(std::move(runtime),
std::move(jsObject));
jSIContext->jniDeallocator->addReference(weakObject);
return weakObject;
}
JavaScriptWeakObject::JavaScriptWeakObject(
const std::weak_ptr<JavaScriptRuntime> &runtime,
const std::shared_ptr<jsi::Object> &jsObject
) : _runtimeHolder(std::move(runtime)) {
auto jsRuntime = _runtimeHolder.lock();
assert((jsRuntime != nullptr) && "JS Runtime was used after deallocation");
auto &rawRuntime = jsRuntime->get();
_weakObject = std::make_shared<jsi::WeakObject>(rawRuntime, *jsObject);
}
} // namespace expo

View File

@@ -0,0 +1,57 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#pragma once
#include "JNIDeallocator.h"
#include "JavaScriptObject.h"
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
#include <memory>
namespace jni = facebook::jni;
namespace jsi = facebook::jsi;
namespace expo {
class JavaScriptObject;
/**
* Represents to a jsi::WeakObject.
*/
class JavaScriptWeakObject
: public jni::HybridClass<JavaScriptWeakObject, Destructible> {
public:
static auto constexpr kJavaDescriptor =
"Lexpo/modules/kotlin/jni/JavaScriptWeakObject;";
static auto constexpr TAG = "JavaScriptWeakObject";
static void registerNatives();
static jni::local_ref<
jni::HybridClass<JavaScriptWeakObject, Destructible>::javaobject
> newInstance(
JSIContext *jSIContext,
std::weak_ptr<JavaScriptRuntime> runtime,
std::shared_ptr<jsi::Object> jsObject
);
jni::local_ref<JavaScriptObject::javaobject> lock();
std::shared_ptr<jsi::WeakObject> getWeak();
private:
JavaScriptWeakObject(
const std::weak_ptr<JavaScriptRuntime> &runtime,
const std::shared_ptr<jsi::Object> &jsObject
);
private:
friend HybridBase;
std::weak_ptr<JavaScriptRuntime> _runtimeHolder;
std::shared_ptr<jsi::WeakObject> _weakObject;
};
} // namespace expo

View File

@@ -0,0 +1,340 @@
#include "MethodMetadata.h"
#include "JSIContext.h"
#include "JavaScriptValue.h"
#include "JavaScriptObject.h"
#include "JavaScriptTypedArray.h"
#include "JavaReferencesCache.h"
#include "Exceptions.h"
#include "JavaCallback.h"
#include "types/JNIToJSIConverter.h"
#include "JSReferencesCache.h"
#include <utility>
#include <functional>
#include <unistd.h>
#include <optional>
#include <react/bridging/LongLivedObject.h>
namespace jni = facebook::jni;
namespace jsi = facebook::jsi;
namespace react = facebook::react;
namespace expo {
jni::local_ref<JavaCallback::JavaPart> createJavaCallback(
jsi::Function &&resolveFunction,
jsi::Function &&rejectFunction,
jsi::Runtime &rt
) {
JSIContext *jsiContext = getJSIContext(rt);
std::shared_ptr<react::CallInvoker> jsInvoker = jsiContext->runtimeHolder->jsInvoker;
std::shared_ptr<JavaCallback::CallbackContext> callbackContext = std::make_shared<JavaCallback::CallbackContext>(
rt,
std::move(jsInvoker),
std::move(resolveFunction),
std::move(rejectFunction)
);
facebook::react::LongLivedObjectCollection::get(rt).add(callbackContext);
return JavaCallback::newInstance(jsiContext, std::move(callbackContext));
}
jobjectArray MethodMetadata::convertJSIArgsToJNI(
JNIEnv *env,
jsi::Runtime &rt,
const jsi::Value &thisValue,
const jsi::Value *args,
size_t count
) {
// This function takes the owner, so the args number is higher because we have access to the thisValue.
if (info.takesOwner) {
count++;
}
// The `count < argTypes.size()` case is handled by the Kotlin part
if (count > info.argTypes.size()) {
throwNewJavaException(
InvalidArgsNumberException::create(
count,
info.argTypes.size()
).get()
);
}
auto argumentArray = env->NewObjectArray(
count,
JCacheHolder::get().jObject,
nullptr
);
#define CONVERT(arg, type, index) \
try { \
auto converterValue = type->converter->convert(rt, env, arg); \
env->SetObjectArrayElement(argumentArray, index, converterValue); \
env->DeleteLocalRef(converterValue); \
} catch (std::exception &exception) { \
auto stringRepresentation = arg.toString(rt).utf8(rt); \
throwNewJavaException( \
UnexpectedException::create( \
"[" + this->info.name + "] Cannot convert '" + stringRepresentation + \
"' to a Kotlin type. " + exception.what()).get() \
); \
}
if (!info.takesOwner) {
for (size_t argIndex = 0; argIndex < count; argIndex++) {
const jsi::Value &arg = args[argIndex];
auto &type = info.argTypes[argIndex];
CONVERT(arg, type, argIndex)
}
} else {
auto &thisType = info.argTypes[0];
CONVERT(thisValue, thisType, 0)
for (size_t argIndex = 1; argIndex < count; argIndex++) {
const jsi::Value &arg = args[argIndex - 1];
auto &type = info.argTypes[argIndex];
CONVERT(arg, type, argIndex)
}
}
#undef CONVERT
return argumentArray;
}
MethodMetadata::MethodMetadata(
Info info,
jni::global_ref<jobject> &&jBodyReference
) : info(std::move(info)),
jBodyReference(std::move(jBodyReference)) {
}
std::shared_ptr<jsi::Function> MethodMetadata::toJSFunction(
jsi::Runtime &runtime
) {
if (body == nullptr) {
if (jBodyReference == nullptr) {
return nullptr;
}
if (info.isAsync) {
body = std::make_shared<jsi::Function>(toAsyncFunction(runtime));
} else {
body = std::make_shared<jsi::Function>(toSyncFunction(runtime));
}
}
return body;
}
jsi::Function MethodMetadata::toSyncFunction(
jsi::Runtime &runtime
) {
auto weakThis = weak_from_this();
return jsi::Function::createFromHostFunction(
runtime,
getJSIContext(runtime)->jsRegistry->getPropNameID(runtime, info.name),
info.argTypes.size(),
[weakThis = std::move(weakThis)](
jsi::Runtime &rt,
const jsi::Value &thisValue,
const jsi::Value *args,
size_t count
) -> jsi::Value {
try {
auto thisPtr = weakThis.lock();
if (thisPtr == nullptr) {
return jsi::Value::undefined();
}
return thisPtr->callSync(
rt,
thisValue,
args,
count
);
} catch (jni::JniException &jniException) {
rethrowAsCodedError(rt, jniException);
}
});
}
jni::local_ref<jobject> MethodMetadata::callJNISync(
JNIEnv *env,
jsi::Runtime &rt,
const jsi::Value &thisValue,
const jsi::Value *args,
size_t count
) {
if (this->jBodyReference == nullptr) {
return nullptr;
}
auto convertedArgs = convertJSIArgsToJNI(env, rt, thisValue, args, count);
auto result = JNIFunctionBody::invoke(this->jBodyReference.get(), convertedArgs);
env->DeleteLocalRef(convertedArgs);
return result;
}
jsi::Value MethodMetadata::callSync(
jsi::Runtime &rt,
const jsi::Value &thisValue,
const jsi::Value *args,
size_t count
) {
JNIEnv *env = jni::Environment::current();
/**
* This will push a new JNI stack frame for the LocalReferences in this
* function call. When the stack frame for this lambda is popped,
* all LocalReferences are deleted.
*/
jni::JniLocalScope scope(env, (int) count);
auto result = this->callJNISync(env, rt, thisValue, args, count);
return convert(env, rt, this->info.returnType, result);
}
jsi::Function MethodMetadata::toAsyncFunction(
jsi::Runtime &runtime
) {
auto weakThis = weak_from_this();
return jsi::Function::createFromHostFunction(
runtime,
getJSIContext(runtime)->jsRegistry->getPropNameID(runtime, info.name),
info.argTypes.size(),
[weakThis = std::move(weakThis)](
jsi::Runtime &rt,
const jsi::Value &thisValue,
const jsi::Value *args,
size_t count
) -> jsi::Value {
auto thisPtr = weakThis.lock();
if (thisPtr == nullptr) {
return jsi::Value::undefined();
}
JSIContext *jsiContext = getJSIContext(rt);
JNIEnv *env = jni::Environment::current();
/**
* This will push a new JNI stack frame for the LocalReferences in this
* function call. When the stack frame for this lambda is popped,
* all LocalReferences are deleted.
*/
jni::JniLocalScope scope(env, (int) count);
auto &Promise = jsiContext->jsRegistry->getObject<jsi::Function>(
JSReferencesCache::JSKeys::PROMISE
);
try {
auto convertedArgs = thisPtr->convertJSIArgsToJNI(env, rt, thisValue, args, count);
auto globalConvertedArgs = (jobjectArray) env->NewGlobalRef(convertedArgs);
env->DeleteLocalRef(convertedArgs);
// Creates a JSI promise
jsi::Value promise = Promise.callAsConstructor(
rt,
thisPtr->createPromiseBody(rt, globalConvertedArgs)
);
return promise;
} catch (jni::JniException &jniException) {
jni::local_ref<jni::JThrowable> unboxedThrowable = jniException.getThrowable();
if (!unboxedThrowable->isInstanceOf(CodedException::javaClassLocal())) {
unboxedThrowable = UnexpectedException::create(jniException.what());
}
auto codedException = jni::static_ref_cast<CodedException>(unboxedThrowable);
auto code = codedException->getCode();
auto message = codedException->getLocalizedMessage().value_or("");
jsi::Value promise = Promise.callAsConstructor(
rt,
jsi::Function::createFromHostFunction(
rt,
jsiContext->jsRegistry->getPropNameID(rt, "promiseFn"),
2,
[code, message](
jsi::Runtime &rt,
const jsi::Value &thisVal,
const jsi::Value *promiseConstructorArgs,
size_t promiseConstructorArgCount
) {
if (promiseConstructorArgCount != 2) {
throw std::invalid_argument("Promise fn arg count must be 2");
}
jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt);
rejectJSIFn.call(
rt,
makeCodedError(
rt,
jsi::String::createFromUtf8(rt, code),
jsi::String::createFromUtf8(rt, message)
)
);
return jsi::Value::undefined();
}
)
);
return promise;
}
}
);
}
jsi::Function MethodMetadata::createPromiseBody(
jsi::Runtime &runtime,
jobjectArray globalArgs
) {
return jsi::Function::createFromHostFunction(
runtime,
getJSIContext(runtime)->jsRegistry->getPropNameID(runtime, "promiseFn"),
2,
[this, globalArgs](
jsi::Runtime &rt,
const jsi::Value &thisVal,
const jsi::Value *promiseConstructorArgs,
size_t promiseConstructorArgCount
) {
if (promiseConstructorArgCount != 2) {
throw std::invalid_argument("Promise fn arg count must be 2");
}
jsi::Function resolveJSIFn = promiseConstructorArgs[0].getObject(rt).getFunction(rt);
jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt);
jobject javaCallback = createJavaCallback(
std::move(resolveJSIFn),
std::move(rejectJSIFn),
rt
).release();
JNIEnv *env = jni::Environment::current();
auto &jPromise = JCacheHolder::get().jPromise;
// Creates a promise object
jobject promise = env->NewObject(
jPromise.clazz,
jPromise.constructor,
javaCallback
);
JNIAsyncFunctionBody::invoke(this->jBodyReference.get(), globalArgs, promise);
// We have to remove the local reference to the promise object.
// It doesn't mean that the promise will be deallocated, but rather that we move
// the ownership to the `JNIAsyncFunctionBody`.
env->DeleteLocalRef(promise);
env->DeleteGlobalRef(globalArgs);
return jsi::Value::undefined();
}
);
}
} // namespace expo

View File

@@ -0,0 +1,129 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include "types/CppType.h"
#include "types/ExpectedType.h"
#include "types/AnyType.h"
#include "types/ReturnType.h"
#include <jsi/jsi.h>
#include <fbjni/fbjni.h>
#include <ReactCommon/TurboModuleUtils.h>
#include <react/jni/ReadableNativeArray.h>
#include <memory>
#include <vector>
#include <folly/dynamic.h>
#include <jsi/JSIDynamic.h>
namespace jni = facebook::jni;
namespace jsi = facebook::jsi;
namespace react = facebook::react;
namespace expo {
class JSIContext;
/**
* A class that holds information about the exported function.
*/
class MethodMetadata : public std::enable_shared_from_this<MethodMetadata> {
public:
struct Info {
/**
* Function name
*/
const std::string name;
/**
* Whether this function takes owner
*/
const bool takesOwner = false;
/**
* Whether this function is async
*/
const bool isAsync = false;
/**
* Whether this function is enumerable
*/
const bool enumerable = true;
/**
* Representation of expected argument types.
*/
std::vector<std::unique_ptr<AnyType>> argTypes;
/**
* Representation of expected return type.
*/
ReturnType returnType = ReturnType::UNKNOWN;
};
Info info;
MethodMetadata(
Info info,
jni::global_ref<jobject> &&jBodyReference
);
// We deleted the copy contractor to not deal with transforming the ownership of the `jBodyReference`.
MethodMetadata(const MethodMetadata &) = delete;
MethodMetadata(MethodMetadata &&other) = default;
/**
* Transforms metadata to a jsi::Function.
*
* @param runtime
* @return shared ptr to the jsi::Function that wrapped the underlying Kotlin's function.
*/
std::shared_ptr<jsi::Function> toJSFunction(
jsi::Runtime &runtime
);
/**
* Calls the underlying Kotlin function.
*/
jsi::Value callSync(
jsi::Runtime &rt,
const jsi::Value &thisValue,
const jsi::Value *args,
size_t count
);
jni::local_ref<jobject> callJNISync(
JNIEnv *env,
jsi::Runtime &rt,
const jsi::Value &thisValue,
const jsi::Value *args,
size_t count
);
private:
/**
* Reference to one of two java objects - `JNIFunctionBody` or `JNIAsyncFunctionBody`.
*
* In case when `isAsync` is `true`, this variable will point to `JNIAsyncFunctionBody`.
* Otherwise to `JNIFunctionBody`
*/
jni::global_ref<jobject> jBodyReference;
/**
* To not create a jsi::Function always when we need it, we cached that value.
*/
std::shared_ptr<jsi::Function> body = nullptr;
jsi::Function toSyncFunction(jsi::Runtime &runtime);
jsi::Function toAsyncFunction(jsi::Runtime &runtime);
jsi::Function createPromiseBody(
jsi::Runtime &runtime,
jobjectArray globalArgs
);
jobjectArray convertJSIArgsToJNI(
JNIEnv *env,
jsi::Runtime &rt,
const jsi::Value &thisValue,
const jsi::Value *args,
size_t count
);
};
} // namespace expo

View File

@@ -0,0 +1,77 @@
#include "NativeArrayBuffer.h"
#include "JavaScriptRuntime.h"
#include "JSIContext.h"
namespace expo {
ByteBufferJSIWrapper::ByteBufferJSIWrapper(const jni::alias_ref<jni::JByteBuffer> &byteBuffer) : _byteBuffer(jni::make_global(byteBuffer)) {
_byteBuffer->order(jni::JByteOrder::nativeOrder());
}
ByteBufferJSIWrapper::~ByteBufferJSIWrapper() {
// Destruction can happen on JS thread
jni::ThreadScope::WithClassLoader([&] { _byteBuffer.reset(); });
}
uint8_t *ByteBufferJSIWrapper::data() {
return _byteBuffer->getDirectBytes();
}
size_t ByteBufferJSIWrapper::size() const {
return _byteBuffer->getDirectSize();
}
const jni::global_ref<jni::JByteBuffer> &ByteBufferJSIWrapper::getBuffer() const {
return this->_byteBuffer;
}
void NativeArrayBuffer::registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", NativeArrayBuffer::initHybrid),
makeNativeMethod("size", NativeArrayBuffer::size),
makeNativeMethod("readByte", NativeArrayBuffer::read<int8_t>),
makeNativeMethod("read2Byte", NativeArrayBuffer::read<int16_t>),
makeNativeMethod("read4Byte", NativeArrayBuffer::read<int32_t>),
makeNativeMethod("read8Byte", NativeArrayBuffer::read<int64_t>),
makeNativeMethod("readFloat", NativeArrayBuffer::read<float>),
makeNativeMethod("readDouble", NativeArrayBuffer::read<double>),
makeNativeMethod("toDirectBuffer", NativeArrayBuffer::toDirectBuffer),
});
}
jni::local_ref<NativeArrayBuffer::jhybriddata>
NativeArrayBuffer::initHybrid(jni::alias_ref<JavaPart::javaobject>,
jni::alias_ref<jni::JByteBuffer> byteBuffer) {
return makeCxxInstance(byteBuffer);
}
jni::local_ref<NativeArrayBuffer::javaobject>
NativeArrayBuffer::newInstance(JSIContext *jsiContext, jsi::Runtime &runtime,
jsi::ArrayBuffer &arrayBuffer) {
size_t size = arrayBuffer.size(runtime);
auto byteBuffer = jni::JByteBuffer::allocateDirect(size);
byteBuffer->order(jni::JByteOrder::nativeOrder());
memcpy(byteBuffer->getDirectAddress(), arrayBuffer.data(runtime), size);
auto value = NativeArrayBuffer::newObjectCxxArgs(byteBuffer);
jsiContext->jniDeallocator->addReference(value);
return value;
}
NativeArrayBuffer::NativeArrayBuffer(const jni::alias_ref<jni::JByteBuffer> &byteBuffer)
: buffer(std::make_shared<ByteBufferJSIWrapper>(byteBuffer)) { }
int NativeArrayBuffer::size() {
return (int) buffer->size();
}
std::shared_ptr<ByteBufferJSIWrapper> NativeArrayBuffer::jsiMutableBuffer() {
return this->buffer;
}
jni::local_ref<jni::JByteBuffer> NativeArrayBuffer::toDirectBuffer() {
return jni::make_local(buffer->getBuffer());
}
}

View File

@@ -0,0 +1,75 @@
#pragma once
#include "JNIDeallocator.h"
#include "JSIContext.h"
#include <fbjni/ByteBuffer.h>
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
#include <memory>
namespace expo {
class JavaScriptRuntime;
namespace jni = facebook::jni;
namespace jsi = facebook::jsi;
/**
* Represents JNI direct ByteBuffer instance, that can be passed
* to JSI and then managed by JS.
*/
class ByteBufferJSIWrapper: public jsi::MutableBuffer {
public:
explicit ByteBufferJSIWrapper(const jni::alias_ref<jni::JByteBuffer>& byteBuffer);
~ByteBufferJSIWrapper() override;
[[nodiscard]] uint8_t* data() override;
[[nodiscard]] size_t size() const override;
[[nodiscard]] const jni::global_ref<jni::JByteBuffer>& getBuffer() const;
private:
jni::global_ref<jni::JByteBuffer> _byteBuffer;
};
class NativeArrayBuffer : public jni::HybridClass<NativeArrayBuffer, Destructible> {
public:
static auto constexpr
kJavaDescriptor = "Lexpo/modules/kotlin/jni/NativeArrayBuffer;";
static auto constexpr TAG = "NativeArrayBuffer";
static void registerNatives();
static jni::local_ref<NativeArrayBuffer::jhybriddata> initHybrid(
jni::alias_ref<jhybridobject>,
jni::alias_ref<jni::JByteBuffer> byteBuffer
);
static jni::local_ref<NativeArrayBuffer::javaobject> newInstance(
JSIContext *jsiContext,
jsi::Runtime& runtime,
jsi::ArrayBuffer& arrayBuffer
);
explicit NativeArrayBuffer(const jni::alias_ref<jni::JByteBuffer>& byteBuffer);
[[nodiscard]] int size();
[[nodiscard]] std::shared_ptr<ByteBufferJSIWrapper> jsiMutableBuffer();
[[nodiscard]] jni::local_ref<jni::JByteBuffer> toDirectBuffer();
template<class T>
T read(int position) {
return *reinterpret_cast<T *>(buffer->data() + position);
}
private:
std::shared_ptr<ByteBufferJSIWrapper> buffer;
};
} // namespace expo

View File

@@ -0,0 +1,111 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#include "RuntimeHolder.h"
#if UNIT_TEST
#include "TestingSyncJSCallInvoker.h"
#if USE_HERMES
#include <hermes/hermes.h>
#include <utility>
#else
#include <jsc/JSCRuntime.h>
#endif
#endif // UNIT_TEST
namespace expo {
void RuntimeHolder::registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", RuntimeHolder::initHybrid),
makeNativeMethod("createRuntime", RuntimeHolder::createRuntime),
makeNativeMethod("createCallInvoker", RuntimeHolder::createCallInvoker),
makeNativeMethod("release", RuntimeHolder::release),
});
}
jni::local_ref<RuntimeHolder::jhybriddata> RuntimeHolder::initHybrid(jni::alias_ref<jhybridobject> jThis) {
return makeCxxInstance();
}
jlong RuntimeHolder::createRuntime() {
#if !UNIT_TEST
throw std::logic_error(
"The RuntimeHolder constructor is only available when UNIT_TEST is defined.");
#else
#if USE_HERMES
auto config = ::hermes::vm::RuntimeConfig::Builder()
.withEnableSampleProfiling(false);
runtime = facebook::hermes::makeHermesRuntime(config.build());
// This version of the Hermes uses a Promise implementation that is provided by the RN.
// The `setImmediate` function isn't defined, but is required by the Promise implementation.
// That's why we inject it here.
auto setImmediatePropName = jsi::PropNameID::forUtf8(*runtime, "setImmediate");
runtime->global().setProperty(
*runtime,
setImmediatePropName,
jsi::Function::createFromHostFunction(
*runtime,
setImmediatePropName,
1,
[](jsi::Runtime &rt,
const jsi::Value &thisVal,
const jsi::Value *args,
size_t count) {
args[0].asObject(rt).asFunction(rt).call(rt);
return jsi::Value::undefined();
}
)
);
#else
runtime = facebook::jsc::makeJSCRuntime();
#endif
// By default "global" property isn't set.
runtime->global().setProperty(
*runtime,
jsi::PropNameID::forUtf8(*runtime, "global"),
runtime->global()
);
// Mock the CodedError that in a typical scenario will be defined by the `expo-modules-core`.
// Note: we can't use `class` syntax here, because Hermes doesn't support it.
runtime->evaluateJavaScript(
std::make_shared<jsi::StringBuffer>(
"function CodedError(code, message) {\n"
" this.code = code;\n"
" this.message = message;\n"
" this.stack = (new Error).stack;\n"
"}\n"
"CodedError.prototype = new Error;\n"
"global.ExpoModulesCore_CodedError = CodedError"
),
"<<evaluated>>"
);
return reinterpret_cast<jlong>(runtime.get());
#endif
}
void RuntimeHolder::release() {
runtime.reset();
}
jni::local_ref<react::CallInvokerHolder::javaobject> RuntimeHolder::createCallInvoker() {
#if !UNIT_TEST
throw std::logic_error(
"The RuntimeHolder::createCallInvoker is only available when UNIT_TEST is defined.");
#else
return react::CallInvokerHolder::newObjectCxxArgs(std::make_shared<TestingSyncJSCallInvoker>(runtime));
#endif
}
} // namespace expo

View File

@@ -0,0 +1,39 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
#include <ReactCommon/CallInvokerHolder.h>
#include <ReactCommon/CallInvoker.h>
namespace jni = facebook::jni;
namespace jsi = facebook::jsi;
namespace react = facebook::react;
namespace expo {
class RuntimeHolder : public jni::HybridClass<RuntimeHolder> {
public:
static auto constexpr
kJavaDescriptor = "Lexpo/modules/kotlin/jni/tests/RuntimeHolder;";
static auto constexpr TAG = "RuntimeHolder";
static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis);
static void registerNatives();
jlong createRuntime();
void release();
jni::local_ref<react::CallInvokerHolder::javaobject> createCallInvoker();
private:
friend HybridBase;
std::shared_ptr<jsi::Runtime> runtime;
};
} // namespace expo

View File

@@ -0,0 +1,49 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include <fbjni/fbjni.h>
#include <android/log.h>
namespace jni = facebook::jni;
namespace expo {
/*
* A wrapper for a global reference that can be deallocated on any thread.
* It should be used with smart pointer. That structure can't be copied or moved.
*/
template<typename T>
class ThreadSafeJNIGlobalRef {
public:
ThreadSafeJNIGlobalRef(jobject globalRef) : globalRef(globalRef) {}
ThreadSafeJNIGlobalRef(const ThreadSafeJNIGlobalRef &other) = delete;
ThreadSafeJNIGlobalRef(ThreadSafeJNIGlobalRef &&other) = delete;
ThreadSafeJNIGlobalRef &operator=(const ThreadSafeJNIGlobalRef &other) = delete;
ThreadSafeJNIGlobalRef &operator=(ThreadSafeJNIGlobalRef &&other) = delete;
void use(std::function<void(jni::alias_ref<T> globalRef)> &&action) noexcept {
if (globalRef == nullptr) {
__android_log_print(ANDROID_LOG_WARN, "ExpoModulesCore", "ThreadSafeJNIGlobalRef was used after deallocation.");
return;
}
jni::ThreadScope::WithClassLoader([this, action = std::move(action)]() {
jni::alias_ref<jobject> aliasRef = jni::wrap_alias(globalRef);
jni::alias_ref<T> jsiContextRef = jni::static_ref_cast<T>(aliasRef);
action(jsiContextRef);
});
}
~ThreadSafeJNIGlobalRef() {
if (globalRef != nullptr) {
jni::ThreadScope::WithClassLoader([this] {
jni::Environment::current()->DeleteGlobalRef(this->globalRef);
});
}
}
jobject globalRef;
};
} // namespace expo

View File

@@ -0,0 +1,43 @@
// Copyright © 2026-present 650 Industries, Inc. (aka Expo)
#pragma once
#include <concepts>
#include <fbjni/fbjni.h>
#include "jni_deref.h"
namespace jni = facebook::jni;
namespace expo {
template<typename T>
concept HasCthis = requires(T &t) { t->cthis(); };
template<typename T>
concept HasToStdString = requires(T &t) { t->toStdString(); };
template<typename T>
concept HasValue = requires(T &t) { t->value(); };
template<typename T>
concept HasGetRegion = requires(T &t, jsize s) { t->getRegion(s, s); };
template<typename T>
concept IsJBoolean = std::is_same_v<jni_deref_t<T>, jni::JBoolean>;
template<typename T>
concept JniRef =
std::is_same_v<T, jni::local_ref<jni_deref_t<T>>> ||
std::is_same_v<T, jni::global_ref<jni_deref_t<T>>> ||
std::is_same_v<T, jni::alias_ref<jni_deref_t<T>>>;
template<typename T, typename Inner>
concept JniRefTo = JniRef<T> && std::is_same_v<jni_deref_t<T>, Inner>;
template<typename T, typename Inner>
concept JCollectionRef = JniRefTo<T, jni::JCollection<Inner>>;
template<typename T, typename Key, typename Value>
concept JMapRef = JniRefTo<T, jni::JMap<Key, Value>>;
} // namespace expo

View File

@@ -0,0 +1,39 @@
// Copyright © 2026-present 650 Industries, Inc. (aka Expo)
#pragma once
#include <concepts>
#include <fbjni/fbjni.h>
namespace jni = facebook::jni;
namespace expo::jni_deref_impl {
template<typename T>
struct deref {
using type = T;
};
template<typename T>
struct deref<jni::local_ref<T>> {
using type = T;
};
template<typename T>
struct deref<jni::global_ref<T>> {
using type = T;
};
template<typename T>
struct deref<jni::alias_ref<T>> {
using type = T;
};
} // namespace expo::jni_deref_impl
namespace expo {
template<typename T>
using jni_deref_t = typename jni_deref_impl::deref<std::remove_cvref_t<T>>::type;
} // namespace expo

View File

@@ -0,0 +1,20 @@
// Copyright © 2026-present 650 Industries, Inc. (aka Expo)
#pragma once
#include <concepts>
#include <jsi/jsi.h>
namespace jsi = facebook::jsi;
namespace expo {
template<typename T>
concept TriviallyConvertibleToJSI = std::is_constructible_v<jsi::Value, T>;
template<typename T>
concept ConvertibleToJSI =
std::is_constructible_v<jsi::Value, jsi::Runtime &, T> &&
!std::is_constructible_v<jsi::Value, T>;
} // namespace expo

View File

@@ -0,0 +1,179 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#include "JSClassesDecorator.h"
#include "SharedObject.h"
#include "SharedRef.h"
#include "JSDecoratorsBridgingObject.h"
#include "../JavaReferencesCache.h"
#include "../JSIContext.h"
#include "../JavaScriptObject.h"
#include "JSFunctionsDecorator.h"
#include "../Exceptions.h"
namespace expo {
void JSClassesDecorator::registerClass(
jni::alias_ref<jstring> name,
jni::alias_ref<jni::HybridClass<JSDecoratorsBridgingObject>::javaobject> prototypeDecorator,
jni::alias_ref<jni::HybridClass<JSDecoratorsBridgingObject>::javaobject> constructorDecorator,
jboolean takesOwner,
jni::alias_ref<jclass> ownerClass,
jboolean isSharedRef,
jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,
jni::alias_ref<JNIFunctionBody::javaobject> body
) {
std::string cName = name->toStdString();
MethodMetadata::Info info{
.name = "constructor",
// We're unsure if takesOwner can be greater than 1, so we're using bitwise AND to ensure it's 0 or 1.
.takesOwner = static_cast<bool>(takesOwner & 0x1),
.isAsync = false,
.enumerable = false,
.argTypes = JSFunctionsDecorator::mapConverters(expectedArgTypes)
};
auto constructor = std::make_shared<MethodMetadata>(
std::move(info),
jni::make_global(body)
);
ClassEntry classTuple{
.prototypeDecorators = prototypeDecorator->cthis()->bridge(),
.constructorDecorators = constructorDecorator->cthis()->bridge(),
.constructor = std::move(constructor),
.ownerClass = jni::make_global(ownerClass),
// We're unsure if takesOwner can be greater than 1, so we're using bitwise AND to ensure it's 0 or 1.
.isSharedRef = static_cast<bool>(isSharedRef & 0x1)
};
classes.try_emplace(
cName,
std::move(classTuple)
);
}
void JSClassesDecorator::decorate(
jsi::Runtime &runtime,
jsi::Object &jsObject
) {
for (auto &[name, classInfo]: classes) {
auto &[prototypeDecorators, constructorDecorators, constructor, ownerClass, isSharedRef] = classInfo;
auto weakConstructor = std::weak_ptr<decltype(constructor)::element_type>(constructor);
expo::common::ClassConstructor jsConstructor = [weakConstructor = std::move(weakConstructor)](
jsi::Runtime &runtime,
const jsi::Value &thisValue,
const jsi::Value *args,
size_t count
) -> jsi::Value {
// We need to check if the constructor is still alive.
// If not we can just ignore the call. We're destroying the module.
auto ctr = weakConstructor.lock();
if (ctr == nullptr) {
return jsi::Value::undefined();
}
auto thisObject = std::make_shared<jsi::Object>(thisValue.asObject(runtime));
try {
JNIEnv *env = jni::Environment::current();
/**
* This will push a new JNI stack frame for the LocalReferences in this
* function call. When the stack frame for this lambda is popped,
* all LocalReferences are deleted.
*/
jni::JniLocalScope scope(env, (int) count);
auto result = ctr->callJNISync(
env,
runtime,
thisValue,
args,
count
);
if (result == nullptr) {
return {runtime, thisValue};
}
jobject unpackedResult = result.get();
jclass resultClass = env->GetObjectClass(unpackedResult);
if (env->IsAssignableFrom(
resultClass,
JCacheHolder::get().jSharedObject
)) {
JSIContext *jsiContext = getJSIContext(runtime);
auto jsThisObject = JavaScriptObject::newInstance(
jsiContext,
jsiContext->runtimeHolder,
thisObject
);
jsiContext->registerSharedObject(result, jsThisObject);
}
return {runtime, thisValue};
} catch (jni::JniException &jniException) {
rethrowAsCodedError(runtime, jniException);
}
};
auto klass = createClass(
runtime,
name,
isSharedRef,
std::move(jsConstructor)
);
for (const auto &decorator: constructorDecorators) {
decorator->decorate(runtime, klass);
}
auto klassSharedPtr = std::make_shared<jsi::Function>(std::move(klass));
JSIContext *jsiContext = getJSIContext(runtime);
auto jsThisObject = JavaScriptObject::newInstance(
jsiContext,
jsiContext->runtimeHolder,
klassSharedPtr
);
if (ownerClass != nullptr) {
jsiContext->registerClass(jni::make_local(ownerClass), jsThisObject);
}
jsObject.setProperty(
runtime,
jsi::String::createFromUtf8(runtime, name),
jsi::Value(runtime, *klassSharedPtr)
);
jsi::PropNameID prototypePropNameId = jsi::PropNameID::forAscii(runtime, "prototype", 9);
jsi::Object klassPrototype = klassSharedPtr
->getProperty(runtime, prototypePropNameId)
.asObject(runtime);
for (const auto &decorator: prototypeDecorators) {
decorator->decorate(runtime, klassPrototype);
}
}
}
jsi::Function JSClassesDecorator::createClass(
jsi::Runtime &runtime,
const std::string &className,
bool isSharedRef,
common::ClassConstructor constructor
) {
if (!isSharedRef) {
return SharedObject::createClass(
runtime,
className.c_str(),
std::move(constructor)
);
}
return SharedRef::createClass(
runtime,
className.c_str(),
std::move(constructor)
);
}
} // namespace expo

View File

@@ -0,0 +1,60 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include <fbjni/fbjni.h>
#include <map>
#include "JSDecorator.h"
#include "../MethodMetadata.h"
#include "../JNIFunctionBody.h"
#include "JSIUtils.h"
namespace jni = facebook::jni;
namespace expo {
class JSDecoratorsBridgingObject;
class JSClassesDecorator : public JSDecorator {
public:
void registerClass(
jni::alias_ref<jstring> name,
jni::alias_ref<jni::HybridClass<JSDecoratorsBridgingObject>::javaobject> prototypeDecorator,
jni::alias_ref<jni::HybridClass<JSDecoratorsBridgingObject>::javaobject> constructorDecorator,
jboolean takesOwner,
jni::alias_ref<jclass> ownerClass,
jboolean isSharedRef,
jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,
jni::alias_ref<JNIFunctionBody::javaobject> body
);
void decorate(
jsi::Runtime &runtime,
jsi::Object &jsObject
) override;
private:
struct ClassEntry {
std::vector<std::unique_ptr<JSDecorator>> prototypeDecorators;
std::vector<std::unique_ptr<JSDecorator>> constructorDecorators;
std::shared_ptr<MethodMetadata> constructor;
jni::global_ref<jclass> ownerClass;
bool isSharedRef;
};
static jsi::Function createClass(
jsi::Runtime &runtime,
const std::string &className,
bool isSharedRef,
common::ClassConstructor constructor
);
std::unordered_map<
std::string,
ClassEntry
> classes;
};
} // namespace expo

View File

@@ -0,0 +1,81 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#include "JSConstantsDecorator.h"
#include "../JavaScriptObject.h"
#include "JSIUtils.h"
#include "JSFunctionsDecorator.h"
#include "../JSIContext.h"
#include "../types/JNIToJSIConverter.h"
#include <jsi/jsi.h>
#include <jsi/JSIDynamic.h>
namespace jsi = facebook::jsi;
namespace expo {
void JSConstantsDecorator::registerConstants(
jni::alias_ref<react::NativeMap::javaobject> constants
) {
auto dynamic = constants->cthis()->consume();
assert(dynamic.isObject());
for (const auto &[key, value]: dynamic.items()) {
this->legacyConstants[key.asString()] = value;
}
}
void JSConstantsDecorator::registerConstant(
jni::alias_ref<jstring> name,
jni::alias_ref<JNINoArgsFunctionBody::javaobject> getter
) {
auto cName = name->toStdString();
constants.insert_or_assign(cName, jni::make_global(getter));
}
void JSConstantsDecorator::decorate(
jsi::Runtime &runtime,
jsi::Object &jsObject
) {
for (const auto &[name, value]: this->legacyConstants) {
jsObject.setProperty(
runtime,
jsi::String::createFromUtf8(runtime, name),
jsi::valueFromDynamic(runtime, value)
);
}
for (auto &[name, getter]: this->constants) {
auto descriptor = JavaScriptObject::preparePropertyDescriptor(runtime,
1 << 1 /* enumerable */);
jsi::Function jsiFunc = jsi::Function::createFromHostFunction(
runtime,
getJSIContext(runtime)->jsRegistry->getPropNameID(runtime, name),
0,
[getterFunc = std::move(getter), prevValue = std::shared_ptr<jsi::Value>()](
jsi::Runtime &rt,
const jsi::Value &thisValue,
const jsi::Value *args,
size_t count
) mutable -> jsi::Value {
if (prevValue == nullptr) {
JNIEnv *env = jni::Environment::current();
auto result = JNINoArgsFunctionBody::invoke(getterFunc.get());
getterFunc = nullptr;
prevValue = std::make_shared<jsi::Value>(convert(env, rt, result));
}
return {rt, *prevValue};
});
descriptor.setProperty(
runtime,
"get",
jsi::Value(runtime, jsiFunc)
);
common::defineProperty(runtime, &jsObject, name.c_str(), std::move(descriptor));
}
this->constants.clear();
}
} // namespace expo

View File

@@ -0,0 +1,41 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include <fbjni/fbjni.h>
#include <react/jni/ReadableNativeMap.h>
#include <folly/dynamic.h>
#include "JSDecorator.h"
#include "../JNIFunctionBody.h"
namespace jni = facebook::jni;
namespace react = facebook::react;
namespace expo {
class JSConstantsDecorator : public JSDecorator {
public:
void registerConstants(jni::alias_ref<react::NativeMap::javaobject> constants);
void registerConstant(
jni::alias_ref<jstring> name,
jni::alias_ref<JNINoArgsFunctionBody::javaobject> getter
);
void decorate(
jsi::Runtime &runtime,
jsi::Object &jsObject
) override;
private:
/**
* A constants map.
*/
std::unordered_map<std::string, folly::dynamic> legacyConstants;
/**
* A registry of constants
*/
std::unordered_map<std::string, jni::global_ref<JNINoArgsFunctionBody::javaobject>> constants;
};
} // namespace expo

View File

@@ -0,0 +1,28 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
namespace jni = facebook::jni;
namespace jsi = facebook::jsi;
namespace expo {
/**
* A base interface for all decorators.
* Used decorators should be retained by the object that is decorated.
* The decorator should only be used by a single object.
*/
class JSDecorator {
public:
virtual ~JSDecorator() = default;
virtual void decorate(
jsi::Runtime &runtime,
jsi::Object &jsObject
) = 0;
};
} // namespace expo

View File

@@ -0,0 +1,178 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#include "JSDecoratorsBridgingObject.h"
#include "JSClassesDecorator.h"
#include "../types/ReturnType.h"
namespace expo {
jni::local_ref<jni::HybridClass<JSDecoratorsBridgingObject>::jhybriddata>
JSDecoratorsBridgingObject::initHybrid(jni::alias_ref<jhybridobject> jThis) {
return makeCxxInstance();
}
void JSDecoratorsBridgingObject::registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", JSDecoratorsBridgingObject::initHybrid),
makeNativeMethod("registerConstants",
JSDecoratorsBridgingObject::registerConstants),
makeNativeMethod("registerSyncFunction",
JSDecoratorsBridgingObject::registerSyncFunction),
makeNativeMethod("registerAsyncFunction",
JSDecoratorsBridgingObject::registerAsyncFunction),
makeNativeMethod("registerProperty",
JSDecoratorsBridgingObject::registerProperty),
makeNativeMethod("registerConstant",
JSDecoratorsBridgingObject::registerConstant),
makeNativeMethod("registerObject",
JSDecoratorsBridgingObject::registerObject),
makeNativeMethod("registerClass",
JSDecoratorsBridgingObject::registerClass)
});
}
void JSDecoratorsBridgingObject::registerSyncFunction(
jni::alias_ref<jstring> name,
jboolean takesOwner,
jboolean enumerable,
jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,
jint cppReturnType,
jni::alias_ref<JNIFunctionBody::javaobject> body
) {
if (!functionDecorator) {
functionDecorator = std::make_unique<JSFunctionsDecorator>();
}
functionDecorator->registerSyncFunction(
name,
takesOwner,
enumerable,
expectedArgTypes,
(ReturnType)cppReturnType,
body
);
}
void JSDecoratorsBridgingObject::registerAsyncFunction(
jni::alias_ref<jstring> name,
jboolean takesOwner,
jboolean enumerable,
jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,
jni::alias_ref<JNIAsyncFunctionBody::javaobject> body
) {
if (!functionDecorator) {
functionDecorator = std::make_unique<JSFunctionsDecorator>();
}
functionDecorator->registerAsyncFunction(name, takesOwner, enumerable, expectedArgTypes, body);
}
void JSDecoratorsBridgingObject::registerProperty(
jni::alias_ref<jstring> name,
jboolean getterTakesOwner,
jni::alias_ref<jni::JArrayClass<ExpectedType>> getterExpectedArgsTypes,
jni::alias_ref<JNIFunctionBody::javaobject> getter,
jboolean setterTakesOwner,
jni::alias_ref<jni::JArrayClass<ExpectedType>> setterExpectedArgsTypes,
jni::alias_ref<JNIFunctionBody::javaobject> setter
) {
if (!propertiesDecorator) {
propertiesDecorator = std::make_unique<JSPropertiesDecorator>();
}
propertiesDecorator->registerProperty(
name,
getterTakesOwner,
getterExpectedArgsTypes,
getter,
setterTakesOwner,
setterExpectedArgsTypes,
setter
);
}
void JSDecoratorsBridgingObject::registerConstant(
jni::alias_ref<jstring> name,
jni::alias_ref<JNINoArgsFunctionBody::javaobject> getter
) {
if (!constantsDecorator) {
constantsDecorator = std::make_unique<JSConstantsDecorator>();
}
constantsDecorator->registerConstant(name, getter);
}
void JSDecoratorsBridgingObject::registerConstants(
jni::alias_ref<react::NativeMap::javaobject> constants) {
if (!constantsDecorator) {
constantsDecorator = std::make_unique<JSConstantsDecorator>();
}
constantsDecorator->registerConstants(constants);
}
void JSDecoratorsBridgingObject::registerObject(
jni::alias_ref<jstring> name,
jni::alias_ref<JSDecoratorsBridgingObject::javaobject> jsDecoratorsBridgingObject
) {
if (!objectDecorator) {
objectDecorator = std::make_unique<JSObjectDecorator>();
}
objectDecorator->registerObject(name, jsDecoratorsBridgingObject);
}
void JSDecoratorsBridgingObject::registerClass(
jni::alias_ref<jstring> name,
jni::alias_ref<JSDecoratorsBridgingObject::javaobject> jsDecoratorsBridgingObject,
jni::alias_ref<JSDecoratorsBridgingObject::javaobject> jsDecoratorsConstructor,
jboolean takesOwner,
jni::alias_ref<jclass> ownerClass,
jboolean isSharedRef,
jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,
jni::alias_ref<JNIFunctionBody::javaobject> body
) {
if (!classDecorator) {
classDecorator = std::make_unique<JSClassesDecorator>();
}
classDecorator->registerClass(
name,
jsDecoratorsBridgingObject,
jsDecoratorsConstructor,
takesOwner,
ownerClass,
isSharedRef,
expectedArgTypes,
body
);
}
std::vector<std::unique_ptr<JSDecorator>> JSDecoratorsBridgingObject::bridge() {
std::vector<std::unique_ptr<JSDecorator>> decorators;
if (functionDecorator) {
decorators.push_back(std::move(functionDecorator));
}
if (propertiesDecorator) {
decorators.push_back(std::move(propertiesDecorator));
}
if (constantsDecorator) {
decorators.push_back(std::move(constantsDecorator));
}
if (objectDecorator) {
decorators.push_back(std::move(objectDecorator));
}
if (classDecorator) {
decorators.push_back(std::move(classDecorator));
}
return decorators;
}
} // namespace expo

View File

@@ -0,0 +1,100 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include <fbjni/fbjni.h>
#include <vector>
#include "JSDecorator.h"
#include "../MethodMetadata.h"
#include "../JNIFunctionBody.h"
#include "../types/ExpectedType.h"
#include "JSFunctionsDecorator.h"
#include "JSPropertiesDecorator.h"
#include "JSConstantsDecorator.h"
#include "JSObjectDecorator.h"
#include "JSClassesDecorator.h"
namespace jni = facebook::jni;
namespace expo {
class JSDecoratorsBridgingObject : public jni::HybridClass<JSDecoratorsBridgingObject> {
public:
static auto constexpr
kJavaDescriptor = "Lexpo/modules/kotlin/jni/decorators/JSDecoratorsBridgingObject;";
static auto constexpr TAG = "JSDecoratorsBridgingObject";
static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis);
static void registerNatives();
void registerProperty(
jni::alias_ref<jstring> name,
jboolean getterTakesOwner,
jni::alias_ref<jni::JArrayClass<ExpectedType>> getterExpectedArgsTypes,
jni::alias_ref<JNIFunctionBody::javaobject> getter,
jboolean setterTakesOwner,
jni::alias_ref<jni::JArrayClass<ExpectedType>> setterExpectedArgsTypes,
jni::alias_ref<JNIFunctionBody::javaobject> setter
);
void registerConstant(
jni::alias_ref<jstring> name,
jni::alias_ref<JNINoArgsFunctionBody::javaobject> getter
);
void registerSyncFunction(
jni::alias_ref<jstring> name,
jboolean takesOwner,
jboolean enumerable,
jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,
jint cppReturnType,
jni::alias_ref<JNIFunctionBody::javaobject> body
);
void registerAsyncFunction(
jni::alias_ref<jstring> name,
jboolean takesOwner,
jboolean enumerable,
jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,
jni::alias_ref<JNIAsyncFunctionBody::javaobject> body
);
void registerConstants(jni::alias_ref<react::NativeMap::javaobject> constants);
void registerObject(
jni::alias_ref<jstring> name,
jni::alias_ref<JSDecoratorsBridgingObject::javaobject> jsDecoratorsBridgingObject
);
void registerClass(
jni::alias_ref<jstring> name,
jni::alias_ref<JSDecoratorsBridgingObject::javaobject> jsDecoratorsBridgingObject,
jni::alias_ref<JSDecoratorsBridgingObject::javaobject> jsDecoratorsConstructor,
jboolean takesOwner,
jni::alias_ref<jclass> ownerClass,
jboolean isSharedRef,
jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,
jni::alias_ref<JNIFunctionBody::javaobject> body
);
/**
* Converts and consume all registered java decorators to C++
* @return vector of unique pointers to decorators
*/
std::vector<std::unique_ptr<JSDecorator>> bridge();
private:
friend HybridBase;
std::unique_ptr<JSFunctionsDecorator> functionDecorator;
std::unique_ptr<JSConstantsDecorator> constantsDecorator;
std::unique_ptr<JSPropertiesDecorator> propertiesDecorator;
std::unique_ptr<JSObjectDecorator> objectDecorator;
std::unique_ptr<JSClassesDecorator> classDecorator;
};
} // namespace expo

View File

@@ -0,0 +1,113 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#include "JSFunctionsDecorator.h"
#include "JSIUtils.h"
#include <jsi/jsi.h>
namespace jsi = facebook::jsi;
namespace expo {
std::vector<std::unique_ptr<AnyType>> JSFunctionsDecorator::mapConverters(
jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes
) {
size_t argsSize = expectedArgTypes->size();
std::vector<std::unique_ptr<AnyType>> argTypes;
argTypes.reserve(argsSize);
for (size_t i = 0; i < argsSize; i++) {
auto expectedType = expectedArgTypes->getElement(i);
argTypes.push_back(
std::make_unique<AnyType>(std::move(expectedType))
);
}
return argTypes;
}
void JSFunctionsDecorator::registerFunction(
const std::string &name,
bool takesOwner,
bool enumerable,
bool isAsync,
std::vector<std::unique_ptr<AnyType>> &&argTypes,
ReturnType returnType,
jni::global_ref<jobject> body
) {
MethodMetadata::Info info{
.name = name,
.takesOwner = takesOwner,
.isAsync = isAsync,
.enumerable = enumerable,
.argTypes = std::move(argTypes)
};
auto methodMetadata = std::make_shared<MethodMetadata>(
std::move(info),
std::move(body)
);
methodsMetadata.insert_or_assign(name, std::move(methodMetadata));
}
void JSFunctionsDecorator::registerSyncFunction(
jni::alias_ref<jstring> name,
jboolean takesOwner,
jboolean enumerable,
jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,
ReturnType returnType,
jni::alias_ref<JNIFunctionBody::javaobject> body
) {
registerFunction(
name->toStdString(),
static_cast<bool>(
takesOwner & 0x1
), // We're unsure if takesOwner can be greater than 1, so we're using bitwise AND to ensure it's 0 or 1.
enumerable,
/*isAsync=*/false,
mapConverters(expectedArgTypes),
returnType,
jni::make_global(body)
);
}
void JSFunctionsDecorator::registerAsyncFunction(
jni::alias_ref<jstring> name,
jboolean takesOwner,
jboolean enumerable,
jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,
jni::alias_ref<JNIAsyncFunctionBody::javaobject> body
) {
registerFunction(
name->toStdString(),
static_cast<bool>(
takesOwner & 0x1
), // We're unsure if takesOwner can be greater than 1, so we're using bitwise AND to ensure it's 0 or 1.
enumerable,
/*isAsync=*/true,
mapConverters(expectedArgTypes),
ReturnType::UNKNOWN,
jni::make_global(body)
);
}
void JSFunctionsDecorator::decorate(
jsi::Runtime &runtime,
jsi::Object &jsObject
) {
for (auto &[name, method]: this->methodsMetadata) {
if (method->info.enumerable) {
jsObject.setProperty(
runtime,
jsi::String::createFromUtf8(runtime, name),
jsi::Value(runtime, *method->toJSFunction(runtime))
);
} else {
common::PropertyDescriptor descriptor{
.enumerable = false,
.value = jsi::Value(runtime, *method->toJSFunction(runtime))
};
defineProperty(runtime, &jsObject, name.c_str(), descriptor);
}
}
}
} // namespace expo

View File

@@ -0,0 +1,66 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
#include <unordered_map>
#include <memory>
#include "JSDecorator.h"
#include "../MethodMetadata.h"
#include "../JNIFunctionBody.h"
#include "../types/ExpectedType.h"
#include "../types/ReturnType.h"
namespace jni = facebook::jni;
namespace jsi = facebook::jsi;
namespace expo {
class JSFunctionsDecorator : public JSDecorator {
public:
void registerSyncFunction(
jni::alias_ref<jstring> name,
jboolean takesOwner,
jboolean enumerable,
jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,
ReturnType returnType,
jni::alias_ref<JNIFunctionBody::javaobject> body
);
void registerAsyncFunction(
jni::alias_ref<jstring> name,
jboolean takesOwner,
jboolean enumerable,
jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,
jni::alias_ref<JNIAsyncFunctionBody::javaobject> body
);
void decorate(
jsi::Runtime &runtime,
jsi::Object &jsObject
) override;
static std::vector<std::unique_ptr<AnyType>> mapConverters(jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes);
private:
/**
* Metadata map that stores information about all available methods on this module.
*/
std::unordered_map<std::string, std::shared_ptr<MethodMetadata>> methodsMetadata;
void registerFunction(
const std::string &name,
bool takesOwner,
bool enumerable,
bool isAsync,
std::vector<std::unique_ptr<AnyType>> &&argTypes,
ReturnType returnType,
jni::global_ref<jobject> body
);
};
}

View File

@@ -0,0 +1,34 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#include "JSObjectDecorator.h"
#include "JSDecoratorsBridgingObject.h"
namespace expo {
void JSObjectDecorator::registerObject(
jni::alias_ref<jstring> name,
jni::alias_ref<JSDecoratorsBridgingObject::javaobject> jsDecoratorsBridgingObject
) {
auto nameStr = name->toStdString();
objects.emplace(nameStr, jsDecoratorsBridgingObject->cthis()->bridge());
}
void JSObjectDecorator::decorate(
jsi::Runtime &runtime,
jsi::Object &jsObject
) {
for (const auto &[name, decorators]: this->objects) {
auto object = jsi::Object(runtime);
for (const auto &decorator: decorators) {
decorator->decorate(runtime, object);
}
jsObject.setProperty(
runtime,
name.c_str(),
jsi::Value(runtime, object)
);
}
}
} // namespace expo

View File

@@ -0,0 +1,31 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include <fbjni/fbjni.h>
#include "JSDecorator.h"
namespace jni = facebook::jni;
namespace expo {
class JSDecoratorsBridgingObject;
class JSObjectDecorator : public JSDecorator {
public:
void registerObject(
jni::alias_ref<jstring> name,
jni::alias_ref<jni::HybridClass<JSDecoratorsBridgingObject>::javaobject> jsDecoratorsBridgingObject
);
void decorate(
jsi::Runtime &runtime,
jsi::Object &jsObject
) override;
private:
std::unordered_map<std::string, std::vector<std::unique_ptr<JSDecorator>>> objects;
};
} // namespace expo

View File

@@ -0,0 +1,88 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#include "JSPropertiesDecorator.h"
#include "../JavaScriptObject.h"
#include "JSIUtils.h"
#include "JSFunctionsDecorator.h"
#include <jsi/jsi.h>
namespace jsi = facebook::jsi;
namespace expo {
void JSPropertiesDecorator::registerProperty(
jni::alias_ref<jstring> name,
jboolean getterTakesOwner,
jni::alias_ref<jni::JArrayClass<ExpectedType>> getterExpectedArgsTypes,
jni::alias_ref<JNIFunctionBody::javaobject> getter,
jboolean setterTakesOwner,
jni::alias_ref<jni::JArrayClass<ExpectedType>> setterExpectedArgsTypes,
jni::alias_ref<JNIFunctionBody::javaobject> setter
) {
auto cName = name->toStdString();
MethodMetadata::Info getterInfo {
.name = cName,
// We're unsure if getterTakesOwner can be greater than 1, so we're using bitwise AND to ensure it's 0 or 1.
.takesOwner = static_cast<bool>(getterTakesOwner & 0x1),
.isAsync = false,
.enumerable = true,
.argTypes = JSFunctionsDecorator::mapConverters(getterExpectedArgsTypes)
};
auto getterMetadata = std::make_shared<MethodMetadata>(
std::move(getterInfo),
jni::make_global(getter)
);
MethodMetadata::Info setterInfo {
.name = cName,
// We're unsure if setterTakesOwner can be greater than 1, so we're using bitwise AND to ensure it's 0 or 1.
.takesOwner = static_cast<bool>(setterTakesOwner & 0x1),
.isAsync = false,
.enumerable = true,
.argTypes = JSFunctionsDecorator::mapConverters(setterExpectedArgsTypes)
};
auto setterMetadata = std::make_shared<MethodMetadata>(
std::move(setterInfo),
jni::make_global(setter)
);
auto functions = std::make_pair(
std::move(getterMetadata),
std::move(setterMetadata)
);
properties.insert_or_assign(cName, std::move(functions));
}
void JSPropertiesDecorator::decorate(
jsi::Runtime &runtime,
jsi::Object &jsObject
) {
for (auto &[name, property]: this->properties) {
auto &[getter, setter] = property;
auto descriptor = JavaScriptObject::preparePropertyDescriptor(runtime,
1 << 1 /* enumerable */);
auto jsGetter = getter->toJSFunction(runtime);
if (jsGetter != nullptr) {
descriptor.setProperty(
runtime,
"get",
jsi::Value(runtime, *jsGetter)
);
}
auto jsSetter = setter->toJSFunction(runtime);
if (jsSetter != nullptr) {
descriptor.setProperty(
runtime,
"set",
jsi::Value(runtime, *jsSetter)
);
}
common::defineProperty(runtime, &jsObject, name.c_str(), std::move(descriptor));
}
}
} // namespace expo

View File

@@ -0,0 +1,41 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#include <fbjni/fbjni.h>
#include <unordered_map>
#include "../MethodMetadata.h"
#include "../JNIFunctionBody.h"
#include "../types/ExpectedType.h"
#include "JSDecorator.h"
namespace jni = facebook::jni;
namespace expo {
class JSPropertiesDecorator : public JSDecorator {
public:
void registerProperty(
jni::alias_ref<jstring> name,
jboolean getterTakesOwner,
jni::alias_ref<jni::JArrayClass<ExpectedType>> getterExpectedArgsTypes,
jni::alias_ref<JNIFunctionBody::javaobject> getter,
jboolean setterTakesOwner,
jni::alias_ref<jni::JArrayClass<ExpectedType>> setterExpectedArgsTypes,
jni::alias_ref<JNIFunctionBody::javaobject> setter
);
void decorate(
jsi::Runtime &runtime,
jsi::Object &jsObject
) override;
private:
/**
* A registry of properties
* The first MethodMetadata points to the getter and the second one to the setter.
*/
std::unordered_map<std::string, std::pair<std::shared_ptr<MethodMetadata>, std::shared_ptr<MethodMetadata>>> properties;
};
}

View File

@@ -0,0 +1,199 @@
#include "MainRuntimeInstaller.h"
#include "EventEmitter.h"
#include "SharedRef.h"
#include "NativeModule.h"
#include "../ExpoModulesHostObject.h"
#if IS_NEW_ARCHITECTURE_ENABLED
#include "BridgelessJSCallInvoker.h"
#endif
namespace jni = facebook::jni;
namespace jsi = facebook::jsi;
namespace react = facebook::react;
namespace expo {
void MainRuntimeInstaller::registerNatives() {
javaClassLocal()->registerNatives({
makeNativeMethod("install",
MainRuntimeInstaller::installLegacy),
makeNativeMethod("install", MainRuntimeInstaller::install),
});
}
jni::local_ref<JSIContext::javaobject> MainRuntimeInstaller::installLegacy(
jni::alias_ref<MainRuntimeInstaller::javaobject> self,
jni::alias_ref<jni::JWeakReference<jobject>::javaobject> runtimeContextHolder,
jlong jsRuntimePointer,
jni::alias_ref<JNIDeallocator::javaobject> jniDeallocator,
jni::alias_ref<react::CallInvokerHolder::javaobject> jsInvokerHolder
) noexcept {
auto jsiContext = createJSIContext(
runtimeContextHolder,
jsRuntimePointer,
jniDeallocator,
jsInvokerHolder->cthis()->getCallInvoker()
);
prepareRuntime(
self,
jsiContext
);
return jsiContext;
}
jni::local_ref<JSIContext::javaobject> MainRuntimeInstaller::install(
jni::alias_ref<MainRuntimeInstaller::javaobject> self,
jni::alias_ref<jni::JWeakReference<jobject>::javaobject> runtimeContextHolder,
jlong jsRuntimePointer,
jni::alias_ref<JNIDeallocator::javaobject> jniDeallocator,
jni::alias_ref<react::JRuntimeExecutor::javaobject> runtimeExecutor
) noexcept {
auto jsiContext = createJSIContext(
runtimeContextHolder,
jsRuntimePointer,
jniDeallocator,
std::make_shared<BridgelessJSCallInvoker>(runtimeExecutor->cthis()->get())
);
prepareRuntime(
self,
jsiContext
);
return jsiContext;
}
jni::local_ref<JSIContext::javaobject> MainRuntimeInstaller::createJSIContext(
jni::alias_ref<jni::JWeakReference<jobject>::javaobject> runtimeContextHolder,
jlong jsRuntimePointer,
jni::alias_ref<JNIDeallocator::javaobject> jniDeallocator,
std::shared_ptr<react::CallInvoker> callInvoker
) noexcept {
auto cxxPart = std::make_unique<JSIContext>(
jsRuntimePointer,
std::move(jniDeallocator),
std::move(callInvoker)
);
auto hybridData = jni::detail::HybridData::create();
auto javaPart = JSIContext::newJavaInstance(
hybridData,
runtimeContextHolder
);
cxxPart->bindToJavaPart(javaPart);
jni::detail::setNativePointer(hybridData, std::move(cxxPart));
return javaPart;
}
void MainRuntimeInstaller::prepareRuntime(
jni::alias_ref<MainRuntimeInstaller::javaobject> self,
jni::local_ref<JSIContext::javaobject> jsiContext
) noexcept {
auto cxxPart = jsiContext->cthis();
auto runtimeHolder = cxxPart->runtimeHolder;
jsi::Runtime &runtime = runtimeHolder->get();
bindJSIContext(runtime, cxxPart);
std::shared_ptr<jsi::Object> mainObject = installMainObject(
runtime, MainRuntimeInstaller::getCoreModule(self)->cthis()->decorators
);
installClasses(
runtime,
cxxPart
);
installModules(
runtime,
cxxPart,
mainObject
);
}
std::shared_ptr<jsi::Object> MainRuntimeInstaller::installMainObject(
jsi::Runtime &runtime,
std::vector<std::unique_ptr<JSDecorator>> &decorators
) noexcept {
auto mainObject = std::make_shared<jsi::Object>(runtime);
for (const auto &decorator: decorators) {
decorator->decorate(runtime, *mainObject);
}
auto global = runtime.global();
jsi::Object descriptor = JavaScriptObject::preparePropertyDescriptor(runtime, 1 << 1);
descriptor.setProperty(runtime, "value", jsi::Value(runtime, *mainObject));
common::defineProperty(
runtime,
&global,
"expo",
std::move(descriptor)
);
return mainObject;
}
void MainRuntimeInstaller::installClasses(
jsi::Runtime &runtime,
JSIContext *jsiContext
) noexcept {
// We can't predict the order of deallocation of the JSIContext and the SharedObject.
// So we need to pass a new ref to retain the JSIContext to make sure it's not deallocated before the SharedObject.
const auto releaser = [threadSafeRef = jsiContext->threadSafeJThis](
const SharedObject::ObjectId objectId) {
threadSafeRef->use([objectId](jni::alias_ref<JSIContext::javaobject> globalRef) {
JSIContext::deleteSharedObject(globalRef, objectId);
});
};
EventEmitter::installClass(runtime);
SharedObject::installBaseClass(runtime, releaser);
SharedRef::installBaseClass(runtime);
NativeModule::installClass(runtime);
}
void MainRuntimeInstaller::installModules(
jsi::Runtime &runtime,
JSIContext *jsiContext,
const std::shared_ptr<jsi::Object> &hostObject
) noexcept {
auto expoModules = std::make_shared<ExpoModulesHostObject>(jsiContext);
auto expoModulesObject = jsi::Object::createFromHostObject(
runtime,
expoModules
);
// Define the `global.expo.modules` object.
hostObject
->setProperty(
runtime,
"modules",
expoModulesObject
);
}
jni::local_ref<JavaScriptModuleObject::javaobject> MainRuntimeInstaller::getCoreModule(
jni::alias_ref<MainRuntimeInstaller::javaobject> self
) {
const static auto method = MainRuntimeInstaller::javaClassLocal()
->getMethod<jni::local_ref<JavaScriptModuleObject::javaobject>()>(
"getCoreModuleObject"
);
return method(self);
}
} // namespace expo

View File

@@ -0,0 +1,84 @@
#pragma once
#include "../JSIContext.h"
#include <fbjni/fbjni.h>
#include <ReactCommon/CallInvokerHolder.h>
#include <ReactCommon/CallInvoker.h>
#include "SharedObject.h"
#if IS_NEW_ARCHITECTURE_ENABLED
#include <ReactCommon/RuntimeExecutor.h>
#include <react/jni/JRuntimeExecutor.h>
#endif
namespace expo {
class MainRuntimeInstaller : public jni::JavaClass<MainRuntimeInstaller> {
public:
static auto constexpr kJavaDescriptor = "Lexpo/modules/kotlin/jni/MainRuntimeInstaller;";
static auto constexpr TAG = "MainRuntimeInstaller";
static void registerNatives();
static jni::local_ref<JSIContext::javaobject> installLegacy(
jni::alias_ref<MainRuntimeInstaller::javaobject> self,
jni::alias_ref<jni::JWeakReference<jobject>::javaobject> runtimeContextHolder,
jlong jsRuntimePointer,
jni::alias_ref<JNIDeallocator::javaobject> jniDeallocator,
jni::alias_ref<react::CallInvokerHolder::javaobject> jsInvokerHolder
) noexcept;
#if IS_NEW_ARCHITECTURE_ENABLED
/**
* Initializes the `ExpoModulesHostObject` and adds it to the global object.
*/
static jni::local_ref<JSIContext::javaobject> install(
jni::alias_ref<MainRuntimeInstaller::javaobject> self,
jni::alias_ref<jni::JWeakReference<jobject>::javaobject> runtimeContextHolder,
jlong jsRuntimePointer,
jni::alias_ref<JNIDeallocator::javaobject> jniDeallocator,
jni::alias_ref<react::JRuntimeExecutor::javaobject> runtimeExecutor
) noexcept;
#endif
static jni::local_ref<JSIContext::javaobject> createJSIContext(
jni::alias_ref<jni::JWeakReference<jobject>::javaobject> runtimeContextHolder,
jlong jsRuntimePointer,
jni::alias_ref<JNIDeallocator::javaobject> jniDeallocator,
std::shared_ptr<react::CallInvoker> callInvoker
) noexcept;
static void prepareRuntime(
jni::alias_ref<MainRuntimeInstaller::javaobject> self,
jni::local_ref<JSIContext::javaobject> jsiContext
) noexcept;
static void installClasses(
jsi::Runtime &runtime,
JSIContext *jsiContext
) noexcept;
static std::shared_ptr<jsi::Object> installMainObject(
jsi::Runtime &runtime,
std::vector<std::unique_ptr<JSDecorator>> &decorators
) noexcept;
static void installModules(
jsi::Runtime &runtime,
JSIContext *jsiContext,
const std::shared_ptr<jsi::Object> &hostObject
) noexcept;
/**
* Gets a core module.
*/
[[nodiscard]] static jni::local_ref<JavaScriptModuleObject::javaobject> getCoreModule(
jni::alias_ref<MainRuntimeInstaller::javaobject> self
);
};
} // namespace expo

View File

@@ -0,0 +1,83 @@
#include "WorkletRuntimeInstaller.h"
#if WORKLETS_ENABLED
#include "../worklets/WorkletJSCallInvoker.h"
#include <worklets/WorkletRuntime/WorkletRuntime.h>
#endif
namespace jni = facebook::jni;
namespace jsi = facebook::jsi;
namespace react = facebook::react;
namespace expo {
void WorkletRuntimeInstaller::registerNatives() {
javaClassLocal()->registerNatives({
makeNativeMethod("install", WorkletRuntimeInstaller::install)
});
}
jni::local_ref<JSIContext::javaobject> WorkletRuntimeInstaller::install(
jni::alias_ref<MainRuntimeInstaller::javaobject> self,
jni::alias_ref<jni::JWeakReference<jobject>::javaobject> runtimeContextHolder,
jlong jsRuntimePointer,
jni::alias_ref<JNIDeallocator::javaobject> jniDeallocator
) noexcept {
#if WORKLETS_ENABLED
auto *jsRuntime = reinterpret_cast<jsi::Runtime *>(jsRuntimePointer);
auto workletRuntime = worklets::WorkletRuntime::getWeakRuntimeFromJSIRuntime(*jsRuntime);
auto jsiContext = MainRuntimeInstaller::createJSIContext(
runtimeContextHolder,
jsRuntimePointer,
jniDeallocator,
std::make_shared<WorkletJSCallInvoker>(workletRuntime)
);
WorkletRuntimeInstaller::prepareRuntime(jsiContext);
return jsiContext;
#else
return nullptr;
#endif
}
void WorkletRuntimeInstaller::prepareRuntime(
jni::local_ref<JSIContext::javaobject> jsiContext
) noexcept {
#if WORKLETS_ENABLED
auto cxxPart = jsiContext->cthis();
auto runtimeHolder = cxxPart->runtimeHolder;
jsi::Runtime &runtime = runtimeHolder->get();
bindJSIContext(runtime, cxxPart);
auto mainObject = std::make_shared<jsi::Object>(runtime);
auto global = runtime.global();
jsi::Object descriptor = JavaScriptObject::preparePropertyDescriptor(runtime, 1 << 1);
descriptor.setProperty(runtime, "value", jsi::Value(runtime, *mainObject));
common::defineProperty(
runtime,
&global,
"expo",
std::move(descriptor)
);
MainRuntimeInstaller::installClasses(
runtime,
cxxPart
);
// TODO(@lukmccall): Re-enable module installation when iOS start supporting exporting native modules
// MainRuntimeInstaller::installModules(
// runtime,
// cxxPart,
// mainObject
// );
#endif
}
} // namespace expo

View File

@@ -0,0 +1,30 @@
#pragma once
#include "MainRuntimeInstaller.h"
#include <fbjni/fbjni.h>
namespace jni = facebook::jni;
namespace expo {
class WorkletRuntimeInstaller : public jni::JavaClass<WorkletRuntimeInstaller> {
public:
static auto constexpr kJavaDescriptor = "Lexpo/modules/kotlin/jni/WorkletRuntimeInstaller;";
static auto constexpr TAG = "WorkletRuntimeInstaller";
static void registerNatives();
static jni::local_ref<JSIContext::javaobject> install(
jni::alias_ref<MainRuntimeInstaller::javaobject> self,
jni::alias_ref<jni::JWeakReference<jobject>::javaobject> runtimeContextHolder,
jlong jsRuntimePointer,
jni::alias_ref<JNIDeallocator::javaobject> jniDeallocator
) noexcept;
static void prepareRuntime(
jni::local_ref<JSIContext::javaobject> jsiContext
) noexcept;
};
} // namespace expo

View File

@@ -0,0 +1,75 @@
#pragma once
#include <fbjni/fbjni.h>
namespace jni = facebook::jni;
namespace expo::java {
template<typename E = jobject>
struct Iterable : public jni::JavaClass<Iterable<E>> {
constexpr static auto kJavaDescriptor = "Ljava/lang/Iterable;";
};
template<typename E = jobject>
struct Collection : public jni::JavaClass<Collection<E>, Iterable<E>> {
constexpr static auto kJavaDescriptor = "Ljava/util/Collection;";
[[nodiscard]] size_t size() const {
static auto sizeMethod =
Collection<E>::javaClassStatic()->template getMethod<jint()>("size");
return sizeMethod(this->self());
}
bool add(jni::alias_ref<E> element) {
static auto addMethod = Collection<E>::javaClassStatic()->
template getMethod<jboolean(jni::alias_ref<jni::JObject>)>("add");
return addMethod(this->self(), element);
}
};
template<typename E = jobject>
struct List : public jni::JavaClass<List<E>, Collection<E>> {
constexpr static auto kJavaDescriptor = "Ljava/util/List;";
[[nodiscard]] jni::local_ref<E> get(int index) const {
static auto getMethod =
List<E>::javaClassStatic()->template getMethod<jni::local_ref<jobject>(int)>("get");
return jni::static_ref_cast<E>(getMethod(this->self(), index));
}
};
template<typename E = jobject>
struct ArrayList : public jni::JavaClass<ArrayList<E>, List<E>> {
constexpr static auto kJavaDescriptor = "Ljava/util/ArrayList;";
static jni::local_ref<typename ArrayList<E>::javaobject> create(size_t size) {
return ArrayList<E>::newInstance((int) size);
}
};
template<typename K = jobject, typename V = jobject>
struct Map : public jni::JavaClass<Map<K, V>> {
constexpr static auto kJavaDescriptor = "Ljava/util/Map;";
jni::local_ref<V> put(jni::alias_ref<K> key, jni::alias_ref<V> value) {
static auto putMethod = Map<K, V>::javaClassStatic()->
template getMethod<jni::local_ref<V>(jni::alias_ref<K>, jni::alias_ref<V>)>("put");
return putMethod(this->self(), key, value);
}
};
template<typename K = jobject, typename V = jobject>
struct HashMap : public jni::JavaClass<HashMap<K, V>, Map<K, V>> {
constexpr static auto kJavaDescriptor = "Ljava/util/HashMap;";
};
template<typename K = jobject, typename V = jobject>
struct LinkedHashMap : public jni::JavaClass<LinkedHashMap<K, V>, HashMap<K, V>> {
constexpr static auto kJavaDescriptor = "Ljava/util/LinkedHashMap;";
static jni::local_ref<typename LinkedHashMap<K, V>::javaobject> create(size_t size) {
return LinkedHashMap<K, V>::newInstance((int) size);
}
};
} // namespace expo::java

View File

@@ -0,0 +1,11 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#include "AnyType.h"
#include "FrontendConverterProvider.h"
#include "../JSIContext.h"
namespace expo {
AnyType::AnyType(
jni::local_ref<expo::ExpectedType> expectedType
) : converter(FrontendConverterProvider::instance()->obtainConverter(std::move(expectedType))) {}
} // namespace expo

View File

@@ -0,0 +1,27 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include "ExpectedType.h"
#include "FrontendConverter.h"
#include <fbjni/fbjni.h>
namespace jni = facebook::jni;
namespace expo {
class JSIContext;
/**
* Holds information about the expected Kotlin type.
*/
class AnyType {
public:
AnyType(jni::local_ref<ExpectedType> expectedType);
/*
* An instance of convert that should be used to convert from the jsi to the expected JNI type.
*/
std::shared_ptr<FrontendConverter> converter;
};
} // namespace expo

View File

@@ -0,0 +1,39 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
namespace expo {
/**
* A cpp version of the `expo.modules.kotlin.jni.CppType` enum.
* Used to determine which representation of the js value should be sent to the Kotlin.
*/
enum class CppType {
NONE = 0,
DOUBLE = 1 << 0,
INT = 1 << 1,
LONG = 1 << 2,
FLOAT = 1 << 3,
BOOLEAN = 1 << 4,
STRING = 1 << 5,
JS_OBJECT = 1 << 6,
JS_VALUE = 1 << 7,
READABLE_ARRAY = 1 << 8,
READABLE_MAP = 1 << 9,
UINT8_TYPED_ARRAY = 1 << 10,
TYPED_ARRAY = 1 << 11,
PRIMITIVE_ARRAY = 1 << 12,
ARRAY = 1 << 13,
LIST = 1 << 14,
MAP = 1 << 15,
VIEW_TAG = 1 << 16,
SHARED_OBJECT_ID = 1 << 17,
JS_FUNCTION = 1 << 18,
ANY = 1 << 19,
NULLABLE = 1 << 20,
VALUE_OR_UNDEFINED = 1 << 21,
JS_ARRAY_BUFFER = 1 << 22,
NATIVE_ARRAY_BUFFER = 1 << 23,
SERIALIZABLE = 1 << 24,
};
} // namespace expo

View File

@@ -0,0 +1,118 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#include "ExpectedType.h"
namespace expo {
jni::local_ref<ExpectedType::javaobject> SingleType::getFirstParameterType() {
static const auto method = getClass()->getMethod<jni::local_ref<ExpectedType::javaobject>()>(
"getFirstParameterType");
return method(self());
}
jni::local_ref<ExpectedType::javaobject> SingleType::getSecondParameterType() {
static const auto method = getClass()->getMethod<jni::local_ref<ExpectedType::javaobject>()>(
"getSecondParameterType");
return method(self());
}
CppType SingleType::getCppType() {
static const auto method = getClass()->getMethod<int()>("getCppType");
return static_cast<CppType>(method(self()));
}
CppType ExpectedType::getCombinedTypes() {
static const auto method = getClass()->getMethod<int()>("getCombinedTypes");
return static_cast<CppType>(method(self()));
}
jni::local_ref<SingleType::javaobject> ExpectedType::getFirstType() {
static const auto method = getClass()->getMethod<jni::local_ref<SingleType::javaobject>()>(
"getFirstType");
return method(self());
}
std::string ExpectedType::getJClassString(bool allowsPrimitives) {
CppType type = this->getCombinedTypes();
if (type == CppType::DOUBLE) {
if (allowsPrimitives) {
return "D";
}
return "java/lang/Double";
}
if (type == CppType::BOOLEAN) {
if (allowsPrimitives) {
return "Z";
}
return "java/lang/Boolean";
}
if (type == CppType::INT) {
if (allowsPrimitives) {
return "I";
}
return "java/lang/Integer";
}
if (type == CppType::FLOAT) {
if (allowsPrimitives) {
return "F";
}
return "java/lang/Float";
}
if (type == CppType::LONG) {
if (allowsPrimitives) {
return "J";
}
return "java/lang/Long";
}
if (type == CppType::STRING) {
return "java/lang/String";
}
if (type == CppType::JS_OBJECT) {
return "expo/modules/kotlin/jni/JavaScriptObject";
}
if (type == CppType::JS_VALUE) {
return "expo/modules/kotlin/jni/JavaScriptValue";
}
if (type == CppType::READABLE_ARRAY) {
return "com/facebook/react/bridge/ReadableNativeArray";
}
if (type == CppType::READABLE_MAP) {
return "com/facebook/react/bridge/ReadableNativeMap";
}
if (type == CppType::UINT8_TYPED_ARRAY) {
return "[B";
}
if (type == CppType::TYPED_ARRAY) {
return "expo/modules/kotlin/jni/JavaScriptTypedArray";
}
if (type == CppType::PRIMITIVE_ARRAY) {
auto innerType = this->getFirstType()->getFirstParameterType()->getJClassString(true);
if (innerType.size() == 1) {
// is a primitive type
return "[" + innerType;
}
return "[L" + innerType + ";";
}
if (type == CppType::ARRAY) {
auto innerType = this->getFirstType()->getFirstParameterType()->getJClassString();
if (innerType.starts_with("[")) {
// contains another array
return "[" + innerType;
}
return "[L" + innerType + ";";
}
if (type == CppType::NULLABLE) {
return this->getFirstType()->getFirstParameterType()->getJClassString();
}
if (type == CppType::LIST) {
return "java/util/ArrayList";
}
return "java/lang/Object";
}
jni::local_ref<jni::JArrayClass<SingleType>::javaobject> ExpectedType::getPossibleTypes() {
static const auto method = getClass()->getMethod<jni::local_ref<jni::JArrayClass<SingleType>::javaobject>()>(
"getPossibleTypes");
return method(self());
}
} // namespace expo

View File

@@ -0,0 +1,49 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include "CppType.h"
#include <fbjni/fbjni.h>
namespace jni = facebook::jni;
namespace expo {
class ExpectedType;
/**
* A C++ representation of the [expo.modules.kotlin.jni.SingleType] class.
*/
class SingleType : public jni::JavaClass<SingleType> {
public:
static auto constexpr
kJavaDescriptor = "Lexpo/modules/kotlin/jni/SingleType;";
CppType getCppType();
jni::local_ref<jni::JavaClass<ExpectedType>::javaobject> getFirstParameterType();
jni::local_ref<jni::JavaClass<ExpectedType>::javaobject> getSecondParameterType();
};
/**
* A C++ representation of the [expo.modules.kotlin.jni.ExpectedType] class.
*/
class ExpectedType : public jni::JavaClass<ExpectedType> {
public:
static auto constexpr
kJavaDescriptor = "Lexpo/modules/kotlin/jni/ExpectedType;";
CppType getCombinedTypes();
jni::local_ref<SingleType::javaobject> getFirstType();
/**
* Converts [ExpectedType] to a string representing a java class.
* If the allowsPrimitives is set to true type like int will be represented as a primitives.
*/
std::string getJClassString(bool allowsPrimitives = false);
jni::local_ref<jni::JArrayClass<SingleType>::javaobject> getPossibleTypes();
};
} // namespace expo

View File

@@ -0,0 +1,772 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#include "FrontendConverter.h"
#include "ExpectedType.h"
#include "FrontendConverterProvider.h"
#include "../JavaReferencesCache.h"
#include "../Exceptions.h"
#include "../JavaScriptTypedArray.h"
#include "../JSIContext.h"
#include "../JavaScriptObject.h"
#include "../JavaScriptArrayBuffer.h"
#include "../NativeArrayBuffer.h"
#include "../JavaScriptValue.h"
#include "../JavaScriptFunction.h"
#include "../javaclasses/Collections.h"
#include "../worklets/Serializable.h"
#include "react/jni/ReadableNativeMap.h"
#include "react/jni/ReadableNativeArray.h"
#include <jsi/JSIDynamic.h>
#include <utility>
#include <algorithm>
namespace jni = facebook::jni;
namespace jsi = facebook::jsi;
namespace react = facebook::react;
namespace expo {
jobject IntegerFrontendConverter::convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
jvalue jValue{
.i = static_cast<int>(value.asNumber())
};
auto &integerClass = JCacheHolder::get().jInteger;
return env->NewObjectA(integerClass.clazz, integerClass.constructor, &jValue);
}
bool IntegerFrontendConverter::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
return value.isNumber();
}
jobject LongFrontendConverter::convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
jvalue jValue{
.j = static_cast<jlong>(value.asNumber())
};
auto &longClass = JCacheHolder::get().jLong;
return env->NewObjectA(longClass.clazz, longClass.constructor, &jValue);
}
bool LongFrontendConverter::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
return value.isNumber();
}
jobject FloatFrontendConverter::convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
jvalue jValue{
.f = static_cast<float>(value.asNumber())
};
auto &floatClass = JCacheHolder::get().jFloat;
return env->NewObjectA(floatClass.clazz, floatClass.constructor, &jValue);
}
bool FloatFrontendConverter::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
return value.isNumber();
}
jobject BooleanFrontendConverter::convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
jvalue jValue{
.z = value.asBool()
};
auto &booleanClass = JCacheHolder::get().jBoolean;
return env->NewObjectA(booleanClass.clazz, booleanClass.constructor, &jValue);
}
bool BooleanFrontendConverter::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
return value.isBool();
}
jobject DoubleFrontendConverter::convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
jvalue jValue{
.d = value.asNumber()
};
auto &doubleClass = JCacheHolder::get().jDouble;
return env->NewObjectA(doubleClass.clazz, doubleClass.constructor, &jValue);
}
bool DoubleFrontendConverter::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
return value.isNumber();
}
jobject StringFrontendConverter::convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
return env->NewStringUTF(value.asString(rt).utf8(rt).c_str());
}
bool StringFrontendConverter::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
return value.isString();
}
jobject ReadableNativeArrayFrontendConverter::convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
auto dynamic = jsi::dynamicFromValue(rt, value);
return react::ReadableNativeArray::newObjectCxxArgs(std::move(dynamic)).release();
}
bool ReadableNativeArrayFrontendConverter::canConvert(
jsi::Runtime &rt,
const jsi::Value &value
) const {
return value.isObject() && value.getObject(rt).isArray(rt);
}
jobject ReadableNativeMapArrayFrontendConverter::convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
auto dynamic = jsi::dynamicFromValue(rt, value);
return react::ReadableNativeMap::createWithContents(std::move(dynamic)).release();
}
bool ReadableNativeMapArrayFrontendConverter::canConvert(
jsi::Runtime &rt,
const jsi::Value &value) const {
return value.isObject();
}
jobject ByteArrayFrontendConverter::convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
auto typedArray = TypedArray(rt, value.asObject(rt));
size_t length = typedArray.byteLength(rt);
auto byteArray = jni::JArrayByte::newArray(length);
byteArray->setRegion(0, length, static_cast<const signed char *>(typedArray.getRawPointer(rt)));
return byteArray.release();
}
bool ByteArrayFrontendConverter::canConvert(
jsi::Runtime &rt,
const jsi::Value &value
) const {
if (value.isObject()) {
auto object = value.getObject(rt);
if (isTypedArray(rt, object)) {
auto typedArray = TypedArray(rt, object);
return typedArray.getKind(rt) == TypedArrayKind::Uint8Array;
}
}
return false;
}
jobject TypedArrayFrontendConverter::convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
JSIContext *jsiContext = getJSIContext(rt);
return JavaScriptTypedArray::newInstance(
jsiContext,
jsiContext->runtimeHolder->weak_from_this(),
std::make_shared<jsi::Object>(value.asObject(rt))
).release();
}
bool TypedArrayFrontendConverter::canConvert(
jsi::Runtime &rt,
const jsi::Value &value
) const {
return value.isObject();
}
jobject NativeArrayBufferFrontendConverter::convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
JSIContext *jsiContext = getJSIContext(rt);
auto arrayBuffer = value.asObject(rt).getArrayBuffer(rt);
return NativeArrayBuffer::newInstance(
jsiContext,
rt,
arrayBuffer
).release();
}
bool NativeArrayBufferFrontendConverter::canConvert(
jsi::Runtime &rt,
const jsi::Value &value
) const {
if (value.isObject()) {
auto object = value.getObject(rt);
return object.isArrayBuffer(rt);
}
return false;
}
jobject JavaScriptValueFrontendConverter::convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
JSIContext *jsiContext = getJSIContext(rt);
return JavaScriptValue::newInstance(
jsiContext,
jsiContext->runtimeHolder->weak_from_this(),
// TODO(@lukmccall): make sure that copy here is necessary
std::make_shared<jsi::Value>(jsi::Value(rt, value))
).release();
}
bool JavaScriptValueFrontendConverter::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
return true;
}
jobject JavaScriptObjectFrontendConverter::convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
JSIContext *jsiContext = getJSIContext(rt);
return JavaScriptObject::newInstance(
jsiContext,
jsiContext->runtimeHolder->weak_from_this(),
std::make_shared<jsi::Object>(value.asObject(rt))
).release();
}
bool JavaScriptObjectFrontendConverter::canConvert(
jsi::Runtime &rt,
const jsi::Value &value
) const {
return value.isObject();
}
jobject JavaScriptArrayBufferFrontendConverter::convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
JSIContext *jsiContext = getJSIContext(rt);
return JavaScriptArrayBuffer::newInstance(
jsiContext,
jsiContext->runtimeHolder->weak_from_this(),
std::make_shared<jsi::ArrayBuffer>(value.asObject(rt).getArrayBuffer(rt))
).release();
}
bool JavaScriptArrayBufferFrontendConverter::canConvert(
jsi::Runtime &rt,
const jsi::Value &value
) const {
return value.isObject() && value.getObject(rt).isArrayBuffer(rt);
}
jobject JavaScriptFunctionFrontendConverter::convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
JSIContext *jsiContext = getJSIContext(rt);
return JavaScriptFunction::newInstance(
jsiContext,
jsiContext->runtimeHolder->weak_from_this(),
std::make_shared<jsi::Function>(value.asObject(rt).asFunction(rt))
).release();
}
bool JavaScriptFunctionFrontendConverter::canConvert(
jsi::Runtime &rt,
const jsi::Value &value
) const {
return value.isObject() && value.getObject(rt).isFunction(rt);
}
jobject UnknownFrontendConverter::convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
auto stringRepresentation = value.toString(rt).utf8(rt);
throwNewJavaException(
UnexpectedException::create(
"Cannot convert '" + stringRepresentation + "' to a Kotlin type.").get()
);
}
bool UnknownFrontendConverter::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
return true;
}
PolyFrontendConverter::PolyFrontendConverter(
std::vector<std::shared_ptr<FrontendConverter>> converters
) : converters(std::move(converters)) {
}
bool PolyFrontendConverter::canConvert(
jsi::Runtime &rt,
const jsi::Value &value
) const {
// Checks whether any of inner converters can handle the conversion.
return std::any_of(
converters.begin(),
converters.end(),
[&rt = rt, &value = value](const std::shared_ptr<FrontendConverter> &converter) {
return converter->canConvert(rt, value);
}
);
}
jobject PolyFrontendConverter::convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
for (auto &converter: converters) {
if (converter->canConvert(rt, value)) {
return converter->convert(rt, env, value);
}
}
// That shouldn't happen.
auto stringRepresentation = value.toString(rt).utf8(rt);
throwNewJavaException(
UnexpectedException::create(
"Cannot convert '" + stringRepresentation + "' to a Kotlin type.").get()
);
}
PrimitiveArrayFrontendConverter::PrimitiveArrayFrontendConverter(
jni::local_ref<SingleType::javaobject> expectedType
) {
auto parameterExpectedType = expectedType->getFirstParameterType();
parameterType = parameterExpectedType->getCombinedTypes();
parameterConverter = FrontendConverterProvider::instance()->obtainConverter(
parameterExpectedType
);
javaType = parameterExpectedType->getJClassString();
}
template<typename T, typename A>
jobject createPrimitiveArray(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Array &jsArray,
A (JNIEnv::*arrayConstructor)(jsize),
void (JNIEnv::*setRegion)(A, jsize, jsize, const T *)
) {
size_t size = jsArray.size(rt);
std::vector<T> tmpVector(size);
for (size_t i = 0; i < size; i++) {
tmpVector[i] = (T) jsArray.getValueAtIndex(rt, i).asNumber();
}
auto result = std::invoke(arrayConstructor, env, size);
std::invoke(setRegion, env, result, 0, size, tmpVector.data());
return result;
}
jobject PrimitiveArrayFrontendConverter::convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
auto jsArray = value.asObject(rt).asArray(rt);
auto _createPrimitiveArray = [&rt, env, &jsArray](
auto arrayConstructor, auto setRegion
) -> jobject {
return createPrimitiveArray(rt, env, jsArray, arrayConstructor, setRegion);
};
if (parameterType == CppType::INT) {
return _createPrimitiveArray(
&JNIEnv::NewIntArray,
&JNIEnv::SetIntArrayRegion
);
}
if (parameterType == CppType::LONG) {
return _createPrimitiveArray(
&JNIEnv::NewLongArray,
&JNIEnv::SetLongArrayRegion
);
}
if (parameterType == CppType::DOUBLE) {
return _createPrimitiveArray(
&JNIEnv::NewDoubleArray,
&JNIEnv::SetDoubleArrayRegion
);
}
if (parameterType == CppType::FLOAT) {
return _createPrimitiveArray(
&JNIEnv::NewFloatArray,
&JNIEnv::SetFloatArrayRegion
);
}
if (parameterType == CppType::BOOLEAN) {
return _createPrimitiveArray(
&JNIEnv::NewBooleanArray,
&JNIEnv::SetBooleanArrayRegion
);
}
size_t size = jsArray.size(rt);
auto result = env->NewObjectArray(
size,
JCacheHolder::get().getOrLoadJClass(env, javaType),
nullptr
);
for (size_t i = 0; i < size; i++) {
auto convertedElement = parameterConverter->convert(
rt, env, jsArray.getValueAtIndex(rt, i)
);
env->SetObjectArrayElement(result, i, convertedElement);
env->DeleteLocalRef(convertedElement);
}
return result;
}
bool PrimitiveArrayFrontendConverter::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
return value.isObject() && value.getObject(rt).isArray(rt);
}
ArrayFrontendConverter::ArrayFrontendConverter(
jni::local_ref<SingleType::javaobject> expectedType
) {
auto parameterExpectedType = expectedType->getFirstParameterType();
parameterType = parameterExpectedType->getCombinedTypes();
parameterConverter = FrontendConverterProvider::instance()->obtainConverter(
parameterExpectedType
);
javaType = parameterExpectedType->getJClassString();
}
jobject ArrayFrontendConverter::convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
auto jsArray = value.asObject(rt).asArray(rt);
size_t size = jsArray.size(rt);
auto result = env->NewObjectArray(
size,
JCacheHolder::get().getOrLoadJClass(env, javaType),
nullptr
);
for (size_t i = 0; i < size; i++) {
auto convertedElement = parameterConverter->convert(
rt, env, jsArray.getValueAtIndex(rt, i)
);
env->SetObjectArrayElement(result, i, convertedElement);
env->DeleteLocalRef(convertedElement);
}
return result;
}
bool ArrayFrontendConverter::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
return value.isObject() && value.getObject(rt).isArray(rt);
}
ListFrontendConverter::ListFrontendConverter(
jni::local_ref<SingleType::javaobject> expectedType
) : parameterConverter(
FrontendConverterProvider::instance()->obtainConverter(
expectedType->getFirstParameterType()
)
) {}
jobject ListFrontendConverter::convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
if (!value.isObject()) {
return convertSingleValue(rt, env, value);
}
auto valueObject = value.asObject(rt);
if (!valueObject.isArray(rt)) {
return convertSingleValue(rt, env, value);
}
auto jsArray = valueObject.asArray(rt);
size_t size = jsArray.size(rt);
auto arrayList = java::ArrayList<jobject>::create(size);
for (size_t i = 0; i < size; i++) {
auto jsValue = jsArray.getValueAtIndex(rt, i);
auto convertedElement = parameterConverter->convert(
rt, env, jsValue
);
arrayList->add(convertedElement);
env->DeleteLocalRef(convertedElement);
}
return arrayList.release();
}
jobject ListFrontendConverter::convertSingleValue(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
auto result = java::ArrayList<jobject>::create(1);
result->add(parameterConverter->convert(rt, env, value));
return result.release();
}
bool ListFrontendConverter::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
return (value.isObject() && value.getObject(rt).isArray(rt)) ||
parameterConverter->canConvert(rt, value);
}
MapFrontendConverter::MapFrontendConverter(
jni::local_ref<SingleType::javaobject> expectedType
) : valueConverter(
FrontendConverterProvider::instance()->obtainConverter(
expectedType->getFirstParameterType()
)
) {}
jobject MapFrontendConverter::convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
auto jsObject = value.asObject(rt);
auto propertyNames = jsObject.getPropertyNames(rt);
size_t size = propertyNames.size(rt);
auto map = java::LinkedHashMap<jobject, jobject>::create(size);
for (size_t i = 0; i < size; i++) {
auto key = propertyNames.getValueAtIndex(rt, i).getString(rt);
auto jsValue = jsObject.getProperty(rt, key);
auto convertedKey = env->NewStringUTF(key.utf8(rt).c_str());
auto convertedValue = valueConverter->convert(
rt, env, jsValue
);
map->put(convertedKey, convertedValue);
env->DeleteLocalRef(convertedKey);
env->DeleteLocalRef(convertedValue);
}
return map.release();
}
bool MapFrontendConverter::canConvert(
jsi::Runtime &rt,
const jsi::Value &value
) const {
return value.isObject();
}
jobject ViewTagFrontendConverter::convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
auto nativeTag = value.asObject(rt).getProperty(rt, "nativeTag");
if (nativeTag.isNull()) {
return nullptr;
}
auto viewTag = (int) nativeTag.getNumber();
auto &integerClass = JCacheHolder::get().jInteger;
return env->NewObject(integerClass.clazz, integerClass.constructor, viewTag);
}
bool ViewTagFrontendConverter::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
return value.isObject() && value.getObject(rt).hasProperty(rt, "nativeTag");
}
jobject SharedObjectIdConverter::convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
auto objectId = value.asObject(rt).getProperty(rt, "__expo_shared_object_id__");
if (objectId.isNull()) {
return nullptr;
}
int id = (int) objectId.asNumber();
auto &integerClass = JCacheHolder::get().jInteger;
return env->NewObject(integerClass.clazz, integerClass.constructor, id);
}
bool SharedObjectIdConverter::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
return value.isObject() && value.getObject(rt).hasProperty(rt, "__expo_shared_object_id__");
}
jobject AnyFrontendConvert::convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
if (booleanConverter.canConvert(rt, value)) {
return booleanConverter.convert(rt, env, value);
}
if (doubleConverter.canConvert(rt, value)) {
return doubleConverter.convert(rt, env, value);
}
if (stringConverter.canConvert(rt, value)) {
return stringConverter.convert(rt, env, value);
}
const jsi::Object &obj = value.asObject(rt);
if (obj.isArray(rt)) {
const jsi::Array &jsArray = obj.asArray(rt);
size_t size = jsArray.size(rt);
auto arrayList = java::ArrayList<jobject>::create(size);
for (size_t i = 0; i < size; i++) {
auto jsValue = jsArray.getValueAtIndex(rt, i);
auto convertedElement = this->convert(
rt, env, jsValue
);
arrayList->add(convertedElement);
env->DeleteLocalRef(convertedElement);
}
return arrayList.release();
}
// it's object, so we're going to convert it to LinkedHashMap
auto propertyNames = obj.getPropertyNames(rt);
size_t size = propertyNames.size(rt);
auto map = java::LinkedHashMap<jobject, jobject>::create(size);
for (size_t i = 0; i < size; i++) {
auto key = propertyNames.getValueAtIndex(rt, i).getString(rt);
auto jsValue = obj.getProperty(rt, key);
auto convertedKey = env->NewStringUTF(key.utf8(rt).c_str());
auto convertedValue = this->convert(
rt, env, jsValue
);
map->put(convertedKey, convertedValue);
env->DeleteLocalRef(convertedKey);
env->DeleteLocalRef(convertedValue);
}
return map.release();
}
bool AnyFrontendConvert::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
return true;
}
NullableFrontendConverter::NullableFrontendConverter(
jni::local_ref<SingleType::javaobject> expectedType
) : parameterConverter(
FrontendConverterProvider::instance()->obtainConverter(
expectedType->getFirstParameterType()
)
) {}
bool NullableFrontendConverter::canConvert(
jsi::Runtime &rt,
const jsi::Value &value
) const {
return value.isNull() || value.isUndefined() ||
parameterConverter->canConvert(rt, value);
}
jobject NullableFrontendConverter::convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
if (value.isNull() || value.isUndefined()) {
return nullptr;
}
return parameterConverter->convert(rt, env, value);
}
ValueOrUndefinedFrontendConverter::ValueOrUndefinedFrontendConverter(
jni::local_ref<SingleType::javaobject> expectedType
) : parameterConverter(
FrontendConverterProvider::instance()->obtainConverter(
expectedType->getFirstParameterType()
)
) {}
bool ValueOrUndefinedFrontendConverter::canConvert(
jsi::Runtime &rt,
const jsi::Value &value
) const {
return value.isUndefined() ||
parameterConverter->canConvert(rt, value);
}
jobject ValueOrUndefinedFrontendConverter::convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
if (value.isUndefined()) {
return env->NewLocalRef(JCacheHolder::get().jUndefined);
}
return parameterConverter->convert(rt, env, value);
}
#if WORKLETS_ENABLED
jobject SynchronizableFrontendConverter::convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const {
JSIContext *jsiContext = getJSIContext(rt);
auto worklet = worklets::extractSerializableOrThrow(rt, value);
return Serializable::newInstance(
jsiContext,
worklet
).release();
}
bool SynchronizableFrontendConverter::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
try {
// TODO(@lukmccall): find a better way to check this without throwing exception
worklets::extractSerializableOrThrow(rt, value);
return true;
} catch (...) {
return false;
}
}
#endif
} // namespace expo

View File

@@ -0,0 +1,505 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include "CppType.h"
#include <jsi/jsi.h>
#include <fbjni/fbjni.h>
#if WORKLETS_ENABLED
#include <worklets/SharedItems/Synchronizable.h>
#endif
namespace jni = facebook::jni;
namespace jsi = facebook::jsi;
namespace expo {
class JSIContext;
class SingleType;
/**
* A base interface for all frontend converter classes - converters that cast jsi values into JNI objects.
* Right now, we have two-step arguments conversion. Firstly, we unwrapped the JSI value into selected JNI objects (see CppType).
* Then, we do a more sophisticated conversion like creating records or mapping into enums.
* The second step lives in the Kotlin codebase.
*/
class FrontendConverter {
public:
virtual ~FrontendConverter() = default;
/**
* Checks if the provided value can be converted.
*/
virtual bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const = 0;
/**
* Converts the provided value.
*/
virtual jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const = 0;
};
/**
* Converter from js number to [java.lang.Integer].
*/
class IntegerFrontendConverter : public FrontendConverter {
public:
jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const override;
bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
};
/**
* Converter from js number to [java.lang.Long].
*/
class LongFrontendConverter : public FrontendConverter {
public:
jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const override;
bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
};
/**
* Converter from js number to [java.lang.Float].
*/
class FloatFrontendConverter : public FrontendConverter {
public:
jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const override;
bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
};
/**
* Converter from js bool to [java.lang.Boolean].
*/
class BooleanFrontendConverter : public FrontendConverter {
public:
jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const override;
bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
};
/**
* Converter from js number to [java.lang.Double].
*/
class DoubleFrontendConverter : public FrontendConverter {
public:
jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const override;
bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
};
/**
* Converter from js string to [java.lang.String].
*/
class StringFrontendConverter : public FrontendConverter {
public:
jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const override;
bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
};
/**
* Converter from js array to [com.facebook.react.bridge.ReadableNativeArray].
*/
class ReadableNativeArrayFrontendConverter : public FrontendConverter {
public:
jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const override;
bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
};
/**
* Converter from js object to [com.facebook.react.bridge.ReadableNativeMap].
*/
class ReadableNativeMapArrayFrontendConverter : public FrontendConverter {
public:
jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const override;
bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
};
/**
* Converter from js Uint8Array to [java.lang.Byte] array.
*/
class ByteArrayFrontendConverter : public FrontendConverter {
public:
jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const override;
bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
};
/**
* Converter from js type array to [expo.modules.kotlin.jni.JavaScriptTypedArray].
*/
class TypedArrayFrontendConverter : public FrontendConverter {
public:
jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const override;
bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
};
class NativeArrayBufferFrontendConverter : public FrontendConverter {
public:
jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const override;
bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
};
/**
* Converter from any js value to [expo.modules.kotlin.jni.JavaScriptValue].
*/
class JavaScriptValueFrontendConverter : public FrontendConverter {
public:
jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const override;
bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
};
/**
* Converter from js object to [expo.modules.kotlin.jni.JavaScriptObject].
*/
class JavaScriptObjectFrontendConverter : public FrontendConverter {
public:
jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const override;
bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
};
/**
* Converter from js function to [expo.modules.kotlin.jni.JavaScriptFunction].
*/
class JavaScriptFunctionFrontendConverter : public FrontendConverter {
public:
jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const override;
bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
};
/**
* Converter from js function to [expo.modules.kotlin.jni.JavaScriptArrayBuffer].
*/
class JavaScriptArrayBufferFrontendConverter : public FrontendConverter {
public:
jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const override;
bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
};
/**
* Converter from js view object to int.
*/
class ViewTagFrontendConverter : public FrontendConverter {
public:
jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const override;
bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
};
/**
* Converter from js shared object to int.
*/
class SharedObjectIdConverter : public FrontendConverter {
public:
jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const override;
bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
};
/**
* Converter that always fails.
* Used to not fail when the function is created.
* TODO(@lukmccall): remove
*/
class UnknownFrontendConverter : public FrontendConverter {
public:
jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const override;
bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
};
/**
* Same types like enums can be represented by multiply js types.
* That's why we have a converter that can combine multiple converters into one.
*
* For instance, enum classes will be represented as a PolyFrontendConverter({StringFrontendConverter, IntegerFrontendConverter})
*/
class PolyFrontendConverter : public FrontendConverter {
public:
PolyFrontendConverter(std::vector<std::shared_ptr<FrontendConverter>> converters);
bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const override;
private:
std::vector<std::shared_ptr<FrontendConverter>> converters;
};
/**
* Converter from js array object to Java primitive array.
*/
class PrimitiveArrayFrontendConverter : public FrontendConverter {
public:
PrimitiveArrayFrontendConverter(
jni::local_ref<jni::JavaClass<SingleType>::javaobject> expectedType
);
jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const override;
bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
private:
/**
* A string representation of desired Java type.
*/
std::string javaType;
/**
* Bare parameter type.
*/
CppType parameterType;
/**
* Converter used to convert array elements.
*/
std::shared_ptr<FrontendConverter> parameterConverter;
};
/**
* Converter from js array object to Java array.
*/
class ArrayFrontendConverter : public FrontendConverter {
public:
ArrayFrontendConverter(
jni::local_ref<jni::JavaClass<SingleType>::javaobject> expectedType
);
jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const override;
bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
private:
/**
* A string representation of desired Java type.
*/
std::string javaType;
/**
* Bare parameter type.
*/
CppType parameterType;
/**
* Converter used to convert array elements.
*/
std::shared_ptr<FrontendConverter> parameterConverter;
};
/**
* Converter from js array object to [java.utils.ArrayList].
*/
class ListFrontendConverter : public FrontendConverter {
public:
ListFrontendConverter(
jni::local_ref<jni::JavaClass<SingleType>::javaobject> expectedType
);
jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const override;
bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
private:
/**
* Converter used to convert array elements.
*/
std::shared_ptr<FrontendConverter> parameterConverter;
jobject convertSingleValue(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const;
};
/**
* Converter from js object to [java.utils.LinkedHashMap].
*/
class MapFrontendConverter : public FrontendConverter {
public:
MapFrontendConverter(
jni::local_ref<jni::JavaClass<SingleType>::javaobject> expectedType
);
jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const override;
bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
private:
/**
* Converter used to convert values.
*/
std::shared_ptr<FrontendConverter> valueConverter;
};
/**
* Converter from js object to [kotlin.Any] (Boolean, Double, String, Map<Any>, List<Any>).
*/
class AnyFrontendConvert : public FrontendConverter {
public:
jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const override;
bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
private:
BooleanFrontendConverter booleanConverter;
DoubleFrontendConverter doubleConverter;
StringFrontendConverter stringConverter;
};
class NullableFrontendConverter : public FrontendConverter {
public:
NullableFrontendConverter(
jni::local_ref<jni::JavaClass<SingleType>::javaobject> expectedType
);
jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const override;
bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
private:
std::shared_ptr<FrontendConverter> parameterConverter;
};
class ValueOrUndefinedFrontendConverter : public FrontendConverter {
public:
ValueOrUndefinedFrontendConverter(
jni::local_ref<jni::JavaClass<SingleType>::javaobject> expectedType
);
jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const override;
bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
private:
std::shared_ptr<FrontendConverter> parameterConverter;
};
#if WORKLETS_ENABLED
class SynchronizableFrontendConverter : public FrontendConverter {
public:
jobject convert(
jsi::Runtime &rt,
JNIEnv *env,
const jsi::Value &value
) const override;
bool canConvert(jsi::Runtime &rt, const jsi::Value &value) const override;
};
#endif
} // namespace expo

View File

@@ -0,0 +1,132 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#include "FrontendConverterProvider.h"
namespace expo {
std::shared_ptr<FrontendConverterProvider> FrontendConverterProvider::instance() {
static std::shared_ptr<FrontendConverterProvider> singleton{new FrontendConverterProvider};
return singleton;
}
void FrontendConverterProvider::createConverters() {
#define RegisterConverter(type, clazz) simpleConverters.insert({type, std::make_shared<clazz>()})
RegisterConverter(CppType::NONE, UnknownFrontendConverter);
RegisterConverter(CppType::INT, IntegerFrontendConverter);
RegisterConverter(CppType::LONG, LongFrontendConverter);
RegisterConverter(CppType::FLOAT, FloatFrontendConverter);
RegisterConverter(CppType::DOUBLE, DoubleFrontendConverter);
RegisterConverter(CppType::BOOLEAN, BooleanFrontendConverter);
RegisterConverter(CppType::UINT8_TYPED_ARRAY, ByteArrayFrontendConverter);
RegisterConverter(CppType::TYPED_ARRAY, TypedArrayFrontendConverter);
RegisterConverter(CppType::JS_OBJECT, JavaScriptObjectFrontendConverter);
RegisterConverter(CppType::JS_VALUE, JavaScriptValueFrontendConverter);
RegisterConverter(CppType::JS_ARRAY_BUFFER, JavaScriptArrayBufferFrontendConverter);
RegisterConverter(CppType::NATIVE_ARRAY_BUFFER, NativeArrayBufferFrontendConverter);
RegisterConverter(CppType::JS_FUNCTION, JavaScriptFunctionFrontendConverter);
RegisterConverter(CppType::STRING, StringFrontendConverter);
RegisterConverter(CppType::READABLE_MAP, ReadableNativeMapArrayFrontendConverter);
RegisterConverter(CppType::READABLE_ARRAY, ReadableNativeArrayFrontendConverter);
RegisterConverter(CppType::VIEW_TAG, ViewTagFrontendConverter);
RegisterConverter(CppType::SHARED_OBJECT_ID, SharedObjectIdConverter);
RegisterConverter(CppType::ANY, AnyFrontendConvert);
#if WORKLETS_ENABLED
RegisterConverter(CppType::SERIALIZABLE, SynchronizableFrontendConverter);
#endif
#undef RegisterConverter
auto registerPolyConverter = [this](const std::vector<CppType> &types) {
std::vector<std::shared_ptr<FrontendConverter>> converters;
CppType finalType = CppType::NONE;
for (const auto type: types) {
finalType = (CppType) ((int) finalType | (int) type);
converters.push_back(simpleConverters.at(type));
}
simpleConverters.insert({finalType, std::make_shared<PolyFrontendConverter>(converters)});
};
// Enums
registerPolyConverter({CppType::STRING, CppType::INT});
}
std::shared_ptr<FrontendConverter> FrontendConverterProvider::obtainConverter(
jni::local_ref<ExpectedType::javaobject> expectedType
) {
CppType combinedType = expectedType->getCombinedTypes();
auto result = simpleConverters.find(combinedType);
if (result != simpleConverters.end()) {
return result->second;
}
if (combinedType == CppType::NULLABLE) {
return std::make_shared<NullableFrontendConverter>(expectedType->getFirstType());
}
if (combinedType == CppType::PRIMITIVE_ARRAY) {
return std::make_shared<PrimitiveArrayFrontendConverter>(expectedType->getFirstType());
}
if (combinedType == CppType::ARRAY) {
return std::make_shared<ArrayFrontendConverter>(expectedType->getFirstType());
}
if (combinedType == CppType::LIST) {
return std::make_shared<ListFrontendConverter>(expectedType->getFirstType());
}
if (combinedType == CppType::MAP) {
return std::make_shared<MapFrontendConverter>(expectedType->getFirstType());
}
if (combinedType == CppType::VALUE_OR_UNDEFINED) {
return std::make_shared<ValueOrUndefinedFrontendConverter>(expectedType->getFirstType());
}
std::vector<std::shared_ptr<FrontendConverter>> converters;
auto singleTypes = expectedType->getPossibleTypes();
size_t size = singleTypes->size();
for (size_t i = 0; i < size; i++) {
jni::local_ref<SingleType> singleType = singleTypes->getElement(i);
converters.push_back(this->obtainConverterForSingleType(singleType));
}
if (converters.empty()) {
// We don't have a converter for the expected type. That's why we used an UnknownFrontendConverter.
return simpleConverters.at(CppType::NONE);
}
return std::make_shared<PolyFrontendConverter>(converters);
}
std::shared_ptr<FrontendConverter> FrontendConverterProvider::obtainConverterForSingleType(
jni::local_ref<SingleType::javaobject> expectedType
) {
CppType combinedType = expectedType->getCppType();
auto result = simpleConverters.find(combinedType);
if (result != simpleConverters.end()) {
return result->second;
}
if (combinedType == CppType::PRIMITIVE_ARRAY) {
return std::make_shared<PrimitiveArrayFrontendConverter>(expectedType);
}
if (combinedType == CppType::ARRAY) {
return std::make_shared<ArrayFrontendConverter>(expectedType);
}
if (combinedType == CppType::LIST) {
return std::make_shared<ListFrontendConverter>(expectedType);
}
if (combinedType == CppType::MAP) {
return std::make_shared<MapFrontendConverter>(expectedType);
}
// We don't have a converter for the expected type. That's why we used an UnknownFrontendConverter.
return simpleConverters.at(CppType::NONE);
}
} // namespace expo

View File

@@ -0,0 +1,47 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include "CppType.h"
#include "FrontendConverter.h"
#include "ExpectedType.h"
#include <fbjni/fbjni.h>
#include <memory>
#include <unordered_map>
namespace jni = facebook::jni;
namespace expo {
/**
* Singleton registry used to store all basic converters.
*/
class FrontendConverterProvider {
public:
/**
* Gets a singleton instance.
*/
static std::shared_ptr<FrontendConverterProvider> instance();
/**
* Creates converters.
*/
void createConverters();
/**
* Obtains a converter for an expected type.
*/
std::shared_ptr<FrontendConverter> obtainConverter(
jni::local_ref<jni::JavaClass<ExpectedType>::javaobject> expectedType
);
private:
FrontendConverterProvider() = default;
std::shared_ptr<FrontendConverter> obtainConverterForSingleType(
jni::local_ref<jni::JavaClass<SingleType>::javaobject> expectedType
);
std::unordered_map<CppType, std::shared_ptr<FrontendConverter>> simpleConverters;
};
} // namespace expo

View File

@@ -0,0 +1,193 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#include "JNIToJSIConverter.h"
#include "../JavaReferencesCache.h"
namespace react = facebook::react;
namespace expo {
// This value should be synced with the value in **FollyDynamicExtensionConverter.kt**
constexpr char DYNAMIC_EXTENSION_PREFIX[] = "__expo_dynamic_extension__#";
/**
* Create an JavaScript Uint8Array instance from Java ByteArray.
*/
jsi::Value createUint8Array(jsi::Runtime &rt, jni::alias_ref<jni::JArrayByte> byteArray) {
auto arrayBufferCtor = rt.global().getPropertyAsFunction(rt, "ArrayBuffer");
auto arrayBufferObject = arrayBufferCtor.callAsConstructor(rt, static_cast<int>(byteArray->size())).getObject(rt);
auto arrayBuffer = arrayBufferObject.getArrayBuffer(rt);
byteArray->getRegion(0, byteArray->size(), reinterpret_cast<signed char *>(arrayBuffer.data(rt)));
auto uint8ArrayCtor = rt.global().getPropertyAsFunction(rt, "Uint8Array");
auto uint8Array = uint8ArrayCtor.callAsConstructor(rt, arrayBufferObject).getObject(rt);
return uint8Array;
}
/**
* Convert a string with FollyDynamicExtensionConverter support.
*/
std::optional<jsi::Value> convertStringToFollyDynamicIfNeeded(jsi::Runtime &rt, const std::string& string) {
if (!string.starts_with(DYNAMIC_EXTENSION_PREFIX)) {
return std::nullopt;
}
auto converterClass = jni::findClassLocal("expo/modules/kotlin/types/folly/FollyDynamicExtensionConverter");
const auto getInstanceMethod = converterClass->getStaticMethod<jni::JObject(std::string)>("get");
jni::local_ref<jni::JObject> instance = getInstanceMethod(converterClass, string);
if (instance->isInstanceOf(jni::JArrayByte::javaClassStatic())) {
return createUint8Array(rt, jni::static_ref_cast<jni::JArrayByte>(instance));
}
return std::nullopt;
}
jsi::Value convert(
JNIEnv *env,
jsi::Runtime &rt,
const jni::local_ref<jobject> &value
) {
if (value == nullptr) {
return jsi::Value::null();
}
auto unpackedValue = value.get();
auto &cache = JCacheHolder::get();
// We could use jni::static_ref_cast here. It will lead to the creation of a new local reference.
// Which is actually slow. We can use some pointer magic to avoid it.
#define CAST_AND_RETURN(type, clazz) \
if (env->IsInstanceOf(unpackedValue, clazz)) { \
return convertToJS(env, rt, *((jni::local_ref<type>*)((void*)&value))); \
}
#define COMMA ,
CAST_AND_RETURN(jni::JDouble, cache.jDouble.clazz)
CAST_AND_RETURN(jni::JInteger, cache.jInteger.clazz)
CAST_AND_RETURN(jni::JLong, cache.jLong.clazz)
CAST_AND_RETURN(jni::JString, cache.jString)
CAST_AND_RETURN(jni::JBoolean, cache.jBoolean.clazz)
CAST_AND_RETURN(jni::JFloat, cache.jFloat.clazz)
CAST_AND_RETURN(react::WritableNativeArray::javaobject, cache.jWritableNativeArray)
CAST_AND_RETURN(react::WritableNativeMap::javaobject, cache.jWritableNativeMap)
CAST_AND_RETURN(JavaScriptModuleObject::javaobject, cache.jJavaScriptModuleObject)
CAST_AND_RETURN(JSharedObject::javaobject, cache.jSharedObject)
CAST_AND_RETURN(JavaScriptTypedArray::javaobject, cache.jJavaScriptTypedArray)
CAST_AND_RETURN(JavaScriptArrayBuffer::javaobject, cache.jJavaScriptArrayBuffer)
CAST_AND_RETURN(NativeArrayBuffer::javaobject, cache.jNativeArrayBuffer)
CAST_AND_RETURN(jni::JMap<jstring COMMA jobject>, cache.jMap)
CAST_AND_RETURN(jni::JCollection<jobject>, cache.jCollection)
// Primitives arrays
CAST_AND_RETURN(jni::JArrayDouble, cache.jDoubleArray)
CAST_AND_RETURN(jni::JArrayBoolean, cache.jBooleanArray)
CAST_AND_RETURN(jni::JArrayInt, cache.jIntegerArray)
CAST_AND_RETURN(jni::JArrayLong, cache.jLongArray)
CAST_AND_RETURN(jni::JArrayFloat, cache.jFloatArray)
#undef COMMA
#undef CAST_AND_RETURN
return jsi::Value::undefined();
}
jsi::Value convert(
JNIEnv *env,
jsi::Runtime &rt,
ReturnType returnType,
const jni::local_ref<jobject> &value
) {
#define CAST_AND_RETURN(type) \
return convertToJS(env, rt, *((jni::local_ref<type>*)((void*)&value)));
#define COMMA ,
switch (returnType) {
case ReturnType::UNKNOWN:
return convert(env, rt, value);
case ReturnType::DOUBLE:
CAST_AND_RETURN(jni::JDouble)
case ReturnType::INT:
CAST_AND_RETURN(jni::JInteger)
case ReturnType::LONG:
CAST_AND_RETURN(jni::JLong)
case ReturnType::STRING:
CAST_AND_RETURN(jni::JString)
case ReturnType::BOOLEAN:
CAST_AND_RETURN(jni::JBoolean)
case ReturnType::FLOAT:
CAST_AND_RETURN(jni::JFloat)
case ReturnType::WRITEABLE_ARRAY:
CAST_AND_RETURN(react::WritableNativeArray::javaobject)
case ReturnType::WRITEABLE_MAP:
CAST_AND_RETURN(react::WritableNativeMap::javaobject)
case ReturnType::JS_MODULE:
CAST_AND_RETURN(JavaScriptModuleObject::javaobject)
case ReturnType::SHARED_OBJECT:
CAST_AND_RETURN(JSharedObject::javaobject)
case ReturnType::JS_TYPED_ARRAY:
CAST_AND_RETURN(JavaScriptTypedArray::javaobject)
case ReturnType::JS_ARRAY_BUFFER:
CAST_AND_RETURN(JavaScriptArrayBuffer::javaobject)
case ReturnType::NATIVE_ARRAY_BUFFER:
CAST_AND_RETURN(NativeArrayBuffer::javaobject)
case ReturnType::MAP:
CAST_AND_RETURN(jni::JMap<jstring COMMA jobject>)
case ReturnType::COLLECTION:
CAST_AND_RETURN(jni::JCollection<jobject>)
case ReturnType::DOUBLE_ARRAY:
CAST_AND_RETURN(jni::JArrayDouble)
case ReturnType::INT_ARRAY:
CAST_AND_RETURN(jni::JArrayInt)
case ReturnType::LONG_ARRAY:
CAST_AND_RETURN(jni::JArrayLong)
case ReturnType::FLOAT_ARRAY:
CAST_AND_RETURN(jni::JArrayFloat)
case ReturnType::BOOLEAN_ARRAY:
CAST_AND_RETURN(jni::JArrayBoolean )
}
#undef COMMA
#undef CAST_AND_RETURN
assert("Unhandled ReturnType in JNIToJSIConverter::convert" && false);
}
std::optional<jsi::Value> decorateValueForDynamicExtension(jsi::Runtime &rt, const jsi::Value &value) {
if (value.isString()) {
std::string string = value.getString(rt).utf8(rt);
return convertStringToFollyDynamicIfNeeded(rt, string);
}
if (value.isObject()) {
auto jsObject = value.getObject(rt);
if (jsObject.isArray(rt)) {
bool changed = false;
auto jsArray = jsObject.getArray(rt);
size_t length = jsArray.length(rt);
for (size_t i = 0; i < length; ++i) {
auto converted = decorateValueForDynamicExtension(rt, jsArray.getValueAtIndex(rt, i));
if (converted) {
jsArray.setValueAtIndex(rt, i, std::move(*converted));
changed = true;
}
}
return changed ? std::make_optional<jsi::Value>(std::move(jsArray)) : std::nullopt;
} else {
bool changed = false;
auto propNames = jsObject.getPropertyNames(rt);
size_t length = propNames.length(rt);
for (size_t i = 0; i < length; ++i) {
auto propName = propNames.getValueAtIndex(rt, i).getString(rt);
auto converted = decorateValueForDynamicExtension(rt, jsObject.getProperty(rt, propName));
if (converted) {
jsObject.setProperty(rt, propName, std::move(*converted));
changed = true;
}
}
return changed ? std::make_optional<jsi::Value>(std::move(jsObject)) : std::nullopt;
}
}
return std::nullopt;
}
} // namespace expo

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