Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions benchmark/napi/create_object_with_properties/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build/
116 changes: 116 additions & 0 deletions benchmark/napi/create_object_with_properties/binding.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#include <assert.h>
#include <node_api.h>
#include <string>

struct BenchmarkParams {
napi_value count_val;
napi_value bench_obj;
napi_value start_fn;
napi_value end_fn;
uint32_t count;
};

static BenchmarkParams ParseBenchmarkArgs(napi_env env,
const napi_callback_info info) {
BenchmarkParams params;
size_t argc = 4;
napi_value args[4];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

params.count_val = args[0];
params.bench_obj = args[1];
params.start_fn = args[2];
params.end_fn = args[3];

napi_get_value_uint32(env, params.count_val, &params.count);
return params;
}

static napi_value global_names[20];
static napi_value global_values[20];
static bool global_properties_initialized = false;

// Creating with many options because complains are when ~20 properties
static void InitializeTestProperties(napi_env env) {
if (global_properties_initialized) return;

for (int i = 0; i < 20; i++) {
std::string name = "foo" + std::to_string(i);
napi_create_string_utf8(
env, name.c_str(), NAPI_AUTO_LENGTH, &global_names[i]);
napi_create_string_utf8(
env, name.c_str(), NAPI_AUTO_LENGTH, &global_values[i]);
}
global_properties_initialized = true;
}

static napi_value CreateObjectWithPropertiesNew(napi_env env,
napi_callback_info info) {
BenchmarkParams params = ParseBenchmarkArgs(env, info);

InitializeTestProperties(env);

napi_value null_prototype;
napi_get_null(env, &null_prototype);

napi_call_function(
env, params.bench_obj, params.start_fn, 0, nullptr, nullptr);

for (uint32_t i = 0; i < params.count; i++) {
napi_value obj;
napi_create_object_with_properties(
env, null_prototype, global_names, global_values, 20, &obj);
}

napi_call_function(
env, params.bench_obj, params.end_fn, 1, &params.count_val, nullptr);

return nullptr;
}

static napi_value CreateObjectWithPropertiesOld(napi_env env,
napi_callback_info info) {
BenchmarkParams params = ParseBenchmarkArgs(env, info);

InitializeTestProperties(env);

napi_call_function(
env, params.bench_obj, params.start_fn, 0, nullptr, nullptr);

for (uint32_t i = 0; i < params.count; i++) {
napi_value obj;
napi_create_object(env, &obj);
for (int j = 0; j < 20; j++) {
napi_set_property(env, obj, global_names[j], global_values[j]);
}
}

napi_call_function(
env, params.bench_obj, params.end_fn, 1, &params.count_val, nullptr);

return nullptr;
}

NAPI_MODULE_INIT() {
napi_property_descriptor desc[] = {
{"createObjectWithPropertiesNew",
0,
CreateObjectWithPropertiesNew,
0,
0,
0,
napi_default,
0},
{"createObjectWithPropertiesOld",
0,
CreateObjectWithPropertiesOld,
0,
0,
0,
napi_default,
0},
};

napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
return exports;
}
9 changes: 9 additions & 0 deletions benchmark/napi/create_object_with_properties/binding.gyp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
'targets': [
{
'target_name': 'binding',
'sources': [ 'binding.cc' ],
'defines': ['NAPI_EXPERIMENTAL']
}
]
}
24 changes: 24 additions & 0 deletions benchmark/napi/create_object_with_properties/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict';

const common = require('../../common.js');

let binding;
try {
binding = require(`./build/${common.buildType}/binding`);
} catch {
console.error(`${__filename}: Binding failed to load`);
process.exit(0);
}

const bench = common.createBenchmark(main, {
n: [1e2, 1e3, 1e4, 1e5, 1e6],
method: ['new', 'old'],
});

function main({ n, method }) {
if (method === 'new') {
binding.createObjectWithPropertiesNew(n, bench, bench.start, bench.end);
} else {
binding.createObjectWithPropertiesOld(n, bench, bench.start, bench.end);
}
}
37 changes: 37 additions & 0 deletions doc/api/n-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2637,6 +2637,43 @@ It is the equivalent of doing `new Object()` in JavaScript.
The JavaScript `Object` type is described in [Section object type][] of the
ECMAScript Language Specification.

#### `napi_create_object_with_properties`

<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental

```cpp
napi_status napi_create_object_with_properties(napi_env env,
napi_value prototype_or_null,
const napi_value* property_names,
const napi_value* property_values,
size_t property_count,
napi_value* result)
```

* `[in] env`: The environment that the API is invoked under.
* `[in] prototype_or_null`: The prototype object for the new object. Can be a
`napi_value` representing a JavaScript object to use as the prototype, a
`napi_value` representing JavaScript `null`, or a `nullptr` that will be converted to `null`.
* `[in] property_names`: Array of `napi_value` representing the property names.
* `[in] property_values`: Array of `napi_value` representing the property values.
* `[in] property_count`: Number of properties in the arrays.
* `[out] result`: A `napi_value` representing a JavaScript `Object`.

Returns `napi_ok` if the API succeeded.

This API creates a JavaScript `Object` with the specified prototype and
properties. This is more efficient than calling `napi_create_object` followed
by multiple `napi_set_property` calls, as it can create the object with all
properties atomically, avoiding potential V8 map transitions.

The arrays `property_names` and `property_values` must have the same length
specified by `property_count`. The properties are added to the object in the
order they appear in the arrays.

#### `napi_create_symbol`

<!-- YAML
Expand Down
11 changes: 11 additions & 0 deletions src/js_native_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,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
Expand Down
44 changes: 44 additions & 0 deletions src/js_native_api_v8.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1591,6 +1591,50 @@ napi_status NAPI_CDECL napi_create_object(napi_env env, napi_value* result) {
return napi_clear_last_error(env);
}

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) {
CHECK_ENV_NOT_IN_GC(env);
CHECK_ARG(env, result);

if (property_count > 0) {
CHECK_ARG(env, property_names);
CHECK_ARG(env, property_values);
}

v8::Local<v8::Value> v8_prototype_or_null;
if (prototype_or_null == nullptr) {
v8_prototype_or_null = v8::Null(env->isolate);
} else {
v8_prototype_or_null = v8impl::V8LocalValueFromJsValue(prototype_or_null);
}

v8::LocalVector<v8::Name> v8_names(env->isolate, property_count);
v8::LocalVector<v8::Value> v8_values(env->isolate, property_count);

for (size_t i = 0; i < property_count; i++) {
v8::Local<v8::Value> name_value =
v8impl::V8LocalValueFromJsValue(property_names[i]);
RETURN_STATUS_IF_FALSE(env, name_value->IsName(), napi_name_expected);
v8_names[i] = name_value.As<v8::Name>();
v8_values[i] = v8impl::V8LocalValueFromJsValue(property_values[i]);
}

v8::Local<v8::Object> obj = v8::Object::New(env->isolate,
v8_prototype_or_null,
v8_names.data(),
v8_values.data(),
property_count);

RETURN_STATUS_IF_FALSE(env, !obj.IsEmpty(), napi_generic_failure);
*result = v8impl::JsValueFromV8LocalValue(obj);
return napi_clear_last_error(env);
}

napi_status NAPI_CDECL napi_create_array(napi_env env, napi_value* result) {
CHECK_ENV_NOT_IN_GC(env);
CHECK_ARG(env, result);
Expand Down
5 changes: 4 additions & 1 deletion test/js-native-api/test_object/binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
"sources": [
"test_null.c",
"test_object.c"
]
],
"defines": [
"NAPI_EXPERIMENTAL"
],
},
{
"target_name": "test_exceptions",
Expand Down
19 changes: 18 additions & 1 deletion test/js-native-api/test_object/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ const assert = require('assert');
// Testing api calls for objects
const test_object = require(`./build/${common.buildType}/test_object`);


const object = {
hello: 'world',
array: [
Expand Down Expand Up @@ -391,3 +390,21 @@ assert.deepStrictEqual(test_object.TestGetProperty(), {
delete obj.x;
}, /Cannot delete property 'x' of #<Object>/);
}

{
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');
}
Loading
Loading