diff --git a/input.go b/input.go index 5c578d1..91ae324 100644 --- a/input.go +++ b/input.go @@ -2,6 +2,7 @@ package chromedp import ( "context" + "fmt" "time" "github.com/knq/chromedp/cdp" @@ -51,11 +52,15 @@ func MouseClickXY(x, y int64, opts ...MouseOption) Action { // MouseClickNode dispatches a mouse left button click event at the center of a // specified node. +// +// Note that the window will be scrolled if the node is not within the window's +// viewport. func MouseClickNode(n *cdp.Node, opts ...MouseOption) Action { return ActionFunc(func(ctxt context.Context, h cdp.Handler) error { var err error - err = ScrollIntoNode(n).Do(ctxt, h) + var pos []int + err = EvaluateAsDevTools(fmt.Sprintf(scrollIntoViewJS, n.FullXPath()), &pos).Do(ctxt, h) if err != nil { return err } diff --git a/input_test.go b/input_test.go index 638909e..dc7bdb7 100644 --- a/input_test.go +++ b/input_test.go @@ -10,6 +10,16 @@ import ( "github.com/knq/chromedp/cdp/input" ) +const ( + // inViewportJS is a javascript snippet that will get the specified node + // position relative to the viewport and returns true if the specified node + // is within the window's viewport. + inViewportJS = `(function(a) { + var r = a[0].getBoundingClientRect(); + return r.top >= 0 && r.left >= 0 && r.bottom <= window.innerHeight && r.right <= window.innerWidth; + })($x('%s'))` +) + func TestMouseClickXY(t *testing.T) { t.Parallel() @@ -145,7 +155,7 @@ func TestMouseClickOffscreenNode(t *testing.T) { } var ok bool - err = c.Run(defaultContext, EvaluateAsDevTools(fmt.Sprintf(isOnViewJS, nodes[0].FullXPath()), &ok)) + err = c.Run(defaultContext, EvaluateAsDevTools(fmt.Sprintf(inViewportJS, nodes[0].FullXPath()), &ok)) if err != nil { t.Fatalf("got error: %v", err) } diff --git a/nav.go b/nav.go index 61b82e2..6904e11 100644 --- a/nav.go +++ b/nav.go @@ -3,7 +3,6 @@ package chromedp import ( "context" "errors" - "fmt" "github.com/knq/chromedp/cdp" "github.com/knq/chromedp/cdp/page" @@ -131,27 +130,3 @@ func Title(title *string) Action { return EvaluateAsDevTools(`document.title`, title) } - -// ScrollIntoNode scrolls the window to the specified node. -func ScrollIntoNode(n *cdp.Node, opts ...QueryOption) Action { - return ActionFunc(func(ctxt context.Context, h cdp.Handler) error { - var res bool - err := EvaluateAsDevTools(fmt.Sprintf(isOnViewJS, n.FullXPath()), &res).Do(ctxt, h) - if err != nil { - return err - } - if res { - return nil - } - - err = EvaluateAsDevTools(fmt.Sprintf(scrollIntoViewJS, n.FullXPath()), &res).Do(ctxt, h) - if err != nil { - return err - } - if !res { - return fmt.Errorf("could not scroll into node %d", n.NodeID) - } - - return nil - }) -} diff --git a/query.go b/query.go index 1aeb46c..2f6b349 100644 --- a/query.go +++ b/query.go @@ -492,3 +492,25 @@ func MatchedStyle(sel interface{}, style **css.GetMatchedStylesForNodeReturns, o return nil }, opts...) } + +// ScrollIntoView scrolls the window to the first node matching the selector. +func ScrollIntoView(sel interface{}, opts ...QueryOption) Action { + return QueryAfter(sel, func(ctxt context.Context, h cdp.Handler, nodes ...*cdp.Node) error { + if len(nodes) < 1 { + return fmt.Errorf("selector `%s` did not return any nodes", sel) + } + + var err error + var pos []int + err = EvaluateAsDevTools(fmt.Sprintf(scrollIntoViewJS, nodes[0].FullXPath()), &pos).Do(ctxt, h) + if err != nil { + return err + } + + if pos == nil { + return fmt.Errorf("could not scroll into node %d", nodes[0].NodeID) + } + + return nil + }, opts...) +} diff --git a/util.go b/util.go index ac643c2..ce470e4 100644 --- a/util.go +++ b/util.go @@ -30,7 +30,7 @@ const ( } } return s; - })($x("%s/node()"))` + })($x('%s/node()'))` // blurJS is a javscript snippet that blurs the specified element. blurJS = `(function(a) { @@ -38,26 +38,6 @@ const ( return true; })($x('%s'))` - // isOnViewJS is a javascript snippet that will get the specified node - // position relative to the viewport and returns true or false depending - // on if the specified node is on view port. - isOnViewJS = `(function(a) { - var rect = a[0].getBoundingClientRect(); - return ( - rect.top >= 0 && - rect.left >= 0 && - rect.bottom <= window.innerHeight && - rect.right <= window.innerWidth - ); - })($x('%s'))` - - // scrollIntoViewJS is a javascript snippet that scrolls the window to the - // specified node. - scrollIntoViewJS = `(function(a) { - a[0].scrollIntoView(true); - return true - })($x('%s'))` - // scrollJS is a javascript snippet that scrolls the window to the // specified x, y coordinates and then returns the actual window x/y after // execution. @@ -66,6 +46,14 @@ const ( return [window.scrollX, window.scrollY]; })(%d, %d)` + // scrollIntoViewJS is a javascript snippet that scrolls the specified node + // into the window's viewport (if needed), returning the actual window x/y + // after execution. + scrollIntoViewJS = `(function(a) { + a[0].scrollIntoViewIfNeeded(true); + return [window.scrollX, window.scrollY]; + })($x('%s'))` + // submitJS is a javascript snippet that will call the containing form's // submit function, returning true or false if the call was successful. submitJS = `(function(a) {