diff --git a/packages/emnapi/include/node/js_native_api.h b/packages/emnapi/include/node/js_native_api.h index 9e8d49d6..84e9ed2d 100644 --- a/packages/emnapi/include/node/js_native_api.h +++ b/packages/emnapi/include/node/js_native_api.h @@ -72,6 +72,17 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_get_boolean(napi_env env, // Methods to create Primitive types/Objects NAPI_EXTERN napi_status NAPI_CDECL napi_create_object(napi_env env, napi_value* result); +#ifdef NAPI_EXPERIMENTAL +#define NODE_API_EXPERIMENTAL_HAS_CREATE_OBJECT_WITH_PROPERTIES +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_object_with_properties(napi_env env, + napi_value prototype_or_null, + napi_value* property_names, + napi_value* property_values, + size_t property_count, + napi_value* result); +#endif // NAPI_EXPERIMENTAL + NAPI_EXTERN napi_status NAPI_CDECL napi_create_array(napi_env env, napi_value* result); NAPI_EXTERN napi_status NAPI_CDECL diff --git a/packages/emnapi/src/value/create.ts b/packages/emnapi/src/value/create.ts index be553cb2..56f39c1d 100644 --- a/packages/emnapi/src/value/create.ts +++ b/packages/emnapi/src/value/create.ts @@ -1,11 +1,11 @@ import { emnapiCtx } from 'emnapi:shared' import { wasmMemory, _malloc } from 'emscripten:runtime' -import { from64, makeSetValue, to64 } from 'emscripten:parse-tools' +import { from64, makeGetValue, makeSetValue, POINTER_SIZE, to64 } from 'emscripten:parse-tools' import { emnapiString } from '../string' import { type MemoryViewDescriptor, emnapiExternalMemory } from '../memory' import { emnapi_create_memory_view } from '../emnapi' import { napi_add_finalizer } from '../wrap' -import { $CHECK_ARG, $CHECK_ENV_NOT_IN_GC, $GET_RETURN_STATUS, $PREAMBLE } from '../macro' +import { $CHECK_ARG, $CHECK_ENV_NOT_IN_GC, $GET_RETURN_STATUS, $PREAMBLE, $RETURN_STATUS_IF_FALSE } from '../macro' /** * @__sig ipp @@ -196,6 +196,59 @@ export function napi_create_object (env: napi_env, result: Pointer): return envObject.clearLastError() } +/** + * @__sig ipppppp + */ +export function napi_create_object_with_properties ( + env: napi_env, + prototype_or_null: napi_value, + property_names: Pointer, + property_values: Pointer, + property_count: size_t, + result: Pointer +): napi_status { + const envObject = $CHECK_ENV_NOT_IN_GC!(env) + $CHECK_ARG!(envObject, result) + + from64('property_count') + property_count = property_count >>> 0 + + if (property_count > 0) { + $CHECK_ARG!(envObject, property_names) + $CHECK_ARG!(envObject, property_values) + } + + const v8_prototype_or_null = prototype_or_null + ? emnapiCtx.jsValueFromNapiValue(prototype_or_null) + : null + + const properties: PropertyDescriptorMap = {} + + from64('property_names') + from64('property_values') + for (let i = 0; i < property_count; i++) { + const name_value = emnapiCtx.jsValueFromNapiValue(makeGetValue('property_names', 'i * ' + POINTER_SIZE, '*')) + $RETURN_STATUS_IF_FALSE(envObject, typeof name_value === 'string' || typeof name_value === 'symbol', napi_status.napi_name_expected) + properties[name_value] = { + value: emnapiCtx.jsValueFromNapiValue(makeGetValue('property_values', 'i * ' + POINTER_SIZE, '*')), + writable: true, + enumerable: true, + configurable: true + } + } + + let obj: any + try { + obj = Object.defineProperties(Object.create(v8_prototype_or_null), properties) + } catch (_) { + return envObject.setLastError(napi_status.napi_generic_failure) + } + const value = emnapiCtx.napiValueFromJsValue(obj) + from64('result') + makeSetValue('result', 0, 'value', '*') + return envObject.clearLastError() +} + /** * @__sig ippp */ diff --git a/packages/test/CMakeLists.txt b/packages/test/CMakeLists.txt index 660c5ea7..540df2ae 100644 --- a/packages/test/CMakeLists.txt +++ b/packages/test/CMakeLists.txt @@ -302,6 +302,7 @@ target_compile_definitions("string" PRIVATE "NAPI_VERSION=10") add_test("property" "./property/binding.c" OFF) add_test("promise" "./promise/binding.c" OFF) add_test("object" "./object/test_null.c;./object/test_object.c" OFF) +target_compile_definitions("object" PRIVATE "NAPI_EXPERIMENTAL") add_test("object_exception" "./object/test_exceptions.c" OFF) add_test("objwrap" "./objwrap/myobject.cc" OFF) add_test("objwrapbasicfinalizer" "./objwrap/myobject.cc" OFF) diff --git a/packages/test/object/object.test.js b/packages/test/object/object.test.js index 7cb8a412..fa50de1b 100644 --- a/packages/test/object/object.test.js +++ b/packages/test/object/object.test.js @@ -392,4 +392,22 @@ module.exports = load('object').then(test_object => { delete obj.x }, /Cannot delete property 'x' of #/) } + + { + const objectWithProperties = test_object.TestCreateObjectWithProperties(); + assert.strictEqual(typeof objectWithProperties, 'object'); + assert.strictEqual(objectWithProperties.name, 'Foo'); + assert.strictEqual(objectWithProperties.age, 42); + assert.strictEqual(objectWithProperties.active, true); + + const emptyObject = test_object.TestCreateObjectWithPropertiesEmpty(); + assert.strictEqual(typeof emptyObject, 'object'); + assert.strictEqual(Object.keys(emptyObject).length, 0); + + const objectWithCustomPrototype = test_object.TestCreateObjectWithCustomPrototype(); + assert.strictEqual(typeof objectWithCustomPrototype, 'object'); + assert.deepStrictEqual(Object.getOwnPropertyNames(objectWithCustomPrototype), ['value']); + assert.strictEqual(objectWithCustomPrototype.value, 42); + assert.strictEqual(typeof objectWithCustomPrototype.test, 'function'); + } }) diff --git a/packages/test/object/test_object.c b/packages/test/object/test_object.c index 6c71b22a..40ac602d 100644 --- a/packages/test/object/test_object.c +++ b/packages/test/object/test_object.c @@ -721,6 +721,79 @@ CheckTypeTag(napi_env env, napi_callback_info info) { return js_result; } +static napi_value TestCreateObjectWithProperties(napi_env env, + napi_callback_info info) { + napi_value names[3]; + napi_value values[3]; + napi_value result; + + NODE_API_CALL( + env, napi_create_string_utf8(env, "name", NAPI_AUTO_LENGTH, &names[0])); + NODE_API_CALL( + env, napi_create_string_utf8(env, "Foo", NAPI_AUTO_LENGTH, &values[0])); + + NODE_API_CALL( + env, napi_create_string_utf8(env, "age", NAPI_AUTO_LENGTH, &names[1])); + NODE_API_CALL(env, napi_create_int32(env, 42, &values[1])); + + NODE_API_CALL( + env, napi_create_string_utf8(env, "active", NAPI_AUTO_LENGTH, &names[2])); + NODE_API_CALL(env, napi_get_boolean(env, true, &values[2])); + + napi_value null_prototype; + NODE_API_CALL(env, napi_get_null(env, &null_prototype)); + NODE_API_CALL(env, + napi_create_object_with_properties( + env, null_prototype, names, values, 3, &result)); + + return result; +} + +static napi_value TestCreateObjectWithPropertiesEmpty(napi_env env, + napi_callback_info info) { + napi_value result; + + NODE_API_CALL( + env, + napi_create_object_with_properties(env, NULL, NULL, NULL, 0, &result)); + + return result; +} + +static napi_value TestCreateObjectWithCustomPrototype(napi_env env, + napi_callback_info info) { + napi_value prototype; + napi_value method_name; + napi_value method_func; + napi_value names[1]; + napi_value values[1]; + napi_value result; + + NODE_API_CALL(env, napi_create_object(env, &prototype)); + NODE_API_CALL( + env, + napi_create_string_utf8(env, "test", NAPI_AUTO_LENGTH, &method_name)); + NODE_API_CALL(env, + napi_create_function(env, + "test", + NAPI_AUTO_LENGTH, + TestCreateObjectWithProperties, + NULL, + &method_func)); + NODE_API_CALL(env, + napi_set_property(env, prototype, method_name, method_func)); + + NODE_API_CALL( + env, napi_create_string_utf8(env, "value", NAPI_AUTO_LENGTH, &names[0])); + NODE_API_CALL(env, napi_create_int32(env, 42, &values[0])); + + NODE_API_CALL(env, + napi_create_object_with_properties( + env, prototype, names, values, 1, &result)); + + return result; +} + EXTERN_C_START napi_value Init(napi_env env, napi_value exports) { napi_property_descriptor descriptors[] = { @@ -754,6 +827,12 @@ napi_value Init(napi_env env, napi_value exports) { DECLARE_NODE_API_PROPERTY("TestGetProperty", TestGetProperty), DECLARE_NODE_API_PROPERTY("TestFreeze", TestFreeze), DECLARE_NODE_API_PROPERTY("TestSeal", TestSeal), + DECLARE_NODE_API_PROPERTY("TestCreateObjectWithProperties", + TestCreateObjectWithProperties), + DECLARE_NODE_API_PROPERTY("TestCreateObjectWithPropertiesEmpty", + TestCreateObjectWithPropertiesEmpty), + DECLARE_NODE_API_PROPERTY("TestCreateObjectWithCustomPrototype", + TestCreateObjectWithCustomPrototype), }; init_test_null(env, exports);