...

Source file src/syscall/js/js.go

Documentation: syscall/js

     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  func finalizeRef(r ref)
    62  
    63  func predefValue(id uint32, typeFlag byte) Value {
    64  	return Value{ref: (nanHead|ref(typeFlag))<<32 | ref(id)}
    65  }
    66  
    67  func floatValue(f float64) Value {
    68  	if f == 0 {
    69  		return valueZero
    70  	}
    71  	if f != f {
    72  		return valueNaN
    73  	}
    74  	return Value{ref: *(*ref)(unsafe.Pointer(&f))}
    75  }
    76  
    77  // Error wraps a JavaScript error.
    78  type Error struct {
    79  	// Value is the underlying JavaScript error value.
    80  	Value
    81  }
    82  
    83  // Error implements the error interface.
    84  func (e Error) Error() string {
    85  	return "JavaScript error: " + e.Get("message").String()
    86  }
    87  
    88  var (
    89  	valueUndefined = Value{ref: 0}
    90  	valueNaN       = predefValue(0, typeFlagNone)
    91  	valueZero      = predefValue(1, typeFlagNone)
    92  	valueNull      = predefValue(2, typeFlagNone)
    93  	valueTrue      = predefValue(3, typeFlagNone)
    94  	valueFalse     = predefValue(4, typeFlagNone)
    95  	valueGlobal    = predefValue(5, typeFlagObject)
    96  	jsGo           = predefValue(6, typeFlagObject) // instance of the Go class in JavaScript
    97  
    98  	objectConstructor = valueGlobal.Get("Object")
    99  	arrayConstructor  = valueGlobal.Get("Array")
   100  )
   101  
   102  // Equal reports whether v and w are equal according to JavaScript's === operator.
   103  func (v Value) Equal(w Value) bool {
   104  	return v.ref == w.ref && v.ref != valueNaN.ref
   105  }
   106  
   107  // Undefined returns the JavaScript value "undefined".
   108  func Undefined() Value {
   109  	return valueUndefined
   110  }
   111  
   112  // IsUndefined reports whether v is the JavaScript value "undefined".
   113  func (v Value) IsUndefined() bool {
   114  	return v.ref == valueUndefined.ref
   115  }
   116  
   117  // Null returns the JavaScript value "null".
   118  func Null() Value {
   119  	return valueNull
   120  }
   121  
   122  // IsNull reports whether v is the JavaScript value "null".
   123  func (v Value) IsNull() bool {
   124  	return v.ref == valueNull.ref
   125  }
   126  
   127  // IsNaN reports whether v is the JavaScript value "NaN".
   128  func (v Value) IsNaN() bool {
   129  	return v.ref == valueNaN.ref
   130  }
   131  
   132  // Global returns the JavaScript global object, usually "window" or "global".
   133  func Global() Value {
   134  	return valueGlobal
   135  }
   136  
   137  // ValueOf returns x as a JavaScript value:
   138  //
   139  //	| Go                     | JavaScript             |
   140  //	| ---------------------- | ---------------------- |
   141  //	| js.Value               | [its value]            |
   142  //	| js.Func                | function               |
   143  //	| nil                    | null                   |
   144  //	| bool                   | boolean                |
   145  //	| integers and floats    | number                 |
   146  //	| string                 | string                 |
   147  //	| []interface{}          | new array              |
   148  //	| map[string]interface{} | new object             |
   149  //
   150  // Panics if x is not one of the expected types.
   151  func ValueOf(x any) Value {
   152  	switch x := x.(type) {
   153  	case Value:
   154  		return x
   155  	case Func:
   156  		return x.Value
   157  	case nil:
   158  		return valueNull
   159  	case bool:
   160  		if x {
   161  			return valueTrue
   162  		} else {
   163  			return valueFalse
   164  		}
   165  	case int:
   166  		return floatValue(float64(x))
   167  	case int8:
   168  		return floatValue(float64(x))
   169  	case int16:
   170  		return floatValue(float64(x))
   171  	case int32:
   172  		return floatValue(float64(x))
   173  	case int64:
   174  		return floatValue(float64(x))
   175  	case uint:
   176  		return floatValue(float64(x))
   177  	case uint8:
   178  		return floatValue(float64(x))
   179  	case uint16:
   180  		return floatValue(float64(x))
   181  	case uint32:
   182  		return floatValue(float64(x))
   183  	case uint64:
   184  		return floatValue(float64(x))
   185  	case uintptr:
   186  		return floatValue(float64(x))
   187  	case unsafe.Pointer:
   188  		return floatValue(float64(uintptr(x)))
   189  	case float32:
   190  		return floatValue(float64(x))
   191  	case float64:
   192  		return floatValue(x)
   193  	case string:
   194  		return makeValue(stringVal(x))
   195  	case []any:
   196  		a := arrayConstructor.New(len(x))
   197  		for i, s := range x {
   198  			a.SetIndex(i, s)
   199  		}
   200  		return a
   201  	case map[string]any:
   202  		o := objectConstructor.New()
   203  		for k, v := range x {
   204  			o.Set(k, v)
   205  		}
   206  		return o
   207  	default:
   208  		panic("ValueOf: invalid value")
   209  	}
   210  }
   211  
   212  func stringVal(x string) ref
   213  
   214  // Type represents the JavaScript type of a Value.
   215  type Type int
   216  
   217  const (
   218  	TypeUndefined Type = iota
   219  	TypeNull
   220  	TypeBoolean
   221  	TypeNumber
   222  	TypeString
   223  	TypeSymbol
   224  	TypeObject
   225  	TypeFunction
   226  )
   227  
   228  func (t Type) String() string {
   229  	switch t {
   230  	case TypeUndefined:
   231  		return "undefined"
   232  	case TypeNull:
   233  		return "null"
   234  	case TypeBoolean:
   235  		return "boolean"
   236  	case TypeNumber:
   237  		return "number"
   238  	case TypeString:
   239  		return "string"
   240  	case TypeSymbol:
   241  		return "symbol"
   242  	case TypeObject:
   243  		return "object"
   244  	case TypeFunction:
   245  		return "function"
   246  	default:
   247  		panic("bad type")
   248  	}
   249  }
   250  
   251  func (t Type) isObject() bool {
   252  	return t == TypeObject || t == TypeFunction
   253  }
   254  
   255  // Type returns the JavaScript type of the value v. It is similar to JavaScript's typeof operator,
   256  // except that it returns TypeNull instead of TypeObject for null.
   257  func (v Value) Type() Type {
   258  	switch v.ref {
   259  	case valueUndefined.ref:
   260  		return TypeUndefined
   261  	case valueNull.ref:
   262  		return TypeNull
   263  	case valueTrue.ref, valueFalse.ref:
   264  		return TypeBoolean
   265  	}
   266  	if v.isNumber() {
   267  		return TypeNumber
   268  	}
   269  	typeFlag := (v.ref >> 32) & 7
   270  	switch typeFlag {
   271  	case typeFlagObject:
   272  		return TypeObject
   273  	case typeFlagString:
   274  		return TypeString
   275  	case typeFlagSymbol:
   276  		return TypeSymbol
   277  	case typeFlagFunction:
   278  		return TypeFunction
   279  	default:
   280  		panic("bad type flag")
   281  	}
   282  }
   283  
   284  // Get returns the JavaScript property p of value v.
   285  // It panics if v is not a JavaScript object.
   286  func (v Value) Get(p string) Value {
   287  	if vType := v.Type(); !vType.isObject() {
   288  		panic(&ValueError{"Value.Get", vType})
   289  	}
   290  	r := makeValue(valueGet(v.ref, p))
   291  	runtime.KeepAlive(v)
   292  	return r
   293  }
   294  
   295  func valueGet(v ref, p string) ref
   296  
   297  // Set sets the JavaScript property p of value v to ValueOf(x).
   298  // It panics if v is not a JavaScript object.
   299  func (v Value) Set(p string, x any) {
   300  	if vType := v.Type(); !vType.isObject() {
   301  		panic(&ValueError{"Value.Set", vType})
   302  	}
   303  	xv := ValueOf(x)
   304  	valueSet(v.ref, p, xv.ref)
   305  	runtime.KeepAlive(v)
   306  	runtime.KeepAlive(xv)
   307  }
   308  
   309  func valueSet(v ref, p string, x ref)
   310  
   311  // Delete deletes the JavaScript property p of value v.
   312  // It panics if v is not a JavaScript object.
   313  func (v Value) Delete(p string) {
   314  	if vType := v.Type(); !vType.isObject() {
   315  		panic(&ValueError{"Value.Delete", vType})
   316  	}
   317  	valueDelete(v.ref, p)
   318  	runtime.KeepAlive(v)
   319  }
   320  
   321  func valueDelete(v ref, p string)
   322  
   323  // Index returns JavaScript index i of value v.
   324  // It panics if v is not a JavaScript object.
   325  func (v Value) Index(i int) Value {
   326  	if vType := v.Type(); !vType.isObject() {
   327  		panic(&ValueError{"Value.Index", vType})
   328  	}
   329  	r := makeValue(valueIndex(v.ref, i))
   330  	runtime.KeepAlive(v)
   331  	return r
   332  }
   333  
   334  func valueIndex(v ref, i int) ref
   335  
   336  // SetIndex sets the JavaScript index i of value v to ValueOf(x).
   337  // It panics if v is not a JavaScript object.
   338  func (v Value) SetIndex(i int, x any) {
   339  	if vType := v.Type(); !vType.isObject() {
   340  		panic(&ValueError{"Value.SetIndex", vType})
   341  	}
   342  	xv := ValueOf(x)
   343  	valueSetIndex(v.ref, i, xv.ref)
   344  	runtime.KeepAlive(v)
   345  	runtime.KeepAlive(xv)
   346  }
   347  
   348  func valueSetIndex(v ref, i int, x ref)
   349  
   350  func makeArgs(args []any) ([]Value, []ref) {
   351  	argVals := make([]Value, len(args))
   352  	argRefs := make([]ref, len(args))
   353  	for i, arg := range args {
   354  		v := ValueOf(arg)
   355  		argVals[i] = v
   356  		argRefs[i] = v.ref
   357  	}
   358  	return argVals, argRefs
   359  }
   360  
   361  // Length returns the JavaScript property "length" of v.
   362  // It panics if v is not a JavaScript object.
   363  func (v Value) Length() int {
   364  	if vType := v.Type(); !vType.isObject() {
   365  		panic(&ValueError{"Value.SetIndex", vType})
   366  	}
   367  	r := valueLength(v.ref)
   368  	runtime.KeepAlive(v)
   369  	return r
   370  }
   371  
   372  func valueLength(v ref) int
   373  
   374  // Call does a JavaScript call to the method m of value v with the given arguments.
   375  // It panics if v has no method m.
   376  // The arguments get mapped to JavaScript values according to the ValueOf function.
   377  func (v Value) Call(m string, args ...any) Value {
   378  	argVals, argRefs := makeArgs(args)
   379  	res, ok := valueCall(v.ref, m, argRefs)
   380  	runtime.KeepAlive(v)
   381  	runtime.KeepAlive(argVals)
   382  	if !ok {
   383  		if vType := v.Type(); !vType.isObject() { // check here to avoid overhead in success case
   384  			panic(&ValueError{"Value.Call", vType})
   385  		}
   386  		if propType := v.Get(m).Type(); propType != TypeFunction {
   387  			panic("syscall/js: Value.Call: property " + m + " is not a function, got " + propType.String())
   388  		}
   389  		panic(Error{makeValue(res)})
   390  	}
   391  	return makeValue(res)
   392  }
   393  
   394  func valueCall(v ref, m string, args []ref) (ref, bool)
   395  
   396  // Invoke does a JavaScript call of the value v with the given arguments.
   397  // It panics if v is not a JavaScript function.
   398  // The arguments get mapped to JavaScript values according to the ValueOf function.
   399  func (v Value) Invoke(args ...any) Value {
   400  	argVals, argRefs := makeArgs(args)
   401  	res, ok := valueInvoke(v.ref, argRefs)
   402  	runtime.KeepAlive(v)
   403  	runtime.KeepAlive(argVals)
   404  	if !ok {
   405  		if vType := v.Type(); vType != TypeFunction { // check here to avoid overhead in success case
   406  			panic(&ValueError{"Value.Invoke", vType})
   407  		}
   408  		panic(Error{makeValue(res)})
   409  	}
   410  	return makeValue(res)
   411  }
   412  
   413  func valueInvoke(v ref, args []ref) (ref, bool)
   414  
   415  // New uses JavaScript's "new" operator with value v as constructor and the given arguments.
   416  // It panics if v is not a JavaScript function.
   417  // The arguments get mapped to JavaScript values according to the ValueOf function.
   418  func (v Value) New(args ...any) Value {
   419  	argVals, argRefs := makeArgs(args)
   420  	res, ok := valueNew(v.ref, argRefs)
   421  	runtime.KeepAlive(v)
   422  	runtime.KeepAlive(argVals)
   423  	if !ok {
   424  		if vType := v.Type(); vType != TypeFunction { // check here to avoid overhead in success case
   425  			panic(&ValueError{"Value.Invoke", vType})
   426  		}
   427  		panic(Error{makeValue(res)})
   428  	}
   429  	return makeValue(res)
   430  }
   431  
   432  func valueNew(v ref, args []ref) (ref, bool)
   433  
   434  func (v Value) isNumber() bool {
   435  	return v.ref == valueZero.ref ||
   436  		v.ref == valueNaN.ref ||
   437  		(v.ref != valueUndefined.ref && (v.ref>>32)&nanHead != nanHead)
   438  }
   439  
   440  func (v Value) float(method string) float64 {
   441  	if !v.isNumber() {
   442  		panic(&ValueError{method, v.Type()})
   443  	}
   444  	if v.ref == valueZero.ref {
   445  		return 0
   446  	}
   447  	return *(*float64)(unsafe.Pointer(&v.ref))
   448  }
   449  
   450  // Float returns the value v as a float64.
   451  // It panics if v is not a JavaScript number.
   452  func (v Value) Float() float64 {
   453  	return v.float("Value.Float")
   454  }
   455  
   456  // Int returns the value v truncated to an int.
   457  // It panics if v is not a JavaScript number.
   458  func (v Value) Int() int {
   459  	return int(v.float("Value.Int"))
   460  }
   461  
   462  // Bool returns the value v as a bool.
   463  // It panics if v is not a JavaScript boolean.
   464  func (v Value) Bool() bool {
   465  	switch v.ref {
   466  	case valueTrue.ref:
   467  		return true
   468  	case valueFalse.ref:
   469  		return false
   470  	default:
   471  		panic(&ValueError{"Value.Bool", v.Type()})
   472  	}
   473  }
   474  
   475  // Truthy returns the JavaScript "truthiness" of the value v. In JavaScript,
   476  // false, 0, "", null, undefined, and NaN are "falsy", and everything else is
   477  // "truthy". See https://developer.mozilla.org/en-US/docs/Glossary/Truthy.
   478  func (v Value) Truthy() bool {
   479  	switch v.Type() {
   480  	case TypeUndefined, TypeNull:
   481  		return false
   482  	case TypeBoolean:
   483  		return v.Bool()
   484  	case TypeNumber:
   485  		return v.ref != valueNaN.ref && v.ref != valueZero.ref
   486  	case TypeString:
   487  		return v.String() != ""
   488  	case TypeSymbol, TypeFunction, TypeObject:
   489  		return true
   490  	default:
   491  		panic("bad type")
   492  	}
   493  }
   494  
   495  // String returns the value v as a string.
   496  // String is a special case because of Go's String method convention. Unlike the other getters,
   497  // it does not panic if v's Type is not TypeString. Instead, it returns a string of the form "<T>"
   498  // or "<T: V>" where T is v's type and V is a string representation of v's value.
   499  func (v Value) String() string {
   500  	switch v.Type() {
   501  	case TypeString:
   502  		return jsString(v)
   503  	case TypeUndefined:
   504  		return "<undefined>"
   505  	case TypeNull:
   506  		return "<null>"
   507  	case TypeBoolean:
   508  		return "<boolean: " + jsString(v) + ">"
   509  	case TypeNumber:
   510  		return "<number: " + jsString(v) + ">"
   511  	case TypeSymbol:
   512  		return "<symbol>"
   513  	case TypeObject:
   514  		return "<object>"
   515  	case TypeFunction:
   516  		return "<function>"
   517  	default:
   518  		panic("bad type")
   519  	}
   520  }
   521  
   522  func jsString(v Value) string {
   523  	str, length := valuePrepareString(v.ref)
   524  	runtime.KeepAlive(v)
   525  	b := make([]byte, length)
   526  	valueLoadString(str, b)
   527  	finalizeRef(str)
   528  	return string(b)
   529  }
   530  
   531  func valuePrepareString(v ref) (ref, int)
   532  
   533  func valueLoadString(v ref, b []byte)
   534  
   535  // InstanceOf reports whether v is an instance of type t according to JavaScript's instanceof operator.
   536  func (v Value) InstanceOf(t Value) bool {
   537  	r := valueInstanceOf(v.ref, t.ref)
   538  	runtime.KeepAlive(v)
   539  	runtime.KeepAlive(t)
   540  	return r
   541  }
   542  
   543  func valueInstanceOf(v ref, t ref) bool
   544  
   545  // A ValueError occurs when a Value method is invoked on
   546  // a Value that does not support it. Such cases are documented
   547  // in the description of each method.
   548  type ValueError struct {
   549  	Method string
   550  	Type   Type
   551  }
   552  
   553  func (e *ValueError) Error() string {
   554  	return "syscall/js: call of " + e.Method + " on " + e.Type.String()
   555  }
   556  
   557  // CopyBytesToGo copies bytes from src to dst.
   558  // It panics if src is not an Uint8Array or Uint8ClampedArray.
   559  // It returns the number of bytes copied, which will be the minimum of the lengths of src and dst.
   560  func CopyBytesToGo(dst []byte, src Value) int {
   561  	n, ok := copyBytesToGo(dst, src.ref)
   562  	runtime.KeepAlive(src)
   563  	if !ok {
   564  		panic("syscall/js: CopyBytesToGo: expected src to be an Uint8Array or Uint8ClampedArray")
   565  	}
   566  	return n
   567  }
   568  
   569  func copyBytesToGo(dst []byte, src ref) (int, bool)
   570  
   571  // CopyBytesToJS copies bytes from src to dst.
   572  // It panics if dst is not an Uint8Array or Uint8ClampedArray.
   573  // It returns the number of bytes copied, which will be the minimum of the lengths of src and dst.
   574  func CopyBytesToJS(dst Value, src []byte) int {
   575  	n, ok := copyBytesToJS(dst.ref, src)
   576  	runtime.KeepAlive(dst)
   577  	if !ok {
   578  		panic("syscall/js: CopyBytesToJS: expected dst to be an Uint8Array or Uint8ClampedArray")
   579  	}
   580  	return n
   581  }
   582  
   583  func copyBytesToJS(dst ref, src []byte) (int, bool)
   584  

View as plain text