diff --git a/crates/neon/src/object/mod.rs b/crates/neon/src/object/mod.rs index ad1084f1e..078dda461 100644 --- a/crates/neon/src/object/mod.rs +++ b/crates/neon/src/object/mod.rs @@ -204,7 +204,7 @@ where pub fn bind(&'a mut self) -> NeonResult> { let callee: Handle = self.this.get(self.cx, self.key)?; let mut bind = callee.bind(self.cx); - bind.this(self.this); + bind.this(self.this)?; Ok(bind) } } diff --git a/crates/neon/src/types_impl/function/mod.rs b/crates/neon/src/types_impl/function/mod.rs index caa5259f3..dbd6f2d62 100644 --- a/crates/neon/src/types_impl/function/mod.rs +++ b/crates/neon/src/types_impl/function/mod.rs @@ -40,9 +40,10 @@ pub struct BindOptions<'a, 'cx: 'a> { impl<'a, 'cx: 'a> BindOptions<'a, 'cx> { /// Set the value of `this` for the function call. - pub fn this(&mut self, this: Handle<'cx, V>) -> &mut Self { - self.this = Some(this.upcast()); - self + pub fn this>(&mut self, this: T) -> NeonResult<&mut Self> { + let v = this.try_into_js(self.cx)?; + self.this = Some(v.upcast()); + Ok(self) } /// Replaces the arguments list with the given arguments. diff --git a/test/napi/lib/functions.js b/test/napi/lib/functions.js index 774d0e6d0..cee8fda6d 100644 --- a/test/napi/lib/functions.js +++ b/test/napi/lib/functions.js @@ -1,6 +1,18 @@ var addon = require(".."); var assert = require("chai").assert; +const STRICT = function() { "use strict"; return this; }; +const SLOPPY = Function('return this;'); + +function isStrict(f) { + try { + f.caller; + return false; + } catch (e) { + return true; + } +} + describe("JsFunction", function () { it("return a JsFunction built in Rust", function () { assert.isFunction(addon.return_js_function()); @@ -41,6 +53,35 @@ describe("JsFunction", function () { assert.equal(addon.call_parse_int_with_bind(), 42); }); + it("bind a JsFunction to an object", function () { + const result = addon.bind_js_function_to_object(function () { + return this.prop; + }); + + assert.equal(result, 42); + }); + + it("bind a strict JsFunction to a number", function() { + assert.isTrue(isStrict(STRICT)); + + // strict mode functions are allowed to have a primitive this binding + const result = addon.bind_js_function_to_number(STRICT); + + assert.strictEqual(result, 42); + }); + + it("bind a sloppy JsFunction to a primitive", function() { + assert.isFalse(isStrict(SLOPPY)); + + // legacy JS functions (aka "sloppy mode") replace primitive this bindings + // with object wrappers, so 42 will get wrapped as new Number(42) + const result = addon.bind_js_function_to_number(SLOPPY); + + assert.instanceOf(result, Number); + assert.strictEqual(typeof result, 'object'); + assert.strictEqual(result.valueOf(), 42); + }); + it("call a JsFunction with zero args", function () { assert.equal(addon.call_js_function_with_zero_args(), -Infinity); }); diff --git a/test/napi/src/js/functions.rs b/test/napi/src/js/functions.rs index 7f9cb88b3..44957153a 100644 --- a/test/napi/src/js/functions.rs +++ b/test/napi/src/js/functions.rs @@ -43,6 +43,18 @@ pub fn call_parse_int_with_bind(mut cx: FunctionContext) -> JsResult { Ok(cx.number(x + 1.0)) } +pub fn bind_js_function_to_object(mut cx: FunctionContext) -> JsResult { + let f = cx.argument::(0)?; + let obj = cx.empty_object(); + obj.prop(&mut cx, "prop").set(42)?; + f.bind(&mut cx).this(obj)?.apply() +} + +pub fn bind_js_function_to_number(mut cx: FunctionContext) -> JsResult { + let f = cx.argument::(0)?; + f.bind(&mut cx).this(42)?.apply() +} + fn get_math_max<'a>(cx: &mut FunctionContext<'a>) -> JsResult<'a, JsFunction> { let math: Handle = cx.global("Math")?; let max: Handle = math.get(cx, "max")?; diff --git a/test/napi/src/lib.rs b/test/napi/src/lib.rs index 2dac42d65..824320426 100644 --- a/test/napi/src/lib.rs +++ b/test/napi/src/lib.rs @@ -150,6 +150,8 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> { )?; cx.export_function("call_js_function_with_bind", call_js_function_with_bind)?; cx.export_function("call_parse_int_with_bind", call_parse_int_with_bind)?; + cx.export_function("bind_js_function_to_object", bind_js_function_to_object)?; + cx.export_function("bind_js_function_to_number", bind_js_function_to_number)?; cx.export_function( "call_js_function_with_zero_args", call_js_function_with_zero_args,