From 9f86827ae094a4c44b1b08bea245527d23821b3e Mon Sep 17 00:00:00 2001 From: Ammar Ahmed Date: Tue, 18 Mar 2025 06:25:05 +0500 Subject: [PATCH] primjs: port host objects support --- .gitignore | 1 + .../src/main/cpp/napi/primjs/primjs-api.cc | 201 +++++++++++++++++- .../src/main/cpp/napi/primjs/primjs-api.h | 17 +- 3 files changed, 217 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 4d8824ea..971706a7 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ dist_v8 dist_quickjs dist_hermes dist_jsc +dist_* \ No newline at end of file diff --git a/test-app/runtime/src/main/cpp/napi/primjs/primjs-api.cc b/test-app/runtime/src/main/cpp/napi/primjs/primjs-api.cc index 2500f8f3..9dc08f37 100644 --- a/test-app/runtime/src/main/cpp/napi/primjs/primjs-api.cc +++ b/test-app/runtime/src/main/cpp/napi/primjs/primjs-api.cc @@ -26,6 +26,8 @@ #include "napi_env_quickjs.h" #include "quickjs/include/quickjs-inner.h" + +std::unordered_map napi_context__::rt_to_env_cache; struct napi_callback_info__ { napi_value newTarget; napi_value thisArg; @@ -2622,7 +2624,6 @@ napi_status napi_get_instance_data(napi_env env, uint64_t key, void **data) { void napi_attach_quickjs(napi_env env, LEPUSContext *context) { env->ctx = new napi_context__(env, context); - InitNapiScope(context); } @@ -2678,5 +2679,203 @@ napi_status primjs_execute_pending_jobs(napi_env env) { } } while (error != 0); + return napi_clear_last_error(env); +} + + +typedef struct NapiHostObjectInfo { + void *data; + napi_ref ref; + napi_finalize finalize_cb; + bool is_array; + napi_ref getter; + napi_ref setter; +} NapiHostObjectInfo; + +void host_object_finalizer(LEPUSRuntime *rt, LEPUSValue value) { + napi_env env = (napi_env) napi_context__::GetEnv(rt); + NapiHostObjectInfo *info = (NapiHostObjectInfo *) LEPUS_GetOpaque(value, + env->ctx->napiHostObjectClassId); + if (info->finalize_cb) { + info->finalize_cb(env, info->data, NULL); + } + if (info->is_array) { + napi_delete_reference(env, info->getter); + napi_delete_reference(env, info->setter); + } + + napi_delete_reference(env, info->ref); + delete info; +} + +int host_object_set(LEPUSContext *ctx, LEPUSValue obj, JSAtom atom, + LEPUSValue value, LEPUSValue receiver, int flags) { + napi_env env = (napi_env) napi_context__::GetEnv(LEPUS_GetRuntime(ctx)); + NapiHostObjectInfo *info = (NapiHostObjectInfo *) LEPUS_GetOpaque(obj, + env->ctx->napiHostObjectClassId); + if (info != NULL) { + auto *target = reinterpret_cast(info->ref); + if (info->is_array) { + LEPUSValue atom_val = LEPUS_AtomToValue(ctx, atom); + + auto *setter = reinterpret_cast(info->setter); + + LEPUSValue argv[4] = { + ToJSValue(target->Get()), + atom_val, + value, + obj + }; + + LEPUSValue result = LEPUS_Call(ctx, ToJSValue(setter->Get()), LEPUS_UNDEFINED, 4, argv); + + JS_FreeValue_Comp(ctx, atom_val); + + if (LEPUS_IsException(result)) return -1; + + return true; + } + return LEPUS_SetProperty(ctx, ToJSValue(target->Get()), atom, JS_DupValue_Comp(ctx, value)); + } + return true; +} + +LEPUSValue host_object_get(LEPUSContext *ctx, LEPUSValue obj, JSAtom atom, LEPUSValue receiver) { + napi_env env = (napi_env) napi_context__::GetEnv(LEPUS_GetRuntime(ctx)); + NapiHostObjectInfo *info = (NapiHostObjectInfo *) LEPUS_GetOpaque(obj, + env->ctx->napiHostObjectClassId); + if (info != NULL) { + auto *target = reinterpret_cast(info->ref); + if (info->is_array) { + LEPUSValue atom_val = LEPUS_AtomToValue(ctx, atom); + auto *getter = reinterpret_cast(info->getter); + LEPUSValue argv[3] = { + ToJSValue(target->Get()), + atom_val, + obj + }; + LEPUSValue value = LEPUS_Call(ctx, ToJSValue(getter->Get()), LEPUS_UNDEFINED, 3, argv); + JS_FreeValue_Comp(ctx, atom_val); + return value; + } + return LEPUS_GetProperty(ctx, ToJSValue(target->Get()), atom); + } + return LEPUS_UNDEFINED; +} + +int host_object_has(LEPUSContext *ctx, LEPUSValue obj, JSAtom atom) { + napi_env env = (napi_env) napi_context__::GetEnv(LEPUS_GetRuntime(ctx)); + NapiHostObjectInfo *info = (NapiHostObjectInfo *) LEPUS_GetOpaque(obj, + env->ctx->napiHostObjectClassId); + if (info != NULL) { + auto *target = reinterpret_cast(info->ref); + return LEPUS_HasProperty(ctx, ToJSValue(target->Get()), atom); + } + return false; +} + +static int host_object_delete(LEPUSContext *ctx, LEPUSValue obj, JSAtom atom) { + napi_env env = (napi_env) napi_context__::GetEnv(LEPUS_GetRuntime(ctx)); + NapiHostObjectInfo *info = (NapiHostObjectInfo *) LEPUS_GetOpaque(obj, + env->ctx->napiHostObjectClassId); + if (info != NULL) { + auto *target = reinterpret_cast(info->ref); + return LEPUS_DeleteProperty(ctx, ToJSValue(target->Get()), atom, 0); + } + return true; +} + +LEPUSClassExoticMethods NapiHostObjectExoticMethods = { + .get_own_property = nullptr, + .get_own_property_names = nullptr, + .delete_property = host_object_delete, + .define_own_property = nullptr, + .has_property = host_object_has, + .get_property = host_object_get, + .set_property = host_object_set, +}; + +napi_status +napi_create_host_object(napi_env env, napi_value value, napi_finalize finalize, void *data, + bool is_array, napi_value getter, napi_value setter, napi_value *result) { + CHECK_ARG(env, result); + + if (env->ctx->napi_host_object_class_init == 0) { + LEPUSClassDef NapiHostObjectClassDef = {"NapiHostObject", host_object_finalizer, NULL, NULL, + &NapiHostObjectExoticMethods}; + LEPUS_NewClass(env->ctx->rt, env->ctx->napiHostObjectClassId, &NapiHostObjectClassDef); + env->ctx->napi_host_object_class_init = 1; + } + + napi_value constructor; + napi_get_named_property(env, value, "constructor", &constructor); + + napi_value prototype; + napi_get_named_property(env, constructor, "prototype", &prototype); + + LEPUSValue jsValue = LEPUS_NewObjectClass(env->ctx->ctx, env->ctx->napiHostObjectClassId); + LEPUS_SetPrototype(env->ctx->ctx, jsValue, ToJSValue(prototype)); + + NapiHostObjectInfo *info = new NapiHostObjectInfo; + info->data = data; + if (finalize) { + info->finalize_cb = finalize; + } else { + info->finalize_cb = NULL; + } + info->is_array = is_array; + + if (is_array) { + if (getter) napi_create_reference(env, getter, 1, &info->getter); + if (setter) napi_create_reference(env, setter, 1, &info->setter); + } + + napi_create_reference(env, value, 1, &info->ref); + + LEPUS_SetOpaque(jsValue, info); + + *result = env->ctx->CreateHandle(jsValue); + return napi_ok; +} + +napi_status napi_get_host_object_data(napi_env env, napi_value object, void **data) { + CHECK_ARG(env, object); + CHECK_ARG(env, data); + + LEPUSValue jsValue = ToJSValue(object); + + + if (!LEPUS_IsObject(jsValue)) { + return napi_set_last_error(env, napi_object_expected); + } + + NapiHostObjectInfo *info = (NapiHostObjectInfo *) LEPUS_GetOpaque(jsValue, + env->ctx->napiHostObjectClassId); + if (info) { + *data = info->data; + } else { + *data = NULL; + } + + return napi_clear_last_error(env); +} + +napi_status napi_is_host_object(napi_env env, napi_value object, bool *result) { + CHECK_ARG(env, object); + + LEPUSValue jsValue = ToJSValue(object); + + if (!LEPUS_IsObject(jsValue)) { + return napi_set_last_error(env, napi_object_expected); + } + + void *data = LEPUS_GetOpaque(jsValue, + env->ctx->napiHostObjectClassId); + if (data != NULL) { + *result = true; + } else { + *result = false; + } + return napi_clear_last_error(env); } \ No newline at end of file diff --git a/test-app/runtime/src/main/cpp/napi/primjs/primjs-api.h b/test-app/runtime/src/main/cpp/napi/primjs/primjs-api.h index 39744a4a..c7953eef 100644 --- a/test-app/runtime/src/main/cpp/napi/primjs/primjs-api.h +++ b/test-app/runtime/src/main/cpp/napi/primjs/primjs-api.h @@ -335,6 +335,8 @@ namespace qjsimpl { inline void reset_napi_env(napi_env env, napi_handle_scope__ *scope); + + struct napi_context__ { napi_env env; LEPUSRuntime *rt{}; @@ -360,6 +362,7 @@ struct napi_context__ { PROP_CTOR_MAGIC(env, ctx, "@#ctor@#"), gc_enable(LEPUS_IsGCModeRT(rt)) { env->ctx = this; + napi_context__::rt_to_env_cache.emplace(rt, env); handle_scope = new napi_handle_scope__(env, ctx, reset_napi_env); LEPUSValue gc = LEPUS_NewCFunction(ctx, [](LEPUSContext *ctx, LEPUSValueConst this_val, @@ -370,6 +373,7 @@ struct napi_context__ { auto globalValue = LEPUS_GetGlobalObject(ctx); LEPUS_SetPropertyStr(ctx, globalValue, "gc", gc); JS_FreeValue_Comp(ctx, globalValue); + LEPUS_NewClassID(&napiHostObjectClassId); // TODO primjs doesn't expose DupContext } @@ -377,10 +381,11 @@ struct napi_context__ { ~napi_context__() { qjsimpl::RefTracker::FinalizeAll(&finalizing_reflist); qjsimpl::RefTracker::FinalizeAll(&reflist); - + // root handle scope may be used during FinalizeAll // must delete at last delete handle_scope; + rt_to_env_cache.erase(rt); } inline void Ref() { refs++; } @@ -415,6 +420,16 @@ struct napi_context__ { std::unordered_map instance_data_registry; + int napi_host_object_class_init = 0; + LEPUSClassID napiHostObjectClassId = 0; + static std::unordered_map rt_to_env_cache; + + static napi_env GetEnv(LEPUSRuntime* rt) { + auto it = rt_to_env_cache.find(rt); + if (it == rt_to_env_cache.end()) return nullptr; + return it->second; + } + int open_handle_scopes = 0; const qjsimpl::Atom PROP_NAME;