1
0
mirror of https://github.com/MontFerret/ferret.git synced 2024-12-14 11:23:02 +02:00
ferret/pkg/drivers/cdp/eval/context.go
Tim Voronov 8e13cf9134
Refactored input and select (#331)
* Refactored input and select

* WIP

* Fixed serialization

* Fixed scriolling

* Fixed XPath result handling

* Renamed some methods
2019-07-16 18:17:42 -04:00

357 lines
7.8 KiB
Go

package eval
import (
"context"
"fmt"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/mafredri/cdp/protocol/dom"
"strings"
"github.com/mafredri/cdp"
"github.com/mafredri/cdp/protocol/page"
"github.com/mafredri/cdp/protocol/runtime"
"github.com/pkg/errors"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
const EmptyExecutionContextID = runtime.ExecutionContextID(-1)
type ExecutionContext struct {
client *cdp.Client
frame page.Frame
contextID runtime.ExecutionContextID
}
func NewExecutionContext(client *cdp.Client, frame page.Frame, contextID runtime.ExecutionContextID) *ExecutionContext {
ec := new(ExecutionContext)
ec.client = client
ec.frame = frame
ec.contextID = contextID
return ec
}
func (ec *ExecutionContext) ID() runtime.ExecutionContextID {
return ec.contextID
}
func (ec *ExecutionContext) Eval(ctx context.Context, exp string) error {
_, err := ec.evalWithValueInternal(
ctx,
runtime.
NewEvaluateArgs(PrepareEval(exp)),
)
return err
}
func (ec *ExecutionContext) EvalWithArguments(ctx context.Context, exp string, args ...runtime.CallArgument) error {
_, err := ec.evalWithArgumentsInternal(ctx, exp, args, false)
return err
}
func (ec *ExecutionContext) EvalWithArgumentsAndReturnValue(ctx context.Context, exp string, args ...runtime.CallArgument) (core.Value, error) {
out, err := ec.evalWithArgumentsInternal(ctx, exp, args, true)
if err != nil {
return values.None, err
}
return Unmarshal(&out)
}
func (ec *ExecutionContext) EvalWithArgumentsAndReturnReference(ctx context.Context, exp string, args ...runtime.CallArgument) (runtime.RemoteObject, error) {
return ec.evalWithArgumentsInternal(ctx, exp, args, false)
}
func (ec *ExecutionContext) EvalWithReturnValue(ctx context.Context, exp string) (core.Value, error) {
return ec.evalWithValueInternal(
ctx,
runtime.
NewEvaluateArgs(PrepareEval(exp)).
SetReturnByValue(true),
)
}
func (ec *ExecutionContext) EvalWithReturnReference(ctx context.Context, exp string) (runtime.RemoteObject, error) {
return ec.evalInternal(
ctx,
runtime.
NewEvaluateArgs(PrepareEval(exp)).
SetReturnByValue(false),
)
}
func (ec *ExecutionContext) EvalAsync(ctx context.Context, exp string) (core.Value, error) {
return ec.evalWithValueInternal(
ctx,
runtime.
NewEvaluateArgs(PrepareEval(exp)).
SetReturnByValue(true).
SetAwaitPromise(true),
)
}
func (ec *ExecutionContext) CallMethod(
ctx context.Context,
objectID runtime.RemoteObjectID,
methodName string,
args []runtime.CallArgument,
) (*runtime.RemoteObject, error) {
callArgs := runtime.NewCallFunctionOnArgs(methodName).
SetObjectID(objectID).
SetArguments(args)
if ec.contextID != EmptyExecutionContextID {
callArgs.SetExecutionContextID(ec.contextID)
}
found, err := ec.client.Runtime.CallFunctionOn(
ctx,
callArgs,
)
if err != nil {
return nil, err
}
err = ec.handleException(found.ExceptionDetails)
if err != nil {
return nil, err
}
if found.Result.ObjectID == nil {
return nil, nil
}
return &found.Result, nil
}
func (ec *ExecutionContext) ReadPropertyByNodeID(
ctx context.Context,
nodeID dom.NodeID,
propName string,
) (core.Value, error) {
obj, err := ec.client.DOM.ResolveNode(ctx, dom.NewResolveNodeArgs().SetNodeID(nodeID))
if err != nil {
return values.None, err
}
if obj.Object.ObjectID == nil {
return values.None, nil
}
return ec.ReadProperty(ctx, *obj.Object.ObjectID, propName)
}
func (ec *ExecutionContext) ReadProperty(
ctx context.Context,
objectID runtime.RemoteObjectID,
propName string,
) (core.Value, error) {
res, err := ec.client.Runtime.GetProperties(
ctx,
runtime.NewGetPropertiesArgs(objectID),
)
if err != nil {
return values.None, err
}
if res.ExceptionDetails != nil {
return values.None, res.ExceptionDetails
}
// all props
if propName == "" {
arr := values.NewArray(len(res.Result))
for _, prop := range res.Result {
val, err := Unmarshal(prop.Value)
if err != nil {
return values.None, err
}
arr.Push(val)
}
return arr, nil
}
for _, prop := range res.Result {
if prop.Name == propName {
return Unmarshal(prop.Value)
}
}
return values.None, nil
}
func (ec *ExecutionContext) DispatchEvent(
ctx context.Context,
objectID runtime.RemoteObjectID,
eventName string,
) (values.Boolean, error) {
args := runtime.NewEvaluateArgs(PrepareEval(fmt.Sprintf(`
return new window.MouseEvent('%s', { bubbles: true, cancelable: true })
`, eventName)))
if ec.contextID != EmptyExecutionContextID {
args.SetContextID(ec.contextID)
}
evt, err := ec.client.Runtime.Evaluate(ctx, args)
if err != nil {
return values.False, nil
}
err = ec.handleException(evt.ExceptionDetails)
if err != nil {
return values.False, nil
}
if evt.Result.ObjectID == nil {
return values.False, nil
}
evtID := evt.Result.ObjectID
// release the event object
defer ec.client.Runtime.ReleaseObject(ctx, runtime.NewReleaseObjectArgs(*evtID))
_, err = ec.CallMethod(
ctx,
objectID,
"dispatchEvent",
[]runtime.CallArgument{
{
ObjectID: evt.Result.ObjectID,
},
},
)
if err != nil {
return values.False, err
}
return values.True, nil
}
func (ec *ExecutionContext) ResolveRemoteObject(ctx context.Context, exp string) (runtime.RemoteObject, error) {
res, err := ec.evalInternal(ctx, runtime.NewEvaluateArgs(PrepareEval(exp)))
if err != nil {
return runtime.RemoteObject{}, err
}
if res.ObjectID == nil {
return runtime.RemoteObject{}, errors.Wrap(core.ErrUnexpected, "unable to resolve remote object")
}
return res, nil
}
func (ec *ExecutionContext) ResolveNode(ctx context.Context, nodeID dom.NodeID) (runtime.RemoteObject, error) {
args := dom.NewResolveNodeArgs().SetNodeID(nodeID)
if ec.contextID != EmptyExecutionContextID {
args.SetExecutionContextID(ec.contextID)
}
repl, err := ec.client.DOM.ResolveNode(ctx, args)
if err != nil {
return runtime.RemoteObject{}, err
}
if repl.Object.ObjectID == nil {
return runtime.RemoteObject{}, errors.Wrap(core.ErrUnexpected, "unable to resolve remote object")
}
return repl.Object, nil
}
func (ec *ExecutionContext) evalWithArgumentsInternal(ctx context.Context, exp string, args []runtime.CallArgument, ret bool) (runtime.RemoteObject, error) {
cfArgs := runtime.
NewCallFunctionOnArgs(exp).
SetArguments(args).
SetReturnByValue(ret)
if ec.contextID != EmptyExecutionContextID {
cfArgs.SetExecutionContextID(ec.contextID)
}
repl, err := ec.client.Runtime.CallFunctionOn(ctx, cfArgs)
if err != nil {
return runtime.RemoteObject{}, err
}
err = ec.handleException(repl.ExceptionDetails)
if err != nil {
return runtime.RemoteObject{}, err
}
return repl.Result, nil
}
func (ec *ExecutionContext) evalWithValueInternal(ctx context.Context, args *runtime.EvaluateArgs) (core.Value, error) {
obj, err := ec.evalInternal(ctx, args)
if err != nil {
return values.None, err
}
if obj.Type != "undefined" && obj.Type != "null" {
return values.Unmarshal(obj.Value)
}
return Unmarshal(&obj)
}
func (ec *ExecutionContext) evalInternal(ctx context.Context, args *runtime.EvaluateArgs) (runtime.RemoteObject, error) {
if ec.contextID != EmptyExecutionContextID {
args.SetContextID(ec.contextID)
}
out, err := ec.client.Runtime.Evaluate(ctx, args)
if err != nil {
return runtime.RemoteObject{}, err
}
err = ec.handleException(out.ExceptionDetails)
if err != nil {
return runtime.RemoteObject{}, err
}
return out.Result, nil
}
func (ec *ExecutionContext) handleException(details *runtime.ExceptionDetails) error {
if details == nil {
return nil
}
desc := *details.Exception.Description
if strings.Contains(desc, drivers.ErrNotFound.Error()) {
return drivers.ErrNotFound
}
return core.Error(
core.ErrUnexpected,
fmt.Sprintf("%s: %s", details.Text, desc),
)
}