Skip to content

Commit 6305aab

Browse files
authored
Extend Value into the Object type (#74)
* basic return of an Object from value * implement object set property * get the global object and get object instance from template * Object Get methods and locking in the API for this PR * fix C++ failing issues * basic tests for the Object methods * value tests and iso fix * update changelog * address comments from review
1 parent 85aad81 commit 6305aab

File tree

12 files changed

+441
-6
lines changed

12 files changed

+441
-6
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
- Create Object Templates with primitive values, including other Object Templates
1212
- Configure Object Template as the global object of any new Context
1313
- Function Templates with callbacks to Go
14+
- Value to Object type, including Get/Set/Has/Delete methods
15+
- Get Global Object from the Context
16+
- Convert a Object Template to an instance of an Object
1417

1518
### Changed
1619
- NewContext() API has been improved to handle optional global object, as well as optional Isolate
1720
- Package error messages are now prefixed with `v8go` rather than the struct name
1821
- Deprecated `iso.Close()` in favor of `iso.Dispose()` to keep consistancy with the C++ API
1922
- Upgraded V8 to 8.8.278.14
23+
- Licence BSD 3-Clause (same as V8 and Go)
2024

2125
## [v0.4.0] - 2021-01-14
2226

context.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,20 @@ func (c *Context) RunScript(source string, origin string) (*Value, error) {
106106
return getValue(c, rtn), getError(rtn)
107107
}
108108

109+
// Global returns the global proxy object.
110+
// Global proxy object is a thin wrapper whose prototype points to actual
111+
// context's global object with the properties like Object, etc. This is
112+
// done that way for security reasons.
113+
// Please note that changes to global proxy object prototype most probably
114+
// would break the VM — V8 expects only global object as a prototype of
115+
// global proxy object.
116+
func (c *Context) Global() *Object {
117+
valPtr := C.ContextGlobal(c.ptr)
118+
v := &Value{valPtr, c}
119+
runtime.SetFinalizer(v, (*Value).finalizer)
120+
return &Object{v}
121+
}
122+
109123
// Close will dispose the context and free the memory.
110124
func (c *Context) Close() {
111125
c.finalizer()

json.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ func JSONParse(ctx *Context, str string) (*Value, error) {
2626
}
2727

2828
// JSONStringify tries to stringify the JSON-serializable object value and returns it as string.
29-
func JSONStringify(ctx *Context, val *Value) (string, error) {
30-
if val == nil {
29+
func JSONStringify(ctx *Context, val Valuer) (string, error) {
30+
if val == nil || val.value() == nil {
3131
return "", errors.New("v8go: Value is required")
3232
}
3333
// If a nil context is passed we'll use the context/isolate that created the value.
@@ -36,7 +36,7 @@ func JSONStringify(ctx *Context, val *Value) (string, error) {
3636
ctxPtr = ctx.ptr
3737
}
3838

39-
str := C.JSONStringify(ctxPtr, val.ptr)
39+
str := C.JSONStringify(ctxPtr, val.value().ptr)
4040
defer C.free(unsafe.Pointer(str))
4141
return C.GoString(str), nil
4242
}

json_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,15 @@ func TestJSONParse(t *testing.T) {
2727
if _, ok := err.(*v8go.JSError); !ok {
2828
t.Errorf("expected error to be of type JSError, got: %T", err)
2929
}
30+
}
31+
32+
func TestJSONStringify(t *testing.T) {
33+
t.Parallel()
3034

35+
ctx, _ := v8go.NewContext()
36+
if _, err := v8go.JSONStringify(ctx, nil); err == nil {
37+
t.Error("expected error but got <nil>")
38+
}
3139
}
3240

3341
func ExampleJSONParse() {

object.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright 2021 Roger Chapman and the v8go contributors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package v8go
6+
7+
// #include <stdlib.h>
8+
// #include "v8go.h"
9+
import "C"
10+
import (
11+
"errors"
12+
"fmt"
13+
"math/big"
14+
"unsafe"
15+
)
16+
17+
// Object is a JavaScript object (ECMA-262, 4.3.3)
18+
type Object struct {
19+
*Value
20+
}
21+
22+
// Set will set a property on the Object to a given value.
23+
// Supports all value types, eg: Object, Array, Date, Set, Map etc
24+
// If the value passed is a Go supported primitive (string, int32, uint32, int64, uint64, float64, big.Int)
25+
// then a *Value will be created and set as the value property.
26+
func (o *Object) Set(key string, val interface{}) error {
27+
if len(key) == 0 {
28+
return errors.New("v8go: You must provide a valid property key")
29+
}
30+
return set(o, key, 0, val)
31+
}
32+
33+
// Set will set a given index on the Object to a given value.
34+
// Supports all value types, eg: Object, Array, Date, Set, Map etc
35+
// If the value passed is a Go supported primitive (string, int32, uint32, int64, uint64, float64, big.Int)
36+
// then a *Value will be created and set as the value property.
37+
func (o *Object) SetIdx(idx uint32, val interface{}) error {
38+
return set(o, "", idx, val)
39+
}
40+
41+
func set(o *Object, key string, idx uint32, val interface{}) error {
42+
var value *Value
43+
switch v := val.(type) {
44+
case string, int32, uint32, int64, uint64, float64, bool, *big.Int:
45+
// ignoring error as code cannot reach the error state as we are already
46+
// validating the new value types in this case statement
47+
value, _ = NewValue(o.ctx.iso, v)
48+
case Valuer:
49+
value = v.value()
50+
default:
51+
return fmt.Errorf("v8go: unsupported object property type `%T`", v)
52+
}
53+
54+
if len(key) > 0 {
55+
ckey := C.CString(key)
56+
defer C.free(unsafe.Pointer(ckey))
57+
C.ObjectSet(o.ptr, ckey, value.ptr)
58+
return nil
59+
}
60+
61+
C.ObjectSetIdx(o.ptr, C.uint32_t(idx), value.ptr)
62+
return nil
63+
}
64+
65+
// Get tries to get a Value for a given Object property key.
66+
func (o *Object) Get(key string) (*Value, error) {
67+
ckey := C.CString(key)
68+
defer C.free(unsafe.Pointer(ckey))
69+
70+
rtn := C.ObjectGet(o.ptr, ckey)
71+
return getValue(o.ctx, rtn), getError(rtn)
72+
}
73+
74+
// GetIdx tries to get a Value at a give Object index.
75+
func (o *Object) GetIdx(idx uint32) (*Value, error) {
76+
rtn := C.ObjectGetIdx(o.ptr, C.uint32_t(idx))
77+
return getValue(o.ctx, rtn), getError(rtn)
78+
}
79+
80+
// Has calls the abstract operation HasProperty(O, P) described in ECMA-262, 7.3.10.
81+
// Returns true, if the object has the property, either own or on the prototype chain.
82+
func (o *Object) Has(key string) bool {
83+
ckey := C.CString(key)
84+
defer C.free(unsafe.Pointer(ckey))
85+
return C.ObjectHas(o.ptr, ckey) != 0
86+
}
87+
88+
// HasIdx returns true if the object has a value at the given index.
89+
func (o *Object) HasIdx(idx uint32) bool {
90+
return C.ObjectHasIdx(o.ptr, C.uint32_t(idx)) != 0
91+
}
92+
93+
// Delete returns true if successful in deleting a named property on the object.
94+
func (o *Object) Delete(key string) bool {
95+
ckey := C.CString(key)
96+
defer C.free(unsafe.Pointer(ckey))
97+
return C.ObjectDelete(o.ptr, ckey) != 0
98+
}
99+
100+
// DeleteIdx returns true if successful in deleting a value at a given index of the object.
101+
func (o *Object) DeleteIdx(idx uint32) bool {
102+
return C.ObjectDeleteIdx(o.ptr, C.uint32_t(idx)) != 0
103+
}

object_template.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,16 @@ func NewObjectTemplate(iso *Isolate) (*ObjectTemplate, error) {
4949
return &ObjectTemplate{tmpl}, nil
5050
}
5151

52+
// NewInstance creates a new Object based on the template.
53+
func (o *ObjectTemplate) NewInstance(ctx *Context) (*Object, error) {
54+
if ctx == nil {
55+
return nil, errors.New("v8go: Context cannot be <nil>")
56+
}
57+
58+
valPtr := C.ObjectTemplateNewInstance(o.ptr, ctx.ptr)
59+
return &Object{&Value{valPtr, ctx}}, nil
60+
}
61+
5262
func (o *ObjectTemplate) apply(opts *contextOptions) {
5363
opts.gTmpl = o
5464
}

object_template_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,20 @@ func TestGlobalObjectTemplate(t *testing.T) {
117117
})
118118
}
119119
}
120+
121+
func TestObjectTemplateNewInstance(t *testing.T) {
122+
t.Parallel()
123+
iso, _ := v8go.NewIsolate()
124+
tmpl, _ := v8go.NewObjectTemplate(iso)
125+
if _, err := tmpl.NewInstance(nil); err == nil {
126+
t.Error("expected error but got <nil>")
127+
}
128+
129+
tmpl.Set("foo", "bar")
130+
ctx, _ := v8go.NewContext(iso)
131+
obj, _ := tmpl.NewInstance(ctx)
132+
if foo, _ := obj.Get("foo"); foo.String() != "bar" {
133+
t.Errorf("unexpected value for object property: %v", foo)
134+
}
135+
136+
}

object_test.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright 2021 Roger Chapman and the v8go contributors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package v8go_test
6+
7+
import (
8+
"fmt"
9+
"testing"
10+
11+
"rogchap.com/v8go"
12+
)
13+
14+
func TestObjectSet(t *testing.T) {
15+
t.Parallel()
16+
17+
ctx, _ := v8go.NewContext()
18+
val, _ := ctx.RunScript("const foo = {}; foo", "")
19+
obj, _ := val.AsObject()
20+
obj.Set("bar", "baz")
21+
baz, _ := ctx.RunScript("foo.bar", "")
22+
if baz.String() != "baz" {
23+
t.Errorf("unexpected value: %q", baz)
24+
}
25+
if err := obj.Set("", nil); err == nil {
26+
t.Error("expected error but got <nil>")
27+
}
28+
if err := obj.Set("a", 0); err == nil {
29+
t.Error("expected error but got <nil>")
30+
}
31+
obj.SetIdx(10, "ten")
32+
if ten, _ := ctx.RunScript("foo[10]", ""); ten.String() != "ten" {
33+
t.Errorf("unexpected value: %q", ten)
34+
}
35+
}
36+
37+
func TestObjectGet(t *testing.T) {
38+
t.Parallel()
39+
40+
ctx, _ := v8go.NewContext()
41+
val, _ := ctx.RunScript("const foo = { bar: 'baz'}; foo", "")
42+
obj, _ := val.AsObject()
43+
if bar, _ := obj.Get("bar"); bar.String() != "baz" {
44+
t.Errorf("unexpected value: %q", bar)
45+
}
46+
if baz, _ := obj.Get("baz"); !baz.IsUndefined() {
47+
t.Errorf("unexpected value: %q", baz)
48+
}
49+
ctx.RunScript("foo[5] = 5", "")
50+
if five, _ := obj.GetIdx(5); five.Integer() != 5 {
51+
t.Errorf("unexpected value: %q", five)
52+
}
53+
if u, _ := obj.GetIdx(55); !u.IsUndefined() {
54+
t.Errorf("unexpected value: %q", u)
55+
}
56+
}
57+
58+
func TestObjectHas(t *testing.T) {
59+
t.Parallel()
60+
61+
ctx, _ := v8go.NewContext()
62+
val, _ := ctx.RunScript("const foo = {a: 1, '2': 2}; foo", "")
63+
obj, _ := val.AsObject()
64+
if !obj.Has("a") {
65+
t.Error("expected true, got false")
66+
}
67+
if obj.Has("c") {
68+
t.Error("expected false, got true")
69+
}
70+
if !obj.HasIdx(2) {
71+
t.Error("expected true, got false")
72+
}
73+
if obj.HasIdx(1) {
74+
t.Error("expected false, got true")
75+
}
76+
}
77+
78+
func TestObjectDelete(t *testing.T) {
79+
t.Parallel()
80+
81+
ctx, _ := v8go.NewContext()
82+
val, _ := ctx.RunScript("const foo = { bar: 'baz', '2': 2}; foo", "")
83+
obj, _ := val.AsObject()
84+
if !obj.Has("bar") {
85+
t.Error("expected property to exist")
86+
}
87+
if !obj.Delete("bar") {
88+
t.Error("expected delete to return true, got false")
89+
}
90+
if obj.Has("bar") {
91+
t.Error("expected property to be deleted")
92+
}
93+
if !obj.DeleteIdx(2) {
94+
t.Error("expected delete to return true, got false")
95+
}
96+
97+
}
98+
99+
func ExampleObject_global() {
100+
iso, _ := v8go.NewIsolate()
101+
ctx, _ := v8go.NewContext(iso)
102+
global := ctx.Global()
103+
104+
console, _ := v8go.NewObjectTemplate(iso)
105+
logfn, _ := v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
106+
fmt.Println(info.Args()[0])
107+
return nil
108+
})
109+
console.Set("log", logfn)
110+
consoleObj, _ := console.NewInstance(ctx)
111+
112+
global.Set("console", consoleObj)
113+
ctx.RunScript("console.log('foo')", "")
114+
// Output:
115+
// foo
116+
}

0 commit comments

Comments
 (0)