Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/#265 dom manipulations #329

Merged
merged 8 commits into from
Jul 11, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions e2e/pages/static/assets/analytics.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion e2e/pages/static/assets/jquery-3.3.1.slim.min.js

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions e2e/tests/dynamic/doc/inner_html/set.fql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
LET url = @dynamic
LET doc = DOCUMENT(url, true)

LET expected = `<span>Hello</span>`

INNER_HTML_SET(doc, "body", "<span>Hello</span>")

LET actual = INNER_HTML(doc, "body")

LET r1 = '(\s|\")'
LET r2 = '(\n|\s|\")'

RETURN EXPECT(REGEXP_REPLACE(expected, r1, ''), REGEXP_REPLACE(TRIM(actual), r2, ''))
14 changes: 14 additions & 0 deletions e2e/tests/dynamic/element/inner_html/set.fql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
LET url = @dynamic
LET doc = DOCUMENT(url, true)
LET el = ELEMENT(doc, "#index")

LET expected = "<span>This node was injected by Ferret</span>"
INNER_HTML_SET(el, "<span>This node was injected by Ferret</span>")
LET actual = INNER_HTML(el)

WAIT(100)

LET r1 = '(\s|\")'
LET r2 = '(\n|\s|\")'

RETURN EXPECT(REGEXP_REPLACE(expected, r1, ''), REGEXP_REPLACE(TRIM(actual), r2, ''))
File renamed without changes.
2 changes: 0 additions & 2 deletions pkg/drivers/cdp/document.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values"
)

const BlankPageURL = "about:blank"

type HTMLDocument struct {
logger *zerolog.Logger
client *cdp.Client
Expand Down
3 changes: 3 additions & 0 deletions pkg/drivers/cdp/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cdp
import (
"context"
"sync"
"time"

"github.com/mafredri/cdp"
"github.com/mafredri/cdp/devtool"
Expand All @@ -16,6 +17,8 @@ import (
)

const DriverName = "cdp"
const BlankPageURL = "about:blank"
const DefaultTimeout = 5000 * time.Millisecond

type Driver struct {
mu sync.Mutex
Expand Down
152 changes: 79 additions & 73 deletions pkg/drivers/cdp/element.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/MontFerret/ferret/pkg/drivers/cdp/templates"
"github.com/pkg/errors"
"golang.org/x/net/html"
"hash/fnv"
"strconv"
"strings"
Expand All @@ -16,6 +13,7 @@ import (
"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"
Expand All @@ -24,7 +22,9 @@ import (
"github.com/mafredri/cdp"
"github.com/mafredri/cdp/protocol/dom"
"github.com/mafredri/cdp/protocol/runtime"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"golang.org/x/net/html"
)

var emptyNodeID = dom.NodeID(0)
Expand All @@ -46,7 +46,7 @@ type (
id HTMLElementIdentity
nodeType html.NodeType
nodeName values.String
innerHTML values.String
innerHTML *common.LazyValue
innerText *common.LazyValue
value core.Value
attributes *common.LazyValue
Expand Down Expand Up @@ -118,7 +118,7 @@ func LoadHTMLElementWithID(
return nil, core.Error(err, strconv.Itoa(int(id.nodeID)))
}

innerHTML, err := loadInnerHTML(ctx, client, exec, id, common.ToHTMLType(node.Node.NodeType))
innerHTML, err := getInnerHTML(ctx, client, exec, id, common.ToHTMLType(node.Node.NodeType))

if err != nil {
return nil, core.Error(err, strconv.Itoa(int(id.nodeID)))
Expand Down Expand Up @@ -168,7 +168,7 @@ func NewHTMLElement(
el.id = id
el.nodeType = common.ToHTMLType(nodeType)
el.nodeName = values.NewString(nodeName)
el.innerHTML = innerHTML
el.innerHTML = common.NewVolatileValue(innerHTML, el.loadInnerHTML)
el.innerText = common.NewLazyValue(el.loadInnerText)
el.attributes = common.NewLazyValue(el.loadAttrs)
el.style = common.NewLazyValue(el.parseStyle)
Expand Down Expand Up @@ -222,17 +222,27 @@ func (el *HTMLElement) MarshalJSON() ([]byte, error) {
}

func (el *HTMLElement) String() string {
return el.GetInnerHTML(context.Background()).String()
ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout)

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)

ctx := context.Background()

return el.GetInnerHTML(ctx).Compare(other.GetInnerHTML(ctx))
return int64(strings.Compare(el.String(), other.String()))
default:
return drivers.Compare(el.Type(), other.Type())
}
Expand All @@ -243,14 +253,11 @@ func (el *HTMLElement) Unwrap() interface{} {
}

func (el *HTMLElement) Hash() uint64 {
el.mu.Lock()
defer el.mu.Unlock()

h := fnv.New64a()

h.Write([]byte(el.Type().String()))
h.Write([]byte(":"))
h.Write([]byte(el.innerHTML))
h.Write([]byte(strconv.Itoa(int(el.id.nodeID))))

return h.Sum64()
}
Expand Down Expand Up @@ -858,75 +865,80 @@ func (el *HTMLElement) InnerTextBySelectorAll(ctx context.Context, selector valu
return arr
}

func (el *HTMLElement) GetInnerHTML(_ context.Context) values.String {
el.mu.Lock()
defer el.mu.Unlock()
func (el *HTMLElement) GetInnerHTML(ctx context.Context) (values.String, error) {
val, err := el.innerHTML.Read(ctx)

if err != nil {
return values.EmptyString, err
}

if val == values.None {
return values.EmptyString, nil
}

return el.innerHTML
return val.(values.String), nil
}

func (el *HTMLElement) InnerHTMLBySelector(ctx context.Context, selector values.String) values.String {
func (el *HTMLElement) SetInnerHTML(ctx context.Context, innerHTML values.String) error {
if el.IsDetached() {
return values.EmptyString
return drivers.ErrDetached
}

el.innerHTML.Reset()

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
}

// TODO: Can we use RemoteObjectID or BackendID instead of NodeId?
found, err := el.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(el.id.nodeID, selector.String()))

if err != nil {
el.logError(err).
Str("selector", selector.String()).
Msg("failed to retrieve nodes by selector")

return values.EmptyString
return values.EmptyString, err
}

text, err := loadInnerHTMLByNodeID(ctx, el.client, el.exec, found.NodeID)
text, err := getInnerHTMLByNodeID(ctx, el.client, el.exec, found.NodeID)

if err != nil {
el.logError(err).
Str("selector", selector.String()).
Int("childNodeID", int(found.NodeID)).
Msg("failed to load inner HTML for found child el")
return values.EmptyString, err
}

return values.EmptyString
return text, nil
}

func (el *HTMLElement) SetInnerHTMLBySelector(ctx context.Context, selector, innerHTML values.String) error {
if el.IsDetached() {
return drivers.ErrDetached
}

return text
return el.exec.Eval(ctx, templates.SetInnerHTMLBySelector(selector.String(), innerHTML.String()))
}

func (el *HTMLElement) InnerHTMLBySelectorAll(ctx context.Context, selector values.String) *values.Array {
// TODO: Can we use RemoteObjectID or BackendID instead of NodeId?
func (el *HTMLElement) GetInnerHTMLBySelectorAll(ctx context.Context, selector values.String) (*values.Array, error) {
// TODO: Can we use RemoteObjectID instead of NodeId?
selectorArgs := dom.NewQuerySelectorAllArgs(el.id.nodeID, selector.String())
res, err := el.client.DOM.QuerySelectorAll(ctx, selectorArgs)

if err != nil {
el.logError(err).
Str("selector", selector.String()).
Msg("failed to retrieve nodes by selector")

return values.NewArray(0)
return values.NewArray(0), err
}

arr := values.NewArray(len(res.NodeIDs))

for _, id := range res.NodeIDs {
text, err := loadInnerHTMLByNodeID(ctx, el.client, el.exec, id)
text, err := getInnerHTMLByNodeID(ctx, el.client, el.exec, id)

if err != nil {
el.logError(err).
Str("selector", selector.String()).
Int("childNodeID", int(id)).
Msg("failed to load inner HTML for found child el")

// return what we have
return arr
return values.NewArray(0), err
}

arr.Push(text)
}

return arr
return arr, nil
}

func (el *HTMLElement) CountBySelector(ctx context.Context, selector values.String) values.Int {
Expand Down Expand Up @@ -1081,9 +1093,17 @@ func (el *HTMLElement) IsDetached() values.Boolean {
return !el.connected
}

func (el *HTMLElement) loadInnerHTML(ctx context.Context) (core.Value, error) {
if el.IsDetached() {
return values.None, drivers.ErrDetached
}

return getInnerHTML(ctx, el.client, el.exec, el.id, el.nodeType)
}

func (el *HTMLElement) loadInnerText(ctx context.Context) (core.Value, error) {
if !el.IsDetached() {
text, err := loadInnerText(ctx, el.client, el.exec, el.id, el.nodeType)
text, err := getInnerText(ctx, el.client, el.exec, el.id, el.nodeType)

if err == nil {
return text, nil
Expand All @@ -1094,7 +1114,13 @@ func (el *HTMLElement) loadInnerText(ctx context.Context) (core.Value, error) {
// and just parse cached innerHTML
}

h := el.GetInnerHTML(ctx)
h, err := el.GetInnerHTML(ctx)

if err != nil {
el.logError(err).Msg("failed to get inner html from remote object")

return values.None, err
}

if h == values.EmptyString {
return h, nil
Expand Down Expand Up @@ -1329,15 +1355,7 @@ func (el *HTMLElement) handleChildInserted(ctx context.Context, message interfac

loadedArr.Insert(values.NewInt(targetIDx), loadedEl)

newInnerHTML, err := loadInnerHTML(ctx, el.client, el.exec, el.id, el.nodeType)

if err != nil {
el.logError(err).Msg("failed to update element")

return
}

el.innerHTML = newInnerHTML
el.innerHTML.Reset()
el.innerText.Reset()
})
}
Expand Down Expand Up @@ -1391,19 +1409,7 @@ func (el *HTMLElement) handleChildRemoved(ctx context.Context, message interface
loadedArr := v.(*values.Array)
loadedArr.RemoveAt(values.NewInt(targetIDx))

newInnerHTML, err := loadInnerHTML(ctx, el.client, el.exec, el.id, el.nodeType)

if err != nil {
el.logger.Error().
Timestamp().
Err(err).
Int("nodeID", int(el.id.nodeID)).
Msg("failed to update element")

return
}

el.innerHTML = newInnerHTML
el.innerHTML.Reset()
el.innerText.Reset()
})
}
Expand Down
Loading