Skip to content
Snippets Groups Projects
js.go 10 KiB
Newer Older
  • Learn to ignore specific revisions
  • Richard Musiol's avatar
    Richard Musiol committed
    // Copyright 2018 The Go Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.
    
    // +build js,wasm
    
    // Package js gives access to the WebAssembly host environment when using the js/wasm architecture.
    // Its API is based on JavaScript semantics.
    //
    // This package is EXPERIMENTAL. Its current scope is only to allow tests to run, but not yet to provide a
    // comprehensive API for users. It is exempt from the Go compatibility promise.
    package js
    
    
    // ref is used to identify a JavaScript value, since the value itself can not be passed to WebAssembly.
    
    //
    // The JavaScript value "undefined" is represented by the value 0.
    // A JavaScript number (64-bit float, except 0 and NaN) is represented by its IEEE 754 binary representation.
    
    // All other values are represented as an IEEE 754 binary representation of NaN with bits 0-31 used as
    // an ID and bits 32-33 used to differentiate between string, symbol, function and object.
    
    // 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).
    
    const nanHead = 0x7FF80000
    
    // Value represents a JavaScript value. The zero value is the JavaScript value "undefined".
    
    Richard Musiol's avatar
    Richard Musiol committed
    type Value struct {
    
    	ref ref
    }
    
    func makeValue(v ref) Value {
    	return Value{ref: v}
    
    func predefValue(id uint32) Value {
    	return Value{ref: nanHead<<32 | ref(id)}
    }
    
    func floatValue(f float64) Value {
    
    	if f == 0 {
    		return valueZero
    	}
    
    	if f != f {
    		return valueNaN
    	}
    	return Value{ref: *(*ref)(unsafe.Pointer(&f))}
    }
    
    
    Richard Musiol's avatar
    Richard Musiol committed
    // Error wraps a JavaScript error.
    type Error struct {
    	// Value is the underlying JavaScript error value.
    	Value
    }
    
    // Error implements the error interface.
    func (e Error) Error() string {
    	return "JavaScript error: " + e.Get("message").String()
    }
    
    var (
    
    	valueUndefined = Value{ref: 0}
    
    	valueNaN       = predefValue(0)
    
    	valueZero      = predefValue(1)
    
    	valueNull      = predefValue(2)
    	valueTrue      = predefValue(3)
    	valueFalse     = predefValue(4)
    	valueGlobal    = predefValue(5)
    	memory         = predefValue(6) // WebAssembly linear memory
    	jsGo           = predefValue(7) // instance of the Go class in JavaScript
    
    
    	objectConstructor = valueGlobal.Get("Object")
    	arrayConstructor  = valueGlobal.Get("Array")
    
    // Undefined returns the JavaScript value "undefined".
    func Undefined() Value {
    	return valueUndefined
    }
    
    // Null returns the JavaScript value "null".
    func Null() Value {
    	return valueNull
    }
    
    // Global returns the JavaScript global object, usually "window" or "global".
    func Global() Value {
    	return valueGlobal
    }
    
    // ValueOf returns x as a JavaScript value:
    //
    
    //  | Go                     | JavaScript             |
    //  | ---------------------- | ---------------------- |
    //  | js.Value               | [its value]            |
    //  | js.TypedArray          | typed array            |
    //  | js.Callback            | function               |
    //  | nil                    | null                   |
    //  | bool                   | boolean                |
    //  | integers and floats    | number                 |
    //  | string                 | string                 |
    //  | []interface{}          | new array              |
    //  | map[string]interface{} | new object             |
    
    Richard Musiol's avatar
    Richard Musiol committed
    func ValueOf(x interface{}) Value {
    	switch x := x.(type) {
    	case Value:
    		return x
    
    	case TypedArray:
    		return x.Value
    
    Richard Musiol's avatar
    Richard Musiol committed
    	case nil:
    
    Richard Musiol's avatar
    Richard Musiol committed
    	case bool:
    
    		if x {
    			return valueTrue
    		} else {
    			return valueFalse
    		}
    
    Richard Musiol's avatar
    Richard Musiol committed
    	case int:
    
    		return floatValue(float64(x))
    
    Richard Musiol's avatar
    Richard Musiol committed
    	case int8:
    
    		return floatValue(float64(x))
    
    Richard Musiol's avatar
    Richard Musiol committed
    	case int16:
    
    		return floatValue(float64(x))
    
    Richard Musiol's avatar
    Richard Musiol committed
    	case int32:
    
    		return floatValue(float64(x))
    
    Richard Musiol's avatar
    Richard Musiol committed
    	case int64:
    
    		return floatValue(float64(x))
    
    Richard Musiol's avatar
    Richard Musiol committed
    	case uint:
    
    		return floatValue(float64(x))
    
    Richard Musiol's avatar
    Richard Musiol committed
    	case uint8:
    
    		return floatValue(float64(x))
    
    Richard Musiol's avatar
    Richard Musiol committed
    	case uint16:
    
    		return floatValue(float64(x))
    
    Richard Musiol's avatar
    Richard Musiol committed
    	case uint32:
    
    		return floatValue(float64(x))
    
    Richard Musiol's avatar
    Richard Musiol committed
    	case uint64:
    
    		return floatValue(float64(x))
    
    Richard Musiol's avatar
    Richard Musiol committed
    	case uintptr:
    
    		return floatValue(float64(x))
    
    Richard Musiol's avatar
    Richard Musiol committed
    	case unsafe.Pointer:
    
    		return floatValue(float64(uintptr(x)))
    
    Richard Musiol's avatar
    Richard Musiol committed
    	case float32:
    
    		return floatValue(float64(x))
    
    Richard Musiol's avatar
    Richard Musiol committed
    	case float64:
    
    Richard Musiol's avatar
    Richard Musiol committed
    	case string:
    
    		return makeValue(stringVal(x))
    
    	case []interface{}:
    		a := arrayConstructor.New(len(x))
    		for i, s := range x {
    			a.SetIndex(i, s)
    		}
    		return a
    	case map[string]interface{}:
    		o := objectConstructor.New()
    		for k, v := range x {
    			o.Set(k, v)
    		}
    		return o
    
    Richard Musiol's avatar
    Richard Musiol committed
    	default:
    
    		panic("ValueOf: invalid value")
    
    func stringVal(x string) ref
    
    // Type represents the JavaScript type of a Value.
    type Type int
    
    const (
    	TypeUndefined Type = iota
    	TypeNull
    	TypeBoolean
    	TypeNumber
    	TypeString
    	TypeSymbol
    	TypeObject
    	TypeFunction
    )
    
    func (t Type) String() string {
    	switch t {
    	case TypeUndefined:
    		return "undefined"
    	case TypeNull:
    		return "null"
    	case TypeBoolean:
    		return "boolean"
    	case TypeNumber:
    		return "number"
    	case TypeString:
    		return "string"
    	case TypeSymbol:
    		return "symbol"
    	case TypeObject:
    		return "object"
    	case TypeFunction:
    		return "function"
    	default:
    		panic("bad type")
    	}
    }
    
    // Type returns the JavaScript type of the value v. It is similar to JavaScript's typeof operator,
    // except that it returns TypeNull instead of TypeObject for null.
    func (v Value) Type() Type {
    	switch v.ref {
    	case valueUndefined.ref:
    		return TypeUndefined
    	case valueNull.ref:
    		return TypeNull
    	case valueTrue.ref, valueFalse.ref:
    		return TypeBoolean
    	}
    	if v.isNumber() {
    		return TypeNumber
    	}
    	typeFlag := v.ref >> 32 & 3
    	switch typeFlag {
    	case 1:
    		return TypeString
    	case 2:
    		return TypeSymbol
    	case 3:
    		return TypeFunction
    	default:
    		return TypeObject
    	}
    }
    
    
    Richard Musiol's avatar
    Richard Musiol committed
    // Get returns the JavaScript property p of value v.
    
    func (v Value) Get(p string) Value {
    	return makeValue(valueGet(v.ref, p))
    }
    
    func valueGet(v ref, p string) ref
    
    // Set sets the JavaScript property p of value v to ValueOf(x).
    
    Richard Musiol's avatar
    Richard Musiol committed
    func (v Value) Set(p string, x interface{}) {
    
    	valueSet(v.ref, p, ValueOf(x).ref)
    
    func valueSet(v ref, p string, x ref)
    
    Richard Musiol's avatar
    Richard Musiol committed
    
    // Index returns JavaScript index i of value v.
    
    func (v Value) Index(i int) Value {
    	return makeValue(valueIndex(v.ref, i))
    }
    
    func valueIndex(v ref, i int) ref
    
    // SetIndex sets the JavaScript index i of value v to ValueOf(x).
    
    Richard Musiol's avatar
    Richard Musiol committed
    func (v Value) SetIndex(i int, x interface{}) {
    
    	valueSetIndex(v.ref, i, ValueOf(x).ref)
    
    func valueSetIndex(v ref, i int, x ref)
    
    func makeArgs(args []interface{}) []ref {
    	argVals := make([]ref, len(args))
    
    Richard Musiol's avatar
    Richard Musiol committed
    	for i, arg := range args {
    
    		argVals[i] = ValueOf(arg).ref
    
    Richard Musiol's avatar
    Richard Musiol committed
    	}
    	return argVals
    }
    
    // Length returns the JavaScript property "length" of v.
    
    func (v Value) Length() int {
    	return valueLength(v.ref)
    }
    
    func valueLength(v ref) int
    
    Richard Musiol's avatar
    Richard Musiol committed
    
    // Call does a JavaScript call to the method m of value v with the given arguments.
    // It panics if v has no method m.
    
    // The arguments get mapped to JavaScript values according to the ValueOf function.
    
    Richard Musiol's avatar
    Richard Musiol committed
    func (v Value) Call(m string, args ...interface{}) Value {
    
    	res, ok := valueCall(v.ref, m, makeArgs(args))
    
    Richard Musiol's avatar
    Richard Musiol committed
    	if !ok {
    
    		if vType := v.Type(); vType != TypeObject && vType != TypeFunction { // check here to avoid overhead in success case
    			panic(&ValueError{"Value.Call", vType})
    		}
    		if propType := v.Get(m).Type(); propType != TypeFunction {
    			panic("syscall/js: Value.Call: property " + m + " is not a function, got " + propType.String())
    		}
    
    		panic(Error{makeValue(res)})
    
    	return makeValue(res)
    
    func valueCall(v ref, m string, args []ref) (ref, bool)
    
    Richard Musiol's avatar
    Richard Musiol committed
    
    // Invoke does a JavaScript call of the value v with the given arguments.
    // It panics if v is not a function.
    
    // The arguments get mapped to JavaScript values according to the ValueOf function.
    
    Richard Musiol's avatar
    Richard Musiol committed
    func (v Value) Invoke(args ...interface{}) Value {
    
    	res, ok := valueInvoke(v.ref, makeArgs(args))
    
    Richard Musiol's avatar
    Richard Musiol committed
    	if !ok {
    
    		if vType := v.Type(); vType != TypeFunction { // check here to avoid overhead in success case
    			panic(&ValueError{"Value.Invoke", vType})
    		}
    
    		panic(Error{makeValue(res)})
    
    	return makeValue(res)
    
    func valueInvoke(v ref, args []ref) (ref, bool)
    
    Richard Musiol's avatar
    Richard Musiol committed
    
    // New uses JavaScript's "new" operator with value v as constructor and the given arguments.
    // It panics if v is not a function.
    
    // The arguments get mapped to JavaScript values according to the ValueOf function.
    
    Richard Musiol's avatar
    Richard Musiol committed
    func (v Value) New(args ...interface{}) Value {
    
    	res, ok := valueNew(v.ref, makeArgs(args))
    
    Richard Musiol's avatar
    Richard Musiol committed
    	if !ok {
    
    		panic(Error{makeValue(res)})
    
    	return makeValue(res)
    
    func valueNew(v ref, args []ref) (ref, bool)
    
    func (v Value) isNumber() bool {
    
    	return v.ref == valueZero.ref ||
    		v.ref == valueNaN.ref ||
    		(v.ref != valueUndefined.ref && v.ref>>32&nanHead != nanHead)
    
    func (v Value) float(method string) float64 {
    
    		panic(&ValueError{method, v.Type()})
    
    	if v.ref == valueZero.ref {
    		return 0
    	}
    
    	return *(*float64)(unsafe.Pointer(&v.ref))
    }
    
    // Float returns the value v as a float64. It panics if v is not a JavaScript number.
    func (v Value) Float() float64 {
    	return v.float("Value.Float")
    }
    
    
    // Int returns the value v truncated to an int. It panics if v is not a JavaScript number.
    
    func (v Value) Int() int {
    
    	return int(v.float("Value.Int"))
    
    // Bool returns the value v as a bool. It panics if v is not a JavaScript boolean.
    
    func (v Value) Bool() bool {
    
    	switch v.ref {
    	case valueTrue.ref:
    		return true
    	case valueFalse.ref:
    		return false
    	default:
    
    		panic(&ValueError{"Value.Bool", v.Type()})
    
    Richard Musiol's avatar
    Richard Musiol committed
    // String returns the value v converted to string according to JavaScript type conversions.
    func (v Value) String() string {
    
    	str, length := valuePrepareString(v.ref)
    
    Richard Musiol's avatar
    Richard Musiol committed
    	b := make([]byte, length)
    
    	valueLoadString(str, b)
    
    Richard Musiol's avatar
    Richard Musiol committed
    	return string(b)
    }
    
    
    func valuePrepareString(v ref) (ref, int)
    
    func valueLoadString(v ref, b []byte)
    
    
    // InstanceOf reports whether v is an instance of type t according to JavaScript's instanceof operator.
    func (v Value) InstanceOf(t Value) bool {
    	return valueInstanceOf(v.ref, t.ref)
    }
    
    func valueInstanceOf(v ref, t ref) bool
    
    
    // A ValueError occurs when a Value method is invoked on
    // a Value that does not support it. Such cases are documented
    // in the description of each method.
    type ValueError struct {
    	Method string
    	Type   Type
    }
    
    func (e *ValueError) Error() string {
    	return "syscall/js: call of " + e.Method + " on " + e.Type.String()
    }