github.com/gopherjs/gopherjs@v1.19.0-beta1.0.20240506212314-27071a8796e4/compiler/natives/src/syscall/js/js.go (about) 1 //go:build js 2 // +build js 3 4 package js 5 6 import ( 7 "unsafe" 8 9 "github.com/gopherjs/gopherjs/js" 10 ) 11 12 type Type int 13 14 const ( 15 TypeUndefined Type = iota 16 TypeNull 17 TypeBoolean 18 TypeNumber 19 TypeString 20 TypeSymbol 21 TypeObject 22 TypeFunction 23 ) 24 25 // Same order as Type constants 26 var typeNames = []string{ 27 "undefined", 28 "null", 29 "boolean", 30 "number", 31 "string", 32 "symbol", 33 "object", 34 "function", 35 } 36 37 func (t Type) String() string { 38 if int(t) < 0 || len(typeNames) <= int(t) { 39 panic("bad type") 40 } 41 return typeNames[t] 42 } 43 44 func (t Type) isObject() bool { 45 return t == TypeObject || t == TypeFunction 46 } 47 48 func Global() Value { 49 return objectToValue(js.Global) 50 } 51 52 func Null() Value { 53 return objectToValue(nil) 54 } 55 56 func Undefined() Value { 57 return objectToValue(js.Undefined) 58 } 59 60 type Func struct { 61 Value 62 } 63 64 func (f Func) Release() { 65 js.Global.Set("$exportedFunctions", js.Global.Get("$exportedFunctions").Int()-1) 66 f.Value = Null() 67 } 68 69 func FuncOf(fn func(this Value, args []Value) interface{}) Func { 70 // Existence of a wrapped function means that an external event may awaken the 71 // program and we need to suppress deadlock detection. 72 js.Global.Set("$exportedFunctions", js.Global.Get("$exportedFunctions").Int()+1) 73 return Func{ 74 Value: objectToValue(js.MakeFunc(func(this *js.Object, args []*js.Object) interface{} { 75 vargs := make([]Value, len(args)) 76 for i, a := range args { 77 vargs[i] = objectToValue(a) 78 } 79 return fn(objectToValue(this), vargs) 80 })), 81 } 82 } 83 84 type Error struct { 85 Value 86 } 87 88 func (e Error) Error() string { 89 return "JavaScript error: " + e.Get("message").String() 90 } 91 92 type Value struct { 93 v *js.Object 94 95 // inited represents whether Value is non-zero value. true represents the value is not 'undefined'. 96 inited bool 97 98 _ [0]func() // uncomparable; to make == not compile 99 } 100 101 func objectToValue(obj *js.Object) Value { 102 if obj == js.Undefined { 103 return Value{} 104 } 105 return Value{v: obj, inited: true} 106 } 107 108 var ( 109 id *js.Object 110 instanceOf *js.Object 111 typeOf *js.Object 112 ) 113 114 func init() { 115 if js.Global != nil { 116 id = js.Global.Get("$id") 117 instanceOf = js.Global.Get("$instanceOf") 118 typeOf = js.Global.Get("$typeOf") 119 } 120 } 121 122 func getValueType(obj *js.Object) Type { 123 if obj == nil { 124 return TypeNull 125 } 126 name := typeOf.Invoke(obj).String() 127 for type2, name2 := range typeNames { 128 if name == name2 { 129 return Type(type2) 130 } 131 } 132 return TypeObject 133 } 134 135 func ValueOf(x interface{}) Value { 136 switch x := x.(type) { 137 case Value: 138 return x 139 case Func: 140 return x.Value 141 case nil: 142 return Null() 143 case bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, unsafe.Pointer, string, map[string]interface{}, []interface{}: 144 return objectToValue(id.Invoke(x)) 145 default: 146 panic("ValueOf: invalid value") 147 } 148 } 149 150 func (v Value) internal() *js.Object { 151 if !v.inited { 152 return js.Undefined 153 } 154 return v.v 155 } 156 157 func (v Value) Bool() bool { 158 if vType := v.Type(); vType != TypeBoolean { 159 panic(&ValueError{"Value.Bool", vType}) 160 } 161 return v.internal().Bool() 162 } 163 164 // convertArgs converts arguments into values for GopherJS arguments. 165 func convertArgs(args ...interface{}) []interface{} { 166 newArgs := []interface{}{} 167 for _, arg := range args { 168 v := ValueOf(arg) 169 newArgs = append(newArgs, v.internal()) 170 } 171 return newArgs 172 } 173 174 func convertJSError() { 175 err := recover() 176 if err == nil { 177 return 178 } 179 if jsErr, ok := err.(*js.Error); ok { 180 // We expect that all panics caught by Value.Call() are in fact JavaScript 181 // exceptions intercepted by GopherJS runtime, which we convert to 182 // syscall/js.Error, which the callers would expect. 183 panic(Error{Value: objectToValue(jsErr.Object)}) 184 } 185 // Panics of other types are unexpected and should never happen. But if it 186 // does, we will just re-raise it as-is. 187 panic(err) 188 } 189 190 func (v Value) Call(m string, args ...interface{}) Value { 191 if vType := v.Type(); vType != TypeObject && vType != TypeFunction { 192 panic(&ValueError{"Value.Call", vType}) 193 } 194 if propType := v.Get(m).Type(); propType != TypeFunction { 195 panic("js: Value.Call: property " + m + " is not a function, got " + propType.String()) 196 } 197 defer convertJSError() 198 return objectToValue(v.internal().Call(m, convertArgs(args...)...)) 199 } 200 201 func (v Value) Float() float64 { 202 if vType := v.Type(); vType != TypeNumber { 203 panic(&ValueError{"Value.Float", vType}) 204 } 205 return v.internal().Float() 206 } 207 208 func (v Value) Get(p string) Value { 209 if vType := v.Type(); !vType.isObject() { 210 panic(&ValueError{"Value.Get", vType}) 211 } 212 return objectToValue(v.internal().Get(p)) 213 } 214 215 func (v Value) Index(i int) Value { 216 if vType := v.Type(); !vType.isObject() { 217 panic(&ValueError{"Value.Index", vType}) 218 } 219 return objectToValue(v.internal().Index(i)) 220 } 221 222 func (v Value) Int() int { 223 if vType := v.Type(); vType != TypeNumber { 224 panic(&ValueError{"Value.Int", vType}) 225 } 226 return v.internal().Int() 227 } 228 229 func (v Value) InstanceOf(t Value) bool { 230 return instanceOf.Invoke(v.internal(), t.internal()).Bool() 231 } 232 233 func (v Value) Invoke(args ...interface{}) Value { 234 if vType := v.Type(); vType != TypeFunction { 235 panic(&ValueError{"Value.Invoke", vType}) 236 } 237 return objectToValue(v.internal().Invoke(convertArgs(args...)...)) 238 } 239 240 func (v Value) JSValue() Value { 241 return v 242 } 243 244 func (v Value) Length() int { 245 return v.internal().Length() 246 } 247 248 func (v Value) New(args ...interface{}) Value { 249 defer func() { 250 err := recover() 251 if err == nil { 252 return 253 } 254 if vType := v.Type(); vType != TypeFunction { // check here to avoid overhead in success case 255 panic(&ValueError{"Value.New", vType}) 256 } 257 if jsErr, ok := err.(*js.Error); ok { 258 panic(Error{objectToValue(jsErr.Object)}) 259 } 260 panic(err) 261 }() 262 return objectToValue(v.internal().New(convertArgs(args...)...)) 263 } 264 265 func (v Value) Set(p string, x interface{}) { 266 if vType := v.Type(); !vType.isObject() { 267 panic(&ValueError{"Value.Set", vType}) 268 } 269 v.internal().Set(p, convertArgs(x)[0]) 270 } 271 272 func (v Value) SetIndex(i int, x interface{}) { 273 if vType := v.Type(); !vType.isObject() { 274 panic(&ValueError{"Value.SetIndex", vType}) 275 } 276 v.internal().SetIndex(i, convertArgs(x)[0]) 277 } 278 279 // String returns the value v as a string. 280 // String is a special case because of Go's String method convention. Unlike the other getters, 281 // it does not panic if v's Type is not TypeString. Instead, it returns a string of the form "<T>" 282 // or "<T: V>" where T is v's type and V is a string representation of v's value. 283 func (v Value) String() string { 284 switch v.Type() { 285 case TypeString: 286 return v.internal().String() 287 case TypeUndefined: 288 return "<undefined>" 289 case TypeNull: 290 return "<null>" 291 case TypeBoolean: 292 return "<boolean: " + v.internal().String() + ">" 293 case TypeNumber: 294 return "<number: " + v.internal().String() + ">" 295 case TypeSymbol: 296 return "<symbol>" 297 case TypeObject: 298 return "<object>" 299 case TypeFunction: 300 return "<function>" 301 default: 302 panic("bad type") 303 } 304 } 305 306 func (v Value) Truthy() bool { 307 return v.internal().Bool() 308 } 309 310 func (v Value) Type() Type { 311 return Type(getValueType(v.internal())) 312 } 313 314 func (v Value) IsNull() bool { 315 return v.Type() == TypeNull 316 } 317 318 func (v Value) IsUndefined() bool { 319 return !v.inited 320 } 321 322 func (v Value) IsNaN() bool { 323 return js.Global.Call("isNaN", v.internal()).Bool() 324 } 325 326 // Delete deletes the JavaScript property p of value v. 327 // It panics if v is not a JavaScript object. 328 func (v Value) Delete(p string) { 329 if vType := v.Type(); !vType.isObject() { 330 panic(&ValueError{"Value.Delete", vType}) 331 } 332 v.internal().Delete(p) 333 } 334 335 func (v Value) Equal(w Value) bool { 336 return v.internal() == w.internal() 337 } 338 339 type ValueError struct { 340 Method string 341 Type Type 342 } 343 344 func (e *ValueError) Error() string { 345 return "syscall/js: call of " + e.Method + " on " + e.Type.String() 346 } 347 348 // CopyBytesToGo copies bytes from the Uint8Array src to dst. 349 // It returns the number of bytes copied, which will be the minimum of the lengths of src and dst. 350 // CopyBytesToGo panics if src is not an Uint8Array. 351 func CopyBytesToGo(dst []byte, src Value) int { 352 vlen := src.v.Length() 353 if dlen := len(dst); dlen < vlen { 354 vlen = dlen 355 } 356 copy(dst, src.v.Interface().([]byte)) 357 return vlen 358 } 359 360 // CopyBytesToJS copies bytes from src to the Uint8Array dst. 361 // It returns the number of bytes copied, which will be the minimum of the lengths of src and dst. 362 // CopyBytesToJS panics if dst is not an Uint8Array. 363 func CopyBytesToJS(dst Value, src []byte) int { 364 dt, ok := dst.v.Interface().([]byte) 365 if !ok { 366 panic("syscall/js: CopyBytesToJS: expected dst to be an Uint8Array") 367 } 368 return copy(dt, src) 369 }