-
-
Notifications
You must be signed in to change notification settings - Fork 1
feat: full test coverage on macos & ios, v8 14 #28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
e21550b
e65eb3e
1a156a0
aff321d
3ae4ca9
ec99553
1af5a9f
c14b50c
0c2de7e
d7af30b
e1fd13e
2b61c33
7a92860
d375034
c7137d1
0a8aca7
f89c0e7
505a68b
650723f
7ae91f3
7b4ad3d
cd12261
ebc6a1c
4ec3407
ccfe268
9111dc1
134e9d2
e91fa8a
cb91a92
76b88c5
dfb9fcf
9388c48
3368c98
01c15a7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -59,4 +59,7 @@ v8_build | |
|
|
||
| .cache/ | ||
|
|
||
| dist | ||
| packages/*/types | ||
|
|
||
| SwiftBindgen | ||
| 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 | ||
|
|
@@ -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
|
||
|
|
||
| void block_copy(void* dest, void* src) { | ||
| auto dst = static_cast<Block_literal_1*>(dest); | ||
|
|
@@ -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; | ||
|
|
@@ -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) { | ||
|
|
@@ -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; | ||
|
|
@@ -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 | ||
|
|
@@ -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) | ||
|
|
||
|
|
@@ -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; | ||
|
|
@@ -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); | ||
|
|
||
|
|
@@ -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; | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
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 onCMAKE_OSX_ARCHITECTURES(orCMAKE_SYSTEM_PROCESSOR) and choose the correct xcframework folder (including universal simulator slices if present), otherwise builds will be host-arch dependent.