github.com/ice-blockchain/go/src@v0.0.0-20240403114104-1564d284e521/syscall/js/js.go (about) 1 // Copyright 2018 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build js && wasm 6 7 // Package js gives access to the WebAssembly host environment when using the js/wasm architecture. 8 // Its API is based on JavaScript semantics. 9 // 10 // This package is EXPERIMENTAL. Its current scope is only to allow tests to run, but not yet to provide a 11 // comprehensive API for users. It is exempt from the Go compatibility promise. 12 package js 13 14 import ( 15 "runtime" 16 "unsafe" 17 ) 18 19 // ref is used to identify a JavaScript value, since the value itself can not be passed to WebAssembly. 20 // 21 // The JavaScript value "undefined" is represented by the value 0. 22 // A JavaScript number (64-bit float, except 0 and NaN) is represented by its IEEE 754 binary representation. 23 // All other values are represented as an IEEE 754 binary representation of NaN with bits 0-31 used as 24 // an ID and bits 32-34 used to differentiate between string, symbol, function and object. 25 type ref uint64 26 27 // nanHead are the upper 32 bits of a ref which are set if the value is not encoded as an IEEE 754 number (see above). 28 const nanHead = 0x7FF80000 29 30 // Value represents a JavaScript value. The zero value is the JavaScript value "undefined". 31 // Values can be checked for equality with the Equal method. 32 type Value struct { 33 _ [0]func() // uncomparable; to make == not compile 34 ref ref // identifies a JavaScript value, see ref type 35 gcPtr *ref // used to trigger the finalizer when the Value is not referenced any more 36 } 37 38 const ( 39 // the type flags need to be in sync with wasm_exec.js 40 typeFlagNone = iota 41 typeFlagObject 42 typeFlagString 43 typeFlagSymbol 44 typeFlagFunction 45 ) 46 47 func makeValue(r ref) Value { 48 var gcPtr *ref 49 typeFlag := (r >> 32) & 7 50 if (r>>32)&nanHead == nanHead && typeFlag != typeFlagNone { 51 gcPtr = new(ref) 52 *gcPtr = r 53 runtime.SetFinalizer(gcPtr, func(p *ref) { 54 finalizeRef(*p) 55 }) 56 } 57 58 return Value{ref: r, gcPtr: gcPtr} 59 } 60 61 //go:wasmimport gojs syscall/js.finalizeRef 62 func finalizeRef(r ref) 63 64 func predefValue(id uint32, typeFlag byte) Value { 65 return Value{ref: (nanHead|ref(typeFlag))<<32 | ref(id)} 66 } 67 68 func floatValue(f float64) Value { 69 if f == 0 { 70 return valueZero 71 } 72 if f != f { 73 return valueNaN 74 } 75 return Value{ref: *(*ref)(unsafe.Pointer(&f))} 76 } 77 78 // Error wraps a JavaScript error. 79 type Error struct { 80 // Value is the underlying JavaScript error value. 81 Value 82 } 83 84 // Error implements the error interface. 85 func (e Error) Error() string { 86 return "JavaScript error: " + e.Get("message").String() 87 } 88 89 var ( 90 valueUndefined = Value{ref: 0} 91 valueNaN = predefValue(0, typeFlagNone) 92 valueZero = predefValue(1, typeFlagNone) 93 valueNull = predefValue(2, typeFlagNone) 94 valueTrue = predefValue(3, typeFlagNone) 95 valueFalse = predefValue(4, typeFlagNone) 96 valueGlobal = predefValue(5, typeFlagObject) 97 jsGo = predefValue(6, typeFlagObject) // instance of the Go class in JavaScript 98 99 objectConstructor = valueGlobal.Get("Object") 100 arrayConstructor = valueGlobal.Get("Array") 101 ) 102 103 // Equal reports whether v and w are equal according to JavaScript's === operator. 104 func (v Value) Equal(w Value) bool { 105 return v.ref == w.ref && v.ref != valueNaN.ref 106 } 107 108 // Undefined returns the JavaScript value "undefined". 109 func Undefined() Value { 110 return valueUndefined 111 } 112 113 // IsUndefined reports whether v is the JavaScript value "undefined". 114 func (v Value) IsUndefined() bool { 115 return v.ref == valueUndefined.ref 116 } 117 118 // Null returns the JavaScript value "null". 119 func Null() Value { 120 return valueNull 121 } 122 123 // IsNull reports whether v is the JavaScript value "null". 124 func (v Value) IsNull() bool { 125 return v.ref == valueNull.ref 126 } 127 128 // IsNaN reports whether v is the JavaScript value "NaN". 129 func (v Value) IsNaN() bool { 130 return v.ref == valueNaN.ref 131 } 132 133 // Global returns the JavaScript global object, usually "window" or "global". 134 func Global() Value { 135 return valueGlobal 136 } 137 138 // ValueOf returns x as a JavaScript value: 139 // 140 // | Go | JavaScript | 141 // | ---------------------- | ---------------------- | 142 // | js.Value | [its value] | 143 // | js.Func | function | 144 // | nil | null | 145 // | bool | boolean | 146 // | integers and floats | number | 147 // | string | string | 148 // | []interface{} | new array | 149 // | map[string]interface{} | new object | 150 // 151 // Panics if x is not one of the expected types. 152 func ValueOf(x any) Value { 153 switch x := x.(type) { 154 case Value: 155 return x 156 case Func: 157 return x.Value 158 case nil: 159 return valueNull 160 case bool: 161 if x { 162 return valueTrue 163 } else { 164 return valueFalse 165 } 166 case int: 167 return floatValue(float64(x)) 168 case int8: 169 return floatValue(float64(x)) 170 case int16: 171 return floatValue(float64(x)) 172 case int32: 173 return floatValue(float64(x)) 174 case int64: 175 return floatValue(float64(x)) 176 case uint: 177 return floatValue(float64(x)) 178 case uint8: 179 return floatValue(float64(x)) 180 case uint16: 181 return floatValue(float64(x)) 182 case uint32: 183 return floatValue(float64(x)) 184 case uint64: 185 return floatValue(float64(x)) 186 case uintptr: 187 return floatValue(float64(x)) 188 case unsafe.Pointer: 189 return floatValue(float64(uintptr(x))) 190 case float32: 191 return floatValue(float64(x)) 192 case float64: 193 return floatValue(x) 194 case string: 195 return makeValue(stringVal(x)) 196 case []any: 197 a := arrayConstructor.New(len(x)) 198 for i, s := range x { 199 a.SetIndex(i, s) 200 } 201 return a 202 case map[string]any: 203 o := objectConstructor.New() 204 for k, v := range x { 205 o.Set(k, v) 206 } 207 return o 208 default: 209 panic("ValueOf: invalid value") 210 } 211 } 212 213 //go:wasmimport gojs syscall/js.stringVal 214 func stringVal(x string) ref 215 216 // Type represents the JavaScript type of a Value. 217 type Type int 218 219 const ( 220 TypeUndefined Type = iota 221 TypeNull 222 TypeBoolean 223 TypeNumber 224 TypeString 225 TypeSymbol 226 TypeObject 227 TypeFunction 228 ) 229 230 func (t Type) String() string { 231 switch t { 232 case TypeUndefined: 233 return "undefined" 234 case TypeNull: 235 return "null" 236 case TypeBoolean: 237 return "boolean" 238 case TypeNumber: 239 return "number" 240 case TypeString: 241 return "string" 242 case TypeSymbol: 243 return "symbol" 244 case TypeObject: 245 return "object" 246 case TypeFunction: 247 return "function" 248 default: 249 panic("bad type") 250 } 251 } 252 253 func (t Type) isObject() bool { 254 return t == TypeObject || t == TypeFunction 255 } 256 257 // Type returns the JavaScript type of the value v. It is similar to JavaScript's typeof operator, 258 // except that it returns TypeNull instead of TypeObject for null. 259 func (v Value) Type() Type { 260 switch v.ref { 261 case valueUndefined.ref: 262 return TypeUndefined 263 case valueNull.ref: 264 return TypeNull 265 case valueTrue.ref, valueFalse.ref: 266 return TypeBoolean 267 } 268 if v.isNumber() { 269 return TypeNumber 270 } 271 typeFlag := (v.ref >> 32) & 7 272 switch typeFlag { 273 case typeFlagObject: 274 return TypeObject 275 case typeFlagString: 276 return TypeString 277 case typeFlagSymbol: 278 return TypeSymbol 279 case typeFlagFunction: 280 return TypeFunction 281 default: 282 panic("bad type flag") 283 } 284 } 285 286 // Get returns the JavaScript property p of value v. 287 // It panics if v is not a JavaScript object. 288 func (v Value) Get(p string) Value { 289 if vType := v.Type(); !vType.isObject() { 290 panic(&ValueError{"Value.Get", vType}) 291 } 292 r := makeValue(valueGet(v.ref, p)) 293 runtime.KeepAlive(v) 294 return r 295 } 296 297 //go:wasmimport gojs syscall/js.valueGet 298 func valueGet(v ref, p string) ref 299 300 // Set sets the JavaScript property p of value v to ValueOf(x). 301 // It panics if v is not a JavaScript object. 302 func (v Value) Set(p string, x any) { 303 if vType := v.Type(); !vType.isObject() { 304 panic(&ValueError{"Value.Set", vType}) 305 } 306 xv := ValueOf(x) 307 valueSet(v.ref, p, xv.ref) 308 runtime.KeepAlive(v) 309 runtime.KeepAlive(xv) 310 } 311 312 //go:wasmimport gojs syscall/js.valueSet 313 func valueSet(v ref, p string, x ref) 314 315 // Delete deletes the JavaScript property p of value v. 316 // It panics if v is not a JavaScript object. 317 func (v Value) Delete(p string) { 318 if vType := v.Type(); !vType.isObject() { 319 panic(&ValueError{"Value.Delete", vType}) 320 } 321 valueDelete(v.ref, p) 322 runtime.KeepAlive(v) 323 } 324 325 //go:wasmimport gojs syscall/js.valueDelete 326 func valueDelete(v ref, p string) 327 328 // Index returns JavaScript index i of value v. 329 // It panics if v is not a JavaScript object. 330 func (v Value) Index(i int) Value { 331 if vType := v.Type(); !vType.isObject() { 332 panic(&ValueError{"Value.Index", vType}) 333 } 334 r := makeValue(valueIndex(v.ref, i)) 335 runtime.KeepAlive(v) 336 return r 337 } 338 339 //go:wasmimport gojs syscall/js.valueIndex 340 func valueIndex(v ref, i int) ref 341 342 // SetIndex sets the JavaScript index i of value v to ValueOf(x). 343 // It panics if v is not a JavaScript object. 344 func (v Value) SetIndex(i int, x any) { 345 if vType := v.Type(); !vType.isObject() { 346 panic(&ValueError{"Value.SetIndex", vType}) 347 } 348 xv := ValueOf(x) 349 valueSetIndex(v.ref, i, xv.ref) 350 runtime.KeepAlive(v) 351 runtime.KeepAlive(xv) 352 } 353 354 //go:wasmimport gojs syscall/js.valueSetIndex 355 func valueSetIndex(v ref, i int, x ref) 356 357 func makeArgs(args []any) ([]Value, []ref) { 358 argVals := make([]Value, len(args)) 359 argRefs := make([]ref, len(args)) 360 for i, arg := range args { 361 v := ValueOf(arg) 362 argVals[i] = v 363 argRefs[i] = v.ref 364 } 365 return argVals, argRefs 366 } 367 368 // Length returns the JavaScript property "length" of v. 369 // It panics if v is not a JavaScript object. 370 func (v Value) Length() int { 371 if vType := v.Type(); !vType.isObject() { 372 panic(&ValueError{"Value.SetIndex", vType}) 373 } 374 r := valueLength(v.ref) 375 runtime.KeepAlive(v) 376 return r 377 } 378 379 //go:wasmimport gojs syscall/js.valueLength 380 func valueLength(v ref) int 381 382 // Call does a JavaScript call to the method m of value v with the given arguments. 383 // It panics if v has no method m. 384 // The arguments get mapped to JavaScript values according to the ValueOf function. 385 func (v Value) Call(m string, args ...any) Value { 386 argVals, argRefs := makeArgs(args) 387 res, ok := valueCall(v.ref, m, argRefs) 388 runtime.KeepAlive(v) 389 runtime.KeepAlive(argVals) 390 if !ok { 391 if vType := v.Type(); !vType.isObject() { // check here to avoid overhead in success case 392 panic(&ValueError{"Value.Call", vType}) 393 } 394 if propType := v.Get(m).Type(); propType != TypeFunction { 395 panic("syscall/js: Value.Call: property " + m + " is not a function, got " + propType.String()) 396 } 397 panic(Error{makeValue(res)}) 398 } 399 return makeValue(res) 400 } 401 402 //go:wasmimport gojs syscall/js.valueCall 403 //go:nosplit 404 func valueCall(v ref, m string, args []ref) (ref, bool) 405 406 // Invoke does a JavaScript call of the value v with the given arguments. 407 // It panics if v is not a JavaScript function. 408 // The arguments get mapped to JavaScript values according to the ValueOf function. 409 func (v Value) Invoke(args ...any) Value { 410 argVals, argRefs := makeArgs(args) 411 res, ok := valueInvoke(v.ref, argRefs) 412 runtime.KeepAlive(v) 413 runtime.KeepAlive(argVals) 414 if !ok { 415 if vType := v.Type(); vType != TypeFunction { // check here to avoid overhead in success case 416 panic(&ValueError{"Value.Invoke", vType}) 417 } 418 panic(Error{makeValue(res)}) 419 } 420 return makeValue(res) 421 } 422 423 //go:wasmimport gojs syscall/js.valueInvoke 424 func valueInvoke(v ref, args []ref) (ref, bool) 425 426 // New uses JavaScript's "new" operator with value v as constructor and the given arguments. 427 // It panics if v is not a JavaScript function. 428 // The arguments get mapped to JavaScript values according to the ValueOf function. 429 func (v Value) New(args ...any) Value { 430 argVals, argRefs := makeArgs(args) 431 res, ok := valueNew(v.ref, argRefs) 432 runtime.KeepAlive(v) 433 runtime.KeepAlive(argVals) 434 if !ok { 435 if vType := v.Type(); vType != TypeFunction { // check here to avoid overhead in success case 436 panic(&ValueError{"Value.Invoke", vType}) 437 } 438 panic(Error{makeValue(res)}) 439 } 440 return makeValue(res) 441 } 442 443 //go:wasmimport gojs syscall/js.valueNew 444 func valueNew(v ref, args []ref) (ref, bool) 445 446 func (v Value) isNumber() bool { 447 return v.ref == valueZero.ref || 448 v.ref == valueNaN.ref || 449 (v.ref != valueUndefined.ref && (v.ref>>32)&nanHead != nanHead) 450 } 451 452 func (v Value) float(method string) float64 { 453 if !v.isNumber() { 454 panic(&ValueError{method, v.Type()}) 455 } 456 if v.ref == valueZero.ref { 457 return 0 458 } 459 return *(*float64)(unsafe.Pointer(&v.ref)) 460 } 461 462 // Float returns the value v as a float64. 463 // It panics if v is not a JavaScript number. 464 func (v Value) Float() float64 { 465 return v.float("Value.Float") 466 } 467 468 // Int returns the value v truncated to an int. 469 // It panics if v is not a JavaScript number. 470 func (v Value) Int() int { 471 return int(v.float("Value.Int")) 472 } 473 474 // Bool returns the value v as a bool. 475 // It panics if v is not a JavaScript boolean. 476 func (v Value) Bool() bool { 477 switch v.ref { 478 case valueTrue.ref: 479 return true 480 case valueFalse.ref: 481 return false 482 default: 483 panic(&ValueError{"Value.Bool", v.Type()}) 484 } 485 } 486 487 // Truthy returns the JavaScript "truthiness" of the value v. In JavaScript, 488 // false, 0, "", null, undefined, and NaN are "falsy", and everything else is 489 // "truthy". See https://developer.mozilla.org/en-US/docs/Glossary/Truthy. 490 func (v Value) Truthy() bool { 491 switch v.Type() { 492 case TypeUndefined, TypeNull: 493 return false 494 case TypeBoolean: 495 return v.Bool() 496 case TypeNumber: 497 return v.ref != valueNaN.ref && v.ref != valueZero.ref 498 case TypeString: 499 return v.String() != "" 500 case TypeSymbol, TypeFunction, TypeObject: 501 return true 502 default: 503 panic("bad type") 504 } 505 } 506 507 // String returns the value v as a string. 508 // String is a special case because of Go's String method convention. Unlike the other getters, 509 // it does not panic if v's Type is not TypeString. Instead, it returns a string of the form "<T>" 510 // or "<T: V>" where T is v's type and V is a string representation of v's value. 511 func (v Value) String() string { 512 switch v.Type() { 513 case TypeString: 514 return jsString(v) 515 case TypeUndefined: 516 return "<undefined>" 517 case TypeNull: 518 return "<null>" 519 case TypeBoolean: 520 return "<boolean: " + jsString(v) + ">" 521 case TypeNumber: 522 return "<number: " + jsString(v) + ">" 523 case TypeSymbol: 524 return "<symbol>" 525 case TypeObject: 526 return "<object>" 527 case TypeFunction: 528 return "<function>" 529 default: 530 panic("bad type") 531 } 532 } 533 534 func jsString(v Value) string { 535 str, length := valuePrepareString(v.ref) 536 runtime.KeepAlive(v) 537 b := make([]byte, length) 538 valueLoadString(str, b) 539 finalizeRef(str) 540 return string(b) 541 } 542 543 //go:wasmimport gojs syscall/js.valuePrepareString 544 func valuePrepareString(v ref) (ref, int) 545 546 //go:wasmimport gojs syscall/js.valueLoadString 547 func valueLoadString(v ref, b []byte) 548 549 // InstanceOf reports whether v is an instance of type t according to JavaScript's instanceof operator. 550 func (v Value) InstanceOf(t Value) bool { 551 r := valueInstanceOf(v.ref, t.ref) 552 runtime.KeepAlive(v) 553 runtime.KeepAlive(t) 554 return r 555 } 556 557 //go:wasmimport gojs syscall/js.valueInstanceOf 558 func valueInstanceOf(v ref, t ref) bool 559 560 // A ValueError occurs when a Value method is invoked on 561 // a Value that does not support it. Such cases are documented 562 // in the description of each method. 563 type ValueError struct { 564 Method string 565 Type Type 566 } 567 568 func (e *ValueError) Error() string { 569 return "syscall/js: call of " + e.Method + " on " + e.Type.String() 570 } 571 572 // CopyBytesToGo copies bytes from src to dst. 573 // It panics if src is not a Uint8Array or Uint8ClampedArray. 574 // It returns the number of bytes copied, which will be the minimum of the lengths of src and dst. 575 func CopyBytesToGo(dst []byte, src Value) int { 576 n, ok := copyBytesToGo(dst, src.ref) 577 runtime.KeepAlive(src) 578 if !ok { 579 panic("syscall/js: CopyBytesToGo: expected src to be a Uint8Array or Uint8ClampedArray") 580 } 581 return n 582 } 583 584 //go:wasmimport gojs syscall/js.copyBytesToGo 585 func copyBytesToGo(dst []byte, src ref) (int, bool) 586 587 // CopyBytesToJS copies bytes from src to dst. 588 // It panics if dst is not a Uint8Array or Uint8ClampedArray. 589 // It returns the number of bytes copied, which will be the minimum of the lengths of src and dst. 590 func CopyBytesToJS(dst Value, src []byte) int { 591 n, ok := copyBytesToJS(dst.ref, src) 592 runtime.KeepAlive(dst) 593 if !ok { 594 panic("syscall/js: CopyBytesToJS: expected dst to be a Uint8Array or Uint8ClampedArray") 595 } 596 return n 597 } 598 599 //go:wasmimport gojs syscall/js.copyBytesToJS 600 func copyBytesToJS(dst ref, src []byte) (int, bool)