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  }