mirror of
https://github.com/MontFerret/ferret.git
synced 2024-12-23 01:24:30 +02:00
7ce6797e9c
* Implemented XPath for CDP driver * Added XPATH function * Added e2e tests for CDP * Fixed linting issues * Added support of XPath to HTTP driver * Fixed linting issues
266 lines
5.6 KiB
Go
266 lines
5.6 KiB
Go
package eval
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"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) EvalWithValue(ctx context.Context, exp string) (core.Value, error) {
|
|
return ec.evalWithValueInternal(
|
|
ctx,
|
|
runtime.
|
|
NewEvaluateArgs(PrepareEval(exp)).
|
|
SetReturnByValue(true),
|
|
)
|
|
}
|
|
|
|
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) 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) 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
|
|
}
|
|
|
|
if found.ExceptionDetails != nil {
|
|
return nil, found.ExceptionDetails
|
|
}
|
|
|
|
if found.Result.ObjectID == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
return &found.Result, nil
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
if evt.ExceptionDetails != nil {
|
|
return values.False, evt.ExceptionDetails
|
|
}
|
|
|
|
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) CallFunction(ctx context.Context, declaration string, args ...runtime.CallArgument) (runtime.RemoteObject, error) {
|
|
cfArgs := runtime.NewCallFunctionOnArgs(declaration).SetArguments(args)
|
|
|
|
if ec.contextID != EmptyExecutionContextID {
|
|
cfArgs.SetExecutionContextID(ec.contextID)
|
|
}
|
|
|
|
repl, err := ec.client.Runtime.CallFunctionOn(ctx, cfArgs)
|
|
|
|
if err != nil {
|
|
return runtime.RemoteObject{}, err
|
|
}
|
|
|
|
if repl.ExceptionDetails != nil {
|
|
exception := *repl.ExceptionDetails
|
|
|
|
return runtime.RemoteObject{}, errors.New(exception.Error())
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
if out.ExceptionDetails != nil {
|
|
ex := out.ExceptionDetails
|
|
|
|
return runtime.RemoteObject{}, core.Error(
|
|
core.ErrUnexpected,
|
|
fmt.Sprintf("%s: %s", ex.Text, *ex.Exception.Description),
|
|
)
|
|
}
|
|
|
|
return out.Result, nil
|
|
}
|