diff --git a/object-template-demo/README.md b/object-template-demo/README.md new file mode 100644 index 00000000..782cfd92 --- /dev/null +++ b/object-template-demo/README.md @@ -0,0 +1,3 @@ +The goal of this demo is to show how to implement V8 ObjectTemplate-like handlers. +NAN uses the ObjectTemplate directly: the code is adapted from NAN namedinterceptors unit test. +Node-API cannot use the ObjectTemplate: it uses the JavaScript Proxy object to get similar behavior. diff --git a/object-template-demo/nan/binding.gyp b/object-template-demo/nan/binding.gyp new file mode 100644 index 00000000..9fe7ff37 --- /dev/null +++ b/object-template-demo/nan/binding.gyp @@ -0,0 +1,11 @@ +{ + "targets": [ + { + "target_name": "object-template-demo", + "sources": [ "object-template-demo.cc" ], + "include_dirs": [ + " + ********************************************************************/ + +#include +#include + +using namespace Nan; // NOLINT(build/namespaces) + +class NamedInterceptor : public ObjectWrap { + char buf[256]; + + public: + NamedInterceptor() { std::strncpy(this->buf, "foo", sizeof (this->buf)); } + static NAN_MODULE_INIT(Init); + static v8::Local NewInstance (); + static NAN_METHOD(New); + + static NAN_PROPERTY_GETTER(PropertyGetter); + static NAN_PROPERTY_SETTER(PropertySetter); + static NAN_PROPERTY_ENUMERATOR(PropertyEnumerator); + static NAN_PROPERTY_DELETER(PropertyDeleter); + static NAN_PROPERTY_QUERY(PropertyQuery); +}; + +static Persistent namedinterceptors_constructor; + +NAN_METHOD(CreateNew) { + info.GetReturnValue().Set(NamedInterceptor::NewInstance()); +} + +NAN_MODULE_INIT(NamedInterceptor::Init) { + v8::Local tpl = + Nan::New(NamedInterceptor::New); + namedinterceptors_constructor.Reset(tpl); + tpl->SetClassName(Nan::New("NamedInterceptor").ToLocalChecked()); + tpl->InstanceTemplate()->SetInternalFieldCount(1); + v8::Local inst = tpl->InstanceTemplate(); + + SetNamedPropertyHandler( + inst + , NamedInterceptor::PropertyGetter + , NamedInterceptor::PropertySetter + , NamedInterceptor::PropertyQuery + , NamedInterceptor::PropertyDeleter + , NamedInterceptor::PropertyEnumerator); + + v8::Local createnew = + Nan::GetFunction(Nan::New(CreateNew)) + .ToLocalChecked(); + Set(target, Nan::New("create").ToLocalChecked(), createnew); +} + +v8::Local NamedInterceptor::NewInstance () { + EscapableHandleScope scope; + v8::Local constructorHandle = + Nan::New(namedinterceptors_constructor); + v8::Local instance = + Nan::NewInstance(GetFunction(constructorHandle).ToLocalChecked()) + .ToLocalChecked(); + return scope.Escape(instance); +} + +NAN_METHOD(NamedInterceptor::New) { + NamedInterceptor* interceptor = new NamedInterceptor(); + interceptor->Wrap(info.This()); + info.GetReturnValue().Set(info.This()); +} + + +NAN_PROPERTY_GETTER(NamedInterceptor::PropertyGetter) { + NamedInterceptor* interceptor = + ObjectWrap::Unwrap(info.Holder()); + if (!std::strcmp(*Nan::Utf8String(property), "prop")) { + info.GetReturnValue().Set(Nan::New(interceptor->buf).ToLocalChecked()); + } else { + info.GetReturnValue().Set(Nan::New("bar").ToLocalChecked()); + } +} + +NAN_PROPERTY_SETTER(NamedInterceptor::PropertySetter) { + NamedInterceptor* interceptor = + ObjectWrap::Unwrap(info.Holder()); + if (!std::strcmp(*Nan::Utf8String(property), "prop")) { + std::strncpy( + interceptor->buf + , *Nan::Utf8String(value) + , sizeof (interceptor->buf)); + info.GetReturnValue().Set(info.This()); + } else { + info.GetReturnValue().Set(info.This()); + } +} + +NAN_PROPERTY_ENUMERATOR(NamedInterceptor::PropertyEnumerator) { + v8::Local arr = Nan::New(); + Set(arr, 0, Nan::New("value").ToLocalChecked()); + info.GetReturnValue().Set(arr); +} + +NAN_PROPERTY_DELETER(NamedInterceptor::PropertyDeleter) { + NamedInterceptor* interceptor = + ObjectWrap::Unwrap(info.Holder()); + std::strncpy(interceptor->buf, "goober", sizeof (interceptor->buf)); + info.GetReturnValue().Set(True()); +} + +NAN_PROPERTY_QUERY(NamedInterceptor::PropertyQuery) { + Nan::Utf8String s(property); + if (!std::strcmp(*s, "thing")) { + return info.GetReturnValue().Set(Nan::New(v8::DontEnum)); + } + if (!std::strcmp(*s, "value")) { + return info.GetReturnValue().Set(Nan::New(0)); + } +} + +NODE_MODULE(namedinterceptors, NamedInterceptor::Init) diff --git a/object-template-demo/nan/package.json b/object-template-demo/nan/package.json new file mode 100644 index 00000000..75defd72 --- /dev/null +++ b/object-template-demo/nan/package.json @@ -0,0 +1,15 @@ +{ + "name": "object-template-demo", + "version": "0.0.0", + "description": "Intercept named property access using V8 ObjectTemplate", + "main": "index.js", + "private": true, + "gypfile": true, + "scripts": { + "test": "node index.js" + }, + "dependencies": { + "bindings": "~1.5.0", + "nan": "^2.14.0" + } +} diff --git a/object-template-demo/napi/binding.gyp b/object-template-demo/napi/binding.gyp new file mode 100644 index 00000000..c1e8cfca --- /dev/null +++ b/object-template-demo/napi/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "object_template_demo", + "sources": [ "object-template-demo.cc", "proxy-template.cc" ] + } + ] +} diff --git a/object-template-demo/napi/index.js b/object-template-demo/napi/index.js new file mode 100644 index 00000000..bcf2920e --- /dev/null +++ b/object-template-demo/napi/index.js @@ -0,0 +1,10 @@ +const addon = require('bindings')('object_template_demo'); + +const interceptor = addon.create(); +console.log(interceptor.prop); // 'foo' +interceptor.prop = 'setting a value'; +console.log(interceptor.prop); // 'setting a value' +delete interceptor.something; +console.log(interceptor.prop); // 'goober'; +console.log(Object.prototype.hasOwnProperty.call(interceptor, "thing")); // true +console.log(Object.keys(interceptor)[0]); // 'value' diff --git a/object-template-demo/napi/node-api-common.h b/object-template-demo/napi/node-api-common.h new file mode 100644 index 00000000..cc7bd91e --- /dev/null +++ b/object-template-demo/napi/node-api-common.h @@ -0,0 +1,141 @@ +#include +#include +#include + +// Empty value so that macros here are able to return NULL or void +#define NODE_API_RETVAL_NOTHING // Intentionally blank #define + +#define GET_AND_THROW_LAST_ERROR(env) \ + do { \ + const napi_extended_error_info* error_info; \ + napi_get_last_error_info((env), &error_info); \ + bool is_pending; \ + const char* err_message = error_info->error_message; \ + napi_is_exception_pending((env), &is_pending); \ + /* If an exception is already pending, don't rethrow it */ \ + if (!is_pending) { \ + const char* error_message = \ + err_message != NULL ? err_message : "empty error message"; \ + napi_throw_error((env), NULL, error_message); \ + } \ + } while (0) + +#define NODE_API_ASSERT_BASE(env, assertion, message, ret_val) \ + do { \ + if (!(assertion)) { \ + napi_throw_error( \ + (env), NULL, "assertion (" #assertion ") failed: " message); \ + return ret_val; \ + } \ + } while (0) + +// Returns NULL on failed assertion. +// This is meant to be used inside napi_callback methods. +#define NODE_API_ASSERT(env, assertion, message) \ + NODE_API_ASSERT_BASE(env, assertion, message, NULL) + +#define NODE_API_CALL_BASE(env, the_call, ret_val) \ + do { \ + if ((the_call) != napi_ok) { \ + GET_AND_THROW_LAST_ERROR((env)); \ + return ret_val; \ + } \ + } while (0) + +// Returns NULL if the_call doesn't return napi_ok. +#define NODE_API_CALL(env, the_call) NODE_API_CALL_BASE(env, the_call, NULL) + +// Returns empty if the_call doesn't return napi_ok. +#define NODE_API_CALL_RETURN_VOID(env, the_call) \ + NODE_API_CALL_BASE(env, the_call, NODE_API_RETVAL_NOTHING) + +#define CHECK_NAPI(...) \ + do { \ + napi_status res__ = (__VA_ARGS__); \ + if (res__ != napi_ok) { \ + return res__; \ + } \ + } while (0) + +#define NAPI_CALL(expr) NODE_API_CALL(env, expr); + +#ifdef __cpp_lib_span +#include +using std::span; +#else +/** + * @brief A span of values that can be used to pass arguments to function. + * + * For C++20 we should consider to replace it with std::span. + */ +template +struct span { + constexpr span(std::initializer_list il) noexcept + : data_{const_cast(il.begin())}, size_{il.size()} {} + constexpr span(T* data, size_t size) noexcept : data_{data}, size_{size} {} + [[nodiscard]] constexpr T* data() const noexcept { return data_; } + [[nodiscard]] constexpr size_t size() const noexcept { return size_; } + [[nodiscard]] constexpr T* begin() const noexcept { return data_; } + [[nodiscard]] constexpr T* end() const noexcept { return data_ + size_; } + const T& operator[](size_t index) const noexcept { return *(data_ + index); } + + private: + T* data_; + size_t size_; +}; +#endif // __cpp_lib_span + +struct RefHolder { + RefHolder(std::nullptr_t = nullptr) noexcept {} + explicit RefHolder(napi_env env, napi_value value) : env_(env) { + // Start with 2 to avoid ever going to 0 that creates a weak ref. + napi_create_reference(env, value, 2, &ref_); + } + + // The class is movable. + RefHolder(RefHolder&& other) noexcept + : env_(std::exchange(other.env_, nullptr)), + ref_(std::exchange(other.ref_, nullptr)) {} + + RefHolder& operator=(RefHolder&& other) noexcept { + if (this != &other) { + swap(*this, other); + RefHolder temp(std::move(other)); + } + return *this; + } + + // The class is not copyable. + RefHolder(const RefHolder& other) = delete; + RefHolder& operator=(const RefHolder& other) = delete; + + ~RefHolder() noexcept { + if (env_ != nullptr && ref_ != nullptr) { + uint32_t refCount{}; + napi_reference_unref(env_, ref_, &refCount); + if (refCount == 1) { + napi_delete_reference(env_, ref_); + } + } + } + + operator napi_value() const { + napi_value result{}; + if (ref_ != nullptr) { + napi_get_reference_value(env_, ref_, &result); + } + return result; + } + + explicit operator bool() const noexcept { return ref_ != nullptr; } + + friend void swap(RefHolder& left, RefHolder& right) noexcept { + using std::swap; + swap(left.env_, right.env_); + swap(left.ref_, right.ref_); + } + + private: + napi_env env_{}; + napi_ref ref_{}; +}; diff --git a/object-template-demo/napi/object-template-demo.cc b/object-template-demo/napi/object-template-demo.cc new file mode 100644 index 00000000..6846e1fc --- /dev/null +++ b/object-template-demo/napi/object-template-demo.cc @@ -0,0 +1,306 @@ +#include +#include +#include +#include "proxy-template.h" + +struct InstanceData { + RefHolder constructor_; + std::unique_ptr proxyTemplate_; +}; + +class NamedInterceptor { + char buf[256]; + + public: + NamedInterceptor() { std::strncpy(this->buf, "foo", sizeof(this->buf)); } + static napi_value Init(napi_env env, napi_value exports); + static napi_value Constructor(napi_env env); + static napi_value NewInstance(napi_env env); + static napi_value New(napi_env env, napi_callback_info info); + static napi_value CreateNew(napi_env env, napi_callback_info info); + + static napi_status ToUtf8String(napi_env env, + napi_value value, + std::string* result); + static napi_status PropertyGetter(napi_env env, + napi_value target, + napi_value key, + napi_value receiver, + napi_value* result); + static napi_status PropertySetter(napi_env env, + napi_value target, + napi_value key, + napi_value value, + napi_value receiver, + bool* result); + static napi_status PropertyDeleter(napi_env env, + napi_value target, + napi_value key, + bool* result); + static napi_status PropertyQuery(napi_env env, + napi_value target, + napi_value key, + bool* result); + static napi_status GetOwnPropertyDescriptor(napi_env env, + napi_value target, + napi_value key, + napi_value* result); + static napi_status OwnKeys(napi_env env, + napi_value target, + napi_value* result); +}; + +napi_value NamedInterceptor::CreateNew(napi_env env, napi_callback_info info) { + return NamedInterceptor::NewInstance(env); +} + +napi_value NamedInterceptor::Init(napi_env env, napi_value exports) { + napi_value constructor{}; + NAPI_CALL(napi_define_class(env, + "NamedInterceptor", + NAPI_AUTO_LENGTH, + NamedInterceptor::New, + nullptr, + 0, + nullptr, + &constructor)); + + ProxyHandlerBuilder handlerBuilder{}; + NAPI_CALL(handlerBuilder.Get(env, &PropertyGetter)); + NAPI_CALL(handlerBuilder.Set(env, &PropertySetter)); + NAPI_CALL(handlerBuilder.DeleteProperty(env, &PropertyDeleter)); + NAPI_CALL(handlerBuilder.Has(env, &PropertyQuery)); + NAPI_CALL( + handlerBuilder.GetOwnPropertyDescriptor(env, &GetOwnPropertyDescriptor)); + NAPI_CALL(handlerBuilder.OwnKeys(env, &OwnKeys)); + napi_value proxyHandler{}; + NAPI_CALL(handlerBuilder.NewHandler(env, &proxyHandler)); + NAPI_CALL(napi_set_instance_data( + env, + new InstanceData{RefHolder(env, constructor), + std::make_unique(env, proxyHandler)}, + [](napi_env /*env*/, void* data, void* /*hint*/) { + delete static_cast(data); + }, + nullptr)); + + napi_value createFunc{}; + NAPI_CALL(napi_create_function( + env, "create", NAPI_AUTO_LENGTH, CreateNew, nullptr, &createFunc)); + + NAPI_CALL(napi_set_named_property(env, exports, "create", createFunc)); + return exports; +} + +napi_value NamedInterceptor::Constructor(napi_env env) { + InstanceData* instanceData{}; + NAPI_CALL( + napi_get_instance_data(env, reinterpret_cast(&instanceData))); + return instanceData->constructor_; +} + +napi_value NamedInterceptor::NewInstance(napi_env env) { + napi_escapable_handle_scope scope{}; + NAPI_CALL(napi_open_escapable_handle_scope(env, &scope)); + + napi_value instance{}; + NAPI_CALL(napi_new_instance(env, Constructor(env), 0, nullptr, &instance)); + + napi_value result{}; + NAPI_CALL(napi_escape_handle(env, scope, instance, &result)); + NAPI_CALL(napi_close_escapable_handle_scope(env, scope)); + return result; +} + +napi_value NamedInterceptor::New(napi_env env, napi_callback_info info) { + napi_value newTarget{}; + NAPI_CALL(napi_get_new_target(env, info, &newTarget)); + NODE_API_ASSERT_BASE( + env, newTarget != nullptr, "Must be invoked as a constructor.", nullptr); + + napi_value proxyTarget{}, result{}; + NAPI_CALL(napi_create_function( + env, + "target", + NAPI_AUTO_LENGTH, + [](napi_env, napi_callback_info) -> napi_value { return nullptr; }, + nullptr, + &proxyTarget)); + + NamedInterceptor* obj = new NamedInterceptor(); + + NAPI_CALL(napi_wrap( + env, + proxyTarget, + obj, + [](napi_env /*env*/, void* data, void* /*hint*/) { + delete reinterpret_cast(data); + }, + nullptr, + nullptr)); + + InstanceData* instanceData{}; + NAPI_CALL( + napi_get_instance_data(env, reinterpret_cast(&instanceData))); + NAPI_CALL( + instanceData->proxyTemplate_->NewInstance(env, proxyTarget, &result)); + return result; +} + +napi_status NamedInterceptor::ToUtf8String(napi_env env, + napi_value value, + std::string* result) { + size_t size{}; + CHECK_NAPI(napi_get_value_string_utf8(env, value, nullptr, 0, &size)); + result->assign(size, ' '); + return napi_get_value_string_utf8( + env, value, &(*result)[0], size + 1, nullptr); +} + +napi_status NamedInterceptor::PropertyGetter(napi_env env, + napi_value target, + napi_value key, + napi_value receiver, + napi_value* result) { + NamedInterceptor* interceptor{}; + CHECK_NAPI(napi_unwrap(env, target, reinterpret_cast(&interceptor))); + std::string keyStr; + CHECK_NAPI(ToUtf8String(env, key, &keyStr)); + if (keyStr == "prop") { + return napi_create_string_utf8( + env, interceptor->buf, NAPI_AUTO_LENGTH, result); + } else { + return napi_create_string_utf8(env, "bar", NAPI_AUTO_LENGTH, result); + } +} + +napi_status NamedInterceptor::PropertySetter(napi_env env, + napi_value target, + napi_value key, + napi_value value, + napi_value receiver, + bool* result) { + NamedInterceptor* interceptor{}; + CHECK_NAPI(napi_unwrap(env, target, reinterpret_cast(&interceptor))); + std::string keyStr; + CHECK_NAPI(ToUtf8String(env, key, &keyStr)); + if (keyStr == "prop") { + std::string valueStr; + CHECK_NAPI(ToUtf8String(env, value, &valueStr)); + std::strncpy(interceptor->buf, valueStr.data(), sizeof(interceptor->buf)); + *result = true; + } else { + *result = false; + } + return napi_ok; +} + +napi_status NamedInterceptor::PropertyDeleter(napi_env env, + napi_value target, + napi_value key, + bool* result) { + NamedInterceptor* interceptor{}; + CHECK_NAPI(napi_unwrap(env, target, reinterpret_cast(&interceptor))); + std::strncpy(interceptor->buf, "goober", sizeof(interceptor->buf)); + *result = true; + return napi_ok; +} + +napi_status NamedInterceptor::PropertyQuery(napi_env env, + napi_value target, + napi_value key, + bool* result) { + std::string keyStr; + CHECK_NAPI(ToUtf8String(env, key, &keyStr)); + + if (keyStr == "thing") { + *result = true; + } + if (keyStr == "value") { + *result = true; + } + return napi_ok; +} + +napi_status NamedInterceptor::GetOwnPropertyDescriptor(napi_env env, + napi_value target, + napi_value key, + napi_value* result) { + std::string keyStr; + CHECK_NAPI(ToUtf8String(env, key, &keyStr)); + + if (keyStr == "thing") { + CHECK_NAPI(napi_create_object(env, result)); + napi_value trueValue{}; + CHECK_NAPI(napi_get_boolean(env, true, &trueValue)); + CHECK_NAPI( + napi_set_named_property(env, *result, "configurable", trueValue)); + CHECK_NAPI(napi_set_named_property(env, *result, "writable", trueValue)); + } + if (keyStr == "value") { + CHECK_NAPI(napi_create_object(env, result)); + napi_value trueValue{}; + CHECK_NAPI(napi_get_boolean(env, true, &trueValue)); + CHECK_NAPI( + napi_set_named_property(env, *result, "configurable", trueValue)); + CHECK_NAPI(napi_set_named_property(env, *result, "writable", trueValue)); + CHECK_NAPI(napi_set_named_property(env, *result, "enumerable", trueValue)); + } + if (keyStr == "arguments" || keyStr == "caller") { + CHECK_NAPI(napi_create_object(env, result)); + napi_value trueValue{}, falseValue{}; + CHECK_NAPI(napi_get_boolean(env, false, &falseValue)); + CHECK_NAPI(napi_get_boolean(env, false, &trueValue)); + CHECK_NAPI( + napi_set_named_property(env, *result, "configurable", falseValue)); + CHECK_NAPI(napi_set_named_property(env, *result, "writable", falseValue)); + CHECK_NAPI(napi_set_named_property(env, *result, "enumerable", falseValue)); + napi_value nullValue{}; + CHECK_NAPI(napi_get_null(env, &nullValue)); + CHECK_NAPI(napi_set_named_property(env, *result, "value", nullValue)); + } + if (keyStr == "prototype") { + CHECK_NAPI(napi_create_object(env, result)); + napi_value trueValue{}, falseValue{}; + CHECK_NAPI(napi_get_boolean(env, false, &falseValue)); + CHECK_NAPI(napi_get_boolean(env, true, &trueValue)); + CHECK_NAPI( + napi_set_named_property(env, *result, "configurable", falseValue)); + CHECK_NAPI(napi_set_named_property(env, *result, "writable", trueValue)); + CHECK_NAPI(napi_set_named_property(env, *result, "enumerable", falseValue)); + napi_value nullValue{}; + CHECK_NAPI(napi_get_null(env, &nullValue)); + CHECK_NAPI(napi_set_named_property(env, *result, "value", nullValue)); + } + return napi_ok; +} + +napi_status NamedInterceptor::OwnKeys(napi_env env, + napi_value target, + napi_value* result) { + CHECK_NAPI(napi_get_all_property_names(env, + target, + napi_key_own_only, + napi_key_all_properties, + napi_key_keep_numbers, + result)); + + uint32_t arraySize{}; + CHECK_NAPI(napi_get_array_length(env, *result, &arraySize)); + napi_value firstElement{}; + CHECK_NAPI(napi_get_element(env, *result, 0, &firstElement)); + CHECK_NAPI(napi_set_element(env, *result, arraySize, firstElement)); + napi_value arraySizeValue{}; + CHECK_NAPI(napi_create_uint32(env, arraySize, &arraySizeValue)); + CHECK_NAPI(napi_set_named_property(env, *result, "length", arraySizeValue)); + napi_value value{}; + CHECK_NAPI(napi_create_string_utf8(env, "value", NAPI_AUTO_LENGTH, &value)); + CHECK_NAPI(napi_set_element(env, *result, 0, value)); + return napi_ok; +} + +extern "C" napi_value Init(napi_env env, napi_value exports) { + return NamedInterceptor::Init(env, exports); +} + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init); diff --git a/object-template-demo/napi/package.json b/object-template-demo/napi/package.json new file mode 100644 index 00000000..6f4da065 --- /dev/null +++ b/object-template-demo/napi/package.json @@ -0,0 +1,14 @@ +{ + "name": "object_template_demo", + "version": "0.0.0", + "description": "Intercept named property access using Proxy", + "main": "index.js", + "private": true, + "gypfile": true, + "scripts": { + "test": "node index.js" + }, + "dependencies": { + "bindings": "~1.5.0" + } +} diff --git a/object-template-demo/napi/proxy-template.cc b/object-template-demo/napi/proxy-template.cc new file mode 100644 index 00000000..d64f5734 --- /dev/null +++ b/object-template-demo/napi/proxy-template.cc @@ -0,0 +1,726 @@ +#include "proxy-template.h" +#include + +namespace { + +static napi_status SetTrap(napi_env env, + napi_value handler, + const char* propertyName, + napi_value trap) noexcept { + if (trap != nullptr) { + CHECK_NAPI(napi_set_named_property(env, handler, propertyName, trap)); + } + return napi_ok; +} + +using TrapCaller = napi_status (*)(napi_env env, + void* trap, + napi_span args, + napi_value* result); + +struct TrapCallInfo { + TrapCaller Caller; + void* trap; +}; + +template +napi_callback CreateTrapCallback() noexcept { + return [](napi_env env, napi_callback_info info) noexcept -> napi_value { + TrapCallInfo* trapCallInfo{}; + napi_value args[argCount]{}; + size_t actualArgCount{argCount}; + NAPI_CALL(napi_get_cb_info(env, + info, + &actualArgCount, + args, + nullptr, + reinterpret_cast(&trapCallInfo))); + NODE_API_ASSERT_BASE(env, + actualArgCount == argCount, + "proxy trap requires argCount arguments.", + nullptr); + napi_value result{}; + NAPI_CALL(trapCallInfo->Caller( + env, trapCallInfo->trap, span(args, argCount), &result)); + return result; + }; +} + +napi_status ToVector(napi_env env, + napi_value arrayValue, + std::vector* result) { + bool isArray{}; + CHECK_NAPI(napi_is_array(env, arrayValue, &isArray)); + if (isArray) { + uint32_t arraySize{}; + CHECK_NAPI(napi_get_array_length(env, arrayValue, &arraySize)); + result->reserve(result->size() + arraySize); + for (uint32_t i = 0; i < arraySize; ++i) { + napi_value arrayElement{}; + CHECK_NAPI(napi_get_element(env, arrayValue, i, &arrayElement)); + result->push_back(arrayElement); + } + } + return napi_ok; +} + +} // namespace + +napi_status ProxyHandlerBuilder::NewHandler(napi_env env, + napi_value* result) noexcept { + CHECK_NAPI(napi_create_object(env, result)); + + CHECK_NAPI(SetTrap(env, *result, "apply", apply_)); + CHECK_NAPI(SetTrap(env, *result, "construct", construct_)); + CHECK_NAPI(SetTrap(env, *result, "defineProperty", defineProperty_)); + CHECK_NAPI(SetTrap(env, *result, "deleteProperty", deleteProperty_)); + CHECK_NAPI(SetTrap(env, *result, "get", get_)); + CHECK_NAPI(SetTrap( + env, *result, "getOwnPropertyDescriptor", getOwnPropertyDescriptor_)); + CHECK_NAPI(SetTrap(env, *result, "getPrototypeOf", getPrototypeOf_)); + CHECK_NAPI(SetTrap(env, *result, "has", has_)); + CHECK_NAPI(SetTrap(env, *result, "isExtensible", isExtensible_)); + CHECK_NAPI(SetTrap(env, *result, "ownKeys", ownKeys_)); + CHECK_NAPI(SetTrap(env, *result, "preventExtensions", preventExtensions_)); + CHECK_NAPI(SetTrap(env, *result, "set", set_)); + CHECK_NAPI(SetTrap(env, *result, "setPrototypeOf", setPrototypeOf_)); + return napi_ok; +} + +napi_value ProxyHandlerBuilder::Apply() noexcept { + return apply_; +} + +void ProxyHandlerBuilder::Apply(napi_value trap) noexcept { + apply_ = trap; +} + +napi_status ProxyHandlerBuilder::Apply(napi_env env, + napi_callback trap, + void* data) noexcept { + return napi_create_function( + env, "apply", NAPI_AUTO_LENGTH, trap, data, &apply_); +} + +napi_status ProxyHandlerBuilder::Apply( + napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value thisArg, + napi_value argumentList, + napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + return reinterpret_cast(trap)( + env, args[0], args[1], args[2], result); + }, + reinterpret_cast(trap)}; + return Apply(env, CreateTrapCallback<3>(), &callInfo); +} + +napi_status ProxyHandlerBuilder::Apply( + napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value thisArg, + napi_span argumentList, + napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{ + [](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + std::vector argVector; + CHECK_NAPI(ToVector(env, args[2], &argVector)); + return reinterpret_cast(trap)( + env, + args[0], + args[1], + span(argVector.data(), argVector.size()), + result); + }, + reinterpret_cast(trap)}; + return Apply(env, CreateTrapCallback<3>(), &callInfo); +} + +napi_value ProxyHandlerBuilder::Construct() noexcept { + return construct_; +} + +void ProxyHandlerBuilder::Construct(napi_value trap) noexcept { + construct_ = trap; +} + +napi_status ProxyHandlerBuilder::Construct(napi_env env, + napi_callback trap, + void* data) noexcept { + return napi_create_function( + env, "construct", NAPI_AUTO_LENGTH, trap, data, &construct_); +} + +napi_status ProxyHandlerBuilder::Construct( + napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value argumentList, + napi_value newTarget, + napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + return reinterpret_cast(trap)( + env, args[0], args[1], args[2], result); + }, + reinterpret_cast(trap)}; + return Construct(env, CreateTrapCallback<3>(), &callInfo); +} + +napi_status ProxyHandlerBuilder::Construct( + napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_span argumentList, + napi_value newTarget, + napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{ + [](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + std::vector argVector; + CHECK_NAPI(ToVector(env, args[1], &argVector)); + return reinterpret_cast(trap)( + env, + args[0], + span(argVector.data(), argVector.size()), + args[2], + result); + }, + reinterpret_cast(trap)}; + return Construct(env, CreateTrapCallback<3>(), &callInfo); +} + +napi_value ProxyHandlerBuilder::DefineProperty() noexcept { + return defineProperty_; +} + +void ProxyHandlerBuilder::DefineProperty(napi_value trap) noexcept { + defineProperty_ = trap; +} + +napi_status ProxyHandlerBuilder::DefineProperty(napi_env env, + napi_callback trap, + void* data) noexcept { + return napi_create_function( + env, "defineProperty", NAPI_AUTO_LENGTH, trap, data, &defineProperty_); +} + +napi_status ProxyHandlerBuilder::DefineProperty( + napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value descriptor, + napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + return reinterpret_cast(trap)( + env, args[0], args[1], args[2], result); + }, + reinterpret_cast(trap)}; + return DefineProperty(env, CreateTrapCallback<3>(), &callInfo); +} + +napi_status ProxyHandlerBuilder::DefineProperty( + napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value descriptor, + bool* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{ + [](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + bool boolResult{}; + CHECK_NAPI(reinterpret_cast(trap)( + env, args[0], args[1], args[2], &boolResult)); + return napi_get_boolean(env, boolResult, result); + }, + reinterpret_cast(trap)}; + return DefineProperty(env, CreateTrapCallback<3>(), &callInfo); +} + +napi_value ProxyHandlerBuilder::DeleteProperty() noexcept { + return deleteProperty_; +} + +void ProxyHandlerBuilder::DeleteProperty(napi_value trap) noexcept { + deleteProperty_ = trap; +} + +napi_status ProxyHandlerBuilder::DeleteProperty(napi_env env, + napi_callback trap, + void* data) noexcept { + return napi_create_function( + env, "deleteProperty", NAPI_AUTO_LENGTH, trap, data, &deleteProperty_); +} + +napi_status ProxyHandlerBuilder::DeleteProperty( + napi_env env, + napi_status (*trap)( + napi_env env, napi_value target, napi_value key, napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + return reinterpret_cast(trap)( + env, args[0], args[1], result); + }, + reinterpret_cast(trap)}; + return DeleteProperty(env, CreateTrapCallback<2>(), &callInfo); +} + +napi_status ProxyHandlerBuilder::DeleteProperty( + napi_env env, + napi_status (*trap)( + napi_env env, napi_value target, napi_value key, bool* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + bool boolResult{}; + CHECK_NAPI(reinterpret_cast(trap)( + env, args[0], args[1], &boolResult)); + return napi_get_boolean( + env, boolResult, result); + }, + reinterpret_cast(trap)}; + return DeleteProperty(env, CreateTrapCallback<2>(), &callInfo); +} + +napi_value ProxyHandlerBuilder::Get() noexcept { + return get_; +} + +void ProxyHandlerBuilder::Get(napi_value trap) noexcept { + get_ = trap; +} + +napi_status ProxyHandlerBuilder::Get(napi_env env, + napi_callback trap, + void* data) noexcept { + return napi_create_function(env, "get", NAPI_AUTO_LENGTH, trap, data, &get_); +} + +napi_status ProxyHandlerBuilder::Get(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value receiver, + napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + return reinterpret_cast(trap)( + env, args[0], args[1], args[2], result); + }, + reinterpret_cast(trap)}; + return Get(env, CreateTrapCallback<3>(), &callInfo); +} + +napi_value ProxyHandlerBuilder::GetOwnPropertyDescriptor() noexcept { + return getOwnPropertyDescriptor_; +} + +void ProxyHandlerBuilder::GetOwnPropertyDescriptor(napi_value trap) noexcept { + getOwnPropertyDescriptor_ = trap; +} + +napi_status ProxyHandlerBuilder::GetOwnPropertyDescriptor(napi_env env, + napi_callback trap, + void* data) noexcept { + return napi_create_function(env, + "getOwnPropertyDescriptor", + NAPI_AUTO_LENGTH, + trap, + data, + &getOwnPropertyDescriptor_); +} + +napi_status ProxyHandlerBuilder::GetOwnPropertyDescriptor( + napi_env env, + napi_status (*trap)( + napi_env env, napi_value target, napi_value key, napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + return reinterpret_cast(trap)( + env, args[0], args[1], result); + }, + reinterpret_cast(trap)}; + return GetOwnPropertyDescriptor(env, CreateTrapCallback<2>(), &callInfo); +} + +napi_value ProxyHandlerBuilder::GetPrototypeOf() noexcept { + return getPrototypeOf_; +} + +void ProxyHandlerBuilder::GetPrototypeOf(napi_value trap) noexcept { + getPrototypeOf_ = trap; +} + +napi_status ProxyHandlerBuilder::GetPrototypeOf(napi_env env, + napi_callback trap, + void* data) noexcept { + return napi_create_function( + env, "getPrototypeOf", NAPI_AUTO_LENGTH, trap, data, &getPrototypeOf_); +} + +napi_status ProxyHandlerBuilder::GetPrototypeOf( + napi_env env, + napi_status (*trap)(napi_env env, napi_value target, napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + return reinterpret_cast(trap)( + env, args[0], result); + }, + reinterpret_cast(trap)}; + return GetPrototypeOf(env, CreateTrapCallback<1>(), &callInfo); +} + +napi_value ProxyHandlerBuilder::Has() noexcept { + return has_; +} + +void ProxyHandlerBuilder::Has(napi_value trap) noexcept { + has_ = trap; +} + +napi_status ProxyHandlerBuilder::Has(napi_env env, + napi_callback trap, + void* data) noexcept { + return napi_create_function(env, "has", NAPI_AUTO_LENGTH, trap, data, &has_); +} + +napi_status ProxyHandlerBuilder::Has(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + return reinterpret_cast(trap)( + env, args[0], args[1], result); + }, + reinterpret_cast(trap)}; + return Has(env, CreateTrapCallback<2>(), &callInfo); +} + +napi_status ProxyHandlerBuilder::Has(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + bool* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + bool boolResult{}; + CHECK_NAPI(reinterpret_cast(trap)( + env, args[0], args[1], &boolResult)); + return napi_get_boolean( + env, boolResult, result); + }, + reinterpret_cast(trap)}; + return Has(env, CreateTrapCallback<2>(), &callInfo); +} + +napi_value ProxyHandlerBuilder::IsExtensible() noexcept { + return isExtensible_; +} + +void ProxyHandlerBuilder::IsExtensible(napi_value trap) noexcept { + isExtensible_ = trap; +} + +napi_status ProxyHandlerBuilder::IsExtensible(napi_env env, + napi_callback trap, + void* data) noexcept { + return napi_create_function( + env, "isExtensible", NAPI_AUTO_LENGTH, trap, data, &isExtensible_); +} + +napi_status ProxyHandlerBuilder::IsExtensible( + napi_env env, + napi_status (*trap)(napi_env env, napi_value target, napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + return reinterpret_cast(trap)( + env, args[0], result); + }, + reinterpret_cast(trap)}; + return IsExtensible(env, CreateTrapCallback<1>(), &callInfo); +} + +napi_status ProxyHandlerBuilder::IsExtensible( + napi_env env, + napi_status (*trap)(napi_env env, napi_value target, bool* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{ + [](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + bool boolResult{}; + CHECK_NAPI(reinterpret_cast(trap)(env, args[0], &boolResult)); + return napi_get_boolean(env, boolResult, result); + }, + reinterpret_cast(trap)}; + return IsExtensible(env, CreateTrapCallback<1>(), &callInfo); +} + +napi_value ProxyHandlerBuilder::OwnKeys() noexcept { + return ownKeys_; +} + +void ProxyHandlerBuilder::OwnKeys(napi_value trap) noexcept { + ownKeys_ = trap; +} + +napi_status ProxyHandlerBuilder::OwnKeys(napi_env env, + napi_callback trap, + void* data) noexcept { + return napi_create_function( + env, "ownKeys", NAPI_AUTO_LENGTH, trap, data, &ownKeys_); +} + +napi_status ProxyHandlerBuilder::OwnKeys( + napi_env env, + napi_status (*trap)(napi_env env, napi_value target, napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + return reinterpret_cast(trap)( + env, args[0], result); + }, + reinterpret_cast(trap)}; + return OwnKeys(env, CreateTrapCallback<1>(), &callInfo); +} + +napi_value ProxyHandlerBuilder::PreventExtensions() noexcept { + return preventExtensions_; +} + +void ProxyHandlerBuilder::PreventExtensions(napi_value trap) noexcept { + preventExtensions_ = trap; +} + +napi_status ProxyHandlerBuilder::PreventExtensions(napi_env env, + napi_callback trap, + void* data) noexcept { + return napi_create_function(env, + "preventExtensions", + NAPI_AUTO_LENGTH, + trap, + data, + &preventExtensions_); +} + +napi_status ProxyHandlerBuilder::PreventExtensions( + napi_env env, + napi_status (*trap)(napi_env env, napi_value target, napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + return reinterpret_cast(trap)( + env, args[0], result); + }, + reinterpret_cast(trap)}; + return PreventExtensions(env, CreateTrapCallback<1>(), &callInfo); +} + +napi_status ProxyHandlerBuilder::PreventExtensions( + napi_env env, + napi_status (*trap)(napi_env env, napi_value target, bool* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{ + [](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + bool boolResult{}; + CHECK_NAPI(reinterpret_cast(trap)(env, args[0], &boolResult)); + return napi_get_boolean(env, boolResult, result); + }, + reinterpret_cast(trap)}; + return PreventExtensions(env, CreateTrapCallback<1>(), &callInfo); +} + +napi_value ProxyHandlerBuilder::Set() noexcept { + return set_; +} + +void ProxyHandlerBuilder::Set(napi_value trap) noexcept { + set_ = trap; +} + +napi_status ProxyHandlerBuilder::Set(napi_env env, + napi_callback trap, + void* data) noexcept { + return napi_create_function(env, "set", NAPI_AUTO_LENGTH, trap, data, &set_); +} + +napi_status ProxyHandlerBuilder::Set(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value value, + napi_value receiver, + napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{ + [](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + return reinterpret_cast(trap)( + env, args[0], args[1], args[2], args[3], result); + }, + reinterpret_cast(trap)}; + return Set(env, CreateTrapCallback<4>(), &callInfo); +} + +napi_status ProxyHandlerBuilder::Set(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value value, + napi_value receiver, + bool* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{ + [](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + bool boolResult{}; + CHECK_NAPI(reinterpret_cast(trap)( + env, args[0], args[1], args[2], args[3], &boolResult)); + return napi_get_boolean(env, boolResult, result); + }, + reinterpret_cast(trap)}; + return Set(env, CreateTrapCallback<4>(), &callInfo); +} + +napi_value ProxyHandlerBuilder::SetPrototypeOf() noexcept { + return setPrototypeOf_; +} + +void ProxyHandlerBuilder::SetPrototypeOf(napi_value trap) noexcept { + setPrototypeOf_ = trap; +} + +napi_status ProxyHandlerBuilder::SetPrototypeOf(napi_env env, + napi_callback trap, + void* data) noexcept { + return napi_create_function( + env, "setPrototypeOf", NAPI_AUTO_LENGTH, trap, data, &setPrototypeOf_); +} + +napi_status ProxyHandlerBuilder::SetPrototypeOf( + napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value prototype, + napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + return reinterpret_cast(trap)( + env, args[0], args[1], result); + }, + reinterpret_cast(trap)}; + return SetPrototypeOf(env, CreateTrapCallback<2>(), &callInfo); +} + +napi_status ProxyHandlerBuilder::SetPrototypeOf( + napi_env env, + napi_status (*trap)( + napi_env env, napi_value target, napi_value prototype, bool* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + bool boolResult{}; + CHECK_NAPI(reinterpret_cast(trap)( + env, args[0], args[1], &boolResult)); + return napi_get_boolean( + env, boolResult, result); + }, + reinterpret_cast(trap)}; + return SetPrototypeOf(env, CreateTrapCallback<2>(), &callInfo); +} + +//============= + +ProxyTemplate::ProxyTemplate(napi_env env, napi_value proxyHandler) noexcept + : proxyHandler_(RefHolder(env, proxyHandler)) {} + +napi_status ProxyTemplate::NewInstance(napi_env env, + napi_value target, + napi_value* result) noexcept { + napi_value proxyConstructor{}; + CHECK_NAPI(GetProxyConstructor(env, &proxyConstructor)); + + napi_value args[] = {target, static_cast(proxyHandler_)}; + return napi_new_instance(env, proxyConstructor, 2, args, result); +} + +napi_status ProxyTemplate::GetProxyConstructor(napi_env env, + napi_value* result) noexcept { + if (!proxyConstructor_) { + napi_value global{}, proxyConstructor{}; + CHECK_NAPI(napi_get_global(env, &global)); + CHECK_NAPI( + napi_get_named_property(env, global, "Proxy", &proxyConstructor)); + proxyConstructor_ = RefHolder(env, proxyConstructor); + } + *result = proxyConstructor_; + return napi_ok; +} diff --git a/object-template-demo/napi/proxy-template.h b/object-template-demo/napi/proxy-template.h new file mode 100644 index 00000000..85fd85f5 --- /dev/null +++ b/object-template-demo/napi/proxy-template.h @@ -0,0 +1,249 @@ +#include "node-api-common.h" + +template +using napi_span = span; + +// Creates a handler object for a JavaScript Proxy. +// See +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy +// for details about JavaScript Proxy. +// +// There are four different ways we can set each Proxy handler trap: +// - using napi_value function. +// - using napi_callback that creates function. +// - using a callback signature that matches Proxy handler definition arguments. +// - using a callback based on the Proxy handler trap signature where we replace +// napi_value with strongly typed values such as bool or +// napi_span. +// +// Note that ProxyHandlerBuilder keeps handler trap functions as napi_value +// and thus cannot be persisted. The typical usage is to create new +// ProxyHandlerBuilder instance on the call stack, set handler traps, create +// handler to pass to Proxy constructor or ProxyTemplate, and not to reuse the +// ProxyHandlerBuilder instance. +struct ProxyHandlerBuilder { + napi_status NewHandler(napi_env env, napi_value* result) noexcept; + + napi_value Apply() noexcept; + void Apply(napi_value trap) noexcept; + napi_status Apply(napi_env env, + napi_callback trap, + void* data = nullptr) noexcept; + napi_status Apply(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value thisArg, + napi_value argumentList, + napi_value* result)); + napi_status Apply(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value thisArg, + napi_span argumentList, + napi_value* result)); + + napi_value Construct() noexcept; + void Construct(napi_value trap) noexcept; + napi_status Construct(napi_env env, + napi_callback trap, + void* data = nullptr) noexcept; + napi_status Construct(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value argumentList, + napi_value newTarget, + napi_value* result)); + napi_status Construct(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_span argumentList, + napi_value newTarget, + napi_value* result)); + + napi_value DefineProperty() noexcept; + void DefineProperty(napi_value trap) noexcept; + napi_status DefineProperty(napi_env env, + napi_callback trap, + void* data = nullptr) noexcept; + napi_status DefineProperty(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value descriptor, + napi_value* result)); + napi_status DefineProperty(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value descriptor, + bool* result)); + + napi_value DeleteProperty() noexcept; + void DeleteProperty(napi_value trap) noexcept; + napi_status DeleteProperty(napi_env env, + napi_callback trap, + void* data = nullptr) noexcept; + napi_status DeleteProperty(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value* result)); + napi_status DeleteProperty(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + bool* result)); + + napi_value Get() noexcept; + void Get(napi_value trap) noexcept; + napi_status Get(napi_env env, + napi_callback trap, + void* data = nullptr) noexcept; + napi_status Get(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value receiver, + napi_value* result)); + + napi_value GetOwnPropertyDescriptor() noexcept; + void GetOwnPropertyDescriptor(napi_value trap) noexcept; + napi_status GetOwnPropertyDescriptor(napi_env env, + napi_callback trap, + void* data = nullptr) noexcept; + napi_status GetOwnPropertyDescriptor(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value* result)); + + napi_value GetPrototypeOf() noexcept; + void GetPrototypeOf(napi_value trap) noexcept; + napi_status GetPrototypeOf(napi_env env, + napi_callback trap, + void* data = nullptr) noexcept; + napi_status GetPrototypeOf(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value* result)); + + napi_value Has() noexcept; + void Has(napi_value trap) noexcept; + napi_status Has(napi_env env, + napi_callback trap, + void* data = nullptr) noexcept; + napi_status Has(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value* result)); + napi_status Has(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + bool* result)); + + napi_value IsExtensible() noexcept; + void IsExtensible(napi_value trap) noexcept; + napi_status IsExtensible(napi_env env, + napi_callback trap, + void* data = nullptr) noexcept; + napi_status IsExtensible(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value* result)); + napi_status IsExtensible(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + bool* result)); + + napi_value OwnKeys() noexcept; + void OwnKeys(napi_value trap) noexcept; + napi_status OwnKeys(napi_env env, + napi_callback trap, + void* data = nullptr) noexcept; + napi_status OwnKeys(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value* result)); + + napi_value PreventExtensions() noexcept; + void PreventExtensions(napi_value trap) noexcept; + napi_status PreventExtensions(napi_env env, + napi_callback trap, + void* data = nullptr) noexcept; + napi_status PreventExtensions(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value* result)); + napi_status PreventExtensions(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + bool* result)); + + napi_value Set() noexcept; + void Set(napi_value trap) noexcept; + napi_status Set(napi_env env, + napi_callback trap, + void* data = nullptr) noexcept; + napi_status Set(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value value, + napi_value receiver, + napi_value* result)); + napi_status Set(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value value, + napi_value receiver, + bool* result)); + + napi_value SetPrototypeOf() noexcept; + void SetPrototypeOf(napi_value trap) noexcept; + napi_status SetPrototypeOf(napi_env env, + napi_callback trap, + void* data = nullptr) noexcept; + napi_status SetPrototypeOf(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value prototype, + napi_value* result)); + napi_status SetPrototypeOf(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value prototype, + bool* result)); + + private: + napi_value apply_{}; + napi_value construct_{}; + napi_value defineProperty_{}; + napi_value deleteProperty_{}; + napi_value get_{}; + napi_value getOwnPropertyDescriptor_{}; + napi_value getPrototypeOf_{}; + napi_value has_{}; + napi_value isExtensible_{}; + napi_value ownKeys_{}; + napi_value preventExtensions_{}; + napi_value set_{}; + napi_value setPrototypeOf_{}; +}; + +struct ProxyTemplate { + ProxyTemplate(napi_env env, napi_value proxyHandler) noexcept; + + napi_status NewInstance(napi_env env, + napi_value target, + napi_value* result) noexcept; + + private: + napi_status GetProxyConstructor(napi_env env, napi_value* result) noexcept; + + private: + RefHolder proxyConstructor_{}; + RefHolder proxyHandler_{}; +};