github.com/MontFerret/ferret@v0.18.0/pkg/drivers/cdp/eval/runtime.go (about) 1 package eval 2 3 import ( 4 "context" 5 6 "github.com/mafredri/cdp" 7 "github.com/mafredri/cdp/protocol/page" 8 "github.com/mafredri/cdp/protocol/runtime" 9 "github.com/pkg/errors" 10 "github.com/rs/zerolog" 11 12 "github.com/MontFerret/ferret/pkg/drivers" 13 "github.com/MontFerret/ferret/pkg/runtime/core" 14 "github.com/MontFerret/ferret/pkg/runtime/logging" 15 "github.com/MontFerret/ferret/pkg/runtime/values" 16 ) 17 18 const ( 19 EmptyExecutionContextID = runtime.ExecutionContextID(-1) 20 EmptyObjectID = runtime.RemoteObjectID("") 21 ) 22 23 type Runtime struct { 24 logger zerolog.Logger 25 client *cdp.Client 26 frame page.Frame 27 contextID runtime.ExecutionContextID 28 resolver *Resolver 29 } 30 31 func Create( 32 ctx context.Context, 33 logger zerolog.Logger, 34 client *cdp.Client, 35 frameID page.FrameID, 36 ) (*Runtime, error) { 37 world, err := client.Page.CreateIsolatedWorld(ctx, page.NewCreateIsolatedWorldArgs(frameID)) 38 39 if err != nil { 40 return nil, err 41 } 42 43 return New(logger, client, frameID, world.ExecutionContextID), nil 44 } 45 46 func New( 47 logger zerolog.Logger, 48 client *cdp.Client, 49 frameID page.FrameID, 50 contextID runtime.ExecutionContextID, 51 ) *Runtime { 52 rt := new(Runtime) 53 rt.logger = logging. 54 WithName(logger.With(), "js-eval"). 55 Str("frame_id", string(frameID)). 56 Int("context_id", int(contextID)). 57 Logger() 58 rt.client = client 59 rt.contextID = contextID 60 rt.resolver = NewResolver(client.Runtime, frameID) 61 62 return rt 63 } 64 65 func (rt *Runtime) SetLoader(loader ValueLoader) *Runtime { 66 rt.resolver.SetLoader(loader) 67 68 return rt 69 } 70 71 func (rt *Runtime) ContextID() runtime.ExecutionContextID { 72 return rt.contextID 73 } 74 75 func (rt *Runtime) Eval(ctx context.Context, fn *Function) error { 76 _, err := rt.evalInternal(ctx, fn.returnNothing()) 77 78 return err 79 } 80 81 func (rt *Runtime) EvalRef(ctx context.Context, fn *Function) (runtime.RemoteObject, error) { 82 out, err := rt.evalInternal(ctx, fn.returnRef()) 83 84 if err != nil { 85 return runtime.RemoteObject{}, err 86 } 87 88 return out, nil 89 } 90 91 func (rt *Runtime) EvalValue(ctx context.Context, fn *Function) (core.Value, error) { 92 out, err := rt.evalInternal(ctx, fn.returnValue()) 93 94 if err != nil { 95 return values.None, err 96 } 97 98 return rt.resolver.ToValue(ctx, out) 99 } 100 101 func (rt *Runtime) EvalElement(ctx context.Context, fn *Function) (core.Value, error) { 102 ref, err := rt.EvalRef(ctx, fn) 103 104 if err != nil { 105 return nil, err 106 } 107 108 if ref.ObjectID == nil { 109 return values.None, nil 110 } 111 112 return rt.resolver.ToElement(ctx, ref) 113 } 114 115 func (rt *Runtime) EvalElements(ctx context.Context, fn *Function) (*values.Array, error) { 116 ref, err := rt.EvalRef(ctx, fn) 117 118 if err != nil { 119 return nil, err 120 } 121 122 val, err := rt.resolver.ToValue(ctx, ref) 123 124 if err != nil { 125 return nil, err 126 } 127 128 arr, ok := val.(*values.Array) 129 130 if ok { 131 return arr, nil 132 } 133 134 return values.NewArrayWith(val), nil 135 } 136 137 func (rt *Runtime) Compile(ctx context.Context, fn *Function) (*CompiledFunction, error) { 138 log := rt.logger.With(). 139 Str("expression", fn.String()). 140 Array("arguments", fn.args). 141 Logger() 142 143 arg := fn.compile(rt.contextID) 144 145 log.Trace().Str("script", arg.Expression).Msg("compiling expression...") 146 147 repl, err := rt.client.Runtime.CompileScript(ctx, arg) 148 149 if err != nil { 150 log.Trace().Err(err).Msg("failed compiling expression") 151 152 return nil, err 153 } 154 155 if err := parseRuntimeException(repl.ExceptionDetails); err != nil { 156 log.Trace().Err(err).Msg("compilation has failed with runtime exception") 157 158 return nil, err 159 } 160 161 if repl.ScriptID == nil { 162 log.Trace().Err(core.ErrUnexpected).Msg("compilation did not return script id") 163 164 return nil, core.ErrUnexpected 165 } 166 167 id := *repl.ScriptID 168 169 log.Trace(). 170 Str("script_id", string(id)). 171 Msg("succeeded compiling expression") 172 173 return CF(id, fn), nil 174 } 175 176 func (rt *Runtime) Call(ctx context.Context, fn *CompiledFunction) error { 177 _, err := rt.callInternal(ctx, fn.returnNothing()) 178 179 return err 180 } 181 182 func (rt *Runtime) CallRef(ctx context.Context, fn *CompiledFunction) (runtime.RemoteObject, error) { 183 out, err := rt.callInternal(ctx, fn.returnRef()) 184 185 if err != nil { 186 return runtime.RemoteObject{}, err 187 } 188 189 return out, nil 190 } 191 192 func (rt *Runtime) CallValue(ctx context.Context, fn *CompiledFunction) (core.Value, error) { 193 out, err := rt.callInternal(ctx, fn.returnValue()) 194 195 if err != nil { 196 return values.None, err 197 } 198 199 return rt.resolver.ToValue(ctx, out) 200 } 201 202 func (rt *Runtime) CallElement(ctx context.Context, fn *CompiledFunction) (drivers.HTMLElement, error) { 203 ref, err := rt.CallRef(ctx, fn) 204 205 if err != nil { 206 return nil, err 207 } 208 209 return rt.resolver.ToElement(ctx, ref) 210 } 211 212 func (rt *Runtime) CallElements(ctx context.Context, fn *CompiledFunction) (*values.Array, error) { 213 ref, err := rt.CallRef(ctx, fn) 214 215 if err != nil { 216 return nil, err 217 } 218 219 val, err := rt.resolver.ToValue(ctx, ref) 220 221 if err != nil { 222 return nil, err 223 } 224 225 arr, ok := val.(*values.Array) 226 227 if ok { 228 return arr, nil 229 } 230 231 return values.NewArrayWith(val), nil 232 } 233 234 func (rt *Runtime) evalInternal(ctx context.Context, fn *Function) (runtime.RemoteObject, error) { 235 log := rt.logger.With(). 236 Str("expression", fn.String()). 237 Str("returns", fn.returnType.String()). 238 Bool("is_async", fn.async). 239 Str("owner", string(fn.ownerID)). 240 Array("arguments", fn.args). 241 Logger() 242 243 log.Trace().Msg("executing expression...") 244 245 repl, err := rt.client.Runtime.CallFunctionOn(ctx, fn.eval(rt.contextID)) 246 247 if err != nil { 248 log.Trace().Err(err).Msg("failed executing expression") 249 250 return runtime.RemoteObject{}, errors.Wrap(err, "runtime evalInternal") 251 } 252 253 if err := parseRuntimeException(repl.ExceptionDetails); err != nil { 254 log.Trace().Err(err).Msg("expression has failed with runtime exception") 255 256 return runtime.RemoteObject{}, err 257 } 258 259 var className string 260 261 if repl.Result.ClassName != nil { 262 className = *repl.Result.ClassName 263 } 264 265 var subtype string 266 267 if repl.Result.Subtype != nil { 268 subtype = *repl.Result.Subtype 269 } 270 271 log.Trace(). 272 Str("returned_type", repl.Result.Type). 273 Str("returned_sub_type", subtype). 274 Str("returned_class_name", className). 275 Str("returned_value", string(repl.Result.Value)). 276 Msg("succeeded executing expression") 277 278 return repl.Result, nil 279 } 280 281 func (rt *Runtime) callInternal(ctx context.Context, fn *CompiledFunction) (runtime.RemoteObject, error) { 282 log := rt.logger.With(). 283 Str("script_id", string(fn.id)). 284 Str("returns", fn.src.returnType.String()). 285 Bool("is_async", fn.src.async). 286 Array("arguments", fn.src.args). 287 Logger() 288 289 log.Trace().Msg("executing compiled script...") 290 291 repl, err := rt.client.Runtime.RunScript(ctx, fn.call(rt.contextID)) 292 293 if err != nil { 294 log.Trace().Err(err).Msg("failed executing compiled script") 295 296 return runtime.RemoteObject{}, errors.Wrap(err, "runtime evalInternal") 297 } 298 299 if err := parseRuntimeException(repl.ExceptionDetails); err != nil { 300 log.Trace().Err(err).Msg("compiled script has failed with runtime exception") 301 302 return runtime.RemoteObject{}, err 303 } 304 305 var className string 306 307 if repl.Result.ClassName != nil { 308 className = *repl.Result.ClassName 309 } 310 311 var subtype string 312 313 if repl.Result.Subtype != nil { 314 subtype = *repl.Result.Subtype 315 } 316 317 log.Trace(). 318 Str("returned_type", repl.Result.Type). 319 Str("returned_sub_type", subtype). 320 Str("returned_class_name", className). 321 Str("returned_value", string(repl.Result.Value)). 322 Msg("succeeded executing compiled script") 323 324 return repl.Result, nil 325 }