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