Skip to content

Commit cee899a

Browse files
p120ph37mhdawson
authored andcommitted
src: allow customization of ObjectWrap behavior
Add new method to allow customization of ObjectWrap behavior PR-URL: #1125 Reviewed-By: Michael Dawson <midawson@redhat.com
1 parent 744c8d2 commit cee899a

File tree

7 files changed

+106
-2
lines changed

7 files changed

+106
-2
lines changed

doc/object_wrap.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,29 @@ property of the `Napi::CallbackInfo`.
212212

213213
Returns a `Napi::Function` representing the constructor function for the class.
214214

215+
### OnCalledAsFunction
216+
217+
Provides an opportunity to customize the behavior when a `Napi::ObjectWrap<T>`
218+
class is called from JavaScript as a function (without the **new** operator).
219+
220+
The default behavior in this scenario is to throw a `Napi::TypeError` with the
221+
message `Class constructors cannot be invoked without 'new'`. Define this
222+
public method on your derived class to override that behavior.
223+
224+
For example, you could internally re-call the JavaScript contstructor _with_
225+
the **new** operator (via
226+
`Napi::Function::New(const std::vector<napi_value> &args)`), and return the
227+
resulting object. Or you might do something else entirely, such as the way
228+
[`Date()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#constructor)
229+
produces a string when called as a function.
230+
231+
```cpp
232+
static Napi::Value OnCalledAsFunction(const Napi::CallbackInfo& callbackInfo);
233+
```
234+
235+
- `[in] callbackInfo`: The object representing the components of the JavaScript
236+
request being made.
237+
215238
### Finalize
216239
217240
Provides an opportunity to run cleanup code that requires access to the

napi-inl.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4451,6 +4451,15 @@ inline ClassPropertyDescriptor<T> ObjectWrap<T>::StaticValue(Symbol name,
44514451
return desc;
44524452
}
44534453

4454+
template <typename T>
4455+
inline Value ObjectWrap<T>::OnCalledAsFunction(
4456+
const Napi::CallbackInfo& callbackInfo) {
4457+
NAPI_THROW(
4458+
TypeError::New(callbackInfo.Env(),
4459+
"Class constructors cannot be invoked without 'new'"),
4460+
Napi::Value());
4461+
}
4462+
44544463
template <typename T>
44554464
inline void ObjectWrap<T>::Finalize(Napi::Env /*env*/) {}
44564465

@@ -4464,8 +4473,8 @@ inline napi_value ObjectWrap<T>::ConstructorCallbackWrapper(
44644473

44654474
bool isConstructCall = (new_target != nullptr);
44664475
if (!isConstructCall) {
4467-
napi_throw_type_error(env, nullptr, "Class constructors cannot be invoked without 'new'");
4468-
return nullptr;
4476+
return details::WrapCallback(
4477+
[&] { return T::OnCalledAsFunction(CallbackInfo(env, info)); });
44694478
}
44704479

44714480
napi_value wrapper = details::WrapCallback([&] {

napi.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2199,6 +2199,8 @@ namespace Napi {
21992199
static PropertyDescriptor StaticValue(Symbol name,
22002200
Napi::Value value,
22012201
napi_property_attributes attributes = napi_default);
2202+
static Napi::Value OnCalledAsFunction(
2203+
const Napi::CallbackInfo& callbackInfo);
22022204
virtual void Finalize(Napi::Env env);
22032205

22042206
private:

test/binding.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ Object InitTypedArray(Env env);
6565
Object InitGlobalObject(Env env);
6666
Object InitObjectWrap(Env env);
6767
Object InitObjectWrapConstructorException(Env env);
68+
Object InitObjectWrapFunction(Env env);
6869
Object InitObjectWrapRemoveWrap(Env env);
6970
Object InitObjectWrapMultipleInheritance(Env env);
7071
Object InitObjectReference(Env env);
@@ -152,6 +153,7 @@ Object Init(Env env, Object exports) {
152153
exports.Set("objectwrap", InitObjectWrap(env));
153154
exports.Set("objectwrapConstructorException",
154155
InitObjectWrapConstructorException(env));
156+
exports.Set("objectwrap_function", InitObjectWrapFunction(env));
155157
exports.Set("objectwrap_removewrap", InitObjectWrapRemoveWrap(env));
156158
exports.Set("objectwrap_multiple_inheritance", InitObjectWrapMultipleInheritance(env));
157159
exports.Set("objectreference", InitObjectReference(env));

test/binding.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
'typedarray.cc',
6767
'objectwrap.cc',
6868
'objectwrap_constructor_exception.cc',
69+
'objectwrap_function.cc',
6970
'objectwrap_removewrap.cc',
7071
'objectwrap_multiple_inheritance.cc',
7172
'object_reference.cc',

test/objectwrap_function.cc

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#include <napi.h>
2+
#include <unordered_map>
3+
#include "test_helper.h"
4+
5+
class FunctionTest : public Napi::ObjectWrap<FunctionTest> {
6+
public:
7+
FunctionTest(const Napi::CallbackInfo& info)
8+
: Napi::ObjectWrap<FunctionTest>(info) {}
9+
10+
static Napi::Value OnCalledAsFunction(const Napi::CallbackInfo& info) {
11+
// If called with a "true" argument, throw an exeption to test the handling.
12+
if (!info[0].IsUndefined() && MaybeUnwrap(info[0].ToBoolean())) {
13+
NAPI_THROW(Napi::Error::New(info.Env(), "an exception"), Napi::Value());
14+
}
15+
// Otherwise, act as a factory.
16+
std::vector<napi_value> args;
17+
for (size_t i = 0; i < info.Length(); i++) args.push_back(info[i]);
18+
return MaybeUnwrap(GetConstructor(info.Env()).New(args));
19+
}
20+
21+
// Constructor-per-env map in a static member because env.SetInstanceData()
22+
// would interfere with Napi::Addon<T>
23+
static std::unordered_map<napi_env, Napi::FunctionReference> constructors;
24+
25+
static void Initialize(Napi::Env env, Napi::Object exports) {
26+
const char* name = "FunctionTest";
27+
Napi::Function func = DefineClass(env, name, {});
28+
constructors[env] = Napi::Persistent(func);
29+
env.AddCleanupHook([env] { constructors.erase(env); });
30+
exports.Set(name, func);
31+
}
32+
33+
static Napi::Function GetConstructor(Napi::Env env) {
34+
return constructors[env].Value();
35+
}
36+
};
37+
38+
std::unordered_map<napi_env, Napi::FunctionReference>
39+
FunctionTest::constructors = {};
40+
41+
Napi::Object InitObjectWrapFunction(Napi::Env env) {
42+
Napi::Object exports = Napi::Object::New(env);
43+
FunctionTest::Initialize(env, exports);
44+
return exports;
45+
}

test/objectwrap_function.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
'use strict';
2+
3+
const assert = require('assert');
4+
const testUtil = require('./testUtil');
5+
6+
function test (binding) {
7+
return testUtil.runGCTests([
8+
'objectwrap function',
9+
() => {
10+
const { FunctionTest } = binding.objectwrap_function;
11+
const newConstructed = new FunctionTest();
12+
const functionConstructed = FunctionTest();
13+
assert(newConstructed instanceof FunctionTest);
14+
assert(functionConstructed instanceof FunctionTest);
15+
assert.throws(() => (FunctionTest(true)), /an exception/);
16+
},
17+
// Do on gc before returning.
18+
() => {}
19+
]);
20+
}
21+
22+
module.exports = require('./common').runTest(test);

0 commit comments

Comments
 (0)