diff --git a/bridge/core/arraybuffer.go b/bridge/core/arraybuffer.go index a99b224..ba017e1 100644 --- a/bridge/core/arraybuffer.go +++ b/bridge/core/arraybuffer.go @@ -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)) } diff --git a/bridge/core/reflection.go b/bridge/core/reflection.go index b84aa2c..8a2f787 100644 --- a/bridge/core/reflection.go +++ b/bridge/core/reflection.go @@ -3,6 +3,7 @@ package core import ( "fmt" "reflect" + "strconv" "sync" "github.com/grafana/sobek" @@ -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) @@ -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 }