Skip to content

Commit 6b1f36d

Browse files
committed
node-api: enable napi_ref for all value types
1 parent 0298b7f commit 6b1f36d

File tree

15 files changed

+497
-20
lines changed

15 files changed

+497
-20
lines changed

doc/api/n-api.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2046,6 +2046,48 @@ the `napi_value` in question is of the JavaScript type expected by the API.
20462046

20472047
### Enum types
20482048

2049+
#### `napi_features`
2050+
2051+
<!-- YAML
2052+
added: REPLACEME
2053+
-->
2054+
2055+
> Stability: 1 - Experimental
2056+
2057+
```c
2058+
typedef enum {
2059+
napi_feature_none = 0,
2060+
napi_feature_reference_all_types = 1 << 0,
2061+
2062+
napi_default_experimental_features = napi_feature_reference_all_types,
2063+
2064+
napi_default_features = napi_default_experimental_features, // version specific
2065+
} napi_features;
2066+
```
2067+
2068+
The `napi_features` allow changing internal behavior of existing Node-API
2069+
functions.
2070+
2071+
We pass a `napi_features` pointer to the `napi_module` struct in the
2072+
`NAPI_MODULE_X` macro. This macro is used for the module registration.
2073+
If the module is initialized without using this macro, then there will be
2074+
no features selected and the module will use the `napi_feature_none`.
2075+
2076+
Each Node-API version defines its own default set of features.
2077+
For the current version it can be accessed using `napi_default_features`.
2078+
A module can override the set of its enabled features by adding
2079+
`NAPI_CUSTOM_FEATURES` definition to the `.gyp` file and then defining the
2080+
value of the global `napi_module_features` variable.
2081+
To check enabled features use the `napi_is_feature_enabled` function.
2082+
2083+
For example, to disables `napi_feature_reference_all_types` feature we can
2084+
exclude its bit from the `napi_default_features` set:
2085+
2086+
```c
2087+
napi_features napi_module_features =
2088+
napi_default_features & ~napi_feature_reference_all_types;
2089+
```
2090+
20492091
#### `napi_key_collection_mode`
20502092

20512093
<!-- YAML
@@ -5700,6 +5742,32 @@ support it:
57005742
* If the function is not available, provide an alternate implementation
57015743
that does not use the function.
57025744

5745+
#### `napi_is_feature_enabled`
5746+
5747+
<!-- YAML
5748+
added: REPLACEME
5749+
-->
5750+
5751+
> Stability: 1 - Experimental
5752+
5753+
```c
5754+
NAPI_EXTERN napi_status napi_is_feature_enabled(napi_env env,
5755+
napi_features feature,
5756+
bool* result);
5757+
```
5758+
5759+
* `[in] env`: The environment that the API is invoked under.
5760+
* `[in] feature`: The feature that we want to test.
5761+
* `[out] result`: Whether the feature or a set of features are enabled.
5762+
5763+
Returns `napi_ok` if the API succeeded.
5764+
5765+
The function checks enabled features for the module.
5766+
If `feature` parameter has multiple `napi_features` bit flags, then the
5767+
function returns `true` only when all the requested fatures are enabled.
5768+
5769+
See [`napi_features`][] for more details about Node-API features.
5770+
57035771
## Memory management
57045772

57055773
### `napi_adjust_external_memory`

src/js_native_api.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,11 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_object_seal(napi_env env,
566566
napi_value object);
567567
#endif // NAPI_VERSION >= 8
568568

569+
#ifdef NAPI_EXPERIMENTAL
570+
NAPI_EXTERN napi_status NAPI_CDECL
571+
napi_is_feature_enabled(napi_env env, napi_features feature, bool* result);
572+
#endif // NAPI_EXPERIMENTAL
573+
569574
EXTERN_C_END
570575

571576
#endif // SRC_JS_NATIVE_API_H_

src/js_native_api_types.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,37 @@ typedef enum {
108108
// * the definition of `napi_status` in doc/api/n-api.md to reflect the newly
109109
// added value(s).
110110

111+
#ifdef NAPI_EXPERIMENTAL
112+
// Features allow changing internal behavior of existing Node-API functions.
113+
//
114+
// We pass a napi_features pointer to the napi_module struct
115+
// in the NAPI_MODULE_X macro. This macro is used for the module registration.
116+
// If the module is initialized without using this macro, then there will be
117+
// no features selected and the module will use the napi_feature_none.
118+
//
119+
// Each Node-API version defines its own default set of features.
120+
// For the current version it can be accessed using napi_default_features.
121+
// A module can override the set of its enabled features by adding
122+
// NAPI_CUSTOM_FEATURES definition to the .gyp file and then defining the
123+
// value of the global napi_module_features variable.
124+
// To check enabled features use the `napi_is_feature_enabled` function.
125+
//
126+
// For example, to disables napi_feature_reference_all_types:
127+
// napi_features napi_module_features =
128+
// napi_default_features & ~napi_feature_reference_all_types;
129+
typedef enum {
130+
// To be used when no features needs to be set.
131+
napi_feature_none = 0,
132+
// Use napi_ref for all value types.
133+
// Not only objects, functions, and symbols as before.
134+
napi_feature_reference_all_types = 1 << 0,
135+
// Each version of NAPI is going to have its own default set of features.
136+
napi_default_experimental_features = napi_feature_reference_all_types,
137+
// This variable must be conditionally set depending on the NAPI version.
138+
napi_default_features = napi_default_experimental_features,
139+
} napi_features;
140+
#endif
141+
111142
typedef napi_value(NAPI_CDECL* napi_callback)(napi_env env,
112143
napi_callback_info info);
113144
typedef void(NAPI_CDECL* napi_finalize)(napi_env env,

src/js_native_api_v8.cc

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,9 @@ Reference::Reference(napi_env env, v8::Local<v8::Value> value, Args&&... args)
576576
: RefBase(env, std::forward<Args>(args)...),
577577
_persistent(env->isolate, value),
578578
_secondPassParameter(new SecondPassCallParameterRef(this)),
579-
_secondPassScheduled(false) {
579+
_secondPassScheduled(false),
580+
_canBeWeak(!env->IsFeatureEnabled(napi_feature_reference_all_types) ||
581+
value->IsObject() || value->IsFunction()) {
580582
if (RefCount() == 0) {
581583
SetWeak();
582584
}
@@ -652,7 +654,7 @@ void Reference::Finalize(bool is_env_teardown) {
652654
// the secondPassParameter so that even if it has been
653655
// scheduled no Finalization will be run.
654656
void Reference::ClearWeak() {
655-
if (!_persistent.IsEmpty()) {
657+
if (!_persistent.IsEmpty() && _canBeWeak) {
656658
_persistent.ClearWeak();
657659
}
658660
if (_secondPassParameter != nullptr) {
@@ -669,8 +671,13 @@ void Reference::SetWeak() {
669671
// nothing
670672
return;
671673
}
672-
_persistent.SetWeak(
673-
_secondPassParameter, FinalizeCallback, v8::WeakCallbackType::kParameter);
674+
if (_canBeWeak) {
675+
_persistent.SetWeak(_secondPassParameter,
676+
FinalizeCallback,
677+
v8::WeakCallbackType::kParameter);
678+
} else {
679+
_persistent.Reset();
680+
}
674681
*_secondPassParameter = this;
675682
}
676683

@@ -2495,9 +2502,11 @@ napi_status NAPI_CDECL napi_create_reference(napi_env env,
24952502
CHECK_ARG(env, result);
24962503

24972504
v8::Local<v8::Value> v8_value = v8impl::V8LocalValueFromJsValue(value);
2498-
if (!(v8_value->IsObject() || v8_value->IsFunction() ||
2499-
v8_value->IsSymbol())) {
2500-
return napi_set_last_error(env, napi_invalid_arg);
2505+
if (!env->IsFeatureEnabled(napi_feature_reference_all_types)) {
2506+
if (!(v8_value->IsObject() || v8_value->IsFunction() ||
2507+
v8_value->IsSymbol())) {
2508+
return napi_set_last_error(env, napi_invalid_arg);
2509+
}
25012510
}
25022511

25032512
v8impl::Reference* reference =
@@ -3257,3 +3266,12 @@ napi_status NAPI_CDECL napi_is_detached_arraybuffer(napi_env env,
32573266

32583267
return napi_clear_last_error(env);
32593268
}
3269+
3270+
napi_status NAPI_CDECL napi_is_feature_enabled(napi_env env,
3271+
napi_features feature,
3272+
bool* result) {
3273+
CHECK_ENV(env);
3274+
CHECK_ARG(env, result);
3275+
*result = env->IsFeatureEnabled(feature);
3276+
return napi_clear_last_error(env);
3277+
}

src/js_native_api_v8.h

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,10 @@ class RefTracker {
5050
} // end of namespace v8impl
5151

5252
struct napi_env__ {
53-
explicit napi_env__(v8::Local<v8::Context> context)
53+
explicit napi_env__(v8::Local<v8::Context> context, napi_features* features)
5454
: isolate(context->GetIsolate()), context_persistent(isolate, context) {
5555
CHECK_EQ(isolate, context->GetIsolate());
56+
SetFeatures(features);
5657
napi_clear_last_error(this);
5758
}
5859

@@ -89,13 +90,28 @@ struct napi_env__ {
8990
}
9091
}
9192

92-
// This should be overridden to schedule the finalization to a properiate
93+
// This should be overridden to schedule the finalization to appropriate
9394
// timing, like next tick of the event loop.
9495
virtual void CallFinalizer(napi_finalize cb, void* data, void* hint) {
9596
v8::HandleScope handle_scope(isolate);
9697
CallIntoModule([&](napi_env env) { cb(env, data, hint); });
9798
}
9899

100+
bool IsFeatureEnabled(napi_features feature) {
101+
// By comparing results of `&` operation to the feature parameter
102+
// we allow to test for multiple feature flags.
103+
return (_features & feature) == feature;
104+
}
105+
106+
void SetFeatures(napi_features* features) {
107+
if (features == nullptr) {
108+
_features = napi_feature_none;
109+
} else {
110+
const napi_features availableFeatures = napi_default_features;
111+
_features = static_cast<napi_features>(availableFeatures & *features);
112+
}
113+
}
114+
99115
virtual void DeleteMe() {
100116
// First we must finalize those references that have `napi_finalizer`
101117
// callbacks. The reason is that addons might store other references which
@@ -122,6 +138,7 @@ struct napi_env__ {
122138
int open_callback_scopes = 0;
123139
int refs = 1;
124140
void* instance_data = nullptr;
141+
napi_features _features = napi_feature_none;
125142

126143
protected:
127144
// Should not be deleted directly. Delete with `napi_env__::DeleteMe()`
@@ -435,6 +452,7 @@ class Reference : public RefBase {
435452
v8impl::Persistent<v8::Value> _persistent;
436453
SecondPassCallParameterRef* _secondPassParameter;
437454
bool _secondPassScheduled;
455+
const bool _canBeWeak;
438456

439457
FRIEND_TEST(JsNativeApiV8Test, Reference);
440458
};

src/node_api.cc

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@
2020
#include <memory>
2121

2222
node_napi_env__::node_napi_env__(v8::Local<v8::Context> context,
23-
const std::string& module_filename)
24-
: napi_env__(context), filename(module_filename) {
23+
const std::string& module_filename,
24+
napi_features* features)
25+
: napi_env__(context, features), filename(module_filename) {
2526
CHECK_NOT_NULL(node_env());
2627
}
2728

@@ -126,10 +127,11 @@ class BufferFinalizer : private Finalizer {
126127
};
127128

128129
inline napi_env NewEnv(v8::Local<v8::Context> context,
129-
const std::string& module_filename) {
130+
const std::string& module_filename,
131+
napi_features* features) {
130132
node_napi_env result;
131133

132-
result = new node_napi_env__(context, module_filename);
134+
result = new node_napi_env__(context, module_filename, features);
133135
// TODO(addaleax): There was previously code that tried to delete the
134136
// napi_env when its v8::Context was garbage collected;
135137
// However, as long as N-API addons using this napi_env are in place,
@@ -586,24 +588,42 @@ class AsyncContext {
586588

587589
} // end of namespace v8impl
588590

591+
void napi_module_register_by_symbol_with_features(
592+
v8::Local<v8::Object> exports,
593+
v8::Local<v8::Value> module,
594+
v8::Local<v8::Context> context,
595+
napi_addon_register_func init,
596+
napi_features* features);
597+
589598
// Intercepts the Node-V8 module registration callback. Converts parameters
590599
// to NAPI equivalents and then calls the registration callback specified
591600
// by the NAPI module.
592601
static void napi_module_register_cb(v8::Local<v8::Object> exports,
593602
v8::Local<v8::Value> module,
594603
v8::Local<v8::Context> context,
595604
void* priv) {
596-
napi_module_register_by_symbol(
605+
napi_module_register_by_symbol_with_features(
597606
exports,
598607
module,
599608
context,
600-
static_cast<const napi_module*>(priv)->nm_register_func);
609+
static_cast<const napi_module*>(priv)->nm_register_func,
610+
static_cast<const napi_module*>(priv)->nm_features);
601611
}
602612

603613
void napi_module_register_by_symbol(v8::Local<v8::Object> exports,
604614
v8::Local<v8::Value> module,
605615
v8::Local<v8::Context> context,
606616
napi_addon_register_func init) {
617+
napi_module_register_by_symbol_with_features(
618+
exports, module, context, init, nullptr);
619+
}
620+
621+
void napi_module_register_by_symbol_with_features(
622+
v8::Local<v8::Object> exports,
623+
v8::Local<v8::Value> module,
624+
v8::Local<v8::Context> context,
625+
napi_addon_register_func init,
626+
napi_features* features) {
607627
node::Environment* node_env = node::Environment::GetCurrent(context);
608628
std::string module_filename = "";
609629
if (init == nullptr) {
@@ -631,7 +651,7 @@ void napi_module_register_by_symbol(v8::Local<v8::Object> exports,
631651
}
632652

633653
// Create a new napi_env for this specific module.
634-
napi_env env = v8impl::NewEnv(context, module_filename);
654+
napi_env env = v8impl::NewEnv(context, module_filename, features);
635655

636656
napi_value _exports;
637657
env->CallIntoModule([&](napi_env env) {

src/node_api.h

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,12 @@ typedef struct napi_module {
3838
napi_addon_register_func nm_register_func;
3939
const char* nm_modname;
4040
void* nm_priv;
41+
#ifdef NAPI_EXPERIMENTAL
42+
napi_features* nm_features;
43+
void* reserved[3];
44+
#else
4145
void* reserved[4];
46+
#endif
4247
} napi_module;
4348

4449
#define NAPI_MODULE_VERSION 1
@@ -73,18 +78,43 @@ typedef struct napi_module {
7378
static void fn(void)
7479
#endif
7580

81+
#ifdef NAPI_EXPERIMENTAL
82+
#ifdef NAPI_CUSTOM_FEATURES
83+
84+
// Define value of napi_module_features variable in your module when
85+
// NAPI_CUSTOM_FEATURES is set in gyp file.
86+
extern napi_features napi_module_features;
87+
#define NAPI_DEFINE_DEFAULT_FEATURES
88+
89+
#else // NAPI_CUSTOM_FEATURES
90+
91+
#define NAPI_DEFINE_DEFAULT_FEATURES \
92+
static napi_features napi_module_features = napi_default_features;
93+
94+
#endif // NAPI_CUSTOM_FEATURES
95+
96+
#define NAPI_FEATURES_PTR /* NOLINT */ &napi_module_features,
97+
98+
#else // NAPI_EXPERIMENTAL
99+
#define NAPI_DEFINE_DEFAULT_FEATURES
100+
#define NAPI_FEATURES_PTR
101+
#endif // NAPI_EXPERIMENTAL
102+
76103
#define NAPI_MODULE_X(modname, regfunc, priv, flags) \
77104
EXTERN_C_START \
105+
NAPI_DEFINE_DEFAULT_FEATURES \
78106
static napi_module _module = { \
79107
NAPI_MODULE_VERSION, \
80108
flags, \
81109
__FILE__, \
82110
regfunc, \
83111
#modname, \
84112
priv, \
85-
{0}, \
113+
NAPI_FEATURES_PTR{0}, \
86114
}; \
87-
NAPI_C_CTOR(_register_##modname) { napi_module_register(&_module); } \
115+
NAPI_C_CTOR(_register_##modname) { \
116+
napi_module_register(&_module); \
117+
} \
88118
EXTERN_C_END
89119

90120
#define NAPI_MODULE_INITIALIZER_X(base, version) \

0 commit comments

Comments
 (0)