Source file src/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  //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 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  //go:wasmimport gojs syscall/js.stringVal
   214  func stringVal(x string) ref
   215  
   216  // Type represents the JavaScript type of a Value.
   217  type Type int
   218  
   219  const (
   220  	TypeUndefined Type = iota
   221  	TypeNull
   222  	TypeBoolean
   223  	TypeNumber
   224  	TypeString
   225  	TypeSymbol
   226  	TypeObject
   227  	TypeFunction
   228  )
   229  
   230  func (t Type) String() string {
   231  	switch t {
   232  	case TypeUndefined:
   233  		return "undefined"
   234  	case TypeNull:
   235  		return "null"
   236  	case TypeBoolean:
   237  		return "boolean"
   238  	case TypeNumber:
   239  		return "number"
   240  	case TypeString:
   241  		return "string"
   242  	case TypeSymbol:
   243  		return "symbol"
   244  	case TypeObject:
   245  		return "object"
   246  	case TypeFunction:
   247  		return "function"
   248  	default:
   249  		panic("bad type")
   250  	}
   251  }
   252  
   253  func (t Type) isObject() bool {
   254  	return t == TypeObject || t == TypeFunction
   255  }
   256  
   257  // Type returns the JavaScript type of the value v. It is similar to JavaScript's typeof operator,
   258  // except that it returns TypeNull instead of TypeObject for null.
   259  func (v Value) Type() Type {
   260  	switch v.ref {
   261  	case valueUndefined.ref:
   262  		return TypeUndefined
   263  	case valueNull.ref:
   264  		return TypeNull
   265  	case valueTrue.ref, valueFalse.ref:
   266  		return TypeBoolean
   267  	}
   268  	if v.isNumber() {
   269  		return TypeNumber
   270  	}
   271  	typeFlag := (v.ref >> 32) & 7
   272  	switch typeFlag {
   273  	case typeFlagObject:
   274  		return TypeObject
   275  	case typeFlagString:
   276  		return TypeString
   277  	case typeFlagSymbol:
   278  		return TypeSymbol
   279  	case typeFlagFunction:
   280  		return TypeFunction
   281  	default:
   282  		panic("bad type flag")
   283  	}
   284  }
   285  
   286  // Get returns the JavaScript property p of value v.
   287  // It panics if v is not a JavaScript object.
   288  func (v Value) Get(p string) Value {
   289  	if vType := v.Type(); !vType.isObject() {
   290  		panic(&ValueError{"Value.Get", vType})
   291  	}
   292  	r := makeValue(valueGet(v.ref, p))
   293  	runtime.KeepAlive(v)
   294  	return r
   295  }
   296  
   297  //go:wasmimport gojs syscall/js.valueGet
   298  func valueGet(v ref, p string) ref
   299  
   300  // Set sets the JavaScript property p of value v to ValueOf(x).
   301  // It panics if v is not a JavaScript object.
   302  func (v Value) Set(p string, x any) {
   303  	if vType := v.Type(); !vType.isObject() {
   304  		panic(&ValueError{"Value.Set", vType})
   305  	}
   306  	xv := ValueOf(x)
   307  	valueSet(v.ref, p, xv.ref)
   308  	runtime.KeepAlive(v)
   309  	runtime.KeepAlive(xv)
   310  }
   311  
   312  //go:wasmimport gojs syscall/js.valueSet
   313  func valueSet(v ref, p string, x ref)
   314  
   315  // Delete deletes the JavaScript property p of value v.
   316  // It panics if v is not a JavaScript object.
   317  func (v Value) Delete(p string) {
   318  	if vType := v.Type(); !vType.isObject() {
   319  		panic(&ValueError{"Value.Delete", vType})
   320  	}
   321  	valueDelete(v.ref, p)
   322  	runtime.KeepAlive(v)
   323  }
   324  
   325  //go:wasmimport gojs syscall/js.valueDelete
   326  func valueDelete(v ref, p string)
   327  
   328  // Index returns JavaScript index i of value v.
   329  // It panics if v is not a JavaScript object.
   330  func (v Value) Index(i int) Value {
   331  	if vType := v.Type(); !vType.isObject() {
   332  		panic(&ValueError{"Value.Index", vType})
   333  	}
   334  	r := makeValue(valueIndex(v.ref, i))
   335  	runtime.KeepAlive(v)
   336  	return r
   337  }
   338  
   339  //go:wasmimport gojs syscall/js.valueIndex
   340  func valueIndex(v ref, i int) ref
   341  
   342  // SetIndex sets the JavaScript index i of value v to ValueOf(x).
   343  // It panics if v is not a JavaScript object.
   344  func (v Value) SetIndex(i int, x any) {
   345  	if vType := v.Type(); !vType.isObject() {
   346  		panic(&ValueError{"Value.SetIndex", vType})
   347  	}
   348  	xv := ValueOf(x)
   349  	valueSetIndex(v.ref, i, xv.ref)
   350  	runtime.KeepAlive(v)
   351  	runtime.KeepAlive(xv)
   352  }
   353  
   354  //go:wasmimport gojs syscall/js.valueSetIndex
   355  func valueSetIndex(v ref, i int, x ref)
   356  
   357  func makeArgs(args []any) ([]Value, []ref) {
   358  	argVals := make([]Value, len(args))
   359  	argRefs := make([]ref, len(args))
   360  	for i, arg := range args {
   361  		v := ValueOf(arg)
   362  		argVals[i] = v
   363  		argRefs[i] = v.ref
   364  	}
   365  	return argVals, argRefs
   366  }
   367  
   368  // Length returns the JavaScript property "length" of v.
   369  // It panics if v is not a JavaScript object.
   370  func (v Value) Length() int {
   371  	if vType := v.Type(); !vType.isObject() {
   372  		panic(&ValueError{"Value.SetIndex", vType})
   373  	}
   374  	r := valueLength(v.ref)
   375  	runtime.KeepAlive(v)
   376  	return r
   377  }
   378  
   379  //go:wasmimport gojs syscall/js.valueLength
   380  func valueLength(v ref) int
   381  
   382  // Call does a JavaScript call to the method m of value v with the given arguments.
   383  // It panics if v has no method m.
   384  // The arguments get mapped to JavaScript values according to the ValueOf function.
   385  func (v Value) Call(m string, args ...any) Value {
   386  	argVals, argRefs := makeArgs(args)
   387  	res, ok := valueCall(v.ref, m, argRefs)
   388  	runtime.KeepAlive(v)
   389  	runtime.KeepAlive(argVals)
   390  	if !ok {
   391  		if vType := v.Type(); !vType.isObject() { // check here to avoid overhead in success case
   392  			panic(&ValueError{"Value.Call", vType})
   393  		}
   394  		if propType := v.Get(m).Type(); propType != TypeFunction {
   395  			panic("syscall/js: Value.Call: property " + m + " is not a function, got " + propType.String())
   396  		}
   397  		panic(Error{makeValue(res)})
   398  	}
   399  	return makeValue(res)
   400  }
   401  
   402  //go:wasmimport gojs syscall/js.valueCall
   403  //go:nosplit
   404  func valueCall(v ref, m string, args []ref) (ref, bool)
   405  
   406  // Invoke does a JavaScript call of the value v with the given arguments.
   407  // It panics if v is not a JavaScript function.
   408  // The arguments get mapped to JavaScript values according to the ValueOf function.
   409  func (v Value) Invoke(args ...any) Value {
   410  	argVals, argRefs := makeArgs(args)
   411  	res, ok := valueInvoke(v.ref, argRefs)
   412  	runtime.KeepAlive(v)
   413  	runtime.KeepAlive(argVals)
   414  	if !ok {
   415  		if vType := v.Type(); vType != TypeFunction { // check here to avoid overhead in success case
   416  			panic(&ValueError{"Value.Invoke", vType})
   417  		}
   418  		panic(Error{makeValue(res)})
   419  	}
   420  	return makeValue(res)
   421  }
   422  
   423  //go:wasmimport gojs syscall/js.valueInvoke
   424  func 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.
   429  func (v Value) New(args ...any) 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  
   443  //go:wasmimport gojs syscall/js.valueNew
   444  func valueNew(v ref, args []ref) (ref, bool)
   445  
   446  func (v Value) isNumber() bool {
   447  	return v.ref == valueZero.ref ||
   448  		v.ref == valueNaN.ref ||
   449  		(v.ref != valueUndefined.ref && (v.ref>>32)&nanHead != nanHead)
   450  }
   451  
   452  func (v Value) float(method string) float64 {
   453  	if !v.isNumber() {
   454  		panic(&ValueError{method, v.Type()})
   455  	}
   456  	if v.ref == valueZero.ref {
   457  		return 0
   458  	}
   459  	return *(*float64)(unsafe.Pointer(&v.ref))
   460  }
   461  
   462  // Float returns the value v as a float64.
   463  // It panics if v is not a JavaScript number.
   464  func (v Value) Float() float64 {
   465  	return v.float("Value.Float")
   466  }
   467  
   468  // Int returns the value v truncated to an int.
   469  // It panics if v is not a JavaScript number.
   470  func (v Value) Int() int {
   471  	return int(v.float("Value.Int"))
   472  }
   473  
   474  // Bool returns the value v as a bool.
   475  // It panics if v is not a JavaScript boolean.
   476  func (v Value) Bool() bool {
   477  	switch v.ref {
   478  	case valueTrue.ref:
   479  		return true
   480  	case valueFalse.ref:
   481  		return false
   482  	default:
   483  		panic(&ValueError{"Value.Bool", v.Type()})
   484  	}
   485  }
   486  
   487  // Truthy returns the JavaScript "truthiness" of the value v. In JavaScript,
   488  // false, 0, "", null, undefined, and NaN are "falsy", and everything else is
   489  // "truthy". See https://developer.mozilla.org/en-US/docs/Glossary/Truthy.
   490  func (v Value) Truthy() bool {
   491  	switch v.Type() {
   492  	case TypeUndefined, TypeNull:
   493  		return false
   494  	case TypeBoolean:
   495  		return v.Bool()
   496  	case TypeNumber:
   497  		return v.ref != valueNaN.ref && v.ref != valueZero.ref
   498  	case TypeString:
   499  		return v.String() != ""
   500  	case TypeSymbol, TypeFunction, TypeObject:
   501  		return true
   502  	default:
   503  		panic("bad type")
   504  	}
   505  }
   506  
   507  // String returns the value v as a string.
   508  // String is a special case because of Go's String method convention. Unlike the other getters,
   509  // it does not panic if v's Type is not TypeString. Instead, it returns a string of the form "<T>"
   510  // or "<T: V>" where T is v's type and V is a string representation of v's value.
   511  func (v Value) String() string {
   512  	switch v.Type() {
   513  	case TypeString:
   514  		return jsString(v)
   515  	case TypeUndefined:
   516  		return "<undefined>"
   517  	case TypeNull:
   518  		return "<null>"
   519  	case TypeBoolean:
   520  		return "<boolean: " + jsString(v) + ">"
   521  	case TypeNumber:
   522  		return "<number: " + jsString(v) + ">"
   523  	case TypeSymbol:
   524  		return "<symbol>"
   525  	case TypeObject:
   526  		return "<object>"
   527  	case TypeFunction:
   528  		return "<function>"
   529  	default:
   530  		panic("bad type")
   531  	}
   532  }
   533  
   534  func jsString(v Value) string {
   535  	str, length := valuePrepareString(v.ref)
   536  	runtime.KeepAlive(v)
   537  	b := make([]byte, length)
   538  	valueLoadString(str, b)
   539  	finalizeRef(str)
   540  	return string(b)
   541  }
   542  
   543  //go:wasmimport gojs syscall/js.valuePrepareString
   544  func valuePrepareString(v ref) (ref, int)
   545  
   546  //go:wasmimport gojs syscall/js.valueLoadString
   547  func valueLoadString(v ref, b []byte)
   548  
   549  // InstanceOf reports whether v is an instance of type t according to JavaScript's instanceof operator.
   550  func (v Value) InstanceOf(t Value) bool {
   551  	r := valueInstanceOf(v.ref, t.ref)
   552  	runtime.KeepAlive(v)
   553  	runtime.KeepAlive(t)
   554  	return r
   555  }
   556  
   557  //go:wasmimport gojs syscall/js.valueInstanceOf
   558  func valueInstanceOf(v ref, t ref) bool
   559  
   560  // A ValueError occurs when a Value method is invoked on
   561  // a Value that does not support it. Such cases are documented
   562  // in the description of each method.
   563  type ValueError struct {
   564  	Method string
   565  	Type   Type
   566  }
   567  
   568  func (e *ValueError) Error() string {
   569  	return "syscall/js: call of " + e.Method + " on " + e.Type.String()
   570  }
   571  
   572  // CopyBytesToGo copies bytes from src to dst.
   573  // It panics if src is not a Uint8Array or Uint8ClampedArray.
   574  // It returns the number of bytes copied, which will be the minimum of the lengths of src and dst.
   575  func CopyBytesToGo(dst []byte, src Value) int {
   576  	n, ok := copyBytesToGo(dst, src.ref)
   577  	runtime.KeepAlive(src)
   578  	if !ok {
   579  		panic("syscall/js: CopyBytesToGo: expected src to be a Uint8Array or Uint8ClampedArray")
   580  	}
   581  	return n
   582  }
   583  
   584  //go:wasmimport gojs syscall/js.copyBytesToGo
   585  func copyBytesToGo(dst []byte, src ref) (int, bool)
   586  
   587  // CopyBytesToJS copies bytes from src to dst.
   588  // It panics if dst is not a Uint8Array or Uint8ClampedArray.
   589  // It returns the number of bytes copied, which will be the minimum of the lengths of src and dst.
   590  func CopyBytesToJS(dst Value, src []byte) int {
   591  	n, ok := copyBytesToJS(dst.ref, src)
   592  	runtime.KeepAlive(dst)
   593  	if !ok {
   594  		panic("syscall/js: CopyBytesToJS: expected dst to be a Uint8Array or Uint8ClampedArray")
   595  	}
   596  	return n
   597  }
   598  
   599  //go:wasmimport gojs syscall/js.copyBytesToJS
   600  func copyBytesToJS(dst ref, src []byte) (int, bool)
   601  

View as plain text