github.com/gopherjs/gopherjs@v1.19.0-beta1.0.20240506212314-27071a8796e4/compiler/natives/src/syscall/js/js.go (about)

     1  //go:build js
     2  // +build js
     3  
     4  package js
     5  
     6  import (
     7  	"unsafe"
     8  
     9  	"github.com/gopherjs/gopherjs/js"
    10  )
    11  
    12  type Type int
    13  
    14  const (
    15  	TypeUndefined Type = iota
    16  	TypeNull
    17  	TypeBoolean
    18  	TypeNumber
    19  	TypeString
    20  	TypeSymbol
    21  	TypeObject
    22  	TypeFunction
    23  )
    24  
    25  // Same order as Type constants
    26  var typeNames = []string{
    27  	"undefined",
    28  	"null",
    29  	"boolean",
    30  	"number",
    31  	"string",
    32  	"symbol",
    33  	"object",
    34  	"function",
    35  }
    36  
    37  func (t Type) String() string {
    38  	if int(t) < 0 || len(typeNames) <= int(t) {
    39  		panic("bad type")
    40  	}
    41  	return typeNames[t]
    42  }
    43  
    44  func (t Type) isObject() bool {
    45  	return t == TypeObject || t == TypeFunction
    46  }
    47  
    48  func Global() Value {
    49  	return objectToValue(js.Global)
    50  }
    51  
    52  func Null() Value {
    53  	return objectToValue(nil)
    54  }
    55  
    56  func Undefined() Value {
    57  	return objectToValue(js.Undefined)
    58  }
    59  
    60  type Func struct {
    61  	Value
    62  }
    63  
    64  func (f Func) Release() {
    65  	js.Global.Set("$exportedFunctions", js.Global.Get("$exportedFunctions").Int()-1)
    66  	f.Value = Null()
    67  }
    68  
    69  func FuncOf(fn func(this Value, args []Value) interface{}) Func {
    70  	// Existence of a wrapped function means that an external event may awaken the
    71  	// program and we need to suppress deadlock detection.
    72  	js.Global.Set("$exportedFunctions", js.Global.Get("$exportedFunctions").Int()+1)
    73  	return Func{
    74  		Value: objectToValue(js.MakeFunc(func(this *js.Object, args []*js.Object) interface{} {
    75  			vargs := make([]Value, len(args))
    76  			for i, a := range args {
    77  				vargs[i] = objectToValue(a)
    78  			}
    79  			return fn(objectToValue(this), vargs)
    80  		})),
    81  	}
    82  }
    83  
    84  type Error struct {
    85  	Value
    86  }
    87  
    88  func (e Error) Error() string {
    89  	return "JavaScript error: " + e.Get("message").String()
    90  }
    91  
    92  type Value struct {
    93  	v *js.Object
    94  
    95  	// inited represents whether Value is non-zero value. true represents the value is not 'undefined'.
    96  	inited bool
    97  
    98  	_ [0]func() // uncomparable; to make == not compile
    99  }
   100  
   101  func objectToValue(obj *js.Object) Value {
   102  	if obj == js.Undefined {
   103  		return Value{}
   104  	}
   105  	return Value{v: obj, inited: true}
   106  }
   107  
   108  var (
   109  	id         *js.Object
   110  	instanceOf *js.Object
   111  	typeOf     *js.Object
   112  )
   113  
   114  func init() {
   115  	if js.Global != nil {
   116  		id = js.Global.Get("$id")
   117  		instanceOf = js.Global.Get("$instanceOf")
   118  		typeOf = js.Global.Get("$typeOf")
   119  	}
   120  }
   121  
   122  func getValueType(obj *js.Object) Type {
   123  	if obj == nil {
   124  		return TypeNull
   125  	}
   126  	name := typeOf.Invoke(obj).String()
   127  	for type2, name2 := range typeNames {
   128  		if name == name2 {
   129  			return Type(type2)
   130  		}
   131  	}
   132  	return TypeObject
   133  }
   134  
   135  func ValueOf(x interface{}) Value {
   136  	switch x := x.(type) {
   137  	case Value:
   138  		return x
   139  	case Func:
   140  		return x.Value
   141  	case nil:
   142  		return Null()
   143  	case bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, unsafe.Pointer, string, map[string]interface{}, []interface{}:
   144  		return objectToValue(id.Invoke(x))
   145  	default:
   146  		panic("ValueOf: invalid value")
   147  	}
   148  }
   149  
   150  func (v Value) internal() *js.Object {
   151  	if !v.inited {
   152  		return js.Undefined
   153  	}
   154  	return v.v
   155  }
   156  
   157  func (v Value) Bool() bool {
   158  	if vType := v.Type(); vType != TypeBoolean {
   159  		panic(&ValueError{"Value.Bool", vType})
   160  	}
   161  	return v.internal().Bool()
   162  }
   163  
   164  // convertArgs converts arguments into values for GopherJS arguments.
   165  func convertArgs(args ...interface{}) []interface{} {
   166  	newArgs := []interface{}{}
   167  	for _, arg := range args {
   168  		v := ValueOf(arg)
   169  		newArgs = append(newArgs, v.internal())
   170  	}
   171  	return newArgs
   172  }
   173  
   174  func convertJSError() {
   175  	err := recover()
   176  	if err == nil {
   177  		return
   178  	}
   179  	if jsErr, ok := err.(*js.Error); ok {
   180  		// We expect that all panics caught by Value.Call() are in fact JavaScript
   181  		// exceptions intercepted by GopherJS runtime, which we convert to
   182  		// syscall/js.Error, which the callers would expect.
   183  		panic(Error{Value: objectToValue(jsErr.Object)})
   184  	}
   185  	// Panics of other types are unexpected and should never happen. But if it
   186  	// does, we will just re-raise it as-is.
   187  	panic(err)
   188  }
   189  
   190  func (v Value) Call(m string, args ...interface{}) Value {
   191  	if vType := v.Type(); vType != TypeObject && vType != TypeFunction {
   192  		panic(&ValueError{"Value.Call", vType})
   193  	}
   194  	if propType := v.Get(m).Type(); propType != TypeFunction {
   195  		panic("js: Value.Call: property " + m + " is not a function, got " + propType.String())
   196  	}
   197  	defer convertJSError()
   198  	return objectToValue(v.internal().Call(m, convertArgs(args...)...))
   199  }
   200  
   201  func (v Value) Float() float64 {
   202  	if vType := v.Type(); vType != TypeNumber {
   203  		panic(&ValueError{"Value.Float", vType})
   204  	}
   205  	return v.internal().Float()
   206  }
   207  
   208  func (v Value) Get(p string) Value {
   209  	if vType := v.Type(); !vType.isObject() {
   210  		panic(&ValueError{"Value.Get", vType})
   211  	}
   212  	return objectToValue(v.internal().Get(p))
   213  }
   214  
   215  func (v Value) Index(i int) Value {
   216  	if vType := v.Type(); !vType.isObject() {
   217  		panic(&ValueError{"Value.Index", vType})
   218  	}
   219  	return objectToValue(v.internal().Index(i))
   220  }
   221  
   222  func (v Value) Int() int {
   223  	if vType := v.Type(); vType != TypeNumber {
   224  		panic(&ValueError{"Value.Int", vType})
   225  	}
   226  	return v.internal().Int()
   227  }
   228  
   229  func (v Value) InstanceOf(t Value) bool {
   230  	return instanceOf.Invoke(v.internal(), t.internal()).Bool()
   231  }
   232  
   233  func (v Value) Invoke(args ...interface{}) Value {
   234  	if vType := v.Type(); vType != TypeFunction {
   235  		panic(&ValueError{"Value.Invoke", vType})
   236  	}
   237  	return objectToValue(v.internal().Invoke(convertArgs(args...)...))
   238  }
   239  
   240  func (v Value) JSValue() Value {
   241  	return v
   242  }
   243  
   244  func (v Value) Length() int {
   245  	return v.internal().Length()
   246  }
   247  
   248  func (v Value) New(args ...interface{}) Value {
   249  	defer func() {
   250  		err := recover()
   251  		if err == nil {
   252  			return
   253  		}
   254  		if vType := v.Type(); vType != TypeFunction { // check here to avoid overhead in success case
   255  			panic(&ValueError{"Value.New", vType})
   256  		}
   257  		if jsErr, ok := err.(*js.Error); ok {
   258  			panic(Error{objectToValue(jsErr.Object)})
   259  		}
   260  		panic(err)
   261  	}()
   262  	return objectToValue(v.internal().New(convertArgs(args...)...))
   263  }
   264  
   265  func (v Value) Set(p string, x interface{}) {
   266  	if vType := v.Type(); !vType.isObject() {
   267  		panic(&ValueError{"Value.Set", vType})
   268  	}
   269  	v.internal().Set(p, convertArgs(x)[0])
   270  }
   271  
   272  func (v Value) SetIndex(i int, x interface{}) {
   273  	if vType := v.Type(); !vType.isObject() {
   274  		panic(&ValueError{"Value.SetIndex", vType})
   275  	}
   276  	v.internal().SetIndex(i, convertArgs(x)[0])
   277  }
   278  
   279  // String returns the value v as a string.
   280  // String is a special case because of Go's String method convention. Unlike the other getters,
   281  // it does not panic if v's Type is not TypeString. Instead, it returns a string of the form "<T>"
   282  // or "<T: V>" where T is v's type and V is a string representation of v's value.
   283  func (v Value) String() string {
   284  	switch v.Type() {
   285  	case TypeString:
   286  		return v.internal().String()
   287  	case TypeUndefined:
   288  		return "<undefined>"
   289  	case TypeNull:
   290  		return "<null>"
   291  	case TypeBoolean:
   292  		return "<boolean: " + v.internal().String() + ">"
   293  	case TypeNumber:
   294  		return "<number: " + v.internal().String() + ">"
   295  	case TypeSymbol:
   296  		return "<symbol>"
   297  	case TypeObject:
   298  		return "<object>"
   299  	case TypeFunction:
   300  		return "<function>"
   301  	default:
   302  		panic("bad type")
   303  	}
   304  }
   305  
   306  func (v Value) Truthy() bool {
   307  	return v.internal().Bool()
   308  }
   309  
   310  func (v Value) Type() Type {
   311  	return Type(getValueType(v.internal()))
   312  }
   313  
   314  func (v Value) IsNull() bool {
   315  	return v.Type() == TypeNull
   316  }
   317  
   318  func (v Value) IsUndefined() bool {
   319  	return !v.inited
   320  }
   321  
   322  func (v Value) IsNaN() bool {
   323  	return js.Global.Call("isNaN", v.internal()).Bool()
   324  }
   325  
   326  // Delete deletes the JavaScript property p of value v.
   327  // It panics if v is not a JavaScript object.
   328  func (v Value) Delete(p string) {
   329  	if vType := v.Type(); !vType.isObject() {
   330  		panic(&ValueError{"Value.Delete", vType})
   331  	}
   332  	v.internal().Delete(p)
   333  }
   334  
   335  func (v Value) Equal(w Value) bool {
   336  	return v.internal() == w.internal()
   337  }
   338  
   339  type ValueError struct {
   340  	Method string
   341  	Type   Type
   342  }
   343  
   344  func (e *ValueError) Error() string {
   345  	return "syscall/js: call of " + e.Method + " on " + e.Type.String()
   346  }
   347  
   348  // CopyBytesToGo copies bytes from the Uint8Array src to dst.
   349  // It returns the number of bytes copied, which will be the minimum of the lengths of src and dst.
   350  // CopyBytesToGo panics if src is not an Uint8Array.
   351  func CopyBytesToGo(dst []byte, src Value) int {
   352  	vlen := src.v.Length()
   353  	if dlen := len(dst); dlen < vlen {
   354  		vlen = dlen
   355  	}
   356  	copy(dst, src.v.Interface().([]byte))
   357  	return vlen
   358  }
   359  
   360  // CopyBytesToJS copies bytes from src to the Uint8Array dst.
   361  // It returns the number of bytes copied, which will be the minimum of the lengths of src and dst.
   362  // CopyBytesToJS panics if dst is not an Uint8Array.
   363  func CopyBytesToJS(dst Value, src []byte) int {
   364  	dt, ok := dst.v.Interface().([]byte)
   365  	if !ok {
   366  		panic("syscall/js: CopyBytesToJS: expected dst to be an Uint8Array")
   367  	}
   368  	return copy(dt, src)
   369  }