Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
ba33209
JavaScriptCore wrapper previously passed nullptr to JSObjectCallAsFun…
matthargett Oct 12, 2025
e1fce6b
Add the node-lite test suite Vlad added into hermes-windows. The JSC …
matthargett Oct 12, 2025
e9aa436
Android tests now pass. StdoutLogger was holding on to destroyed mute…
matthargett Oct 12, 2025
9ebd4ce
Run the macOS NodeApiTests under sanitizers, which found another bug …
matthargett Oct 13, 2025
eb2eae4
Fix build errors. This deduplicates struct definitions that were inli…
matthargett Oct 15, 2025
a18d1cc
always build the napi tests
matthargett Oct 16, 2025
2eabe90
try and get address sanitizer and thread sanitizer to run on Android,…
matthargett Oct 16, 2025
8362442
Add N-API version/conformance roadmap (folds in engine-compat baseline)
matthargett Jun 4, 2026
1180892
Restore NodeApi tests build on current macOS toolchain
matthargett Jun 4, 2026
900b8eb
Restore Android NodeApi test build (compiles/links/installs/runs on e…
matthargett Jun 4, 2026
7714d9f
Fix Android NodeApi harness JNI crash; wire up SetNodeApiTestEnvironment
matthargett Jun 4, 2026
1cedda1
Android: make the NodeApi conformance tests actually execute on-device
matthargett Jun 4, 2026
33412f5
Android: enter the V8 context in jsr_open_napi_env_scope (fix napi_cr…
matthargett Jun 4, 2026
b15d528
Android/in-process node_lite: let ExitOnException propagate the fatal…
matthargett Jun 4, 2026
b35d519
Android/in-process: make node_lite teardown destructors exception-safe
matthargett Jun 4, 2026
f4e170b
Android/in-process: guard the fatal handler against throwing while un…
matthargett Jun 4, 2026
38864e4
Android/in-process: drop noexcept from throwing error-exit functions …
matthargett Jun 4, 2026
39a87ea
Android: skip in-process js-native-api addon tests pending shared-lib…
matthargett Jun 4, 2026
b559f69
docs(roadmap): document Android in-process addon-load constraint + sh…
matthargett Jun 4, 2026
f32130e
Android: statically link conformance addons into the test binary (run…
matthargett Jun 5, 2026
f0d1c2e
Android tests: pump native stdout/stderr to logcat
matthargett Jun 5, 2026
1938ff0
docs(roadmap): Android v5 js-native-api now green via static linking …
matthargett Jun 5, 2026
d4231ee
Android: drop the now-dead dynamic-.node build machinery (superseded …
matthargett Jun 5, 2026
04e3158
Android: remove vestigial V8Platform scaffolding from the env holder
matthargett Jun 5, 2026
b51d1a7
docs(roadmap): record node-api-cts FetchContent evaluation (task 6) —…
matthargett Jun 5, 2026
58223c0
Android: dlopen conformance addons as dynamic .node backed by a share…
matthargett Jun 5, 2026
538fe04
docs(roadmap): Android uses dynamic .node + shared libnapi.so (aligns…
matthargett Jun 5, 2026
44d903e
Android tests: use AndroidExtensions StdoutLogger for stdout->logcat
matthargett Jun 5, 2026
d225e74
docs(roadmap): stdout->logcat is via AndroidExtensions StdoutLogger, …
matthargett Jun 5, 2026
e94c72e
Sync napi shared-lib change with PR #183 (gate behind JSR_NAPI_SHARED…
matthargett Jun 5, 2026
17316b7
Tests: enable the v5-clean reference double-free conformance test
matthargett Jun 5, 2026
ed74d61
docs(roadmap): reference-test staging + GC-safety review (re hermes-w…
matthargett Jun 5, 2026
3f63934
Node-API: address #116 review (JSC call dispatch, status type, Window…
matthargett Jun 5, 2026
eee2960
Node-API: raise default NAPI_VERSION 5 -> 7
matthargett Jun 5, 2026
30a1dff
JSC: implement napi_set/get_instance_data (N-API v6)
matthargett Jun 5, 2026
d728c4f
JSC: implement N-API v6 BigInt + v7 ArrayBuffer detach
matthargett Jun 5, 2026
8a56437
tests: note detach/property_names conformance tests are v9-gated (nod…
matthargett Jun 5, 2026
84ede39
JSC: detect BigInt in napi_typeof on jsc-android (cached typeof predi…
matthargett Jun 5, 2026
d69bade
Chakra/JSI: N-API v6/v7 surface for the cross-engine version bump
matthargett Jun 5, 2026
98bca40
Tests/NodeApi: add missing <cstdint> in child_process.h (fixes Linux …
matthargett Jun 5, 2026
2e70227
Tests/node_lite: select the napi engine by compile-define, not platform
matthargett Jun 5, 2026
9e6c171
Tests/NodeApi: link napi into addons on Windows/UWP so napi_* resolve…
matthargett Jun 5, 2026
522f310
Tests/Android: run test_bigint on V8 only (jsc-android ~2020 BigInt s…
matthargett Jun 5, 2026
f21ccc0
build: suppress MSVC C4875 from vendored GSL to unblock the Windows b…
matthargett Jun 5, 2026
e55e6bc
Core/Node-API-JSI: add the shared napi include path (fixes pre-existi…
matthargett Jun 5, 2026
172a990
JSC: feature-detect BigInt; throw ENOTSUP where the engine lacks it (…
matthargett Jun 5, 2026
32d37fa
Tests/NodeApi: add literal-free test_bigint_unsupported fallback; gat…
matthargett Jun 5, 2026
8e6a62a
Tests/Android: surface in-process node_lite failure detail to logcat
matthargett Jun 5, 2026
d79ecbd
Merge upstream main into napi-v7 and refresh conformance
matthargett Jul 21, 2026
e281b7a
Complete N-API v7 coverage for current CTS
matthargett Jul 21, 2026
f51ead9
Fix JSC v7 conformance and CI portability
matthargett Jul 21, 2026
46dd367
Preserve JSC null external buffer semantics
matthargett Jul 21, 2026
fb01dd8
Release node_lite function callback data
matthargett Jul 21, 2026
4d8a140
Keep system JSC setup off Android
matthargett Jul 21, 2026
bb3b45b
Align Android BigInt conformance selection
matthargett Jul 21, 2026
012cd98
Export full Hermes Node-API surface on Android
matthargett Jul 21, 2026
6a436a1
Run Node-API conformance on Hermes
matthargett Jul 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions .github/workflows/build-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,25 @@ jobs:

- name: Run Tests
working-directory: Build/ubuntu/Tests/UnitTests
run: ./UnitTests
env:
TSAN_OPTIONS: ${{ inputs.enable-thread-sanitizer && format('suppressions={0}/.github/tsan_suppressions.txt', github.workspace) || '' }}
run: |
# JSC's concurrent GC on Linux uses SIGUSR1 + sem_wait to suspend mutator
# threads at safepoints. TSan's signal interception delays SIGUSR1 delivery
# indefinitely, deadlocking the Collector Thread's sem_wait. Disabling the
# concurrent collector removes the dedicated Collector Thread, so GC runs
# on the mutator without cross-thread signals. macOS JSC uses Mach
# thread_suspend() and is unaffected.
JSC_useConcurrentGC: ${{ inputs.enable-thread-sanitizer && '0' || '' }}
if [[ "${{ inputs.enable-thread-sanitizer }}" == "true" ]]; then
export TSAN_OPTIONS="suppressions=${{ github.workspace }}/.github/tsan_suppressions.txt"
export JSC_useConcurrentGC=0
fi
./UnitTests

- name: Run Node-API v7 conformance subset
working-directory: Build/ubuntu/Tests/NodeApi
run: |
if [[ "${{ inputs.enable-thread-sanitizer }}" == "true" ]]; then
export TSAN_OPTIONS="suppressions=${{ github.workspace }}/.github/tsan_suppressions.txt"
export JSC_useConcurrentGC=0
fi
./NodeApiTests \
--gtest_filter=js_native_api.test_bigint_test_js:js_native_api.test_instance_data_test_js:js_native_api.test_typedarray_test_js
21 changes: 20 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ FetchContent_Declare(quickjs-ng
GIT_REPOSITORY https://github.com/quickjs-ng/quickjs.git
GIT_TAG 93d3f7df465027f487ed37e175a0bc3012fee79e
EXCLUDE_FROM_ALL)

# --------------------------------------------------

FetchContent_MakeAvailable(CMakeExtensions)
Expand All @@ -75,6 +75,14 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# A newer MSVC (windows-latest) emits C4875 ("a non-string literal argument to [[gsl::suppress]] is
# deprecated") from the vendored GSL headers, which several targets compile with warnings-as-error.
# Suppress this dependency-side deprecation so the Windows build isn't broken by toolchain drift. Must
# be set before arcana.cpp / Core / Polyfills are added below so they inherit it.
if(MSVC)
add_compile_options(/wd4875)
endif()

# --------------------------------------------------
# Options
# --------------------------------------------------
Expand Down Expand Up @@ -154,6 +162,17 @@ endif()
FetchContent_MakeAvailable_With_Message(arcana.cpp)
set_property(TARGET arcana PROPERTY FOLDER Dependencies)

if(ANDROID)
FetchContent_GetProperties(AndroidExtensions)
if(NOT AndroidExtensions_POPULATED)
FetchContent_Populate(AndroidExtensions)
FetchContent_GetProperties(AndroidExtensions)
add_subdirectory(${androidextensions_SOURCE_DIR} ${androidextensions_BINARY_DIR})
else()
add_subdirectory(${androidextensions_SOURCE_DIR} ${androidextensions_BINARY_DIR})
endif()
endif()

if(JSRUNTIMEHOST_POLYFILL_XMLHTTPREQUEST OR JSRUNTIMEHOST_POLYFILL_FETCH)
FetchContent_MakeAvailable_With_Message(UrlLib)
set_property(TARGET UrlLib PROPERTY FOLDER Dependencies)
Expand Down
7 changes: 6 additions & 1 deletion Core/Node-API-JSI/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,12 @@ if(NOT TARGET jsi)
endif()

target_include_directories(napi
PUBLIC "include")
PUBLIC "include"
# napi.h pulls in the shared <napi/js_native_api_types.h>, which lives in
# Core/Node-API/Include/Shared. That sibling isn't built when the engine is JSI
# (Core/CMakeLists.txt selects Node-API-JSI instead of Node-API), so reference the shared
# headers directly here -- otherwise the JSI napi fails to compile with C1083.
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../Node-API/Include/Shared")

target_link_libraries(napi
PUBLIC jsi)
Expand Down
37 changes: 1 addition & 36 deletions Core/Node-API-JSI/Include/napi/napi.h
Original file line number Diff line number Diff line change
@@ -1,49 +1,14 @@
#pragma once

#include <jsi/jsi.h>
#include <napi/js_native_api_types.h>
#include <functional>
#include <initializer_list>
#include <memory>
#include <string>
#include <vector>
#include <optional>

// Copied from js_native_api_types.h (https://git.io/J8aI5)
typedef enum {
napi_default = 0,
napi_writable = 1 << 0,
napi_enumerable = 1 << 1,
napi_configurable = 1 << 2,
} napi_property_attributes;

typedef enum {
// ES6 types (corresponds to typeof)
napi_undefined,
napi_null,
napi_boolean,
napi_number,
napi_string,
napi_symbol,
napi_object,
napi_function,
napi_external,
} napi_valuetype;

typedef enum {
napi_int8_array,
napi_uint8_array,
napi_uint8_clamped_array,
napi_int16_array,
napi_uint16_array,
napi_int32_array,
napi_uint32_array,
napi_float32_array,
napi_float64_array,
// JSI doesn't support bigint.
// napi_bigint64_array,
// napi_biguint64_array,
} napi_typedarray_type;

struct napi_env__ {
napi_env__(facebook::jsi::Runtime& rt)
: rt{rt}
Expand Down
97 changes: 81 additions & 16 deletions Core/Node-API/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
# Set per-platform defaults if unspecified.
if(WIN32)
set(NAPI_JAVASCRIPT_ENGINE "Chakra" CACHE STRING "JavaScript engine for Node-API")
elseif(APPLE)
set(NAPI_JAVASCRIPT_ENGINE "JavaScriptCore" CACHE STRING "JavaScript engine for Node-API")
elseif(ANDROID)
set(NAPI_JAVASCRIPT_ENGINE "V8" CACHE STRING "JavaScript engine for Node-API")
elseif(UNIX)
set(NAPI_JAVASCRIPT_ENGINE "JavaScriptCore" CACHE STRING "JavaScript engine for Node-API")
set(JAVASCRIPTCORE_LIBRARY "/usr/lib/x86_64-linux-gnu/libjavascriptcoregtk-4.1.so" CACHE STRING "Path to the JavaScriptCore shared library")
else()
message(FATAL_ERROR "Unable to select Node-API JavaScript engine for platform")
# Set per-platform defaults only when the caller did not select an engine.
if(NOT NAPI_JAVASCRIPT_ENGINE)
if(WIN32)
set(NAPI_JAVASCRIPT_ENGINE "Chakra" CACHE STRING "JavaScript engine for Node-API")
elseif(APPLE)
set(NAPI_JAVASCRIPT_ENGINE "JavaScriptCore" CACHE STRING "JavaScript engine for Node-API")
elseif(ANDROID)
set(NAPI_JAVASCRIPT_ENGINE "V8" CACHE STRING "JavaScript engine for Node-API")
elseif(UNIX)
set(NAPI_JAVASCRIPT_ENGINE "JavaScriptCore" CACHE STRING "JavaScript engine for Node-API")
else()
message(FATAL_ERROR "Unable to select Node-API JavaScript engine for platform")
endif()
endif()

if(UNIX AND NOT APPLE AND NOT ANDROID AND NAPI_JAVASCRIPT_ENGINE STREQUAL "JavaScriptCore")
find_library(JAVASCRIPTCORE_LIBRARY javascriptcoregtk-4.1)
if(NOT JAVASCRIPTCORE_LIBRARY)
message(FATAL_ERROR "JavaScriptCore library not found. Please install libwebkit2gtk-4.1-dev")
endif()
endif()

set(SOURCES
Expand Down Expand Up @@ -94,6 +102,13 @@ if(NAPI_BUILD_ABI)
else()
message(FATAL_ERROR "Unsupported JavaScript engine: ${NAPI_JAVASCRIPT_ENGINE}")
endif()

# The maintained Apple and WebKitGTK headers/runtimes expose the
# BigInt typed-array enum values. The frozen jsc-android package does
# not, so keep its capability-gated build on the older enum surface.
if(NOT ANDROID)
set(JSR_JSC_HAS_BIGINT_TYPED_ARRAYS ON)
endif()
elseif(NAPI_JAVASCRIPT_ENGINE STREQUAL "V8")
set(SOURCES ${SOURCES}
"Source/env_v8.cc"
Expand Down Expand Up @@ -187,9 +202,9 @@ Make sure Hermes was fetched at the top-level CMakeLists.txt and NAPI_JAVASCRIPT
# (bigint::exponentiate, regex::parseRegex, hbc::compileEvalModule,
# llvh::raw_ostream, platform_unicode::normalize, ...) which
# `hermesvm_a` bundles together for us.
set(LINK_LIBRARIES ${LINK_LIBRARIES}
PRIVATE hermesNapi
PRIVATE hermesvm_a)
set(NAPI_HERMES_LINK_LIBRARIES
hermesNapi
hermesvm_a)

# `hermes_napi.h` lives in Hermes's `API/napi/` source directory and
# is NOT installed alongside the other `include/hermes/napi/`
Expand Down Expand Up @@ -261,11 +276,61 @@ Make sure Hermes was fetched at the top-level CMakeLists.txt and NAPI_JAVASCRIPT
message(STATUS "Selected ${NAPI_JAVASCRIPT_ENGINE}")
endif()

add_library(napi ${SOURCES})
# On Android, native addons are dlopen'd as standalone .node modules and resolve their napi_* imports
# from a shared napi at load time -- bionic will not surface a statically-linked host's napi to a
# dlopen'd module, so the host and every addon must share a single libnapi.so. Default napi to a
# shared library on Android so that model works out of the box; an integrator who wants a static napi
# (e.g. for size/packaging) can override with -DJSR_NAPI_SHARED=OFF. The option defaults OFF on other
# platforms, where napi keeps following the project's default library type (i.e. honors
# BUILD_SHARED_LIBS).
set(JSR_NAPI_SHARED_DEFAULT OFF)
if(ANDROID)
set(JSR_NAPI_SHARED_DEFAULT ON)
endif()
option(JSR_NAPI_SHARED "Build napi as a shared library (libnapi.so)" ${JSR_NAPI_SHARED_DEFAULT})

if(JSR_NAPI_SHARED)
add_library(napi SHARED ${SOURCES})
else()
add_library(napi ${SOURCES})
endif()

target_include_directories(napi ${INCLUDE_DIRECTORIES})
target_link_libraries(napi ${LINK_LIBRARIES})

if(NAPI_JAVASCRIPT_ENGINE STREQUAL "Hermes")
if(JSR_NAPI_SHARED)
# hermesNapi is a static archive. When it is folded into libnapi.so,
# the linker would normally extract only the objects referenced by
# env_hermes.cc, leaving most exported napi_* entry points out of the
# shared library. Android addons then fail to resolve even core APIs
# such as napi_create_arraybuffer and napi_wrap. Include the complete
# archive so the shared library provides the full Node-API surface.
if(MSVC)
target_link_libraries(napi PRIVATE ${NAPI_HERMES_LINK_LIBRARIES})
target_link_options(napi PRIVATE "/WHOLEARCHIVE:$<TARGET_FILE:hermesNapi>")
else()
target_link_libraries(napi PRIVATE
"-Wl,--whole-archive"
hermesNapi
"-Wl,--no-whole-archive"
hermesvm_a)
endif()
else()
target_link_libraries(napi PRIVATE ${NAPI_HERMES_LINK_LIBRARIES})
endif()
endif()

if(JSR_JSC_HAS_BIGINT_TYPED_ARRAYS)
target_compile_definitions(napi PRIVATE JSR_JSC_HAS_BIGINT_TYPED_ARRAYS=1)
endif()

# Expose the selected engine as a compile definition so engine-agnostic consumers (the node_lite test
# harness) can branch on engine capability instead of guessing from the platform
# (__APPLE__ == JSC / __ANDROID__ == V8 breaks Android-JSC, Linux-V8, Windows-Chakra, ...).
string(TOUPPER "${NAPI_JAVASCRIPT_ENGINE}" NAPI_ENGINE_UPPER)
target_compile_definitions(napi PUBLIC JSR_NAPI_ENGINE_${NAPI_ENGINE_UPPER})

if(NAPI_JAVASCRIPT_ENGINE STREQUAL "Hermes")
# Apply Hermes-specific warning suppressions ONLY to env_hermes.cc so
# they don't relax the rules for the rest of the napi sources.
Expand Down
3 changes: 3 additions & 0 deletions Core/Node-API/Include/Engine/Hermes/napi/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,7 @@ namespace Napi
// top-level dispatch. Equivalent engines (V8, Chakra) auto-drain
// microtasks at scope exit; Hermes requires an explicit drainJobs().
void DrainJobs(Napi::Env env);

// Force a collection for conformance tests that expose global.gc().
void CollectGarbage(Napi::Env env);
}
2 changes: 1 addition & 1 deletion Core/Node-API/Include/Shared/napi/js_native_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

// [BABYLON-NATIVE-ADDITION]
#ifndef NAPI_VERSION
#define NAPI_VERSION 5
#define NAPI_VERSION 7
#endif

// This file needs to be compatible with C compilers.
Expand Down
2 changes: 1 addition & 1 deletion Core/Node-API/Include/Shared/napi/js_native_api_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

// [BABYLON-NATIVE-ADDITION]
#ifndef NAPI_VERSION
#define NAPI_VERSION 5
#define NAPI_VERSION 7
#endif

// This file needs to be compatible with C compilers.
Expand Down
2 changes: 1 addition & 1 deletion Core/Node-API/Include/Shared/napi/napi.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
#define NODE_ADDON_API_DISABLE_NODE_SPECIFIC
#endif
#ifndef NAPI_VERSION
#define NAPI_VERSION 5
#define NAPI_VERSION 7
#endif
#ifndef NAPI_HAS_THREADS
#define NAPI_HAS_THREADS 0
Expand Down
11 changes: 10 additions & 1 deletion Core/Node-API/Source/env_hermes.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
// `hermes_run_script` directly — so the linker is never asked to find
// the 4-arg symbol).
//
// We keep `NAPI_VERSION` at the shared default (5) so the inline wrappers
// We keep `NAPI_VERSION` at the shared default (7) so the inline wrappers
// in napi-inl.h that target newer NAPI revisions (e.g.
// `Env::GetModuleFileName` which calls `node_api_get_module_file_name`)
// aren't pulled in — they would reference symbols absent from our shared
Expand Down Expand Up @@ -166,6 +166,15 @@ namespace Napi
(void)runtime->drainJobs();
}

void CollectGarbage(Napi::Env env)
{
hermes::vm::Runtime* runtime = LookupRuntime(env);
if (runtime != nullptr)
{
runtime->collect("node_lite explicit collection");
}
}

Napi::Value Eval(Napi::Env env, const char* source, const char* sourceUrl)
{
napi_env env_ptr{env};
Expand Down
14 changes: 14 additions & 0 deletions Core/Node-API/Source/env_quickjs.cc
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,20 @@ namespace Napi
napi_env env_ptr{env};
if (env_ptr)
{
// Node-API instance data belongs to the environment rather than a
// JS object. Finalize it while both the context and env are still
// valid; user finalizers are permitted to call Node-API.
if (env_ptr->instance_data_finalize != nullptr)
{
auto finalize = env_ptr->instance_data_finalize;
void* data = env_ptr->instance_data;
void* hint = env_ptr->instance_data_finalize_hint;
env_ptr->instance_data = nullptr;
env_ptr->instance_data_finalize = nullptr;
env_ptr->instance_data_finalize_hint = nullptr;
finalize(env_ptr, data, hint);
}

// Release every strong napi_ref still outstanding. This mirrors
// the V8 impl (napi_env__::DeleteMe) and is essential on QuickJS:
// any surviving strong ref pins a JS value from outside the GC
Expand Down
Loading