class.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. package jsc
  2. import (
  3. "time"
  4. "github.com/sagernet/sing/common"
  5. "github.com/dop251/goja"
  6. )
  7. type Module interface {
  8. Runtime() *goja.Runtime
  9. }
  10. type Class[M Module, C any] interface {
  11. Module() M
  12. Runtime() *goja.Runtime
  13. DefineField(name string, getter func(this C) any, setter func(this C, value goja.Value))
  14. DefineMethod(name string, method func(this C, call goja.FunctionCall) any)
  15. DefineStaticMethod(name string, method func(c Class[M, C], call goja.FunctionCall) any)
  16. DefineConstructor(constructor func(c Class[M, C], call goja.ConstructorCall) C)
  17. ToValue() goja.Value
  18. New(instance C) *goja.Object
  19. Prototype() *goja.Object
  20. Is(value goja.Value) bool
  21. As(value goja.Value) C
  22. }
  23. func GetClass[M Module, C any](runtime *goja.Runtime, exports *goja.Object, className string) Class[M, C] {
  24. objectValue := exports.Get(className)
  25. if objectValue == nil {
  26. panic(runtime.NewTypeError("Missing class: " + className))
  27. }
  28. object, isObject := objectValue.(*goja.Object)
  29. if !isObject {
  30. panic(runtime.NewTypeError("Invalid class: " + className))
  31. }
  32. classObject, isClass := object.Get("_class").(*goja.Object)
  33. if !isClass {
  34. panic(runtime.NewTypeError("Invalid class: " + className))
  35. }
  36. class, isClass := classObject.Export().(Class[M, C])
  37. if !isClass {
  38. panic(runtime.NewTypeError("Invalid class: " + className))
  39. }
  40. return class
  41. }
  42. type goClass[M Module, C any] struct {
  43. m M
  44. prototype *goja.Object
  45. constructor goja.Value
  46. }
  47. func NewClass[M Module, C any](module M) Class[M, C] {
  48. class := &goClass[M, C]{
  49. m: module,
  50. prototype: module.Runtime().NewObject(),
  51. }
  52. clazz := module.Runtime().ToValue(class).(*goja.Object)
  53. clazz.Set("toString", module.Runtime().ToValue(func(call goja.FunctionCall) goja.Value {
  54. return module.Runtime().ToValue("[sing-box Class]")
  55. }))
  56. class.prototype.DefineAccessorProperty("_class", class.Runtime().ToValue(func(call goja.FunctionCall) goja.Value { return clazz }), nil, goja.FLAG_FALSE, goja.FLAG_TRUE)
  57. return class
  58. }
  59. func (c *goClass[M, C]) Module() M {
  60. return c.m
  61. }
  62. func (c *goClass[M, C]) Runtime() *goja.Runtime {
  63. return c.m.Runtime()
  64. }
  65. func (c *goClass[M, C]) DefineField(name string, getter func(this C) any, setter func(this C, value goja.Value)) {
  66. var (
  67. getterValue goja.Value
  68. setterValue goja.Value
  69. )
  70. if getter != nil {
  71. getterValue = c.Runtime().ToValue(func(call goja.FunctionCall) goja.Value {
  72. this, isThis := call.This.Export().(C)
  73. if !isThis {
  74. panic(c.Runtime().NewTypeError("Illegal this value: " + call.This.ExportType().String()))
  75. }
  76. return c.toValue(getter(this), goja.Null())
  77. })
  78. }
  79. if setter != nil {
  80. setterValue = c.Runtime().ToValue(func(call goja.FunctionCall) goja.Value {
  81. this, isThis := call.This.Export().(C)
  82. if !isThis {
  83. panic(c.Runtime().NewTypeError("Illegal this value: " + call.This.String()))
  84. }
  85. setter(this, call.Argument(0))
  86. return goja.Undefined()
  87. })
  88. }
  89. c.prototype.DefineAccessorProperty(name, getterValue, setterValue, goja.FLAG_FALSE, goja.FLAG_TRUE)
  90. }
  91. func (c *goClass[M, C]) DefineMethod(name string, method func(this C, call goja.FunctionCall) any) {
  92. methodValue := c.Runtime().ToValue(func(call goja.FunctionCall) goja.Value {
  93. this, isThis := call.This.Export().(C)
  94. if !isThis {
  95. panic(c.Runtime().NewTypeError("Illegal this value: " + call.This.String()))
  96. }
  97. return c.toValue(method(this, call), goja.Undefined())
  98. })
  99. c.prototype.Set(name, methodValue)
  100. if name == "entries" {
  101. c.prototype.DefineDataPropertySymbol(goja.SymIterator, methodValue, goja.FLAG_TRUE, goja.FLAG_FALSE, goja.FLAG_TRUE)
  102. }
  103. }
  104. func (c *goClass[M, C]) DefineStaticMethod(name string, method func(c Class[M, C], call goja.FunctionCall) any) {
  105. c.prototype.Set(name, c.Runtime().ToValue(func(call goja.FunctionCall) goja.Value {
  106. return c.toValue(method(c, call), goja.Undefined())
  107. }))
  108. }
  109. func (c *goClass[M, C]) DefineConstructor(constructor func(c Class[M, C], call goja.ConstructorCall) C) {
  110. constructorObject := c.Runtime().ToValue(func(call goja.ConstructorCall) *goja.Object {
  111. value := constructor(c, call)
  112. object := c.toValue(value, goja.Undefined()).(*goja.Object)
  113. object.SetPrototype(call.This.Prototype())
  114. return object
  115. }).(*goja.Object)
  116. constructorObject.SetPrototype(c.prototype)
  117. c.prototype.DefineDataProperty("constructor", constructorObject, goja.FLAG_FALSE, goja.FLAG_FALSE, goja.FLAG_FALSE)
  118. c.constructor = constructorObject
  119. }
  120. func (c *goClass[M, C]) toValue(rawValue any, defaultValue goja.Value) goja.Value {
  121. switch value := rawValue.(type) {
  122. case nil:
  123. return defaultValue
  124. case time.Time:
  125. return TimeToValue(c.Runtime(), value)
  126. default:
  127. return c.Runtime().ToValue(value)
  128. }
  129. }
  130. func (c *goClass[M, C]) ToValue() goja.Value {
  131. if c.constructor == nil {
  132. constructorObject := c.Runtime().ToValue(func(call goja.ConstructorCall) *goja.Object {
  133. panic(c.Runtime().NewTypeError("Illegal constructor call"))
  134. }).(*goja.Object)
  135. constructorObject.SetPrototype(c.prototype)
  136. c.prototype.DefineDataProperty("constructor", constructorObject, goja.FLAG_FALSE, goja.FLAG_FALSE, goja.FLAG_FALSE)
  137. c.constructor = constructorObject
  138. }
  139. return c.constructor
  140. }
  141. func (c *goClass[M, C]) New(instance C) *goja.Object {
  142. object := c.Runtime().ToValue(instance).(*goja.Object)
  143. object.SetPrototype(c.prototype)
  144. return object
  145. }
  146. func (c *goClass[M, C]) Prototype() *goja.Object {
  147. return c.prototype
  148. }
  149. func (c *goClass[M, C]) Is(value goja.Value) bool {
  150. object, isObject := value.(*goja.Object)
  151. if !isObject {
  152. return false
  153. }
  154. prototype := object.Prototype()
  155. for prototype != nil {
  156. if prototype == c.prototype {
  157. return true
  158. }
  159. prototype = prototype.Prototype()
  160. }
  161. return false
  162. }
  163. func (c *goClass[M, C]) As(value goja.Value) C {
  164. object, isObject := value.(*goja.Object)
  165. if !isObject {
  166. return common.DefaultValue[C]()
  167. }
  168. if !c.Is(object) {
  169. return common.DefaultValue[C]()
  170. }
  171. return object.Export().(C)
  172. }