From 8dfc97a67268c253387f465c31e84a6b0f01cae8 Mon Sep 17 00:00:00 2001 From: Jason Ginchereau Date: Mon, 24 Apr 2017 14:21:17 -0700 Subject: [PATCH] Add PropertyDescriptor creation methods Add static methods on the PropertyDescriptor class that provide convenient ways to construct PropertyDescriptor instances for different kinds of property descriptors. These are equivalent to the similar static methods on the ObjectWrap class, except they are for use when defining properties outside of an object-wrapping scenario. Callbacks are templatized (like with Function::New()) so that they can work with lambdas or regular function pointers. New test cases cover the various overloads for constructing property descriptors and defining properties. And the Name class constructors needed to be made public, to allow code like value.As(), that is used by the test. --- napi-inl.h | 259 +++++++++++++++++++++++++++++++++++++++++++++++ napi.h | 87 +++++++++++++++- test/binding.cc | 2 + test/binding.gyp | 1 + test/object.cc | 80 +++++++++++++++ test/object.js | 71 +++++++++++++ 6 files changed, 495 insertions(+), 5 deletions(-) create mode 100644 test/object.cc create mode 100644 test/object.js diff --git a/napi-inl.h b/napi-inl.h index b0636a3df..177edb1db 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -1125,6 +1125,35 @@ struct FinalizeData { Hint* hint; }; +template +struct AccessorCallbackData { + static inline + napi_value GetterWrapper(napi_env env, napi_callback_info info) { + try { + CallbackInfo callbackInfo(env, info); + AccessorCallbackData* callbackData = + static_cast(callbackInfo.Data()); + return callbackData->getterCallback(callbackInfo); + } + NAPI_RETHROW_JS_ERROR(env) + } + + static inline + napi_value SetterWrapper(napi_env env, napi_callback_info info) { + try { + CallbackInfo callbackInfo(env, info); + AccessorCallbackData* callbackData = + static_cast(callbackInfo.Data()); + callbackData->setterCallback(callbackInfo); + return nullptr; + } + NAPI_RETHROW_JS_ERROR(env) + } + + Getter getterCallback; + Setter setterCallback; +}; + } // namespace details template @@ -1908,6 +1937,236 @@ inline void CallbackInfo::SetData(void* data) { _data = data; } +//////////////////////////////////////////////////////////////////////////////// +// PropertyDescriptor class +//////////////////////////////////////////////////////////////////////////////// + +template +inline PropertyDescriptor +PropertyDescriptor::Accessor(const char* utf8name, + Getter getter, + napi_property_attributes attributes, + void* data) { + typedef details::CallbackData CbData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({ getter }); + + return PropertyDescriptor({ + utf8name, + nullptr, + nullptr, + CbData::Wrapper, + nullptr, + nullptr, + attributes, + callbackData + }); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor(const std::string& utf8name, + Getter getter, + napi_property_attributes attributes, + void* data) { + return Accessor(utf8name.c_str(), getter, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor(napi_value name, + Getter getter, + napi_property_attributes attributes, + void* data) { + typedef details::CallbackData CbData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({ getter }); + + return PropertyDescriptor({ + nullptr, + name, + nullptr, + CbData::Wrapper, + nullptr, + nullptr, + attributes, + callbackData + }); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor(Name name, + Getter getter, + napi_property_attributes attributes, + void* data) { + napi_value nameValue = name; + return PropertyDescriptor::Accessor(nameValue, getter, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor(const char* utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { + typedef details::AccessorCallbackData CbData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({ getter, setter }); + + return PropertyDescriptor({ + utf8name, + nullptr, + nullptr, + CbData::GetterWrapper, + CbData::SetterWrapper, + nullptr, + attributes, + callbackData + }); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor(const std::string& utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { + return Accessor(utf8name.c_str(), getter, setter, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor(napi_value name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { + typedef details::AccessorCallbackData CbData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({ getter, setter }); + + return PropertyDescriptor({ + nullptr, + name, + nullptr, + CbData::GetterWrapper, + CbData::SetterWrapper, + nullptr, + attributes, + callbackData + }); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor(Name name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { + napi_value nameValue = name; + return PropertyDescriptor::Accessor(nameValue, getter, setter, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function(const char* utf8name, + Callable cb, + napi_property_attributes attributes, + void* data) { + typedef decltype(cb(CallbackInfo(nullptr, nullptr))) ReturnType; + typedef details::CallbackData CbData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({ cb }); + + return PropertyDescriptor({ + utf8name, + nullptr, + CbData::Wrapper, + nullptr, + nullptr, + nullptr, + attributes, + callbackData + }); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function(const std::string& utf8name, + Callable cb, + napi_property_attributes attributes, + void* data) { + return Function(utf8name.c_str(), cb, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function(napi_value name, + Callable cb, + napi_property_attributes attributes, + void* data) { + typedef decltype(cb(CallbackInfo(nullptr, nullptr))) ReturnType; + typedef details::CallbackData CbData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({ cb }); + + return PropertyDescriptor({ + nullptr, + name, + CbData::Wrapper, + nullptr, + nullptr, + nullptr, + attributes, + callbackData + }); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function(Name name, + Callable cb, + napi_property_attributes attributes, + void* data) { + napi_value nameValue = name; + return PropertyDescriptor::Function(nameValue, cb, attributes, data); +} + +inline PropertyDescriptor PropertyDescriptor::Value(const char* utf8name, + napi_value value, + napi_property_attributes attributes) { + return PropertyDescriptor({ + utf8name, nullptr, nullptr, nullptr, nullptr, value, attributes, nullptr + }); +} + +inline PropertyDescriptor PropertyDescriptor::Value(const std::string& utf8name, + napi_value value, + napi_property_attributes attributes) { + return Value(utf8name.c_str(), value, attributes); +} + +inline PropertyDescriptor PropertyDescriptor::Value(napi_value name, + napi_value value, + napi_property_attributes attributes) { + return PropertyDescriptor({ + nullptr, name, nullptr, nullptr, nullptr, value, attributes, nullptr + }); +} + +inline PropertyDescriptor PropertyDescriptor::Value(Name name, + Napi::Value value, + napi_property_attributes attributes) { + napi_value nameValue = name; + napi_value valueValue = value; + return PropertyDescriptor::Value(nameValue, valueValue, attributes); +} + +inline PropertyDescriptor::PropertyDescriptor(napi_property_descriptor desc) + : _desc(desc) { +} + +inline PropertyDescriptor::operator napi_property_descriptor&() { + return _desc; +} + +inline PropertyDescriptor::operator const napi_property_descriptor&() const { + return _desc; +} + //////////////////////////////////////////////////////////////////////////////// // ObjectWrap class //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index 67b1cf42e..674522164 100644 --- a/napi.h +++ b/napi.h @@ -152,7 +152,7 @@ namespace Napi { }; class Name : public Value { - protected: + public: Name(); Name(napi_env env, napi_value value); }; @@ -685,10 +685,87 @@ namespace Napi { class PropertyDescriptor { public: - PropertyDescriptor(napi_property_descriptor desc) : _desc(desc) {} - - operator napi_property_descriptor&() { return _desc; } - operator const napi_property_descriptor&() const { return _desc; } + template + static PropertyDescriptor Accessor(const char* utf8name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor(const std::string& utf8name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor(napi_value name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor(Name name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor(const char* utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor(const std::string& utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor(napi_value name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor(Name name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function(const char* utf8name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function(const std::string& utf8name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function(napi_value name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function(Name name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor Value(const char* utf8name, + napi_value value, + napi_property_attributes attributes = napi_default); + static PropertyDescriptor Value(const std::string& utf8name, + napi_value value, + napi_property_attributes attributes = napi_default); + static PropertyDescriptor Value(napi_value name, + napi_value value, + napi_property_attributes attributes = napi_default); + static PropertyDescriptor Value(Name name, + Napi::Value value, + napi_property_attributes attributes = napi_default); + + PropertyDescriptor(napi_property_descriptor desc); + + operator napi_property_descriptor&(); + operator const napi_property_descriptor&() const; private: napi_property_descriptor _desc; diff --git a/test/binding.cc b/test/binding.cc index 69fd39a3f..d858e8d66 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -8,6 +8,7 @@ Object InitError(Env env); Object InitExternal(Env env); Object InitFunction(Env env); Object InitName(Env env); +Object InitObject(Env env); void Init(Env env, Object exports, Object module) { exports.Set("arraybuffer", InitArrayBuffer(env)); @@ -16,6 +17,7 @@ void Init(Env env, Object exports, Object module) { exports.Set("external", InitExternal(env)); exports.Set("function", InitFunction(env)); exports.Set("name", InitName(env)); + exports.Set("object", InitObject(env)); } NODE_API_MODULE(addon, Init) diff --git a/test/binding.gyp b/test/binding.gyp index 8c8053141..a76e9e51d 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -10,6 +10,7 @@ 'external.cc', 'function.cc', 'name.cc', + 'object.cc', ], 'include_dirs': ["(); +} + +Value TestFunction(const CallbackInfo& info) { + return Boolean::New(info.Env(), true); +} + +void DefineProperties(const CallbackInfo& info) { + Object obj = info[0].As(); + String nameType = info[1].As(); + + Boolean trueValue = Boolean::New(info.Env(), true); + + if (nameType.Utf8Value() == "literal") { + obj.DefineProperties({ + PropertyDescriptor::Accessor("readonlyAccessor", TestGetter), + PropertyDescriptor::Accessor("readwriteAccessor", TestGetter, TestSetter), + PropertyDescriptor::Value("readonlyValue", trueValue), + PropertyDescriptor::Value("readwriteValue", trueValue, napi_writable), + PropertyDescriptor::Value("enumerableValue", trueValue, napi_enumerable), + PropertyDescriptor::Value("configurableValue", trueValue, napi_configurable), + PropertyDescriptor::Function("function", TestFunction), + }); + } else if (nameType.Utf8Value() == "string") { + obj.DefineProperties({ + PropertyDescriptor::Accessor(std::string("readonlyAccessor"), TestGetter), + PropertyDescriptor::Accessor(std::string("readwriteAccessor"), TestGetter, TestSetter), + PropertyDescriptor::Value(std::string("readonlyValue"), trueValue), + PropertyDescriptor::Value(std::string("readwriteValue"), trueValue, napi_writable), + PropertyDescriptor::Value(std::string("enumerableValue"), trueValue, napi_enumerable), + PropertyDescriptor::Value(std::string("configurableValue"), trueValue, napi_configurable), + PropertyDescriptor::Function(std::string("function"), TestFunction), + }); + } else if (nameType.Utf8Value() == "value") { + obj.DefineProperties({ + PropertyDescriptor::Accessor( + Napi::String::New(info.Env(), "readonlyAccessor"), TestGetter), + PropertyDescriptor::Accessor( + Napi::String::New(info.Env(), "readwriteAccessor"), TestGetter, TestSetter), + PropertyDescriptor::Value( + Napi::String::New(info.Env(), "readonlyValue"), trueValue), + PropertyDescriptor::Value( + Napi::String::New(info.Env(), "readwriteValue"), trueValue, napi_writable), + PropertyDescriptor::Value( + Napi::String::New(info.Env(), "enumerableValue"), trueValue, napi_enumerable), + PropertyDescriptor::Value( + Napi::String::New(info.Env(), "configurableValue"), trueValue, napi_configurable), + PropertyDescriptor::Function( + Napi::String::New(info.Env(), "function"), TestFunction), + }); + } +} + +void DefineValueProperty(const CallbackInfo& info) { + Object obj = info[0].As(); + Name name = info[1].As(); + Value value = info[2]; + + obj.DefineProperty(PropertyDescriptor::Value(name, value)); +} + +Object InitObject(Env env) { + Object exports = Object::New(env); + + exports["defineProperties"] = Function::New(env, DefineProperties); + exports["defineValueProperty"] = Function::New(env, DefineValueProperty); + + return exports; +} diff --git a/test/object.js b/test/object.js new file mode 100644 index 000000000..718b73f6a --- /dev/null +++ b/test/object.js @@ -0,0 +1,71 @@ +'use strict'; +const buildType = process.config.target_defaults.default_configuration; +const binding = require(`./build/${buildType}/binding.node`); +const assert = require('assert'); + +function assertPropertyIs(obj, key, attribute) { + const propDesc = Object.getOwnPropertyDescriptor(obj, key); + assert.ok(propDesc); + assert.ok(propDesc[attribute]); +} + +function assertPropertyIsNot(obj, key, attribute) { + const propDesc = Object.getOwnPropertyDescriptor(obj, key); + assert.ok(propDesc); + assert.ok(!propDesc[attribute]); +} + +function testDefineProperties(nameType) { + const obj = {}; + binding.object.defineProperties(obj, nameType); + + assertPropertyIsNot(obj, 'readonlyAccessor', 'writable'); + assertPropertyIsNot(obj, 'readonlyAccessor', 'enumerable'); + assertPropertyIsNot(obj, 'readonlyAccessor', 'configurable'); + assert.strictEqual(obj.readonlyAccessor, true); + + assertPropertyIs(obj, 'readwriteAccessor', 'writable'); + assertPropertyIsNot(obj, 'readwriteAccessor', 'enumerable'); + assertPropertyIsNot(obj, 'readwriteAccessor', 'configurable'); + obj.readwriteAccessor = false; + assert.strictEqual(obj.readwriteAccessor, false); + obj.readwriteAccessor = true; + assert.strictEqual(obj.readwriteAccessor, true); + + assertPropertyIsNot(obj, 'readonlyValue', 'writable'); + assertPropertyIsNot(obj, 'readonlyValue', 'enumerable'); + assertPropertyIsNot(obj, 'readonlyValue', 'configurable'); + assert.strictEqual(obj.readonlyValue, true); + + assertPropertyIs(obj, 'readwriteValue', 'writable'); + assertPropertyIsNot(obj, 'readwriteValue', 'enumerable'); + assertPropertyIsNot(obj, 'readwriteValue', 'configurable'); + obj.readwriteValue = false; + assert.strictEqual(obj.readwriteValue, false); + obj.readwriteValue = true; + assert.strictEqual(obj.readwriteValue, true); + + assertPropertyIsNot(obj, 'enumerableValue', 'writable'); + assertPropertyIs(obj, 'enumerableValue', 'enumerable'); + assertPropertyIsNot(obj, 'enumerableValue', 'configurable'); + + assertPropertyIsNot(obj, 'configurableValue', 'writable'); + assertPropertyIsNot(obj, 'configurableValue', 'enumerable'); + assertPropertyIs(obj, 'configurableValue', 'configurable'); + + assertPropertyIsNot(obj, 'function', 'writable'); + assertPropertyIsNot(obj, 'function', 'enumerable'); + assertPropertyIsNot(obj, 'function', 'configurable'); + assert.strictEqual(obj.function(), true); +} + +testDefineProperties('literal'); +testDefineProperties('string'); +testDefineProperties('value'); + +{ + const obj = {}; + const testSym = Symbol(); + binding.object.defineValueProperty(obj, testSym, 1); + assert.strictEqual(obj[testSym], 1); +}