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

n-api: add napi_get_all_property_names #30006

Closed
wants to merge 4 commits into from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
91 changes: 91 additions & 0 deletions doc/api/n-api.md
Expand Up @@ -1656,6 +1656,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
Expand Down Expand Up @@ -3528,6 +3588,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
9 changes: 9 additions & 0 deletions src/js_native_api.h
Expand Up @@ -506,6 +506,15 @@ NAPI_EXTERN napi_status napi_get_value_bigint_words(napi_env env,
size_t* word_count,
uint64_t* words);

// 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);

// Instance data
NAPI_EXTERN napi_status napi_set_instance_data(napi_env env,
void* data,
Expand Down
21 changes: 21 additions & 0 deletions src/js_native_api_types.h
Expand Up @@ -115,4 +115,25 @@ typedef struct {
napi_status error_code;
} napi_extended_error_info;

#ifdef NAPI_EXPERIMENTAL
typedef enum {
himself65 marked this conversation as resolved.
Show resolved Hide resolved
napi_key_include_prototypes,
napi_key_own_only
} napi_key_collection_mode;

typedef enum {
napi_key_all_properties = 0,
napi_key_writable = 1,
himself65 marked this conversation as resolved.
Show resolved Hide resolved
napi_key_enumerable = 1 << 1,
napi_key_configurable = 1 << 2,
himself65 marked this conversation as resolved.
Show resolved Hide resolved
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be great if this #endif had a comment to tie it to #ifdef NAPI_EXPERIMENTAL, but just a nit.


#endif // SRC_JS_NATIVE_API_TYPES_H_
88 changes: 77 additions & 11 deletions src/js_native_api_v8.cc
Expand Up @@ -898,26 +898,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);

v8::MaybeLocal<v8::Array> maybe_propertynames = obj->GetPropertyNames(
context,
v8::KeyCollectionMode::kIncludePrototypes,
static_cast<v8::PropertyFilter>(
v8::PropertyFilter::ONLY_ENUMERABLE |
v8::PropertyFilter::SKIP_SYMBOLS),
v8::IndexFilter::kIncludeIndices,
v8::KeyConversionMode::kConvertToString);
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
11 changes: 11 additions & 0 deletions src/js_native_api_v8.h
Expand Up @@ -165,6 +165,17 @@ napi_status napi_set_last_error(napi_env env, napi_status error_code,
} \
} 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 v8impl {

//=== Conversion between V8 Handles and napi_value ========================
Expand Down
7 changes: 6 additions & 1 deletion test/js-native-api/test_object/test.js
Expand Up @@ -212,8 +212,10 @@ assert.strictEqual(newObject.test_string, 'test string');
inherited: 1
});

const fooSymbol = Symbol('foo');

object.normal = 2;
object[Symbol('foo')] = 3;
object[fooSymbol] = 3;
Object.defineProperty(object, 'unenumerable', {
value: 4,
enumerable: false,
Expand All @@ -224,6 +226,9 @@ assert.strictEqual(newObject.test_string, 'test string');

assert.deepStrictEqual(test_object.GetPropertyNames(object),
['5', 'normal', 'inherited']);

assert.deepStrictEqual(test_object.GetSymbolNames(object),
[fooSymbol]);
}

// Verify that passing NULL to napi_set_property() results in the correct
Expand Down
30 changes: 30 additions & 0 deletions test/js-native-api/test_object/test_object.c
@@ -1,3 +1,5 @@
#define NAPI_EXPERIMENTAL

#include <js_native_api.h>
#include "../common.h"
#include <string.h>
Expand Down Expand Up @@ -82,6 +84,33 @@ static napi_value GetPropertyNames(napi_env env, napi_callback_info info) {
return output;
}

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 Set(napi_env env, napi_callback_info info) {
size_t argc = 3;
napi_value args[3];
Expand Down Expand Up @@ -449,6 +478,7 @@ napi_value Init(napi_env env, napi_value exports) {
DECLARE_NAPI_PROPERTY("Get", Get),
DECLARE_NAPI_PROPERTY("GetNamed", GetNamed),
DECLARE_NAPI_PROPERTY("GetPropertyNames", GetPropertyNames),
DECLARE_NAPI_PROPERTY("GetSymbolNames", GetSymbolNames),
DECLARE_NAPI_PROPERTY("Set", Set),
DECLARE_NAPI_PROPERTY("SetNamed", SetNamed),
DECLARE_NAPI_PROPERTY("Has", Has),
Expand Down