Revising ScrollIntoView Action

- Rewrote ScrollIntoView to be consistent with other query actions
This commit is contained in:
Kenneth Shaw 2017-02-28 08:35:00 +07:00
parent f85f3777fa
commit bf1d5ecfbc
5 changed files with 48 additions and 48 deletions

View File

@ -2,6 +2,7 @@ package chromedp
import ( import (
"context" "context"
"fmt"
"time" "time"
"github.com/knq/chromedp/cdp" "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 // MouseClickNode dispatches a mouse left button click event at the center of a
// specified node. // 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 { func MouseClickNode(n *cdp.Node, opts ...MouseOption) Action {
return ActionFunc(func(ctxt context.Context, h cdp.Handler) error { return ActionFunc(func(ctxt context.Context, h cdp.Handler) error {
var err 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 { if err != nil {
return err return err
} }

View File

@ -10,6 +10,16 @@ import (
"github.com/knq/chromedp/cdp/input" "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) { func TestMouseClickXY(t *testing.T) {
t.Parallel() t.Parallel()
@ -145,7 +155,7 @@ func TestMouseClickOffscreenNode(t *testing.T) {
} }
var ok bool 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 { if err != nil {
t.Fatalf("got error: %v", err) t.Fatalf("got error: %v", err)
} }

25
nav.go
View File

@ -3,7 +3,6 @@ package chromedp
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"github.com/knq/chromedp/cdp" "github.com/knq/chromedp/cdp"
"github.com/knq/chromedp/cdp/page" "github.com/knq/chromedp/cdp/page"
@ -131,27 +130,3 @@ func Title(title *string) Action {
return EvaluateAsDevTools(`document.title`, title) 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
})
}

View File

@ -492,3 +492,25 @@ func MatchedStyle(sel interface{}, style **css.GetMatchedStylesForNodeReturns, o
return nil return nil
}, opts...) }, 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...)
}

30
util.go
View File

@ -30,7 +30,7 @@ const (
} }
} }
return s; return s;
})($x("%s/node()"))` })($x('%s/node()'))`
// blurJS is a javscript snippet that blurs the specified element. // blurJS is a javscript snippet that blurs the specified element.
blurJS = `(function(a) { blurJS = `(function(a) {
@ -38,26 +38,6 @@ const (
return true; return true;
})($x('%s'))` })($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 // scrollJS is a javascript snippet that scrolls the window to the
// specified x, y coordinates and then returns the actual window x/y after // specified x, y coordinates and then returns the actual window x/y after
// execution. // execution.
@ -66,6 +46,14 @@ const (
return [window.scrollX, window.scrollY]; return [window.scrollX, window.scrollY];
})(%d, %d)` })(%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 // submitJS is a javascript snippet that will call the containing form's
// submit function, returning true or false if the call was successful. // submit function, returning true or false if the call was successful.
submitJS = `(function(a) { submitJS = `(function(a) {