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_own_property_names #28944

Closed
wants to merge 3 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
85 changes: 85 additions & 0 deletions doc/api/n-api.md
Expand Up @@ -697,6 +697,58 @@ functions) that can be passed as parameters when `js_callback` is invoked. This
pointer is managed entirely by the threads and this callback. Thus this callback
should free the data.

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

> Stability: 1 - Experimental

todo

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

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

> Stability: 1 - Experimental

todo

```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;
```

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

> Stability: 1 - Experimental

todo

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

## Error Handling
N-API uses both return values and JavaScript exceptions for error handling.
The following sections explain the approach for each case.
Expand Down Expand Up @@ -3502,6 +3554,39 @@ 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

todo

```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
12 changes: 12 additions & 0 deletions src/js_native_api.h
Expand Up @@ -506,6 +506,18 @@ 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);
NAPI_EXTERN napi_status napi_get_own_property_names(napi_env env,
napi_value object,
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 {
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_JS_NATIVE_API_TYPES_H_
88 changes: 77 additions & 11 deletions src/js_native_api_v8.cc
Expand Up @@ -866,26 +866,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_include_own_properties:
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
33 changes: 24 additions & 9 deletions test/js-native-api/test_object/test.js
Expand Up @@ -6,7 +6,7 @@ const assert = require('assert');
const test_object = require(`./build/${common.buildType}/test_object`);


const object = {
let object = {
hello: 'world',
array: [
1, 94, 'str', 12.321, { test: 'obj in arr' }
Expand Down Expand Up @@ -203,17 +203,14 @@ assert.strictEqual(newObject.test_string, 'test string');
assert.strictEqual(obj.foo, 'baz');
}

{
// Verify that napi_get_property_names gets the right set of property names,
// i.e.: includes prototypes, only enumerable properties, skips symbols,
// and includes indices and converts them to strings.
object = Object.create({
inherited: 1
});

const object = Object.create({
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 @@ -222,8 +219,26 @@ assert.strictEqual(newObject.test_string, 'test string');
});
object[5] = 5;

{
// Verify that napi_get_property_names gets the right set of property names,
// i.e.: includes prototypes, only enumerable properties, skips symbols,
// and includes indices and converts them to strings.

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

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

{
// Verify that napi_get_own_property_names gets
// the right set of property names,
// i.e.: skips symbols and prototypes,
// and includes indices and converts them to strings.

assert.deepStrictEqual(test_object.GetOwnPropertyNames(object),
['5', 'normal', 'unenumerable']);
}

// Verify that passing NULL to napi_set_property() results in the correct
Expand Down
51 changes: 51 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,53 @@ 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 GetOwnPropertyNames(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_own_property_names(env, args[0], &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 +498,8 @@ 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("GetOwnPropertyNames", GetOwnPropertyNames),
DECLARE_NAPI_PROPERTY("Set", Set),
DECLARE_NAPI_PROPERTY("SetNamed", SetNamed),
DECLARE_NAPI_PROPERTY("Has", Has),
Expand Down