Newer
Older
// 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.
type ref uint64
// 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".
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))}
}
// 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 |
func ValueOf(x interface{}) Value {
switch x := x.(type) {
case Value:
return x
case Callback:
return x.Value
if x {
return valueTrue
} else {
return valueFalse
}
return floatValue(float64(x))
return floatValue(float64(x))
return floatValue(float64(x))
return floatValue(float64(x))
return floatValue(float64(x))
return floatValue(float64(x))
return floatValue(float64(x))
return floatValue(float64(x))
return floatValue(float64(x))
return floatValue(float64(x))
return floatValue(float64(x))
return floatValue(float64(uintptr(x)))
return floatValue(float64(x))
return floatValue(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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
// 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
}
}
// 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).
valueSet(v.ref, p, ValueOf(x).ref)
func valueSet(v ref, p string, x ref)
// 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).
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))
}
return argVals
}
// Length returns the JavaScript property "length" of v.
func (v Value) Length() int {
return valueLength(v.ref)
}
func valueLength(v ref) int
// 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.
func (v Value) Call(m string, args ...interface{}) Value {
res, ok := valueCall(v.ref, m, makeArgs(args))
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())
}
func valueCall(v ref, m string, args []ref) (ref, bool)
// 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.
func (v Value) Invoke(args ...interface{}) Value {
res, ok := valueInvoke(v.ref, makeArgs(args))
if vType := v.Type(); vType != TypeFunction { // check here to avoid overhead in success case
panic(&ValueError{"Value.Invoke", vType})
}
func valueInvoke(v ref, args []ref) (ref, bool)
// 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.
func (v Value) New(args ...interface{}) Value {
res, ok := valueNew(v.ref, makeArgs(args))
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 {
if !v.isNumber() {
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.
// Bool returns the value v as a bool. It panics if v is not a JavaScript boolean.
switch v.ref {
case valueTrue.ref:
return true
case valueFalse.ref:
return false
default:
panic(&ValueError{"Value.Bool", v.Type()})
// String returns the value v converted to string according to JavaScript type conversions.
func (v Value) String() string {
str, length := valuePrepareString(v.ref)
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()
}