1
0
mirror of https://github.com/MontFerret/ferret.git synced 2025-07-03 00:46:51 +02:00
Files
ferret/pkg/drivers/cdp/dom/element.go
Tim Voronov 5119d62838 Feature/#478 keyboard events (#618)
* Added support of pressin special keys

* Linting
2021-04-23 10:42:31 -04:00

1138 lines
25 KiB
Go

package dom
import (
"context"
"encoding/json"
"fmt"
"hash/fnv"
"strconv"
"strings"
"time"
"github.com/mafredri/cdp"
"github.com/mafredri/cdp/protocol/dom"
"github.com/mafredri/cdp/protocol/runtime"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/wI2L/jettison"
"golang.org/x/net/html"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/drivers/cdp/eval"
"github.com/MontFerret/ferret/pkg/drivers/cdp/events"
"github.com/MontFerret/ferret/pkg/drivers/cdp/input"
"github.com/MontFerret/ferret/pkg/drivers/cdp/templates"
"github.com/MontFerret/ferret/pkg/drivers/common"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
var emptyNodeID = dom.NodeID(0)
type (
HTMLElementIdentity struct {
NodeID dom.NodeID
ObjectID runtime.RemoteObjectID
}
HTMLElement struct {
logger *zerolog.Logger
client *cdp.Client
dom *Manager
input *input.Manager
exec *eval.ExecutionContext
id HTMLElementIdentity
nodeType html.NodeType
nodeName values.String
}
)
func LoadHTMLElement(
ctx context.Context,
logger *zerolog.Logger,
client *cdp.Client,
domManager *Manager,
input *input.Manager,
exec *eval.ExecutionContext,
nodeID dom.NodeID,
) (*HTMLElement, error) {
if client == nil {
return nil, core.Error(core.ErrMissedArgument, "client")
}
// getting a remote object that represents the current DOM Node
args := dom.NewResolveNodeArgs().SetNodeID(nodeID).SetExecutionContextID(exec.ID())
obj, err := client.DOM.ResolveNode(ctx, args)
if err != nil {
return nil, err
}
if obj.Object.ObjectID == nil {
return nil, core.Error(core.ErrNotFound, fmt.Sprintf("element %d", nodeID))
}
return LoadHTMLElementWithID(
ctx,
logger,
client,
domManager,
input,
exec,
HTMLElementIdentity{
NodeID: nodeID,
ObjectID: *obj.Object.ObjectID,
},
)
}
func LoadHTMLElementWithID(
ctx context.Context,
logger *zerolog.Logger,
client *cdp.Client,
domManager *Manager,
input *input.Manager,
exec *eval.ExecutionContext,
id HTMLElementIdentity,
) (*HTMLElement, error) {
node, err := client.DOM.DescribeNode(
ctx,
dom.
NewDescribeNodeArgs().
SetObjectID(id.ObjectID).
SetDepth(0),
)
if err != nil {
return nil, core.Error(err, strconv.Itoa(int(id.NodeID)))
}
return NewHTMLElement(
logger,
client,
domManager,
input,
exec,
id,
node.Node.NodeType,
node.Node.NodeName,
), nil
}
func NewHTMLElement(
logger *zerolog.Logger,
client *cdp.Client,
domManager *Manager,
input *input.Manager,
exec *eval.ExecutionContext,
id HTMLElementIdentity,
nodeType int,
nodeName string,
) *HTMLElement {
el := new(HTMLElement)
el.logger = logger
el.client = client
el.dom = domManager
el.input = input
el.exec = exec
el.id = id
el.nodeType = common.ToHTMLType(nodeType)
el.nodeName = values.NewString(nodeName)
return el
}
func (el *HTMLElement) Close() error {
return nil
}
func (el *HTMLElement) Type() core.Type {
return drivers.HTMLElementType
}
func (el *HTMLElement) MarshalJSON() ([]byte, error) {
return jettison.MarshalOpts(el.String(), jettison.NoHTMLEscaping())
}
func (el *HTMLElement) String() string {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(drivers.DefaultWaitTimeout)*time.Millisecond)
defer cancel()
res, err := el.GetInnerHTML(ctx)
if err != nil {
el.logError(errors.Wrap(err, "HTMLElement.String"))
return ""
}
return res.String()
}
func (el *HTMLElement) Compare(other core.Value) int64 {
switch other.Type() {
case drivers.HTMLElementType:
other := other.(drivers.HTMLElement)
return int64(strings.Compare(el.String(), other.String()))
default:
return drivers.Compare(el.Type(), other.Type())
}
}
func (el *HTMLElement) Unwrap() interface{} {
return el
}
func (el *HTMLElement) Hash() uint64 {
h := fnv.New64a()
h.Write([]byte(el.Type().String()))
h.Write([]byte(":"))
h.Write([]byte(strconv.Itoa(int(el.id.NodeID))))
return h.Sum64()
}
func (el *HTMLElement) Copy() core.Value {
return values.None
}
func (el *HTMLElement) Iterate(_ context.Context) (core.Iterator, error) {
return common.NewIterator(el)
}
func (el *HTMLElement) GetIn(ctx context.Context, path []core.Value) (core.Value, error) {
return common.GetInElement(ctx, el, path)
}
func (el *HTMLElement) SetIn(ctx context.Context, path []core.Value, value core.Value) error {
return common.SetInElement(ctx, el, path, value)
}
func (el *HTMLElement) GetValue(ctx context.Context) (core.Value, error) {
return el.exec.ReadProperty(ctx, el.id.ObjectID, "value")
}
func (el *HTMLElement) SetValue(ctx context.Context, value core.Value) error {
return el.client.DOM.SetNodeValue(ctx, dom.NewSetNodeValueArgs(el.id.NodeID, value.String()))
}
func (el *HTMLElement) GetNodeType() values.Int {
return values.NewInt(common.FromHTMLType(el.nodeType))
}
func (el *HTMLElement) GetNodeName() values.String {
return el.nodeName
}
func (el *HTMLElement) Length() values.Int {
value, err := el.exec.EvalWithArgumentsAndReturnValue(context.Background(), templates.GetChildrenCount(), runtime.CallArgument{
ObjectID: &el.id.ObjectID,
})
if err != nil {
el.logError(err)
return 0
}
return values.ToInt(value)
}
func (el *HTMLElement) GetStyles(ctx context.Context) (*values.Object, error) {
value, err := el.exec.EvalWithArgumentsAndReturnValue(ctx, templates.GetStyles(), runtime.CallArgument{
ObjectID: &el.id.ObjectID,
})
if err != nil {
return values.NewObject(), err
}
if value.Type() == types.Object {
return value.(*values.Object), err
}
return values.NewObject(), core.TypeError(value.Type(), types.Object)
}
func (el *HTMLElement) GetStyle(ctx context.Context, name values.String) (core.Value, error) {
styles, err := el.GetStyles(ctx)
if err != nil {
return values.None, err
}
val, found := styles.Get(name)
if !found {
return values.None, nil
}
return val, nil
}
func (el *HTMLElement) SetStyles(ctx context.Context, styles *values.Object) error {
if styles == nil {
return nil
}
currentStyles, err := el.GetStyles(ctx)
if err != nil {
return err
}
styles.ForEach(func(value core.Value, key string) bool {
currentStyles.Set(values.NewString(key), value)
return true
})
str := common.SerializeStyles(ctx, currentStyles)
return el.SetAttribute(ctx, common.AttrNameStyle, str)
}
func (el *HTMLElement) SetStyle(ctx context.Context, name, value values.String) error {
// we manually set only those that are defined in attribute only
attrValue, err := el.GetAttribute(ctx, common.AttrNameStyle)
if err != nil {
return err
}
var styles *values.Object
if attrValue == values.None {
styles = values.NewObject()
} else {
styleAttr, ok := attrValue.(*values.Object)
if !ok {
return core.TypeError(attrValue.Type(), types.Object)
}
styles = styleAttr
}
styles.Set(name, value)
str := common.SerializeStyles(ctx, styles)
return el.SetAttribute(ctx, common.AttrNameStyle, str)
}
func (el *HTMLElement) RemoveStyle(ctx context.Context, names ...values.String) error {
if len(names) == 0 {
return nil
}
value, err := el.GetAttribute(ctx, common.AttrNameStyle)
if err != nil {
return err
}
// no attribute
if value == values.None {
return nil
}
styles, ok := value.(*values.Object)
if !ok {
return core.TypeError(styles.Type(), types.Object)
}
for _, name := range names {
styles.Remove(name)
}
str := common.SerializeStyles(ctx, styles)
return el.SetAttribute(ctx, common.AttrNameStyle, str)
}
func (el *HTMLElement) GetAttributes(ctx context.Context) (*values.Object, error) {
repl, err := el.client.DOM.GetAttributes(ctx, dom.NewGetAttributesArgs(el.id.NodeID))
if err != nil {
return values.NewObject(), err
}
attrs := values.NewObject()
traverseAttrs(repl.Attributes, func(name, value string) bool {
key := values.NewString(name)
var val core.Value = values.None
if name != common.AttrNameStyle {
val = values.NewString(value)
} else {
parsed, err := common.DeserializeStyles(values.NewString(value))
if err == nil {
val = parsed
} else {
val = values.NewObject()
}
}
attrs.Set(key, val)
return true
})
return attrs, nil
}
func (el *HTMLElement) GetAttribute(ctx context.Context, name values.String) (core.Value, error) {
repl, err := el.client.DOM.GetAttributes(ctx, dom.NewGetAttributesArgs(el.id.NodeID))
if err != nil {
return values.None, err
}
var result core.Value = values.None
targetName := strings.ToLower(name.String())
traverseAttrs(repl.Attributes, func(name, value string) bool {
if name == targetName {
if name != common.AttrNameStyle {
result = values.NewString(value)
} else {
parsed, err := common.DeserializeStyles(values.NewString(value))
if err == nil {
result = parsed
} else {
result = values.NewObject()
}
}
return false
}
return true
})
return result, nil
}
func (el *HTMLElement) SetAttributes(ctx context.Context, attrs *values.Object) error {
var err error
attrs.ForEach(func(value core.Value, key string) bool {
err = el.SetAttribute(ctx, values.NewString(key), values.NewString(value.String()))
return err == nil
})
return err
}
func (el *HTMLElement) SetAttribute(ctx context.Context, name, value values.String) error {
return el.client.DOM.SetAttributeValue(
ctx,
dom.NewSetAttributeValueArgs(el.id.NodeID, string(name), string(value)),
)
}
func (el *HTMLElement) RemoveAttribute(ctx context.Context, names ...values.String) error {
for _, name := range names {
err := el.client.DOM.RemoveAttribute(
ctx,
dom.NewRemoveAttributeArgs(el.id.NodeID, name.String()),
)
if err != nil {
return err
}
}
return nil
}
func (el *HTMLElement) GetChildNodes(ctx context.Context) (*values.Array, error) {
out, err := el.exec.EvalWithArgumentsAndReturnReference(ctx, templates.GetChildren(),
runtime.CallArgument{
ObjectID: &el.id.ObjectID,
},
)
if err != nil {
return values.EmptyArray(), err
}
val, err := el.convertEvalResult(ctx, out)
if err != nil {
el.logError(err)
return values.EmptyArray(), err
}
arr, ok := val.(*values.Array)
if ok {
return arr, nil
}
return values.EmptyArray(), nil
}
func (el *HTMLElement) GetChildNode(ctx context.Context, idx values.Int) (core.Value, error) {
out, err := el.exec.EvalWithArgumentsAndReturnReference(ctx, templates.GetChildByIndex(int64(idx)),
runtime.CallArgument{
ObjectID: &el.id.ObjectID,
},
)
if err != nil {
return values.None, err
}
return el.convertEvalResult(ctx, out)
}
func (el *HTMLElement) GetParentElement(ctx context.Context) (core.Value, error) {
return el.evalAndGetElement(ctx, templates.GetParent())
}
func (el *HTMLElement) GetPreviousElementSibling(ctx context.Context) (core.Value, error) {
return el.evalAndGetElement(ctx, templates.GetPreviousElementSibling())
}
func (el *HTMLElement) GetNextElementSibling(ctx context.Context) (core.Value, error) {
return el.evalAndGetElement(ctx, templates.GetNextElementSibling())
}
func (el *HTMLElement) evalAndGetElement(ctx context.Context, expr string) (core.Value, error) {
obj, err := el.exec.EvalWithArgumentsAndReturnReference(ctx, expr, runtime.CallArgument{
ObjectID: &el.id.ObjectID,
})
if err != nil {
return values.None, err
}
if obj.Type != "object" || obj.ObjectID == nil {
return values.None, nil
}
repl, err := el.client.DOM.RequestNode(ctx, dom.NewRequestNodeArgs(*obj.ObjectID))
if err != nil {
return values.None, err
}
return LoadHTMLElementWithID(
ctx,
el.logger,
el.client,
el.dom,
el.input,
el.exec,
HTMLElementIdentity{
NodeID: repl.NodeID,
ObjectID: *obj.ObjectID,
},
)
}
func (el *HTMLElement) QuerySelector(ctx context.Context, selector values.String) (core.Value, error) {
selectorArgs := dom.NewQuerySelectorArgs(el.id.NodeID, selector.String())
found, err := el.client.DOM.QuerySelector(ctx, selectorArgs)
if err != nil {
return values.None, err
}
if found.NodeID == emptyNodeID {
return values.None, nil
}
res, err := LoadHTMLElement(
ctx,
el.logger,
el.client,
el.dom,
el.input,
el.exec,
found.NodeID,
)
if err != nil {
return values.None, nil
}
return res, nil
}
func (el *HTMLElement) QuerySelectorAll(ctx context.Context, selector values.String) (*values.Array, error) {
selectorArgs := dom.NewQuerySelectorAllArgs(el.id.NodeID, selector.String())
res, err := el.client.DOM.QuerySelectorAll(ctx, selectorArgs)
if err != nil {
return values.EmptyArray(), err
}
arr := values.NewArray(len(res.NodeIDs))
for _, id := range res.NodeIDs {
if id == emptyNodeID {
el.logError(err).
Str("selector", selector.String()).
Msg("failed to find a node by selector. returned 0 NodeID")
continue
}
childEl, err := LoadHTMLElement(
ctx,
el.logger,
el.client,
el.dom,
el.input,
el.exec,
id,
)
if err != nil {
return values.EmptyArray(), err
}
arr.Push(childEl)
}
return arr, nil
}
func (el *HTMLElement) XPath(ctx context.Context, expression values.String) (result core.Value, err error) {
exp, err := expression.MarshalJSON()
if err != nil {
return values.None, err
}
out, err := el.exec.EvalWithArgumentsAndReturnReference(ctx, templates.XPath(),
runtime.CallArgument{
ObjectID: &el.id.ObjectID,
},
runtime.CallArgument{
Value: exp,
},
)
if err != nil {
return values.None, err
}
return el.convertEvalResult(ctx, out)
}
func (el *HTMLElement) GetInnerText(ctx context.Context) (values.String, error) {
return getInnerText(ctx, el.client, el.exec, el.id, el.nodeType)
}
func (el *HTMLElement) SetInnerText(ctx context.Context, innerText values.String) error {
return setInnerText(ctx, el.client, el.exec, el.id, innerText)
}
func (el *HTMLElement) GetInnerTextBySelector(ctx context.Context, selector values.String) (values.String, error) {
sel, err := selector.MarshalJSON()
if err != nil {
return values.EmptyString, err
}
out, err := el.exec.EvalWithArgumentsAndReturnValue(
ctx,
templates.GetInnerTextBySelector(),
runtime.CallArgument{
ObjectID: &el.id.ObjectID,
},
runtime.CallArgument{
Value: sel,
},
)
if err != nil {
return values.EmptyString, err
}
return values.NewString(out.String()), nil
}
func (el *HTMLElement) SetInnerTextBySelector(ctx context.Context, selector, innerText values.String) error {
sel, err := selector.MarshalJSON()
if err != nil {
return err
}
val, err := innerText.MarshalJSON()
if err != nil {
return err
}
return el.exec.EvalWithArguments(
ctx,
templates.SetInnerTextBySelector(),
runtime.CallArgument{
ObjectID: &el.id.ObjectID,
},
runtime.CallArgument{
Value: sel,
},
runtime.CallArgument{
Value: val,
},
)
}
func (el *HTMLElement) GetInnerTextBySelectorAll(ctx context.Context, selector values.String) (*values.Array, error) {
sel, err := selector.MarshalJSON()
if err != nil {
return nil, err
}
out, err := el.exec.EvalWithArgumentsAndReturnValue(
ctx,
templates.GetInnerTextBySelectorAll(),
runtime.CallArgument{
ObjectID: &el.id.ObjectID,
},
runtime.CallArgument{
Value: sel,
},
)
if err != nil {
return values.EmptyArray(), err
}
arr, ok := out.(*values.Array)
if !ok {
return values.EmptyArray(), errors.New("unexpected output")
}
return arr, nil
}
func (el *HTMLElement) GetInnerHTML(ctx context.Context) (values.String, error) {
return getInnerHTML(ctx, el.client, el.exec, el.id, el.nodeType)
}
func (el *HTMLElement) SetInnerHTML(ctx context.Context, innerHTML values.String) error {
return setInnerHTML(ctx, el.client, el.exec, el.id, innerHTML)
}
func (el *HTMLElement) GetInnerHTMLBySelector(ctx context.Context, selector values.String) (values.String, error) {
sel, err := selector.MarshalJSON()
if err != nil {
return values.EmptyString, err
}
out, err := el.exec.EvalWithArgumentsAndReturnValue(
ctx,
templates.GetInnerHTMLBySelector(),
runtime.CallArgument{
ObjectID: &el.id.ObjectID,
},
runtime.CallArgument{
Value: sel,
},
)
if err != nil {
return values.EmptyString, err
}
return values.NewString(out.String()), nil
}
func (el *HTMLElement) SetInnerHTMLBySelector(ctx context.Context, selector, innerHTML values.String) error {
sel, err := selector.MarshalJSON()
if err != nil {
return err
}
val, err := innerHTML.MarshalJSON()
if err != nil {
return err
}
return el.exec.EvalWithArguments(
ctx,
templates.SetInnerHTMLBySelector(),
runtime.CallArgument{
ObjectID: &el.id.ObjectID,
},
runtime.CallArgument{
Value: sel,
},
runtime.CallArgument{
Value: val,
},
)
}
func (el *HTMLElement) GetInnerHTMLBySelectorAll(ctx context.Context, selector values.String) (*values.Array, error) {
sel, err := selector.MarshalJSON()
if err != nil {
return values.EmptyArray(), err
}
out, err := el.exec.EvalWithArgumentsAndReturnValue(
ctx,
templates.GetInnerHTMLBySelectorAll(),
runtime.CallArgument{
ObjectID: &el.id.ObjectID,
},
runtime.CallArgument{
Value: sel,
},
)
if err != nil {
return values.EmptyArray(), err
}
arr, ok := out.(*values.Array)
if !ok {
return values.EmptyArray(), errors.New("unexpected output")
}
return arr, nil
}
func (el *HTMLElement) CountBySelector(ctx context.Context, selector values.String) (values.Int, error) {
selectorArgs := dom.NewQuerySelectorAllArgs(el.id.NodeID, selector.String())
res, err := el.client.DOM.QuerySelectorAll(ctx, selectorArgs)
if err != nil {
return values.ZeroInt, err
}
return values.NewInt(len(res.NodeIDs)), nil
}
func (el *HTMLElement) ExistsBySelector(ctx context.Context, selector values.String) (values.Boolean, error) {
selectorArgs := dom.NewQuerySelectorArgs(el.id.NodeID, selector.String())
res, err := el.client.DOM.QuerySelector(ctx, selectorArgs)
if err != nil {
return values.False, err
}
if res.NodeID == 0 {
return values.False, nil
}
return values.True, nil
}
func (el *HTMLElement) WaitForClass(ctx context.Context, class values.String, when drivers.WaitEvent) error {
task := events.NewWaitTask(
func(ctx2 context.Context) (core.Value, error) {
current, err := el.GetAttribute(ctx2, "class")
if err != nil {
return values.None, nil
}
if current.Type() != types.String {
return values.None, nil
}
str := current.(values.String)
classStr := string(class)
classes := strings.Split(string(str), " ")
if when != drivers.WaitEventAbsence {
for _, c := range classes {
if c == classStr {
// The value does not really matter if it's not None
// None indicates that operation needs to be repeated
return values.True, nil
}
}
} else {
var found values.Boolean
for _, c := range classes {
if c == classStr {
found = values.True
break
}
}
if found == values.False {
// The value does not really matter if it's not None
// None indicates that operation needs to be repeated
return values.False, nil
}
}
return values.None, nil
},
events.DefaultPolling,
)
_, err := task.Run(ctx)
return err
}
func (el *HTMLElement) WaitForAttribute(
ctx context.Context,
name values.String,
value core.Value,
when drivers.WaitEvent,
) error {
task := events.NewValueWaitTask(when, value, func(ctx context.Context) (core.Value, error) {
return el.GetAttribute(ctx, name)
}, events.DefaultPolling)
_, err := task.Run(ctx)
return err
}
func (el *HTMLElement) WaitForStyle(ctx context.Context, name values.String, value core.Value, when drivers.WaitEvent) error {
task := events.NewValueWaitTask(when, value, func(ctx context.Context) (core.Value, error) {
return el.GetStyle(ctx, name)
}, events.DefaultPolling)
_, err := task.Run(ctx)
return err
}
func (el *HTMLElement) Click(ctx context.Context, count values.Int) error {
return el.input.Click(ctx, el.id.ObjectID, int(count))
}
func (el *HTMLElement) ClickBySelector(ctx context.Context, selector values.String, count values.Int) error {
return el.input.ClickBySelector(ctx, el.id.NodeID, selector.String(), int(count))
}
func (el *HTMLElement) ClickBySelectorAll(ctx context.Context, selector values.String, count values.Int) error {
return el.input.ClickBySelectorAll(ctx, el.id.NodeID, selector.String(), int(count))
}
func (el *HTMLElement) Input(ctx context.Context, value core.Value, delay values.Int) error {
if el.GetNodeName() != "INPUT" {
return core.Error(core.ErrInvalidOperation, "element is not an <input> element.")
}
return el.input.Type(ctx, el.id.ObjectID, input.TypeParams{
Text: value.String(),
Clear: false,
Delay: time.Duration(delay) * time.Millisecond,
})
}
func (el *HTMLElement) InputBySelector(ctx context.Context, selector values.String, value core.Value, delay values.Int) error {
return el.input.TypeBySelector(ctx, el.id.NodeID, selector.String(), input.TypeParams{
Text: value.String(),
Clear: false,
Delay: time.Duration(delay) * time.Millisecond,
})
}
func (el *HTMLElement) Press(ctx context.Context, keys []values.String, count values.Int) error {
return el.input.Press(ctx, values.UnwrapStrings(keys), int(count))
}
func (el *HTMLElement) PressBySelector(ctx context.Context, selector values.String, keys []values.String, count values.Int) error {
return el.input.PressBySelector(ctx, el.id.NodeID, selector.String(), values.UnwrapStrings(keys), int(count))
}
func (el *HTMLElement) Clear(ctx context.Context) error {
return el.input.Clear(ctx, el.id.ObjectID)
}
func (el *HTMLElement) ClearBySelector(ctx context.Context, selector values.String) error {
return el.input.ClearBySelector(ctx, el.id.NodeID, selector.String())
}
func (el *HTMLElement) Select(ctx context.Context, value *values.Array) (*values.Array, error) {
return el.input.Select(ctx, el.id.ObjectID, value)
}
func (el *HTMLElement) SelectBySelector(ctx context.Context, selector values.String, value *values.Array) (*values.Array, error) {
return el.input.SelectBySelector(ctx, el.id.NodeID, selector.String(), value)
}
func (el *HTMLElement) ScrollIntoView(ctx context.Context, options drivers.ScrollOptions) error {
return el.input.ScrollIntoView(ctx, el.id.ObjectID, options)
}
func (el *HTMLElement) Focus(ctx context.Context) error {
return el.input.Focus(ctx, el.id.ObjectID)
}
func (el *HTMLElement) FocusBySelector(ctx context.Context, selector values.String) error {
return el.input.FocusBySelector(ctx, el.id.NodeID, selector.String())
}
func (el *HTMLElement) Blur(ctx context.Context) error {
return el.input.Blur(ctx, el.id.ObjectID)
}
func (el *HTMLElement) BlurBySelector(ctx context.Context, selector values.String) error {
return el.input.BlurBySelector(ctx, el.id.ObjectID, selector.String())
}
func (el *HTMLElement) Hover(ctx context.Context) error {
return el.input.MoveMouse(ctx, el.id.ObjectID)
}
func (el *HTMLElement) HoverBySelector(ctx context.Context, selector values.String) error {
return el.input.MoveMouseBySelector(ctx, el.id.NodeID, selector.String())
}
func (el *HTMLElement) convertEvalResult(ctx context.Context, out runtime.RemoteObject) (core.Value, error) {
typeName := out.Type
// checking whether it's actually an array
if typeName == "object" {
if out.ClassName != nil {
switch *out.ClassName {
case "Array":
typeName = "array"
case "HTMLCollection":
typeName = "HTMLCollection"
default:
break
}
}
}
switch typeName {
case "string", "number", "boolean":
return eval.Unmarshal(&out)
case "array":
if out.ObjectID == nil {
return values.None, nil
}
props, err := el.client.Runtime.GetProperties(ctx, runtime.NewGetPropertiesArgs(*out.ObjectID).SetOwnProperties(true))
if err != nil {
return values.None, err
}
if props.ExceptionDetails != nil {
exception := *props.ExceptionDetails
return values.None, errors.New(exception.Text)
}
result := values.NewArray(len(props.Result))
for _, descr := range props.Result {
if !descr.Enumerable {
continue
}
if descr.Value == nil {
continue
}
// it's not a Node, it's an attr value
if descr.Value.ObjectID == nil {
var value interface{}
if err := json.Unmarshal(descr.Value.Value, &value); err != nil {
return values.None, err
}
result.Push(values.Parse(value))
continue
}
repl, err := el.client.DOM.RequestNode(ctx, dom.NewRequestNodeArgs(*descr.Value.ObjectID))
if err != nil {
return values.None, err
}
el, err := LoadHTMLElementWithID(
ctx,
el.logger,
el.client,
el.dom,
el.input,
el.exec,
HTMLElementIdentity{
NodeID: repl.NodeID,
ObjectID: *descr.Value.ObjectID,
},
)
if err != nil {
return values.None, err
}
result.Push(el)
}
return result, nil
case "object":
if out.ObjectID == nil {
var value interface{}
if err := json.Unmarshal(out.Value, &value); err != nil {
return values.None, err
}
return values.Parse(value), nil
}
repl, err := el.client.DOM.RequestNode(ctx, dom.NewRequestNodeArgs(*out.ObjectID))
if err != nil {
return values.None, err
}
return LoadHTMLElementWithID(
ctx,
el.logger,
el.client,
el.dom,
el.input,
el.exec,
HTMLElementIdentity{
NodeID: repl.NodeID,
ObjectID: *out.ObjectID,
},
)
default:
return values.None, nil
}
}
func (el *HTMLElement) logError(err error) *zerolog.Event {
return el.logger.
Error().
Timestamp().
Int("nodeID", int(el.id.NodeID)).
Str("objectID", string(el.id.ObjectID)).
Err(err)
}