mirror of
https://github.com/MontFerret/ferret.git
synced 2025-09-16 09:06:36 +02:00
Feature/#177 hover (#178)
This commit is contained in:
@@ -4,7 +4,8 @@
|
||||
#### Added
|
||||
- DateTime functions.
|
||||
- ``PAGINATION`` function.
|
||||
- ``SCROLL_TOP`` and ``SCROLL_BOTTOM`` functions.
|
||||
- ``SCROLL_TOP``, ``SCROLL_BOTTOM`` and ``SCROLL_ELEMENT`` functions.
|
||||
- ``HOVER`` function.
|
||||
|
||||
#### Fixed
|
||||
- Unable to define variables and make function calls before FILTER, SORT and etc statements.
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import Layout from './layout.js';
|
||||
import IndexPage from './pages/index.js';
|
||||
import FormsPage from './pages/forms/index.js';
|
||||
import EventsPage from './pages/events/index.js';
|
||||
|
||||
const e = React.createElement;
|
||||
const Router = ReactRouter.Router;
|
||||
@@ -21,7 +22,11 @@ export default function AppComponent({ redirect = null}) {
|
||||
e(Route, {
|
||||
path: '/forms',
|
||||
component: FormsPage
|
||||
})
|
||||
}),
|
||||
e(Route, {
|
||||
path: '/events',
|
||||
component: EventsPage
|
||||
}),
|
||||
]),
|
||||
redirect ? e(Redirect, { to: redirect }) : null
|
||||
])
|
||||
|
52
e2e/pages/dynamic/components/pages/events/hoverable.js
Normal file
52
e2e/pages/dynamic/components/pages/events/hoverable.js
Normal file
@@ -0,0 +1,52 @@
|
||||
const e = React.createElement;
|
||||
|
||||
export default class HoverableComponent extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
hovered: false
|
||||
};
|
||||
}
|
||||
|
||||
handleMouseEnter() {
|
||||
this.setState({
|
||||
hovered: true
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseLeave() {
|
||||
this.setState({
|
||||
hovered: false
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const children = [];
|
||||
children.push(
|
||||
e("p", null, [
|
||||
e("a", {
|
||||
id: "hoverable-btn",
|
||||
className: "btn btn-primary",
|
||||
href: "#",
|
||||
onMouseEnter: this.handleMouseEnter.bind(this),
|
||||
onMouseLeave: this.handleMouseLeave.bind(this)
|
||||
}, [
|
||||
"Hoverable link"
|
||||
]),
|
||||
])
|
||||
);
|
||||
|
||||
if (this.state.hovered) {
|
||||
children.push(
|
||||
e("div", null, [
|
||||
e("div", { id: "hoverable-content", className: "card card-body"}, [
|
||||
"Lorem ipsum dolor sit amet."
|
||||
])
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
return e("div", null, children);
|
||||
}
|
||||
}
|
13
e2e/pages/dynamic/components/pages/events/index.js
Normal file
13
e2e/pages/dynamic/components/pages/events/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import Hoverable from "./hoverable.js";
|
||||
|
||||
const e = React.createElement;
|
||||
|
||||
export default class EventsPage extends React.Component {
|
||||
render() {
|
||||
return e("div", { className: "row", id: "page-events" }, [
|
||||
e("div", { className: "col-lg-12"}, [
|
||||
e(Hoverable)
|
||||
])
|
||||
])
|
||||
}
|
||||
}
|
@@ -45,7 +45,7 @@ export default class FormsPage extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
return e("form", null, [
|
||||
return e("form", { id: "page-form" }, [
|
||||
e("div", { className: "form-group" }, [
|
||||
e("label", null, "Text input"),
|
||||
e("input", {
|
||||
|
11
e2e/tests/doc_hover_d.fql
Normal file
11
e2e/tests/doc_hover_d.fql
Normal file
@@ -0,0 +1,11 @@
|
||||
LET url = @dynamic + "?redirect=/events"
|
||||
LET doc = DOCUMENT(url, true)
|
||||
|
||||
WAIT_ELEMENT(doc, "#page-events")
|
||||
|
||||
HOVER(doc, "#hoverable-btn")
|
||||
WAIT_ELEMENT(doc, "#hoverable-content")
|
||||
|
||||
LET output = INNER_TEXT(doc, "#hoverable-content")
|
||||
|
||||
RETURN EXPECT(output, "Lorem ipsum dolor sit amet.")
|
13
e2e/tests/el_hover_d.fql
Normal file
13
e2e/tests/el_hover_d.fql
Normal file
@@ -0,0 +1,13 @@
|
||||
LET url = @dynamic + "?redirect=/events"
|
||||
LET doc = DOCUMENT(url, true)
|
||||
|
||||
WAIT_ELEMENT(doc, "#page-events")
|
||||
|
||||
LET input = ELEMENT(doc, "#hoverable-btn")
|
||||
|
||||
HOVER(input)
|
||||
WAIT_ELEMENT(doc, "#hoverable-content")
|
||||
|
||||
LET output = ELEMENT(doc, "#hoverable-content")
|
||||
|
||||
RETURN EXPECT(output.innerText, "Lorem ipsum dolor sit amet.")
|
@@ -3,6 +3,7 @@ package dynamic
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/mafredri/cdp/protocol/dom"
|
||||
"hash/fnv"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -531,6 +532,45 @@ func (doc *HTMLDocument) SelectBySelector(selector values.String, value *values.
|
||||
return nil, core.TypeError(core.ArrayType, res.Type())
|
||||
}
|
||||
|
||||
func (doc *HTMLDocument) HoverBySelector(selector values.String) error {
|
||||
ctx, cancel := contextWithTimeout()
|
||||
defer cancel()
|
||||
|
||||
err := doc.ScrollBySelector(selector)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
selectorArgs := dom.NewQuerySelectorArgs(doc.element.id.nodeID, selector.String())
|
||||
found, err := doc.client.DOM.QuerySelector(ctx, selectorArgs)
|
||||
|
||||
if err != nil {
|
||||
doc.element.logError(err).
|
||||
Str("selector", selector.String()).
|
||||
Msg("failed to retrieve a node by selector")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if found.NodeID <= 0 {
|
||||
return errors.New("element not found")
|
||||
}
|
||||
|
||||
q, err := getClickablePoint(ctx, doc.client, &HTMLElementIdentity{
|
||||
nodeID: found.NodeID,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return doc.client.Input.DispatchMouseEvent(
|
||||
ctx,
|
||||
input.NewDispatchMouseEventArgs("mouseMoved", q.X, q.Y),
|
||||
)
|
||||
}
|
||||
|
||||
func (doc *HTMLDocument) WaitForSelector(selector values.String, timeout values.Int) error {
|
||||
task := events.NewEvalWaitTask(
|
||||
doc.client,
|
||||
@@ -815,7 +855,7 @@ func (doc *HTMLDocument) ScrollTop() error {
|
||||
window.scrollTo({
|
||||
left: 0,
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
behavior: 'instant'
|
||||
});
|
||||
`, false, false)
|
||||
|
||||
@@ -827,13 +867,32 @@ func (doc *HTMLDocument) ScrollBottom() error {
|
||||
window.scrollTo({
|
||||
left: 0,
|
||||
top: window.document.body.scrollHeight,
|
||||
behavior: 'smooth'
|
||||
behavior: 'instant'
|
||||
});
|
||||
`, false, false)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (doc *HTMLDocument) ScrollBySelector(selector values.String) error {
|
||||
_, err := eval.Eval(doc.client, fmt.Sprintf(`
|
||||
var el = document.querySelector(%s);
|
||||
|
||||
if (el == null) {
|
||||
throw new Error("element not found");
|
||||
}
|
||||
|
||||
el.scrollIntoView({
|
||||
behavior: 'instant'
|
||||
});
|
||||
|
||||
return true;
|
||||
`, eval.ParamString(selector.String()),
|
||||
), false, false)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (doc *HTMLDocument) handlePageLoad(_ interface{}) {
|
||||
doc.Lock()
|
||||
defer doc.Unlock()
|
||||
|
@@ -27,8 +27,6 @@ const DefaultTimeout = time.Second * 30
|
||||
|
||||
var emptyNodeID = dom.NodeID(0)
|
||||
var emptyBackendID = dom.BackendNodeID(0)
|
||||
var emptyObjectID = ""
|
||||
var attrID = "data-ferret-id"
|
||||
|
||||
type (
|
||||
HTMLElementIdentity struct {
|
||||
@@ -439,36 +437,6 @@ func (el *HTMLElement) QuerySelectorAll(selector values.String) core.Value {
|
||||
return arr
|
||||
}
|
||||
|
||||
func (el *HTMLElement) WaitForClass(class values.String, timeout values.Int) error {
|
||||
task := events.NewWaitTask(
|
||||
func() (core.Value, error) {
|
||||
current := el.GetAttribute("class")
|
||||
|
||||
if current.Type() != core.StringType {
|
||||
return values.None, nil
|
||||
}
|
||||
|
||||
str := current.(values.String)
|
||||
classStr := string(class)
|
||||
classes := strings.Split(string(str), " ")
|
||||
|
||||
for _, c := range classes {
|
||||
if c == classStr {
|
||||
return values.True, nil
|
||||
}
|
||||
}
|
||||
|
||||
return values.None, nil
|
||||
},
|
||||
time.Millisecond*time.Duration(timeout),
|
||||
events.DefaultPolling,
|
||||
)
|
||||
|
||||
_, err := task.Run()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (el *HTMLElement) InnerText() values.String {
|
||||
val, err := el.innerText.Read()
|
||||
|
||||
@@ -707,6 +675,36 @@ func (el *HTMLElement) CountBySelector(selector values.String) values.Int {
|
||||
return values.NewInt(len(res.NodeIDs))
|
||||
}
|
||||
|
||||
func (el *HTMLElement) WaitForClass(class values.String, timeout values.Int) error {
|
||||
task := events.NewWaitTask(
|
||||
func() (core.Value, error) {
|
||||
current := el.GetAttribute("class")
|
||||
|
||||
if current.Type() != core.StringType {
|
||||
return values.None, nil
|
||||
}
|
||||
|
||||
str := current.(values.String)
|
||||
classStr := string(class)
|
||||
classes := strings.Split(string(str), " ")
|
||||
|
||||
for _, c := range classes {
|
||||
if c == classStr {
|
||||
return values.True, nil
|
||||
}
|
||||
}
|
||||
|
||||
return values.None, nil
|
||||
},
|
||||
time.Millisecond*time.Duration(timeout),
|
||||
events.DefaultPolling,
|
||||
)
|
||||
|
||||
_, err := task.Run()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (el *HTMLElement) Click() (values.Boolean, error) {
|
||||
ctx, cancel := contextWithTimeout()
|
||||
|
||||
@@ -749,6 +747,8 @@ func (el *HTMLElement) Input(value core.Value, delay values.Int) error {
|
||||
}
|
||||
|
||||
func (el *HTMLElement) Select(value *values.Array) (*values.Array, error) {
|
||||
var attrID = "data-ferret-select"
|
||||
|
||||
if el.NodeName() != "SELECT" {
|
||||
return nil, core.Error(core.ErrInvalidOperation, "Element is not a <select> element.")
|
||||
}
|
||||
@@ -807,6 +807,8 @@ func (el *HTMLElement) Select(value *values.Array) (*values.Array, error) {
|
||||
false,
|
||||
)
|
||||
|
||||
el.client.DOM.RemoveAttribute(ctx, dom.NewRemoveAttributeArgs(el.id.nodeID, attrID))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -820,6 +822,68 @@ func (el *HTMLElement) Select(value *values.Array) (*values.Array, error) {
|
||||
return nil, core.TypeError(core.ArrayType, res.Type())
|
||||
}
|
||||
|
||||
func (el *HTMLElement) ScrollIntoView() error {
|
||||
var attrID = "data-ferret-scroll"
|
||||
|
||||
id, err := uuid.NewV4()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := contextWithTimeout()
|
||||
defer cancel()
|
||||
|
||||
err = el.client.DOM.SetAttributeValue(ctx, dom.NewSetAttributeValueArgs(el.id.nodeID, attrID, id.String()))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = eval.Eval(el.client, fmt.Sprintf(`
|
||||
var el = document.querySelector('[%s="%s"]');
|
||||
|
||||
if (el == null) {
|
||||
throw new Error('element not found');
|
||||
}
|
||||
|
||||
el.scrollIntoView({
|
||||
behavior: 'instant',
|
||||
inline: 'center',
|
||||
block: 'center'
|
||||
});
|
||||
`,
|
||||
attrID,
|
||||
id.String(),
|
||||
), false, false)
|
||||
|
||||
el.client.DOM.RemoveAttribute(ctx, dom.NewRemoveAttributeArgs(el.id.nodeID, attrID))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (el *HTMLElement) Hover() error {
|
||||
err := el.ScrollIntoView()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := contextWithTimeout()
|
||||
defer cancel()
|
||||
|
||||
q, err := getClickablePoint(ctx, el.client, el.id)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return el.client.Input.DispatchMouseEvent(
|
||||
ctx,
|
||||
input.NewDispatchMouseEventArgs("mouseMoved", q.X, q.Y),
|
||||
)
|
||||
}
|
||||
|
||||
func (el *HTMLElement) IsConnected() values.Boolean {
|
||||
el.mu.Lock()
|
||||
defer el.mu.Unlock()
|
||||
|
@@ -14,10 +14,18 @@ import (
|
||||
"github.com/mafredri/cdp/protocol/page"
|
||||
"github.com/mafredri/cdp/protocol/runtime"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"math"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type batchFunc = func() error
|
||||
type (
|
||||
batchFunc = func() error
|
||||
|
||||
Quad struct {
|
||||
X float64
|
||||
Y float64
|
||||
}
|
||||
)
|
||||
|
||||
func runBatch(funcs ...batchFunc) error {
|
||||
eg := errgroup.Group{}
|
||||
@@ -39,6 +47,90 @@ func getRootElement(ctx context.Context, client *cdp.Client) (*dom.GetDocumentRe
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func fromProtocolQuad(quad dom.Quad) []Quad {
|
||||
return []Quad{
|
||||
{
|
||||
X: quad[0],
|
||||
Y: quad[1],
|
||||
},
|
||||
{
|
||||
X: quad[2],
|
||||
Y: quad[3],
|
||||
},
|
||||
{
|
||||
X: quad[4],
|
||||
Y: quad[5],
|
||||
},
|
||||
{
|
||||
X: quad[6],
|
||||
Y: quad[7],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func computeQuadArea(quads []Quad) float64 {
|
||||
var area float64
|
||||
|
||||
for i := range quads {
|
||||
p1 := quads[i]
|
||||
p2 := quads[(i+1)%len(quads)]
|
||||
area += (p1.X*p2.Y - p2.X*p1.Y) / 2
|
||||
}
|
||||
|
||||
return math.Abs(area)
|
||||
}
|
||||
|
||||
func getClickablePoint(ctx context.Context, client *cdp.Client, id *HTMLElementIdentity) (Quad, error) {
|
||||
qargs := dom.NewGetContentQuadsArgs()
|
||||
|
||||
if id.objectID != "" {
|
||||
qargs.SetObjectID(id.objectID)
|
||||
} else if id.backendID != 0 {
|
||||
qargs.SetBackendNodeID(id.backendID)
|
||||
} else {
|
||||
qargs.SetNodeID(id.nodeID)
|
||||
}
|
||||
|
||||
res, err := client.DOM.GetContentQuads(ctx, qargs)
|
||||
|
||||
if err != nil {
|
||||
return Quad{}, err
|
||||
}
|
||||
|
||||
if res.Quads == nil || len(res.Quads) == 0 {
|
||||
return Quad{}, errors.New("node is either not visible or not an HTMLElement")
|
||||
}
|
||||
|
||||
quads := make([][]Quad, 0, len(res.Quads))
|
||||
|
||||
for _, q := range res.Quads {
|
||||
quad := fromProtocolQuad(q)
|
||||
|
||||
if computeQuadArea(quad) > 1 {
|
||||
quads = append(quads, quad)
|
||||
}
|
||||
}
|
||||
|
||||
if len(quads) == 0 {
|
||||
return Quad{}, errors.New("node is either not visible or not an HTMLElement")
|
||||
}
|
||||
|
||||
// Return the middle point of the first quad.
|
||||
quad := quads[0]
|
||||
var x float64
|
||||
var y float64
|
||||
|
||||
for _, q := range quad {
|
||||
x += q.X
|
||||
y += q.Y
|
||||
}
|
||||
|
||||
return Quad{
|
||||
X: x / 4,
|
||||
Y: y / 4,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseAttrs(attrs []string) *values.Object {
|
||||
var attr values.String
|
||||
|
||||
|
55
pkg/stdlib/html/hover.go
Normal file
55
pkg/stdlib/html/hover.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package html
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/html/dynamic"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
// Hover fetches an element with selector, scrolls it into view if needed, and then uses page.mouse to hover over the center of the element.
|
||||
// If there's no element matching selector, the method returns an error.
|
||||
// @param docOrEl (HTMLDocument|HTMLElement) - Target document or element.
|
||||
// @param selector (String, options) - If document is passed, this param must represent an element selector.
|
||||
func Hover(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 1, 2)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
// document or element
|
||||
err = core.ValidateType(args[0], core.HTMLDocumentType, core.HTMLElementType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
if len(args) == 2 {
|
||||
err = core.ValidateType(args[1], core.StringType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
// Document with a selector
|
||||
doc, ok := args[0].(*dynamic.HTMLDocument)
|
||||
|
||||
if !ok {
|
||||
return values.None, core.Errors(core.ErrInvalidType, ErrNotDynamic)
|
||||
}
|
||||
|
||||
selector := args[1].(values.String)
|
||||
|
||||
return values.None, doc.HoverBySelector(selector)
|
||||
}
|
||||
|
||||
// Element
|
||||
el, ok := args[0].(*dynamic.HTMLElement)
|
||||
|
||||
if !ok {
|
||||
return values.None, core.Errors(core.ErrInvalidType, ErrNotDynamic)
|
||||
}
|
||||
|
||||
return values.None, el.Hover()
|
||||
}
|
@@ -13,31 +13,33 @@ var (
|
||||
|
||||
func NewLib() map[string]core.Function {
|
||||
return map[string]core.Function{
|
||||
"CLICK": Click,
|
||||
"CLICK_ALL": ClickAll,
|
||||
"DOCUMENT": Document,
|
||||
"DOCUMENT_PARSE": DocumentParse,
|
||||
"DOWNLOAD": Download,
|
||||
"ELEMENT": Element,
|
||||
"ELEMENTS": Elements,
|
||||
"ELEMENTS_COUNT": ElementsCount,
|
||||
"WAIT_ELEMENT": WaitElement,
|
||||
"WAIT_NAVIGATION": WaitNavigation,
|
||||
"WAIT_CLASS": WaitClass,
|
||||
"WAIT_CLASS_ALL": WaitClassAll,
|
||||
"CLICK": Click,
|
||||
"CLICK_ALL": ClickAll,
|
||||
"NAVIGATE": Navigate,
|
||||
"NAVIGATE_BACK": NavigateBack,
|
||||
"NAVIGATE_FORWARD": NavigateForward,
|
||||
"INPUT": Input,
|
||||
"HOVER": Hover,
|
||||
"INNER_HTML": InnerHTML,
|
||||
"INNER_HTML_ALL": InnerHTMLAll,
|
||||
"INNER_TEXT": InnerText,
|
||||
"INNER_TEXT_ALL": InnerTextAll,
|
||||
"SELECT": Select,
|
||||
"SCREENSHOT": Screenshot,
|
||||
"SCROLL_TOP": ScrollTop,
|
||||
"SCROLL_BOTTOM": ScrollBottom,
|
||||
"INPUT": Input,
|
||||
"NAVIGATE": Navigate,
|
||||
"NAVIGATE_BACK": NavigateBack,
|
||||
"NAVIGATE_FORWARD": NavigateForward,
|
||||
"PAGINATION": Pagination,
|
||||
"PDF": PDF,
|
||||
"DOWNLOAD": Download,
|
||||
"SCREENSHOT": Screenshot,
|
||||
"SCROLL_BOTTOM": ScrollBottom,
|
||||
"SCROLL_ELEMENT": ScrollInto,
|
||||
"SCROLL_TOP": ScrollTop,
|
||||
"SELECT": Select,
|
||||
"WAIT_ELEMENT": WaitElement,
|
||||
"WAIT_CLASS": WaitClass,
|
||||
"WAIT_CLASS_ALL": WaitClassAll,
|
||||
"WAIT_NAVIGATION": WaitNavigation,
|
||||
}
|
||||
}
|
||||
|
@@ -1,56 +0,0 @@
|
||||
package html
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/html/dynamic"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
// ScrollTop Scrolls the document's window to its top.
|
||||
// @param doc (Document) - Target document.
|
||||
func ScrollTop(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 1, 1)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[0], core.HTMLDocumentType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
doc, ok := args[0].(*dynamic.HTMLDocument)
|
||||
|
||||
if !ok {
|
||||
return values.None, core.Errors(core.ErrInvalidType, ErrNotDynamic)
|
||||
}
|
||||
|
||||
return values.None, doc.ScrollTop()
|
||||
}
|
||||
|
||||
// ScrollTop Scrolls the document's window to its bottom.
|
||||
// @param doc (Document) - Target document.
|
||||
func ScrollBottom(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 1, 1)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[0], core.HTMLDocumentType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
doc, ok := args[0].(*dynamic.HTMLDocument)
|
||||
|
||||
if !ok {
|
||||
return values.None, core.Errors(core.ErrInvalidType, ErrNotDynamic)
|
||||
}
|
||||
|
||||
return values.None, doc.ScrollBottom()
|
||||
}
|
32
pkg/stdlib/html/scroll_bottom.go
Normal file
32
pkg/stdlib/html/scroll_bottom.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package html
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/html/dynamic"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
// ScrollTop scrolls the document's window to its bottom.
|
||||
// @param doc (HTMLDocument) - Target document.
|
||||
func ScrollBottom(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 1, 1)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[0], core.HTMLDocumentType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
doc, ok := args[0].(*dynamic.HTMLDocument)
|
||||
|
||||
if !ok {
|
||||
return values.None, core.Errors(core.ErrInvalidType, ErrNotDynamic)
|
||||
}
|
||||
|
||||
return values.None, doc.ScrollBottom()
|
||||
}
|
54
pkg/stdlib/html/scroll_element.go
Normal file
54
pkg/stdlib/html/scroll_element.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package html
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/html/dynamic"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
// ScrollInto scrolls an element on.
|
||||
// @param docOrEl (HTMLDocument|HTMLElement) - Target document or element.
|
||||
// @param selector (String, options) - If document is passed, this param must represent an element selector.
|
||||
func ScrollInto(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 1, 2)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
// document or element
|
||||
err = core.ValidateType(args[0], core.HTMLDocumentType, core.HTMLElementType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
if len(args) == 2 {
|
||||
err = core.ValidateType(args[1], core.StringType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
// Document with a selector
|
||||
doc, ok := args[0].(*dynamic.HTMLDocument)
|
||||
|
||||
if !ok {
|
||||
return values.None, core.Errors(core.ErrInvalidType, ErrNotDynamic)
|
||||
}
|
||||
|
||||
selector := args[1].(values.String)
|
||||
|
||||
return values.None, doc.ScrollBySelector(selector)
|
||||
}
|
||||
|
||||
// Element
|
||||
el, ok := args[0].(*dynamic.HTMLElement)
|
||||
|
||||
if !ok {
|
||||
return values.None, core.Errors(core.ErrInvalidType, ErrNotDynamic)
|
||||
}
|
||||
|
||||
return values.None, el.ScrollIntoView()
|
||||
}
|
32
pkg/stdlib/html/scroll_top.go
Normal file
32
pkg/stdlib/html/scroll_top.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package html
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/html/dynamic"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
// ScrollTop scrolls the document's window to its top.
|
||||
// @param doc (HTMLDocument) - Target document.
|
||||
func ScrollTop(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 1, 1)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[0], core.HTMLDocumentType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
doc, ok := args[0].(*dynamic.HTMLDocument)
|
||||
|
||||
if !ok {
|
||||
return values.None, core.Errors(core.ErrInvalidType, ErrNotDynamic)
|
||||
}
|
||||
|
||||
return values.None, doc.ScrollTop()
|
||||
}
|
@@ -50,7 +50,12 @@ func WaitClass(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
doc := args[0].(*dynamic.HTMLDocument)
|
||||
doc, ok := args[0].(*dynamic.HTMLDocument)
|
||||
|
||||
if !ok {
|
||||
return values.None, core.Errors(core.ErrInvalidType, ErrNotDynamic)
|
||||
}
|
||||
|
||||
selector := args[1].(values.String)
|
||||
class := args[2].(values.String)
|
||||
|
||||
@@ -66,7 +71,12 @@ func WaitClass(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
|
||||
return values.None, doc.WaitForClass(selector, class, timeout)
|
||||
case *dynamic.HTMLElement:
|
||||
el := args[0].(*dynamic.HTMLElement)
|
||||
el, ok := args[0].(*dynamic.HTMLElement)
|
||||
|
||||
if !ok {
|
||||
return values.None, core.Errors(core.ErrInvalidType, ErrNotDynamic)
|
||||
}
|
||||
|
||||
class := args[1].(values.String)
|
||||
|
||||
if len(args) == 3 {
|
||||
|
Reference in New Issue
Block a user