Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions bridge/core/arraybuffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,22 @@ import (
//
// For shared memory scenarios where modifications should be visible to both
// Go and JavaScript, use MapSharedBuffer instead.
// ToUint8Array safely creates a JS Uint8Array from a byte slice.
// If Uint8Array is missing from the global object, it falls back to ArrayBuffer.
func ToUint8Array(vm *sobek.Runtime, data []byte) sobek.Value {
buf := vm.NewArrayBuffer(data)
ctor := vm.Get("Uint8Array")
if ctor == nil || sobek.IsNull(ctor) || sobek.IsUndefined(ctor) {
return vm.ToValue(buf)
}

typedArray, err := vm.New(ctor.ToObject(vm), vm.ToValue(buf))
if err != nil || typedArray == nil {
return vm.ToValue(buf)
}
return typedArray
}

func ToArrayBuffer(vm *sobek.Runtime, data []byte) sobek.Value {
return vm.ToValue(vm.NewArrayBuffer(data))
}
Expand Down
36 changes: 31 additions & 5 deletions bridge/core/reflection.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package core
import (
"fmt"
"reflect"
"strconv"
"sync"

"github.com/grafana/sobek"
Expand Down Expand Up @@ -337,6 +338,24 @@ func wrapJSCallback(vm *sobek.Runtime, callable sobek.Callable, goType reflect.T
}

func bindSlice(vm *sobek.Runtime, v reflect.Value, visited map[uintptr]sobek.Value) (sobek.Value, error) {
// @optimized: bindSlice optimizes []byte by using ToUint8Array to create a JavaScript Uint8Array directly.
if v.Type().Elem() == reflect.TypeOf(byte(0)) {
var data []byte
if v.CanAddr() || v.Kind() == reflect.Slice {
data = v.Bytes()
} else {
// Unaddressable array, fallback to copying via allocation
l := v.Len()
data = make([]byte, l)
reflect.Copy(reflect.ValueOf(data), v)
}

// @safety: Ensure copy of the byte slice to prevent shared memory mutation
cpy := make([]byte, len(data))
copy(cpy, data)
return ToUint8Array(vm, cpy), nil
}

// @optimized: Pre-allocate slice and use NewArray(vals...) to avoid repeated Set calls.
l := v.Len()
vals := make([]interface{}, l)
Expand All @@ -352,16 +371,23 @@ func bindSlice(vm *sobek.Runtime, v reflect.Value, visited map[uintptr]sobek.Val

func bindMap(vm *sobek.Runtime, v reflect.Value, visited map[uintptr]sobek.Value) (sobek.Value, error) {
obj := vm.NewObject()
for _, key := range v.MapKeys() {
iter := v.MapRange()
for iter.Next() {
key := iter.Key()
var keyStr string
// @optimized: Avoid Sprintf if key is already a string.
if key.Kind() == reflect.String {
// @optimized: Use MapRange to avoid allocating a slice of keys. Avoid Sprintf if key is already a string or numeric type.
switch key.Kind() {
case reflect.String:
keyStr = key.String()
} else {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
keyStr = strconv.FormatInt(key.Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
keyStr = strconv.FormatUint(key.Uint(), 10)
default:
keyStr = fmt.Sprint(key.Interface())
}

val, err := bindValue(vm, v.MapIndex(key), visited)
val, err := bindValue(vm, iter.Value(), visited)
if err != nil {
return nil, err
}
Expand Down
Loading