├── .gitignore ├── .gitmodules ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── example ├── .bundle │ └── config ├── .eslintrc.js ├── .gitignore ├── .prettierrc.js ├── .watchmanconfig ├── App.tsx ├── Gemfile ├── README.md ├── android │ ├── app │ │ ├── build.gradle │ │ ├── debug.keystore │ │ ├── proguard-rules.pro │ │ └── src │ │ │ ├── debug │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── ReactNativeFlipper.java │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ ├── ExampleJsiModule.java │ │ │ │ │ ├── ExampleJsiPackage.java │ │ │ │ │ ├── MainActivity.java │ │ │ │ │ └── MainApplication.java │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ └── rn_edit_text_material.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ └── values │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ │ └── release │ │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── ReactNativeFlipper.java │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── app.json ├── babel.config.js ├── example-jsi-module │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ ├── android.rs │ │ ├── ios.rs │ │ └── lib.rs ├── index.js ├── ios │ ├── .xcode.env │ ├── Podfile │ ├── example.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── example.xcscheme │ ├── example │ │ ├── AppDelegate.h │ │ ├── AppDelegate.mm │ │ ├── ExampleJsiModule.h │ │ ├── Images.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Info.plist │ │ ├── LaunchScreen.storyboard │ │ ├── RCTExampleJsiModule.h │ │ ├── RTCExampleJsiModule.mm │ │ └── main.m │ └── exampleTests │ │ ├── Info.plist │ │ └── exampleTests.m ├── jest.config.js ├── metro.config.js ├── package.json ├── tsconfig.json └── yarn.lock ├── jsi-macros ├── Cargo.lock ├── Cargo.toml └── src │ ├── host_event.rs │ ├── host_object.rs │ └── lib.rs ├── jsi-sys ├── Cargo.lock ├── Cargo.toml ├── build.rs ├── include │ ├── host.h │ └── wrapper.h ├── src │ ├── ffi │ │ ├── android.rs │ │ ├── base.rs │ │ ├── host.rs │ │ └── mod.rs │ ├── lib.rs │ └── shim.rs └── vendor ├── jsi-tests ├── Cargo.lock ├── Cargo.toml ├── README.md ├── build.rs ├── include │ └── helper.h ├── src │ ├── ffi.rs │ └── lib.rs └── tests │ ├── basic.rs │ ├── common.rs │ └── function.rs ├── jsi ├── Cargo.lock ├── Cargo.toml ├── README.md └── src │ ├── array.rs │ ├── array_buffer.rs │ ├── call_invoker.rs │ ├── convert │ ├── de.rs │ ├── mod.rs │ └── ser.rs │ ├── function.rs │ ├── host_function.rs │ ├── host_object.rs │ ├── lib.rs │ ├── object.rs │ ├── prop_name.rs │ ├── runtime.rs │ ├── string.rs │ ├── symbol.rs │ └── value.rs └── vendor └── build-hermes.sh /.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | **/lib 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "react-native"] 2 | path = vendor/react-native 3 | url = https://github.com/facebook/react-native.git 4 | ignore = dirty 5 | [submodule "fbjni"] 6 | path = vendor/fbjni 7 | url = https://github.com/facebookincubator/fbjni.git 8 | ignore = dirty 9 | [submodule "hermes"] 10 | path = vendor/hermes 11 | url = https://github.com/facebook/hermes.git 12 | ignore = dirty 13 | 14 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "jsi-sys", 4 | "jsi-macros", 5 | "jsi-tests", 6 | "jsi", 7 | "example/example-jsi-module" 8 | ] 9 | resolver = "2" 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 Ibiyemi Abiodun 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `jsi-rs` 2 | 3 | This library makes it possible to write React Native JSI modules in Rust. 4 | 5 | For an example, check out the `example` folder. 6 | 7 | ## Getting Started 8 | 9 | 1. Clone this repo 10 | 2. Run `git submodule init` 11 | 3. Run `git submodule update`, this will ensure that all the vendor dependencies are cloned locally 12 | 4. Make sure you have Ninja installed locally, which is necessary for building Hermes. You can find instructions [here](https://github.com/ninja-build/ninja/wiki/Pre-built-Ninja-packages). On macOS, you can install it with `brew install ninja` 13 | 5. Install dependencies for the example app: `cd example && yarn install` 14 | 6. Run the example app on android with `yarn android` 15 | 16 | > NOTE: Make sure that you have not installed rust with homebrew on mac, use the `rustup` toolchain instead. 17 | 18 | ## Contributing 19 | 20 | I wrote this code in winter 2022 as part of another project. A few months later, 21 | I have decided to release it to the world. However, I'm not planning to maintain 22 | it unless I encounter another project that requires it, so for now, the code is 23 | given to you as-is. Feel free to contribute PRs that would improve the API or 24 | stability of the library. 25 | 26 | ## Safety 27 | 28 | Right now, this library is quite `unsafe`. 29 | 30 | ## Copyright / license 31 | 32 | Copyright Ibiyemi Abiodun. MIT License. 33 | -------------------------------------------------------------------------------- /example/.bundle/config: -------------------------------------------------------------------------------- 1 | BUNDLE_PATH: "vendor/bundle" 2 | BUNDLE_FORCE_RUBY_PLATFORM: 1 3 | -------------------------------------------------------------------------------- /example/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: '@react-native', 4 | }; 5 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | ios/.xcode.env.local 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | *.hprof 33 | .cxx/ 34 | *.keystore 35 | !debug.keystore 36 | 37 | # node.js 38 | # 39 | node_modules/ 40 | npm-debug.log 41 | yarn-error.log 42 | 43 | # fastlane 44 | # 45 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 46 | # screenshots whenever they are needed. 47 | # For more information about the recommended setup visit: 48 | # https://docs.fastlane.tools/best-practices/source-control/ 49 | 50 | **/fastlane/report.xml 51 | **/fastlane/Preview.html 52 | **/fastlane/screenshots 53 | **/fastlane/test_output 54 | 55 | # Bundle artifact 56 | *.jsbundle 57 | 58 | # Ruby / CocoaPods 59 | /ios/Pods/ 60 | /vendor/bundle/ 61 | 62 | # Temporary files created by Metro to check the health of the file watcher 63 | .metro-health-check* 64 | 65 | # testing 66 | /coverage 67 | -------------------------------------------------------------------------------- /example/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'avoid', 3 | bracketSameLine: true, 4 | bracketSpacing: false, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | }; 8 | -------------------------------------------------------------------------------- /example/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /example/App.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Sample React Native App 3 | * https://github.com/facebook/react-native 4 | * 5 | * @format 6 | */ 7 | 8 | import React from 'react'; 9 | import type {PropsWithChildren} from 'react'; 10 | import { 11 | SafeAreaView, 12 | ScrollView, 13 | StatusBar, 14 | StyleSheet, 15 | Text, 16 | useColorScheme, 17 | View, 18 | NativeModules, 19 | } from 'react-native'; 20 | 21 | import { 22 | Colors, 23 | DebugInstructions, 24 | Header, 25 | LearnMoreLinks, 26 | ReloadInstructions, 27 | } from 'react-native/Libraries/NewAppScreen'; 28 | 29 | // call our Rust module 30 | const {ExampleJsiModule} = NativeModules; 31 | ExampleJsiModule.install(); 32 | 33 | // call our global object injected by Rust 34 | console.log('the current time is: ' + ExampleGlobal.time()); 35 | 36 | type SectionProps = PropsWithChildren<{ 37 | title: string; 38 | }>; 39 | 40 | function Section({children, title}: SectionProps): JSX.Element { 41 | const isDarkMode = useColorScheme() === 'dark'; 42 | return ( 43 | 44 | 51 | {title} 52 | 53 | 60 | {children} 61 | 62 | 63 | ); 64 | } 65 | 66 | function App(): JSX.Element { 67 | const isDarkMode = useColorScheme() === 'dark'; 68 | 69 | const backgroundStyle = { 70 | backgroundColor: isDarkMode ? Colors.darker : Colors.lighter, 71 | }; 72 | 73 | return ( 74 | 75 | 79 | 82 |
83 | 87 |
88 | Edit App.tsx to change this 89 | screen and then come back to see your edits. 90 |
91 |
92 | 93 |
94 |
95 | 96 |
97 |
98 | Read the docs to discover what to do next: 99 |
100 | 101 |
102 | 103 | 104 | ); 105 | } 106 | 107 | const styles = StyleSheet.create({ 108 | sectionContainer: { 109 | marginTop: 32, 110 | paddingHorizontal: 24, 111 | }, 112 | sectionTitle: { 113 | fontSize: 24, 114 | fontWeight: '600', 115 | }, 116 | sectionDescription: { 117 | marginTop: 8, 118 | fontSize: 18, 119 | fontWeight: '400', 120 | }, 121 | highlight: { 122 | fontWeight: '700', 123 | }, 124 | }); 125 | 126 | export default App; 127 | -------------------------------------------------------------------------------- /example/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version 4 | ruby ">= 2.6.10" 5 | 6 | gem 'cocoapods', '~> 1.12' 7 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # An example React Native project using a `jsi-rs` module 2 | 3 | ## How to reproduce (Android) 4 | 5 | 1. Create a blank React Native project: `npx react-native@latest init example` 6 | 2. Create a blank Cargo project inside: `cd example && cargo init --lib example-jsi-module` 7 | 3. Add `jsi`, `jni`, and `cxx` as dependencies of `example-jsi-module` 8 | - `jsi` will allow us to create a React Native module 9 | - `jni` is needed for interop with Java code on Android 10 | - `cxx` is needed to initialize `jsi` because JSI is implemented in C++ with smart pointers 11 | 4. Write the code in `example-jsi-module/src` 12 | 5. Update `android/build.gradle` to add the following plugin under `buildscript.dependencies`: 13 | ```groovy 14 | buildscript { 15 | repositories { 16 | // don't remove the existing repositories, just add maven b/c the Rust plugin is hosted there 17 | maven { 18 | url "https://plugins.gradle.org/m2/" 19 | } 20 | } 21 | dependencies { 22 | // don't remove the existing dependencies, just add this one 23 | classpath("io.github.MatrixDev.android-rust:plugin:0.3.2") 24 | } 25 | } 26 | ``` 27 | 28 | This plugin will compile `example-jsi-module` using the Android NDK as part 29 | of building our application for Android 30 | 6. Make sure the [Android NDK](https://developer.android.com/ndk) is installed. 31 | Version 23 should work 32 | 7. Update `android/app/build.gradle` to add the following lines: 33 | ```groovy 34 | apply plugin: "io.github.MatrixDev.android-rust" 35 | androidRust { 36 | module("example-jsi-module") { 37 | it.path = file("../example-jsi-module") 38 | 39 | // default abi targets are arm and arm64; if you want to run on Android 40 | // Emulator then you may need to add x86_64 41 | 42 | // targets = ["arm", "arm64", "x86", "x86_64"] 43 | } 44 | } 45 | ``` 46 | 47 | **Note:** when you compile the program, this Gradle plugin will install all of 48 | the Rust Android targets if they are not already installed 49 | 8. Write the code in 50 | `android/app/src/main/java/com/example/ExampleJsiModule.java`. This will be 51 | called when the application starts, and it gives us a pointer to the React 52 | Native runtime so we can initialize our Rust code 53 | 9. Write the code in 54 | `android/app/src/main/java/com/example/ExampleJsiPackage.java`. This lets 55 | React Native discover our module 56 | 10. In `android/app/src/main/java/com/example/MainApplication.java`, add the following line to `getPackages()`: 57 | ```java 58 | packages.add(new ExampleJsiPackage()); 59 | ``` 60 | 11. Add a couple of lines to `App.tsx` to call our native module (I added them near the top): 61 | ```typescript 62 | // call our Rust module 63 | const {ExampleJsiModule} = NativeModules; 64 | ExampleJsiModule.install(); 65 | ``` 66 | 12. Connect your Android device or start an emulator 67 | 13. Run `npm run start` and press A to deploy to Android 68 | 14. You should see `hello from Rust` in the terminal after the app loads 69 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | apply plugin: "com.facebook.react" 3 | 4 | /** 5 | * This is the configuration block to customize your React Native Android app. 6 | * By default you don't need to apply any configuration, just uncomment the lines you need. 7 | */ 8 | react { 9 | /* Folders */ 10 | // The root of your project, i.e. where "package.json" lives. Default is '..' 11 | // root = file("../") 12 | // The folder where the react-native NPM package is. Default is ../node_modules/react-native 13 | // reactNativeDir = file("../node_modules/react-native") 14 | // The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen 15 | // codegenDir = file("../node_modules/@react-native/codegen") 16 | // The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js 17 | // cliFile = file("../node_modules/react-native/cli.js") 18 | 19 | /* Variants */ 20 | // The list of variants to that are debuggable. For those we're going to 21 | // skip the bundling of the JS bundle and the assets. By default is just 'debug'. 22 | // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. 23 | // debuggableVariants = ["liteDebug", "prodDebug"] 24 | 25 | /* Bundling */ 26 | // A list containing the node command and its flags. Default is just 'node'. 27 | // nodeExecutableAndArgs = ["node"] 28 | // 29 | // The command to run when bundling. By default is 'bundle' 30 | // bundleCommand = "ram-bundle" 31 | // 32 | // The path to the CLI configuration file. Default is empty. 33 | // bundleConfig = file(../rn-cli.config.js) 34 | // 35 | // The name of the generated asset file containing your JS bundle 36 | // bundleAssetName = "MyApplication.android.bundle" 37 | // 38 | // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' 39 | // entryFile = file("../js/MyApplication.android.js") 40 | // 41 | // A list of extra flags to pass to the 'bundle' commands. 42 | // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle 43 | // extraPackagerArgs = [] 44 | 45 | /* Hermes Commands */ 46 | // The hermes compiler command to run. By default it is 'hermesc' 47 | // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" 48 | // 49 | // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" 50 | // hermesFlags = ["-O", "-output-source-map"] 51 | } 52 | 53 | /** 54 | * Set this to true to Run Proguard on Release builds to minify the Java bytecode. 55 | */ 56 | def enableProguardInReleaseBuilds = false 57 | 58 | /** 59 | * The preferred build flavor of JavaScriptCore (JSC) 60 | * 61 | * For example, to use the international variant, you can use: 62 | * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` 63 | * 64 | * The international variant includes ICU i18n library and necessary data 65 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that 66 | * give correct results when using with locales other than en-US. Note that 67 | * this variant is about 6MiB larger per architecture than default. 68 | */ 69 | def jscFlavor = 'org.webkit:android-jsc:+' 70 | 71 | android { 72 | ndkVersion rootProject.ext.ndkVersion 73 | 74 | compileSdkVersion rootProject.ext.compileSdkVersion 75 | 76 | namespace "com.example" 77 | defaultConfig { 78 | applicationId "com.example" 79 | minSdkVersion rootProject.ext.minSdkVersion 80 | targetSdkVersion rootProject.ext.targetSdkVersion 81 | versionCode 1 82 | versionName "1.0" 83 | } 84 | signingConfigs { 85 | debug { 86 | storeFile file('debug.keystore') 87 | storePassword 'android' 88 | keyAlias 'androiddebugkey' 89 | keyPassword 'android' 90 | } 91 | } 92 | buildTypes { 93 | debug { 94 | signingConfig signingConfigs.debug 95 | 96 | // if you need to debug your native module in Android Studio, this 97 | // will prevent Gradle from stripping out the native symbols that 98 | // are needed 99 | packagingOptions { 100 | doNotStrip '**/libexample_jsi_module.so' 101 | } 102 | } 103 | release { 104 | // Caution! In production, you need to generate your own keystore file. 105 | // see https://reactnative.dev/docs/signed-apk-android. 106 | signingConfig signingConfigs.debug 107 | minifyEnabled enableProguardInReleaseBuilds 108 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 109 | } 110 | } 111 | } 112 | 113 | dependencies { 114 | // The version of react-native is set by the React Native Gradle Plugin 115 | implementation("com.facebook.react:react-android") 116 | 117 | debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") 118 | debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { 119 | exclude group:'com.squareup.okhttp3', module:'okhttp' 120 | } 121 | 122 | debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") 123 | if (hermesEnabled.toBoolean()) { 124 | implementation("com.facebook.react:hermes-android") 125 | } else { 126 | implementation jscFlavor 127 | } 128 | } 129 | 130 | apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) 131 | 132 | // apply plugin: "com.github.willir.rust.cargo-ndk-android" 133 | // cargoNdk { 134 | // module = "../example-jsi-module" 135 | // librariesNames = ["libexample_jsi_module.so"] 136 | // targets = ["arm64", "x86_64"] 137 | // } 138 | 139 | apply plugin: "io.github.MatrixDev.android-rust" 140 | 141 | androidRust { 142 | module("example-jsi-module") { 143 | it.path = file("../../example-jsi-module") 144 | 145 | // default abi targets are arm and arm64; if you want to run on Android 146 | // Emulator then you probably need to add x86_64 147 | 148 | targets = ["arm", "arm64", "x86", "x86_64"] 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /example/android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laptou/jsi-rs/72afac135eb930b1184c8fe2b4b2f92ee1376b1c/example/android/app/debug.keystore -------------------------------------------------------------------------------- /example/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /example/android/app/src/debug/java/com/example/ReactNativeFlipper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | *

This source code is licensed under the MIT license found in the LICENSE file in the root 5 | * directory of this source tree. 6 | */ 7 | package com.example; 8 | 9 | import android.content.Context; 10 | import com.facebook.flipper.android.AndroidFlipperClient; 11 | import com.facebook.flipper.android.utils.FlipperUtils; 12 | import com.facebook.flipper.core.FlipperClient; 13 | import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin; 14 | import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin; 15 | import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin; 16 | import com.facebook.flipper.plugins.inspector.DescriptorMapping; 17 | import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin; 18 | import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor; 19 | import com.facebook.flipper.plugins.network.NetworkFlipperPlugin; 20 | import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; 21 | import com.facebook.react.ReactInstanceEventListener; 22 | import com.facebook.react.ReactInstanceManager; 23 | import com.facebook.react.bridge.ReactContext; 24 | import com.facebook.react.modules.network.NetworkingModule; 25 | import okhttp3.OkHttpClient; 26 | 27 | /** 28 | * Class responsible of loading Flipper inside your React Native application. This is the debug 29 | * flavor of it. Here you can add your own plugins and customize the Flipper setup. 30 | */ 31 | public class ReactNativeFlipper { 32 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { 33 | if (FlipperUtils.shouldEnableFlipper(context)) { 34 | final FlipperClient client = AndroidFlipperClient.getInstance(context); 35 | 36 | client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults())); 37 | client.addPlugin(new DatabasesFlipperPlugin(context)); 38 | client.addPlugin(new SharedPreferencesFlipperPlugin(context)); 39 | client.addPlugin(CrashReporterPlugin.getInstance()); 40 | 41 | NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin(); 42 | NetworkingModule.setCustomClientBuilder( 43 | new NetworkingModule.CustomClientBuilder() { 44 | @Override 45 | public void apply(OkHttpClient.Builder builder) { 46 | builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin)); 47 | } 48 | }); 49 | client.addPlugin(networkFlipperPlugin); 50 | client.start(); 51 | 52 | // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized 53 | // Hence we run if after all native modules have been initialized 54 | ReactContext reactContext = reactInstanceManager.getCurrentReactContext(); 55 | if (reactContext == null) { 56 | reactInstanceManager.addReactInstanceEventListener( 57 | new ReactInstanceEventListener() { 58 | @Override 59 | public void onReactContextInitialized(ReactContext reactContext) { 60 | reactInstanceManager.removeReactInstanceEventListener(this); 61 | reactContext.runOnNativeModulesQueueThread( 62 | new Runnable() { 63 | @Override 64 | public void run() { 65 | client.addPlugin(new FrescoFlipperPlugin()); 66 | } 67 | }); 68 | } 69 | }); 70 | } else { 71 | client.addPlugin(new FrescoFlipperPlugin()); 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/example/ExampleJsiModule.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import android.util.Log; 4 | 5 | import com.facebook.react.bridge.JSIModulePackage; 6 | import com.facebook.react.bridge.JSIModuleProvider; 7 | import com.facebook.react.bridge.JSIModuleSpec; 8 | import com.facebook.react.bridge.JSIModuleType; 9 | import com.facebook.react.bridge.JavaScriptContextHolder; 10 | import com.facebook.react.bridge.ReactApplicationContext; 11 | import com.facebook.react.turbomodule.core.CallInvokerHolderImpl; 12 | 13 | import com.facebook.react.bridge.Arguments; 14 | import com.facebook.react.bridge.Callback; 15 | import com.facebook.react.bridge.JavaScriptContextHolder; 16 | import com.facebook.react.bridge.ReactApplicationContext; 17 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 18 | import com.facebook.react.bridge.ReactMethod; 19 | import com.facebook.react.bridge.ReadableMap; 20 | import com.facebook.react.bridge.WritableArray; 21 | import com.facebook.react.bridge.WritableMap; 22 | import com.facebook.react.module.annotations.ReactModule; 23 | 24 | import java.util.Arrays; 25 | import java.util.List; 26 | 27 | @ReactModule(name = "ExampleJsiModule") 28 | public class ExampleJsiModule extends ReactContextBaseJavaModule { 29 | public static native void init(long runtimePtr, CallInvokerHolderImpl callInvoker); 30 | 31 | public ExampleJsiModule(ReactApplicationContext reactContext) { 32 | super(reactContext); 33 | } 34 | 35 | @Override 36 | public String getName() { 37 | return "ExampleJsiModule"; 38 | } 39 | 40 | @ReactMethod(isBlockingSynchronousMethod = true) 41 | public boolean install() { 42 | // load our dynamic library 43 | System.loadLibrary("example_jsi_module"); 44 | 45 | var ctx = getReactApplicationContext(); 46 | var jsContext = ctx.getJavaScriptContextHolder(); 47 | var runtimePtr = jsContext.get(); 48 | var callInvoker = (CallInvokerHolderImpl)ctx.getCatalystInstance().getJSCallInvokerHolder(); 49 | 50 | Log.i("ExampleJsiModule", "initializing"); 51 | 52 | if (jsContext.get() != 0) { 53 | ExampleJsiModule.init(runtimePtr, callInvoker); 54 | Log.i("ExampleJsiModule", "initialized"); 55 | 56 | return true; 57 | } else { 58 | Log.e("ExampleJsiModule","initialization failed: no runtime"); 59 | return false; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/example/ExampleJsiPackage.java: -------------------------------------------------------------------------------- 1 | 2 | package com.example; 3 | 4 | import java.util.Arrays; 5 | import java.util.Collections; 6 | import java.util.List; 7 | 8 | import com.facebook.react.ReactPackage; 9 | import com.facebook.react.bridge.NativeModule; 10 | import com.facebook.react.bridge.ReactApplicationContext; 11 | import com.facebook.react.uimanager.ViewManager; 12 | import com.facebook.react.bridge.JavaScriptModule; 13 | 14 | public class ExampleJsiPackage implements ReactPackage { 15 | @Override 16 | public List createNativeModules(ReactApplicationContext reactContext) { 17 | return Arrays.asList(new ExampleJsiModule(reactContext)); 18 | } 19 | 20 | @Override 21 | public List createViewManagers(ReactApplicationContext reactContext) { 22 | return Collections.emptyList(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import com.facebook.react.ReactActivity; 4 | import com.facebook.react.ReactActivityDelegate; 5 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; 6 | import com.facebook.react.defaults.DefaultReactActivityDelegate; 7 | 8 | public class MainActivity extends ReactActivity { 9 | 10 | /** 11 | * Returns the name of the main component registered from JavaScript. This is used to schedule 12 | * rendering of the component. 13 | */ 14 | @Override 15 | protected String getMainComponentName() { 16 | return "example"; 17 | } 18 | 19 | /** 20 | * Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link 21 | * DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React 22 | * (aka React 18) with two boolean flags. 23 | */ 24 | @Override 25 | protected ReactActivityDelegate createReactActivityDelegate() { 26 | return new DefaultReactActivityDelegate( 27 | this, 28 | getMainComponentName(), 29 | // If you opted-in for the New Architecture, we enable the Fabric Renderer. 30 | DefaultNewArchitectureEntryPoint.getFabricEnabled()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/example/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import android.app.Application; 4 | import com.facebook.react.PackageList; 5 | import com.facebook.react.ReactApplication; 6 | import com.facebook.react.ReactNativeHost; 7 | import com.facebook.react.ReactPackage; 8 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; 9 | import com.facebook.react.defaults.DefaultReactNativeHost; 10 | import com.facebook.soloader.SoLoader; 11 | import java.util.List; 12 | 13 | public class MainApplication extends Application implements ReactApplication { 14 | 15 | private final ReactNativeHost mReactNativeHost = 16 | new DefaultReactNativeHost(this) { 17 | @Override 18 | public boolean getUseDeveloperSupport() { 19 | return BuildConfig.DEBUG; 20 | } 21 | 22 | @Override 23 | protected List getPackages() { 24 | @SuppressWarnings("UnnecessaryLocalVariable") 25 | List packages = new PackageList(this).getPackages(); 26 | // Packages that cannot be autolinked yet can be added manually here, for example: 27 | // packages.add(new MyReactNativePackage()); 28 | packages.add(new ExampleJsiPackage()); 29 | return packages; 30 | } 31 | 32 | @Override 33 | protected String getJSMainModuleName() { 34 | return "index"; 35 | } 36 | 37 | @Override 38 | protected boolean isNewArchEnabled() { 39 | return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; 40 | } 41 | 42 | @Override 43 | protected Boolean isHermesEnabled() { 44 | return BuildConfig.IS_HERMES_ENABLED; 45 | } 46 | }; 47 | 48 | @Override 49 | public ReactNativeHost getReactNativeHost() { 50 | return mReactNativeHost; 51 | } 52 | 53 | @Override 54 | public void onCreate() { 55 | super.onCreate(); 56 | SoLoader.init(this, /* native exopackage */ false); 57 | if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { 58 | // If you opted-in for the New Architecture, we load the native entry point for this app. 59 | DefaultNewArchitectureEntryPoint.load(); 60 | } 61 | ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/rn_edit_text_material.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 21 | 22 | 23 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laptou/jsi-rs/72afac135eb930b1184c8fe2b4b2f92ee1376b1c/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laptou/jsi-rs/72afac135eb930b1184c8fe2b4b2f92ee1376b1c/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laptou/jsi-rs/72afac135eb930b1184c8fe2b4b2f92ee1376b1c/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laptou/jsi-rs/72afac135eb930b1184c8fe2b4b2f92ee1376b1c/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laptou/jsi-rs/72afac135eb930b1184c8fe2b4b2f92ee1376b1c/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laptou/jsi-rs/72afac135eb930b1184c8fe2b4b2f92ee1376b1c/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laptou/jsi-rs/72afac135eb930b1184c8fe2b4b2f92ee1376b1c/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laptou/jsi-rs/72afac135eb930b1184c8fe2b4b2f92ee1376b1c/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laptou/jsi-rs/72afac135eb930b1184c8fe2b4b2f92ee1376b1c/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laptou/jsi-rs/72afac135eb930b1184c8fe2b4b2f92ee1376b1c/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | example 3 | 4 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /example/android/app/src/release/java/com/example/ReactNativeFlipper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | *

This source code is licensed under the MIT license found in the LICENSE file in the root 5 | * directory of this source tree. 6 | */ 7 | package com.example; 8 | 9 | import android.content.Context; 10 | import com.facebook.react.ReactInstanceManager; 11 | 12 | /** 13 | * Class responsible of loading Flipper inside your React Native application. This is the release 14 | * flavor of it so it's empty as we don't want to load Flipper. 15 | */ 16 | public class ReactNativeFlipper { 17 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { 18 | // Do nothing as we don't want to initialize Flipper on Release. 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | buildToolsVersion = "33.0.0" 6 | minSdkVersion = 21 7 | compileSdkVersion = 33 8 | targetSdkVersion = 33 9 | 10 | // We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP. 11 | ndkVersion = "23.1.7779620" 12 | } 13 | repositories { 14 | google() 15 | mavenCentral() 16 | maven { 17 | url "https://plugins.gradle.org/m2/" 18 | } 19 | } 20 | dependencies { 21 | classpath("com.android.tools.build:gradle") 22 | classpath("com.facebook.react:react-native-gradle-plugin") 23 | // classpath("gradle.plugin.com.github.willir.rust:plugin:0.3.4") 24 | classpath("io.github.MatrixDev.android-rust:plugin:0.3.2") 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m 13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true 24 | # Automatically convert third-party libraries to use AndroidX 25 | android.enableJetifier=true 26 | 27 | # Version of flipper SDK to use with React Native 28 | FLIPPER_VERSION=0.182.0 29 | 30 | # Use this property to specify which architecture you want to build. 31 | # You can also override it from the CLI using 32 | # ./gradlew -PreactNativeArchitectures=x86_64 33 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 34 | 35 | # Use this property to enable support to the new architecture. 36 | # This will allow you to use TurboModules and the Fabric render in 37 | # your application. You should enable this flag either if you want 38 | # to write custom TurboModules/Fabric components OR use libraries that 39 | # are providing them. 40 | newArchEnabled=false 41 | 42 | # Use this property to enable or disable the Hermes JS engine. 43 | # If set to false, you will be using JSC instead. 44 | hermesEnabled=true 45 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laptou/jsi-rs/72afac135eb930b1184c8fe2b4b2f92ee1376b1c/example/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-all.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /example/android/gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | # Collect all arguments for the java command; 201 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 202 | # shell script including quotes and variable substitutions, so put them in 203 | # double quotes to make sure that they get re-expanded; and 204 | # * put everything else in single quotes, so that it's not re-expanded. 205 | 206 | set -- \ 207 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 208 | -classpath "$CLASSPATH" \ 209 | org.gradle.wrapper.GradleWrapperMain \ 210 | "$@" 211 | 212 | # Stop when "xargs" is not available. 213 | if ! command -v xargs >/dev/null 2>&1 214 | then 215 | die "xargs is not available" 216 | fi 217 | 218 | # Use "xargs" to parse quoted args. 219 | # 220 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 221 | # 222 | # In Bash we could simply go: 223 | # 224 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 225 | # set -- "${ARGS[@]}" "$@" 226 | # 227 | # but POSIX shell has neither arrays nor command substitution, so instead we 228 | # post-process each arg (as a line of input to sed) to backslash-escape any 229 | # character that might be a shell metacharacter, then use eval to reverse 230 | # that process (while maintaining the separation between arguments), and wrap 231 | # the whole thing up as a single "set" statement. 232 | # 233 | # This will of course break if any of these variables contains a newline or 234 | # an unmatched quote. 235 | # 236 | 237 | eval "set -- $( 238 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 239 | xargs -n1 | 240 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 241 | tr '\n' ' ' 242 | )" '"$@"' 243 | 244 | exec "$JAVACMD" "$@" 245 | -------------------------------------------------------------------------------- /example/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'example' 2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) 3 | include ':app' 4 | includeBuild('../node_modules/@react-native/gradle-plugin') 5 | -------------------------------------------------------------------------------- /example/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "displayName": "example" 4 | } 5 | -------------------------------------------------------------------------------- /example/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /example/example-jsi-module/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-jsi-module" 3 | version = "0.1.1-alpha.1" 4 | edition = "2021" 5 | publish = false 6 | 7 | [lib] 8 | # Android requires dynamic libraries, and iOS requires static ones 9 | crate-type = ["cdylib", "staticlib"] 10 | 11 | [dependencies] 12 | jsi = { path = "../../jsi" } 13 | anyhow = { version = "1.0", features = ["backtrace"] } 14 | jni = "0.21.1" 15 | cxx = "1.0.106" 16 | -------------------------------------------------------------------------------- /example/example-jsi-module/src/android.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context; 2 | use jni::objects::{JClass, JObject, JString}; 3 | use jni::sys::{jlong, jstring}; 4 | use jni::JNIEnv; 5 | 6 | // This function signature allows this function to be called from Java 7 | #[no_mangle] 8 | pub extern "system" fn Java_com_example_ExampleJsiModule_init<'env>( 9 | mut env: JNIEnv<'env>, 10 | _class: JClass<'env>, 11 | runtime_ptr: jlong, 12 | call_invoker_holder: JObject<'env>, 13 | ) -> () { 14 | // from https://github.com/facebookincubator/fbjni/blob/8b5aa9eb323184b27b87b5955b242e6e5a342c1a/cxx/fbjni/detail/Hybrid.h 15 | let hybrid_data: JObject = env 16 | .get_field( 17 | call_invoker_holder, 18 | "mHybridData", 19 | "Lcom/facebook/jni/HybridData;", 20 | ) 21 | .expect("can't find hybrid data on CallInvokerHolderImpl") 22 | .try_into() 23 | .unwrap(); 24 | 25 | let destructor: JObject = env 26 | .get_field( 27 | hybrid_data, 28 | "mDestructor", 29 | "Lcom/facebook/jni/HybridData$Destructor;", 30 | ) 31 | .expect("can't find internal destructor on CallInvokerHolderImpl") 32 | .try_into() 33 | .unwrap(); 34 | 35 | let call_invoker_holder_ptr: jlong = env 36 | .get_field(destructor, "mNativePointer", "J") 37 | .expect("can't find native pointer on CallInvokerHolderImpl") 38 | .try_into() 39 | .unwrap(); 40 | 41 | let call_invoker_holder = call_invoker_holder_ptr as *mut jsi::sys::CallInvokerHolder; 42 | let call_invoker_holder = unsafe { std::pin::Pin::new_unchecked(&mut *call_invoker_holder) }; 43 | let call_invoker = call_invoker_holder.getCallInvoker(); 44 | 45 | let runtime_ptr = runtime_ptr as *mut _; 46 | 47 | crate::init(runtime_ptr, call_invoker); 48 | } 49 | -------------------------------------------------------------------------------- /example/example-jsi-module/src/ios.rs: -------------------------------------------------------------------------------- 1 | #[no_mangle] 2 | pub extern "C" fn ExampleJsiModule_init( 3 | rt: *mut jsi::sys::Runtime, 4 | call_invoker: cxx::SharedPtr, 5 | ) { 6 | crate::init(rt, call_invoker); 7 | } 8 | -------------------------------------------------------------------------------- /example/example-jsi-module/src/lib.rs: -------------------------------------------------------------------------------- 1 | use jsi::{ 2 | host_object, FromObject, FromValue, IntoValue, JsiFn, JsiObject, JsiString, JsiValue, PropName, RuntimeHandle 3 | }; 4 | 5 | #[cfg(target_os = "android")] 6 | mod android; 7 | 8 | #[cfg(target_os = "ios")] 9 | mod ios; 10 | 11 | pub fn init(rt: *mut jsi::sys::Runtime, call_invoker: cxx::SharedPtr) { 12 | let (mut rt, _) = jsi::init(rt, call_invoker); 13 | 14 | let console = PropName::new("console", &mut rt); 15 | let console = rt.global().get(console, &mut rt); 16 | let console = JsiObject::from_value(&console, &mut rt).unwrap(); 17 | 18 | let console_log = console.get(PropName::new("log", &mut rt), &mut rt); 19 | let console_log = JsiObject::from_value(&console_log, &mut rt).unwrap(); 20 | let console_log = JsiFn::from_object(&console_log, &mut rt).unwrap(); 21 | console_log 22 | .call( 23 | [JsiString::new("hello from Rust", &mut rt).into_value(&mut rt)], 24 | &mut rt, 25 | ) 26 | .unwrap(); 27 | 28 | // we called console.log("hello from Rust") using JSI! you should see the 29 | // log in your React Native bundler terminal 30 | 31 | // this is just an example, but from here, you could spawn threads or really 32 | // do whatever you want with the RuntimeHandle 33 | 34 | // make sure that any multithreaded operations use the CallInvoker if they 35 | // want to call back to JavaScript 36 | 37 | // now, for my next trick, I will add a host object to the global namespace 38 | let host_object = ExampleHostObject; 39 | let host_object = host_object.into_value(&mut rt); 40 | 41 | rt.global().set(PropName::new("ExampleGlobal", &mut rt), &host_object, &mut rt); 42 | 43 | let global_str = JsiString::new("hallo", &mut rt); 44 | let global_str = global_str.into_value(&mut rt); 45 | rt.global().set(PropName::new("ExampleGlobal2", &mut rt), &global_str, &mut rt); 46 | 47 | let global_num = JsiValue::new_number(3.200); 48 | rt.global().set(PropName::new("ExampleGlobal3", &mut rt), &global_num, &mut rt); 49 | } 50 | 51 | struct ExampleHostObject; 52 | 53 | #[host_object] 54 | impl ExampleHostObject { 55 | pub fn time(&self, _rt: &mut RuntimeHandle) -> anyhow::Result { 56 | Ok(3200) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import {AppRegistry} from 'react-native'; 6 | import App from './App'; 7 | import {name as appName} from './app.json'; 8 | 9 | AppRegistry.registerComponent(appName, () => App); 10 | -------------------------------------------------------------------------------- /example/ios/.xcode.env: -------------------------------------------------------------------------------- 1 | # This `.xcode.env` file is versioned and is used to source the environment 2 | # used when running script phases inside Xcode. 3 | # To customize your local environment, you can create an `.xcode.env.local` 4 | # file that is not versioned. 5 | 6 | # NODE_BINARY variable contains the PATH to the node executable. 7 | # 8 | # Customize the NODE_BINARY variable here. 9 | # For example, to use nvm with brew, add the following line 10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use 11 | export NODE_BINARY=$(command -v node) 12 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Resolve react_native_pods.rb with node to allow for hoisting 2 | require Pod::Executable.execute_command('node', ['-p', 3 | 'require.resolve( 4 | "react-native/scripts/react_native_pods.rb", 5 | {paths: [process.argv[1]]}, 6 | )', __dir__]).strip 7 | 8 | platform :ios, min_ios_version_supported 9 | prepare_react_native_project! 10 | 11 | # If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set. 12 | # because `react-native-flipper` depends on (FlipperKit,...) that will be excluded 13 | # 14 | # To fix this you can also exclude `react-native-flipper` using a `react-native.config.js` 15 | # ```js 16 | # module.exports = { 17 | # dependencies: { 18 | # ...(process.env.NO_FLIPPER ? { 'react-native-flipper': { platforms: { ios: null } } } : {}), 19 | # ``` 20 | flipper_config = ENV['NO_FLIPPER'] == "1" ? FlipperConfiguration.disabled : FlipperConfiguration.enabled 21 | 22 | linkage = ENV['USE_FRAMEWORKS'] 23 | if linkage != nil 24 | Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green 25 | use_frameworks! :linkage => linkage.to_sym 26 | end 27 | 28 | target 'example' do 29 | config = use_native_modules! 30 | 31 | # Flags change depending on the env values. 32 | flags = get_default_flags() 33 | 34 | use_react_native!( 35 | :path => config[:reactNativePath], 36 | # Hermes is now enabled by default. Disable by setting this flag to false. 37 | :hermes_enabled => flags[:hermes_enabled], 38 | :fabric_enabled => flags[:fabric_enabled], 39 | # Enables Flipper. 40 | # 41 | # Note that if you have use_frameworks! enabled, Flipper will not work and 42 | # you should disable the next line. 43 | :flipper_configuration => flipper_config, 44 | # An absolute path to your application root. 45 | :app_path => "#{Pod::Config.instance.installation_root}/.." 46 | ) 47 | 48 | target 'exampleTests' do 49 | inherit! :complete 50 | # Pods for testing 51 | end 52 | 53 | post_install do |installer| 54 | # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202 55 | react_native_post_install( 56 | installer, 57 | config[:reactNativePath], 58 | :mac_catalyst_enabled => false 59 | ) 60 | __apply_Xcode_12_5_M1_post_install_workaround(installer) 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /example/ios/example.xcodeproj/xcshareddata/xcschemes/example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /example/ios/example/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : RCTAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /example/ios/example/AppDelegate.mm: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | 3 | #import 4 | 5 | @implementation AppDelegate 6 | 7 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 8 | { 9 | self.moduleName = @"example"; 10 | // You can add your custom initial props in the dictionary below. 11 | // They will be passed down to the ViewController used by React Native. 12 | self.initialProps = @{}; 13 | 14 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 15 | } 16 | 17 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge 18 | { 19 | #if DEBUG 20 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"]; 21 | #else 22 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 23 | #endif 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /example/ios/example/ExampleJsiModule.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef ExampleJsiModule_h 3 | #define ExampleJsiModule_h 4 | 5 | #include 6 | 7 | extern "C" { 8 | void ExampleJsiModule_init(void* rt, std::shared_ptr ctx); 9 | } 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /example/ios/example/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "scale" : "1x", 46 | "size" : "1024x1024" 47 | } 48 | ], 49 | "info" : { 50 | "author" : "xcode", 51 | "version" : 1 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /example/ios/example/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /example/ios/example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | example 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(CURRENT_PROJECT_VERSION) 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSExceptionDomains 30 | 31 | localhost 32 | 33 | NSExceptionAllowsInsecureHTTPLoads 34 | 35 | 36 | 37 | 38 | NSLocationWhenInUseUsageDescription 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UIViewControllerBasedStatusBarAppearance 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /example/ios/example/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /example/ios/example/RCTExampleJsiModule.h: -------------------------------------------------------------------------------- 1 | #ifndef RCTExampleJsiModule_h 2 | #define RCTExampleJsiModule_h 3 | 4 | #include "ExampleJsiModule.h" 5 | 6 | #import 7 | @interface RCTExampleJsiModule : NSObject 8 | @end 9 | 10 | 11 | #endif /* RCTExampleJsiModule_h */ 12 | -------------------------------------------------------------------------------- /example/ios/example/RTCExampleJsiModule.mm: -------------------------------------------------------------------------------- 1 | #import 2 | #import "RCTExampleJsiModule.h" 3 | 4 | #import 5 | #import 6 | #import 7 | #import 8 | #import 9 | 10 | @implementation RCTExampleJsiModule 11 | 12 | @synthesize bridge = _bridge; 13 | 14 | RCT_EXPORT_MODULE(ExampleJsiModule) 15 | 16 | - (void)invalidate { 17 | _bridge = nil; 18 | } 19 | 20 | - (void)setBridge:(RCTBridge *)bridge { 21 | _bridge = bridge; 22 | } 23 | 24 | + (BOOL)requiresMainQueueSetup { 25 | return YES; 26 | } 27 | 28 | 29 | void installApi(std::shared_ptr callInvoker, 30 | facebook::jsi::Runtime *runtime) { 31 | 32 | ExampleJsiModule_init((void*)runtime, (std::shared_ptr) callInvoker); 33 | } 34 | 35 | RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(install) { 36 | RCTCxxBridge *cxxBridge = (RCTCxxBridge *)_bridge; 37 | if (cxxBridge.runtime != nullptr) { 38 | auto callInvoker = cxxBridge.jsCallInvoker; 39 | facebook::jsi::Runtime *jsRuntime = (facebook::jsi::Runtime *)cxxBridge.runtime; 40 | 41 | installApi(callInvoker, jsRuntime); 42 | return @true; 43 | } 44 | return @false; 45 | } 46 | 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /example/ios/example/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | @autoreleasepool { 8 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /example/ios/exampleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /example/ios/exampleTests/exampleTests.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #import 5 | #import 6 | 7 | #define TIMEOUT_SECONDS 600 8 | #define TEXT_TO_LOOK_FOR @"Welcome to React" 9 | 10 | @interface exampleTests : XCTestCase 11 | 12 | @end 13 | 14 | @implementation exampleTests 15 | 16 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL (^)(UIView *view))test 17 | { 18 | if (test(view)) { 19 | return YES; 20 | } 21 | for (UIView *subview in [view subviews]) { 22 | if ([self findSubviewInView:subview matching:test]) { 23 | return YES; 24 | } 25 | } 26 | return NO; 27 | } 28 | 29 | - (void)testRendersWelcomeScreen 30 | { 31 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; 32 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 33 | BOOL foundElement = NO; 34 | 35 | __block NSString *redboxError = nil; 36 | #ifdef DEBUG 37 | RCTSetLogFunction( 38 | ^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 39 | if (level >= RCTLogLevelError) { 40 | redboxError = message; 41 | } 42 | }); 43 | #endif 44 | 45 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 46 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 47 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 48 | 49 | foundElement = [self findSubviewInView:vc.view 50 | matching:^BOOL(UIView *view) { 51 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 52 | return YES; 53 | } 54 | return NO; 55 | }]; 56 | } 57 | 58 | #ifdef DEBUG 59 | RCTSetLogFunction(RCTDefaultLogFunction); 60 | #endif 61 | 62 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 63 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 64 | } 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /example/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'react-native', 3 | }; 4 | -------------------------------------------------------------------------------- /example/metro.config.js: -------------------------------------------------------------------------------- 1 | const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config'); 2 | 3 | /** 4 | * Metro configuration 5 | * https://facebook.github.io/metro/docs/configuration 6 | * 7 | * @type {import('metro-config').MetroConfig} 8 | */ 9 | const config = {}; 10 | 11 | module.exports = mergeConfig(getDefaultConfig(__dirname), config); 12 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "android": "react-native run-android", 7 | "ios": "react-native run-ios", 8 | "lint": "eslint .", 9 | "start": "react-native start", 10 | "test": "jest" 11 | }, 12 | "dependencies": { 13 | "react": "18.2.0", 14 | "react-native": "0.72.4" 15 | }, 16 | "devDependencies": { 17 | "@babel/core": "^7.20.0", 18 | "@babel/preset-env": "^7.20.0", 19 | "@babel/runtime": "^7.20.0", 20 | "@react-native/eslint-config": "^0.72.2", 21 | "@react-native/metro-config": "^0.72.11", 22 | "@tsconfig/react-native": "^3.0.0", 23 | "@types/react": "^18.0.24", 24 | "@types/react-test-renderer": "^18.0.0", 25 | "babel-jest": "^29.2.1", 26 | "eslint": "^8.19.0", 27 | "jest": "^29.2.1", 28 | "metro-react-native-babel-preset": "0.76.8", 29 | "prettier": "^2.4.1", 30 | "react-test-renderer": "18.2.0", 31 | "typescript": "4.8.4" 32 | }, 33 | "engines": { 34 | "node": ">=16" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/react-native/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /jsi-macros/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "Inflector" 7 | version = "0.11.4" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" 10 | dependencies = [ 11 | "lazy_static", 12 | "regex", 13 | ] 14 | 15 | [[package]] 16 | name = "aho-corasick" 17 | version = "1.0.4" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" 20 | dependencies = [ 21 | "memchr", 22 | ] 23 | 24 | [[package]] 25 | name = "lazy_static" 26 | version = "1.4.0" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 29 | 30 | [[package]] 31 | name = "memchr" 32 | version = "2.5.0" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 35 | 36 | [[package]] 37 | name = "proc-macro-error" 38 | version = "1.0.4" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 41 | dependencies = [ 42 | "proc-macro-error-attr", 43 | "proc-macro2", 44 | "quote", 45 | "syn", 46 | "version_check", 47 | ] 48 | 49 | [[package]] 50 | name = "proc-macro-error-attr" 51 | version = "1.0.4" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 54 | dependencies = [ 55 | "proc-macro2", 56 | "quote", 57 | "version_check", 58 | ] 59 | 60 | [[package]] 61 | name = "proc-macro2" 62 | version = "1.0.66" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" 65 | dependencies = [ 66 | "unicode-ident", 67 | ] 68 | 69 | [[package]] 70 | name = "quote" 71 | version = "1.0.33" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 74 | dependencies = [ 75 | "proc-macro2", 76 | ] 77 | 78 | [[package]] 79 | name = "regex" 80 | version = "1.9.3" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" 83 | dependencies = [ 84 | "aho-corasick", 85 | "memchr", 86 | "regex-automata", 87 | "regex-syntax", 88 | ] 89 | 90 | [[package]] 91 | name = "regex-automata" 92 | version = "0.3.6" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" 95 | dependencies = [ 96 | "aho-corasick", 97 | "memchr", 98 | "regex-syntax", 99 | ] 100 | 101 | [[package]] 102 | name = "regex-syntax" 103 | version = "0.7.4" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" 106 | 107 | [[package]] 108 | name = "splicer-js-api-macros" 109 | version = "0.1.0" 110 | dependencies = [ 111 | "Inflector", 112 | "proc-macro-error", 113 | "proc-macro2", 114 | "quote", 115 | "syn", 116 | ] 117 | 118 | [[package]] 119 | name = "syn" 120 | version = "1.0.109" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 123 | dependencies = [ 124 | "proc-macro2", 125 | "quote", 126 | "unicode-ident", 127 | ] 128 | 129 | [[package]] 130 | name = "unicode-ident" 131 | version = "1.0.11" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 134 | 135 | [[package]] 136 | name = "version_check" 137 | version = "0.9.4" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 140 | -------------------------------------------------------------------------------- /jsi-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsi-macros" 3 | version = "0.3.1-alpha.5" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "Support macros for jsi-rs" 7 | authors = ["Ibiyemi Abiodun "] 8 | repository = "https://github.com/laptou/jsi-rs/" 9 | 10 | [lib] 11 | proc-macro = true 12 | 13 | [dependencies] 14 | Inflector = "0.11.4" 15 | proc-macro-error = "1.0" 16 | proc-macro2 = "1.0" 17 | quote = "1.0" 18 | syn = { version = "2", features = ["full", "quote"] } 19 | 20 | [features] 21 | host-object-trace = [] 22 | -------------------------------------------------------------------------------- /jsi-macros/src/host_event.rs: -------------------------------------------------------------------------------- 1 | use inflector::Inflector; 2 | use proc_macro2::TokenStream; 3 | 4 | use quote::{format_ident, quote}; 5 | use syn::{parse::Parse, ItemEnum}; 6 | 7 | extern crate proc_macro; 8 | pub struct HostEventImpl(pub TokenStream); 9 | 10 | impl Parse for HostEventImpl { 11 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 12 | let enum_def: ItemEnum = input.parse()?; 13 | 14 | let enum_name = &enum_def.ident; 15 | 16 | let event_key_name = format_ident!("{}Key", enum_name); 17 | 18 | let mut event_names = vec![]; 19 | let mut event_key_mappers = vec![]; 20 | let mut event_args_mappers = vec![]; 21 | 22 | for variant in &enum_def.variants { 23 | let variant_name = variant.ident.clone(); 24 | event_names.push(variant_name.clone()); 25 | 26 | event_key_mappers.push(match &variant.fields { 27 | syn::Fields::Named(_) => { 28 | quote! { Self::#variant_name { .. } => #event_key_name::#variant_name } 29 | } 30 | syn::Fields::Unnamed(_) => { 31 | quote! { Self::#variant_name ( .. ) => #event_key_name::#variant_name } 32 | } 33 | syn::Fields::Unit => { 34 | quote! { Self::#variant_name => #event_key_name::#variant_name } 35 | } 36 | }); 37 | 38 | event_args_mappers.push(match &variant.fields { 39 | syn::Fields::Named(fields) => { 40 | let field_names: Vec<_> = fields 41 | .named 42 | .iter() 43 | .map(|f| f.ident.clone().unwrap()) 44 | .collect(); 45 | 46 | quote! { Self::#variant_name { #(#field_names),* } => { 47 | let mut args: Vec<::jsi::JsiValue> = vec![]; 48 | #( args.push(::jsi::IntoValue::into_value(#field_names, rt)); )* 49 | args 50 | } } 51 | } 52 | syn::Fields::Unnamed(fields) => { 53 | let field_names: Vec<_> = fields 54 | .unnamed 55 | .iter() 56 | .enumerate() 57 | .map(|(idx, _)| format_ident!("f{}", idx)) 58 | .collect(); 59 | 60 | quote! { Self::#variant_name { #(#field_names),* } => { 61 | let mut args: Vec<::jsi::JsiValue> = vec![]; 62 | #( args.push(::jsi::IntoValue::into_value(#field_names, rt)); )* 63 | args 64 | } } 65 | } 66 | syn::Fields::Unit => { 67 | quote! { Self::#variant_name => vec![], } 68 | } 69 | }); 70 | } 71 | 72 | let event_key_def = quote! { 73 | #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] 74 | #[automatically_derived] 75 | pub enum #event_key_name { 76 | #(#event_names),* 77 | } 78 | }; 79 | 80 | let event_names_str = event_names.iter().map(|id| id.to_string().to_camel_case()); 81 | 82 | let event_key_impl_def = quote! { 83 | #[automatically_derived] 84 | impl ::std::str::FromStr for #event_key_name { 85 | type Err = ::anyhow::Error; 86 | 87 | fn from_str(s: &str) -> Result { 88 | match s { 89 | #(#event_names_str => Ok(Self::#event_names),)* 90 | _ => bail!("invalid event name") 91 | } 92 | } 93 | } 94 | 95 | #[automatically_derived] 96 | impl ::splicer_js_api::event::HostEventKey for #event_key_name { 97 | } 98 | }; 99 | 100 | let has_rt_lifetime = enum_def.generics.params.iter().any(|p| match p { 101 | syn::GenericParam::Lifetime(lt) => lt.lifetime.ident == "rt", 102 | _ => false, 103 | }); 104 | 105 | let enum_impl_generics = if has_rt_lifetime { 106 | quote! { <'rt> } 107 | } else { 108 | quote! {} 109 | }; 110 | 111 | let event_impl_def = quote! { 112 | #[automatically_derived] 113 | impl<'rt> ::splicer_js_api::event::HostEvent<'rt> for #enum_name #enum_impl_generics { 114 | type Key = #event_key_name; 115 | 116 | fn key(&self) -> Self::Key { 117 | match self { 118 | #(#event_key_mappers),* 119 | } 120 | } 121 | 122 | fn args(self, rt: &mut ::jsi::RuntimeHandle<'rt>) -> Vec> { 123 | match self { 124 | #(#event_args_mappers),* 125 | } 126 | } 127 | } 128 | }; 129 | 130 | Ok(Self(quote! { 131 | #event_key_def 132 | 133 | #event_key_impl_def 134 | 135 | #event_impl_def 136 | })) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /jsi-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro_error::proc_macro_error; 2 | use syn::parse_macro_input; 3 | 4 | mod host_event; 5 | mod host_object; 6 | 7 | 8 | /// A macro that makes it easy to define functions and properies on host 9 | /// objects. 10 | /// 11 | /// ```no_run 12 | /// struct ExampleHostObject; 13 | /// 14 | /// #[host_object] 15 | /// impl ExampleHostObject { 16 | /// pub fn time(&self, _rt: &mut RuntimeHandle) -> anyhow::Result { 17 | /// Ok(3200) 18 | /// } 19 | /// } 20 | /// ``` 21 | /// 22 | /// It is used on `impl` blocks and on the functions within the `impl` block. 23 | /// 24 | /// - Add `#[host_object(getter)]` to designate an `fn` as a getter. This will 25 | /// allow JavaScript code to read the member like a property instead of having 26 | /// to call it like a function. Getters should be `fn(&self, &mut 27 | /// RuntimeHandle) -> Result` where `T: IntoValue`. 28 | /// 29 | /// If the `fn` name in Rust starts with `get_`, this prefix will be removed. 30 | /// 31 | /// - Add `#[host_object(setter)]` to designate an `fn` as a setter. This will 32 | /// allow JavaScript code to write the like a property instead of having to 33 | /// call it like a function. Setters should be `fn(&self, &mut RuntimeHandle, 34 | /// value: JsiValue) -> Result<()>`. 35 | /// 36 | /// If the `fn` name in Rust starts with `set_`, this prefix will be removed. 37 | /// 38 | /// - All other methods will appear as methods on the host object in JavaScript. 39 | /// The first two arguments should be `&self` and a `&mut Runtime`. 40 | /// 41 | /// By default, all member names in a `host_object` block are converted from 42 | /// `snake_case` to `camelCase` to give them idiomatic names in JavaScript. To 43 | /// override this, you can set a name with `#[host_object(method as 44 | /// custom_method_name)]` or `#[host_object(getter as custom_prop_name)]`. 45 | /// 46 | #[proc_macro_attribute] 47 | pub fn host_object( 48 | _attr_input: proc_macro::TokenStream, 49 | attr_target: proc_macro::TokenStream, 50 | ) -> proc_macro::TokenStream { 51 | let impl_block = parse_macro_input!(attr_target as host_object::HostObjectImpl); 52 | let s = proc_macro::TokenStream::from(impl_block.0); 53 | // println!("{}", s); 54 | s 55 | } 56 | 57 | #[proc_macro_error] 58 | #[proc_macro_derive(HostEvent)] 59 | pub fn host_event_emitter(target: proc_macro::TokenStream) -> proc_macro::TokenStream { 60 | let impl_block = parse_macro_input!(target as host_event::HostEventImpl); 61 | proc_macro::TokenStream::from(impl_block.0) 62 | } 63 | -------------------------------------------------------------------------------- /jsi-sys/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.75" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" 10 | 11 | [[package]] 12 | name = "cc" 13 | version = "1.0.83" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 16 | dependencies = [ 17 | "libc", 18 | ] 19 | 20 | [[package]] 21 | name = "codespan-reporting" 22 | version = "0.11.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" 25 | dependencies = [ 26 | "termcolor", 27 | "unicode-width", 28 | ] 29 | 30 | [[package]] 31 | name = "cxx" 32 | version = "1.0.106" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "28403c86fc49e3401fdf45499ba37fad6493d9329449d6449d7f0e10f4654d28" 35 | dependencies = [ 36 | "cc", 37 | "cxxbridge-flags", 38 | "cxxbridge-macro", 39 | "link-cplusplus", 40 | ] 41 | 42 | [[package]] 43 | name = "cxx-build" 44 | version = "1.0.106" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "78da94fef01786dc3e0c76eafcd187abcaa9972c78e05ff4041e24fdf059c285" 47 | dependencies = [ 48 | "cc", 49 | "codespan-reporting", 50 | "once_cell", 51 | "proc-macro2", 52 | "quote", 53 | "scratch", 54 | "syn", 55 | ] 56 | 57 | [[package]] 58 | name = "cxxbridge-flags" 59 | version = "1.0.106" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "e2a6f5e1dfb4b34292ad4ea1facbfdaa1824705b231610087b00b17008641809" 62 | 63 | [[package]] 64 | name = "cxxbridge-macro" 65 | version = "1.0.106" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "50c49547d73ba8dcfd4ad7325d64c6d5391ff4224d498fc39a6f3f49825a530d" 68 | dependencies = [ 69 | "proc-macro2", 70 | "quote", 71 | "syn", 72 | ] 73 | 74 | [[package]] 75 | name = "dunce" 76 | version = "1.0.4" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" 79 | 80 | [[package]] 81 | name = "libc" 82 | version = "0.2.147" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" 85 | 86 | [[package]] 87 | name = "link-cplusplus" 88 | version = "1.0.9" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" 91 | dependencies = [ 92 | "cc", 93 | ] 94 | 95 | [[package]] 96 | name = "once_cell" 97 | version = "1.18.0" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 100 | 101 | [[package]] 102 | name = "proc-macro2" 103 | version = "1.0.66" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" 106 | dependencies = [ 107 | "unicode-ident", 108 | ] 109 | 110 | [[package]] 111 | name = "quote" 112 | version = "1.0.33" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 115 | dependencies = [ 116 | "proc-macro2", 117 | ] 118 | 119 | [[package]] 120 | name = "scratch" 121 | version = "1.0.7" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" 124 | 125 | [[package]] 126 | name = "splicer-jsi-sys" 127 | version = "0.1.0" 128 | dependencies = [ 129 | "anyhow", 130 | "cxx", 131 | "cxx-build", 132 | "dunce", 133 | ] 134 | 135 | [[package]] 136 | name = "syn" 137 | version = "2.0.29" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" 140 | dependencies = [ 141 | "proc-macro2", 142 | "quote", 143 | "unicode-ident", 144 | ] 145 | 146 | [[package]] 147 | name = "termcolor" 148 | version = "1.2.0" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" 151 | dependencies = [ 152 | "winapi-util", 153 | ] 154 | 155 | [[package]] 156 | name = "unicode-ident" 157 | version = "1.0.11" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 160 | 161 | [[package]] 162 | name = "unicode-width" 163 | version = "0.1.10" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 166 | 167 | [[package]] 168 | name = "winapi" 169 | version = "0.3.9" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 172 | dependencies = [ 173 | "winapi-i686-pc-windows-gnu", 174 | "winapi-x86_64-pc-windows-gnu", 175 | ] 176 | 177 | [[package]] 178 | name = "winapi-i686-pc-windows-gnu" 179 | version = "0.4.0" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 182 | 183 | [[package]] 184 | name = "winapi-util" 185 | version = "0.1.5" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 188 | dependencies = [ 189 | "winapi", 190 | ] 191 | 192 | [[package]] 193 | name = "winapi-x86_64-pc-windows-gnu" 194 | version = "0.4.0" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 197 | -------------------------------------------------------------------------------- /jsi-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsi-sys" 3 | version = "0.3.0-alpha.5" 4 | edition = "2021" 5 | links = "jsi" 6 | license = "MIT" 7 | description = "Low-level interface to React Native JSI" 8 | authors = ["Ibiyemi Abiodun "] 9 | repository = "https://github.com/laptou/jsi-rs/" 10 | 11 | exclude = [ 12 | "vendor/hermes" 13 | ] 14 | 15 | [dependencies] 16 | cxx = "1.0" 17 | anyhow = "1.0" 18 | 19 | [build-dependencies] 20 | cxx-build = "1.0" 21 | dunce = "1.0" 22 | -------------------------------------------------------------------------------- /jsi-sys/build.rs: -------------------------------------------------------------------------------- 1 | use std::{env, path::PathBuf}; 2 | 3 | fn main() { 4 | let base = env::var_os("CARGO_MANIFEST_DIR").unwrap(); 5 | let pkg_base = PathBuf::from(base); 6 | 7 | let target_os = env::var_os("CARGO_CFG_TARGET_OS"); 8 | let target_os = if let Some(s) = &target_os { 9 | s.to_str() 10 | } else { 11 | None 12 | }; 13 | 14 | let rn_base = pkg_base.join("vendor/react-native/packages/react-native"); 15 | 16 | let mut includes = vec![ 17 | rn_base.join("React"), 18 | rn_base.join("React/Base"), 19 | rn_base.join("ReactCommon/jsi"), 20 | rn_base.join("ReactCommon/callinvoker"), 21 | pkg_base.join("include"), 22 | ]; 23 | 24 | if let Some("android") = target_os { 25 | includes.push( 26 | rn_base.join("ReactAndroid/src/main/jni/react/turbomodule/"), 27 | ); 28 | includes.push(pkg_base.join("vendor/fbjni/cxx")); 29 | } 30 | 31 | let includes: Vec<_> = IntoIterator::into_iter(includes) 32 | .map(|p| dunce::canonicalize(&p).expect(&format!("missing include path {:?}", p))) 33 | .collect(); 34 | 35 | let mut compiles = vec![rn_base.join("ReactCommon/jsi/jsi/jsi.cpp")]; 36 | 37 | if let Some("android") = target_os { 38 | compiles.push( 39 | rn_base.join("ReactAndroid/src/main/jni/react/turbomodule/ReactCommon/CallInvokerHolder.cpp") 40 | ); 41 | } 42 | 43 | let compiles: Vec<_> = IntoIterator::into_iter(compiles) 44 | .map(|p| dunce::canonicalize(&p).expect(&format!("missing compile file {:?}", p))) 45 | .collect(); 46 | 47 | cxx_build::CFG 48 | .exported_header_dirs 49 | .extend(includes.iter().map(|e| e.as_path())); 50 | 51 | let mut bridges = vec!["src/ffi/base.rs", "src/ffi/host.rs"]; 52 | 53 | if let Some("android") = target_os { 54 | bridges.push("src/ffi/android.rs"); 55 | } 56 | 57 | for bridge in &bridges { 58 | println!("cargo:rerun-if-changed={}", bridge); 59 | } 60 | 61 | cxx_build::bridges(bridges) 62 | .flag_if_supported("-std=c++17") 63 | .files(compiles) 64 | .compile("jsi"); 65 | 66 | println!("cargo:rerun-if-changed=include/wrapper.h"); 67 | println!("cargo:rerun-if-changed=include/host.h"); 68 | } 69 | -------------------------------------------------------------------------------- /jsi-sys/include/host.h: -------------------------------------------------------------------------------- 1 | #ifndef JSI_HOST_H 2 | #define JSI_HOST_H 3 | #pragma once 4 | 5 | #include "rust/cxx.h" 6 | #include "jsi/jsi.h" 7 | 8 | namespace jsi_rs 9 | { 10 | namespace ffi 11 | { 12 | using Value = ::facebook::jsi::Value; 13 | using HostObject = ::facebook::jsi::HostObject; 14 | using Runtime = ::facebook::jsi::Runtime; 15 | using PropNameID = ::facebook::jsi::PropNameID; 16 | using JSError = ::facebook::jsi::JSError; 17 | 18 | struct RustHostObject; 19 | 20 | ::std::unique_ptr rho_get( 21 | RustHostObject &_self, Runtime &rt, const PropNameID &name); 22 | 23 | void rho_set( 24 | RustHostObject &_self, Runtime &rt, const PropNameID &name, const Value &value); 25 | 26 | ::std::unique_ptr<::std::vector> rho_properties( 27 | RustHostObject &_self, Runtime &rt) noexcept; 28 | 29 | class CxxHostObject : public HostObject 30 | { 31 | public: 32 | rust::Box inner; 33 | 34 | CxxHostObject(rust::Box it) : HostObject(), inner(std::move(it)) {} 35 | 36 | Value get(Runtime &rt, const PropNameID &name) 37 | { 38 | try 39 | { 40 | auto value = rho_get(*inner, rt, name); 41 | return std::move(*value.release()); 42 | } 43 | catch (rust::Error &e) 44 | { 45 | throw JSError(rt, e.what()); 46 | } 47 | } 48 | 49 | void set(Runtime &rt, const PropNameID &name, Value const &value) 50 | { 51 | try 52 | { 53 | rho_set(*inner, rt, name, value); 54 | } 55 | catch (rust::Error &e) 56 | { 57 | throw JSError(rt, e.what()); 58 | } 59 | } 60 | 61 | std::vector getPropertyNames(Runtime &rt) 62 | { 63 | auto value = rho_properties(*inner, rt); 64 | return std::move(*value.release()); 65 | } 66 | }; 67 | 68 | ::std::unique_ptr CxxHostObject_create( 69 | rust::Box<::jsi_rs::ffi::RustHostObject> rho) noexcept 70 | { 71 | return std::make_unique(std::move(rho)); 72 | } 73 | 74 | ::std::unique_ptr CxxHostObject_toHostObjectU( 75 | ::std::unique_ptr cho) noexcept 76 | { 77 | return cho; 78 | } 79 | 80 | ::std::unique_ptr CxxHostObject_fromHostObjectU( 81 | ::std::unique_ptr ho) noexcept 82 | { 83 | return std::unique_ptr(dynamic_cast(ho.release())); 84 | } 85 | 86 | ::std::shared_ptr CxxHostObject_toHostObjectS( 87 | ::std::shared_ptr cho) noexcept 88 | { 89 | return cho; 90 | } 91 | 92 | ::std::shared_ptr CxxHostObject_fromHostObjectS( 93 | ::std::shared_ptr ho) noexcept 94 | { 95 | return std::dynamic_pointer_cast(ho); 96 | } 97 | 98 | RustHostObject const &CxxHostObject_getInner( 99 | CxxHostObject const &cho) noexcept 100 | { 101 | return *cho.inner; 102 | } 103 | 104 | RustHostObject &CxxHostObject_getInnerMut( 105 | CxxHostObject &cho) noexcept 106 | { 107 | return *cho.inner; 108 | } 109 | } 110 | } 111 | 112 | #endif 113 | -------------------------------------------------------------------------------- /jsi-sys/src/ffi/android.rs: -------------------------------------------------------------------------------- 1 | #[cxx::bridge] 2 | pub mod ffi { 3 | #[namespace = "facebook::react"] 4 | unsafe extern "C++" { 5 | include!("ReactCommon/CallInvokerHolder.h"); 6 | 7 | type CallInvoker = crate::ffi::base::CallInvoker; 8 | pub type CallInvokerHolder; 9 | 10 | pub fn getCallInvoker(self: Pin<&mut CallInvokerHolder>) -> SharedPtr; 11 | } 12 | } 13 | 14 | pub use ffi::CallInvokerHolder; 15 | -------------------------------------------------------------------------------- /jsi-sys/src/ffi/host.rs: -------------------------------------------------------------------------------- 1 | use crate::shim::{rho_get, rho_properties, rho_set, RustHostObject}; 2 | 3 | #[cxx::bridge] 4 | pub(crate) mod ffi { 5 | #[namespace = "jsi_rs::ffi"] 6 | unsafe extern "C++" { 7 | include!("host.h"); 8 | 9 | #[namespace = "facebook::jsi"] 10 | pub type HostObject = crate::ffi::base::HostObject; 11 | #[namespace = "facebook::jsi"] 12 | pub type Runtime = crate::ffi::base::Runtime; 13 | #[cxx_name = "Value"] 14 | #[namespace = "facebook::jsi"] 15 | pub type JsiValue = crate::ffi::base::JsiValue; 16 | #[namespace = "facebook::jsi"] 17 | pub type PropNameID = crate::ffi::base::PropNameID; 18 | 19 | pub type CxxHostObject; 20 | pub fn CxxHostObject_create(rho: Box>) -> UniquePtr; 21 | 22 | pub fn CxxHostObject_toHostObjectU(ptr: UniquePtr) -> UniquePtr; 23 | pub fn CxxHostObject_fromHostObjectU( 24 | ptr: UniquePtr, 25 | ) -> UniquePtr; 26 | 27 | pub fn CxxHostObject_toHostObjectS(ptr: SharedPtr) -> SharedPtr; 28 | pub fn CxxHostObject_fromHostObjectS( 29 | ptr: SharedPtr, 30 | ) -> SharedPtr; 31 | 32 | pub fn CxxHostObject_getInner(ptr: &CxxHostObject) -> &RustHostObject; 33 | pub fn CxxHostObject_getInnerMut(ptr: Pin<&mut CxxHostObject>) -> &mut RustHostObject; 34 | } 35 | 36 | #[namespace = "jsi_rs::ffi"] 37 | extern "Rust" { 38 | type RustHostObject<'a>; 39 | 40 | unsafe fn rho_get<'a>( 41 | _self: &mut RustHostObject<'a>, 42 | rt: Pin<&mut Runtime>, 43 | name: &PropNameID, 44 | ) -> Result>; 45 | unsafe fn rho_set<'a>( 46 | _self: &mut RustHostObject<'a>, 47 | rt: Pin<&mut Runtime>, 48 | name: &PropNameID, 49 | value: &JsiValue, 50 | ) -> Result<()>; 51 | unsafe fn rho_properties<'a>( 52 | _self: &mut RustHostObject<'a>, 53 | rt: Pin<&mut Runtime>, 54 | ) -> UniquePtr>; 55 | } 56 | } 57 | 58 | pub use ffi::*; 59 | -------------------------------------------------------------------------------- /jsi-sys/src/ffi/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "android")] 2 | mod android; 3 | mod base; 4 | mod host; 5 | 6 | #[cfg(target_os = "android")] 7 | pub use android::*; 8 | pub use base::*; 9 | pub use host::*; 10 | -------------------------------------------------------------------------------- /jsi-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod ffi; 2 | mod shim; 3 | 4 | pub use ffi::*; 5 | pub use shim::*; 6 | -------------------------------------------------------------------------------- /jsi-sys/src/shim.rs: -------------------------------------------------------------------------------- 1 | use std::pin::Pin; 2 | 3 | use crate::ffi::*; 4 | use cxx::*; 5 | 6 | impl HostObject { 7 | pub fn get( 8 | self: Pin<&mut HostObject>, 9 | rt: Pin<&mut Runtime>, 10 | name: &PropNameID, 11 | ) -> UniquePtr { 12 | unsafe { HostObject_get(self, rt, name) } 13 | } 14 | 15 | pub fn get_property_names( 16 | self: Pin<&mut HostObject>, 17 | rt: Pin<&mut Runtime>, 18 | ) -> UniquePtr> { 19 | unsafe { HostObject_getPropertyNames(self, rt) } 20 | } 21 | } 22 | 23 | #[repr(transparent)] 24 | pub struct RustHostObject<'a>(pub Box); 25 | 26 | // i wanted to put these functions inside of an impl block, but this create a 27 | // circular dependency headache on the C++ side b/c you can't access the members 28 | // of a type before it is defined, so they're just normal functions 29 | 30 | // functions are used by FFI interface, but Rust thinks it's dead code b/c it's 31 | // pub(crate) 32 | #[allow(dead_code)] 33 | pub(crate) fn rho_get( 34 | rho: &mut RustHostObject, 35 | rt: Pin<&mut Runtime>, 36 | name: &PropNameID, 37 | ) -> anyhow::Result> { 38 | rho.0.get(rt, name) 39 | } 40 | 41 | #[allow(dead_code)] 42 | pub(crate) fn rho_set( 43 | rho: &mut RustHostObject, 44 | rt: Pin<&mut Runtime>, 45 | name: &PropNameID, 46 | value: &JsiValue, 47 | ) -> anyhow::Result<()> { 48 | rho.0.set(rt, name, value) 49 | } 50 | 51 | #[allow(dead_code)] 52 | pub(crate) fn rho_properties( 53 | rho: &mut RustHostObject, 54 | rt: Pin<&mut Runtime>, 55 | ) -> UniquePtr> { 56 | unsafe { 57 | let props = rho.0.properties(rt); 58 | let mut vec = create_prop_name_vector(); 59 | for prop in props { 60 | push_prop_name_vector(vec.pin_mut(), prop); 61 | } 62 | vec 63 | } 64 | } 65 | 66 | pub trait HostObjectImpl { 67 | fn get( 68 | &mut self, 69 | rt: Pin<&mut Runtime>, 70 | name: &PropNameID, 71 | ) -> anyhow::Result>; 72 | fn set( 73 | &mut self, 74 | rt: Pin<&mut Runtime>, 75 | name: &PropNameID, 76 | value: &JsiValue, 77 | ) -> anyhow::Result<()>; 78 | fn properties(&mut self, rt: Pin<&mut Runtime>) -> Vec>; 79 | } 80 | 81 | impl Runtime { 82 | pub fn evaluate_javascript( 83 | self: Pin<&mut Runtime>, 84 | buffer: &SharedPtr, 85 | source_url: &str, 86 | ) -> UniquePtr { 87 | unsafe { Runtime_evaluateJavaScript(self, buffer, source_url) } 88 | } 89 | 90 | pub fn prepare_javascript( 91 | self: Pin<&mut Runtime>, 92 | buffer: &SharedPtr, 93 | source_url: &str, 94 | ) -> SharedPtr { 95 | unsafe { Runtime_prepareJavaScript(self, buffer, source_url) } 96 | } 97 | 98 | pub fn evaluate_prepared_javascript( 99 | self: Pin<&mut Runtime>, 100 | js: &SharedPtr, 101 | ) -> UniquePtr { 102 | unsafe { Runtime_evaluatePreparedJavaScript(self, &js) } 103 | } 104 | 105 | pub fn global(self: Pin<&mut Runtime>) -> UniquePtr { 106 | unsafe { Runtime_global(self) } 107 | } 108 | 109 | pub fn description(self: Pin<&mut Runtime>) -> UniquePtr { 110 | unsafe { Runtime_description(self) } 111 | } 112 | } 113 | 114 | impl PropNameID { 115 | pub fn from_str(rt: Pin<&mut Runtime>, s: &str) -> UniquePtr { 116 | unsafe { PropNameID_forUtf8(rt, s) } 117 | } 118 | 119 | pub fn from_jsi_string(rt: Pin<&mut Runtime>, s: &JsiString) -> UniquePtr { 120 | unsafe { PropNameID_forString(rt, s) } 121 | } 122 | 123 | pub fn to_string(&self, rt: Pin<&mut Runtime>) -> UniquePtr { 124 | unsafe { PropNameID_toUtf8(self, rt) } 125 | } 126 | 127 | pub fn compare(&self, other: &Self, rt: Pin<&mut Runtime>) -> bool { 128 | unsafe { PropNameID_compare(rt, self, other) } 129 | } 130 | } 131 | 132 | impl JsiSymbol { 133 | pub fn to_string(&self, rt: Pin<&mut Runtime>) -> UniquePtr { 134 | unsafe { Symbol_toString(self, rt) } 135 | } 136 | 137 | pub fn compare(&self, other: &Self, rt: Pin<&mut Runtime>) -> bool { 138 | unsafe { Symbol_compare(rt, self, other) } 139 | } 140 | } 141 | 142 | impl JsiString { 143 | pub fn from_str(rt: Pin<&mut Runtime>, s: &str) -> UniquePtr { 144 | unsafe { String_fromUtf8(rt, s) } 145 | } 146 | 147 | pub fn to_string(&self, rt: Pin<&mut Runtime>) -> UniquePtr { 148 | unsafe { String_toString(self, rt) } 149 | } 150 | 151 | pub fn compare(&self, other: &Self, rt: Pin<&mut Runtime>) -> bool { 152 | unsafe { String_compare(rt, self, other) } 153 | } 154 | } 155 | 156 | impl JsiObject { 157 | pub fn new(rt: Pin<&mut Runtime>) -> UniquePtr { 158 | unsafe { Object_create(rt) } 159 | } 160 | 161 | pub fn from_host_object(rt: Pin<&mut Runtime>, ho: SharedPtr) -> UniquePtr { 162 | unsafe { Object_createFromHostObjectShared(rt, ho) } 163 | } 164 | 165 | pub fn compare(&self, other: &Self, rt: Pin<&mut Runtime>) -> bool { 166 | unsafe { Object_compare(rt, self, other) } 167 | } 168 | 169 | pub fn get_property(&self, rt: Pin<&mut Runtime>, prop: &PropNameID) -> UniquePtr { 170 | unsafe { Object_getProperty(self, rt, prop) } 171 | } 172 | 173 | pub fn set_property( 174 | self: Pin<&mut Self>, 175 | rt: Pin<&mut Runtime>, 176 | prop: &PropNameID, 177 | value: &JsiValue, 178 | ) { 179 | unsafe { Object_setProperty(self, rt, prop, value) } 180 | } 181 | 182 | pub fn as_array(&self, rt: Pin<&mut Runtime>) -> Option> { 183 | unsafe { Object_asArray(self, rt).ok() } 184 | } 185 | 186 | pub fn as_array_buffer(&self, rt: Pin<&mut Runtime>) -> Option> { 187 | unsafe { Object_asArrayBuffer(self, rt).ok() } 188 | } 189 | 190 | pub fn as_function(&self, rt: Pin<&mut Runtime>) -> Option> { 191 | unsafe { Object_asFunction(self, rt).ok() } 192 | } 193 | 194 | pub fn get_property_names(self: Pin<&mut Self>, rt: Pin<&mut Runtime>) -> UniquePtr { 195 | unsafe { Object_getPropertyNames(self, rt) } 196 | } 197 | } 198 | 199 | impl JsiValue { 200 | pub fn undefined() -> UniquePtr { 201 | unsafe { Value_fromUndefined() } 202 | } 203 | 204 | pub fn null() -> UniquePtr { 205 | unsafe { Value_fromNull() } 206 | } 207 | 208 | pub fn int(i: i32) -> UniquePtr { 209 | unsafe { Value_fromInt(i) } 210 | } 211 | 212 | pub fn bool(b: bool) -> UniquePtr { 213 | unsafe { Value_fromBool(b) } 214 | } 215 | 216 | pub fn double(d: f64) -> UniquePtr { 217 | unsafe { Value_fromDouble(d) } 218 | } 219 | 220 | pub fn object(rt: Pin<&mut Runtime>, o: &JsiObject) -> UniquePtr { 221 | unsafe { Value_copyFromObject(rt, o) } 222 | } 223 | 224 | pub fn symbol(rt: Pin<&mut Runtime>, s: &JsiSymbol) -> UniquePtr { 225 | unsafe { Value_copyFromSymbol(rt, s) } 226 | } 227 | 228 | pub fn string(rt: Pin<&mut Runtime>, s: &JsiString) -> UniquePtr { 229 | unsafe { Value_copyFromString(rt, s) } 230 | } 231 | 232 | pub fn from_json(rt: Pin<&mut Runtime>, json: &str) -> UniquePtr { 233 | unsafe { Value_fromJson(rt, json) } 234 | } 235 | 236 | pub fn as_object(&self, rt: Pin<&mut Runtime>) -> Result, cxx::Exception> { 237 | unsafe { Value_asObject(self, rt) } 238 | } 239 | 240 | pub fn as_symbol(&self, rt: Pin<&mut Runtime>) -> Result, cxx::Exception> { 241 | unsafe { Value_asSymbol(self, rt) } 242 | } 243 | 244 | pub fn as_string(&self, rt: Pin<&mut Runtime>) -> Result, cxx::Exception> { 245 | unsafe { Value_asString(self, rt) } 246 | } 247 | 248 | pub fn to_string(&self, rt: Pin<&mut Runtime>) -> UniquePtr { 249 | unsafe { Value_toString(self, rt) } 250 | } 251 | } 252 | 253 | impl JsiWeakObject { 254 | pub fn from_object(rt: Pin<&mut Runtime>, object: &JsiObject) -> UniquePtr { 255 | unsafe { WeakObject_fromObject(rt, object) } 256 | } 257 | 258 | pub fn lock(self: Pin<&mut Self>, rt: Pin<&mut Runtime>) -> UniquePtr { 259 | unsafe { WeakObject_lock(self, rt) } 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /jsi-sys/vendor: -------------------------------------------------------------------------------- 1 | ../vendor -------------------------------------------------------------------------------- /jsi-tests/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.75" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" 10 | 11 | [[package]] 12 | name = "better_any" 13 | version = "0.1.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "b359aebd937c17c725e19efcb661200883f04c49c53e7132224dac26da39d4a0" 16 | dependencies = [ 17 | "better_typeid_derive", 18 | ] 19 | 20 | [[package]] 21 | name = "better_typeid_derive" 22 | version = "0.1.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "3deeecb812ca5300b7d3f66f730cc2ebd3511c3d36c691dd79c165d5b19a26e3" 25 | dependencies = [ 26 | "proc-macro2", 27 | "quote", 28 | "syn 1.0.109", 29 | ] 30 | 31 | [[package]] 32 | name = "cc" 33 | version = "1.0.83" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 36 | dependencies = [ 37 | "libc", 38 | ] 39 | 40 | [[package]] 41 | name = "codespan-reporting" 42 | version = "0.11.1" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" 45 | dependencies = [ 46 | "termcolor", 47 | "unicode-width", 48 | ] 49 | 50 | [[package]] 51 | name = "cxx" 52 | version = "1.0.106" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "28403c86fc49e3401fdf45499ba37fad6493d9329449d6449d7f0e10f4654d28" 55 | dependencies = [ 56 | "cc", 57 | "cxxbridge-flags", 58 | "cxxbridge-macro", 59 | "link-cplusplus", 60 | ] 61 | 62 | [[package]] 63 | name = "cxx-build" 64 | version = "1.0.106" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "78da94fef01786dc3e0c76eafcd187abcaa9972c78e05ff4041e24fdf059c285" 67 | dependencies = [ 68 | "cc", 69 | "codespan-reporting", 70 | "once_cell", 71 | "proc-macro2", 72 | "quote", 73 | "scratch", 74 | "syn 2.0.29", 75 | ] 76 | 77 | [[package]] 78 | name = "cxxbridge-flags" 79 | version = "1.0.106" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "e2a6f5e1dfb4b34292ad4ea1facbfdaa1824705b231610087b00b17008641809" 82 | 83 | [[package]] 84 | name = "cxxbridge-macro" 85 | version = "1.0.106" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "50c49547d73ba8dcfd4ad7325d64c6d5391ff4224d498fc39a6f3f49825a530d" 88 | dependencies = [ 89 | "proc-macro2", 90 | "quote", 91 | "syn 2.0.29", 92 | ] 93 | 94 | [[package]] 95 | name = "dunce" 96 | version = "1.0.4" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" 99 | 100 | [[package]] 101 | name = "libc" 102 | version = "0.2.147" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" 105 | 106 | [[package]] 107 | name = "link-cplusplus" 108 | version = "1.0.9" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" 111 | dependencies = [ 112 | "cc", 113 | ] 114 | 115 | [[package]] 116 | name = "log" 117 | version = "0.4.20" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 120 | 121 | [[package]] 122 | name = "once_cell" 123 | version = "1.18.0" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 126 | 127 | [[package]] 128 | name = "proc-macro2" 129 | version = "1.0.66" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" 132 | dependencies = [ 133 | "unicode-ident", 134 | ] 135 | 136 | [[package]] 137 | name = "quote" 138 | version = "1.0.33" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 141 | dependencies = [ 142 | "proc-macro2", 143 | ] 144 | 145 | [[package]] 146 | name = "scratch" 147 | version = "1.0.7" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" 150 | 151 | [[package]] 152 | name = "splicer-js-tests" 153 | version = "0.1.0" 154 | dependencies = [ 155 | "anyhow", 156 | "cxx", 157 | "cxx-build", 158 | "dunce", 159 | "splicer-jsi", 160 | "splicer-jsi-sys", 161 | ] 162 | 163 | [[package]] 164 | name = "splicer-jsi" 165 | version = "0.2.0" 166 | dependencies = [ 167 | "anyhow", 168 | "better_any", 169 | "cxx", 170 | "log", 171 | "splicer-jsi-sys", 172 | ] 173 | 174 | [[package]] 175 | name = "splicer-jsi-sys" 176 | version = "0.1.0" 177 | dependencies = [ 178 | "anyhow", 179 | "cxx", 180 | "cxx-build", 181 | "dunce", 182 | ] 183 | 184 | [[package]] 185 | name = "syn" 186 | version = "1.0.109" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 189 | dependencies = [ 190 | "proc-macro2", 191 | "quote", 192 | "unicode-ident", 193 | ] 194 | 195 | [[package]] 196 | name = "syn" 197 | version = "2.0.29" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" 200 | dependencies = [ 201 | "proc-macro2", 202 | "quote", 203 | "unicode-ident", 204 | ] 205 | 206 | [[package]] 207 | name = "termcolor" 208 | version = "1.2.0" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" 211 | dependencies = [ 212 | "winapi-util", 213 | ] 214 | 215 | [[package]] 216 | name = "unicode-ident" 217 | version = "1.0.11" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 220 | 221 | [[package]] 222 | name = "unicode-width" 223 | version = "0.1.10" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 226 | 227 | [[package]] 228 | name = "winapi" 229 | version = "0.3.9" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 232 | dependencies = [ 233 | "winapi-i686-pc-windows-gnu", 234 | "winapi-x86_64-pc-windows-gnu", 235 | ] 236 | 237 | [[package]] 238 | name = "winapi-i686-pc-windows-gnu" 239 | version = "0.4.0" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 242 | 243 | [[package]] 244 | name = "winapi-util" 245 | version = "0.1.5" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 248 | dependencies = [ 249 | "winapi", 250 | ] 251 | 252 | [[package]] 253 | name = "winapi-x86_64-pc-windows-gnu" 254 | version = "0.4.0" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 257 | -------------------------------------------------------------------------------- /jsi-tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsi-tests" 3 | version = "0.3.1-alpha.1" 4 | edition = "2021" 5 | links = "hermes" 6 | publish = false 7 | 8 | [dependencies] 9 | cxx = "1.0" 10 | anyhow = "1.0" 11 | jsi-sys = { path = "../jsi-sys" } 12 | jsi = { path = "../jsi" } 13 | 14 | [build-dependencies] 15 | cxx-build = "1.0" 16 | dunce = "1.0" 17 | -------------------------------------------------------------------------------- /jsi-tests/README.md: -------------------------------------------------------------------------------- 1 | # `js-tests` 2 | 3 | This crate contains tests for `jsi-rs`. The tests are run against 4 | [Hermes](https://github.com/facebook/hermes), which is Facebook's reference 5 | implementation of JavaScript for React Native. 6 | 7 | ## Building and running tests 8 | 9 | Hermes is a Git submodule of this repository, so it should be cloned 10 | automatically when you clone this repo. The initial build of this crate will 11 | take a while because it builds Hermes first. 12 | 13 | Once that build finishes, you can run `cargo test` as normal. 14 | -------------------------------------------------------------------------------- /jsi-tests/build.rs: -------------------------------------------------------------------------------- 1 | use std::{env, path::PathBuf, process::Command}; 2 | 3 | fn main() { 4 | let pkg_base = env::var_os("CARGO_MANIFEST_DIR").unwrap(); 5 | let pkg_base = PathBuf::from(pkg_base); 6 | 7 | let hermes_build_status = Command::new("bash") 8 | .args([pkg_base.join("../vendor/build-hermes.sh")]) 9 | .current_dir(pkg_base.join("../vendor")) 10 | .output() 11 | .expect("hermes build script could not be executed"); 12 | 13 | if !hermes_build_status.status.success() { 14 | panic!( 15 | "hermes build script failed\n\nstdout: {}\n\nstderr: {}", 16 | String::from_utf8_lossy(&hermes_build_status.stdout), 17 | String::from_utf8_lossy(&hermes_build_status.stderr), 18 | ) 19 | } 20 | 21 | let rn_base = pkg_base.join("../vendor/react-native/packages/react-native"); 22 | 23 | let includes = vec![ 24 | rn_base.join("React"), 25 | rn_base.join("React/Base"), 26 | rn_base.join("ReactCommon/jsi"), 27 | rn_base.join("ReactCommon/callinvoker"), 28 | pkg_base.join("../vendor/hermes/API"), 29 | pkg_base.join("../vendor/hermes/public"), 30 | pkg_base.join("include"), 31 | ]; 32 | 33 | for include in &includes { 34 | println!("cargo:rerun-if-changed={:?}", include); 35 | } 36 | 37 | let includes: Vec<_> = IntoIterator::into_iter(includes) 38 | .map(|p| dunce::canonicalize(&p).expect(&format!("missing include path {:?}", p))) 39 | .collect(); 40 | 41 | let compiles: Vec = vec![]; 42 | 43 | let compiles: Vec<_> = IntoIterator::into_iter(compiles) 44 | .map(|p| dunce::canonicalize(&p).expect(&format!("missing compile file {:?}", p))) 45 | .collect(); 46 | 47 | cxx_build::CFG 48 | .exported_header_dirs 49 | .extend(includes.iter().map(|e| e.as_path())); 50 | 51 | let bridges = vec!["src/ffi.rs"]; 52 | 53 | for bridge in &bridges { 54 | println!("cargo:rerun-if-changed={}", bridge); 55 | } 56 | 57 | cxx_build::bridges(bridges) 58 | .flag_if_supported("-std=c++17") 59 | .files(compiles) 60 | .compile("js-tests"); 61 | 62 | println!("cargo:rustc-link-lib=hermes"); 63 | println!( 64 | "cargo:rustc-link-search={}", 65 | pkg_base 66 | .join("../vendor/hermes/build/API/hermes/") 67 | .to_string_lossy() 68 | ); 69 | println!( 70 | "cargo:rustc-env=LD_LIBRARY_PATH={}", 71 | pkg_base 72 | .join("../vendor/hermes/build/API/hermes/") 73 | .to_string_lossy() 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /jsi-tests/include/helper.h: -------------------------------------------------------------------------------- 1 | #include 2 | // #include 3 | #include 4 | #include 5 | #include "rust/cxx.h" 6 | 7 | std::unique_ptr cast_hermes_runtime(std::unique_ptr runtime) 8 | { 9 | return runtime; 10 | } 11 | 12 | std::unique_ptr create_runtime_config() 13 | { 14 | return std::make_unique(); 15 | } 16 | 17 | std::unique_ptr eval_js(facebook::jsi::Runtime& rt, rust::Str js) 18 | { 19 | // std::string bytecode; 20 | // assert(hermes::compileJS(std::string(js), bytecode)); 21 | auto out = rt.evaluateJavaScript( 22 | std::make_unique(std::string(js)), 23 | ""); 24 | return std::make_unique(std::move(out)); 25 | } 26 | -------------------------------------------------------------------------------- /jsi-tests/src/ffi.rs: -------------------------------------------------------------------------------- 1 | #[cxx::bridge] 2 | pub mod bridge { 3 | unsafe extern "C++" { 4 | include!("helper.h"); 5 | include!("jsi/jsi.h"); 6 | 7 | #[namespace = "facebook::jsi"] 8 | type Runtime = jsi_sys::Runtime; 9 | #[namespace = "facebook::jsi"] 10 | #[cxx_name = "Value"] 11 | type JsiValue = jsi_sys::JsiValue; 12 | 13 | pub fn cast_hermes_runtime(ptr: UniquePtr) -> UniquePtr; 14 | pub fn create_runtime_config() -> UniquePtr; 15 | pub fn eval_js(rt: Pin<&mut Runtime>, js: &str) -> UniquePtr; 16 | } 17 | 18 | #[namespace = "hermes::vm"] 19 | extern "C++" { 20 | include!("hermes/Public/RuntimeConfig.h"); 21 | 22 | type RuntimeConfig; 23 | } 24 | 25 | #[namespace = "facebook::hermes"] 26 | unsafe extern "C++" { 27 | include!("hermes/hermes.h"); 28 | 29 | type HermesRuntime; 30 | 31 | #[cxx_name = "makeHermesRuntime"] 32 | pub fn create_hermes_runtime(config: &RuntimeConfig) -> UniquePtr; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /jsi-tests/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod ffi; 2 | -------------------------------------------------------------------------------- /jsi-tests/tests/basic.rs: -------------------------------------------------------------------------------- 1 | use cxx::UniquePtr; 2 | 3 | use jsi_tests::ffi::bridge::*; 4 | 5 | #[test] 6 | fn create_runtime() { 7 | let config = create_runtime_config(); 8 | let rt = create_hermes_runtime(&*config); 9 | let mut rt: UniquePtr = cast_hermes_runtime(rt); 10 | let rt = rt.as_mut().unwrap(); 11 | assert_eq!("HermesRuntime", rt.description().to_string()); 12 | } 13 | 14 | #[test] 15 | fn evaluate_exprs() { 16 | let config = create_runtime_config(); 17 | let rt = create_hermes_runtime(&*config); 18 | let mut rt: UniquePtr = cast_hermes_runtime(rt); 19 | 20 | let out = eval_js(rt.pin_mut(), "1 + 1"); 21 | let out = out.get_number().unwrap(); 22 | assert_eq!(2., out); 23 | 24 | let out = eval_js(rt.pin_mut(), "'hello'"); 25 | let out = out.as_string(rt.pin_mut()).unwrap(); 26 | assert_eq!("hello", out.to_string(rt.pin_mut()).to_string()); 27 | } 28 | -------------------------------------------------------------------------------- /jsi-tests/tests/common.rs: -------------------------------------------------------------------------------- 1 | use cxx::UniquePtr; 2 | 3 | use jsi_tests::ffi::bridge::*; 4 | 5 | pub fn create_raw_runtime() -> UniquePtr { 6 | let config = create_runtime_config(); 7 | let rt = create_hermes_runtime(&*config); 8 | cast_hermes_runtime(rt) 9 | } 10 | -------------------------------------------------------------------------------- /jsi-tests/tests/function.rs: -------------------------------------------------------------------------------- 1 | use cxx::UniquePtr; 2 | 3 | use jsi_tests::ffi::bridge::*; 4 | 5 | #[test] 6 | fn run_host_function() { 7 | let config = create_runtime_config(); 8 | let rt = create_hermes_runtime(&*config); 9 | let mut rt: UniquePtr = cast_hermes_runtime(rt); 10 | let rt = rt.as_mut().unwrap(); 11 | assert_eq!("HermesRuntime", rt.description().to_string()); 12 | } 13 | 14 | #[test] 15 | fn evaluate_exprs() { 16 | let config = create_runtime_config(); 17 | let rt = create_hermes_runtime(&*config); 18 | let mut rt: UniquePtr = cast_hermes_runtime(rt); 19 | 20 | let out = eval_js(rt.pin_mut(), "1 + 1"); 21 | let out = out.get_number().unwrap(); 22 | assert_eq!(2., out); 23 | 24 | let out = eval_js(rt.pin_mut(), "'hello'"); 25 | let out = out.as_string(rt.pin_mut()).unwrap(); 26 | assert_eq!("hello", out.to_string(rt.pin_mut()).to_string()); 27 | } 28 | -------------------------------------------------------------------------------- /jsi/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.75" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" 10 | 11 | [[package]] 12 | name = "better_any" 13 | version = "0.1.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "b359aebd937c17c725e19efcb661200883f04c49c53e7132224dac26da39d4a0" 16 | dependencies = [ 17 | "better_typeid_derive", 18 | ] 19 | 20 | [[package]] 21 | name = "better_typeid_derive" 22 | version = "0.1.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "3deeecb812ca5300b7d3f66f730cc2ebd3511c3d36c691dd79c165d5b19a26e3" 25 | dependencies = [ 26 | "proc-macro2", 27 | "quote", 28 | "syn 1.0.109", 29 | ] 30 | 31 | [[package]] 32 | name = "cc" 33 | version = "1.0.83" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 36 | dependencies = [ 37 | "libc", 38 | ] 39 | 40 | [[package]] 41 | name = "codespan-reporting" 42 | version = "0.11.1" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" 45 | dependencies = [ 46 | "termcolor", 47 | "unicode-width", 48 | ] 49 | 50 | [[package]] 51 | name = "cxx" 52 | version = "1.0.106" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "28403c86fc49e3401fdf45499ba37fad6493d9329449d6449d7f0e10f4654d28" 55 | dependencies = [ 56 | "cc", 57 | "cxxbridge-flags", 58 | "cxxbridge-macro", 59 | "link-cplusplus", 60 | ] 61 | 62 | [[package]] 63 | name = "cxx-build" 64 | version = "1.0.106" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "78da94fef01786dc3e0c76eafcd187abcaa9972c78e05ff4041e24fdf059c285" 67 | dependencies = [ 68 | "cc", 69 | "codespan-reporting", 70 | "once_cell", 71 | "proc-macro2", 72 | "quote", 73 | "scratch", 74 | "syn 2.0.29", 75 | ] 76 | 77 | [[package]] 78 | name = "cxxbridge-flags" 79 | version = "1.0.106" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "e2a6f5e1dfb4b34292ad4ea1facbfdaa1824705b231610087b00b17008641809" 82 | 83 | [[package]] 84 | name = "cxxbridge-macro" 85 | version = "1.0.106" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "50c49547d73ba8dcfd4ad7325d64c6d5391ff4224d498fc39a6f3f49825a530d" 88 | dependencies = [ 89 | "proc-macro2", 90 | "quote", 91 | "syn 2.0.29", 92 | ] 93 | 94 | [[package]] 95 | name = "dunce" 96 | version = "1.0.4" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" 99 | 100 | [[package]] 101 | name = "libc" 102 | version = "0.2.147" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" 105 | 106 | [[package]] 107 | name = "link-cplusplus" 108 | version = "1.0.9" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" 111 | dependencies = [ 112 | "cc", 113 | ] 114 | 115 | [[package]] 116 | name = "log" 117 | version = "0.4.20" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 120 | 121 | [[package]] 122 | name = "once_cell" 123 | version = "1.18.0" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 126 | 127 | [[package]] 128 | name = "proc-macro2" 129 | version = "1.0.66" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" 132 | dependencies = [ 133 | "unicode-ident", 134 | ] 135 | 136 | [[package]] 137 | name = "quote" 138 | version = "1.0.33" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 141 | dependencies = [ 142 | "proc-macro2", 143 | ] 144 | 145 | [[package]] 146 | name = "scratch" 147 | version = "1.0.7" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" 150 | 151 | [[package]] 152 | name = "splicer-jsi" 153 | version = "0.2.0" 154 | dependencies = [ 155 | "anyhow", 156 | "better_any", 157 | "cxx", 158 | "log", 159 | "splicer-jsi-sys", 160 | ] 161 | 162 | [[package]] 163 | name = "splicer-jsi-sys" 164 | version = "0.1.0" 165 | dependencies = [ 166 | "anyhow", 167 | "cxx", 168 | "cxx-build", 169 | "dunce", 170 | ] 171 | 172 | [[package]] 173 | name = "syn" 174 | version = "1.0.109" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 177 | dependencies = [ 178 | "proc-macro2", 179 | "quote", 180 | "unicode-ident", 181 | ] 182 | 183 | [[package]] 184 | name = "syn" 185 | version = "2.0.29" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" 188 | dependencies = [ 189 | "proc-macro2", 190 | "quote", 191 | "unicode-ident", 192 | ] 193 | 194 | [[package]] 195 | name = "termcolor" 196 | version = "1.2.0" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" 199 | dependencies = [ 200 | "winapi-util", 201 | ] 202 | 203 | [[package]] 204 | name = "unicode-ident" 205 | version = "1.0.11" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 208 | 209 | [[package]] 210 | name = "unicode-width" 211 | version = "0.1.10" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 214 | 215 | [[package]] 216 | name = "winapi" 217 | version = "0.3.9" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 220 | dependencies = [ 221 | "winapi-i686-pc-windows-gnu", 222 | "winapi-x86_64-pc-windows-gnu", 223 | ] 224 | 225 | [[package]] 226 | name = "winapi-i686-pc-windows-gnu" 227 | version = "0.4.0" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 230 | 231 | [[package]] 232 | name = "winapi-util" 233 | version = "0.1.5" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 236 | dependencies = [ 237 | "winapi", 238 | ] 239 | 240 | [[package]] 241 | name = "winapi-x86_64-pc-windows-gnu" 242 | version = "0.4.0" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 245 | -------------------------------------------------------------------------------- /jsi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsi" 3 | version = "0.3.0-alpha.5" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "Write React Native JSI modules in Rust" 7 | authors = ["Ibiyemi Abiodun "] 8 | repository = "https://github.com/laptou/jsi-rs/" 9 | 10 | [dependencies] 11 | jsi-sys = { path = "../jsi-sys", version = "0.3.0-alpha.5" } 12 | jsi-macros = { path = "../jsi-macros", version = "0.3.1-alpha.5", optional = true } 13 | cxx = "1.0" 14 | anyhow = "1.0" 15 | log = { version = "0.4", optional = true } 16 | better_any = "0.2" 17 | serde = { version = "1.0", optional = true } 18 | thiserror = "1.0.47" 19 | 20 | [features] 21 | default = ["macros", "serde"] 22 | host-fn-trace = ["log"] 23 | js-fn-trace = ["log"] 24 | call-invoker-trace = ["log"] 25 | macros = ["jsi-macros"] 26 | serde = ["dep:serde"] 27 | -------------------------------------------------------------------------------- /jsi/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /jsi/src/array.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use crate::{sys, JsiValue, RuntimeHandle}; 4 | 5 | /// A JavaScript 6 | /// [`Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array). 7 | /// Can be used to share large buffers of data with a React Native application. 8 | pub struct JsiArray<'rt>( 9 | pub(crate) cxx::UniquePtr, 10 | pub(crate) PhantomData<&'rt mut ()>, 11 | ); 12 | 13 | impl<'rt> JsiArray<'rt> { 14 | pub fn new(len: usize, rt: &mut RuntimeHandle<'rt>) -> Self { 15 | Self( 16 | sys::Array_createWithLength(rt.get_inner_mut(), len), 17 | PhantomData, 18 | ) 19 | } 20 | 21 | pub fn len(&self, rt: &mut RuntimeHandle<'rt>) -> usize { 22 | self.0.length(rt.get_inner_mut()) 23 | } 24 | 25 | pub fn get(&self, index: usize, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 26 | JsiValue( 27 | sys::Array_get(&*self.0, rt.get_inner_mut(), index), 28 | PhantomData, 29 | ) 30 | } 31 | 32 | pub fn set(&mut self, index: usize, value: &JsiValue<'rt>, rt: &mut RuntimeHandle<'rt>) { 33 | sys::Array_set( 34 | self.0.pin_mut(), 35 | rt.get_inner_mut(), 36 | index, 37 | value.0.as_ref().unwrap(), 38 | ) 39 | } 40 | 41 | pub fn iter<'a>(&'a self, rt: &'a mut RuntimeHandle<'rt>) -> JsiArrayIter<'a, 'rt> { 42 | JsiArrayIter(self, 0, rt) 43 | } 44 | } 45 | 46 | pub struct JsiArrayIter<'a, 'rt: 'a>(&'a JsiArray<'rt>, usize, &'a mut RuntimeHandle<'rt>); 47 | 48 | impl<'a, 'rt: 'a> Iterator for JsiArrayIter<'a, 'rt> { 49 | type Item = JsiValue<'rt>; 50 | 51 | fn next(&mut self) -> Option { 52 | if self.1 < self.0.len(self.2) { 53 | let val = self.0.get(self.1, self.2); 54 | self.1 += 1; 55 | Some(val) 56 | } else { 57 | None 58 | } 59 | } 60 | } 61 | 62 | // impl<'a, 'rt: 'a> ExactSizeIterator for JsiArrayIter<'a, 'rt> { 63 | // fn len(&self) -> usize { 64 | // self.0.len(self.2) 65 | // } 66 | // } 67 | -------------------------------------------------------------------------------- /jsi/src/array_buffer.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use crate::{sys, RuntimeHandle}; 4 | 5 | /// A JavaScript 6 | /// [`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer). 7 | /// Can be used to share large buffers of data with a React Native application. 8 | pub struct JsiArrayBuffer<'rt>( 9 | pub(crate) cxx::UniquePtr, 10 | pub(crate) PhantomData<&'rt mut ()>, 11 | ); 12 | 13 | impl<'rt> JsiArrayBuffer<'rt> { 14 | pub fn data(&self, rt: &mut RuntimeHandle<'rt>) -> &mut [u8] { 15 | unsafe { 16 | std::slice::from_raw_parts_mut( 17 | self.0.data(rt.get_inner_mut()), 18 | self.0.length(rt.get_inner_mut()), 19 | ) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /jsi/src/call_invoker.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | pub use sys::CallInvokerCallback; 4 | 5 | use crate::sys; 6 | 7 | /// Used to run JavaScript functions in a given runtime from Rust. Required when 8 | /// trying to call a JavaScript function from a thread other than the JS thread. 9 | #[derive(Clone)] 10 | pub struct CallInvoker<'rt>( 11 | pub(crate) cxx::SharedPtr, 12 | pub(crate) PhantomData<&'rt ()>, 13 | ); 14 | 15 | unsafe impl Send for CallInvoker<'_> {} 16 | unsafe impl Sync for CallInvoker<'_> {} 17 | 18 | impl<'rt> CallInvoker<'rt> { 19 | pub fn new(ptr: cxx::SharedPtr) -> Self { 20 | CallInvoker(ptr, PhantomData) 21 | } 22 | 23 | /// WARNING: currently crashes with message "Synchronous native -> JS calls are currently not supported" 24 | pub fn invoke_sync(&self, job: CallInvokerCallback<'rt>) { 25 | #[cfg(feature = "call-invoker-trace")] 26 | log::trace!("call invoker sync call with closure at {:p}", job); 27 | 28 | unsafe { 29 | sys::CallInvoker_invokeSync(self.0.clone(), Box::into_raw(Box::new(job)) as *mut _) 30 | } 31 | } 32 | 33 | pub fn invoke_async(&self, job: CallInvokerCallback<'rt>) { 34 | #[cfg(feature = "call-invoker-trace")] 35 | log::trace!("call invoker async call with closure at {:p}", job); 36 | 37 | unsafe { 38 | sys::CallInvoker_invokeAsync(self.0.clone(), Box::into_raw(Box::new(job)) as *mut _) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /jsi/src/convert/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod de; 2 | pub mod ser; 3 | 4 | pub use de::JsiDeserializer; 5 | pub use ser::JsiSerializer; 6 | 7 | use jsi::{JsiValue, RuntimeHandle}; 8 | use serde::{Deserialize, Serialize}; 9 | 10 | pub trait SerializeValue { 11 | fn serialize_value<'rt>( 12 | &self, 13 | rt: &mut RuntimeHandle<'rt>, 14 | ) -> Result, ser::JsiSerializeError>; 15 | } 16 | 17 | impl SerializeValue for T { 18 | fn serialize_value<'rt>( 19 | &self, 20 | rt: &mut RuntimeHandle<'rt>, 21 | ) -> Result, ser::JsiSerializeError> { 22 | T::serialize(&self, JsiSerializer::new(rt)) 23 | } 24 | } 25 | 26 | pub trait DeserializeValue: Sized { 27 | fn deserialize_value<'rt>( 28 | val: JsiValue<'rt>, 29 | rt: &mut RuntimeHandle<'rt>, 30 | ) -> Result; 31 | } 32 | 33 | impl<'de, T: Deserialize<'de>> DeserializeValue for T { 34 | fn deserialize_value<'rt>( 35 | val: JsiValue<'rt>, 36 | rt: &mut RuntimeHandle<'rt>, 37 | ) -> Result { 38 | Self::deserialize(JsiDeserializer::new(val, rt)) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /jsi/src/convert/ser.rs: -------------------------------------------------------------------------------- 1 | use jsi::*; 2 | use serde::{ 3 | ser::{ 4 | SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, SerializeTuple, 5 | SerializeTupleStruct, SerializeTupleVariant, 6 | }, 7 | Serializer, 8 | }; 9 | 10 | /// Serializes objects into JavaScript via JSI. Useful for transferring Rust 11 | /// structures and objects from `serde_json` into JavaScript. 12 | pub struct JsiSerializer<'a, 'rt: 'a> { 13 | rt: &'a mut RuntimeHandle<'rt>, 14 | } 15 | 16 | impl<'a, 'rt: 'a> JsiSerializer<'a, 'rt> { 17 | pub fn new(rt: &'a mut RuntimeHandle<'rt>) -> Self { 18 | Self { rt } 19 | } 20 | } 21 | 22 | impl<'a, 'rt: 'a> Serializer for JsiSerializer<'a, 'rt> { 23 | type Ok = JsiValue<'rt>; 24 | type Error = JsiSerializeError; 25 | 26 | type SerializeSeq = JsiSeqSerializer<'a, 'rt>; 27 | type SerializeTuple = JsiTupleSerializer<'a, 'rt>; 28 | type SerializeTupleStruct = JsiTupleVariantSerializer<'a, 'rt>; 29 | type SerializeTupleVariant = JsiTupleVariantSerializer<'a, 'rt>; 30 | type SerializeMap = JsiMapSerializer<'a, 'rt>; 31 | type SerializeStruct = JsiStructSerializer<'a, 'rt>; 32 | type SerializeStructVariant = JsiStructVariantSerializer<'a, 'rt>; 33 | 34 | fn is_human_readable(&self) -> bool { 35 | true 36 | } 37 | 38 | fn serialize_bool(self, v: bool) -> Result { 39 | Ok(JsiValue::new_bool(v)) 40 | } 41 | 42 | fn serialize_i8(self, v: i8) -> Result { 43 | Ok(JsiValue::new_number(v as f64)) 44 | } 45 | 46 | fn serialize_i16(self, v: i16) -> Result { 47 | Ok(JsiValue::new_number(v as f64)) 48 | } 49 | 50 | fn serialize_i32(self, v: i32) -> Result { 51 | Ok(JsiValue::new_number(v as f64)) 52 | } 53 | 54 | fn serialize_i64(self, v: i64) -> Result { 55 | Ok(JsiValue::new_number(v as f64)) 56 | } 57 | 58 | fn serialize_u8(self, v: u8) -> Result { 59 | Ok(JsiValue::new_number(v as f64)) 60 | } 61 | 62 | fn serialize_u16(self, v: u16) -> Result { 63 | Ok(JsiValue::new_number(v as f64)) 64 | } 65 | 66 | fn serialize_u32(self, v: u32) -> Result { 67 | Ok(JsiValue::new_number(v as f64)) 68 | } 69 | 70 | fn serialize_u64(self, v: u64) -> Result { 71 | Ok(JsiValue::new_number(v as f64)) 72 | } 73 | 74 | fn serialize_f32(self, v: f32) -> Result { 75 | Ok(JsiValue::new_number(v as f64)) 76 | } 77 | 78 | fn serialize_f64(self, v: f64) -> Result { 79 | Ok(JsiValue::new_number(v)) 80 | } 81 | 82 | fn serialize_char(self, v: char) -> Result { 83 | Ok(JsiString::new(v.encode_utf8(&mut [0; 4]), self.rt).into_value(self.rt)) 84 | } 85 | 86 | fn serialize_str(self, v: &str) -> Result { 87 | Ok(JsiString::new(v, self.rt).into_value(self.rt)) 88 | } 89 | 90 | fn serialize_bytes(mut self, v: &[u8]) -> Result { 91 | let rt = &mut self.rt; 92 | let array_buffer_ctor = rt.global().get(PropName::new("ArrayBuffer", rt), rt); 93 | let array_buffer_ctor: JsiFn = array_buffer_ctor 94 | .try_into_js(rt) 95 | .expect("ArrayBuffer constructor is not a function"); 96 | let array_buffer = array_buffer_ctor 97 | .call_as_constructor(vec![JsiValue::new_number(v.len() as f64)], rt) 98 | .expect("ArrayBuffer constructor threw an exception"); 99 | let array_buffer: JsiArrayBuffer = array_buffer 100 | .try_into_js(rt) 101 | .expect("ArrayBuffer constructor did not return an ArrayBuffer"); 102 | 103 | array_buffer.data(rt).copy_from_slice(v); 104 | 105 | Ok(array_buffer.into_value(rt)) 106 | } 107 | 108 | fn serialize_none(self) -> Result { 109 | Ok(JsiValue::new_null()) 110 | } 111 | 112 | fn serialize_some(self, value: &T) -> Result 113 | where 114 | T: serde::Serialize, 115 | { 116 | value.serialize(self) 117 | } 118 | 119 | fn serialize_unit(self) -> Result { 120 | Ok(JsiObject::new(self.rt).into_value(self.rt)) 121 | } 122 | 123 | fn serialize_unit_struct(self, _name: &'static str) -> Result { 124 | self.serialize_unit() 125 | } 126 | 127 | fn serialize_unit_variant( 128 | self, 129 | _name: &'static str, 130 | _variant_index: u32, 131 | variant: &'static str, 132 | ) -> Result { 133 | self.serialize_str(variant) 134 | } 135 | 136 | fn serialize_newtype_struct( 137 | self, 138 | _name: &'static str, 139 | value: &T, 140 | ) -> Result 141 | where 142 | T: serde::Serialize, 143 | { 144 | value.serialize(self) 145 | } 146 | 147 | fn serialize_newtype_variant( 148 | self, 149 | _name: &'static str, 150 | _variant_index: u32, 151 | _variant: &'static str, 152 | value: &T, 153 | ) -> Result 154 | where 155 | T: serde::Serialize, 156 | { 157 | value.serialize(self) 158 | } 159 | 160 | fn serialize_seq(self, len: Option) -> Result { 161 | match len { 162 | Some(len) => Ok(JsiSeqSerializer::new(len, self.rt)), 163 | None => Err(JsiSerializeError::UnsizedSequence), 164 | } 165 | } 166 | 167 | fn serialize_tuple(self, _len: usize) -> Result { 168 | Ok(JsiTupleSerializer::new(self.rt)) 169 | } 170 | 171 | fn serialize_tuple_struct( 172 | self, 173 | name: &'static str, 174 | _len: usize, 175 | ) -> Result { 176 | Ok(JsiTupleVariantSerializer::new(name, self.rt)) 177 | } 178 | 179 | fn serialize_tuple_variant( 180 | self, 181 | _name: &'static str, 182 | _variant_index: u32, 183 | variant: &'static str, 184 | _len: usize, 185 | ) -> Result { 186 | Ok(JsiTupleVariantSerializer::new(variant, self.rt)) 187 | } 188 | 189 | fn serialize_map(self, _len: Option) -> Result { 190 | Ok(JsiMapSerializer::new(self.rt)) 191 | } 192 | 193 | fn serialize_struct( 194 | self, 195 | _name: &'static str, 196 | _len: usize, 197 | ) -> Result { 198 | Ok(JsiStructSerializer::new(self.rt)) 199 | } 200 | 201 | fn serialize_struct_variant( 202 | self, 203 | _name: &'static str, 204 | _variant_index: u32, 205 | variant: &'static str, 206 | _len: usize, 207 | ) -> Result { 208 | Ok(JsiStructVariantSerializer::new(variant, self.rt)) 209 | } 210 | } 211 | 212 | pub struct JsiSeqSerializer<'a, 'rt: 'a> { 213 | rt: &'a mut RuntimeHandle<'rt>, 214 | arr: JsiArray<'rt>, 215 | idx: usize, 216 | } 217 | 218 | impl<'a, 'rt: 'a> JsiSeqSerializer<'a, 'rt> { 219 | pub fn new(len: usize, rt: &'a mut RuntimeHandle<'rt>) -> Self { 220 | let arr = JsiArray::new(len, rt); 221 | Self { rt, arr, idx: 0 } 222 | } 223 | } 224 | 225 | impl<'a, 'rt: 'a> SerializeSeq for JsiSeqSerializer<'a, 'rt> { 226 | type Ok = JsiValue<'rt>; 227 | type Error = JsiSerializeError; 228 | 229 | fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> 230 | where 231 | T: serde::Serialize, 232 | { 233 | self.arr.set( 234 | self.idx, 235 | &value.serialize(JsiSerializer { rt: self.rt })?, 236 | self.rt, 237 | ); 238 | self.idx += 1; 239 | 240 | Ok(()) 241 | } 242 | 243 | fn end(self) -> Result { 244 | Ok(self.arr.into_value(self.rt)) 245 | } 246 | } 247 | 248 | pub struct JsiTupleSerializer<'a, 'rt: 'a> { 249 | rt: &'a mut RuntimeHandle<'rt>, 250 | obj: JsiObject<'rt>, 251 | idx: usize, 252 | } 253 | 254 | impl<'a, 'rt: 'a> JsiTupleSerializer<'a, 'rt> { 255 | pub fn new(rt: &'a mut RuntimeHandle<'rt>) -> Self { 256 | let obj = JsiObject::new(rt); 257 | Self { rt, obj, idx: 0 } 258 | } 259 | } 260 | 261 | impl<'a, 'rt: 'a> SerializeTuple for JsiTupleSerializer<'a, 'rt> { 262 | type Ok = JsiValue<'rt>; 263 | type Error = JsiSerializeError; 264 | 265 | fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> 266 | where 267 | T: serde::Serialize, 268 | { 269 | self.obj.set( 270 | PropName::new(self.idx.to_string().as_str(), self.rt), 271 | &value.serialize(JsiSerializer { rt: self.rt })?, 272 | self.rt, 273 | ); 274 | self.idx += 1; 275 | 276 | Ok(()) 277 | } 278 | 279 | fn end(self) -> Result { 280 | Ok(self.obj.into_value(self.rt)) 281 | } 282 | } 283 | 284 | pub struct JsiTupleVariantSerializer<'a, 'rt: 'a> { 285 | inner: JsiTupleSerializer<'a, 'rt>, 286 | variant: &'static str, 287 | } 288 | 289 | impl<'a, 'rt: 'a> JsiTupleVariantSerializer<'a, 'rt> { 290 | pub fn new(variant: &'static str, rt: &'a mut RuntimeHandle<'rt>) -> Self { 291 | Self { 292 | inner: JsiTupleSerializer::new(rt), 293 | variant, 294 | } 295 | } 296 | } 297 | 298 | impl<'a, 'rt: 'a> SerializeTupleVariant for JsiTupleVariantSerializer<'a, 'rt> { 299 | type Ok = JsiValue<'rt>; 300 | type Error = JsiSerializeError; 301 | 302 | fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> 303 | where 304 | T: serde::Serialize, 305 | { 306 | self.inner.serialize_element(value) 307 | } 308 | 309 | fn end(mut self) -> Result { 310 | self.inner.obj.set( 311 | PropName::new("__variant", self.inner.rt), 312 | &JsiValue::new_string(self.variant, self.inner.rt), 313 | self.inner.rt, 314 | ); 315 | self.inner.end() 316 | } 317 | } 318 | 319 | impl<'a, 'rt: 'a> SerializeTupleStruct for JsiTupleVariantSerializer<'a, 'rt> { 320 | type Ok = JsiValue<'rt>; 321 | type Error = JsiSerializeError; 322 | 323 | fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> 324 | where 325 | T: serde::Serialize, 326 | { 327 | self.inner.serialize_element(value) 328 | } 329 | 330 | fn end(mut self) -> Result { 331 | self.inner.obj.set( 332 | PropName::new("__name", self.inner.rt), 333 | &JsiValue::new_string(self.variant, self.inner.rt), 334 | self.inner.rt, 335 | ); 336 | self.inner.end() 337 | } 338 | } 339 | 340 | pub struct JsiMapSerializer<'a, 'rt: 'a> { 341 | rt: &'a mut RuntimeHandle<'rt>, 342 | map: JsiObject<'rt>, 343 | setter: JsiFn<'rt>, 344 | current_key: Option>, 345 | } 346 | 347 | impl<'a, 'rt: 'a> JsiMapSerializer<'a, 'rt> { 348 | pub fn new(rt: &'a mut RuntimeHandle<'rt>) -> Self { 349 | let map_ctor = rt.global().get(PropName::new("Map", rt), rt); 350 | let map_ctor: JsiFn = map_ctor 351 | .try_into_js(rt) 352 | .expect("Map constructor is not a function"); 353 | let map = map_ctor 354 | .call_as_constructor(vec![], rt) 355 | .expect("Map constructor threw an exception"); 356 | let map: JsiObject = map 357 | .try_into_js(rt) 358 | .expect("Map constructor did not return an object"); 359 | let map_setter = map.get(PropName::new("set", rt), rt); 360 | let map_setter: JsiFn = map_setter 361 | .try_into_js(rt) 362 | .expect("Map.set is not an function"); 363 | 364 | Self { 365 | rt, 366 | map, 367 | setter: map_setter, 368 | current_key: None, 369 | } 370 | } 371 | } 372 | 373 | impl<'a, 'rt: 'a> SerializeMap for JsiMapSerializer<'a, 'rt> { 374 | type Ok = JsiValue<'rt>; 375 | type Error = JsiSerializeError; 376 | 377 | fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> 378 | where 379 | T: serde::Serialize, 380 | { 381 | let ser = JsiSerializer { rt: self.rt }; 382 | let key = key.serialize(ser)?; 383 | self.current_key = Some(key); 384 | Ok(()) 385 | } 386 | 387 | fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> 388 | where 389 | T: serde::Serialize, 390 | { 391 | let value = value.serialize(JsiSerializer { rt: self.rt })?; 392 | let key = self 393 | .current_key 394 | .take() 395 | .expect("tried to serialize value without serializing key first"); 396 | 397 | self.setter 398 | .call_with_this(&self.map, vec![key, value], self.rt) 399 | .expect("Map.set threw an exception"); 400 | 401 | Ok(()) 402 | } 403 | 404 | fn end(self) -> Result { 405 | Ok(self.map.into_value(self.rt)) 406 | } 407 | } 408 | 409 | pub struct JsiStructSerializer<'a, 'rt: 'a> { 410 | rt: &'a mut RuntimeHandle<'rt>, 411 | obj: JsiObject<'rt>, 412 | } 413 | 414 | impl<'a, 'rt: 'a> JsiStructSerializer<'a, 'rt> { 415 | pub fn new(rt: &'a mut RuntimeHandle<'rt>) -> Self { 416 | let obj = JsiObject::new(rt); 417 | Self { rt, obj } 418 | } 419 | } 420 | 421 | impl<'a, 'rt: 'a> SerializeStruct for JsiStructSerializer<'a, 'rt> { 422 | type Ok = JsiValue<'rt>; 423 | type Error = JsiSerializeError; 424 | 425 | fn serialize_field( 426 | &mut self, 427 | key: &'static str, 428 | value: &T, 429 | ) -> Result<(), Self::Error> 430 | where 431 | T: serde::Serialize, 432 | { 433 | self.obj.set( 434 | PropName::new(key, self.rt), 435 | &value.serialize(JsiSerializer { rt: self.rt })?, 436 | self.rt, 437 | ); 438 | Ok(()) 439 | } 440 | 441 | fn end(self) -> Result { 442 | Ok(self.obj.into_value(self.rt)) 443 | } 444 | } 445 | 446 | pub struct JsiStructVariantSerializer<'a, 'rt: 'a> { 447 | inner: JsiStructSerializer<'a, 'rt>, 448 | variant: &'static str, 449 | } 450 | 451 | impl<'a, 'rt: 'a> JsiStructVariantSerializer<'a, 'rt> { 452 | pub fn new(variant: &'static str, rt: &'a mut RuntimeHandle<'rt>) -> Self { 453 | Self { 454 | inner: JsiStructSerializer::new(rt), 455 | variant, 456 | } 457 | } 458 | } 459 | 460 | impl<'a, 'rt: 'a> SerializeStructVariant for JsiStructVariantSerializer<'a, 'rt> { 461 | type Ok = JsiValue<'rt>; 462 | type Error = JsiSerializeError; 463 | 464 | fn serialize_field( 465 | &mut self, 466 | key: &'static str, 467 | value: &T, 468 | ) -> Result<(), Self::Error> 469 | where 470 | T: serde::Serialize, 471 | { 472 | self.inner.serialize_field(key, value) 473 | } 474 | 475 | fn end(mut self) -> Result { 476 | self.inner.obj.set( 477 | PropName::new("__variant", self.inner.rt), 478 | &JsiValue::new_string(self.variant, self.inner.rt), 479 | self.inner.rt, 480 | ); 481 | self.inner.end() 482 | } 483 | } 484 | 485 | #[derive(Debug)] 486 | pub enum JsiSerializeError { 487 | Custom(String), 488 | UnsizedSequence, 489 | } 490 | 491 | impl std::fmt::Display for JsiSerializeError { 492 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 493 | match self { 494 | JsiSerializeError::Custom(s) => f.write_str(s.as_str()), 495 | JsiSerializeError::UnsizedSequence => f.write_str("cannot serialize unsized sequences"), 496 | } 497 | } 498 | } 499 | 500 | impl serde::ser::Error for JsiSerializeError { 501 | fn custom(msg: T) -> Self 502 | where 503 | T: std::fmt::Display, 504 | { 505 | Self::Custom(msg.to_string()) 506 | } 507 | } 508 | 509 | impl std::error::Error for JsiSerializeError {} 510 | -------------------------------------------------------------------------------- /jsi/src/function.rs: -------------------------------------------------------------------------------- 1 | use crate::host_function::UserHostFunction; 2 | use crate::{sys, IntoValue, JsiObject, JsiValue, PropName, RuntimeHandle}; 3 | use anyhow::{bail, Context}; 4 | use std::marker::PhantomData; 5 | use std::pin::Pin; 6 | 7 | /// A JavaScript function. 8 | pub struct JsiFn<'rt>( 9 | pub(crate) cxx::UniquePtr, 10 | pub(crate) PhantomData<&'rt mut ()>, 11 | ); 12 | 13 | impl<'rt> JsiFn<'rt> { 14 | pub fn call>>( 15 | &self, 16 | args: T, 17 | rt: &mut RuntimeHandle<'rt>, 18 | ) -> Result, cxx::Exception> { 19 | let mut args_cxx = sys::create_value_vector(); 20 | for arg in args { 21 | sys::push_value_vector(args_cxx.pin_mut(), arg.0); 22 | } 23 | 24 | Ok(JsiValue( 25 | sys::Function_call( 26 | self.0.as_ref().unwrap(), 27 | rt.get_inner_mut(), 28 | args_cxx.as_ref().unwrap(), 29 | )?, 30 | PhantomData, 31 | )) 32 | } 33 | 34 | pub fn call_as_constructor>>( 35 | &self, 36 | args: T, 37 | rt: &mut RuntimeHandle<'rt>, 38 | ) -> Result, cxx::Exception> { 39 | let mut args_cxx = sys::create_value_vector(); 40 | for arg in args { 41 | sys::push_value_vector(args_cxx.pin_mut(), arg.0); 42 | } 43 | 44 | Ok(JsiValue( 45 | sys::Function_callAsConstructor( 46 | self.0.as_ref().unwrap(), 47 | rt.get_inner_mut(), 48 | args_cxx.as_ref().unwrap(), 49 | )?, 50 | PhantomData, 51 | )) 52 | } 53 | 54 | pub fn call_with_this<'a, 'ret, T: IntoIterator>>( 55 | &self, 56 | this: &JsiObject, 57 | args: T, 58 | rt: &mut RuntimeHandle<'rt>, 59 | ) -> Result, cxx::Exception> 60 | where 61 | 'rt: 'a, 62 | 'rt: 'ret, 63 | { 64 | let mut args_cxx = sys::create_value_vector(); 65 | for arg in args { 66 | sys::push_value_vector(args_cxx.pin_mut(), arg.0); 67 | } 68 | 69 | Ok(JsiValue( 70 | sys::Function_callWithThis( 71 | self.0.as_ref().unwrap(), 72 | rt.get_inner_mut(), 73 | this.0.as_ref().unwrap(), 74 | args_cxx.as_ref().unwrap(), 75 | )?, 76 | PhantomData, 77 | )) 78 | } 79 | 80 | pub fn from_host_fn( 81 | name: &PropName, 82 | param_count: usize, 83 | mut body: Box>, 84 | rt: &mut RuntimeHandle<'rt>, 85 | ) -> Self { 86 | #[cfg(feature = "host-fn-trace")] 87 | log::trace!( 88 | "creating host fn {} with closure at {:p}", 89 | rt.display(name), 90 | body 91 | ); 92 | 93 | let cb: sys::HostFunctionCallback = 94 | Box::new(move |rt: Pin<&mut sys::Runtime>, this, args| { 95 | let mut rt: RuntimeHandle = 96 | unsafe { RuntimeHandle::new_unchecked(rt.get_unchecked_mut() as *mut _) }; 97 | let this = JsiValue(sys::Value_copy(this, rt.get_inner_mut()), PhantomData); 98 | let args = args 99 | .into_iter() 100 | .map(|arg| JsiValue(sys::Value_copy(arg, rt.get_inner_mut()), PhantomData)) 101 | .collect(); 102 | 103 | #[cfg(feature = "host-fn-trace")] 104 | log::trace!("host fn call with closure at {:p}", body); 105 | 106 | // log::trace!( 107 | // "host fn call with closure at {:p} (this = {:?}, args = {:?})", 108 | // body.as_ref(), 109 | // this, 110 | // args 111 | // ); 112 | 113 | let val = body(this, args, &mut rt)?; 114 | Ok(val.0) 115 | }); 116 | 117 | let cb = Box::into_raw(Box::new(cb)) as *mut _; 118 | 119 | JsiFn( 120 | unsafe { 121 | sys::Function_createFromHostFunction( 122 | rt.get_inner_mut(), 123 | name.0.as_ref().unwrap(), 124 | param_count as u32, 125 | cb, 126 | ) 127 | }, 128 | PhantomData, 129 | ) 130 | } 131 | } 132 | 133 | unsafe impl<'rt> Send for JsiFn<'rt> {} 134 | 135 | pub fn create_promise< 136 | 'rt, 137 | F: 'rt + FnOnce(JsiFn<'rt>, JsiFn<'rt>, &mut RuntimeHandle<'rt>) -> (), 138 | >( 139 | body: F, 140 | rt: &mut RuntimeHandle<'rt>, 141 | ) -> JsiObject<'rt> { 142 | let mut inner = Some(body); 143 | 144 | let body = JsiFn::from_host_fn( 145 | &PropName::new("_", rt), 146 | 2, 147 | Box::new(move |_this, mut args, rt| { 148 | if args.len() != 2 { 149 | bail!( 150 | "promise callback called with {} args, expected 2", 151 | args.len() 152 | ); 153 | } 154 | 155 | let resolve: JsiFn = args 156 | .remove(0) 157 | .try_into_js(rt) 158 | .context("promise resolver is not a function")?; 159 | 160 | let reject: JsiFn = args 161 | .remove(0) 162 | .try_into_js(rt) 163 | .context("promise rejecter is not a function")?; 164 | 165 | match inner.take() { 166 | Some(inner) => inner(resolve, reject, rt), 167 | None => anyhow::bail!("promise lambda is only supposed to be called once!"), 168 | } 169 | 170 | Ok(JsiValue::new_undefined()) 171 | }), 172 | rt, 173 | ); 174 | 175 | let ctor = rt.global().get(PropName::new("Promise", rt), rt); 176 | let ctor: JsiFn = ctor 177 | .try_into_js(rt) 178 | .expect("Promise constructor is not an object"); 179 | 180 | let promise = ctor 181 | .call_as_constructor(vec![body.into_value(rt)], rt) 182 | .expect("Promise constructor threw an exception"); 183 | 184 | promise 185 | .try_into_js(rt) 186 | .expect("Promise constructor did not return an object") 187 | } 188 | -------------------------------------------------------------------------------- /jsi/src/host_function.rs: -------------------------------------------------------------------------------- 1 | use crate::{JsiValue, RuntimeHandle}; 2 | 3 | pub type UserHostFunction<'rt> = dyn FnMut( 4 | JsiValue<'rt>, // this 5 | Vec>, // args 6 | &mut RuntimeHandle<'rt>, // runtime 7 | ) -> Result, anyhow::Error> 8 | + 'rt; 9 | -------------------------------------------------------------------------------- /jsi/src/host_object.rs: -------------------------------------------------------------------------------- 1 | //! # Host objects 2 | //! 3 | //! Host objects are used in JSI to create special objects accessible from 4 | //! JavaScript which have behaviour that is defined in native code. 5 | 6 | use anyhow::bail; 7 | use std::{marker::PhantomData, pin::Pin}; 8 | 9 | use crate::{sys, IntoValue, JsTaskCallback, JsiValue, PropName, RuntimeHandle}; 10 | use sys::CallInvokerCallback; 11 | 12 | /// An owned host object 13 | pub struct OwnedJsiHostObject<'rt>( 14 | pub(crate) cxx::UniquePtr, 15 | pub(crate) PhantomData<&'rt mut ()>, 16 | ); 17 | 18 | impl<'rt> OwnedJsiHostObject<'rt> { 19 | pub fn get(&mut self, prop: &PropName, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 20 | JsiValue( 21 | sys::HostObject_get( 22 | self.0.pin_mut(), 23 | rt.get_inner_mut(), 24 | prop.0.as_ref().unwrap(), 25 | ), 26 | PhantomData, 27 | ) 28 | } 29 | 30 | pub fn set(&mut self, prop: &PropName, value: &JsiValue, rt: &mut RuntimeHandle<'rt>) { 31 | self.0.pin_mut().set( 32 | rt.get_inner_mut(), 33 | prop.0.as_ref().unwrap(), 34 | value.0.as_ref().unwrap(), 35 | ) 36 | } 37 | 38 | pub fn properties(&mut self, rt: &mut RuntimeHandle<'rt>) -> Vec { 39 | let mut props = sys::HostObject_getPropertyNames(self.0.pin_mut(), rt.get_inner_mut()); 40 | let mut vec = Vec::with_capacity(props.len()); 41 | loop { 42 | let ptr = sys::pop_prop_name_vector(props.pin_mut()); 43 | if ptr.is_null() { 44 | break; 45 | } 46 | vec.push(PropName(ptr, PhantomData)); 47 | } 48 | vec 49 | } 50 | } 51 | 52 | /// A shared reference to a host object 53 | pub struct SharedJsiHostObject<'rt>( 54 | pub(crate) cxx::SharedPtr, 55 | pub(crate) PhantomData<&'rt mut ()>, 56 | ); 57 | 58 | /// Helper trait for implementing a host object in Rust 59 | pub trait UserHostObject<'rt> { 60 | fn get( 61 | &mut self, 62 | name: PropName<'rt>, 63 | rt: &mut RuntimeHandle<'rt>, 64 | ) -> anyhow::Result>; 65 | 66 | fn set( 67 | &mut self, 68 | name: PropName<'rt>, 69 | value: JsiValue<'rt>, 70 | rt: &mut RuntimeHandle<'rt>, 71 | ) -> anyhow::Result<()>; 72 | 73 | fn properties(&mut self, rt: &mut RuntimeHandle<'rt>) -> Vec>; 74 | } 75 | 76 | impl<'rt, T: UserHostObject<'rt>> IntoValue<'rt> for T { 77 | fn into_value(self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 78 | let host_object = OwnedJsiUserHostObject::new(self, rt); 79 | let host_object: OwnedJsiHostObject = host_object.into(); 80 | host_object.into_value(rt) 81 | } 82 | } 83 | 84 | /// A generic wrapper struct is used to avoid using a triple-`Box` to contain 85 | /// user host objects. 86 | struct UserHostObjectWrapper(T); 87 | 88 | impl<'rt, T: UserHostObject<'rt>> sys::HostObjectImpl for UserHostObjectWrapper { 89 | fn get( 90 | &mut self, 91 | rt: Pin<&mut sys::Runtime>, 92 | name: &sys::PropNameID, 93 | ) -> anyhow::Result> { 94 | let mut rt = RuntimeHandle::new_unchecked(unsafe { rt.get_unchecked_mut() as *mut _ }); 95 | let name = PropName(sys::PropNameID_copy(name, rt.get_inner_mut()), PhantomData); 96 | let value = UserHostObject::get(&mut self.0, name, &mut rt)?; 97 | Ok(value.0) 98 | } 99 | 100 | fn set( 101 | &mut self, 102 | rt: Pin<&mut sys::Runtime>, 103 | name: &sys::PropNameID, 104 | value: &sys::JsiValue, 105 | ) -> anyhow::Result<()> { 106 | let mut rt = RuntimeHandle::new_unchecked(unsafe { rt.get_unchecked_mut() as *mut _ }); 107 | let name = PropName(sys::PropNameID_copy(name, rt.get_inner_mut()), PhantomData); 108 | let value = JsiValue(sys::Value_copy(value, rt.get_inner_mut()), PhantomData); 109 | UserHostObject::set(&mut self.0, name, value, &mut rt) 110 | } 111 | 112 | fn properties(&mut self, rt: Pin<&mut sys::Runtime>) -> Vec> { 113 | let mut rt = RuntimeHandle::new_unchecked(unsafe { rt.get_unchecked_mut() as *mut _ }); 114 | let props = UserHostObject::properties(&mut self.0, &mut rt); 115 | props.into_iter().map(|p| p.0).collect() 116 | } 117 | } 118 | 119 | impl Drop for UserHostObjectWrapper { 120 | fn drop(&mut self) { 121 | #[cfg(feature = "host-object-trace")] 122 | log::trace!( 123 | "dropping host object wrapper {:p} with host object {:p}", 124 | self, 125 | &self.0 126 | ); 127 | } 128 | } 129 | 130 | /// A host object that is implemented in Rust using a [`UserHostObject`] trait 131 | /// object. Start with this if you want to implement a host object. 132 | pub struct OwnedJsiUserHostObject<'rt>( 133 | cxx::UniquePtr, 134 | PhantomData<&'rt mut ()>, 135 | ); 136 | 137 | impl<'rt> OwnedJsiUserHostObject<'rt> { 138 | // The methods in here have an extra lifetime parameter `'u` so that 139 | // user-defined host objects don't have to have their lifetimes limited by 140 | // `'rt`. This allows us to use `'static` objects as host objects, for 141 | // example. 142 | 143 | pub fn new<'u: 'rt, T: UserHostObject<'u>>(ho: T, _rt: &mut RuntimeHandle<'rt>) -> Self { 144 | // Box 1 b/c C++ can't hold Rust objects w/o a Box 145 | // Box 2 b/c RustHostObject can't be generic (no way for the trampoline 146 | // function to know) which function to call w/o dynamic dispatch 147 | 148 | let b = Box::new(UserHostObjectWrapper(ho)); 149 | 150 | #[cfg(feature = "host-object-trace")] 151 | log::trace!( 152 | "created owned jsi user host object of {} inner box: {:p}", 153 | std::any::type_name::(), 154 | b.as_ref() 155 | ); 156 | 157 | let b = Box::new(sys::RustHostObject(b)); 158 | 159 | #[cfg(feature = "host-object-trace")] 160 | log::trace!( 161 | "created owned jsi user host object of {} outer box: {:p}", 162 | std::any::type_name::(), 163 | b.as_ref() 164 | ); 165 | 166 | let ptr = sys::CxxHostObject_create(b); 167 | 168 | #[cfg(feature = "host-object-trace")] 169 | log::trace!( 170 | "created owned jsi user host of {} object: {:p}", 171 | std::any::type_name::(), 172 | ptr.as_ref().unwrap() 173 | ); 174 | 175 | OwnedJsiUserHostObject(ptr, PhantomData) 176 | } 177 | 178 | // TODO: find some way to do a safety check at runtime w/o relying on `Any`, 179 | // because `Any` imposes `'static` which makes life hard due to how there 180 | // are lifetimes everywhere in this code 181 | pub fn get_inner_mut<'u: 'rt, T: UserHostObject<'u>>(&mut self) -> Option<&mut T> { 182 | let rho = sys::CxxHostObject_getInnerMut(self.0.as_mut().unwrap()); 183 | let ho_inner = unsafe { 184 | &mut *(rho.0.as_mut() as *mut dyn sys::HostObjectImpl as *mut UserHostObjectWrapper) 185 | }; 186 | Some(&mut ho_inner.0) 187 | } 188 | 189 | pub fn get_inner<'u: 'rt, T: UserHostObject<'u>>(&self) -> Option<&T> { 190 | let rho = sys::CxxHostObject_getInner(self.0.as_ref().unwrap()); 191 | let ho_inner = unsafe { 192 | &*(rho.0.as_ref() as *const dyn sys::HostObjectImpl as *const UserHostObjectWrapper) 193 | }; 194 | Some(&ho_inner.0) 195 | } 196 | } 197 | 198 | impl<'rt> Into> for OwnedJsiUserHostObject<'rt> { 199 | fn into(self) -> OwnedJsiHostObject<'rt> { 200 | OwnedJsiHostObject(sys::CxxHostObject_toHostObjectU(self.0), self.1) 201 | } 202 | } 203 | 204 | impl<'rt> TryInto> for OwnedJsiHostObject<'rt> { 205 | type Error = anyhow::Error; 206 | 207 | fn try_into(self) -> Result, Self::Error> { 208 | let ptr = sys::CxxHostObject_fromHostObjectU(self.0); 209 | 210 | if ptr.is_null() { 211 | bail!("this host object is not a Rust host object"); 212 | } else { 213 | Ok(OwnedJsiUserHostObject(ptr, self.1)) 214 | } 215 | } 216 | } 217 | 218 | /// A host object that is implemented in Rust using a [`UserHostObject`] trait object. 219 | #[derive(Clone)] 220 | pub struct SharedJsiUserHostObject<'rt>( 221 | cxx::SharedPtr, 222 | PhantomData<&'rt mut ()>, 223 | ); 224 | 225 | impl<'rt> SharedJsiUserHostObject<'rt> { 226 | pub fn get_inner<'u: 'rt, T: UserHostObject<'u>>(&self) -> Option<&T> { 227 | #[cfg(feature = "host-object-trace")] 228 | log::trace!( 229 | "recovering shared inner host object as {} from {:p}", 230 | std::any::type_name::(), 231 | self.0.as_ref().unwrap() 232 | ); 233 | 234 | let rho = sys::CxxHostObject_getInner(self.0.as_ref().unwrap()); 235 | 236 | #[cfg(feature = "host-object-trace")] 237 | log::trace!("recovered outer box {:p}", rho); 238 | 239 | let ho_inner = unsafe { 240 | &*(rho.0.as_ref() as *const dyn sys::HostObjectImpl as *const UserHostObjectWrapper) 241 | }; 242 | 243 | #[cfg(feature = "host-object-trace")] 244 | log::trace!("recovered inner box {:p}", ho_inner); 245 | 246 | #[cfg(feature = "host-object-trace")] 247 | log::trace!("recovered user object {:p}", &ho_inner.0); 248 | 249 | Some(&ho_inner.0) 250 | } 251 | } 252 | 253 | impl<'rt> Into> for SharedJsiUserHostObject<'rt> { 254 | fn into(self) -> SharedJsiHostObject<'rt> { 255 | SharedJsiHostObject(sys::CxxHostObject_toHostObjectS(self.0), self.1) 256 | } 257 | } 258 | 259 | impl<'rt> TryInto> for SharedJsiHostObject<'rt> { 260 | type Error = anyhow::Error; 261 | 262 | fn try_into(self) -> Result, Self::Error> { 263 | let ptr = sys::CxxHostObject_fromHostObjectS(self.0); 264 | 265 | if ptr.is_null() { 266 | bail!("this host object is not a Rust host object"); 267 | } else { 268 | Ok(SharedJsiUserHostObject(ptr, self.1)) 269 | } 270 | } 271 | } 272 | 273 | unsafe impl<'rt> Send for OwnedJsiHostObject<'rt> {} 274 | unsafe impl<'rt> Send for OwnedJsiUserHostObject<'rt> {} 275 | unsafe impl<'rt> Send for SharedJsiHostObject<'rt> {} 276 | unsafe impl<'rt> Send for SharedJsiUserHostObject<'rt> {} 277 | 278 | /// Support trait to allow implementation of async functions via attribute 279 | /// macro. 280 | pub trait AsyncUserHostObject<'rt> { 281 | fn spawn(task: JsTaskCallback); 282 | 283 | fn invoke(cb: CallInvokerCallback); 284 | } 285 | -------------------------------------------------------------------------------- /jsi/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{future::Future, pin::Pin}; 2 | 3 | #[cfg(feature = "macros")] 4 | pub use jsi_macros::host_object; 5 | pub use jsi_sys as sys; 6 | 7 | // allows us to use the proc macros inside this crate 8 | extern crate self as jsi; 9 | 10 | mod array; 11 | mod array_buffer; 12 | mod call_invoker; 13 | #[cfg(feature = "serde")] 14 | mod convert; 15 | mod function; 16 | mod host_function; 17 | mod host_object; 18 | mod object; 19 | mod prop_name; 20 | mod runtime; 21 | mod string; 22 | mod symbol; 23 | mod value; 24 | 25 | pub use array::*; 26 | pub use array_buffer::*; 27 | pub use call_invoker::*; 28 | #[cfg(feature = "serde")] 29 | pub use convert::*; 30 | pub use function::*; 31 | pub use host_function::*; 32 | pub use host_object::*; 33 | pub use object::*; 34 | pub use prop_name::*; 35 | pub use runtime::*; 36 | pub use string::*; 37 | pub use symbol::*; 38 | pub use value::*; 39 | 40 | /// Creates a JavaScript `Error` object with the given string as the message. 41 | #[macro_export] 42 | macro_rules! js_error { 43 | ($rt: expr, $err: tt) => {{ 44 | let mut rt = $rt; 45 | let error_ctor = rt.global().get(PropName::new("Error", rt), rt); 46 | let error_ctor: ::jsi::JsiFn = error_ctor.try_into_js(rt).unwrap(); 47 | let error_str = format!($err); 48 | let error_str = ::jsi::JsiValue::new_string(error_str.as_str(), rt); 49 | let error = error_ctor 50 | .call_as_constructor(::std::iter::once(error_str), rt) 51 | .expect("Error constructor threw an exception"); 52 | let error: ::jsi::JsiObject = error 53 | .try_into_js(rt) 54 | .expect("Error constructor returned a non-object"); 55 | error 56 | }}; 57 | } 58 | 59 | pub type JsTaskCallback = Box< 60 | dyn (for<'a> FnOnce( 61 | &'a mut RuntimeHandle<'a>, 62 | ) -> Pin> + 'a>>) 63 | + Send, 64 | >; 65 | 66 | pub fn init( 67 | rt: *mut sys::Runtime, 68 | call_invoker: cxx::SharedPtr, 69 | ) -> (RuntimeHandle<'static>, CallInvoker<'static>) { 70 | #[cfg(feature = "log")] 71 | log::debug!("got JSI runtime pointer: {:p}", rt); 72 | #[cfg(feature = "log")] 73 | log::debug!( 74 | "got JSI call invoker pointer: {:p}", 75 | call_invoker.as_ref().unwrap() 76 | ); 77 | 78 | let runtime_handle = RuntimeHandle::new_unchecked(rt); 79 | let call_invoker = CallInvoker::new(call_invoker); 80 | 81 | (runtime_handle, call_invoker) 82 | } 83 | -------------------------------------------------------------------------------- /jsi/src/object.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use crate::array::JsiArray; 4 | use crate::array_buffer::JsiArrayBuffer; 5 | use crate::function::JsiFn; 6 | use crate::host_object::{OwnedJsiHostObject, SharedJsiHostObject}; 7 | use crate::{ 8 | sys, FromValue, JsiValue, OwnedJsiUserHostObject, PropName, RuntimeHandle, 9 | SharedJsiUserHostObject, 10 | }; 11 | 12 | unsafe impl<'rt> Send for JsiObject<'rt> {} 13 | 14 | /// A JavaScript `Object`. 15 | pub struct JsiObject<'rt>( 16 | pub(crate) cxx::UniquePtr, 17 | pub(crate) PhantomData<&'rt mut ()>, 18 | ); 19 | 20 | impl<'rt> JsiObject<'rt> { 21 | pub fn new(rt: &mut RuntimeHandle<'rt>) -> Self { 22 | JsiObject(sys::Object_create(rt.get_inner_mut()), PhantomData) 23 | } 24 | 25 | pub fn get(&self, prop: PropName, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 26 | JsiValue( 27 | sys::Object_getProperty( 28 | self.0.as_ref().unwrap(), 29 | rt.get_inner_mut(), 30 | prop.0.as_ref().unwrap(), 31 | ), 32 | PhantomData, 33 | ) 34 | } 35 | 36 | pub fn has(&self, prop: PropName, rt: &mut RuntimeHandle<'rt>) -> bool { 37 | self.0 38 | .has_property(rt.get_inner_mut(), prop.0.as_ref().unwrap()) 39 | } 40 | 41 | pub fn set(&mut self, prop: PropName, value: &JsiValue, rt: &mut RuntimeHandle<'rt>) { 42 | sys::Object_setProperty( 43 | self.0.pin_mut(), 44 | rt.get_inner_mut(), 45 | prop.0.as_ref().unwrap(), 46 | value.0.as_ref().unwrap(), 47 | ) 48 | } 49 | 50 | pub fn properties(&mut self, rt: &mut RuntimeHandle<'rt>) -> JsiArray<'rt> { 51 | JsiArray( 52 | sys::Object_getPropertyNames(self.0.pin_mut(), rt.get_inner_mut()), 53 | PhantomData, 54 | ) 55 | } 56 | 57 | pub fn is_array(&self, rt: &mut RuntimeHandle<'rt>) -> bool { 58 | self.0.is_array(rt.get_inner_mut()) 59 | } 60 | 61 | pub fn is_array_buffer(&self, rt: &mut RuntimeHandle<'rt>) -> bool { 62 | self.0.is_array_buffer(rt.get_inner_mut()) 63 | } 64 | 65 | pub fn is_fn(&self, rt: &mut RuntimeHandle<'rt>) -> bool { 66 | self.0.is_function(rt.get_inner_mut()) 67 | } 68 | 69 | pub fn is_instance(&mut self, ctor: JsiFn, rt: &mut RuntimeHandle<'rt>) -> bool { 70 | self.0 71 | .pin_mut() 72 | .instance_of(rt.get_inner_mut(), ctor.0.as_ref().unwrap()) 73 | } 74 | } 75 | 76 | pub trait FromObject<'rt>: Sized { 77 | fn from_object(obj: &JsiObject<'rt>, rt: &mut RuntimeHandle<'rt>) -> Option; 78 | } 79 | 80 | pub trait IntoObject<'rt> { 81 | fn into_object(self, rt: &mut RuntimeHandle<'rt>) -> JsiObject<'rt>; 82 | } 83 | 84 | impl<'rt> FromObject<'rt> for JsiArray<'rt> { 85 | fn from_object(obj: &JsiObject<'rt>, rt: &mut RuntimeHandle<'rt>) -> Option { 86 | sys::Object_asArray(&*obj.0, rt.get_inner_mut()) 87 | .ok() 88 | .map(|raw| JsiArray(raw, PhantomData)) 89 | } 90 | } 91 | 92 | impl<'rt> FromObject<'rt> for JsiArrayBuffer<'rt> { 93 | fn from_object(ojb: &JsiObject<'rt>, rt: &mut RuntimeHandle<'rt>) -> Option { 94 | sys::Object_asArrayBuffer(&*ojb.0, rt.get_inner_mut()) 95 | .ok() 96 | .map(|raw| JsiArrayBuffer(raw, PhantomData)) 97 | } 98 | } 99 | 100 | impl<'rt> FromObject<'rt> for JsiFn<'rt> { 101 | fn from_object(obj: &JsiObject<'rt>, rt: &mut RuntimeHandle<'rt>) -> Option { 102 | sys::Object_asFunction(&*obj.0, rt.get_inner_mut()) 103 | .ok() 104 | .map(|raw| JsiFn(raw, PhantomData)) 105 | } 106 | } 107 | 108 | impl<'rt, T: FromValue<'rt>> FromObject<'rt> for Vec { 109 | fn from_object(obj: &JsiObject<'rt>, rt: &mut RuntimeHandle<'rt>) -> Option { 110 | let arr: Option = FromObject::from_object(obj, rt); 111 | arr.and_then(|arr| { 112 | let arr: Vec<_> = arr.iter(rt).collect(); 113 | 114 | arr.into_iter() 115 | .map(|it| FromValue::from_value(&it, rt)) 116 | .collect() 117 | }) 118 | } 119 | } 120 | 121 | impl<'rt> From> for JsiObject<'rt> { 122 | fn from(a: JsiArray<'rt>) -> Self { 123 | // JsiArray is a subclass of JsiObject, so we can just pointer cast 124 | JsiObject( 125 | unsafe { cxx::UniquePtr::<_>::from_raw(a.0.into_raw() as *mut _) }, 126 | PhantomData, 127 | ) 128 | } 129 | } 130 | 131 | impl<'rt> From> for JsiObject<'rt> { 132 | fn from(a: JsiArrayBuffer<'rt>) -> Self { 133 | // JsiArrayBuffer is a subclass of JsiObject, so we can just pointer cast 134 | JsiObject( 135 | unsafe { cxx::UniquePtr::<_>::from_raw(a.0.into_raw() as *mut _) }, 136 | PhantomData, 137 | ) 138 | } 139 | } 140 | 141 | impl<'rt> From> for JsiObject<'rt> { 142 | fn from(a: JsiFn<'rt>) -> Self { 143 | // JsiFn is a subclass of JsiObject, so we can just pointer cast 144 | JsiObject( 145 | unsafe { cxx::UniquePtr::<_>::from_raw(a.0.into_raw() as *mut _) }, 146 | PhantomData, 147 | ) 148 | } 149 | } 150 | 151 | impl<'rt, T: Into>> IntoObject<'rt> for T { 152 | fn into_object(self, _: &mut RuntimeHandle<'rt>) -> JsiObject<'rt> { 153 | Into::into(self) 154 | } 155 | } 156 | 157 | impl<'rt> IntoObject<'rt> for OwnedJsiHostObject<'rt> { 158 | fn into_object(self, rt: &mut RuntimeHandle<'rt>) -> JsiObject<'rt> { 159 | JsiObject( 160 | sys::Object_createFromHostObjectUnique(rt.get_inner_mut(), self.0), 161 | PhantomData, 162 | ) 163 | } 164 | } 165 | 166 | impl<'rt> IntoObject<'rt> for OwnedJsiUserHostObject<'rt> { 167 | fn into_object(self, rt: &mut RuntimeHandle<'rt>) -> JsiObject<'rt> { 168 | let obj: OwnedJsiHostObject = self.into(); 169 | obj.into_object(rt) 170 | } 171 | } 172 | 173 | impl<'rt> IntoObject<'rt> for SharedJsiHostObject<'rt> { 174 | fn into_object(self, rt: &mut RuntimeHandle<'rt>) -> JsiObject<'rt> { 175 | JsiObject( 176 | sys::Object_createFromHostObjectShared(rt.get_inner_mut(), self.0), 177 | PhantomData, 178 | ) 179 | } 180 | } 181 | 182 | impl<'rt> IntoObject<'rt> for SharedJsiUserHostObject<'rt> { 183 | fn into_object(self, rt: &mut RuntimeHandle<'rt>) -> JsiObject<'rt> { 184 | let obj: SharedJsiHostObject = self.into(); 185 | obj.into_object(rt) 186 | } 187 | } 188 | 189 | impl<'rt> FromObject<'rt> for SharedJsiHostObject<'rt> { 190 | fn from_object(obj: &JsiObject<'rt>, rt: &mut RuntimeHandle<'rt>) -> Option { 191 | sys::Object_asHostObject(&*obj.0, rt.get_inner_mut()) 192 | .ok() 193 | .map(|raw| SharedJsiHostObject(raw, PhantomData)) 194 | } 195 | } 196 | 197 | impl<'rt> FromObject<'rt> for SharedJsiUserHostObject<'rt> { 198 | fn from_object(obj: &JsiObject<'rt>, rt: &mut RuntimeHandle<'rt>) -> Option { 199 | let obj: Option = FromObject::from_object(obj, rt); 200 | obj.and_then(|obj| obj.try_into().ok()) 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /jsi/src/prop_name.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use crate::string::JsiString; 4 | use crate::{sys, RuntimeClone, RuntimeDisplay, RuntimeEq, RuntimeHandle}; 5 | 6 | /// A `PropName`, which is used to retrieve properties from `Object`s. 7 | pub struct PropName<'rt>( 8 | pub(crate) cxx::UniquePtr, 9 | pub(crate) PhantomData<&'rt mut ()>, 10 | ); 11 | 12 | impl<'rt> PropName<'rt> { 13 | pub fn new(name: &str, rt: &mut RuntimeHandle<'rt>) -> Self { 14 | PropName( 15 | sys::PropNameID_forUtf8(rt.get_inner_mut(), name), 16 | PhantomData, 17 | ) 18 | } 19 | 20 | pub fn from_string(name: JsiString<'rt>, rt: &mut RuntimeHandle<'rt>) -> Self { 21 | PropName( 22 | sys::PropNameID_forString(rt.get_inner_mut(), &*name.0), 23 | PhantomData, 24 | ) 25 | } 26 | } 27 | 28 | impl RuntimeClone<'_> for PropName<'_> { 29 | fn clone(&self, rt: &mut RuntimeHandle<'_>) -> Self { 30 | PropName( 31 | sys::PropNameID_copy(self.0.as_ref().unwrap(), rt.get_inner_mut()), 32 | PhantomData, 33 | ) 34 | } 35 | } 36 | 37 | impl RuntimeDisplay for PropName<'_> { 38 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>, rt: &mut RuntimeHandle<'_>) -> std::fmt::Result { 39 | write!( 40 | f, 41 | "{}", 42 | sys::PropNameID_toUtf8(&*self.0, rt.get_inner_mut()).to_string() 43 | ) 44 | } 45 | } 46 | 47 | impl RuntimeEq for PropName<'_> { 48 | fn eq(&self, other: &Self, rt: &mut RuntimeHandle<'_>) -> bool { 49 | sys::PropNameID_compare(rt.get_inner_mut(), &*self.0, &*other.0) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /jsi/src/runtime.rs: -------------------------------------------------------------------------------- 1 | use crate::object::JsiObject; 2 | use crate::sys; 3 | use std::cell::Cell; 4 | use std::marker::PhantomData; 5 | use std::pin::Pin; 6 | 7 | #[derive(Debug)] 8 | pub struct RuntimeHandle<'rt>(pub(crate) *mut sys::Runtime, PhantomData<&'rt mut ()>); 9 | 10 | impl<'rt> RuntimeHandle<'rt> { 11 | // Creates a new RuntimeHandle; it's the caller's responsibility to make 12 | // sure that the runtime is not destroyed while objects under this runtime 13 | // are still being used 14 | pub fn new_unchecked(ptr: *mut sys::Runtime) -> Self { 15 | RuntimeHandle(ptr, PhantomData) 16 | } 17 | 18 | pub fn get_inner_mut(&mut self) -> Pin<&'rt mut sys::Runtime> { 19 | unsafe { Pin::new_unchecked(&mut *self.0) } 20 | } 21 | 22 | pub fn get_inner(&mut self) -> &'rt sys::Runtime { 23 | unsafe { &*self.0 } 24 | } 25 | 26 | pub fn global(&mut self) -> JsiObject<'rt> { 27 | JsiObject(sys::Runtime_global(self.get_inner_mut()), PhantomData) 28 | } 29 | 30 | pub fn eq(&mut self, lhs: &T, rhs: &T) -> bool { 31 | lhs.eq(rhs, self) 32 | } 33 | 34 | pub fn clone>(&mut self, it: &T) -> T { 35 | it.clone(self) 36 | } 37 | 38 | pub fn display<'a, T: RuntimeDisplay>(&'a mut self, it: &'a T) -> impl std::fmt::Display + 'a 39 | where 40 | 'rt: 'a, 41 | { 42 | // unsafe: transmute converts RuntimeHandle<'rt> to RuntimeHandle<'a> 43 | RuntimeDisplayWrapper(Cell::new(Some(unsafe { std::mem::transmute(self) })), it) 44 | } 45 | 46 | pub fn to_string<'a, T: RuntimeDisplay>(&'a mut self, it: &'a T) -> String { 47 | self.display(it).to_string() 48 | } 49 | } 50 | 51 | unsafe impl<'rt> Send for RuntimeHandle<'rt> {} 52 | 53 | pub trait RuntimeEq { 54 | fn eq(&self, other: &Self, rt: &mut RuntimeHandle<'_>) -> bool; 55 | } 56 | 57 | pub trait RuntimeClone<'rt> { 58 | fn clone(&self, rt: &mut RuntimeHandle<'rt>) -> Self; 59 | } 60 | 61 | impl<'a, T: RuntimeClone<'a>> RuntimeClone<'a> for Vec { 62 | fn clone(&self, rt: &mut RuntimeHandle<'a>) -> Self { 63 | let mut v = Vec::with_capacity(self.len()); 64 | for i in self { 65 | v.push(RuntimeClone::clone(i, rt)); 66 | } 67 | v 68 | } 69 | } 70 | 71 | pub trait RuntimeDisplay { 72 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>, rt: &mut RuntimeHandle<'_>) -> std::fmt::Result; 73 | } 74 | 75 | struct RuntimeDisplayWrapper<'a, T: RuntimeDisplay>(Cell>>, &'a T); 76 | 77 | impl<'a, T: RuntimeDisplay> std::fmt::Display for RuntimeDisplayWrapper<'a, T> { 78 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 79 | let s = self.0.replace(None).unwrap(); 80 | let r = self.1.fmt(f, s); 81 | self.0.replace(Some(s)); 82 | r 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /jsi/src/string.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use crate::{sys, RuntimeClone, RuntimeDisplay, RuntimeEq, RuntimeHandle}; 4 | 5 | /// A JavaScript `String` 6 | pub struct JsiString<'rt>( 7 | pub(crate) cxx::UniquePtr, 8 | pub(crate) PhantomData<&'rt mut ()>, 9 | ); 10 | 11 | impl<'rt> JsiString<'rt> { 12 | pub fn new(name: &str, rt: &mut RuntimeHandle<'rt>) -> Self { 13 | JsiString(sys::String_fromUtf8(rt.get_inner_mut(), name), PhantomData) 14 | } 15 | } 16 | 17 | impl RuntimeEq for JsiString<'_> { 18 | fn eq(&self, other: &Self, rt: &mut RuntimeHandle<'_>) -> bool { 19 | sys::String_compare( 20 | rt.get_inner_mut(), 21 | self.0.as_ref().unwrap(), 22 | other.0.as_ref().unwrap(), 23 | ) 24 | } 25 | } 26 | 27 | impl RuntimeDisplay for JsiString<'_> { 28 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>, rt: &mut RuntimeHandle<'_>) -> std::fmt::Result { 29 | write!(f, "{}", self.0.to_string(rt.get_inner_mut())) 30 | } 31 | } 32 | 33 | impl<'rt> RuntimeClone<'rt> for JsiString<'rt> { 34 | fn clone(&self, rt: &mut RuntimeHandle<'rt>) -> Self { 35 | let text = self.0.to_string(rt.get_inner_mut()); 36 | let text = String::from_utf8_lossy(text.as_bytes()); 37 | Self::new(text.as_ref(), rt) 38 | } 39 | } 40 | 41 | unsafe impl<'rt> Send for JsiString<'rt> {} 42 | -------------------------------------------------------------------------------- /jsi/src/symbol.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use crate::{sys, RuntimeDisplay, RuntimeEq, RuntimeHandle}; 4 | 5 | /// A JavaScript `Symbol` 6 | pub struct JsiSymbol<'rt>( 7 | pub(crate) cxx::UniquePtr, 8 | pub(crate) PhantomData<&'rt ()>, 9 | ); 10 | 11 | impl RuntimeEq for JsiSymbol<'_> { 12 | fn eq(&self, other: &Self, rt: &mut RuntimeHandle<'_>) -> bool { 13 | sys::Symbol_compare( 14 | rt.get_inner_mut(), 15 | self.0.as_ref().unwrap(), 16 | other.0.as_ref().unwrap(), 17 | ) 18 | } 19 | } 20 | 21 | impl RuntimeDisplay for JsiSymbol<'_> { 22 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>, rt: &mut RuntimeHandle<'_>) -> std::fmt::Result { 23 | write!(f, "{}", self.0.to_string(rt.get_inner_mut())) 24 | } 25 | } 26 | 27 | unsafe impl<'rt> Send for JsiSymbol<'rt> {} 28 | -------------------------------------------------------------------------------- /jsi/src/value.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | use std::marker::PhantomData; 3 | 4 | use crate::array::JsiArray; 5 | use crate::array_buffer::JsiArrayBuffer; 6 | use crate::function::JsiFn; 7 | use crate::object::JsiObject; 8 | use crate::string::JsiString; 9 | use crate::symbol::JsiSymbol; 10 | use crate::{ 11 | sys, FromObject, IntoObject, OwnedJsiHostObject, OwnedJsiUserHostObject, RuntimeClone, 12 | RuntimeDisplay, RuntimeEq, RuntimeHandle, SharedJsiHostObject, SharedJsiUserHostObject, 13 | }; 14 | 15 | pub struct JsiValue<'rt>( 16 | pub(crate) cxx::UniquePtr, 17 | pub(crate) PhantomData<&'rt ()>, 18 | ); 19 | 20 | impl<'rt> JsiValue<'rt> { 21 | pub fn new_undefined() -> Self { 22 | Self(sys::Value_fromUndefined(), PhantomData) 23 | } 24 | 25 | pub fn new_null() -> Self { 26 | Self(sys::Value_fromUndefined(), PhantomData) 27 | } 28 | 29 | pub fn new_number(n: f64) -> Self { 30 | Self(sys::Value_fromDouble(n), PhantomData) 31 | } 32 | 33 | pub fn new_bool(b: bool) -> Self { 34 | Self(sys::Value_fromBool(b), PhantomData) 35 | } 36 | 37 | pub fn new_json(s: &str, rt: &mut RuntimeHandle<'rt>) -> Self { 38 | Self(sys::Value_fromJson(rt.get_inner_mut(), s), PhantomData) 39 | } 40 | 41 | pub fn new_string(s: &str, rt: &mut RuntimeHandle<'rt>) -> Self { 42 | JsiString::new(s, rt).into_value(rt) 43 | } 44 | 45 | pub fn is_null(&self) -> bool { 46 | // qualify this method call to avoid confusion with UniquePtr::is_null 47 | sys::JsiValue::is_null(self.0.as_ref().unwrap()) 48 | } 49 | 50 | pub fn is_undefined(&self) -> bool { 51 | self.0.is_undefined() 52 | } 53 | 54 | pub fn is_number(&self) -> bool { 55 | self.0.is_number() 56 | } 57 | 58 | pub fn is_bool(&self) -> bool { 59 | self.0.is_bool() 60 | } 61 | 62 | pub fn is_string(&self) -> bool { 63 | self.0.is_string() 64 | } 65 | 66 | pub fn is_symbol(&self) -> bool { 67 | self.0.is_symbol() 68 | } 69 | 70 | pub fn is_object(&self) -> bool { 71 | self.0.is_object() 72 | } 73 | 74 | pub fn is_truthy(&self, rt: &mut RuntimeHandle<'rt>) -> bool { 75 | match self.kind(rt) { 76 | JsiValueKind::Undefined | JsiValueKind::Null => false, 77 | JsiValueKind::Number(n) => n.abs() > f64::EPSILON, 78 | JsiValueKind::Bool(b) => b, 79 | JsiValueKind::String(s) => rt.display(&s).to_string().len() > 0, 80 | JsiValueKind::Symbol(_) | JsiValueKind::Object(_) => true, 81 | } 82 | } 83 | 84 | pub fn to_js_string(&self, rt: &mut RuntimeHandle<'rt>) -> JsiString<'rt> { 85 | JsiString(self.0.to_string(rt.get_inner_mut()), PhantomData) 86 | } 87 | 88 | pub fn kind(&self, rt: &mut RuntimeHandle<'rt>) -> JsiValueKind<'rt> { 89 | if self.is_null() { 90 | JsiValueKind::Null 91 | } else if self.is_undefined() { 92 | JsiValueKind::Undefined 93 | } else if self.is_number() { 94 | JsiValueKind::Number(FromValue::from_value(self, rt).unwrap()) 95 | } else if self.is_bool() { 96 | JsiValueKind::Bool(FromValue::from_value(self, rt).unwrap()) 97 | } else if self.is_string() { 98 | JsiValueKind::String(FromValue::from_value(self, rt).unwrap()) 99 | } else if self.is_symbol() { 100 | JsiValueKind::Symbol(FromValue::from_value(self, rt).unwrap()) 101 | } else if self.is_object() { 102 | JsiValueKind::Object(FromValue::from_value(self, rt).unwrap()) 103 | } else { 104 | panic!("JSI value has no known type") 105 | } 106 | } 107 | 108 | pub fn try_into_js>(&self, rt: &mut RuntimeHandle<'rt>) -> Option { 109 | T::from_value(self, rt) 110 | } 111 | 112 | pub fn into_js>(&self, rt: &mut RuntimeHandle<'rt>) -> T { 113 | self.try_into_js(rt).unwrap() 114 | } 115 | } 116 | 117 | unsafe impl<'rt> Send for JsiValue<'rt> {} 118 | 119 | impl<'rt> Debug for JsiValue<'rt> { 120 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 121 | write!(f, "JsiValue({:p})", &*self.0) 122 | } 123 | } 124 | 125 | impl RuntimeEq for JsiValue<'_> { 126 | fn eq(&self, other: &Self, rt: &mut RuntimeHandle<'_>) -> bool { 127 | sys::Value_compare(rt.get_inner_mut(), &*self.0, &*other.0) 128 | } 129 | } 130 | 131 | impl RuntimeDisplay for JsiValue<'_> { 132 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>, rt: &mut RuntimeHandle<'_>) -> std::fmt::Result { 133 | let s = JsiString(self.0.to_string(rt.get_inner_mut()), PhantomData); 134 | { 135 | let disp = rt.display(&s); 136 | write!(f, "{}", disp) 137 | } 138 | } 139 | } 140 | 141 | impl RuntimeClone<'_> for JsiValue<'_> { 142 | fn clone(&self, rt: &mut RuntimeHandle<'_>) -> Self { 143 | Self(sys::Value_copy(&*self.0, rt.get_inner_mut()), PhantomData) 144 | } 145 | } 146 | 147 | /// Conversion trait to and from [`JsiValue`]. This is needed instead of the 148 | /// normal `Into` trait b/c creating a JsiValue requires a [`RuntimeHandle`]. 149 | // Also has the benefit of allowing us to avoid implementing `Into` separately 150 | // for `T` and `&T`. 151 | pub trait IntoValue<'rt> { 152 | fn into_value(self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt>; 153 | } 154 | 155 | /// Conversion trait to and from [`JsiValue`]. This is needed instead of the 156 | /// normal `Into` trait b/c creating a JsiValue requires a [`RuntimeHandle`]. 157 | /// Note that using this trait will create a copy of the data being converted 158 | /// (ex.: using `as_value()` on a [`JsiObject`] will create a value that 159 | /// contains a copy of that object). 160 | pub trait AsValue<'rt> { 161 | fn as_value(&self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt>; 162 | } 163 | 164 | pub trait FromValue<'rt>: Sized { 165 | fn from_value(value: &JsiValue<'rt>, rt: &mut RuntimeHandle<'rt>) -> Option; 166 | } 167 | 168 | impl<'rt> FromValue<'rt> for JsiValue<'rt> { 169 | fn from_value(value: &JsiValue<'rt>, rt: &mut RuntimeHandle<'rt>) -> Option { 170 | Some(rt.clone(value)) 171 | } 172 | } 173 | 174 | impl<'rt> FromValue<'rt> for f64 { 175 | fn from_value(value: &JsiValue<'rt>, _rt: &mut RuntimeHandle<'rt>) -> Option { 176 | value.0.get_number().ok() 177 | } 178 | } 179 | 180 | impl<'rt> FromValue<'rt> for bool { 181 | fn from_value(value: &JsiValue<'rt>, _rt: &mut RuntimeHandle<'rt>) -> Option { 182 | value.0.get_bool().ok() 183 | } 184 | } 185 | 186 | impl<'rt> FromValue<'rt> for JsiObject<'rt> { 187 | fn from_value(value: &JsiValue<'rt>, rt: &mut RuntimeHandle<'rt>) -> Option { 188 | sys::Value_asObject(&*value.0, rt.get_inner_mut()) 189 | .ok() 190 | .map(|raw| JsiObject(raw, PhantomData)) 191 | } 192 | } 193 | 194 | impl<'rt, T: FromObject<'rt>> FromValue<'rt> for T { 195 | fn from_value(value: &JsiValue<'rt>, rt: &mut RuntimeHandle<'rt>) -> Option { 196 | let obj: Option = FromValue::from_value(value, rt); 197 | obj.and_then(|obj| FromObject::from_object(&obj, rt)) 198 | } 199 | } 200 | 201 | impl<'rt> FromValue<'rt> for String { 202 | fn from_value(value: &JsiValue<'rt>, rt: &mut RuntimeHandle<'rt>) -> Option { 203 | let s: Option = FromValue::from_value(value, rt); 204 | s.map(|s| rt.to_string(&s)) 205 | } 206 | } 207 | 208 | impl<'rt> FromValue<'rt> for JsiString<'rt> { 209 | fn from_value(value: &JsiValue<'rt>, rt: &mut RuntimeHandle<'rt>) -> Option { 210 | sys::Value_asString(&*value.0, rt.get_inner_mut()) 211 | .ok() 212 | .map(|raw| JsiString(raw, PhantomData)) 213 | } 214 | } 215 | 216 | impl<'rt> FromValue<'rt> for JsiSymbol<'rt> { 217 | fn from_value(value: &JsiValue<'rt>, rt: &mut RuntimeHandle<'rt>) -> Option { 218 | sys::Value_asSymbol(&*value.0, rt.get_inner_mut()) 219 | .ok() 220 | .map(|raw| JsiSymbol(raw, PhantomData)) 221 | } 222 | } 223 | 224 | impl<'rt> IntoValue<'rt> for JsiValue<'rt> { 225 | fn into_value(self, _: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 226 | self 227 | } 228 | } 229 | 230 | impl<'rt> IntoValue<'rt> for bool { 231 | fn into_value(self, _rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 232 | JsiValue::new_bool(self) 233 | } 234 | } 235 | 236 | impl<'rt> IntoValue<'rt> for String { 237 | fn into_value(self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 238 | JsiValue::new_string(&self, rt) 239 | } 240 | } 241 | 242 | impl<'rt> IntoValue<'rt> for &str { 243 | fn into_value(self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 244 | JsiValue::new_string(self, rt) 245 | } 246 | } 247 | 248 | impl<'rt> IntoValue<'rt> for f64 { 249 | fn into_value(self, _rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 250 | JsiValue::new_number(self) 251 | } 252 | } 253 | 254 | impl<'rt> IntoValue<'rt> for usize { 255 | fn into_value(self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 256 | IntoValue::into_value(self as f64, rt) 257 | } 258 | } 259 | 260 | impl<'rt> IntoValue<'rt> for u8 { 261 | fn into_value(self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 262 | IntoValue::into_value(self as f64, rt) 263 | } 264 | } 265 | 266 | impl<'rt> IntoValue<'rt> for u16 { 267 | fn into_value(self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 268 | IntoValue::into_value(self as f64, rt) 269 | } 270 | } 271 | 272 | impl<'rt> IntoValue<'rt> for u32 { 273 | fn into_value(self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 274 | IntoValue::into_value(self as f64, rt) 275 | } 276 | } 277 | 278 | impl<'rt> IntoValue<'rt> for u64 { 279 | fn into_value(self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 280 | IntoValue::into_value(self as f64, rt) 281 | } 282 | } 283 | 284 | impl<'rt> IntoValue<'rt> for isize { 285 | fn into_value(self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 286 | IntoValue::into_value(self as f64, rt) 287 | } 288 | } 289 | 290 | impl<'rt> IntoValue<'rt> for i8 { 291 | fn into_value(self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 292 | IntoValue::into_value(self as f64, rt) 293 | } 294 | } 295 | 296 | impl<'rt> IntoValue<'rt> for i16 { 297 | fn into_value(self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 298 | IntoValue::into_value(self as f64, rt) 299 | } 300 | } 301 | 302 | impl<'rt> IntoValue<'rt> for i32 { 303 | fn into_value(self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 304 | IntoValue::into_value(self as f64, rt) 305 | } 306 | } 307 | 308 | impl<'rt> IntoValue<'rt> for i64 { 309 | fn into_value(self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 310 | IntoValue::into_value(self as f64, rt) 311 | } 312 | } 313 | 314 | impl<'rt> IntoValue<'rt> for () { 315 | fn into_value(self, _rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 316 | JsiValue::new_undefined() 317 | } 318 | } 319 | 320 | impl<'rt, T: IntoValue<'rt>> IntoValue<'rt> for Vec { 321 | fn into_value(self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 322 | let mut arr = JsiArray::new(self.len(), rt); 323 | 324 | for (idx, item) in self.into_iter().enumerate() { 325 | arr.set(idx, &item.into_value(rt), rt); 326 | } 327 | 328 | arr.into_value(rt) 329 | } 330 | } 331 | 332 | impl<'rt, T: IntoValue<'rt>> IntoValue<'rt> for Option { 333 | fn into_value(self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 334 | match self { 335 | Some(inner) => inner.into_value(rt), 336 | None => JsiValue::new_null(), 337 | } 338 | } 339 | } 340 | 341 | impl<'rt> IntoValue<'rt> for JsiObject<'rt> { 342 | fn into_value(self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 343 | JsiValue( 344 | sys::Value_fromObject(rt.get_inner_mut(), self.0), 345 | PhantomData, 346 | ) 347 | } 348 | } 349 | 350 | impl<'rt> IntoValue<'rt> for JsiString<'rt> { 351 | fn into_value(self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 352 | JsiValue( 353 | sys::Value_fromString(rt.get_inner_mut(), self.0), 354 | PhantomData, 355 | ) 356 | } 357 | } 358 | 359 | impl<'rt> IntoValue<'rt> for JsiSymbol<'rt> { 360 | fn into_value(self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 361 | JsiValue( 362 | sys::Value_fromSymbol(rt.get_inner_mut(), self.0), 363 | PhantomData, 364 | ) 365 | } 366 | } 367 | 368 | impl<'rt> IntoValue<'rt> for JsiArray<'rt> { 369 | fn into_value(self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 370 | let obj: JsiObject = self.into(); 371 | obj.into_value(rt) 372 | } 373 | } 374 | 375 | impl<'rt> IntoValue<'rt> for JsiArrayBuffer<'rt> { 376 | fn into_value(self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 377 | let obj: JsiObject = self.into(); 378 | obj.into_value(rt) 379 | } 380 | } 381 | 382 | impl<'rt> IntoValue<'rt> for JsiFn<'rt> { 383 | fn into_value(self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 384 | let obj: JsiObject = self.into(); 385 | obj.into_value(rt) 386 | } 387 | } 388 | 389 | impl<'rt> IntoValue<'rt> for OwnedJsiUserHostObject<'rt> { 390 | fn into_value(self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 391 | let obj: JsiObject = self.into_object(rt); 392 | obj.into_value(rt) 393 | } 394 | } 395 | 396 | impl<'rt> IntoValue<'rt> for OwnedJsiHostObject<'rt> { 397 | fn into_value(self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 398 | let obj: JsiObject = self.into_object(rt); 399 | obj.into_value(rt) 400 | } 401 | } 402 | 403 | impl<'rt> IntoValue<'rt> for SharedJsiUserHostObject<'rt> { 404 | fn into_value(self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 405 | let obj: JsiObject = self.into_object(rt); 406 | obj.into_value(rt) 407 | } 408 | } 409 | 410 | impl<'rt> IntoValue<'rt> for SharedJsiHostObject<'rt> { 411 | fn into_value(self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 412 | let obj: JsiObject = self.into_object(rt); 413 | obj.into_value(rt) 414 | } 415 | } 416 | 417 | impl<'rt, T: AsValue<'rt>> AsValue<'rt> for Option<&T> { 418 | fn as_value(&self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 419 | match self { 420 | Some(inner) => inner.as_value(rt), 421 | None => JsiValue::new_null(), 422 | } 423 | } 424 | } 425 | 426 | impl<'rt> AsValue<'rt> for JsiObject<'rt> { 427 | fn as_value(&self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 428 | JsiValue( 429 | sys::Value_copyFromObject(rt.get_inner_mut(), &*self.0), 430 | PhantomData, 431 | ) 432 | } 433 | } 434 | 435 | impl<'rt> AsValue<'rt> for JsiString<'rt> { 436 | fn as_value(&self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 437 | JsiValue( 438 | sys::Value_copyFromString(rt.get_inner_mut(), self.0.as_ref().unwrap()), 439 | PhantomData, 440 | ) 441 | } 442 | } 443 | 444 | impl<'rt> AsValue<'rt> for JsiSymbol<'rt> { 445 | fn as_value(&self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 446 | JsiValue( 447 | sys::Value_copyFromSymbol(rt.get_inner_mut(), self.0.as_ref().unwrap()), 448 | PhantomData, 449 | ) 450 | } 451 | } 452 | 453 | impl<'rt> AsValue<'rt> for JsiArray<'rt> { 454 | fn as_value(&self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 455 | // this is a subclass of JsiObject, so pointer cast is safe 456 | let ptr = &*self.0 as *const _ as *const sys::JsiObject; 457 | 458 | JsiValue( 459 | sys::Value_copyFromObject(rt.get_inner_mut(), unsafe { &*ptr }), 460 | PhantomData, 461 | ) 462 | } 463 | } 464 | 465 | impl<'rt> AsValue<'rt> for JsiArrayBuffer<'rt> { 466 | fn as_value(&self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 467 | // this is a subclass of JsiObject, so pointer cast is safe 468 | let ptr = &*self.0 as *const _ as *const sys::JsiObject; 469 | 470 | JsiValue( 471 | sys::Value_copyFromObject(rt.get_inner_mut(), unsafe { &*ptr }), 472 | PhantomData, 473 | ) 474 | } 475 | } 476 | 477 | impl<'rt> AsValue<'rt> for JsiFn<'rt> { 478 | fn as_value(&self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> { 479 | // this is a subclass of JsiObject, so pointer cast is safe 480 | let ptr = &*self.0 as *const _ as *const sys::JsiObject; 481 | 482 | JsiValue( 483 | sys::Value_copyFromObject(rt.get_inner_mut(), unsafe { &*ptr }), 484 | PhantomData, 485 | ) 486 | } 487 | } 488 | 489 | pub enum JsiValueKind<'rt> { 490 | Undefined, 491 | Null, 492 | Number(f64), 493 | Bool(bool), 494 | String(JsiString<'rt>), 495 | Symbol(JsiSymbol<'rt>), 496 | Object(JsiObject<'rt>), 497 | } 498 | -------------------------------------------------------------------------------- /vendor/build-hermes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euxo pipefail 3 | cd hermes 4 | cmake -S . -B build -G Ninja 5 | cmake --build ./build 6 | --------------------------------------------------------------------------------