From 1c2cf245a0018ee39186e4c193a3fb427069e351 Mon Sep 17 00:00:00 2001 From: Genevieve Date: Wed, 8 Sep 2021 07:40:29 -0700 Subject: [PATCH] Support calling a method on an object. (#160) --- CHANGELOG.md | 1 + function.go | 2 +- object.go | 28 ++++++++++++++++++++++++++++ object_test.go | 32 ++++++++++++++++++++++++++++++++ v8go.cc | 10 +++++++--- v8go.h | 2 +- 6 files changed, 70 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fcc46ff..595c62ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Access "this" from function callback - value.SameValue(otherValue) function to compare values for sameness - Undefined, Null functions to get these constant values for the isolate +- Support for calling a method on an object. ### Changed - Removed error return value from Context.Isolate() which never fails diff --git a/function.go b/function.go index dd13b7b3..bbe96b06 100644 --- a/function.go +++ b/function.go @@ -26,7 +26,7 @@ func (fn *Function) Call(args ...Valuer) (*Value, error) { argptr = (*C.ValuePtr)(unsafe.Pointer(&cArgs[0])) } fn.ctx.register() - rtn := C.FunctionCall(fn.ptr, C.int(len(args)), argptr) + rtn := C.FunctionCall(fn.ptr, fn.ctx.iso.undefined.ptr, C.int(len(args)), argptr) fn.ctx.deregister() return getValue(fn.ctx, rtn), getError(rtn) } diff --git a/object.go b/object.go index 4c1dc5c5..e563b3d9 100644 --- a/object.go +++ b/object.go @@ -19,6 +19,34 @@ type Object struct { *Value } +func (o *Object) MethodCall(methodName string, args ...Valuer) (*Value, error) { + ckey := C.CString(methodName) + defer C.free(unsafe.Pointer(ckey)) + + getRtn := C.ObjectGet(o.ptr, ckey) + err := getError(getRtn) + if err != nil { + return nil, err + } + fn, err := getValue(o.ctx, getRtn).AsFunction() + if err != nil { + return nil, err + } + + var argptr *C.ValuePtr + if len(args) > 0 { + var cArgs = make([]C.ValuePtr, len(args)) + for i, arg := range args { + cArgs[i] = arg.value().ptr + } + argptr = (*C.ValuePtr)(unsafe.Pointer(&cArgs[0])) + } + fn.ctx.register() + rtn := C.FunctionCall(fn.ptr, o.ptr, C.int(len(args)), argptr) + fn.ctx.deregister() + return getValue(fn.ctx, rtn), getError(rtn) +} + // Set will set a property on the Object to a given value. // Supports all value types, eg: Object, Array, Date, Set, Map etc // If the value passed is a Go supported primitive (string, int32, uint32, int64, uint64, float64, big.Int) diff --git a/object_test.go b/object_test.go index b4c2b8d5..607b0f73 100644 --- a/object_test.go +++ b/object_test.go @@ -11,6 +11,38 @@ import ( "rogchap.com/v8go" ) +func TestObjectMethodCall(t *testing.T) { + t.Parallel() + + ctx, _ := v8go.NewContext() + iso := ctx.Isolate() + val, _ := ctx.RunScript(`class Obj { constructor(input) { this.input = input, this.prop = "" } print() { return this.input.toString() } }; new Obj("some val")`, "") + obj, _ := val.AsObject() + val, err := obj.MethodCall("print") + failIf(t, err) + if val.String() != "some val" { + t.Errorf("unexpected value: %q", val) + } + _, err = obj.MethodCall("prop") + if err == nil { + t.Errorf("expected an error, got none") + } + + val, err = ctx.RunScript(`class Obj2 { print(str) { return str.toString() }; get fails() { throw "error" } }; new Obj2()`, "") + failIf(t, err) + obj, _ = val.AsObject() + arg, _ := v8go.NewValue(iso, "arg") + val, err = obj.MethodCall("print", arg) + failIf(t, err) + if val.String() != "arg" { + t.Errorf("unexpected value: %q", val) + } + _, err = obj.MethodCall("fails") + if err == nil { + t.Errorf("expected an error, got none") + } +} + func TestObjectSet(t *testing.T) { t.Parallel() diff --git a/v8go.cc b/v8go.cc index 1cb88aff..c239d3e7 100644 --- a/v8go.cc +++ b/v8go.cc @@ -1216,14 +1216,18 @@ static void buildCallArguments(Isolate* iso, } } -RtnValue FunctionCall(ValuePtr ptr, int argc, ValuePtr args[]) { +RtnValue FunctionCall(ValuePtr ptr, ValuePtr recv, int argc, ValuePtr args[]) { LOCAL_VALUE(ptr) + RtnValue rtn = {nullptr, nullptr}; Local fn = Local::Cast(value); Local argv[argc]; buildCallArguments(iso, argv, argc, args); - Local recv = Undefined(iso); - MaybeLocal result = fn->Call(local_ctx, recv, argc, argv); + + m_value* recv_val = static_cast(recv); + Local local_recv = recv_val->ptr.Get(iso); + + MaybeLocal result = fn->Call(local_ctx, local_recv, argc, argv); if (result.IsEmpty()) { rtn.error = ExceptionError(try_catch, iso, local_ctx); return rtn; diff --git a/v8go.h b/v8go.h index 51651edc..d5364e2e 100644 --- a/v8go.h +++ b/v8go.h @@ -182,7 +182,7 @@ ValuePtr PromiseThen2(ValuePtr ptr, int on_fulfilled_ref, int on_rejected_ref); ValuePtr PromiseCatch(ValuePtr ptr, int callback_ref); extern ValuePtr PromiseResult(ValuePtr ptr); -extern RtnValue FunctionCall(ValuePtr ptr, int argc, ValuePtr argv[]); +extern RtnValue FunctionCall(ValuePtr ptr, ValuePtr recv, int argc, ValuePtr argv[]); RtnValue FunctionNewInstance(ValuePtr ptr, int argc, ValuePtr args[]); ValuePtr FunctionSourceMapUrl(ValuePtr ptr);