mirror of
https://github.com/MontFerret/ferret.git
synced 2024-12-14 11:23:02 +02:00
8e13cf9134
* Refactored input and select * WIP * Fixed serialization * Fixed scriolling * Fixed XPath result handling * Renamed some methods
357 lines
7.8 KiB
Go
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),
|
|
)
|
|
}
|