github.com/MontFerret/ferret@v0.18.0/pkg/drivers/cdp/eval/function.go (about) 1 package eval 2 3 import ( 4 "strconv" 5 "strings" 6 7 "github.com/mafredri/cdp/protocol/runtime" 8 "github.com/wI2L/jettison" 9 10 "github.com/MontFerret/ferret/pkg/drivers" 11 "github.com/MontFerret/ferret/pkg/runtime/core" 12 ) 13 14 type Function struct { 15 exp string 16 name string 17 ownerID runtime.RemoteObjectID 18 args FunctionArguments 19 returnType ReturnType 20 async bool 21 } 22 23 const ( 24 defaultArgsCount = 5 25 expName = "$exp" 26 compiledExpName = "$$exp" 27 ) 28 29 func F(exp string) *Function { 30 op := new(Function) 31 op.exp = exp 32 op.returnType = ReturnNothing 33 34 return op 35 } 36 37 func (fn *Function) CallOn(id runtime.RemoteObjectID) *Function { 38 fn.ownerID = id 39 40 return fn 41 } 42 43 func (fn *Function) AsAsync() *Function { 44 fn.async = true 45 46 return fn 47 } 48 49 func (fn *Function) AsSync() *Function { 50 fn.async = false 51 52 return fn 53 } 54 55 func (fn *Function) AsAnonymous() *Function { 56 fn.name = "" 57 58 return fn 59 } 60 61 func (fn *Function) AsNamed(name string) *Function { 62 if name != "" { 63 fn.name = name 64 } 65 66 return fn 67 } 68 69 func (fn *Function) WithArgRemoteValue(value RemoteValue) *Function { 70 return fn.WithArgRef(value.RemoteID()) 71 } 72 73 func (fn *Function) WithArgRef(id runtime.RemoteObjectID) *Function { 74 return fn.withArg(runtime.CallArgument{ 75 ObjectID: &id, 76 }) 77 } 78 79 func (fn *Function) WithArgValue(value core.Value) *Function { 80 raw, err := value.MarshalJSON() 81 82 if err != nil { 83 panic(err) 84 } 85 86 return fn.withArg(runtime.CallArgument{ 87 Value: raw, 88 }) 89 } 90 91 func (fn *Function) WithArgSelector(selector drivers.QuerySelector) *Function { 92 return fn.WithArg(selector.String()) 93 } 94 95 func (fn *Function) WithArg(value interface{}) *Function { 96 raw, err := jettison.MarshalOpts(value, jettison.NoHTMLEscaping()) 97 98 if err != nil { 99 panic(err) 100 } 101 102 return fn.withArg(runtime.CallArgument{ 103 Value: raw, 104 }) 105 } 106 107 func (fn *Function) String() string { 108 return fn.exp 109 } 110 111 func (fn *Function) Length() int { 112 return len(fn.args) 113 } 114 115 func (fn *Function) returnNothing() *Function { 116 fn.returnType = ReturnNothing 117 118 return fn 119 } 120 121 func (fn *Function) returnRef() *Function { 122 fn.returnType = ReturnRef 123 124 return fn 125 } 126 127 func (fn *Function) returnValue() *Function { 128 fn.returnType = ReturnValue 129 130 return fn 131 } 132 133 func (fn *Function) withArg(arg runtime.CallArgument) *Function { 134 if fn.args == nil { 135 fn.args = make([]runtime.CallArgument, 0, defaultArgsCount) 136 } 137 138 fn.args = append(fn.args, arg) 139 140 return fn 141 } 142 143 func (fn *Function) eval(ctx runtime.ExecutionContextID) *runtime.CallFunctionOnArgs { 144 exp := fn.prepExp() 145 146 call := runtime.NewCallFunctionOnArgs(exp). 147 SetAwaitPromise(fn.async). 148 SetReturnByValue(fn.returnType == ReturnValue) 149 150 if fn.ownerID != EmptyObjectID { 151 call.SetObjectID(fn.ownerID) 152 } else if ctx != EmptyExecutionContextID { 153 call.SetExecutionContextID(ctx) 154 } 155 156 if len(fn.args) > 0 { 157 call.SetArguments(fn.args) 158 } 159 160 return call 161 } 162 163 func (fn *Function) compile(ctx runtime.ExecutionContextID) *runtime.CompileScriptArgs { 164 exp := fn.precompileExp() 165 166 call := runtime.NewCompileScriptArgs(exp, "", true) 167 168 if ctx != EmptyExecutionContextID { 169 call.SetExecutionContextID(ctx) 170 } 171 172 return call 173 } 174 175 func (fn *Function) prepExp() string { 176 var invoke bool 177 exp := strings.TrimSpace(fn.exp) 178 name := fn.name 179 180 // If the given expression is either an arrow or plain function 181 if strings.HasPrefix(exp, "(") || strings.HasPrefix(exp, "function") { 182 // And if this function must be an anonymous 183 // we just pass the expression as is without wrapping it. 184 if name == "" { 185 return exp 186 } 187 188 // But if the function must be identified (named) 189 // we need to wrap the given function with a named one/ 190 // And then eval it with passing available arguments. 191 invoke = true 192 } 193 194 // Start building a wrapper 195 var buf strings.Builder 196 buf.WriteString("function") 197 198 // Name the function if the name is set 199 if name != "" { 200 buf.WriteString(" ") 201 buf.WriteString(name) 202 } 203 204 buf.WriteString("(") 205 206 // If the given expression is a function then we do not need to define wrapper's function arguments. 207 // Any available arguments will be passed down via 'arguments' runtime variable. 208 // Otherwise, we define a list of arguments as argN, so the given expression could access them by name. 209 if !invoke { 210 args := len(fn.args) 211 lastIndex := args - 1 212 213 for i := 0; i < args; i++ { 214 buf.WriteString("arg") 215 buf.WriteString(strconv.Itoa(i + 1)) 216 217 if i != lastIndex { 218 buf.WriteString(",") 219 } 220 } 221 } 222 223 buf.WriteString(") {\n") 224 225 if !invoke { 226 buf.WriteString(exp) 227 } else { 228 buf.WriteString("const ") 229 buf.WriteString(expName) 230 buf.WriteString(" = ") 231 buf.WriteString(exp) 232 buf.WriteString(";\n") 233 buf.WriteString("return ") 234 buf.WriteString(expName) 235 buf.WriteString(".apply(this, arguments);") 236 } 237 238 buf.WriteString("\n}") 239 240 return buf.String() 241 } 242 243 func (fn *Function) precompileExp() string { 244 exp := fn.prepExp() 245 args := fn.args 246 247 var buf strings.Builder 248 var l = len(args) 249 250 buf.WriteString("const args = [") 251 252 if l > 0 { 253 for i := 0; i < l; i++ { 254 buf.WriteRune('\n') 255 256 arg := args[i] 257 258 if arg.Value != nil { 259 buf.Write(arg.Value) 260 } else if arg.ObjectID != nil { 261 buf.WriteString("(() => { throw new Error('Reference values cannot be used in pre-compiled scrips')})()") 262 } 263 264 buf.WriteString(",") 265 } 266 267 buf.WriteRune('\n') 268 } 269 270 buf.WriteString("];") 271 272 buf.WriteRune('\n') 273 buf.WriteString("const ") 274 buf.WriteString(compiledExpName) 275 buf.WriteString(" = ") 276 buf.WriteString(exp) 277 buf.WriteString(";\n") 278 buf.WriteString(compiledExpName) 279 buf.WriteString(".apply(this, args);") 280 buf.WriteString("\n") 281 282 return buf.String() 283 }