diff --git a/doc/object.md b/doc/object.md index ce5430cc4..596e6d632 100644 --- a/doc/object.md +++ b/doc/object.md @@ -217,6 +217,30 @@ void Napi::Object::DefineProperties (____ properties) Defines properties on the object. +### Freeze() + +```cpp +void Napi::Object::Freeze() +``` + +The `Napi::Object::Freeze()` method freezes an object. A frozen object can no +longer changed. Freezing an object prevents new properties from being added to +it, existing properties from being removed, prevents changing the +enumerability, configurability, or writability of existing properties and +prevents the valuee of existing properties from being changed. In addition, +freezing an object also prevents its prototype from being changed. + +### Seal() + +```cpp +void Napi::Object::Seal() +``` + +The `Napi::Object::Seal()` method seals an object, preventing new properties +from being added to it and marking all existing properties as non-configurable. +Values of present properties can still be changed as long as thery are +writable. + ### operator\[\]() ```cpp diff --git a/napi-inl.h b/napi-inl.h index 0177c7d6b..9d2f366e1 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -1379,6 +1379,18 @@ inline void Object::AddFinalizer(Finalizer finalizeCallback, } } +#if NAPI_VERSION >= 8 +inline void Object::Freeze() { + napi_status status = napi_object_freeze(_env, _value); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline void Object::Seal() { + napi_status status = napi_object_seal(_env, _value); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} +#endif // NAPI_VERSION >= 8 + //////////////////////////////////////////////////////////////////////////////// // External class //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index 8eeb1443b..d27ddb28b 100644 --- a/napi.h +++ b/napi.h @@ -776,6 +776,10 @@ namespace Napi { inline void AddFinalizer(Finalizer finalizeCallback, T* data, Hint* finalizeHint); +#if NAPI_VERSION >= 8 + void Freeze(); + void Seal(); +#endif // NAPI_VERSION >= 8 }; template diff --git a/test/binding.cc b/test/binding.cc index 1ed7cb053..2dc089cbe 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -67,6 +67,9 @@ Object InitObjectReference(Env env); Object InitReference(Env env); Object InitVersionManagement(Env env); Object InitThunkingManual(Env env); +#if (NAPI_VERSION > 7) +Object InitObjectFreezeSeal(Env env); +#endif Object Init(Env env, Object exports) { #if (NAPI_VERSION > 5) @@ -141,6 +144,9 @@ Object Init(Env env, Object exports) { exports.Set("reference", InitReference(env)); exports.Set("version_management", InitVersionManagement(env)); exports.Set("thunking_manual", InitThunkingManual(env)); +#if (NAPI_VERSION > 7) + exports.Set("object_freeze_seal", InitObjectFreezeSeal(env)); +#endif return exports; } diff --git a/test/binding.gyp b/test/binding.gyp index 8f67fee55..8ecf91b83 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -39,6 +39,7 @@ 'object/has_own_property.cc', 'object/has_property.cc', 'object/object.cc', + 'object/object_freeze_seal.cc', 'object/set_property.cc', 'object/subscript_operator.cc', 'promise.cc', diff --git a/test/index.js b/test/index.js index 8799bb302..46bb749f2 100644 --- a/test/index.js +++ b/test/index.js @@ -112,6 +112,10 @@ if (majorNodeVersion < 12) { testModules.splice(testModules.indexOf('objectwrap_worker_thread'), 1); } +if (napiVersion < 8) { + testModules.splice(testModules.indexOf('object/object_freeze_seal'), 1); +} + (async function() { console.log(`Testing with Node-API Version '${napiVersion}'.`); diff --git a/test/object/object_freeze_seal.cc b/test/object/object_freeze_seal.cc new file mode 100644 index 000000000..2d6f4b616 --- /dev/null +++ b/test/object/object_freeze_seal.cc @@ -0,0 +1,24 @@ +#include "napi.h" + +#if (NAPI_VERSION > 7) + +using namespace Napi; + +void Freeze(const CallbackInfo& info) { + Object obj = info[0].As(); + obj.Freeze(); +} + +void Seal(const CallbackInfo& info) { + Object obj = info[0].As(); + obj.Seal(); +} + +Object InitObjectFreezeSeal(Env env) { + Object exports = Object::New(env); + exports["freeze"] = Function::New(env, Freeze); + exports["seal"] = Function::New(env, Seal); + return exports; +} + +#endif diff --git a/test/object/object_freeze_seal.js b/test/object/object_freeze_seal.js new file mode 100644 index 000000000..f85c36d67 --- /dev/null +++ b/test/object/object_freeze_seal.js @@ -0,0 +1,38 @@ +'use strict'; +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); + +test(require(`../build/${buildType}/binding.node`)); +test(require(`../build/${buildType}/binding_noexcept.node`)); + +function test(binding) { + { + const obj = { x: 'a', y: 'b', z: 'c' }; + binding.object_freeze_seal.freeze(obj); + assert.strictEqual(Object.isFrozen(obj), true); + assert.throws(() => { + obj.x = 10; + }, /Cannot assign to read only property 'x' of object '#/); + assert.throws(() => { + obj.w = 15; + }, /Cannot add property w, object is not extensible/); + assert.throws(() => { + delete obj.x; + }, /Cannot delete property 'x' of #/); + } + + { + const obj = { x: 'a', y: 'b', z: 'c' }; + binding.object_freeze_seal.seal(obj); + assert.strictEqual(Object.isSealed(obj), true); + assert.throws(() => { + obj.w = 'd'; + }, /Cannot add property w, object is not extensible/); + assert.throws(() => { + delete obj.x; + }, /Cannot delete property 'x' of #/); + // Sealed objects allow updating existing properties, + // so this should not throw. + obj.x = 'd'; + } +}