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