From 526916efb6598078c56279d780fd21f2482d4a9a Mon Sep 17 00:00:00 2001 From: Tim Voronov Date: Sat, 19 Dec 2020 13:42:57 -0500 Subject: [PATCH] Bugfix/#575 element children (#580) * Fixed retreival of child elements * Fixed formatting * Set tree depth to 0 * Removed redundant code --- e2e/tests/dynamic/element/children/count.fql | 8 + e2e/tests/dynamic/element/children/get.fql | 8 + .../dynamic/element/children/get_by_index.fql | 7 + e2e/tests/static/element/children/count.fql | 9 + e2e/tests/static/element/children/get.fql | 9 + .../static/element/children/get_by_index.fql | 9 + pkg/drivers/cdp/dom/document.go | 4 - pkg/drivers/cdp/dom/element.go | 565 ++++++------------ pkg/drivers/cdp/dom/helpers.go | 1 + pkg/drivers/cdp/templates/children.go | 23 + pkg/drivers/common/getter.go | 2 - pkg/drivers/http/document.go | 4 - pkg/drivers/http/element.go | 4 - pkg/drivers/value.go | 2 - pkg/runtime/values/array.go | 4 + pkg/runtime/values/helpers.go | 2 +- 16 files changed, 248 insertions(+), 413 deletions(-) create mode 100644 e2e/tests/dynamic/element/children/count.fql create mode 100644 e2e/tests/dynamic/element/children/get.fql create mode 100644 e2e/tests/dynamic/element/children/get_by_index.fql create mode 100644 e2e/tests/static/element/children/count.fql create mode 100644 e2e/tests/static/element/children/get.fql create mode 100644 e2e/tests/static/element/children/get_by_index.fql create mode 100644 pkg/drivers/cdp/templates/children.go diff --git a/e2e/tests/dynamic/element/children/count.fql b/e2e/tests/dynamic/element/children/count.fql new file mode 100644 index 00000000..be4f5767 --- /dev/null +++ b/e2e/tests/dynamic/element/children/count.fql @@ -0,0 +1,8 @@ +LET doc = DOCUMENT(@lab.cdn.dynamic + "/#/lists", { driver:"cdp" }) + +LET list = ELEMENT(doc, ".track-list") + +T::EQ(list.length, 20) +T::LEN(list, 20) + +RETURN NONE \ No newline at end of file diff --git a/e2e/tests/dynamic/element/children/get.fql b/e2e/tests/dynamic/element/children/get.fql new file mode 100644 index 00000000..87d231db --- /dev/null +++ b/e2e/tests/dynamic/element/children/get.fql @@ -0,0 +1,8 @@ +LET doc = DOCUMENT(@lab.cdn.dynamic + "/#/lists", { driver:"cdp" }) + +LET list = ELEMENT(doc, ".track-list") +LET children = list.children +T::NOT::NONE(children) +T::NOT::EMPTY(children) + +RETURN NONE \ No newline at end of file diff --git a/e2e/tests/dynamic/element/children/get_by_index.fql b/e2e/tests/dynamic/element/children/get_by_index.fql new file mode 100644 index 00000000..007af46e --- /dev/null +++ b/e2e/tests/dynamic/element/children/get_by_index.fql @@ -0,0 +1,7 @@ +LET doc = DOCUMENT(@lab.cdn.dynamic + "/#/lists", { driver:"cdp" }) + +LET list = ELEMENT(doc, ".track-list") +T::NOT::NONE(list.children[0]) +T::NOT::NONE(list.children[1]) + +RETURN NONE \ No newline at end of file diff --git a/e2e/tests/static/element/children/count.fql b/e2e/tests/static/element/children/count.fql new file mode 100644 index 00000000..ebc8d90d --- /dev/null +++ b/e2e/tests/static/element/children/count.fql @@ -0,0 +1,9 @@ +LET url = @lab.cdn.static + '/list.html' +LET doc = DOCUMENT(url) + +LET list = ELEMENT(doc, ".track-list") + +T::EQ(list.length, 20) +T::LEN(list, 20) + +RETURN NONE \ No newline at end of file diff --git a/e2e/tests/static/element/children/get.fql b/e2e/tests/static/element/children/get.fql new file mode 100644 index 00000000..2e2f7b32 --- /dev/null +++ b/e2e/tests/static/element/children/get.fql @@ -0,0 +1,9 @@ +LET url = @lab.cdn.static + '/list.html' +LET doc = DOCUMENT(url) + +LET list = ELEMENT(doc, ".track-list") +LET children = list.children +T::NOT::NONE(children) +T::NOT::EMPTY(children) + +RETURN NONE \ No newline at end of file diff --git a/e2e/tests/static/element/children/get_by_index.fql b/e2e/tests/static/element/children/get_by_index.fql new file mode 100644 index 00000000..01063bf9 --- /dev/null +++ b/e2e/tests/static/element/children/get_by_index.fql @@ -0,0 +1,9 @@ +LET url = @lab.cdn.static + '/list.html' +LET doc = DOCUMENT(url) + +LET list = ELEMENT(doc, ".track-list") + +T::NOT::NONE(list.children[0]) +T::NOT::NONE(list.children[1]) + +RETURN NONE \ No newline at end of file diff --git a/pkg/drivers/cdp/dom/document.go b/pkg/drivers/cdp/dom/document.go index ae7c359f..ce8b9c92 100644 --- a/pkg/drivers/cdp/dom/document.go +++ b/pkg/drivers/cdp/dom/document.go @@ -193,10 +193,6 @@ func (doc *HTMLDocument) Frame() page.FrameTree { return doc.frameTree } -func (doc *HTMLDocument) IsDetached() values.Boolean { - return doc.element.IsDetached() -} - func (doc *HTMLDocument) GetNodeType() values.Int { return 9 } diff --git a/pkg/drivers/cdp/dom/element.go b/pkg/drivers/cdp/dom/element.go index 316252e3..6dab151c 100644 --- a/pkg/drivers/cdp/dom/element.go +++ b/pkg/drivers/cdp/dom/element.go @@ -7,7 +7,6 @@ import ( "hash/fnv" "strconv" "strings" - "sync" "time" "github.com/mafredri/cdp" @@ -37,25 +36,15 @@ type ( ObjectID runtime.RemoteObjectID } - elementListeners struct { - pageReload events.ListenerID - childNodeInserted events.ListenerID - childNodeRemoved events.ListenerID - } - HTMLElement struct { - mu sync.Mutex - logger *zerolog.Logger - client *cdp.Client - dom *Manager - input *input.Manager - exec *eval.ExecutionContext - connected values.Boolean - id HTMLElementIdentity - nodeType html.NodeType - nodeName values.String - children []HTMLElementIdentity - listeners *elementListeners + logger *zerolog.Logger + client *cdp.Client + dom *Manager + input *input.Manager + exec *eval.ExecutionContext + id HTMLElementIdentity + nodeType html.NodeType + nodeName values.String } ) @@ -113,7 +102,7 @@ func LoadHTMLElementWithID( dom. NewDescribeNodeArgs(). SetObjectID(id.ObjectID). - SetDepth(1), + SetDepth(0), ) if err != nil { @@ -129,7 +118,6 @@ func LoadHTMLElementWithID( id, node.Node.NodeType, node.Node.NodeName, - createChildrenArray(node.Node.Children), ), nil } @@ -142,7 +130,6 @@ func NewHTMLElement( id HTMLElementIdentity, nodeType int, nodeName string, - children []HTMLElementIdentity, ) *HTMLElement { el := new(HTMLElement) el.logger = logger @@ -150,35 +137,14 @@ func NewHTMLElement( el.dom = domManager el.input = input el.exec = exec - el.connected = values.True el.id = id el.nodeType = common.ToHTMLType(nodeType) el.nodeName = values.NewString(nodeName) - el.children = children - el.listeners = &elementListeners{ - pageReload: domManager.AddDocumentUpdatedListener(el.handlePageReload), - childNodeInserted: domManager.AddChildNodeInsertedListener(el.handleChildInserted), - childNodeRemoved: domManager.AddChildNodeRemovedListener(el.handleChildRemoved), - } return el } func (el *HTMLElement) Close() error { - el.mu.Lock() - defer el.mu.Unlock() - - // already closed - if !el.connected { - return nil - } - - el.connected = values.False - - el.dom.RemoveReloadListener(el.listeners.pageReload) - el.dom.RemoveChildNodeInsertedListener(el.listeners.childNodeInserted) - el.dom.RemoveChildNodeRemovedListener(el.listeners.childNodeRemoved) - return nil } @@ -247,18 +213,10 @@ func (el *HTMLElement) SetIn(ctx context.Context, path []core.Value, value core. } func (el *HTMLElement) GetValue(ctx context.Context) (core.Value, error) { - if el.IsDetached() { - return values.None, drivers.ErrDetached - } - return el.exec.ReadProperty(ctx, el.id.ObjectID, "value") } func (el *HTMLElement) SetValue(ctx context.Context, value core.Value) error { - if el.IsDetached() { - return drivers.ErrDetached - } - return el.client.DOM.SetNodeValue(ctx, dom.NewSetNodeValueArgs(el.id.NodeID, value.String())) } @@ -271,14 +229,20 @@ func (el *HTMLElement) GetNodeName() values.String { } func (el *HTMLElement) Length() values.Int { - return values.NewInt(len(el.children)) + 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) { - if el.IsDetached() { - return values.NewObject(), drivers.ErrDetached - } - value, err := el.exec.EvalWithArgumentsAndReturnValue(ctx, templates.GetStyles(), runtime.CallArgument{ ObjectID: &el.id.ObjectID, }) @@ -295,10 +259,6 @@ func (el *HTMLElement) GetStyles(ctx context.Context) (*values.Object, error) { } func (el *HTMLElement) GetStyle(ctx context.Context, name values.String) (core.Value, error) { - if el.IsDetached() { - return values.None, drivers.ErrDetached - } - styles, err := el.GetStyles(ctx) if err != nil { @@ -315,10 +275,6 @@ func (el *HTMLElement) GetStyle(ctx context.Context, name values.String) (core.V } func (el *HTMLElement) SetStyles(ctx context.Context, styles *values.Object) error { - if el.IsDetached() { - return drivers.ErrDetached - } - if styles == nil { return nil } @@ -341,10 +297,6 @@ func (el *HTMLElement) SetStyles(ctx context.Context, styles *values.Object) err } func (el *HTMLElement) SetStyle(ctx context.Context, name, value values.String) error { - if el.IsDetached() { - return drivers.ErrDetached - } - // we manually set only those that are defined in attribute only attrValue, err := el.GetAttribute(ctx, common.AttrNameStyle) @@ -374,10 +326,6 @@ func (el *HTMLElement) SetStyle(ctx context.Context, name, value values.String) } func (el *HTMLElement) RemoveStyle(ctx context.Context, names ...values.String) error { - if el.IsDetached() { - return drivers.ErrDetached - } - if len(names) == 0 { return nil } @@ -409,10 +357,6 @@ func (el *HTMLElement) RemoveStyle(ctx context.Context, names ...values.String) } func (el *HTMLElement) GetAttributes(ctx context.Context) (*values.Object, error) { - if el.IsDetached() { - return values.NewObject(), drivers.ErrDetached - } - repl, err := el.client.DOM.GetAttributes(ctx, dom.NewGetAttributesArgs(el.id.NodeID)) if err != nil { @@ -446,10 +390,6 @@ func (el *HTMLElement) GetAttributes(ctx context.Context) (*values.Object, error } func (el *HTMLElement) GetAttribute(ctx context.Context, name values.String) (core.Value, error) { - if el.IsDetached() { - return values.None, drivers.ErrDetached - } - repl, err := el.client.DOM.GetAttributes(ctx, dom.NewGetAttributesArgs(el.id.NodeID)) if err != nil { @@ -483,10 +423,6 @@ func (el *HTMLElement) GetAttribute(ctx context.Context, name values.String) (co } func (el *HTMLElement) SetAttributes(ctx context.Context, attrs *values.Object) error { - if el.IsDetached() { - return drivers.ErrDetached - } - var err error attrs.ForEach(func(value core.Value, key string) bool { @@ -499,10 +435,6 @@ func (el *HTMLElement) SetAttributes(ctx context.Context, attrs *values.Object) } func (el *HTMLElement) SetAttribute(ctx context.Context, name, value values.String) error { - if el.IsDetached() { - return drivers.ErrDetached - } - return el.client.DOM.SetAttributeValue( ctx, dom.NewSetAttributeValueArgs(el.id.NodeID, string(name), string(value)), @@ -510,10 +442,6 @@ func (el *HTMLElement) SetAttribute(ctx context.Context, name, value values.Stri } func (el *HTMLElement) RemoveAttribute(ctx context.Context, names ...values.String) error { - if el.IsDetached() { - return drivers.ErrDetached - } - for _, name := range names { err := el.client.DOM.RemoveAttribute( ctx, @@ -529,51 +457,45 @@ func (el *HTMLElement) RemoveAttribute(ctx context.Context, names ...values.Stri } func (el *HTMLElement) GetChildNodes(ctx context.Context) (*values.Array, error) { - if el.IsDetached() { - return values.NewArray(0), drivers.ErrDetached + out, err := el.exec.EvalWithArgumentsAndReturnReference(ctx, templates.GetChildren(), + runtime.CallArgument{ + ObjectID: &el.id.ObjectID, + }, + ) + + if err != nil { + return values.EmptyArray(), err } - res := values.NewArray(len(el.children)) + val, err := el.convertEvalResult(ctx, out) - for _, childID := range el.children { - child, err := LoadHTMLElement( - ctx, - el.logger, - el.client, - el.dom, - el.input, - el.exec, - childID.NodeID, - ) + if err != nil { + el.logError(err) - if err != nil { - el.logError(err).Msg("failed to load child elements") - - continue - } - - res.Push(child) + return values.EmptyArray(), err } - return res, nil + 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) { - if el.IsDetached() { - return values.None, drivers.ErrDetached + out, err := el.exec.EvalWithArgumentsAndReturnReference(ctx, templates.GetChildByIndex(int64(idx)), + runtime.CallArgument{ + ObjectID: &el.id.ObjectID, + }, + ) + + if err != nil { + return values.None, err } - nodeIdentity := el.children[idx] - - return LoadHTMLElement( - ctx, - el.logger, - el.client, - el.dom, - el.input, - el.exec, - nodeIdentity.NodeID, - ) + return el.convertEvalResult(ctx, out) } func (el *HTMLElement) GetParentElement(ctx context.Context) (core.Value, error) { @@ -589,10 +511,6 @@ func (el *HTMLElement) GetNextElementSibling(ctx context.Context) (core.Value, e } func (el *HTMLElement) evalAndGetElement(ctx context.Context, expr string) (core.Value, error) { - if el.IsDetached() { - return values.None, drivers.ErrDetached - } - obj, err := el.exec.EvalWithArgumentsAndReturnReference(ctx, expr, runtime.CallArgument{ ObjectID: &el.id.ObjectID, }) @@ -626,10 +544,6 @@ func (el *HTMLElement) evalAndGetElement(ctx context.Context, expr string) (core } func (el *HTMLElement) QuerySelector(ctx context.Context, selector values.String) (core.Value, error) { - if el.IsDetached() { - return values.None, drivers.ErrDetached - } - selectorArgs := dom.NewQuerySelectorArgs(el.id.NodeID, selector.String()) found, err := el.client.DOM.QuerySelector(ctx, selectorArgs) @@ -659,15 +573,11 @@ func (el *HTMLElement) QuerySelector(ctx context.Context, selector values.String } func (el *HTMLElement) QuerySelectorAll(ctx context.Context, selector values.String) (*values.Array, error) { - if el.IsDetached() { - return values.NewArray(0), drivers.ErrDetached - } - selectorArgs := dom.NewQuerySelectorAllArgs(el.id.NodeID, selector.String()) res, err := el.client.DOM.QuerySelectorAll(ctx, selectorArgs) if err != nil { - return values.NewArray(0), err + return values.EmptyArray(), err } arr := values.NewArray(len(res.NodeIDs)) @@ -692,16 +602,7 @@ func (el *HTMLElement) QuerySelectorAll(ctx context.Context, selector values.Str ) if err != nil { - // close elements that are already loaded, but won't be used because of the error - if arr.Length() > 0 { - arr.ForEach(func(e core.Value, _ int) bool { - e.(*HTMLElement).Close() - - return true - }) - } - - return values.NewArray(0), err + return values.EmptyArray(), err } arr.Push(childEl) @@ -730,155 +631,18 @@ func (el *HTMLElement) XPath(ctx context.Context, expression values.String) (res return values.None, err } - typeName := out.Type - - // checking whether it's actually an array - if typeName == "object" { - if out.ClassName != nil && *out.ClassName == "Array" { - typeName = "array" - } - } - - 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)) - - defer func() { - if err != nil { - result.ForEach(func(value core.Value, idx int) bool { - el, ok := value.(*HTMLElement) - - if ok { - el.Close() - } - - return true - }) - } - }() - - 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 - } + return el.convertEvalResult(ctx, out) } func (el *HTMLElement) GetInnerText(ctx context.Context) (values.String, error) { - if el.IsDetached() { - return values.EmptyString, drivers.ErrDetached - } - return getInnerText(ctx, el.client, el.exec, el.id, el.nodeType) } func (el *HTMLElement) SetInnerText(ctx context.Context, innerText values.String) error { - if el.IsDetached() { - return drivers.ErrDetached - } - return setInnerText(ctx, el.client, el.exec, el.id, innerText) } func (el *HTMLElement) GetInnerTextBySelector(ctx context.Context, selector values.String) (values.String, error) { - if el.IsDetached() { - return values.EmptyString, drivers.ErrDetached - } - sel, err := selector.MarshalJSON() if err != nil { @@ -904,10 +668,6 @@ func (el *HTMLElement) GetInnerTextBySelector(ctx context.Context, selector valu } func (el *HTMLElement) SetInnerTextBySelector(ctx context.Context, selector, innerText values.String) error { - if el.IsDetached() { - return drivers.ErrDetached - } - sel, err := selector.MarshalJSON() if err != nil { @@ -936,10 +696,6 @@ func (el *HTMLElement) SetInnerTextBySelector(ctx context.Context, selector, inn } func (el *HTMLElement) GetInnerTextBySelectorAll(ctx context.Context, selector values.String) (*values.Array, error) { - if el.IsDetached() { - return values.NewArray(0), drivers.ErrDetached - } - sel, err := selector.MarshalJSON() if err != nil { @@ -958,39 +714,27 @@ func (el *HTMLElement) GetInnerTextBySelectorAll(ctx context.Context, selector v ) if err != nil { - return values.NewArray(0), err + return values.EmptyArray(), err } arr, ok := out.(*values.Array) if !ok { - return values.NewArray(0), errors.New("unexpected output") + return values.EmptyArray(), errors.New("unexpected output") } return arr, nil } func (el *HTMLElement) GetInnerHTML(ctx context.Context) (values.String, error) { - if el.IsDetached() { - return values.EmptyString, drivers.ErrDetached - } - return getInnerHTML(ctx, el.client, el.exec, el.id, el.nodeType) } func (el *HTMLElement) SetInnerHTML(ctx context.Context, innerHTML values.String) error { - if el.IsDetached() { - return drivers.ErrDetached - } - return setInnerHTML(ctx, el.client, el.exec, el.id, innerHTML) } func (el *HTMLElement) GetInnerHTMLBySelector(ctx context.Context, selector values.String) (values.String, error) { - if el.IsDetached() { - return values.EmptyString, drivers.ErrDetached - } - sel, err := selector.MarshalJSON() if err != nil { @@ -1016,10 +760,6 @@ func (el *HTMLElement) GetInnerHTMLBySelector(ctx context.Context, selector valu } func (el *HTMLElement) SetInnerHTMLBySelector(ctx context.Context, selector, innerHTML values.String) error { - if el.IsDetached() { - return drivers.ErrDetached - } - sel, err := selector.MarshalJSON() if err != nil { @@ -1048,14 +788,10 @@ func (el *HTMLElement) SetInnerHTMLBySelector(ctx context.Context, selector, inn } func (el *HTMLElement) GetInnerHTMLBySelectorAll(ctx context.Context, selector values.String) (*values.Array, error) { - if el.IsDetached() { - return values.NewArray(0), drivers.ErrDetached - } - sel, err := selector.MarshalJSON() if err != nil { - return values.NewArray(0), err + return values.EmptyArray(), err } out, err := el.exec.EvalWithArgumentsAndReturnValue( @@ -1070,23 +806,19 @@ func (el *HTMLElement) GetInnerHTMLBySelectorAll(ctx context.Context, selector v ) if err != nil { - return values.NewArray(0), err + return values.EmptyArray(), err } arr, ok := out.(*values.Array) if !ok { - return values.NewArray(0), errors.New("unexpected output") + return values.EmptyArray(), errors.New("unexpected output") } return arr, nil } func (el *HTMLElement) CountBySelector(ctx context.Context, selector values.String) (values.Int, error) { - if el.IsDetached() { - return values.ZeroInt, drivers.ErrDetached - } - selectorArgs := dom.NewQuerySelectorAllArgs(el.id.NodeID, selector.String()) res, err := el.client.DOM.QuerySelectorAll(ctx, selectorArgs) @@ -1098,11 +830,6 @@ func (el *HTMLElement) CountBySelector(ctx context.Context, selector values.Stri } func (el *HTMLElement) ExistsBySelector(ctx context.Context, selector values.String) (values.Boolean, error) { - if el.IsDetached() { - return values.False, drivers.ErrDetached - } - - // TODO: Can we use RemoteObjectID or BackendID instead of NodeId? selectorArgs := dom.NewQuerySelectorArgs(el.id.NodeID, selector.String()) res, err := el.client.DOM.QuerySelector(ctx, selectorArgs) @@ -1270,80 +997,126 @@ func (el *HTMLElement) HoverBySelector(ctx context.Context, selector values.Stri return el.input.MoveMouseBySelector(ctx, el.id.NodeID, selector.String()) } -func (el *HTMLElement) IsDetached() values.Boolean { - el.mu.Lock() - defer el.mu.Unlock() +func (el *HTMLElement) convertEvalResult(ctx context.Context, out runtime.RemoteObject) (core.Value, error) { + typeName := out.Type - return !el.connected -} - -func (el *HTMLElement) handlePageReload(_ context.Context) { - el.Close() -} - -func (el *HTMLElement) handleChildInserted(_ context.Context, parentNodeID, prevNodeID dom.NodeID, node dom.Node) { - if parentNodeID != el.id.NodeID { - return - } - - targetIDx := -1 - prevID := prevNodeID - nextID := node.NodeID - - if el.IsDetached() { - return - } - - el.mu.Lock() - defer el.mu.Unlock() - - for idx, id := range el.children { - if id.NodeID == prevID { - targetIDx = idx - break + // 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 + } } } - if targetIDx == -1 { - return - } - - nextIdentity := HTMLElementIdentity{ - NodeID: nextID, - } - - arr := el.children - el.children = append(arr[:targetIDx], append([]HTMLElementIdentity{nextIdentity}, arr[targetIDx:]...)...) -} - -func (el *HTMLElement) handleChildRemoved(_ context.Context, nodeID, prevNodeID dom.NodeID) { - if nodeID != el.id.NodeID { - return - } - - targetIDx := -1 - targetID := prevNodeID - - if el.IsDetached() { - return - } - - el.mu.Lock() - defer el.mu.Unlock() - - for idx, id := range el.children { - if id.NodeID == targetID { - targetIDx = idx - break + switch typeName { + case "string", "number", "boolean": + return eval.Unmarshal(&out) + case "array": + if out.ObjectID == nil { + return values.None, nil } - } - if targetIDx == -1 { - return - } + props, err := el.client.Runtime.GetProperties(ctx, runtime.NewGetPropertiesArgs(*out.ObjectID).SetOwnProperties(true)) - arr := el.children - el.children = append(arr[:targetIDx], arr[targetIDx+1:]...) + 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 { diff --git a/pkg/drivers/cdp/dom/helpers.go b/pkg/drivers/cdp/dom/helpers.go index 420a15eb..3888db50 100644 --- a/pkg/drivers/cdp/dom/helpers.go +++ b/pkg/drivers/cdp/dom/helpers.go @@ -203,6 +203,7 @@ func createChildrenArray(nodes []dom.Node) []HTMLElementIdentity { for idx, child := range nodes { child := child + children[idx] = HTMLElementIdentity{ NodeID: child.NodeID, } diff --git a/pkg/drivers/cdp/templates/children.go b/pkg/drivers/cdp/templates/children.go new file mode 100644 index 00000000..92c797bb --- /dev/null +++ b/pkg/drivers/cdp/templates/children.go @@ -0,0 +1,23 @@ +package templates + +import ( + "fmt" + "github.com/MontFerret/ferret/pkg/drivers/cdp/eval" +) + +const getChildren = "(el) => Array.from(el.children)" +const getChildrenCount = "(el) => el.children.length" + +func GetChildren() string { + return getChildren +} + +func GetChildrenCount() string { + return getChildrenCount +} + +func GetChildByIndex(idx int64) string { + return fmt.Sprintf(` + (el) => el.children[%s] +`, eval.ParamInt(idx)) +} diff --git a/pkg/drivers/common/getter.go b/pkg/drivers/common/getter.go index 3db55437..f06ba6c9 100644 --- a/pkg/drivers/common/getter.go +++ b/pkg/drivers/common/getter.go @@ -230,8 +230,6 @@ func GetInNode(ctx context.Context, node drivers.HTMLNode, path []core.Value) (c segment := segment.(values.String) switch segment { - case "isDetached": - return node.IsDetached(), nil case "nodeType": return node.GetNodeType(), nil case "nodeName": diff --git a/pkg/drivers/http/document.go b/pkg/drivers/http/document.go index d95cf3f7..5578a6e3 100644 --- a/pkg/drivers/http/document.go +++ b/pkg/drivers/http/document.go @@ -178,10 +178,6 @@ func (doc *HTMLDocument) XPath(ctx context.Context, expression values.String) (c return doc.element.XPath(ctx, expression) } -func (doc *HTMLDocument) IsDetached() values.Boolean { - return values.False -} - func (doc *HTMLDocument) GetTitle() values.String { title := doc.doc.Find("head > title") diff --git a/pkg/drivers/http/element.go b/pkg/drivers/http/element.go index 3243b2d3..2ec1feaf 100644 --- a/pkg/drivers/http/element.go +++ b/pkg/drivers/http/element.go @@ -86,10 +86,6 @@ func (el *HTMLElement) Copy() core.Value { return c } -func (el *HTMLElement) IsDetached() values.Boolean { - return values.True -} - func (el *HTMLElement) GetNodeType() values.Int { nodes := el.selection.Nodes diff --git a/pkg/drivers/value.go b/pkg/drivers/value.go index ab549f3c..16ab6d06 100644 --- a/pkg/drivers/value.go +++ b/pkg/drivers/value.go @@ -24,8 +24,6 @@ type ( collections.Measurable io.Closer - IsDetached() values.Boolean - GetNodeType() values.Int GetNodeName() values.String diff --git a/pkg/runtime/values/array.go b/pkg/runtime/values/array.go index 726dab9a..7b523eb3 100644 --- a/pkg/runtime/values/array.go +++ b/pkg/runtime/values/array.go @@ -22,6 +22,10 @@ type ( } ) +func EmptyArray() *Array { + return &Array{items: make([]core.Value, 0, 0)} +} + func NewArray(size int) *Array { return &Array{items: make([]core.Value, 0, size)} } diff --git a/pkg/runtime/values/helpers.go b/pkg/runtime/values/helpers.go index 2c3f04ff..54879b52 100644 --- a/pkg/runtime/values/helpers.go +++ b/pkg/runtime/values/helpers.go @@ -375,7 +375,7 @@ func ToArray(ctx context.Context, input core.Value) *Array { return arr default: - return NewArray(0) + return EmptyArray() } }