dop251/goja

`Object.keys` have different result than `Reflect.ownKeys` when using `NewProxy`

zyxkad opened this issue · 3 comments

I want to implement something like the Storage API.

This is what I'm doing right now:

type Storage interface {
	Len()(int)
	Keys()(keys []string)
	GetItem(key string)(value goja.Value)
	SetItem(key string, value goja.Value)
	RemoveItem(key string)
	Clear()
}

func ProxyStorage(s Storage, vm *goja.Runtime)(goja.Proxy){
	getitem := vm.ToValue(s.GetItem)
	setitem := vm.ToValue(s.SetItem)
	removeitem := vm.ToValue(s.RemoveItem)
	clear := vm.ToValue(s.Clear)
	o := vm.NewObject()
	o.Set("test", 234)
	return vm.NewProxy(o, &goja.ProxyTrapConfig{
		IsExtensible: func(_ *goja.Object)(success bool){
			return true
		},
		Has: func(_ *goja.Object, property string)(available bool){
			switch property {
			case "length", "getItem", "setItem", "removeItem", "clear":
				return true
			}
			return s.GetItem(property) != nil
		},
		Get: func(_ *goja.Object, property string, receiver goja.Value)(value goja.Value){
			switch property {
			case "length":
				return vm.ToValue(s.Len())
			case "getItem":
				return getitem
			case "setItem":
				return setitem
			case "removeItem":
				return removeitem
			case "clear":
				return clear
			}
			return s.GetItem(property)
		},
		Set: func(_ *goja.Object, property string, value goja.Value, receiver goja.Value)(success bool){
			s.SetItem(property, value)
			return true
		},
		DeleteProperty: func(_ *goja.Object, property string)(success bool){
			s.RemoveItem(property)
			return true
		},
		OwnKeys: func(_ *goja.Object)(object *goja.Object){
			println("called OwnKeys")
			object = vm.ToValue(s.Keys()).ToObject(vm)
			object.Set("testKey", "test value")
			return
		},
	})
}

But when I calling Object.keys(storage) from js, it actually called the OwnKeys function, but retuned a new empty array.
However, when I use Reflect.ownKeys(stroage), it returned exactly what it should return (a non empty array).
Also, when I call Runtime.ToValue(proxiedStorage).ToObject(runtime).Keys() from go, it didn't invoke the OwnKeys at all.
I don't know what's wrong here.

But when I calling Object.keys(storage) from js, it actually called the OwnKeys function, but retuned a new empty array. However, when I use Reflect.ownKeys(stroage), it returned exactly what it should return (a non empty array).

Object.keys() and Reflect.ownKeys() are not the same, the former only returns enumerable string properties, the latter returns all string and symbol properties.

Since you have not defined the GetOwnPropertyDescriptor trap, all properties are considered non-enumerable. You'd get exactly the same behaviour if you wrote it in javascript.

Also, when I call Runtime.ToValue(proxiedStorage).ToObject(runtime).Keys() from go, it didn't invoke the OwnKeys at all. I don't know what's wrong here.

Please provide a runnable test case.

Also, when I call Runtime.ToValue(proxiedStorage).ToObject(runtime).Keys() from go, it didn't invoke the OwnKeys at all. I don't know what's wrong here.

Please provide a runnable test case.

I was wrong, the OwnKeys was called, but the Keys didn't return anything.

package main

import (
	"fmt"

	"github.com/dop251/goja"
)

func main() {
	vm := goja.New()
	proxied := ProxyStorage(vm)
	keys := vm.ToValue(proxied).ToObject(vm).Keys()
	fmt.Println("keys:", keys)
}


func ProxyStorage(vm *goja.Runtime)(goja.Proxy){
	return vm.NewProxy(vm.NewObject(), &goja.ProxyTrapConfig{
		OwnKeys: func(_ *goja.Object)(object *goja.Object){
			fmt.Println("OwnKeys has been called")
			return vm.ToValue([]string{"a", "b", "c"}).ToObject(vm)
		},
	})
}

Output:

$ go run .
OwnKeys has been called
keys: []

Since you have not defined the GetOwnPropertyDescriptor trap, all properties are considered non-enumerable. You'd get exactly the same behaviour if you wrote it in javascript.

Got it, it works for me, thanks :)