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

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

View File

@@ -0,0 +1,300 @@
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
#pragma once
#include "../JSIContext.h"
#include "../JSharedObject.h"
#include "../JNIUtils.h"
#include "ObjectDeallocator.h"
#include "../javaclasses/Collections.h"
#include "../JavaScriptArrayBuffer.h"
#include "../NativeArrayBuffer.h"
#include "../concepts/jni_deref.h"
#include "../concepts/jni.h"
#include "../concepts/jsi.h"
#include "ReturnType.h"
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
#include <optional>
#include <concepts>
#include <react/jni/ReadableNativeMap.h>
#include <react/jni/ReadableNativeArray.h>
#include <react/jni/WritableNativeArray.h>
#include <react/jni/WritableNativeMap.h>
#include <jsi/JSIDynamic.h>
namespace jni = facebook::jni;
namespace jsi = facebook::jsi;
namespace react = facebook::react;
namespace expo {
jsi::Value convert(
JNIEnv *env,
jsi::Runtime &rt,
const jni::local_ref<jobject> &value
);
jsi::Value convert(
JNIEnv *env,
jsi::Runtime &rt,
ReturnType returnType,
const jni::local_ref<jobject> &value
);
std::optional<jsi::Value> convertStringToFollyDynamicIfNeeded(
jsi::Runtime &rt,
const std::string &string
);
std::optional<jsi::Value> decorateValueForDynamicExtension(
jsi::Runtime &rt,
const jsi::Value &value
);
template<typename T>
struct RawArray {
using element_type = T;
std::shared_ptr<T[]> data;
size_t size;
};
template<typename T>
inline auto unwrapJNIRef(T &&value) {
if constexpr (HasCthis<T>) {
return value->cthis();
} else if constexpr (HasToStdString<T>) {
return value->toStdString();
} else if constexpr (IsJBoolean<T>) {
return static_cast<bool>(value->value());
} else if constexpr (HasValue<T>) {
return value->value();
} else if constexpr (HasGetRegion<T>) {
size_t size = value->size();
auto region = value->getRegion(0, size);
return RawArray<typename decltype(region)::element_type>{
.data = std::move(region),
.size = size
};
} else {
return value;
}
}
template<typename T, typename Enable = void>
struct JNIToJSIConverter;
template<TriviallyConvertibleToJSI T>
struct JNIToJSIConverter<T> {
static jsi::Value convert(JNIEnv *, jsi::Runtime &, T value) {
return jsi::Value{value};
}
};
template<ConvertibleToJSI T>
struct JNIToJSIConverter<T> {
static jsi::Value convert(JNIEnv *, jsi::Runtime &rt, T value) {
return jsi::Value{rt, value};
}
};
template<>
struct JNIToJSIConverter<std::nullptr_t> {
static jsi::Value convert(JNIEnv *, jsi::Runtime &, std::nullptr_t) {
return jsi::Value::null();
}
};
template<>
struct JNIToJSIConverter<long> {
static jsi::Value convert(JNIEnv *, jsi::Runtime &, long value) {
return jsi::Value{static_cast<double>(value)};
}
};
template<>
struct JNIToJSIConverter<long long> {
static jsi::Value convert(JNIEnv *, jsi::Runtime &, long long value) {
return jsi::Value{static_cast<double>(value)};
}
};
template<>
struct JNIToJSIConverter<JavaScriptModuleObject *> {
static jsi::Value convert(
JNIEnv *,
jsi::Runtime &rt,
JavaScriptModuleObject *value,
const jni::local_ref<JavaScriptModuleObject::javaobject> &ref
) {
auto jsiObject = value->getJSIObject(rt);
auto globalRef = jni::make_global(ref);
common::setDeallocator(
rt,
jsiObject,
[globalRef = std::move(globalRef)]() mutable {
globalRef.reset();
}
);
return jsi::Value{rt, *jsiObject};
}
};
template<typename T>
concept SharedObjectRef = JniRefTo<T, JSharedObject::javaobject>;
template<SharedObjectRef T>
struct JNIToJSIConverter<T> {
static jsi::Value convert(JNIEnv *, jsi::Runtime &rt, const T &value) {
JSIContext *jsiContext = getJSIContext(rt);
if constexpr (std::is_same_v<T, jni::local_ref<JSharedObject::javaobject>>) {
return convertSharedObject(value, rt, jsiContext);
} else {
return convertSharedObject(jni::make_local(value), rt, jsiContext);
}
}
};
template<>
struct JNIToJSIConverter<JavaScriptTypedArray *> {
static jsi::Value convert(JNIEnv *, jsi::Runtime &rt, JavaScriptTypedArray *value) {
return jsi::Value{rt, *value->get()};
}
};
template<>
struct JNIToJSIConverter<JavaScriptArrayBuffer *> {
static jsi::Value convert(JNIEnv *, jsi::Runtime &rt, JavaScriptArrayBuffer *value) {
return jsi::Value{rt, *value->jsiArrayBuffer()};
}
};
template<>
struct JNIToJSIConverter<NativeArrayBuffer *> {
static jsi::Value convert(JNIEnv *, jsi::Runtime &rt, NativeArrayBuffer *value) {
jsi::ArrayBuffer arrayBuffer(rt, value->jsiMutableBuffer());
return jsi::Value{rt, arrayBuffer};
}
};
template<>
struct JNIToJSIConverter<react::WritableNativeArray *> {
static jsi::Value convert(JNIEnv *, jsi::Runtime &rt, react::WritableNativeArray *value) {
auto dynamic = value->consume();
auto result = jsi::valueFromDynamic(rt, dynamic);
if (auto enhanced = decorateValueForDynamicExtension(rt, result)) {
return std::move(*enhanced);
}
return result;
}
};
template<>
struct JNIToJSIConverter<react::WritableNativeMap *> {
static jsi::Value convert(JNIEnv *, jsi::Runtime &rt, react::WritableNativeMap *value) {
auto dynamic = value->consume();
auto result = jsi::valueFromDynamic(rt, dynamic);
if (auto enhanced = decorateValueForDynamicExtension(rt, result)) {
return std::move(*enhanced);
}
return result;
}
};
template<>
struct JNIToJSIConverter<std::string> {
static jsi::Value convert(JNIEnv *, jsi::Runtime &rt, const std::string &value) {
if (auto enhanced = convertStringToFollyDynamicIfNeeded(rt, value)) {
return std::move(*enhanced);
}
return jsi::String::createFromUtf8(rt, value);
}
};
template<>
struct JNIToJSIConverter<folly::dynamic> {
static jsi::Value convert(JNIEnv *, jsi::Runtime &rt, const folly::dynamic &value) {
auto result = jsi::valueFromDynamic(rt, value);
if (auto enhanced = decorateValueForDynamicExtension(rt, result)) {
return std::move(*enhanced);
}
return result;
}
};
template<typename T>
struct JNIToJSIConverter<RawArray<T>> {
static jsi::Value convert(JNIEnv *env, jsi::Runtime &rt, const RawArray<T> &value) {
auto jsArray = jsi::Array(rt, value.size);
for (size_t i = 0; i < value.size; i++) {
jsArray.setValueAtIndex(rt, i, JNIToJSIConverter<T>::convert(env, rt, value.data[i]));
}
return jsArray;
}
};
template<JCollectionRef<jobject> T>
struct JNIToJSIConverter<T> {
static jsi::Value convert(JNIEnv *env, jsi::Runtime &rt, const T &list) {
size_t size = list->size();
auto jsArray = jsi::Array(rt, size);
size_t index = 0;
for (const auto &item: *list) {
jsArray.setValueAtIndex(rt, index++, ::expo::convert(env, rt, item));
}
return jsArray;
}
};
template<JMapRef<jstring, jobject> T>
struct JNIToJSIConverter<T> {
static jsi::Value convert(JNIEnv *env, jsi::Runtime &rt, const T &map) {
jsi::Object jsObject(rt);
for (const auto &entry: *map) {
jsObject.setProperty(
rt,
entry.first->toStdString().c_str(),
::expo::convert(env, rt, entry.second)
);
}
return jsObject;
}
};
template<typename RefType>
std::vector<jsi::Value> convertArray(JNIEnv *env, jsi::Runtime &rt, RefType &values) {
size_t size = values->size();
std::vector<jsi::Value> result;
result.reserve(size);
for (size_t i = 0; i < size; i++) {
result.push_back(convert(env, rt, values->getElement(i)));
}
return result;
}
template<typename Converter, typename T>
concept SimpleConversion = requires(JNIEnv *env, jsi::Runtime &rt, T value) {
{ Converter::convert(env, rt, value) } -> std::same_as<jsi::Value>;
};
template<typename T>
jsi::Value convertToJS(JNIEnv *env, jsi::Runtime &rt, T &&value) {
using UnwrappedType = decltype(unwrapJNIRef(std::declval<T>()));
using Converter = JNIToJSIConverter<UnwrappedType>;
if constexpr (SimpleConversion<Converter, UnwrappedType>) {
return Converter::convert(env, rt, unwrapJNIRef(std::forward<T>(value)));
} else {
return Converter::convert(env, rt, unwrapJNIRef(value), value);
}
}
} // namespace expo

View File

@@ -0,0 +1,33 @@
// Copyright © 2026-present 650 Industries, Inc. (aka Expo)
#pragma once
namespace expo {
/**
* A cpp version of the `expo.modules.kotlin.jni.ReturnType` enum.
*/
enum class ReturnType {
UNKNOWN = 0,
DOUBLE = 1 << 0,
INT = 1 << 1,
LONG = 1 << 2,
STRING = 1 << 3,
BOOLEAN = 1 << 4,
FLOAT = 1 << 5,
WRITEABLE_ARRAY = 1 << 6,
WRITEABLE_MAP = 1 << 7,
JS_MODULE = 1 << 8,
SHARED_OBJECT = 1 << 9,
JS_TYPED_ARRAY = 1 << 10,
JS_ARRAY_BUFFER = 1 << 11,
NATIVE_ARRAY_BUFFER = 1 << 12,
MAP = 1 << 13,
COLLECTION = 1 << 14,
DOUBLE_ARRAY = 1 << 15,
INT_ARRAY = 1 << 16,
LONG_ARRAY = 1 << 17,
FLOAT_ARRAY = 1 << 18,
BOOLEAN_ARRAY = 1 << 19,
};
} // namespace expo

View File

@@ -0,0 +1,95 @@
#include "Serializable.h"
#if WORKLETS_ENABLED
namespace expo {
constexpr jint toStableId(worklets::Serializable::ValueType type) {
switch (type) {
case worklets::Serializable::ValueType::UndefinedType:
return 1;
case worklets::Serializable::ValueType::NullType:
return 2;
case worklets::Serializable::ValueType::BooleanType:
return 3;
case worklets::Serializable::ValueType::NumberType:
return 4;
case worklets::Serializable::ValueType::BigIntType:
return 5;
case worklets::Serializable::ValueType::StringType:
return 6;
case worklets::Serializable::ValueType::ObjectType:
return 7;
case worklets::Serializable::ValueType::ArrayType:
return 8;
case worklets::Serializable::ValueType::MapType:
return 9;
case worklets::Serializable::ValueType::SetType:
return 10;
case worklets::Serializable::ValueType::WorkletType:
return 11;
case worklets::Serializable::ValueType::RemoteFunctionType:
return 12;
case worklets::Serializable::ValueType::HandleType:
return 13;
case worklets::Serializable::ValueType::HostObjectType:
return 14;
case worklets::Serializable::ValueType::HostFunctionType:
return 15;
case worklets::Serializable::ValueType::ArrayBufferType:
return 16;
case worklets::Serializable::ValueType::TurboModuleLikeType:
return 17;
case worklets::Serializable::ValueType::ImportType:
return 18;
case worklets::Serializable::ValueType::SynchronizableType:
return 19;
case worklets::Serializable::ValueType::CustomType:
return 20;
default:
return 0; // Handle unknown cases
}
}
jni::local_ref<Serializable::javaobject> Serializable::newJavaInstance(
jni::local_ref<jni::detail::HybridData> hybridData,
worklets::Serializable::ValueType valueType
) {
jint jValueType = toStableId(valueType);
return Serializable::newObjectJavaArgs(
std::move(hybridData),
jValueType
);
}
Serializable::Serializable(
const std::shared_ptr<worklets::Serializable> &serializable
) : serializable_(serializable) {}
jni::local_ref<Serializable::javaobject> Serializable::newInstance(
JSIContext *jsiContext,
const std::shared_ptr<worklets::Serializable> &serializable
) {
auto cxxPart = std::make_unique<Serializable>(
serializable
);
auto hybridData = jni::detail::HybridData::create();
auto javaPart = Serializable::newJavaInstance(
hybridData,
serializable->valueType()
);
jni::detail::setNativePointer(hybridData, std::move(cxxPart));
return javaPart;
}
std::shared_ptr<worklets::Serializable> Serializable::getSerializable() {
return serializable_;
}
} // namespace expo
#endif

View File

@@ -0,0 +1,46 @@
#pragma once
#if WORKLETS_ENABLED
#include "../JSIContext.h"
#include "../JNIDeallocator.h"
#include "WorkletNativeRuntime.h"
#include <fbjni/fbjni.h>
#include <worklets/SharedItems/Serializable.h>
namespace jni = facebook::jni;
namespace expo {
class Serializable : public jni::HybridClass<Serializable, Destructible> {
public:
static auto constexpr
kJavaDescriptor = "Lexpo/modules/kotlin/jni/worklets/Serializable;";
static auto constexpr TAG = "Serializable";
static jni::local_ref<Serializable::javaobject> newInstance(
JSIContext *jsiContext,
const std::shared_ptr<worklets::Serializable> &serializable
);
explicit Serializable(
const std::shared_ptr<worklets::Serializable> &serializable
);
std::shared_ptr<worklets::Serializable> getSerializable();
private:
friend HybridBase;
static jni::local_ref<Serializable::javaobject> newJavaInstance(
jni::local_ref<jni::detail::HybridData> hybridData,
worklets::Serializable::ValueType valueType
);
std::shared_ptr<worklets::Serializable> serializable_;
};
} // namespace expo
#endif

View File

@@ -0,0 +1,94 @@
#include "Worklet.h"
#if WORKLETS_ENABLED
#include "../types/JNIToJSIConverter.h"
namespace expo {
void Worklet::registerNatives() {
javaClassLocal()->registerNatives({
makeNativeMethod("schedule", Worklet::schedule),
makeNativeMethod("schedule", Worklet::scheduleWithArgs),
makeNativeMethod("execute", Worklet::execute),
makeNativeMethod("execute", Worklet::executeWithArgs),
});
}
void Worklet::schedule(
jni::alias_ref<Worklet::javaobject> self,
jni::alias_ref<WorkletNativeRuntime::javaobject> workletRuntimeHolder,
jni::alias_ref<Serializable::javaobject> synchronizable
) {
auto workletRuntime = workletRuntimeHolder->cthis()->workletRuntime.lock();
auto worklet = std::dynamic_pointer_cast<worklets::SerializableWorklet>(
synchronizable->cthis()->getSerializable()
);
workletRuntime->schedule(std::move(worklet));
}
void Worklet::execute(
jni::alias_ref<Worklet::javaobject> self,
jni::alias_ref<WorkletNativeRuntime::javaobject> workletRuntimeHolder,
jni::alias_ref<Serializable::javaobject> synchronizable
) {
auto workletRuntime = workletRuntimeHolder->cthis()->workletRuntime.lock();
auto worklet = std::dynamic_pointer_cast<worklets::SerializableWorklet>(
synchronizable->cthis()->getSerializable()
);
workletRuntime->runSync(worklet);
}
void Worklet::scheduleWithArgs(
jni::alias_ref<Worklet::javaobject> self,
jni::alias_ref<WorkletNativeRuntime::javaobject> workletRuntimeHolder,
jni::alias_ref<Serializable::javaobject> synchronizable,
jni::alias_ref<jni::JArrayClass<jobject>> args
) {
auto workletRuntime = workletRuntimeHolder->cthis()->workletRuntime.lock();
auto worklet = std::dynamic_pointer_cast<worklets::SerializableWorklet>(
synchronizable->cthis()->getSerializable()
);
workletRuntime->schedule([&worklet, globalArgs = jni::make_global(args)](jsi::Runtime &rt) mutable {
JNIEnv *env = jni::Environment::current();
std::vector<jsi::Value> convertedArgs = convertArray(env, rt, globalArgs);
auto func = worklet->toJSValue(rt).asObject(rt).asFunction(rt);
func.call(
rt,
(const jsi::Value *) convertedArgs.data(),
convertedArgs.size()
);
});
}
void Worklet::executeWithArgs(
jni::alias_ref<Worklet::javaobject> self,
jni::alias_ref<WorkletNativeRuntime::javaobject> workletRuntimeHolder,
jni::alias_ref<Serializable::javaobject> synchronizable,
jni::alias_ref<jni::JArrayClass<jobject>> args
) {
auto workletRuntime = workletRuntimeHolder->cthis()->workletRuntime.lock();
auto worklet = std::dynamic_pointer_cast<worklets::SerializableWorklet>(
synchronizable->cthis()->getSerializable()
);
workletRuntime->runSync([&args, &worklet](jsi::Runtime &rt) {
JNIEnv *env = jni::Environment::current();
std::vector<jsi::Value> convertedArgs = convertArray(env, rt, args);
auto func = worklet->toJSValue(rt).asObject(rt).asFunction(rt);
func.call(
rt,
(const jsi::Value *) convertedArgs.data(),
convertedArgs.size()
);
});
}
} // namespace expo
#endif

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