...

Source file src/pkg/syscall/js/js.go

     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