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. 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// Wrapper is implemented by types that are backed by a JavaScript value. 31type 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. 38type 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 44const ( 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. 54func (v Value) JSValue() Value { 55 return v 56} 57 58func 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 72func finalizeRef(r ref) 73 74func predefValue(id uint32, typeFlag byte) Value { 75 return Value{ref: (nanHead|ref(typeFlag))<<32 | ref(id)} 76} 77 78func 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. 89type Error struct { 90 // Value is the underlying JavaScript error value. 91 Value 92} 93 94// Error implements the error interface. 95func (e Error) Error() string { 96 return "JavaScript error: " + e.Get("message").String() 97} 98 99var ( 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. 114func (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". 119func Undefined() Value { 120 return valueUndefined 121} 122 123// IsUndefined reports whether v is the JavaScript value "undefined". 124func (v Value) IsUndefined() bool { 125 return v.ref == valueUndefined.ref 126} 127 128// Null returns the JavaScript value "null". 129func Null() Value { 130 return valueNull 131} 132 133// IsNull reports whether v is the JavaScript value "null". 134func (v Value) IsNull() bool { 135 return v.ref == valueNull.ref 136} 137 138// IsNaN reports whether v is the JavaScript value "NaN". 139func (v Value) IsNaN() bool { 140 return v.ref == valueNaN.ref 141} 142 143// Global returns the JavaScript global object, usually "window" or "global". 144func 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. 162func 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 223func stringVal(x string) ref 224 225// Type represents the JavaScript type of a Value. 226type Type int 227 228const ( 229 TypeUndefined Type = iota 230 TypeNull 231 TypeBoolean 232 TypeNumber 233 TypeString 234 TypeSymbol 235 TypeObject 236 TypeFunction 237) 238 239func (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 262func (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. 268func (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. 297func (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 306func 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. 310func (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 320func 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. 324func (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 332func 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. 336func (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 345func 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. 349func (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 359func valueSetIndex(v ref, i int, x ref) 360 361func 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. 374func (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 383func 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. 388func (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 405func 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. 410func (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 424func 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. 429func (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 443func valueNew(v ref, args []ref) (ref, bool) 444 445func (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 451func (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. 463func (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. 469func (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. 475func (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. 489func (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. 510func (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 533func 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 542func valuePrepareString(v ref) (ref, int) 543 544func valueLoadString(v ref, b []byte) 545 546// InstanceOf reports whether v is an instance of type t according to JavaScript's instanceof operator. 547func (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 554func 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. 559type ValueError struct { 560 Method string 561 Type Type 562} 563 564func (e *ValueError) Error() string { 565 return "syscall/js: call of " + e.Method + " on " + e.Type.String() 566} 567 568// CopyBytesToGo copies bytes from the Uint8Array src to dst. 569// It returns the number of bytes copied, which will be the minimum of the lengths of src and dst. 570// CopyBytesToGo panics if src is not an Uint8Array. 571func 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") 576 } 577 return n 578} 579 580func copyBytesToGo(dst []byte, src ref) (int, bool) 581 582// CopyBytesToJS copies bytes from src to the Uint8Array dst. 583// It returns the number of bytes copied, which will be the minimum of the lengths of src and dst. 584// CopyBytesToJS panics if dst is not an Uint8Array. 585func 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") 590 } 591 return n 592} 593 594func copyBytesToJS(dst ref, src []byte) (int, bool) 595