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  	"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