Skip to content
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

[v10.x] n-api: add napi_get_all_property_names #31384

Closed
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
91 changes: 91 additions & 0 deletions doc/api/n-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1372,6 +1372,66 @@ However, for better performance, it's better for the caller to make sure that
the `napi_value` in question is of the JavaScript type expected by the API.

### Enum types
#### napi_key_collection_mode
<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental

```C
typedef enum {
napi_key_include_prototypes,
napi_key_own_only
} napi_key_collection_mode;
```

Describes the `Keys/Properties` filter enums:

`napi_key_collection_mode` limits the range of collected properties.

`napi_key_own_only` limits the collected properties to the given
object only. `napi_key_include_prototypes` will include all keys
of the objects's prototype chain as well.

#### napi_key_filter
<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental

```C
typedef enum {
napi_key_all_properties = 0,
napi_key_writable = 1,
napi_key_enumerable = 1 << 1,
napi_key_configurable = 1 << 2,
napi_key_skip_strings = 1 << 3,
napi_key_skip_symbols = 1 << 4
} napi_key_filter;
```

Property filter bits. They can be or'ed to build a composite filter.

#### napi_key_conversion
<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental

```C
typedef enum {
napi_key_keep_numbers,
napi_key_numbers_to_strings
} napi_key_conversion;
```

`napi_key_numbers_to_strings` will convert integer indices to
strings. `napi_key_keep_numbers` will return numbers for integer
indices.

#### napi_valuetype
```C
typedef enum {
Expand Down Expand Up @@ -3126,6 +3186,37 @@ This API returns the names of the enumerable properties of `object` as an array
of strings. The properties of `object` whose key is a symbol will not be
included.

#### napi_get_all_property_names
<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental

```C
napi_get_all_property_names(napi_env env,
napi_value object,
napi_key_collection_mode key_mode,
napi_key_filter key_filter,
napi_key_conversion key_conversion,
napi_value* result);
```

* `[in] env`: The environment that the N-API call is invoked under.
* `[in] object`: The object from which to retrieve the properties.
* `[in] key_mode`: Whether to retrieve prototype properties as well.
* `[in] key_filter`: Which properties to retrieve
(enumerable/readable/writable).
* `[in] key_conversion`: Whether to convert numbered property keys to strings.
* `[out] result`: A `napi_value` representing an array of JavaScript values
that represent the property names of the object. [`napi_get_array_length`][] and
[`napi_get_element`][] can be used to iterate over `result`.

Returns `napi_ok` if the API succeeded.

This API returns an array containing the names of the available properties
of this object.

#### napi_set_property
<!-- YAML
added: v8.0.0
Expand Down
92 changes: 88 additions & 4 deletions src/node_api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,17 @@ struct napi_env__ {
(out) = v8::type::New((buffer), (byte_offset), (length)); \
} while (0)

#define RETURN_STATUS_IF_FALSE_WITH_PREAMBLE(env, condition, status) \
do { \
if (!(condition)) { \
return napi_set_last_error( \
(env), try_catch.HasCaught() ? napi_pending_exception : (status)); \
} \
} while (0)

#define CHECK_MAYBE_EMPTY_WITH_PREAMBLE(env, maybe, status) \
RETURN_STATUS_IF_FALSE_WITH_PREAMBLE((env), !((maybe).IsEmpty()), (status))

namespace {
namespace v8impl {

Expand Down Expand Up @@ -1604,19 +1615,92 @@ napi_status napi_define_class(napi_env env,
napi_status napi_get_property_names(napi_env env,
napi_value object,
napi_value* result) {
return napi_get_all_property_names(
env,
object,
napi_key_include_prototypes,
static_cast<napi_key_filter>(napi_key_enumerable |
napi_key_skip_symbols),
napi_key_numbers_to_strings,
result);
}

napi_status napi_get_all_property_names(napi_env env,
napi_value object,
napi_key_collection_mode key_mode,
napi_key_filter key_filter,
napi_key_conversion key_conversion,
napi_value* result) {
NAPI_PREAMBLE(env);
CHECK_ARG(env, result);

v8::Local<v8::Context> context = env->context();
v8::Local<v8::Object> obj;
CHECK_TO_OBJECT(env, context, obj, object);

auto maybe_propertynames = obj->GetPropertyNames(context);
v8::PropertyFilter filter = v8::PropertyFilter::ALL_PROPERTIES;
if (key_filter & napi_key_writable) {
filter =
static_cast<v8::PropertyFilter>(filter |
v8::PropertyFilter::ONLY_WRITABLE);
}
if (key_filter & napi_key_enumerable) {
filter =
static_cast<v8::PropertyFilter>(filter |
v8::PropertyFilter::ONLY_ENUMERABLE);
}
if (key_filter & napi_key_configurable) {
filter =
static_cast<v8::PropertyFilter>(filter |
v8::PropertyFilter::ONLY_WRITABLE);
}
if (key_filter & napi_key_skip_strings) {
filter =
static_cast<v8::PropertyFilter>(filter |
v8::PropertyFilter::SKIP_STRINGS);
}
if (key_filter & napi_key_skip_symbols) {
filter =
static_cast<v8::PropertyFilter>(filter |
v8::PropertyFilter::SKIP_SYMBOLS);
}
v8::KeyCollectionMode collection_mode;
v8::KeyConversionMode conversion_mode;

switch (key_mode) {
case napi_key_include_prototypes:
collection_mode = v8::KeyCollectionMode::kIncludePrototypes;
break;
case napi_key_own_only:
collection_mode = v8::KeyCollectionMode::kOwnOnly;
break;
default:
return napi_set_last_error(env, napi_invalid_arg);
}

CHECK_MAYBE_EMPTY(env, maybe_propertynames, napi_generic_failure);
switch (key_conversion) {
case napi_key_keep_numbers:
conversion_mode = v8::KeyConversionMode::kKeepNumbers;
break;
case napi_key_numbers_to_strings:
conversion_mode = v8::KeyConversionMode::kConvertToString;
break;
default:
return napi_set_last_error(env, napi_invalid_arg);
}

*result = v8impl::JsValueFromV8LocalValue(
maybe_propertynames.ToLocalChecked());
v8::MaybeLocal<v8::Array> maybe_all_propertynames =
obj->GetPropertyNames(context,
collection_mode,
filter,
v8::IndexFilter::kIncludeIndices,
conversion_mode);

CHECK_MAYBE_EMPTY_WITH_PREAMBLE(
env, maybe_all_propertynames, napi_generic_failure);

*result =
v8impl::JsValueFromV8LocalValue(maybe_all_propertynames.ToLocalChecked());
return GET_RETURN_STATUS(env);
}

Expand Down
10 changes: 10 additions & 0 deletions src/node_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,16 @@ NAPI_EXTERN napi_status napi_set_instance_data(napi_env env,

NAPI_EXTERN napi_status napi_get_instance_data(napi_env env,
void** data);

// Object
NAPI_EXTERN napi_status
napi_get_all_property_names(napi_env env,
napi_value object,
napi_key_collection_mode key_mode,
napi_key_filter key_filter,
napi_key_conversion key_conversion,
napi_value* result);

#endif // NAPI_EXPERIMENTAL

EXTERN_C_END
Expand Down
21 changes: 21 additions & 0 deletions src/node_api_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,25 @@ typedef struct {
const char* release;
} napi_node_version;

#ifdef NAPI_EXPERIMENTAL
typedef enum {
napi_key_include_prototypes,
napi_key_own_only
} napi_key_collection_mode;

typedef enum {
napi_key_all_properties = 0,
napi_key_writable = 1,
napi_key_enumerable = 1 << 1,
napi_key_configurable = 1 << 2,
napi_key_skip_strings = 1 << 3,
napi_key_skip_symbols = 1 << 4
} napi_key_filter;

typedef enum {
napi_key_keep_numbers,
napi_key_numbers_to_strings
} napi_key_conversion;
#endif

#endif // SRC_NODE_API_TYPES_H_
4 changes: 4 additions & 0 deletions test/addons-napi/test_object/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const assert = require('assert');
// Testing api calls for objects
const test_object = require(`./build/${common.buildType}/test_object`);

const fooSymbol = Symbol('foo');

const object = {
hello: 'world',
Expand All @@ -16,6 +17,8 @@ const object = {
}
};

object[fooSymbol] = 3;

assert.strictEqual(test_object.Get(object, 'hello'), 'world');
assert.deepStrictEqual(test_object.Get(object, 'array'),
[ 1, 94, 'str', 12.321, { test: 'obj in arr' } ]);
Expand All @@ -25,6 +28,7 @@ assert.deepStrictEqual(test_object.Get(object, 'newObject'),
assert(test_object.Has(object, 'hello'));
assert(test_object.Has(object, 'array'));
assert(test_object.Has(object, 'newObject'));
assert.deepStrictEqual(test_object.GetSymbolNames(object), [fooSymbol]);

const newObject = test_object.New();
assert(test_object.Has(newObject, 'test_number'));
Expand Down
29 changes: 29 additions & 0 deletions test/addons-napi/test_object/test_object.c
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#define NAPI_EXPERIMENTAL
#include <node_api.h>
#include "../common.h"
#include <string.h>
Expand Down Expand Up @@ -193,6 +194,33 @@ static napi_value Inflate(napi_env env, napi_callback_info info) {
return obj;
}

static napi_value GetSymbolNames(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1];
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));

NAPI_ASSERT(env, argc >= 1, "Wrong number of arguments");

napi_valuetype value_type0;
NAPI_CALL(env, napi_typeof(env, args[0], &value_type0));

NAPI_ASSERT(env,
value_type0 == napi_object,
"Wrong type of arguments. Expects an object as first argument.");

napi_value output;
NAPI_CALL(env,
napi_get_all_property_names(
env,
args[0],
napi_key_include_prototypes,
napi_key_skip_strings,
napi_key_numbers_to_strings,
&output));

return output;
}

static napi_value Wrap(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value arg;
Expand Down Expand Up @@ -220,6 +248,7 @@ static napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor descriptors[] = {
DECLARE_NAPI_PROPERTY("Get", Get),
DECLARE_NAPI_PROPERTY("Set", Set),
DECLARE_NAPI_PROPERTY("GetSymbolNames", GetSymbolNames),
DECLARE_NAPI_PROPERTY("Has", Has),
DECLARE_NAPI_PROPERTY("HasOwn", HasOwn),
DECLARE_NAPI_PROPERTY("Delete", Delete),
Expand Down