...

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  	memory         = predefValue(6) // WebAssembly linear memory
    83  	jsGo           = predefValue(7) // instance of the Go class in JavaScript
    84  
    85  	objectConstructor = valueGlobal.Get("Object")
    86  	arrayConstructor  = valueGlobal.Get("Array")
    87  )
    88  
    89  // Undefined returns the JavaScript value "undefined".
    90  func Undefined() Value {
    91  	return valueUndefined
    92  }
    93  
    94  // Null returns the JavaScript value "null".
    95  func Null() Value {
    96  	return valueNull
    97  }
    98  
    99  // Global returns the JavaScript global object, usually "window" or "global".
   100  func Global() Value {
   101  	return valueGlobal
   102  }
   103  
   104  // ValueOf returns x as a JavaScript value:
   105  //
   106  //  | Go                     | JavaScript             |
   107  //  | ---------------------- | ---------------------- |
   108  //  | js.Value               | [its value]            |
   109  //  | js.TypedArray          | typed array            |
   110  //  | js.Func                | function               |
   111  //  | nil                    | null                   |
   112  //  | bool                   | boolean                |
   113  //  | integers and floats    | number                 |
   114  //  | string                 | string                 |
   115  //  | []interface{}          | new array              |
   116  //  | map[string]interface{} | new object             |
   117  //
   118  // Panics if x is not one of the expected types.
   119  func ValueOf(x interface{}) Value {
   120  	switch x := x.(type) {
   121  	case Value: // should precede Wrapper to avoid a loop
   122  		return x
   123  	case Wrapper:
   124  		return x.JSValue()
   125  	case nil:
   126  		return valueNull
   127  	case bool:
   128  		if x {
   129  			return valueTrue
   130  		} else {
   131  			return valueFalse
   132  		}
   133  	case int:
   134  		return floatValue(float64(x))
   135  	case int8:
   136  		return floatValue(float64(x))
   137  	case int16:
   138  		return floatValue(float64(x))
   139  	case int32:
   140  		return floatValue(float64(x))
   141  	case int64:
   142  		return floatValue(float64(x))
   143  	case uint:
   144  		return floatValue(float64(x))
   145  	case uint8:
   146  		return floatValue(float64(x))
   147  	case uint16:
   148  		return floatValue(float64(x))
   149  	case uint32:
   150  		return floatValue(float64(x))
   151  	case uint64:
   152  		return floatValue(float64(x))
   153  	case uintptr:
   154  		return floatValue(float64(x))
   155  	case unsafe.Pointer:
   156  		return floatValue(float64(uintptr(x)))
   157  	case float32:
   158  		return floatValue(float64(x))
   159  	case float64:
   160  		return floatValue(x)
   161  	case string:
   162  		return makeValue(stringVal(x))
   163  	case []interface{}:
   164  		a := arrayConstructor.New(len(x))
   165  		for i, s := range x {
   166  			a.SetIndex(i, s)
   167  		}
   168  		return a
   169  	case map[string]interface{}:
   170  		o := objectConstructor.New()
   171  		for k, v := range x {
   172  			o.Set(k, v)
   173  		}
   174  		return o
   175  	default:
   176  		panic("ValueOf: invalid value")
   177  	}
   178  }
   179  
   180  func stringVal(x string) ref
   181  
   182  // Type represents the JavaScript type of a Value.
   183  type Type int
   184  
   185  const (
   186  	TypeUndefined Type = iota
   187  	TypeNull
   188  	TypeBoolean
   189  	TypeNumber
   190  	TypeString
   191  	TypeSymbol
   192  	TypeObject
   193  	TypeFunction
   194  )
   195  
   196  func (t Type) String() string {
   197  	switch t {
   198  	case TypeUndefined:
   199  		return "undefined"
   200  	case TypeNull:
   201  		return "null"
   202  	case TypeBoolean:
   203  		return "boolean"
   204  	case TypeNumber:
   205  		return "number"
   206  	case TypeString:
   207  		return "string"
   208  	case TypeSymbol:
   209  		return "symbol"
   210  	case TypeObject:
   211  		return "object"
   212  	case TypeFunction:
   213  		return "function"
   214  	default:
   215  		panic("bad type")
   216  	}
   217  }
   218  
   219  // Type returns the JavaScript type of the value v. It is similar to JavaScript's typeof operator,
   220  // except that it returns TypeNull instead of TypeObject for null.
   221  func (v Value) Type() Type {
   222  	switch v.ref {
   223  	case valueUndefined.ref:
   224  		return TypeUndefined
   225  	case valueNull.ref:
   226  		return TypeNull
   227  	case valueTrue.ref, valueFalse.ref:
   228  		return TypeBoolean
   229  	}
   230  	if v.isNumber() {
   231  		return TypeNumber
   232  	}
   233  	typeFlag := v.ref >> 32 & 3
   234  	switch typeFlag {
   235  	case 1:
   236  		return TypeString
   237  	case 2:
   238  		return TypeSymbol
   239  	case 3:
   240  		return TypeFunction
   241  	default:
   242  		return TypeObject
   243  	}
   244  }
   245  
   246  // Get returns the JavaScript property p of value v.
   247  func (v Value) Get(p string) Value {
   248  	return makeValue(valueGet(v.ref, p))
   249  }
   250  
   251  func valueGet(v ref, p string) ref
   252  
   253  // Set sets the JavaScript property p of value v to ValueOf(x).
   254  func (v Value) Set(p string, x interface{}) {
   255  	valueSet(v.ref, p, ValueOf(x).ref)
   256  }
   257  
   258  func valueSet(v ref, p string, x ref)
   259  
   260  // Index returns JavaScript index i of value v.
   261  func (v Value) Index(i int) Value {
   262  	return makeValue(valueIndex(v.ref, i))
   263  }
   264  
   265  func valueIndex(v ref, i int) ref
   266  
   267  // SetIndex sets the JavaScript index i of value v to ValueOf(x).
   268  func (v Value) SetIndex(i int, x interface{}) {
   269  	valueSetIndex(v.ref, i, ValueOf(x).ref)
   270  }
   271  
   272  func valueSetIndex(v ref, i int, x ref)
   273  
   274  func makeArgs(args []interface{}) []ref {
   275  	argVals := make([]ref, len(args))
   276  	for i, arg := range args {
   277  		argVals[i] = ValueOf(arg).ref
   278  	}
   279  	return argVals
   280  }
   281  
   282  // Length returns the JavaScript property "length" of v.
   283  func (v Value) Length() int {
   284  	return valueLength(v.ref)
   285  }
   286  
   287  func valueLength(v ref) int
   288  
   289  // Call does a JavaScript call to the method m of value v with the given arguments.
   290  // It panics if v has no method m.
   291  // The arguments get mapped to JavaScript values according to the ValueOf function.
   292  func (v Value) Call(m string, args ...interface{}) Value {
   293  	res, ok := valueCall(v.ref, m, makeArgs(args))
   294  	if !ok {
   295  		if vType := v.Type(); vType != TypeObject && vType != TypeFunction { // check here to avoid overhead in success case
   296  			panic(&ValueError{"Value.Call", vType})
   297  		}
   298  		if propType := v.Get(m).Type(); propType != TypeFunction {
   299  			panic("syscall/js: Value.Call: property " + m + " is not a function, got " + propType.String())
   300  		}
   301  		panic(Error{makeValue(res)})
   302  	}
   303  	return makeValue(res)
   304  }
   305  
   306  func valueCall(v ref, m string, args []ref) (ref, bool)
   307  
   308  // Invoke does a JavaScript call of the value v with the given arguments.
   309  // It panics if v is not a function.
   310  // The arguments get mapped to JavaScript values according to the ValueOf function.
   311  func (v Value) Invoke(args ...interface{}) Value {
   312  	res, ok := valueInvoke(v.ref, makeArgs(args))
   313  	if !ok {
   314  		if vType := v.Type(); vType != TypeFunction { // check here to avoid overhead in success case
   315  			panic(&ValueError{"Value.Invoke", vType})
   316  		}
   317  		panic(Error{makeValue(res)})
   318  	}
   319  	return makeValue(res)
   320  }
   321  
   322  func valueInvoke(v ref, args []ref) (ref, bool)
   323  
   324  // New uses JavaScript's "new" operator with value v as constructor and the given arguments.
   325  // It panics if v is not a function.
   326  // The arguments get mapped to JavaScript values according to the ValueOf function.
   327  func (v Value) New(args ...interface{}) Value {
   328  	res, ok := valueNew(v.ref, makeArgs(args))
   329  	if !ok {
   330  		panic(Error{makeValue(res)})
   331  	}
   332  	return makeValue(res)
   333  }
   334  
   335  func valueNew(v ref, args []ref) (ref, bool)
   336  
   337  func (v Value) isNumber() bool {
   338  	return v.ref == valueZero.ref ||
   339  		v.ref == valueNaN.ref ||
   340  		(v.ref != valueUndefined.ref && v.ref>>32&nanHead != nanHead)
   341  }
   342  
   343  func (v Value) float(method string) float64 {
   344  	if !v.isNumber() {
   345  		panic(&ValueError{method, v.Type()})
   346  	}
   347  	if v.ref == valueZero.ref {
   348  		return 0
   349  	}
   350  	return *(*float64)(unsafe.Pointer(&v.ref))
   351  }
   352  
   353  // Float returns the value v as a float64. It panics if v is not a JavaScript number.
   354  func (v Value) Float() float64 {
   355  	return v.float("Value.Float")
   356  }
   357  
   358  // Int returns the value v truncated to an int. It panics if v is not a JavaScript number.
   359  func (v Value) Int() int {
   360  	return int(v.float("Value.Int"))
   361  }
   362  
   363  // Bool returns the value v as a bool. It panics if v is not a JavaScript boolean.
   364  func (v Value) Bool() bool {
   365  	switch v.ref {
   366  	case valueTrue.ref:
   367  		return true
   368  	case valueFalse.ref:
   369  		return false
   370  	default:
   371  		panic(&ValueError{"Value.Bool", v.Type()})
   372  	}
   373  }
   374  
   375  // Truthy returns the JavaScript "truthiness" of the value v. In JavaScript,
   376  // false, 0, "", null, undefined, and NaN are "falsy", and everything else is
   377  // "truthy". See https://developer.mozilla.org/en-US/docs/Glossary/Truthy.
   378  func (v Value) Truthy() bool {
   379  	switch v.Type() {
   380  	case TypeUndefined, TypeNull:
   381  		return false
   382  	case TypeBoolean:
   383  		return v.Bool()
   384  	case TypeNumber:
   385  		return v.ref != valueNaN.ref && v.ref != valueZero.ref
   386  	case TypeString:
   387  		return v.String() != ""
   388  	case TypeSymbol, TypeFunction, TypeObject:
   389  		return true
   390  	default:
   391  		panic("bad type")
   392  	}
   393  }
   394  
   395  // String returns the value v converted to string according to JavaScript type conversions.
   396  func (v Value) String() string {
   397  	str, length := valuePrepareString(v.ref)
   398  	b := make([]byte, length)
   399  	valueLoadString(str, b)
   400  	return string(b)
   401  }
   402  
   403  func valuePrepareString(v ref) (ref, int)
   404  
   405  func valueLoadString(v ref, b []byte)
   406  
   407  // InstanceOf reports whether v is an instance of type t according to JavaScript's instanceof operator.
   408  func (v Value) InstanceOf(t Value) bool {
   409  	return valueInstanceOf(v.ref, t.ref)
   410  }
   411  
   412  func valueInstanceOf(v ref, t ref) bool
   413  
   414  // A ValueError occurs when a Value method is invoked on
   415  // a Value that does not support it. Such cases are documented
   416  // in the description of each method.
   417  type ValueError struct {
   418  	Method string
   419  	Type   Type
   420  }
   421  
   422  func (e *ValueError) Error() string {
   423  	return "syscall/js: call of " + e.Method + " on " + e.Type.String()
   424  }
   425  

View as plain text