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 "unsafe" 16 ) 17 18 // ref is used to identify a JavaScript value, since the value itself can not be passed to WebAssembly. 19 // 20 // The JavaScript value "undefined" is represented by the value 0. 21 // A JavaScript number (64-bit float, except 0 and NaN) is represented by its IEEE 754 binary representation. 22 // All other values are represented as an IEEE 754 binary representation of NaN with bits 0-31 used as 23 // an ID and bits 32-33 used to differentiate between string, symbol, function and object. 24 type ref uint64 25 26 // 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). 27 const nanHead = 0x7FF80000 28 29 // Wrapper is implemented by types that are backed by a JavaScript value. 30 type Wrapper interface { 31 // JSValue returns a JavaScript value associated with an object. 32 JSValue() Value 33 } 34 35 // Value represents a JavaScript value. The zero value is the JavaScript value "undefined". 36 type Value struct { 37 ref ref 38 } 39 40 // JSValue implements Wrapper interface. 41 func (v Value) JSValue() Value { 42 return v 43 } 44 45 func makeValue(v ref) Value { 46 return Value{ref: v} 47 } 48 49 func predefValue(id uint32) Value { 50 return Value{ref: nanHead<<32 | ref(id)} 51 } 52 53 func floatValue(f float64) Value { 54 if f == 0 { 55 return valueZero 56 } 57 if f != f { 58 return valueNaN 59 } 60 return Value{ref: *(*ref)(unsafe.Pointer(&f))} 61 } 62 63 // Error wraps a JavaScript error. 64 type Error struct { 65 // Value is the underlying JavaScript error value. 66 Value 67 } 68 69 // Error implements the error interface. 70 func (e Error) Error() string { 71 return "JavaScript error: " + e.Get("message").String() 72 } 73 74 var ( 75 valueUndefined = Value{ref: 0} 76 valueNaN = predefValue(0) 77 valueZero = predefValue(1) 78 valueNull = predefValue(2) 79 valueTrue = predefValue(3) 80 valueFalse = predefValue(4) 81 valueGlobal = predefValue(5) 82 jsGo = predefValue(6) // instance of the Go class in JavaScript 83 84 objectConstructor = valueGlobal.Get("Object") 85 arrayConstructor = valueGlobal.Get("Array") 86 ) 87 88 // Undefined returns the JavaScript value "undefined". 89 func Undefined() Value { 90 return valueUndefined 91 } 92 93 // Null returns the JavaScript value "null". 94 func Null() Value { 95 return valueNull 96 } 97 98 // Global returns the JavaScript global object, usually "window" or "global". 99 func Global() Value { 100 return valueGlobal 101 } 102 103 // ValueOf returns x as a JavaScript value: 104 // 105 // | Go | JavaScript | 106 // | ---------------------- | ---------------------- | 107 // | js.Value | [its value] | 108 // | js.Func | function | 109 // | nil | null | 110 // | bool | boolean | 111 // | integers and floats | number | 112 // | string | string | 113 // | []interface{} | new array | 114 // | map[string]interface{} | new object | 115 // 116 // Panics if x is not one of the expected types. 117 func ValueOf(x interface{}) Value { 118 switch x := x.(type) { 119 case Value: // should precede Wrapper to avoid a loop 120 return x 121 case Wrapper: 122 return x.JSValue() 123 case nil: 124 return valueNull 125 case bool: 126 if x { 127 return valueTrue 128 } else { 129 return valueFalse 130 } 131 case int: 132 return floatValue(float64(x)) 133 case int8: 134 return floatValue(float64(x)) 135 case int16: 136 return floatValue(float64(x)) 137 case int32: 138 return floatValue(float64(x)) 139 case int64: 140 return floatValue(float64(x)) 141 case uint: 142 return floatValue(float64(x)) 143 case uint8: 144 return floatValue(float64(x)) 145 case uint16: 146 return floatValue(float64(x)) 147 case uint32: 148 return floatValue(float64(x)) 149 case uint64: 150 return floatValue(float64(x)) 151 case uintptr: 152 return floatValue(float64(x)) 153 case unsafe.Pointer: 154 return floatValue(float64(uintptr(x))) 155 case float32: 156 return floatValue(float64(x)) 157 case float64: 158 return floatValue(x) 159 case string: 160 return makeValue(stringVal(x)) 161 case []interface{}: 162 a := arrayConstructor.New(len(x)) 163 for i, s := range x { 164 a.SetIndex(i, s) 165 } 166 return a 167 case map[string]interface{}: 168 o := objectConstructor.New() 169 for k, v := range x { 170 o.Set(k, v) 171 } 172 return o 173 default: 174 panic("ValueOf: invalid value") 175 } 176 } 177 178 func stringVal(x string) ref 179 180 // Type represents the JavaScript type of a Value. 181 type Type int 182 183 const ( 184 TypeUndefined Type = iota 185 TypeNull 186 TypeBoolean 187 TypeNumber 188 TypeString 189 TypeSymbol 190 TypeObject 191 TypeFunction 192 ) 193 194 func (t Type) String() string { 195 switch t { 196 case TypeUndefined: 197 return "undefined" 198 case TypeNull: 199 return "null" 200 case TypeBoolean: 201 return "boolean" 202 case TypeNumber: 203 return "number" 204 case TypeString: 205 return "string" 206 case TypeSymbol: 207 return "symbol" 208 case TypeObject: 209 return "object" 210 case TypeFunction: 211 return "function" 212 default: 213 panic("bad type") 214 } 215 } 216 217 func (t Type) isObject() bool { 218 return t == TypeObject || t == TypeFunction 219 } 220 221 // Type returns the JavaScript type of the value v. It is similar to JavaScript's typeof operator, 222 // except that it returns TypeNull instead of TypeObject for null. 223 func (v Value) Type() Type { 224 switch v.ref { 225 case valueUndefined.ref: 226 return TypeUndefined 227 case valueNull.ref: 228 return TypeNull 229 case valueTrue.ref, valueFalse.ref: 230 return TypeBoolean 231 } 232 if v.isNumber() { 233 return TypeNumber 234 } 235 typeFlag := v.ref >> 32 & 3 236 switch typeFlag { 237 case 1: 238 return TypeString 239 case 2: 240 return TypeSymbol 241 case 3: 242 return TypeFunction 243 default: 244 return TypeObject 245 } 246 } 247 248 // Get returns the JavaScript property p of value v. 249 // It panics if v is not a JavaScript object. 250 func (v Value) Get(p string) Value { 251 if vType := v.Type(); !vType.isObject() { 252 panic(&ValueError{"Value.Get", vType}) 253 } 254 return makeValue(valueGet(v.ref, p)) 255 } 256 257 func valueGet(v ref, p string) ref 258 259 // Set sets the JavaScript property p of value v to ValueOf(x). 260 // It panics if v is not a JavaScript object. 261 func (v Value) Set(p string, x interface{}) { 262 if vType := v.Type(); !vType.isObject() { 263 panic(&ValueError{"Value.Set", vType}) 264 } 265 valueSet(v.ref, p, ValueOf(x).ref) 266 } 267 268 func valueSet(v ref, p string, x ref) 269 270 // Index returns JavaScript index i of value v. 271 // It panics if v is not a JavaScript object. 272 func (v Value) Index(i int) Value { 273 if vType := v.Type(); !vType.isObject() { 274 panic(&ValueError{"Value.Index", vType}) 275 } 276 return makeValue(valueIndex(v.ref, i)) 277 } 278 279 func valueIndex(v ref, i int) ref 280 281 // SetIndex sets the JavaScript index i of value v to ValueOf(x). 282 // It panics if v is not a JavaScript object. 283 func (v Value) SetIndex(i int, x interface{}) { 284 if vType := v.Type(); !vType.isObject() { 285 panic(&ValueError{"Value.SetIndex", vType}) 286 } 287 valueSetIndex(v.ref, i, ValueOf(x).ref) 288 } 289 290 func valueSetIndex(v ref, i int, x ref) 291 292 func makeArgs(args []interface{}) []ref { 293 argVals := make([]ref, len(args)) 294 for i, arg := range args { 295 argVals[i] = ValueOf(arg).ref 296 } 297 return argVals 298 } 299 300 // Length returns the JavaScript property "length" of v. 301 // It panics if v is not a JavaScript object. 302 func (v Value) Length() int { 303 if vType := v.Type(); !vType.isObject() { 304 panic(&ValueError{"Value.SetIndex", vType}) 305 } 306 return valueLength(v.ref) 307 } 308 309 func valueLength(v ref) int 310 311 // Call does a JavaScript call to the method m of value v with the given arguments. 312 // It panics if v has no method m. 313 // The arguments get mapped to JavaScript values according to the ValueOf function. 314 func (v Value) Call(m string, args ...interface{}) Value { 315 res, ok := valueCall(v.ref, m, makeArgs(args)) 316 if !ok { 317 if vType := v.Type(); !vType.isObject() { // check here to avoid overhead in success case 318 panic(&ValueError{"Value.Call", vType}) 319 } 320 if propType := v.Get(m).Type(); propType != TypeFunction { 321 panic("syscall/js: Value.Call: property " + m + " is not a function, got " + propType.String()) 322 } 323 panic(Error{makeValue(res)}) 324 } 325 return makeValue(res) 326 } 327 328 func valueCall(v ref, m string, args []ref) (ref, bool) 329 330 // Invoke does a JavaScript call of the value v with the given arguments. 331 // It panics if v is not a JavaScript function. 332 // The arguments get mapped to JavaScript values according to the ValueOf function. 333 func (v Value) Invoke(args ...interface{}) Value { 334 res, ok := valueInvoke(v.ref, makeArgs(args)) 335 if !ok { 336 if vType := v.Type(); vType != TypeFunction { // check here to avoid overhead in success case 337 panic(&ValueError{"Value.Invoke", vType}) 338 } 339 panic(Error{makeValue(res)}) 340 } 341 return makeValue(res) 342 } 343 344 func valueInvoke(v ref, args []ref) (ref, bool) 345 346 // New uses JavaScript's "new" operator with value v as constructor and the given arguments. 347 // It panics if v is not a JavaScript function. 348 // The arguments get mapped to JavaScript values according to the ValueOf function. 349 func (v Value) New(args ...interface{}) Value { 350 res, ok := valueNew(v.ref, makeArgs(args)) 351 if !ok { 352 if vType := v.Type(); vType != TypeFunction { // check here to avoid overhead in success case 353 panic(&ValueError{"Value.Invoke", vType}) 354 } 355 panic(Error{makeValue(res)}) 356 } 357 return makeValue(res) 358 } 359 360 func valueNew(v ref, args []ref) (ref, bool) 361 362 func (v Value) isNumber() bool { 363 return v.ref == valueZero.ref || 364 v.ref == valueNaN.ref || 365 (v.ref != valueUndefined.ref && v.ref>>32&nanHead != nanHead) 366 } 367 368 func (v Value) float(method string) float64 { 369 if !v.isNumber() { 370 panic(&ValueError{method, v.Type()}) 371 } 372 if v.ref == valueZero.ref { 373 return 0 374 } 375 return *(*float64)(unsafe.Pointer(&v.ref)) 376 } 377 378 // Float returns the value v as a float64. 379 // It panics if v is not a JavaScript number. 380 func (v Value) Float() float64 { 381 return v.float("Value.Float") 382 } 383 384 // Int returns the value v truncated to an int. 385 // It panics if v is not a JavaScript number. 386 func (v Value) Int() int { 387 return int(v.float("Value.Int")) 388 } 389 390 // Bool returns the value v as a bool. 391 // It panics if v is not a JavaScript boolean. 392 func (v Value) Bool() bool { 393 switch v.ref { 394 case valueTrue.ref: 395 return true 396 case valueFalse.ref: 397 return false 398 default: 399 panic(&ValueError{"Value.Bool", v.Type()}) 400 } 401 } 402 403 // Truthy returns the JavaScript "truthiness" of the value v. In JavaScript, 404 // false, 0, "", null, undefined, and NaN are "falsy", and everything else is 405 // "truthy". See https://developer.mozilla.org/en-US/docs/Glossary/Truthy. 406 func (v Value) Truthy() bool { 407 switch v.Type() { 408 case TypeUndefined, TypeNull: 409 return false 410 case TypeBoolean: 411 return v.Bool() 412 case TypeNumber: 413 return v.ref != valueNaN.ref && v.ref != valueZero.ref 414 case TypeString: 415 return v.String() != "" 416 case TypeSymbol, TypeFunction, TypeObject: 417 return true 418 default: 419 panic("bad type") 420 } 421 } 422 423 // String returns the value v as a string. 424 // String is a special case because of Go's String method convention. Unlike the other getters, 425 // it does not panic if v's Type is not TypeString. Instead, it returns a string of the form "<T>" 426 // or "<T: V>" where T is v's type and V is a string representation of v's value. 427 func (v Value) String() string { 428 switch v.Type() { 429 case TypeString: 430 return jsString(v.ref) 431 case TypeUndefined: 432 return "<undefined>" 433 case TypeNull: 434 return "<null>" 435 case TypeBoolean: 436 return "<boolean: " + jsString(v.ref) + ">" 437 case TypeNumber: 438 return "<number: " + jsString(v.ref) + ">" 439 case TypeSymbol: 440 return "<symbol>" 441 case TypeObject: 442 return "<object>" 443 case TypeFunction: 444 return "<function>" 445 default: 446 panic("bad type") 447 } 448 } 449 450 func jsString(v ref) string { 451 str, length := valuePrepareString(v) 452 b := make([]byte, length) 453 valueLoadString(str, b) 454 return string(b) 455 } 456 457 func valuePrepareString(v ref) (ref, int) 458 459 func valueLoadString(v ref, b []byte) 460 461 // InstanceOf reports whether v is an instance of type t according to JavaScript's instanceof operator. 462 func (v Value) InstanceOf(t Value) bool { 463 return valueInstanceOf(v.ref, t.ref) 464 } 465 466 func valueInstanceOf(v ref, t ref) bool 467 468 // A ValueError occurs when a Value method is invoked on 469 // a Value that does not support it. Such cases are documented 470 // in the description of each method. 471 type ValueError struct { 472 Method string 473 Type Type 474 } 475 476 func (e *ValueError) Error() string { 477 return "syscall/js: call of " + e.Method + " on " + e.Type.String() 478 } 479 480 // CopyBytesToGo copies bytes from the Uint8Array src to dst. 481 // It returns the number of bytes copied, which will be the minimum of the lengths of src and dst. 482 // CopyBytesToGo panics if src is not an Uint8Array. 483 func CopyBytesToGo(dst []byte, src Value) int { 484 n, ok := copyBytesToGo(dst, src.ref) 485 if !ok { 486 panic("syscall/js: CopyBytesToGo: expected src to be an Uint8Array") 487 } 488 return n 489 } 490 491 func copyBytesToGo(dst []byte, src ref) (int, bool) 492 493 // CopyBytesToJS copies bytes from src to the Uint8Array dst. 494 // It returns the number of bytes copied, which will be the minimum of the lengths of src and dst. 495 // CopyBytesToJS panics if dst is not an Uint8Array. 496 func CopyBytesToJS(dst Value, src []byte) int { 497 n, ok := copyBytesToJS(dst.ref, src) 498 if !ok { 499 panic("syscall/js: CopyBytesToJS: expected dst to be an Uint8Array") 500 } 501 return n 502 } 503 504 func copyBytesToJS(dst ref, src []byte) (int, bool) 505
View as plain text