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. 12package js 13 14import ( 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. 25type 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). 28const 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. 32type 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 38const ( 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 47func 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 62func finalizeRef(r ref) 63 64func predefValue(id uint32, typeFlag byte) Value { 65 return Value{ref: (nanHead|ref(typeFlag))<<32 | ref(id)} 66} 67 68func 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. 79type Error struct { 80 // Value is the underlying JavaScript error value. 81 Value 82} 83 84// Error implements the error interface. 85func (e Error) Error() string { 86 return "JavaScript error: " + e.Get("message").String() 87} 88 89var ( 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. 104func (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". 109func Undefined() Value { 110 return valueUndefined 111} 112 113// IsUndefined reports whether v is the JavaScript value "undefined". 114func (v Value) IsUndefined() bool { 115 return v.ref == valueUndefined.ref 116} 117 118// Null returns the JavaScript value "null". 119func Null() Value { 120 return valueNull 121} 122 123// IsNull reports whether v is the JavaScript value "null". 124func (v Value) IsNull() bool { 125 return v.ref == valueNull.ref 126} 127 128// IsNaN reports whether v is the JavaScript value "NaN". 129func (v Value) IsNaN() bool { 130 return v.ref == valueNaN.ref 131} 132 133// Global returns the JavaScript global object, usually "window" or "global". 134func 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. 152func 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// stringVal copies string x to Javascript and returns a ref. 214// 215// (noescape): This is safe because no references are maintained to the 216// Go string x after the syscall returns. 217// 218//go:wasmimport gojs syscall/js.stringVal 219//go:noescape 220func stringVal(x string) ref 221 222// Type represents the JavaScript type of a Value. 223type Type int 224 225const ( 226 TypeUndefined Type = iota 227 TypeNull 228 TypeBoolean 229 TypeNumber 230 TypeString 231 TypeSymbol 232 TypeObject 233 TypeFunction 234) 235 236func (t Type) String() string { 237 switch t { 238 case TypeUndefined: 239 return "undefined" 240 case TypeNull: 241 return "null" 242 case TypeBoolean: 243 return "boolean" 244 case TypeNumber: 245 return "number" 246 case TypeString: 247 return "string" 248 case TypeSymbol: 249 return "symbol" 250 case TypeObject: 251 return "object" 252 case TypeFunction: 253 return "function" 254 default: 255 panic("bad type") 256 } 257} 258 259func (t Type) isObject() bool { 260 return t == TypeObject || t == TypeFunction 261} 262 263// Type returns the JavaScript type of the value v. It is similar to JavaScript's typeof operator, 264// except that it returns TypeNull instead of TypeObject for null. 265func (v Value) Type() Type { 266 switch v.ref { 267 case valueUndefined.ref: 268 return TypeUndefined 269 case valueNull.ref: 270 return TypeNull 271 case valueTrue.ref, valueFalse.ref: 272 return TypeBoolean 273 } 274 if v.isNumber() { 275 return TypeNumber 276 } 277 typeFlag := (v.ref >> 32) & 7 278 switch typeFlag { 279 case typeFlagObject: 280 return TypeObject 281 case typeFlagString: 282 return TypeString 283 case typeFlagSymbol: 284 return TypeSymbol 285 case typeFlagFunction: 286 return TypeFunction 287 default: 288 panic("bad type flag") 289 } 290} 291 292// Get returns the JavaScript property p of value v. 293// It panics if v is not a JavaScript object. 294func (v Value) Get(p string) Value { 295 if vType := v.Type(); !vType.isObject() { 296 panic(&ValueError{"Value.Get", vType}) 297 } 298 r := makeValue(valueGet(v.ref, p)) 299 runtime.KeepAlive(v) 300 return r 301} 302 303// valueGet returns a ref to JavaScript property p of ref v. 304// 305// (noescape): This is safe because no references are maintained to the 306// Go string p after the syscall returns. 307// 308//go:wasmimport gojs syscall/js.valueGet 309//go:noescape 310func valueGet(v ref, p string) ref 311 312// Set sets the JavaScript property p of value v to ValueOf(x). 313// It panics if v is not a JavaScript object. 314func (v Value) Set(p string, x any) { 315 if vType := v.Type(); !vType.isObject() { 316 panic(&ValueError{"Value.Set", vType}) 317 } 318 xv := ValueOf(x) 319 valueSet(v.ref, p, xv.ref) 320 runtime.KeepAlive(v) 321 runtime.KeepAlive(xv) 322} 323 324// valueSet sets property p of ref v to ref x. 325// 326// (noescape): This is safe because no references are maintained to the 327// Go string p after the syscall returns. 328// 329//go:wasmimport gojs syscall/js.valueSet 330//go:noescape 331func valueSet(v ref, p string, x ref) 332 333// Delete deletes the JavaScript property p of value v. 334// It panics if v is not a JavaScript object. 335func (v Value) Delete(p string) { 336 if vType := v.Type(); !vType.isObject() { 337 panic(&ValueError{"Value.Delete", vType}) 338 } 339 valueDelete(v.ref, p) 340 runtime.KeepAlive(v) 341} 342 343// valueDelete deletes the JavaScript property p of ref v. 344// 345// (noescape): This is safe because no references are maintained to the 346// Go string p after the syscall returns. 347// 348//go:wasmimport gojs syscall/js.valueDelete 349//go:noescape 350func valueDelete(v ref, p string) 351 352// Index returns JavaScript index i of value v. 353// It panics if v is not a JavaScript object. 354func (v Value) Index(i int) Value { 355 if vType := v.Type(); !vType.isObject() { 356 panic(&ValueError{"Value.Index", vType}) 357 } 358 r := makeValue(valueIndex(v.ref, i)) 359 runtime.KeepAlive(v) 360 return r 361} 362 363//go:wasmimport gojs syscall/js.valueIndex 364func valueIndex(v ref, i int) ref 365 366// SetIndex sets the JavaScript index i of value v to ValueOf(x). 367// It panics if v is not a JavaScript object. 368func (v Value) SetIndex(i int, x any) { 369 if vType := v.Type(); !vType.isObject() { 370 panic(&ValueError{"Value.SetIndex", vType}) 371 } 372 xv := ValueOf(x) 373 valueSetIndex(v.ref, i, xv.ref) 374 runtime.KeepAlive(v) 375 runtime.KeepAlive(xv) 376} 377 378//go:wasmimport gojs syscall/js.valueSetIndex 379func valueSetIndex(v ref, i int, x ref) 380 381// makeArgSlices makes two slices to hold JavaScript arg data. 382// It can be paired with storeArgs to make-and-store JavaScript arg slices. 383// However, the two functions are separated to ensure makeArgSlices is inlined 384// which will prevent the slices from being heap allocated for small (<=16) 385// numbers of args. 386func makeArgSlices(size int) (argVals []Value, argRefs []ref) { 387 // value chosen for being power of two, and enough to handle all web APIs 388 // in particular, note that WebGL2's texImage2D takes up to 10 arguments 389 const maxStackArgs = 16 390 if size <= maxStackArgs { 391 // as long as makeArgs is inlined, these will be stack-allocated 392 argVals = make([]Value, size, maxStackArgs) 393 argRefs = make([]ref, size, maxStackArgs) 394 } else { 395 // allocates on the heap, but exceeding maxStackArgs should be rare 396 argVals = make([]Value, size) 397 argRefs = make([]ref, size) 398 } 399 return 400} 401 402// storeArgs maps input args onto respective Value and ref slices. 403// It can be paired with makeArgSlices to make-and-store JavaScript arg slices. 404func storeArgs(args []any, argValsDst []Value, argRefsDst []ref) { 405 // would go in makeArgs if the combined func was simple enough to inline 406 for i, arg := range args { 407 v := ValueOf(arg) 408 argValsDst[i] = v 409 argRefsDst[i] = v.ref 410 } 411} 412 413// Length returns the JavaScript property "length" of v. 414// It panics if v is not a JavaScript object. 415func (v Value) Length() int { 416 if vType := v.Type(); !vType.isObject() { 417 panic(&ValueError{"Value.SetIndex", vType}) 418 } 419 r := valueLength(v.ref) 420 runtime.KeepAlive(v) 421 return r 422} 423 424//go:wasmimport gojs syscall/js.valueLength 425func valueLength(v ref) int 426 427// Call does a JavaScript call to the method m of value v with the given arguments. 428// It panics if v has no method m. 429// The arguments get mapped to JavaScript values according to the ValueOf function. 430func (v Value) Call(m string, args ...any) Value { 431 argVals, argRefs := makeArgSlices(len(args)) 432 storeArgs(args, argVals, argRefs) 433 res, ok := valueCall(v.ref, m, argRefs) 434 runtime.KeepAlive(v) 435 runtime.KeepAlive(argVals) 436 if !ok { 437 if vType := v.Type(); !vType.isObject() { // check here to avoid overhead in success case 438 panic(&ValueError{"Value.Call", vType}) 439 } 440 if propType := v.Get(m).Type(); propType != TypeFunction { 441 panic("syscall/js: Value.Call: property " + m + " is not a function, got " + propType.String()) 442 } 443 panic(Error{makeValue(res)}) 444 } 445 return makeValue(res) 446} 447 448// valueCall does a JavaScript call to the method name m of ref v with the given arguments. 449// 450// (noescape): This is safe because no references are maintained to the 451// Go string m after the syscall returns. Additionally, the args slice 452// is only used temporarily to collect the JavaScript objects for 453// the JavaScript method invocation. 454// 455//go:wasmimport gojs syscall/js.valueCall 456//go:nosplit 457//go:noescape 458func valueCall(v ref, m string, args []ref) (ref, bool) 459 460// Invoke does a JavaScript call of the value v with the given arguments. 461// It panics if v is not a JavaScript function. 462// The arguments get mapped to JavaScript values according to the ValueOf function. 463func (v Value) Invoke(args ...any) Value { 464 argVals, argRefs := makeArgSlices(len(args)) 465 storeArgs(args, argVals, argRefs) 466 res, ok := valueInvoke(v.ref, argRefs) 467 runtime.KeepAlive(v) 468 runtime.KeepAlive(argVals) 469 if !ok { 470 if vType := v.Type(); vType != TypeFunction { // check here to avoid overhead in success case 471 panic(&ValueError{"Value.Invoke", vType}) 472 } 473 panic(Error{makeValue(res)}) 474 } 475 return makeValue(res) 476} 477 478// valueInvoke does a JavaScript call to value v with the given arguments. 479// 480// (noescape): This is safe because the args slice is only used temporarily 481// to collect the JavaScript objects for the JavaScript method 482// invocation. 483// 484//go:wasmimport gojs syscall/js.valueInvoke 485//go:noescape 486func valueInvoke(v ref, args []ref) (ref, bool) 487 488// New uses JavaScript's "new" operator with value v as constructor and the given arguments. 489// It panics if v is not a JavaScript function. 490// The arguments get mapped to JavaScript values according to the ValueOf function. 491func (v Value) New(args ...any) Value { 492 argVals, argRefs := makeArgSlices(len(args)) 493 storeArgs(args, argVals, argRefs) 494 res, ok := valueNew(v.ref, argRefs) 495 runtime.KeepAlive(v) 496 runtime.KeepAlive(argVals) 497 if !ok { 498 if vType := v.Type(); vType != TypeFunction { // check here to avoid overhead in success case 499 panic(&ValueError{"Value.Invoke", vType}) 500 } 501 panic(Error{makeValue(res)}) 502 } 503 return makeValue(res) 504} 505 506// valueNew uses JavaScript's "new" operator with value v as a constructor and the given arguments. 507// 508// (noescape): This is safe because the args slice is only used temporarily 509// to collect the JavaScript objects for the constructor execution. 510// 511//go:wasmimport gojs syscall/js.valueNew 512//go:noescape 513func valueNew(v ref, args []ref) (ref, bool) 514 515func (v Value) isNumber() bool { 516 return v.ref == valueZero.ref || 517 v.ref == valueNaN.ref || 518 (v.ref != valueUndefined.ref && (v.ref>>32)&nanHead != nanHead) 519} 520 521func (v Value) float(method string) float64 { 522 if !v.isNumber() { 523 panic(&ValueError{method, v.Type()}) 524 } 525 if v.ref == valueZero.ref { 526 return 0 527 } 528 return *(*float64)(unsafe.Pointer(&v.ref)) 529} 530 531// Float returns the value v as a float64. 532// It panics if v is not a JavaScript number. 533func (v Value) Float() float64 { 534 return v.float("Value.Float") 535} 536 537// Int returns the value v truncated to an int. 538// It panics if v is not a JavaScript number. 539func (v Value) Int() int { 540 return int(v.float("Value.Int")) 541} 542 543// Bool returns the value v as a bool. 544// It panics if v is not a JavaScript boolean. 545func (v Value) Bool() bool { 546 switch v.ref { 547 case valueTrue.ref: 548 return true 549 case valueFalse.ref: 550 return false 551 default: 552 panic(&ValueError{"Value.Bool", v.Type()}) 553 } 554} 555 556// Truthy returns the JavaScript "truthiness" of the value v. In JavaScript, 557// false, 0, "", null, undefined, and NaN are "falsy", and everything else is 558// "truthy". See https://developer.mozilla.org/en-US/docs/Glossary/Truthy. 559func (v Value) Truthy() bool { 560 switch v.Type() { 561 case TypeUndefined, TypeNull: 562 return false 563 case TypeBoolean: 564 return v.Bool() 565 case TypeNumber: 566 return v.ref != valueNaN.ref && v.ref != valueZero.ref 567 case TypeString: 568 return v.String() != "" 569 case TypeSymbol, TypeFunction, TypeObject: 570 return true 571 default: 572 panic("bad type") 573 } 574} 575 576// String returns the value v as a string. 577// String is a special case because of Go's String method convention. Unlike the other getters, 578// it does not panic if v's Type is not TypeString. Instead, it returns a string of the form "<T>" 579// or "<T: V>" where T is v's type and V is a string representation of v's value. 580func (v Value) String() string { 581 switch v.Type() { 582 case TypeString: 583 return jsString(v) 584 case TypeUndefined: 585 return "<undefined>" 586 case TypeNull: 587 return "<null>" 588 case TypeBoolean: 589 return "<boolean: " + jsString(v) + ">" 590 case TypeNumber: 591 return "<number: " + jsString(v) + ">" 592 case TypeSymbol: 593 return "<symbol>" 594 case TypeObject: 595 return "<object>" 596 case TypeFunction: 597 return "<function>" 598 default: 599 panic("bad type") 600 } 601} 602 603func jsString(v Value) string { 604 str, length := valuePrepareString(v.ref) 605 runtime.KeepAlive(v) 606 b := make([]byte, length) 607 valueLoadString(str, b) 608 finalizeRef(str) 609 return string(b) 610} 611 612//go:wasmimport gojs syscall/js.valuePrepareString 613func valuePrepareString(v ref) (ref, int) 614 615// valueLoadString loads string data located at ref v into byte slice b. 616// 617// (noescape): This is safe because the byte slice is only used as a destination 618// for storing the string data and references to it are not maintained. 619// 620//go:wasmimport gojs syscall/js.valueLoadString 621//go:noescape 622func valueLoadString(v ref, b []byte) 623 624// InstanceOf reports whether v is an instance of type t according to JavaScript's instanceof operator. 625func (v Value) InstanceOf(t Value) bool { 626 r := valueInstanceOf(v.ref, t.ref) 627 runtime.KeepAlive(v) 628 runtime.KeepAlive(t) 629 return r 630} 631 632//go:wasmimport gojs syscall/js.valueInstanceOf 633func valueInstanceOf(v ref, t ref) bool 634 635// A ValueError occurs when a Value method is invoked on 636// a Value that does not support it. Such cases are documented 637// in the description of each method. 638type ValueError struct { 639 Method string 640 Type Type 641} 642 643func (e *ValueError) Error() string { 644 return "syscall/js: call of " + e.Method + " on " + e.Type.String() 645} 646 647// CopyBytesToGo copies bytes from src to dst. 648// It panics if src is not a Uint8Array or Uint8ClampedArray. 649// It returns the number of bytes copied, which will be the minimum of the lengths of src and dst. 650func CopyBytesToGo(dst []byte, src Value) int { 651 n, ok := copyBytesToGo(dst, src.ref) 652 runtime.KeepAlive(src) 653 if !ok { 654 panic("syscall/js: CopyBytesToGo: expected src to be a Uint8Array or Uint8ClampedArray") 655 } 656 return n 657} 658 659// copyBytesToGo copies bytes from src to dst. 660// 661// (noescape): This is safe because the dst byte slice is only used as a dst 662// copy buffer and no references to it are maintained. 663// 664//go:wasmimport gojs syscall/js.copyBytesToGo 665//go:noescape 666func copyBytesToGo(dst []byte, src ref) (int, bool) 667 668// CopyBytesToJS copies bytes from src to dst. 669// It panics if dst is not a Uint8Array or Uint8ClampedArray. 670// It returns the number of bytes copied, which will be the minimum of the lengths of src and dst. 671func CopyBytesToJS(dst Value, src []byte) int { 672 n, ok := copyBytesToJS(dst.ref, src) 673 runtime.KeepAlive(dst) 674 if !ok { 675 panic("syscall/js: CopyBytesToJS: expected dst to be a Uint8Array or Uint8ClampedArray") 676 } 677 return n 678} 679 680// copyBytesToJs copies bytes from src to dst. 681// 682// (noescape): This is safe because the src byte slice is only used as a src 683// copy buffer and no references to it are maintained. 684// 685//go:wasmimport gojs syscall/js.copyBytesToJS 686//go:noescape 687func copyBytesToJS(dst ref, src []byte) (int, bool) 688