Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
25dcff7
JavaScriptCore wrapper previously passed nullptr to JSObjectCallAsFun…
matthargett Oct 12, 2025
efcb060
Add the node-lite test suite Vlad added into hermes-windows. The JSC …
matthargett Oct 12, 2025
8dbd11e
Android tests now pass. StdoutLogger was holding on to destroyed mute…
matthargett Oct 12, 2025
dfaccbe
Run the macOS NodeApiTests under sanitizers, which found another bug …
matthargett Oct 13, 2025
ef2949f
Fix build errors. This deduplicates struct definitions that were inli…
matthargett Oct 15, 2025
b725184
always build the napi tests
matthargett Oct 16, 2025
c68b5e9
try and get address sanitizer and thread sanitizer to run on Android,…
matthargett Oct 16, 2025
1952f7b
Add N-API version/conformance roadmap (folds in engine-compat baseline)
matthargett Jun 4, 2026
cc265c3
Restore NodeApi tests build on current macOS toolchain
matthargett Jun 4, 2026
4eb18cc
Restore Android NodeApi test build (compiles/links/installs/runs on e…
matthargett Jun 4, 2026
ca21767
Fix Android NodeApi harness JNI crash; wire up SetNodeApiTestEnvironment
matthargett Jun 4, 2026
c486dc4
Android: make the NodeApi conformance tests actually execute on-device
matthargett Jun 4, 2026
ab74f9e
Android: enter the V8 context in jsr_open_napi_env_scope (fix napi_cr…
matthargett Jun 4, 2026
c9ff696
Android/in-process node_lite: let ExitOnException propagate the fatal…
matthargett Jun 4, 2026
9e0a62c
Android/in-process: make node_lite teardown destructors exception-safe
matthargett Jun 4, 2026
09e9678
Android/in-process: guard the fatal handler against throwing while un…
matthargett Jun 4, 2026
4fea275
Android/in-process: drop noexcept from throwing error-exit functions …
matthargett Jun 4, 2026
0a78615
Android: skip in-process js-native-api addon tests pending shared-lib…
matthargett Jun 4, 2026
a8282b7
docs(roadmap): document Android in-process addon-load constraint + sh…
matthargett Jun 4, 2026
e0484e1
Android: statically link conformance addons into the test binary (run…
matthargett Jun 5, 2026
aa1daff
Android tests: pump native stdout/stderr to logcat
matthargett Jun 5, 2026
9013f87
docs(roadmap): Android v5 js-native-api now green via static linking …
matthargett Jun 5, 2026
7ca16d9
Android: drop the now-dead dynamic-.node build machinery (superseded …
matthargett Jun 5, 2026
8949da4
Android: remove vestigial V8Platform scaffolding from the env holder
matthargett Jun 5, 2026
7b20e53
docs(roadmap): record node-api-cts FetchContent evaluation (task 6) —…
matthargett Jun 5, 2026
46c7664
Android: dlopen conformance addons as dynamic .node backed by a share…
matthargett Jun 5, 2026
ed90fd5
docs(roadmap): Android uses dynamic .node + shared libnapi.so (aligns…
matthargett Jun 5, 2026
4fc648a
Android tests: use AndroidExtensions StdoutLogger for stdout->logcat
matthargett Jun 5, 2026
56f26af
docs(roadmap): stdout->logcat is via AndroidExtensions StdoutLogger, …
matthargett Jun 5, 2026
6f2737b
Sync napi shared-lib change with PR #183 (gate behind JSR_NAPI_SHARED…
matthargett Jun 5, 2026
94d7818
Tests: enable the v5-clean reference double-free conformance test
matthargett Jun 5, 2026
c3d885f
docs(roadmap): reference-test staging + GC-safety review (re hermes-w…
matthargett Jun 5, 2026
2867083
Node-API: address #116 review (JSC call dispatch, status type, Window…
matthargett Jun 5, 2026
63e1604
Keep JSI's napi.h self-contained (restores upstream's inlined type copy)
matthargett Sep 1, 2026
a197dc2
Tests/NodeApi: add missing <cstdint> in child_process.h (fixes Linux …
matthargett Jun 5, 2026
ddd6dc6
Tests/node_lite: select the napi engine by compile-define, not platform
matthargett Jun 5, 2026
4137a99
Tests/NodeApi: link napi into addons on Windows/UWP so napi_* resolve…
matthargett Jun 5, 2026
908851b
Tests: don't build the C-API conformance harness for the JSI backend
matthargett Sep 1, 2026
256daf9
Don't hard-fail the Linux JSC library probe (breaks the Hermes host b…
matthargett Sep 1, 2026
b059acf
Tests/Android: build the on-device Node-API harness only for validate…
matthargett Sep 1, 2026
ad6bfbc
Don't default napi to a shared library for the Hermes engine on Android
matthargett Sep 1, 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
11 changes: 11 additions & 0 deletions CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -154,6 +154,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
25 changes: 24 additions & 1 deletion Core/Node-API/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -261,7 +261,24 @@ 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 AND NOT NAPI_JAVASCRIPT_ENGINE STREQUAL "Hermes")
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})
Expand All@@ -277,5 +294,11 @@ if(NAPI_JAVASCRIPT_ENGINE STREQUAL "Hermes")
NAPI_HERMES_MAX_HEAP_SIZE_MB=${NAPI_HERMES_MAX_HEAP_SIZE_MB})
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})

set_property(TARGET napi PROPERTY FOLDER Dependencies)
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCES})
42 changes: 35 additions & 7 deletions Core/Node-API/Source/js_native_api_javascriptcore.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -740,6 +740,9 @@ struct napi_ref__ {
CHECK_NAPI(ReferenceInfo::GetObjectId(env, _value, &_objectId));
if (_objectId == 0) {
CHECK_NAPI(ReferenceInfo::Initialize(env, _value, [value = _value](ReferenceInfo* info) {
if (info->Env()->shutting_down) {
return;
}
auto entry{info->Env()->active_ref_values.find(value)};
// NOTE: The finalizer callback is actually on a "sentinel" JS object that is linked to the
// actual JS object we are trying to track. This means it is possible for the tracked object
Expand DownExpand Up@@ -841,6 +844,18 @@ void napi_env__::deinit_symbol(JSValueRef symbol) {
JSValueUnprotect(context, symbol);
}

void napi_env__::init_function_prototype_call() {
// Capture the canonical Function.prototype.call once, at env init, so napi_call_function does not
// depend on a target function's own (user-overridable) "call" property.
JSObjectRef global = JSContextGetGlobalObject(context);
JSValueRef function_ctor = JSObjectGetProperty(context, global, JSString("Function"), nullptr);
JSObjectRef function_ctor_obj = JSValueToObject(context, function_ctor, nullptr);
JSValueRef prototype = JSObjectGetProperty(context, function_ctor_obj, JSString("prototype"), nullptr);
JSObjectRef prototype_obj = JSValueToObject(context, prototype, nullptr);
function_prototype_call = JSObjectGetProperty(context, prototype_obj, JSString("call"), nullptr);
JSValueProtect(context, function_prototype_call);
}

// Warning: Keep in-sync with napi_status enum
static const char* error_messages[] = {
nullptr,
Expand DownExpand Up@@ -1647,14 +1662,27 @@ napi_status napi_call_function(napi_env env,
CHECK_ARG(env, argv);
}

JSObjectRef function_object = ToJSObject(env, func);

std::vector<JSValueRef> call_args(argc + 1);
call_args[0] = ToJSValue(recv);
for (size_t i = 0; i < argc; ++i) {
call_args[i + 1] = ToJSValue(argv[i]);
}

JSValueRef exception{};
JSValueRef return_value{JSObjectCallAsFunction(
env->context,
ToJSObject(env, func),
JSValueIsUndefined(env->context, ToJSValue(recv)) ? nullptr : ToJSObject(env, recv),
argc,
ToJSValues(argv),
&exception)};
// Invoke through the canonical Function.prototype.call (captured at env init), not the target's own
// "call" property -- user code could override func.call and change native call behavior.
JSObjectRef call_object =
JSValueToObject(env->context, env->function_prototype_call, &exception);
CHECK_JSC(env, exception);

JSValueRef return_value{JSObjectCallAsFunction(env->context,
call_object,
function_object,
call_args.size(),
call_args.data(),
&exception)};
Comment thread
matthargett marked this conversation as resolved.
CHECK_JSC(env, exception);

if (result != nullptr) {
Expand Down
6 changes: 6 additions & 0 deletions Core/Node-API/Source/js_native_api_javascriptcore.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,11 +15,13 @@ struct napi_env__ {
napi_extended_error_info last_error{nullptr, nullptr, 0, napi_ok};
std::unordered_map<napi_value, std::uintptr_t> active_ref_values{};
std::list<napi_ref> strong_refs{};
bool shutting_down{false};

JSValueRef constructor_info_symbol{};
JSValueRef function_info_symbol{};
JSValueRef reference_info_symbol{};
JSValueRef wrapper_info_symbol{};
JSValueRef function_prototype_call{};

// Escapable scope bookkeeping: token -> whether that scope has escaped. Values
// are rooted by the engine rather than by a scope here, so this exists only to
Expand All@@ -38,10 +40,13 @@ struct napi_env__ {
init_symbol(function_info_symbol, "BabylonNative_FunctionInfo");
init_symbol(reference_info_symbol, "BabylonNative_ReferenceInfo");
init_symbol(wrapper_info_symbol, "BabylonNative_WrapperInfo");
init_function_prototype_call();
}

~napi_env__() {
shutting_down = true;
deinit_refs();
deinit_symbol(function_prototype_call);
deinit_symbol(wrapper_info_symbol);
deinit_symbol(reference_info_symbol);
deinit_symbol(function_info_symbol);
Expand All@@ -64,6 +69,7 @@ struct napi_env__ {

void deinit_refs();
void init_symbol(JSValueRef& symbol, const char* description);
void init_function_prototype_call();
void deinit_symbol(JSValueRef symbol);
};

Expand Down
10 changes: 10 additions & 0 deletions Tests/CMakeLists.txt
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,12 @@
add_subdirectory(UnitTests)

# The Node-API conformance harness (node_lite + the js-native-api addons) is written against the C
# napi_* API. The JSI backend (Core/Node-API-JSI) implements only the C++ `Napi::` wrapper over jsi and
# exports no C napi_* symbols, and its napi.h carries its own copy of the napi types -- which collides
# with the shared <napi/js_native_api_types.h> the harness includes (C2365 redefinition). Skip the
# harness for JSI rather than trying to reconcile the two type sources.
if(NOT NAPI_JAVASCRIPT_ENGINE STREQUAL "JSI")
add_subdirectory(NodeApi)
endif()

npm(install --silent)
111 changes: 111 additions & 0 deletions Tests/NodeApi/.clang-format
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
---
Language: Cpp
# BasedOnStyle: Google
AccessModifierOffset: -1
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Right
AlignOperands: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: true
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: true
BinPackArguments: false
BinPackParameters: false
BraceWrapping:
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: false
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^<ext/.*\.h>'
Priority: 2
- Regex: '^<.*\.h>'
Priority: 1
- Regex: '^<.*'
Priority: 2
- Regex: '.*'
Priority: 3
IncludeIsMainRegex: '([-_](test|unittest))?$'
IndentCaseLabels: true
IndentPPDirectives: None
IndentWidth: 2
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: false
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Auto
TabWidth: 8
UseTab: Never
Loading
Loading