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