Black Lives Matter. Support the Equal Justice Initiative.

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  // +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  // Wrapper is implemented by types that are backed by a JavaScript value.
    31  type 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.
    38  type 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  
    44  const (
    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.
    54  func (v Value) JSValue() Value {
    55  	return v
    56  }
    57  
    58  func 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  
    72  func finalizeRef(r ref)
    73  
    74  func predefValue(id uint32, typeFlag byte) Value {
    75  	return Value{ref: (nanHead|ref(typeFlag))<<32 | ref(id)}
    76  }
    77  
    78  func 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.
    89  type Error struct {
    90  	// Value is the underlying JavaScript error value.
    91  	Value
    92  }
    93  
    94  // Error implements the error interface.
    95  func (e Error) Error() string {
    96  	return "JavaScript error: " + e.Get("message").String()
    97  }
    98  
    99  var (
   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.
   114  func (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".
   119  func Undefined() Value {
   120  	return valueUndefined
   121  }
   122  
   123  // IsUndefined reports whether v is the JavaScript value "undefined".
   124  func (v Value) IsUndefined() bool {
   125  	return v.ref == valueUndefined.ref
   126  }
   127  
   128  // Null returns the JavaScript value "null".
   129  func Null() Value {
   130  	return valueNull
   131  }
   132  
   133  // IsNull reports whether v is the JavaScript value "null".
   134  func (v Value) IsNull() bool {
   135  	return v.ref == valueNull.ref
   136  }
   137  
   138  // IsNaN reports whether v is the JavaScript value "NaN".
   139  func (v Value) IsNaN() bool {
   140  	return v.ref == valueNaN.ref
   141  }
   142  
   143  // Global returns the JavaScript global object, usually "window" or "global".
   144  func 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.
   162  func 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  
   223  func stringVal(x string) ref
   224  
   225  // Type represents the JavaScript type of a Value.
   226  type Type int
   227  
   228  const (
   229  	TypeUndefined Type = iota
   230  	TypeNull
   231  	TypeBoolean
   232  	TypeNumber
   233  	TypeString
   234  	TypeSymbol
   235  	TypeObject
   236  	TypeFunction
   237  )
   238  
   239  func (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  
   262  func (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.
   268  func (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.
   297  func (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  
   306  func 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.
   310  func (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  
   320  func 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.
   324  func (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  
   332  func 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.
   336  func (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  
   345  func 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.
   349  func (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  
   359  func valueSetIndex(v ref, i int, x ref)
   360  
   361  func 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.
   374  func (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  
   383  func 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.
   388  func (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  
   405  func 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.
   410  func (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  
   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 ...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  
   443  func valueNew(v ref, args []ref) (ref, bool)
   444  
   445  func (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  
   451  func (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.
   463  func (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.
   469  func (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.
   475  func (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.
   489  func (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.
   510  func (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  
   533  func 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  
   542  func valuePrepareString(v ref) (ref, int)
   543  
   544  func valueLoadString(v ref, b []byte)
   545  
   546  // InstanceOf reports whether v is an instance of type t according to JavaScript's instanceof operator.
   547  func (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  
   554  func 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.
   559  type ValueError struct {
   560  	Method string
   561  	Type   Type
   562  }
   563  
   564  func (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.
   571  func 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  
   580  func 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.
   585  func 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  
   594  func copyBytesToJS(dst ref, src []byte) (int, bool)
   595  

View as plain text