Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
e21550b
update metadata & types
DjDeveloperr Feb 17, 2026
e65eb3e
fix: dont allocate extra error info object
DjDeveloperr Feb 17, 2026
1a156a0
fix: module resolution allow resolving index.js, dont require package…
DjDeveloperr Feb 17, 2026
aff321d
fix: proper fail safe jsonparse
DjDeveloperr Feb 17, 2026
3ae4ca9
fix: improved metadata availability checks
DjDeveloperr Feb 18, 2026
ec99553
chore: add scripts to run macos/ios test suite
DjDeveloperr Feb 18, 2026
1af5a9f
fix: improved URL module compatibility, passes test suite
DjDeveloperr Feb 18, 2026
c14b50c
feat: addd timeOrigin to performance object
DjDeveloperr Feb 18, 2026
0c2de7e
fix: variable getter fallback to RTLD_DEFAULT
DjDeveloperr Feb 18, 2026
d7af30b
fix: improved module resolution and compatibility functions added for…
DjDeveloperr Feb 18, 2026
e1fd13e
chore: disable URLPattern tests for now
DjDeveloperr Feb 18, 2026
2b61c33
fix: refactoring, and compatibility improvements
DjDeveloperr Feb 18, 2026
7a92860
fix: test runner script paths
DjDeveloperr Feb 18, 2026
d375034
remove v8 headers
DjDeveloperr Feb 19, 2026
c7137d1
bump v8 to 14.3.92
DjDeveloperr Feb 19, 2026
0a8aca7
fix: build config for latest v8
DjDeveloperr Feb 19, 2026
f89c0e7
fix: add round trip frame guard for objc objects
DjDeveloperr Feb 19, 2026
505a68b
more macos tests
DjDeveloperr Feb 19, 2026
650723f
Refactor TNSTestNativeCallbacks and related tests
DjDeveloperr Feb 19, 2026
7ae91f3
fixes
DjDeveloperr Feb 23, 2026
7b4ad3d
feat: enhance macOS test runner with build state management and resou…
DjDeveloperr Feb 23, 2026
cd12261
feat: enable additional test modules and enhance crash reporting in m…
DjDeveloperr Feb 23, 2026
ebc6a1c
feat: add UTF-8 string coercion utility for URL parsing in macOS
DjDeveloperr Feb 23, 2026
4ec3407
feat: Enhance ObjC class member handling and add URLPattern support
DjDeveloperr Feb 24, 2026
ccfe268
feat: Refactor JSObject finalization and enhance NSTimerHandle manage…
DjDeveloperr Feb 24, 2026
9111dc1
Enhance NativeScript FFI with improved type conversions and compatibi…
DjDeveloperr Feb 24, 2026
134e9d2
Refactor libffi and metadata generator for improved memory management…
DjDeveloperr Feb 24, 2026
e91fa8a
cleanup autogenerated stuff
DjDeveloperr Feb 24, 2026
cb91a92
feat: Enhance lifecycle management and improve type handling in ObjC …
DjDeveloperr Feb 24, 2026
76b88c5
chore: Remove unused default.profraw file
DjDeveloperr Feb 24, 2026
dfb9fcf
fix: don’t include v8 headers in repo, include from xcframework
DjDeveloperr Feb 25, 2026
9388c48
feat: Enhance ObjC bridge state management with mutex and token handling
DjDeveloperr Feb 26, 2026
3368c98
feat: Add maxBuffer configuration for command output in iOS and macOS…
DjDeveloperr Feb 26, 2026
01c15a7
Add tests for Web and Node builtins in WebNodeBuiltins.js
DjDeveloperr Feb 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,7 @@ v8_build

.cache/

dist
packages/*/types

SwiftBindgen
65 changes: 46 additions & 19 deletions NativeScript/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ elseif(TARGET_ENGINE STREQUAL "hermes")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -std=c++20 -DTARGET_ENGINE_HERMES")
elseif(TARGET_ENGINE STREQUAL "v8")
set(TARGET_ENGINE_V8 TRUE)
add_link_options("-fuse-ld=lld")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti -stdlib=libc++ -std=c++20 -DTARGET_ENGINE_V8")
elseif(TARGET_ENGINE STREQUAL "quickjs")
set(TARGET_ENGINE_QUICKJS TRUE)
Expand Down Expand Up @@ -152,10 +151,13 @@ if(ENABLE_JS_RUNTIME)
runtime/modules/worker/WorkerImpl.mm
runtime/modules/worker/WorkerImpl.mm
runtime/modules/module/ModuleInternal.cpp
runtime/modules/node/Node.cpp
runtime/modules/node/FS.cpp
runtime/modules/performance/Performance.cpp
runtime/Bundle.mm
runtime/modules/timers/Timers.mm
runtime/modules/app/App.mm
runtime/modules/web/Web.mm
runtime/NativeScript.mm
runtime/RuntimeConfig.cpp
runtime/modules/url/ada/ada.cpp
Expand All @@ -166,7 +168,6 @@ if(ENABLE_JS_RUNTIME)
if(TARGET_ENGINE_V8)
include_directories(
napi/v8
napi/v8/include
napi/v8/v8_inspector
)

Expand Down Expand Up @@ -292,6 +293,17 @@ target_sources(
"NativeScript.h"
)

if(TARGET_ENGINE_V8 AND TARGET_PLATFORM_IOS)
# iOS V8 slices are built with pointer compression enabled. Keep embedder
# build flags in sync to satisfy V8::Initialize() build config checks.
target_compile_definitions(
${NAME}
PRIVATE
V8_COMPRESS_POINTERS
V8_31BIT_SMIS_ON_64BIT_ARCH
)
endif()

set(FRAMEWORK_VERSION_VALUE "${VERSION}")
if(TARGET_PLATFORM_MACOS)
# macOS framework consumers (including Xcode's copy/sign phases) expect
Expand Down Expand Up @@ -392,27 +404,42 @@ if(TARGET_ENGINE_JSC)
endif()

if(TARGET_ENGINE_V8)
if(TARGET_PLATFORM_MACOS)
target_link_directories(
${NAME}
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../Frameworks/v8_macos
)
add_library(v8::monolith STATIC IMPORTED GLOBAL)

set(V8_XCFRAMEWORK "${CMAKE_SOURCE_DIR}/../Frameworks/libv8_monolith.xcframework")

# Decide platform + slice
if(APPLE)
if(TARGET_PLATFORM STREQUAL "ios-sim")
# Prefer universal sim slice if present
set(V8_SLICE_DIR "${V8_XCFRAMEWORK}/ios-arm64-simulator/libv8_monolith.framework")
Comment on lines +413 to +415
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The xcframework slice selection is hard-coded to *-arm64* paths, which will fail on Intel macOS (macos-x86_64) and Intel iOS Simulator (ios-x86_64-simulator) environments. Select the slice based on CMAKE_OSX_ARCHITECTURES (or CMAKE_SYSTEM_PROCESSOR) and choose the correct xcframework folder (including universal simulator slices if present), otherwise builds will be host-arch dependent.

Copilot uses AI. Check for mistakes.
if(NOT EXISTS "${V8_SLICE_DIR}")
set(V8_SLICE_DIR "${V8_XCFRAMEWORK}/ios-arm64-simulator/libv8_monolith.framework") # fallback
endif()
elseif(TARGET_PLATFORM STREQUAL "ios")
# Prefer universal sim slice if present
set(V8_SLICE_DIR "${V8_XCFRAMEWORK}/ios-arm64/libv8_monolith.framework")
if(NOT EXISTS "${V8_SLICE_DIR}")
set(V8_SLICE_DIR "${V8_XCFRAMEWORK}/ios-arm64/libv8_monolith.framework") # fallback
endif()
else()
# macOS
set(V8_SLICE_DIR "${V8_XCFRAMEWORK}/macos-arm64/libv8_monolith.framework")
if(NOT EXISTS "${V8_SLICE_DIR}")
set(V8_SLICE_DIR "${V8_XCFRAMEWORK}/macos-arm64/libv8_monolith.framework") # keep same, just explicit
endif()
endif()
else()
target_link_directories(
${NAME}
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../Frameworks/v8_ios
)
message(FATAL_ERROR "This example is for Apple platforms only.")
endif()

target_link_libraries(
${NAME}
PRIVATE
"v8_monolith"
"v8_libbase"
"v8_libplatform"
# Point imported lib at the slice
set_target_properties(v8::monolith PROPERTIES
IMPORTED_LOCATION "${V8_SLICE_DIR}/libv8_monolith"
INTERFACE_INCLUDE_DIRECTORIES "${V8_SLICE_DIR}/Headers"
)

target_link_libraries(${NAME} PRIVATE v8::monolith)
endif()

target_link_libraries(
Expand Down
6 changes: 6 additions & 0 deletions NativeScript/ffi/Block.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,22 @@ class FunctionPointer {
void* function;
metagen::MDSectionOffset offset;
Cif* cif;
bool ownsCif = false;

static napi_value wrap(napi_env env, void* function,
metagen::MDSectionOffset offset, bool isBlock);
static napi_value wrapWithEncoding(napi_env env, void* function,
const char* encoding, bool isBlock);
static void finalize(napi_env env, void* finalize_data, void* finalize_hint);

static napi_value jsCallAsCFunction(napi_env env, napi_callback_info cbinfo);
static napi_value jsCallAsBlock(napi_env env, napi_callback_info cbinfo);
};

id registerBlock(napi_env env, Closure* closure, napi_value callback);
napi_value getCachedBlockCallback(napi_env env, void* blockPtr);
bool isObjCBlockObject(id obj);
const char* getObjCBlockSignature(void* blockPtr);

NAPI_FUNCTION(registerBlock);

Expand Down
182 changes: 181 additions & 1 deletion NativeScript/ffi/Block.mm
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
#include "Block.h"
#import <Foundation/Foundation.h>
#include <cstdint>
#include <cstring>
#include <unordered_map>
#include "Interop.h"
#include "ObjCBridge.h"
#include "js_native_api.h"
#include "js_native_api_types.h"
#include "node_api_util.h"
#include "objc/runtime.h"
#include <cstring>

struct Block_descriptor_1 {
unsigned long int reserved; // NULL
Expand All @@ -33,6 +35,8 @@
constexpr int kBlockNeedsFree = (1 << 24);
constexpr int kBlockHasCopyDispose = (1 << 25);
constexpr int kBlockRefCountOne = (1 << 1);
constexpr int kBlockHasSignature = (1 << 30);
std::unordered_map<void*, napi_ref> g_blockToJsFunction;
Comment on lines +38 to +39
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

g_blockToJsFunction is a global std::unordered_map accessed from multiple codepaths (block_release, block_finalize, wrapper creation) and can be hit from different threads (blocks can be copied/released off the JS thread). This is a data race and can corrupt the map. Protect all accesses with a mutex (or move the cache behind a single-threaded dispatch queue) and ensure reference deletion happens on the same thread/loop as N-API expects.

Copilot uses AI. Check for mistakes.

void block_copy(void* dest, void* src) {
auto dst = static_cast<Block_literal_1*>(dest);
Expand All @@ -46,6 +50,14 @@ void block_release(void* src) {
return;
}

if (block->closure != nullptr && block->closure->env != nullptr) {
auto it = g_blockToJsFunction.find(block);
if (it != g_blockToJsFunction.end()) {
napi_delete_reference(block->closure->env, it->second);
g_blockToJsFunction.erase(it);
}
}

if (block->closure != nullptr) {
delete block->closure;
block->closure = nullptr;
Expand All @@ -60,6 +72,30 @@ void block_release(void* src) {
.signature = nullptr,
};

inline napi_value getCachedBlockJsFunction(napi_env env, void* blockPtr) {
auto it = g_blockToJsFunction.find(blockPtr);
if (it == g_blockToJsFunction.end()) {
return nullptr;
}
napi_value value = nativescript::get_ref_value(env, it->second);
if (value == nullptr) {
napi_delete_reference(env, it->second);
g_blockToJsFunction.erase(it);
}
return value;
}

inline void cacheBlockJsFunction(napi_env env, void* blockPtr, napi_value jsFunction) {
if (blockPtr == nullptr || jsFunction == nullptr) {
return;
}
if (g_blockToJsFunction.find(blockPtr) != g_blockToJsFunction.end()) {
return;
}
// Keep this weak so callback identity can round-trip without preventing GC.
g_blockToJsFunction[blockPtr] = nativescript::make_ref(env, jsFunction, 0);
}

} // namespace

void block_finalize(napi_env env, void* data, void* hint) {
Expand All @@ -68,6 +104,12 @@ void block_finalize(napi_env env, void* data, void* hint) {
return;
}

auto it = g_blockToJsFunction.find(block);
if (it != g_blockToJsFunction.end()) {
napi_delete_reference(env, it->second);
g_blockToJsFunction.erase(it);
}

if (block->closure != nullptr) {
delete block->closure;
block->closure = nullptr;
Expand Down Expand Up @@ -102,6 +144,12 @@ id registerBlock(napi_env env, Closure* closure, napi_value callback) {

closure->func = make_ref(env, callback, 1);

// Expose the native block pointer on the JS callback so interop.handleof/sizeof
// can resolve pointers for blocks that round-trip through Objective-C.
napi_value ptrExternal;
napi_create_external(env, block, nullptr, nullptr, &ptrExternal);
napi_set_named_property(env, callback, "__ns_native_ptr", ptrExternal);

auto bridgeState = ObjCBridgeState::InstanceData(env);

#ifndef ENABLE_JS_RUNTIME
Expand All @@ -114,9 +162,59 @@ id registerBlock(napi_env env, Closure* closure, napi_value callback) {
}
#endif // ENABLE_JS_RUNTIME

cacheBlockJsFunction(env, block, callback);

return (id)block;
}

napi_value getCachedBlockCallback(napi_env env, void* blockPtr) {
return getCachedBlockJsFunction(env, blockPtr);
}

bool isObjCBlockObject(id obj) {
if (obj == nil) {
return false;
}

Class cls = object_getClass(obj);
if (cls == nil) {
return false;
}

const char* className = class_getName(cls);
if (className == nullptr) {
return false;
}

// Runtime block classes are typically internal names like
// __NSGlobalBlock__, __NSMallocBlock__, __NSStackBlock__.
return className[0] == '_' && className[1] == '_' && strstr(className, "Block") != nullptr;
}

const char* getObjCBlockSignature(void* blockPtr) {
auto block = static_cast<Block_literal_1*>(blockPtr);
if (block == nullptr || block->descriptor == nullptr) {
return nullptr;
}

if ((block->flags & kBlockHasSignature) == 0) {
return nullptr;
}

// Descriptor layout:
// unsigned long reserved;
// unsigned long size;
// [copy_helper, dispose_helper] if BLOCK_HAS_COPY_DISPOSE
// const char* signature if BLOCK_HAS_SIGNATURE
auto descriptorCursor = reinterpret_cast<uint8_t*>(block->descriptor);
descriptorCursor += sizeof(unsigned long) * 2;
if ((block->flags & kBlockHasCopyDispose) != 0) {
descriptorCursor += sizeof(void*) * 2;
}

return *reinterpret_cast<const char**>(descriptorCursor);
}

NAPI_FUNCTION(registerBlock) {
NAPI_CALLBACK_BEGIN(2)

Expand All @@ -137,6 +235,13 @@ id registerBlock(napi_env env, Closure* closure, napi_value callback) {

napi_value FunctionPointer::wrap(napi_env env, void* function, metagen::MDSectionOffset offset,
bool isBlock) {
if (isBlock) {
napi_value cached = getCachedBlockJsFunction(env, function);
if (cached != nullptr) {
return cached;
}
}

FunctionPointer* ref = new FunctionPointer();
ref->function = function;
ref->offset = offset;
Expand All @@ -153,6 +258,77 @@ id registerBlock(napi_env env, Closure* closure, napi_value callback) {
napi_create_function(env, isBlock ? "objcBlockWrapper" : "cFunctionWrapper", NAPI_AUTO_LENGTH,
isBlock ? jsCallAsBlock : jsCallAsCFunction, ref, &result);

// Allow fast pointer extraction when JS function wrappers are passed back to native.
napi_ref nativePointerRef;
napi_wrap(env, result, function, nullptr, nullptr, &nativePointerRef);
(void)nativePointerRef;

// Keep raw pointer metadata without overriding the function callback data.
// Overriding callback data breaks JS invocation for wrapped function pointers.
napi_value ptrExternal;
napi_create_external(env, function, nullptr, nullptr, &ptrExternal);
napi_property_descriptor ptrProp = {
.utf8name = "__ns_native_ptr",
.method = nullptr,
.getter = nullptr,
.setter = nullptr,
.value = ptrExternal,
.attributes = napi_default,
.data = nullptr,
};
napi_define_properties(env, result, 1, &ptrProp);

napi_ref jsRef;
napi_add_finalizer(env, result, ref, FunctionPointer::finalize, nullptr, &jsRef);

return result;
}

napi_value FunctionPointer::wrapWithEncoding(napi_env env, void* function, const char* encoding,
bool isBlock) {
if (function == nullptr || encoding == nullptr || encoding[0] == '\0') {
napi_value nullValue;
napi_get_null(env, &nullValue);
return nullValue;
}

if (isBlock) {
napi_value cached = getCachedBlockJsFunction(env, function);
if (cached != nullptr) {
return cached;
}
}

FunctionPointer* ref = new FunctionPointer();
ref->function = function;
ref->offset = 0;
ref->ownsCif = true;
ref->cif = new Cif(env, encoding, isBlock ? 1 : 0);

napi_value result;
napi_create_function(env, isBlock ? "objcBlockWrapper" : "cFunctionWrapper", NAPI_AUTO_LENGTH,
isBlock ? jsCallAsBlock : jsCallAsCFunction, ref, &result);

// Allow fast pointer extraction when JS function wrappers are passed back to native.
napi_ref nativePointerRef;
napi_wrap(env, result, function, nullptr, nullptr, &nativePointerRef);
(void)nativePointerRef;

// Keep raw pointer metadata without overriding the function callback data.
// Overriding callback data breaks JS invocation for wrapped function pointers.
napi_value ptrExternal;
napi_create_external(env, function, nullptr, nullptr, &ptrExternal);
napi_property_descriptor ptrProp = {
.utf8name = "__ns_native_ptr",
.method = nullptr,
.getter = nullptr,
.setter = nullptr,
.value = ptrExternal,
.attributes = napi_default,
.data = nullptr,
};
napi_define_properties(env, result, 1, &ptrProp);

napi_ref jsRef;
napi_add_finalizer(env, result, ref, FunctionPointer::finalize, nullptr, &jsRef);

Expand All @@ -161,6 +337,10 @@ id registerBlock(napi_env env, Closure* closure, napi_value callback) {

void FunctionPointer::finalize(napi_env env, void* finalize_data, void* finalize_hint) {
auto ref = (FunctionPointer*)finalize_data;
if (ref->ownsCif && ref->cif != nullptr) {
delete ref->cif;
ref->cif = nullptr;
}
delete ref;
}

Expand Down
Loading
Loading