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