├── webapi
├── c-api
│ ├── binding.cpp
│ └── binding.hpp
├── src
│ ├── readable_stream.rs
│ └── lib.rs
├── Cargo.toml
└── build.rs
├── rust-toolchain
├── rustfmt.toml
├── tyr
├── src
│ ├── web
│ │ ├── mod.rs
│ │ └── btoa.rs
│ ├── wellknown_property_name.rs
│ ├── async_callback.rs
│ ├── timer.rs
│ ├── main.rs
│ └── console.rs
└── Cargo.toml
├── .yarnrc.yml
├── aarch64.cmake
├── .prettierignore
├── renovate.json
├── specs
├── console.js
├── react
│ └── server-rendering.js
└── timer.js
├── linux-x64-musl.Dockerfile
├── .rustfmt.toml
├── .gitmodules
├── .editorconfig
├── .taplo.toml
├── jsc-sys
├── Cargo.toml
├── c-api
│ ├── binding.hpp
│ └── binding.cpp
├── src
│ ├── lib.rs
│ └── binding.rs
└── build.rs
├── jsc
├── Cargo.toml
└── src
│ ├── value.rs
│ ├── error.rs
│ ├── object.rs
│ ├── class.rs
│ └── lib.rs
├── linux-x64.Dockerfile
├── node-api
├── Cargo.toml
└── src
│ └── lib.rs
├── Cargo.toml
├── .cargo
└── config.toml
├── linux-aarch64.Dockerfile
├── tsconfig.json
├── xtask
├── Cargo.toml
└── src
│ ├── main.rs
│ ├── release_jsc.rs
│ └── build.rs
├── .gitignore
├── package.json
├── README.md
├── .github
└── workflows
│ ├── docker.yaml
│ ├── build-test.yaml
│ └── release-jsc.yaml
└── .eslintrc.yaml
/webapi/c-api/binding.cpp:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/webapi/c-api/binding.hpp:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/rust-toolchain:
--------------------------------------------------------------------------------
1 | nightly-2022-07-11
--------------------------------------------------------------------------------
/rustfmt.toml:
--------------------------------------------------------------------------------
1 | tab_spaces = 2
2 |
--------------------------------------------------------------------------------
/tyr/src/web/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod btoa;
2 |
--------------------------------------------------------------------------------
/webapi/src/readable_stream.rs:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/webapi/src/lib.rs:
--------------------------------------------------------------------------------
1 | mod readable_stream;
2 |
3 | pub use readable_stream::*;
4 |
--------------------------------------------------------------------------------
/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | yarnPath: .yarn/releases/yarn-3.3.0.cjs
2 | nodeLinker: node-modules
3 |
--------------------------------------------------------------------------------
/aarch64.cmake:
--------------------------------------------------------------------------------
1 | set(CMAKE_SYSTEM_NAME "Linux")
2 | set(CMAKE_SYSTEM_PROCESSOR "aarch64")
3 | set(CMAKE_CROSSCOMPILING "TRUE")
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .yarn
2 | target
3 | dist
4 | node_modules
5 | icu
6 | WebKit
7 | jsc
8 | jsc-sys
9 | tyr
10 | xtask
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "config:base"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/specs/console.js:
--------------------------------------------------------------------------------
1 | console.log('hello')
2 | console.log(Symbol('foo'))
3 | console.log([1, '2', { foo: 'bar' }])
4 | console.log({ foo: 1, bar: '2', baz: Math.PI })
5 |
--------------------------------------------------------------------------------
/linux-x64-musl.Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
2 |
3 | ENV RUSTFLAGS=""
4 |
5 | RUN apk add --update --no-cache lld ruby perl openssl-dev
6 |
--------------------------------------------------------------------------------
/.rustfmt.toml:
--------------------------------------------------------------------------------
1 | tab_spaces = 2
2 | hard_tabs = false
3 | format_strings = true
4 | wrap_comments = true
5 |
6 | imports_granularity = "Crate"
7 | group_imports = "StdExternalCrate"
8 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "WebKit"]
2 | path = WebKit
3 | url = git@github.com:WebKit/WebKit.git
4 | [submodule "icu"]
5 | path = icu
6 | url = git@github.com:unicode-org/icu.git
7 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 |
7 | [*.{js,json,yml}]
8 | charset = utf-8
9 | indent_style = space
10 | indent_size = 2
11 |
--------------------------------------------------------------------------------
/.taplo.toml:
--------------------------------------------------------------------------------
1 | exclude = ["**/node_modules/**", "**/target/**"]
2 |
3 | [[rule]]
4 | include = ["**/Cargo.toml"]
5 | keys = ["dependencies", "*-dependencies"]
6 |
7 | [rule.formatting]
8 | reorder_keys = true
--------------------------------------------------------------------------------
/jsc-sys/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "jsc-sys"
3 | version = "0.0.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | compiler_builtins = "0.1"
8 |
9 | [build-dependencies]
10 | build-support = { path = "../build-support" }
11 |
--------------------------------------------------------------------------------
/jsc/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "jsc-safe"
3 | version = "0.0.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | anyhow = "1"
8 | bitflags = "1"
9 | jsc-sys = { path = "../jsc-sys", version = "0.0.0" }
10 | thiserror = "1"
11 |
--------------------------------------------------------------------------------
/linux-x64.Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian
2 |
3 | RUN apt-get update && \
4 | apt-get install libc++-14-dev libc++abi-14-dev ruby -y --fix-missing --no-install-recommends && \
5 | rm -rf /var/lib/apt/lists/*
--------------------------------------------------------------------------------
/tyr/src/wellknown_property_name.rs:
--------------------------------------------------------------------------------
1 | use jsc_safe::sys::{jsc_string_from_static_rust_str, JSStringRef};
2 |
3 | thread_local! {
4 | pub static LENGTH: JSStringRef = unsafe { jsc_string_from_static_rust_str("length\0".as_ptr() as *const _) };
5 | }
6 |
--------------------------------------------------------------------------------
/node-api/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "node-api"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | jsc-sys = { path = "../jsc-sys" }
10 |
--------------------------------------------------------------------------------
/specs/react/server-rendering.js:
--------------------------------------------------------------------------------
1 | import { renderToReadableStream } from 'react-dom/server'
2 |
3 | function App() {
4 | return
Hello World
5 | }
6 |
7 | const readableStream = await renderToReadableStream()
8 |
9 | console.info(readableStream.allReady)
10 |
--------------------------------------------------------------------------------
/specs/timer.js:
--------------------------------------------------------------------------------
1 | setTimeout(() => {
2 | console.log('hello')
3 | })
4 |
5 | setTimeout(() => {
6 | console.log('hello after 1s')
7 | }, 1000)
8 |
9 | const i = setTimeout(() => {
10 | console.log('hello after 1s should be cancelled')
11 | }, 1000)
12 |
13 | clearTimeout(i)
14 |
--------------------------------------------------------------------------------
/node-api/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![allow(non_camel_case_types)]
2 |
3 | use jsc_sys::{JSContextRef, JSValueMakeUndefined, JSValueRef};
4 |
5 | pub type napi_env = JSContextRef;
6 | pub type napi_value = JSValueRef;
7 |
8 | pub fn napi_get_undefined(env: napi_env) -> napi_value {
9 | unsafe { JSValueMakeUndefined(env) }
10 | }
11 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = [
3 | "build-support",
4 | "jsc",
5 | "jsc-sys",
6 | "tyr",
7 | "node-api",
8 | "xtask",
9 | "webapi",
10 | ]
11 |
12 | [profile.release]
13 | lto = true
14 | codegen-units = 1
15 |
16 | [workspace.metadata]
17 | icu = { tag = "release-71-1" }
18 | WebKit = { tag = "webkitgtk-2.37.1" }
19 |
--------------------------------------------------------------------------------
/webapi/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "webapi"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | jsc-sys = { path = "../jsc-sys" }
10 |
11 | [build-dependencies]
12 | build-support = { path = "../build-support" }
13 |
--------------------------------------------------------------------------------
/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | [alias]
2 | xtask = "run --package xtask --"
3 |
4 | [target.aarch64-unknown-linux-gnu]
5 | rustflags = [
6 | "-C", "link-args=-fuse-ld=lld",
7 | "-C", "link-args=--target=aarch64-unknown-linux-gnu",
8 | "-C", "link-args=-march=armv8-a",
9 | "-C", "link-args=--sysroot=/usr/aarch64-unknown-linux-gnu/aarch64-unknown-linux-gnu/sysroot",
10 | ]
11 |
--------------------------------------------------------------------------------
/linux-aarch64.Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64
2 |
3 | ADD ./lib/llvm-14 /usr/aarch64-unknown-linux-gnu/lib/llvm-14
4 |
5 | RUN apt-get update && \
6 | apt-get install libssl-dev libc++-14-dev libc++abi-14-dev pkg-config ruby -y --fix-missing --no-install-recommends && \
7 | cp -r /usr/aarch64-unknown-linux-gnu/lib/gcc /usr/aarch64-unknown-linux-gnu/aarch64-unknown-linux-gnu/sysroot/lib/ && \
8 | rm -rf /var/lib/apt/lists/*
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "module": "ESNext",
5 | "moduleResolution": "node",
6 | "strict": true,
7 | "importHelpers": false,
8 | "noEmitHelpers": false,
9 | "skipLibCheck": true,
10 | "allowJs": true,
11 | "outDir": "./dist"
12 | },
13 | "exclude": [
14 | "node_modules",
15 | "jsc",
16 | "jsc-sys",
17 | "icu",
18 | "WebKit",
19 | "tyr",
20 | ".yarn",
21 | "target"
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/tyr/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "tyr"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | anyhow = "1"
10 | base64-simd = "0.7"
11 | clap = { version = "3", features = ["derive"] }
12 | encoding_rs = "0.8"
13 | jsc-safe = { path = "../jsc", version = "0.0.0" }
14 | once_cell = "1"
15 | serde = { version = "1", features = ["derive"] }
16 | serde_json = "1"
17 | tokio = { version = "1", features = ["full"] }
18 |
19 | [target.'cfg(not(all(target_os = "linux", target_env = "musl", target_arch = "aarch64")))'.dependencies]
20 | mimalloc-rust = "0.2"
21 |
--------------------------------------------------------------------------------
/xtask/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "xtask"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [features]
9 | default = ["rustls-tls"]
10 | native-tls = ["octorust/native-tls", "reqwest/default-tls"]
11 | rustls-tls = ["octorust/rustls-tls", "reqwest/rustls-tls"]
12 |
13 | [dependencies]
14 | clap = { version = "3", features = ["derive"] }
15 | num_cpus = "1"
16 | octorust = "0.2"
17 | reqwest = { version = "0.11", default-features = false, features = [
18 | "json",
19 | "multipart",
20 | ] }
21 | serde = { version = "1", features = ["derive"] }
22 | tar = "0.4"
23 | tokio = "1"
24 | toml = { version = "0.5" }
25 |
--------------------------------------------------------------------------------
/jsc/src/value.rs:
--------------------------------------------------------------------------------
1 | use std::ptr;
2 |
3 | use jsc_sys::{
4 | JSGlobalContextRef, JSValueIsArray, JSValueIsDate, JSValueIsEqual, JSValueIsUndefined, JSValueRef,
5 | };
6 |
7 | pub trait JsValue {
8 | fn ctx(&self) -> JSGlobalContextRef;
9 | fn raw(&self) -> JSValueRef;
10 |
11 | fn is_undefined(&self) -> bool {
12 | unsafe { JSValueIsUndefined(self.ctx(), self.raw()) }
13 | }
14 |
15 | fn is_array(&self) -> bool {
16 | unsafe { JSValueIsArray(self.ctx(), self.raw()) }
17 | }
18 |
19 | fn is_date(&self) -> bool {
20 | unsafe { JSValueIsDate(self.ctx(), self.raw()) }
21 | }
22 |
23 | fn is_equal(&self, other: &T) -> bool {
24 | let mut exception = ptr::null();
25 | unsafe { JSValueIsEqual(self.ctx(), self.raw(), other.raw(), &mut exception) }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/xtask/src/main.rs:
--------------------------------------------------------------------------------
1 | use clap::{self, Parser, Subcommand};
2 |
3 | mod build;
4 | mod release_jsc;
5 |
6 | use build::build;
7 | use release_jsc::{download, release};
8 |
9 | #[derive(Parser)]
10 | #[clap(author, version, about = "Tyr cargo tasks", long_about = None)]
11 | #[clap(propagate_version = true)]
12 | struct Cli {
13 | #[clap(subcommand)]
14 | command: Commands,
15 | }
16 |
17 | #[derive(Subcommand)]
18 | enum Commands {
19 | Build,
20 | Release {
21 | #[clap(short, long)]
22 | target: String,
23 | },
24 | Download {
25 | #[clap(short, long)]
26 | target: String,
27 | },
28 | }
29 |
30 | #[tokio::main]
31 | async fn main() {
32 | let cli = Cli::parse();
33 |
34 | match &cli.command {
35 | Commands::Build => {
36 | build();
37 | }
38 | &Commands::Release { ref target } => {
39 | release(target).await;
40 | }
41 | &Commands::Download { ref target } => {
42 | download(target).await;
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/jsc/src/error.rs:
--------------------------------------------------------------------------------
1 | use std::{
2 | ffi::NulError,
3 | fmt::{Display, Formatter},
4 | io,
5 | };
6 |
7 | use jsc_sys::JSValueRef;
8 | use thiserror::Error;
9 |
10 | #[derive(Error, Debug)]
11 | pub enum JscError {
12 | #[error("Convert to `CString` failed")]
13 | NulError,
14 | #[error("{0}")]
15 | JSCException(JSCException),
16 | #[error("{0}")]
17 | IO(io::Error),
18 | }
19 |
20 | impl From for JscError {
21 | fn from(_: NulError) -> Self {
22 | Self::NulError
23 | }
24 | }
25 |
26 | impl From for JscError {
27 | fn from(err: io::Error) -> Self {
28 | Self::IO(err)
29 | }
30 | }
31 |
32 | impl From for JscError {
33 | fn from(err: JSValueRef) -> Self {
34 | Self::JSCException(JSCException { exception: err })
35 | }
36 | }
37 |
38 | #[derive(Debug)]
39 | pub struct JSCException {
40 | pub exception: JSValueRef,
41 | }
42 |
43 | impl Display for JSCException {
44 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
45 | write!(f, "JSCException")
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | Cargo.lock
3 | # Created by https://www.toptal.com/developers/gitignore/api/macos
4 | # Edit at https://www.toptal.com/developers/gitignore?templates=macos
5 |
6 | ### macOS ###
7 | # General
8 | .DS_Store
9 | .AppleDouble
10 | .LSOverride
11 |
12 | # Icon must end with two \r
13 | Icon
14 |
15 |
16 | # Thumbnails
17 | ._*
18 |
19 | # Files that might appear in the root of a volume
20 | .DocumentRevisions-V100
21 | .fseventsd
22 | .Spotlight-V100
23 | .TemporaryItems
24 | .Trashes
25 | .VolumeIcon.icns
26 | .com.apple.timemachine.donotpresent
27 |
28 | # Directories potentially created on remote AFP share
29 | .AppleDB
30 | .AppleDesktop
31 | Network Trash Folder
32 | Temporary Items
33 | .apdisk
34 |
35 | ### macOS Patch ###
36 | # iCloud generated files
37 | *.icloud
38 |
39 | # End of https://www.toptal.com/developers/gitignore/api/macos
40 |
41 | .vscode
42 | icu/icu4c/source/include
43 | lib
44 | icu-linux-aarch64
45 | .pnp.*
46 | .yarn/*
47 | !.yarn/patches
48 | !.yarn/plugins
49 | !.yarn/releases
50 | !.yarn/sdks
51 | !.yarn/versions
52 | node_modules
53 |
54 | webapi/webcore-build
--------------------------------------------------------------------------------
/tyr/src/async_callback.rs:
--------------------------------------------------------------------------------
1 | use std::fmt::Display;
2 |
3 | use jsc_safe::sys::*;
4 |
5 | pub struct AsyncCallback {
6 | func: JSValueRef,
7 | this: JSValueRef,
8 | args: *const JSValueRef,
9 | len: usize,
10 | exception: *mut JSValueRef,
11 | }
12 |
13 | impl Display for AsyncCallback {
14 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15 | write!(f, "AsyncCallback")
16 | }
17 | }
18 |
19 | unsafe impl Send for AsyncCallback {}
20 |
21 | impl AsyncCallback {
22 | pub fn new(
23 | func: JSValueRef,
24 | this: JSValueRef,
25 | args: *const JSValueRef,
26 | len: usize,
27 | exception: *mut JSValueRef,
28 | ) -> Self {
29 | Self {
30 | func,
31 | this,
32 | args,
33 | len,
34 | exception,
35 | }
36 | }
37 |
38 | pub fn call(self, ctx: JSGlobalContextRef) -> JSValueRef {
39 | unsafe {
40 | JSObjectCallAsFunction(
41 | ctx,
42 | self.func as *mut _,
43 | self.this as *mut _,
44 | self.len,
45 | self.args,
46 | self.exception,
47 | )
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/webapi/build.rs:
--------------------------------------------------------------------------------
1 | extern crate build_support;
2 |
3 | use std::{env, fs};
4 |
5 | fn main() -> Result<(), std::io::Error> {
6 | let current_dir = env::current_dir().unwrap();
7 | let root_dir = current_dir.parent().unwrap();
8 | let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
9 | let mut build = build_support::create_cc();
10 | build
11 | .include(root_dir.join("WebKit/WebKitBuild/bmalloc/Headers"))
12 | .include(root_dir.join("WebKit/WebKitBuild/WTF/Headers"))
13 | .include(root_dir.join("WebKit/WebKitBuild/JavaScriptCore/Headers"))
14 | .include(root_dir.join("WebKit/WebKitBuild/JavaScriptCore/PrivateHeaders"))
15 | .include(root_dir.join("WebKit/WebKitBuild/JavaScriptCore/PrivateHeaders/JavaScriptCore"))
16 | .include(root_dir.join("WebKit/WebKitBuild/JavaScriptCore/DerivedSources"))
17 | .include(current_dir.join("c-api"));
18 | if target_os == "macos" {
19 | build.flag("-DJSC_API_AVAILABLE(...)=");
20 | }
21 | for file in fs::read_dir(current_dir.join("c-api")).expect("Read c-api dir failed") {
22 | let f = file?;
23 | if f.file_type()?.is_file() && f.path().extension().and_then(|e| e.to_str()) == Some("cpp") {
24 | build.file(f.path());
25 | }
26 | }
27 | build.compile("webcore");
28 | Ok(())
29 | }
30 |
--------------------------------------------------------------------------------
/jsc-sys/c-api/binding.hpp:
--------------------------------------------------------------------------------
1 | #if defined(_MSC_VER)
2 | #define NOMINMAX // Prevent windows.h from defining min and max macros.
3 | #include
4 | #endif
5 |
6 | #include
7 | #include
8 |
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 |
19 | typedef struct WTFStringImpl WTFStringImpl;
20 | typedef struct JSContext JSContext;
21 |
22 | struct WTFString
23 | {
24 | WTFStringImpl *inner;
25 | bool is_utf8;
26 | const unsigned char *characters8;
27 | const char16_t *characters16;
28 | uint32_t length;
29 | };
30 |
31 | extern "C"
32 | {
33 | bool jsc_value_is_int(JSValueRef value);
34 | int32_t jsc_value_as_int(JSValueRef value);
35 | WTFString jsc_string_to_wtf_string(JSStringRef s);
36 | WTFString jsc_symbol_desc_string(JSValueRef value);
37 | JSStringRef jsc_string_from_static_rust_str(const char *str);
38 | void jsc_wtf_string_release(WTFStringImpl *inner);
39 | }
40 |
--------------------------------------------------------------------------------
/jsc/src/object.rs:
--------------------------------------------------------------------------------
1 | #![allow(non_upper_case_globals)]
2 |
3 | use std::{ffi::CString, ptr};
4 |
5 | use bitflags::bitflags;
6 | use jsc_sys::{
7 | JSGlobalContextRef, JSObjectRef, JSObjectSetProperty, JSStringCreateWithUTF8CString, JSValueRef,
8 | };
9 |
10 | use crate::JsValue;
11 |
12 | bitflags! {
13 | pub struct PropertyAttributes: u32 {
14 | const None = 0;
15 | const ReadOnly = 2;
16 | const DontEnum = 4;
17 | const DontDelete = 8;
18 | }
19 | }
20 |
21 | pub struct Object {
22 | pub(crate) ctx: JSGlobalContextRef,
23 | pub(crate) inner: JSObjectRef,
24 | }
25 |
26 | impl JsValue for Object {
27 | fn ctx(&self) -> JSGlobalContextRef {
28 | self.ctx
29 | }
30 |
31 | fn raw(&self) -> JSValueRef {
32 | self.inner
33 | }
34 | }
35 |
36 | impl Object {
37 | pub fn set_property>, T: JsValue>(
38 | &mut self,
39 | property: N,
40 | value: &T,
41 | attr: PropertyAttributes,
42 | ) -> Result<(), JSValueRef> {
43 | let property = CString::new(property).unwrap();
44 | let mut exception = ptr::null();
45 | unsafe {
46 | let property_js = JSStringCreateWithUTF8CString(property.as_ptr());
47 | JSObjectSetProperty(
48 | self.ctx,
49 | self.inner,
50 | property_js,
51 | value.raw(),
52 | attr.bits,
53 | &mut exception,
54 | );
55 | };
56 | if exception.is_null() {
57 | Ok(())
58 | } else {
59 | Err(exception)
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jsc-rs",
3 | "private": true,
4 | "packageManager": "yarn@3.3.0",
5 | "type": "module",
6 | "devDependencies": {
7 | "@taplo/cli": "0.5.2",
8 | "@types/react": "18.0.25",
9 | "@types/react-dom": "18.0.8",
10 | "@typescript-eslint/eslint-plugin": "5.44.0",
11 | "@typescript-eslint/parser": "5.44.0",
12 | "esbuild": "0.15.16",
13 | "eslint": "8.28.0",
14 | "eslint-config-prettier": "8.5.0",
15 | "eslint-plugin-import": "2.26.0",
16 | "husky": "8.0.2",
17 | "lint-staged": "13.0.4",
18 | "npm-run-all": "4.1.5",
19 | "prettier": "2.8.0",
20 | "typescript": "4.9.3"
21 | },
22 | "lint-staged": {
23 | "*.@(js|ts)": [
24 | "eslint --quiet --fix"
25 | ],
26 | "*.@(js|ts|json|md|yml|yaml)": [
27 | "prettier --write"
28 | ],
29 | "*.toml": [
30 | "taplo format"
31 | ],
32 | "*.rs": [
33 | "cargo fmt --"
34 | ]
35 | },
36 | "scripts": {
37 | "format": "run-p format:prettier format:rs format:toml",
38 | "format:prettier": "prettier --config ./package.json -w .",
39 | "format:rs": "cargo fmt --all",
40 | "format:toml": "taplo format",
41 | "lint": "eslint . --ext js,jsx,ts,tsx -c ./.eslintrc.yaml",
42 | "postinstall": "husky install"
43 | },
44 | "husky": {
45 | "hooks": {
46 | "pre-commit": "lint-staged && cargo fmt --all"
47 | }
48 | },
49 | "prettier": {
50 | "printWidth": 80,
51 | "semi": false,
52 | "singleQuote": true,
53 | "trailingComma": "all",
54 | "arrowParens": "always"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/jsc-sys/c-api/binding.cpp:
--------------------------------------------------------------------------------
1 | #include "binding.hpp"
2 |
3 | inline WTFString toWTFString(WTF::String s)
4 | {
5 | auto is_utf8 = s.is8Bit();
6 | auto characters8 = is_utf8 ? s.characters8() : nullptr;
7 | auto characters16 = is_utf8 ? nullptr : s.characters16();
8 | auto length = s.length();
9 | auto inner = reinterpret_cast(s.releaseImpl().leakRef());
10 | WTFString result{
11 | inner,
12 | is_utf8,
13 | characters8,
14 | characters16,
15 | length,
16 | };
17 | return result;
18 | }
19 |
20 | extern "C"
21 | {
22 | bool jsc_value_is_int(JSValueRef value)
23 | {
24 | auto jsValue = toJS(value);
25 | return jsValue.isAnyInt();
26 | }
27 |
28 | int32_t jsc_value_as_int(JSValueRef value)
29 | {
30 | auto jsValue = toJS(value);
31 | return jsValue.asInt32();
32 | }
33 |
34 | WTFString jsc_symbol_desc_string(JSValueRef value)
35 | {
36 | auto jsValue = toJS(value);
37 | auto desc = asSymbol(jsValue)->descriptiveString();
38 | return toWTFString(desc);
39 | }
40 |
41 | WTFString jsc_string_to_wtf_string(JSStringRef s)
42 | {
43 | auto ss = reinterpret_cast(s);
44 | auto wtf_string = ss->string();
45 | return toWTFString(wtf_string);
46 | }
47 |
48 | JSStringRef jsc_string_from_static_rust_str(const char *str)
49 | {
50 | auto wtf_string = WTF::String::fromUTF8(str);
51 | return OpaqueJSString::tryCreate(wtf_string).leakRef();
52 | }
53 |
54 | void jsc_wtf_string_release(WTFStringImpl *inner)
55 | {
56 | WTF::StringImpl::destroy(reinterpret_cast(inner));
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Týr
2 |
3 | Tyr is a simple runtime for JavaScript and TypeScript that uses JavaScriptCore and is built in Rust.
4 |
5 | [](https://github.com/WebKit/WebKit/releases/tag/webkitgtk-2.37.1)
6 | [](https://github.com/unicode-org/icu/releases/tag/release-71-1)
7 |
8 | ## Support Matrix
9 |
10 | | Operating System | Architectures | Versions | Notes | Status |
11 | | ---------------- | ------------- | -------------------------- | ------------------------------------- | ----------- |
12 | | Linux | x86_64 | glibc >= 2.17 | e.g. Ubuntu 14.04, Debian 9, CentOS 7 | ✅ |
13 | | Linux | arm64 | glibc >= 2.17 | e.g. Ubuntu 14.04, Debian 9, CentOS 7 | ✅ |
14 | | Linux | x86_64 | musl >= 1.1.19 | e.g. Alpine 3.8 | ✅ |
15 | | Linux | arm64 | musl >= 1.1.19 | e.g. Alpine 3.8 | coming soon |
16 | | Linux | armv7 | glibc >= 2.28 | e.g. Ubuntu 18.04, Debian 10 | coming soon |
17 | | macOS | x64 | \>= 10.15 | | ✅ |
18 | | macOS | arm64 | \>= 11 | | ✅ |
19 | | Windows | x64 | \>= Windows 10/Server 2016 | | ✅ |
20 |
--------------------------------------------------------------------------------
/jsc-sys/src/lib.rs:
--------------------------------------------------------------------------------
1 | #[cfg(not(target_os = "windows"))]
2 | extern crate compiler_builtins;
3 |
4 | use std::{fmt::Display, os::raw::c_char, slice};
5 |
6 | mod binding;
7 |
8 | pub use binding::*;
9 |
10 | #[repr(C)]
11 | #[derive(Debug, Copy, Clone)]
12 | pub struct WTFStringImpl {
13 | _unused: [u8; 0],
14 | }
15 |
16 | #[repr(C)]
17 | #[derive(Debug, Clone)]
18 | pub struct WTString {
19 | inner: *mut WTFStringImpl,
20 | pub is_utf8: bool,
21 | pub characters8: *const u8,
22 | pub characters16: *const u16,
23 | pub length: u32,
24 | }
25 |
26 | impl Display for WTString {
27 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 | if self.is_utf8 {
29 | write!(
30 | f,
31 | "{}",
32 | String::from_utf8_lossy(unsafe {
33 | slice::from_raw_parts(self.characters8, self.length as usize)
34 | })
35 | )
36 | } else {
37 | write!(
38 | f,
39 | "{}",
40 | String::from_utf16_lossy(unsafe {
41 | slice::from_raw_parts(self.characters16, self.length as usize)
42 | })
43 | )
44 | }
45 | }
46 | }
47 |
48 | impl Drop for WTString {
49 | fn drop(&mut self) {
50 | unsafe {
51 | jsc_wtf_string_release(self.inner);
52 | }
53 | }
54 | }
55 |
56 | extern "C" {
57 | pub fn jsc_value_is_int(value: JSValueRef) -> bool;
58 | pub fn jsc_value_as_int(value: JSValueRef) -> i32;
59 | pub fn jsc_string_to_wtf_string(string: JSStringRef) -> WTString;
60 | /// The created String will be leaked in runtime
61 | /// It's used for create Object Property attached to GlobalObject
62 | pub fn jsc_string_from_static_rust_str(string: *const c_char) -> JSStringRef;
63 | pub fn jsc_symbol_desc_string(symbol: JSValueRef) -> WTString;
64 | fn jsc_wtf_string_release(inner: *mut WTFStringImpl);
65 | }
66 |
--------------------------------------------------------------------------------
/tyr/src/web/btoa.rs:
--------------------------------------------------------------------------------
1 | use std::slice;
2 |
3 | use encoding_rs::mem::convert_utf16_to_str;
4 | use jsc_safe::sys::*;
5 |
6 | pub unsafe extern "C" fn btoa(
7 | ctx: JSContextRef,
8 | _function: JSObjectRef,
9 | _this: JSObjectRef,
10 | argument_count: usize,
11 | arguments: *const JSValueRef,
12 | exception: *mut JSValueRef,
13 | ) -> JSValueRef {
14 | if argument_count == 0 {
15 | return JSValueMakeUndefined(ctx);
16 | }
17 | let args = slice::from_raw_parts(arguments, argument_count);
18 | let string_to_encode = args[0];
19 | if !JSValueIsString(ctx, string_to_encode) {
20 | return JSValueMakeUndefined(ctx);
21 | }
22 | let string_to_encode = JSValueToStringCopy(ctx, string_to_encode, exception);
23 | let wtf_string = jsc_string_to_wtf_string(string_to_encode);
24 | let len = wtf_string.length as usize;
25 | let (str_to_encode, container_to_drop) = if wtf_string.is_utf8 {
26 | (
27 | slice::from_raw_parts(wtf_string.characters8, len),
28 | String::new(),
29 | )
30 | } else {
31 | let mut output = String::from_utf8_unchecked(vec![0; len * 3]);
32 | let new_length = convert_utf16_to_str(
33 | slice::from_raw_parts(wtf_string.characters16, len),
34 | &mut output,
35 | );
36 | (slice::from_raw_parts(output.as_ptr(), new_length), output)
37 | };
38 | let b64 = base64_simd::Base64::URL_SAFE;
39 | let mut output = vec![0; len * 3];
40 | let output_buf = base64_simd::OutBuf::new(output.as_mut_slice());
41 | let output_ptr = {
42 | if let Ok(o) = b64.encode(str_to_encode, output_buf) {
43 | let l = o.len();
44 | let o = slice::from_raw_parts_mut(o.as_mut_ptr(), l + 1);
45 | o[l] = b'\0';
46 | output.as_ptr()
47 | } else {
48 | std::ptr::null_mut()
49 | }
50 | };
51 | if output_ptr.is_null() {
52 | return JSValueMakeUndefined(ctx);
53 | }
54 | drop(container_to_drop);
55 | JSValueMakeString(ctx, JSStringCreateWithUTF8CString(output_ptr as *const _))
56 | }
57 |
--------------------------------------------------------------------------------
/jsc/src/class.rs:
--------------------------------------------------------------------------------
1 | use std::{ffi::CString, os::raw::c_char, ptr};
2 |
3 | use jsc_sys::{JSClassCreate, JSClassDefinition, JSClassRef, JSObjectMake};
4 |
5 | use crate::{Context, JscError, Object};
6 |
7 | #[repr(u32)]
8 | pub enum ClassAttribute {
9 | None = jsc_sys::kJSClassAttributeNone,
10 | NoAutomaticPrototype = jsc_sys::kJSClassAttributeNoAutomaticPrototype,
11 | }
12 |
13 | pub struct ClassDefinition {
14 | inner: JSClassDefinition,
15 | }
16 |
17 | impl Default for ClassDefinition {
18 | fn default() -> Self {
19 | Self {
20 | inner: JSClassDefinition {
21 | version: 0,
22 | attributes: ClassAttribute::None as u32,
23 | className: ptr::null(),
24 | parentClass: ptr::null_mut(),
25 | staticValues: ptr::null(),
26 | staticFunctions: ptr::null(),
27 | initialize: None,
28 | finalize: None,
29 | hasProperty: None,
30 | getProperty: None,
31 | setProperty: None,
32 | deleteProperty: None,
33 | getPropertyNames: None,
34 | callAsFunction: None,
35 | callAsConstructor: None,
36 | hasInstance: None,
37 | convertToType: None,
38 | },
39 | }
40 | }
41 | }
42 |
43 | impl ClassDefinition {
44 | pub fn with_name>>(mut self, name: S) -> Result {
45 | let c_name = CString::new(name)?;
46 | self.inner.className = c_name.as_ptr();
47 | Ok(Self { inner: self.inner })
48 | }
49 |
50 | pub fn with_c_name(mut self, c_name: *const c_char) -> Self {
51 | self.inner.className = c_name;
52 | Self { inner: self.inner }
53 | }
54 |
55 | pub fn with_attribute(mut self, attribute: ClassAttribute) -> Self {
56 | self.inner.attributes = attribute as u32;
57 | Self { inner: self.inner }
58 | }
59 |
60 | pub fn into_class(self) -> Class {
61 | Class {
62 | inner: unsafe { JSClassCreate(&self.inner as *const JSClassDefinition) },
63 | }
64 | }
65 | }
66 |
67 | pub struct Class {
68 | inner: JSClassRef,
69 | }
70 |
71 | impl Class {
72 | pub fn make_object(self, ctx: &Context) -> Object {
73 | unsafe { JSObjectMake(ctx.inner, self.inner, ptr::null_mut()) };
74 | Object {
75 | ctx: ctx.inner,
76 | inner: unsafe { JSObjectMake(ctx.inner, self.inner, ptr::null_mut()) },
77 | }
78 | }
79 |
80 | pub fn raw(&self) -> JSClassRef {
81 | self.inner
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/.github/workflows/docker.yaml:
--------------------------------------------------------------------------------
1 | name: Docker nightly build
2 |
3 | on:
4 | schedule:
5 | - cron: '0 1 * * 1'
6 |
7 | jobs:
8 | build_image:
9 | name: Build nodejs-rust:lts
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v3
14 |
15 | - name: Setup QEMU
16 | uses: docker/setup-qemu-action@v2
17 |
18 | - name: Setup Docker Buildx
19 | uses: docker/setup-buildx-action@v2
20 |
21 | - name: Login to GitHub Container Registry
22 | uses: docker/login-action@v2
23 | with:
24 | registry: ghcr.io
25 | username: Brooooooklyn
26 | password: ${{ secrets.GH_TOKEN }}
27 | logout: false
28 |
29 | - name: Build and push Linux x64
30 | uses: docker/build-push-action@v3
31 | with:
32 | file: linux-x64.Dockerfile
33 | platforms: linux/amd64
34 | context: .
35 | push: true
36 | tags: ghcr.io/brooooooklyn/jsc-rs/linux-builder:x64
37 |
38 | - name: Build and push Linux x64 musl
39 | uses: docker/build-push-action@v3
40 | with:
41 | file: linux-x64-musl.Dockerfile
42 | platforms: linux/amd64
43 | context: .
44 | push: true
45 | tags: ghcr.io/brooooooklyn/jsc-rs/linux-builder:x64-musl
46 |
47 | - name: Install latest libc++-dev for cross build
48 | uses: addnab/docker-run-action@v3
49 | with:
50 | image: ghcr.io/napi-rs/napi-rs/nodejs:aarch64-16
51 | options: '--user 0:0 -e GITHUB_TOKEN -v ${{ github.workspace }}/lib/llvm-14:/usr/lib/llvm-14'
52 | run: >-
53 | apt-get update &&
54 | apt-get install -y wget &&
55 | wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - &&
56 | echo "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-14 main" >> /etc/apt/sources.list &&
57 | echo "deb-src http://apt.llvm.org/focal/ llvm-toolchain-focal-14 main" >> /etc/apt/sources.list &&
58 | apt-get update &&
59 | apt-get install libc++-14-dev libc++abi-14-dev -y --fix-missing --no-install-recommends
60 | - name: Build and push Linux aarch64
61 | uses: docker/build-push-action@v3
62 | with:
63 | file: linux-aarch64.Dockerfile
64 | context: .
65 | platforms: linux/amd64
66 | push: true
67 | tags: ghcr.io/brooooooklyn/jsc-rs/linux-builder:aarch64
68 |
--------------------------------------------------------------------------------
/tyr/src/timer.rs:
--------------------------------------------------------------------------------
1 | use std::{
2 | collections::HashMap,
3 | ptr, slice,
4 | sync::{
5 | atomic::{AtomicU32, Ordering},
6 | Mutex,
7 | },
8 | time::Duration,
9 | };
10 |
11 | use jsc_safe::sys::*;
12 | use once_cell::sync::Lazy;
13 | use tokio::{task::JoinHandle, time::sleep};
14 |
15 | use crate::async_callback::AsyncCallback;
16 |
17 | static TIMER_ID: AtomicU32 = AtomicU32::new(0);
18 | static TIMER_MAP: Lazy>>> = Lazy::new(|| Default::default());
19 |
20 | pub unsafe extern "C" fn set_timeout(
21 | ctx: JSContextRef,
22 | _function: JSObjectRef,
23 | _this: JSObjectRef,
24 | argument_count: usize,
25 | arguments: *const JSValueRef,
26 | exception: *mut JSValueRef,
27 | ) -> JSValueRef {
28 | crate::queue_async_task();
29 | let args = slice::from_raw_parts(arguments, argument_count);
30 | let callback = args[0];
31 | let duration_time = if let Some(dur) = args.get(1) {
32 | JSValueToNumber(ctx, *dur, exception) as i32
33 | } else {
34 | 4
35 | };
36 | let duration = if duration_time < 0 {
37 | Duration::from_millis(0)
38 | } else {
39 | Duration::from_millis(duration_time as u64)
40 | };
41 | let timer_id = TIMER_ID.fetch_add(1, Ordering::Relaxed);
42 | let callback = AsyncCallback::new(
43 | callback,
44 | JSValueMakeUndefined(ctx),
45 | if argument_count == 1 {
46 | ptr::null()
47 | } else {
48 | args[1..].as_ptr()
49 | },
50 | if argument_count == 1 {
51 | 0
52 | } else {
53 | argument_count - 2
54 | },
55 | exception,
56 | );
57 | let jh = crate::GLOBAL_RUNTIME.spawn(async move {
58 | sleep(duration).await;
59 | let s = crate::GLOBAL_SENDER.get_unchecked();
60 | if !s.is_closed() {
61 | if let Err(err) = s.send(callback) {
62 | eprintln!("{err}");
63 | }
64 | }
65 | });
66 | let mut timer_map_lock_guard = TIMER_MAP.lock().unwrap();
67 | timer_map_lock_guard.insert(timer_id, jh);
68 | JSValueMakeNumber(ctx, timer_id as f64)
69 | }
70 |
71 | pub unsafe extern "C" fn clear_timeout(
72 | ctx: JSContextRef,
73 | _function: JSObjectRef,
74 | _this: JSObjectRef,
75 | argument_count: usize,
76 | arguments: *const JSValueRef,
77 | _exception: *mut JSValueRef,
78 | ) -> JSValueRef {
79 | let args = slice::from_raw_parts(arguments, argument_count);
80 | let timer_id = args[0];
81 | let timer_id = jsc_value_as_int(timer_id) as u32;
82 | let timer_map_lock_guard = TIMER_MAP.lock().unwrap();
83 | if let Some(jh) = timer_map_lock_guard.get(&timer_id) {
84 | jh.abort();
85 | crate::dequeue_async_task();
86 | }
87 | drop(timer_map_lock_guard);
88 | JSValueMakeUndefined(ctx)
89 | }
90 |
--------------------------------------------------------------------------------
/jsc/src/lib.rs:
--------------------------------------------------------------------------------
1 | use std::{ffi::CString, ptr};
2 |
3 | use jsc_sys::{
4 | JSContextGetGlobalObject, JSContextGroupCreate, JSContextGroupRef, JSContextGroupRelease,
5 | JSEvaluateScript, JSGarbageCollect, JSGlobalContextCreateInGroup, JSGlobalContextRef,
6 | JSGlobalContextRelease, JSObjectCallAsFunctionCallback, JSObjectMakeFunctionWithCallback,
7 | JSStringCreateWithUTF8CString, JSStringRef, JSValueRef,
8 | };
9 |
10 | mod class;
11 | mod error;
12 | mod object;
13 | mod value;
14 |
15 | pub use class::*;
16 | pub use error::*;
17 | pub use jsc_sys as sys;
18 | pub use object::*;
19 | pub use value::*;
20 |
21 | pub struct Context {
22 | group: JSContextGroupRef,
23 | pub(crate) inner: JSGlobalContextRef,
24 | }
25 |
26 | impl Context {
27 | pub fn new() -> Self {
28 | Self::create(None)
29 | }
30 |
31 | pub fn with_global_class(class: Class) -> Self {
32 | Self::create(Some(class))
33 | }
34 |
35 | fn create(global_class: Option) -> Self {
36 | let group = unsafe { JSContextGroupCreate() };
37 | let inner = unsafe {
38 | JSGlobalContextCreateInGroup(
39 | group,
40 | global_class.map(|c| c.raw()).unwrap_or(ptr::null_mut()),
41 | )
42 | };
43 | Context { group, inner }
44 | }
45 |
46 | pub fn global(&self) -> Object {
47 | Object {
48 | ctx: self.inner,
49 | inner: unsafe { JSContextGetGlobalObject(self.inner) },
50 | }
51 | }
52 |
53 | pub fn create_function>>(
54 | &self,
55 | name: N,
56 | callback: JSObjectCallAsFunctionCallback,
57 | ) -> Result