github.com/MontFerret/ferret@v0.18.0/pkg/drivers/cdp/input/quad.go (about)

     1  package input
     2  
     3  import (
     4  	"context"
     5  	"math"
     6  
     7  	"github.com/mafredri/cdp"
     8  	"github.com/mafredri/cdp/protocol/dom"
     9  	"github.com/mafredri/cdp/protocol/runtime"
    10  	"github.com/pkg/errors"
    11  
    12  	"github.com/MontFerret/ferret/pkg/drivers/cdp/utils"
    13  )
    14  
    15  type Quad struct {
    16  	X float64
    17  	Y float64
    18  }
    19  
    20  func fromProtocolQuad(quad dom.Quad) []Quad {
    21  	return []Quad{
    22  		{
    23  			X: quad[0],
    24  			Y: quad[1],
    25  		},
    26  		{
    27  			X: quad[2],
    28  			Y: quad[3],
    29  		},
    30  		{
    31  			X: quad[4],
    32  			Y: quad[5],
    33  		},
    34  		{
    35  			X: quad[6],
    36  			Y: quad[7],
    37  		},
    38  	}
    39  }
    40  
    41  func computeQuadArea(quads []Quad) float64 {
    42  	var area float64
    43  
    44  	for i := range quads {
    45  		p1 := quads[i]
    46  		p2 := quads[(i+1)%len(quads)]
    47  		area += (p1.X*p2.Y - p2.X*p1.Y) / 2
    48  	}
    49  
    50  	return math.Abs(area)
    51  }
    52  
    53  func intersectQuadWithViewport(quad []Quad, width, height float64) []Quad {
    54  	quads := make([]Quad, 0, len(quad))
    55  
    56  	for _, point := range quad {
    57  		quads = append(quads, Quad{
    58  			X: math.Min(math.Max(point.X, 0), width),
    59  			Y: math.Min(math.Max(point.Y, 0), height),
    60  		})
    61  	}
    62  
    63  	return quads
    64  }
    65  
    66  func getClickablePoint(ctx context.Context, client *cdp.Client, qargs *dom.GetContentQuadsArgs) (Quad, error) {
    67  	contentQuadsReply, err := client.DOM.GetContentQuads(ctx, qargs)
    68  
    69  	if err != nil {
    70  		return Quad{}, err
    71  	}
    72  
    73  	if contentQuadsReply.Quads == nil || len(contentQuadsReply.Quads) == 0 {
    74  		return Quad{}, errors.New("node is either not visible or not an HTMLElement")
    75  	}
    76  
    77  	layoutMetricsReply, err := client.Page.GetLayoutMetrics(ctx)
    78  
    79  	if err != nil {
    80  		return Quad{}, err
    81  	}
    82  
    83  	clientWidth, clientHeight := utils.GetLayoutViewportWH(layoutMetricsReply)
    84  	quads := make([][]Quad, 0, len(contentQuadsReply.Quads))
    85  
    86  	for _, q := range contentQuadsReply.Quads {
    87  		quad := intersectQuadWithViewport(fromProtocolQuad(q), float64(clientWidth), float64(clientHeight))
    88  
    89  		if computeQuadArea(quad) > 1 {
    90  			quads = append(quads, quad)
    91  		}
    92  	}
    93  
    94  	if len(quads) == 0 {
    95  		return Quad{}, errors.New("node is either not visible or not an HTMLElement")
    96  	}
    97  
    98  	// Return the middle point of the first quad.
    99  	quad := quads[0]
   100  	var x float64
   101  	var y float64
   102  
   103  	for _, q := range quad {
   104  		x += q.X
   105  		y += q.Y
   106  	}
   107  
   108  	return Quad{
   109  		X: x / 4,
   110  		Y: y / 4,
   111  	}, nil
   112  }
   113  
   114  func GetClickablePointByObjectID(ctx context.Context, client *cdp.Client, objectID runtime.RemoteObjectID) (Quad, error) {
   115  	return getClickablePoint(ctx, client, dom.NewGetContentQuadsArgs().SetObjectID(objectID))
   116  }