Skip to content

Commit

Permalink
simplify the design of js helper
Browse files Browse the repository at this point in the history
  • Loading branch information
ysmood committed Mar 14, 2021
1 parent a0a8747 commit 52b64f0
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 68 deletions.
14 changes: 7 additions & 7 deletions dev_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func (b *Browser) trySlowmotion() {

// ExposeHelpers helper functions to page's js context so that we can use the Devtools' console to debug them.
func (p *Page) ExposeHelpers(list ...*js.Function) {
p.MustEvaluate(EvalHelper(&js.Function{
p.MustEvaluate(evalHelper(&js.Function{
Name: "_" + utils.RandString(8), // use a random name so it won't hit the cache
Definition: "() => { window.rod = functions }",
Dependencies: list,
Expand All @@ -114,7 +114,7 @@ func (p *Page) ExposeHelpers(list ...*js.Function) {
func (p *Page) Overlay(left, top, width, height float64, msg string) (remove func()) {
id := utils.RandString(8)

_, _ = p.root.Evaluate(EvalHelper(js.Overlay,
_, _ = p.root.Evaluate(evalHelper(js.Overlay,
id,
left,
top,
Expand All @@ -124,7 +124,7 @@ func (p *Page) Overlay(left, top, width, height float64, msg string) (remove fun
).ByPromise())

remove = func() {
_, _ = p.root.Evaluate(EvalHelper(js.RemoveOverlay, id))
_, _ = p.root.Evaluate(evalHelper(js.RemoveOverlay, id))
}

return
Expand Down Expand Up @@ -198,13 +198,13 @@ func (p *Page) tryTraceReq(includes, excludes []string) func(map[proto.NetworkRe
func (el *Element) Overlay(msg string) (removeOverlay func()) {
id := utils.RandString(8)

_, _ = el.Evaluate(EvalHelper(js.ElementOverlay,
_, _ = el.Evaluate(evalHelper(js.ElementOverlay,
id,
msg,
).ByPromise())

removeOverlay = func() {
_, _ = el.Evaluate(EvalHelper(js.RemoveOverlay, id))
_, _ = el.Evaluate(evalHelper(js.RemoveOverlay, id))
}

return
Expand All @@ -224,11 +224,11 @@ func (el *Element) tryTrace(typ TraceType, msg ...interface{}) func() {
}

func (m *Mouse) initMouseTracer() {
_, _ = m.page.Evaluate(EvalHelper(js.InitMouseTracer, m.id, assets.MousePointer).ByPromise())
_, _ = m.page.Evaluate(evalHelper(js.InitMouseTracer, m.id, assets.MousePointer).ByPromise())
}

func (m *Mouse) updateMouseTracer() bool {
res, err := m.page.Evaluate(EvalHelper(js.UpdateMouseTracer, m.id, m.x, m.y))
res, err := m.page.Evaluate(evalHelper(js.UpdateMouseTracer, m.id, m.x, m.y))
if err != nil {
return true
}
Expand Down
24 changes: 12 additions & 12 deletions element.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ func (el *Element) SelectText(regex string) error {
defer el.tryTrace(TraceTypeInput, "select text: "+regex)()
el.page.browser.trySlowmotion()

_, err = el.Evaluate(EvalHelper(js.SelectText, regex).ByUser())
_, err = el.Evaluate(evalHelper(js.SelectText, regex).ByUser())
return err
}

Expand All @@ -235,7 +235,7 @@ func (el *Element) SelectAllText() error {
defer el.tryTrace(TraceTypeInput, "select all text")()
el.page.browser.trySlowmotion()

_, err = el.Evaluate(EvalHelper(js.SelectAllText).ByUser())
_, err = el.Evaluate(evalHelper(js.SelectAllText).ByUser())
return err
}

Expand Down Expand Up @@ -270,7 +270,7 @@ func (el *Element) Input(text string) error {
return err
}

_, err = el.Evaluate(EvalHelper(js.InputEvent).ByUser())
_, err = el.Evaluate(evalHelper(js.InputEvent).ByUser())
return err
}

Expand Down Expand Up @@ -300,7 +300,7 @@ func (el *Element) InputTime(t time.Time) error {

defer el.tryTrace(TraceTypeInput, "input "+t.String())()

_, err = el.Evaluate(EvalHelper(js.InputTime, t.UnixNano()/1e6).ByUser())
_, err = el.Evaluate(evalHelper(js.InputTime, t.UnixNano()/1e6).ByUser())
return err
}

Expand All @@ -326,7 +326,7 @@ func (el *Element) Select(selectors []string, selected bool, t SelectorType) err
defer el.tryTrace(TraceTypeInput, fmt.Sprintf(`select "%s"`, strings.Join(selectors, "; ")))()
el.page.browser.trySlowmotion()

_, err = el.Evaluate(EvalHelper(js.Select, selectors, selected, t).ByUser())
_, err = el.Evaluate(evalHelper(js.Select, selectors, selected, t).ByUser())
return err
}

Expand Down Expand Up @@ -436,7 +436,7 @@ func (el *Element) Frame() (*Page, error) {

// ContainsElement check if the target is equal or inside the element.
func (el *Element) ContainsElement(target *Element) (bool, error) {
res, err := el.Evaluate(EvalHelper(js.ContainsElement, target.Object))
res, err := el.Evaluate(evalHelper(js.ContainsElement, target.Object))
if err != nil {
return false, err
}
Expand All @@ -445,7 +445,7 @@ func (el *Element) ContainsElement(target *Element) (bool, error) {

// Text that the element displays
func (el *Element) Text() (string, error) {
str, err := el.Evaluate(EvalHelper(js.Text))
str, err := el.Evaluate(evalHelper(js.Text))
if err != nil {
return "", err
}
Expand All @@ -463,7 +463,7 @@ func (el *Element) HTML() (string, error) {

// Visible returns true if the element is visible on the page
func (el *Element) Visible() (bool, error) {
res, err := el.Evaluate(EvalHelper(js.Visible))
res, err := el.Evaluate(evalHelper(js.Visible))
if err != nil {
return false, err
}
Expand All @@ -473,7 +473,7 @@ func (el *Element) Visible() (bool, error) {
// WaitLoad for element like <img>
func (el *Element) WaitLoad() error {
defer el.tryTrace(TraceTypeWait, "load")()
_, err := el.Evaluate(EvalHelper(js.WaitLoad).ByPromise())
_, err := el.Evaluate(evalHelper(js.WaitLoad).ByPromise())
return err
}

Expand Down Expand Up @@ -586,7 +586,7 @@ func (el *Element) Wait(opts *EvalOptions) error {
// WaitVisible until the element is visible
func (el *Element) WaitVisible() error {
defer el.tryTrace(TraceTypeWait, "visible")()
return el.Wait(EvalHelper(js.Visible))
return el.Wait(evalHelper(js.Visible))
}

// WaitEnabled until the element is not disabled.
Expand All @@ -606,7 +606,7 @@ func (el *Element) WaitWritable() error {
// WaitInvisible until the element invisible
func (el *Element) WaitInvisible() error {
defer el.tryTrace(TraceTypeWait, "invisible")()
return el.Wait(EvalHelper(js.Invisible))
return el.Wait(evalHelper(js.Invisible))
}

// CanvasToImage get image data of a canvas.
Expand All @@ -625,7 +625,7 @@ func (el *Element) CanvasToImage(format string, quality float64) ([]byte, error)

// Resource returns the "src" content of current element. Such as the jpg of <img src="a.jpg">
func (el *Element) Resource() ([]byte, error) {
src, err := el.Evaluate(EvalHelper(js.Resource).ByPromise())
src, err := el.Evaluate(evalHelper(js.Resource).ByPromise())
if err != nil {
return nil, err
}
Expand Down
8 changes: 4 additions & 4 deletions page.go
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ func (p *Page) WaitRequestIdle(d time.Duration, includes, excludes []string) fun

// WaitIdle waits until the next window.requestIdleCallback is called.
func (p *Page) WaitIdle(timeout time.Duration) (err error) {
_, err = p.Evaluate(EvalHelper(js.WaitIdle, timeout.Seconds()).ByPromise())
_, err = p.Evaluate(evalHelper(js.WaitIdle, timeout.Seconds()).ByPromise())
return err
}

Expand All @@ -531,23 +531,23 @@ func (p *Page) WaitRepaint() error {
// WaitLoad waits for the `window.onload` event, it returns immediately if the event is already fired.
func (p *Page) WaitLoad() error {
defer p.tryTrace(TraceTypeWait, "load")()
_, err := p.Evaluate(EvalHelper(js.WaitLoad).ByPromise())
_, err := p.Evaluate(evalHelper(js.WaitLoad).ByPromise())
return err
}

// AddScriptTag to page. If url is empty, content will be used.
func (p *Page) AddScriptTag(url, content string) error {
hash := md5.Sum([]byte(url + content))
id := hex.EncodeToString(hash[:])
_, err := p.Evaluate(EvalHelper(js.AddScriptTag, id, url, content).ByPromise())
_, err := p.Evaluate(evalHelper(js.AddScriptTag, id, url, content).ByPromise())
return err
}

// AddStyleTag to page. If url is empty, content will be used.
func (p *Page) AddStyleTag(url, content string) error {
hash := md5.Sum([]byte(url + content))
id := hex.EncodeToString(hash[:])
_, err := p.Evaluate(EvalHelper(js.AddStyleTag, id, url, content).ByPromise())
_, err := p.Evaluate(evalHelper(js.AddStyleTag, id, url, content).ByPromise())
return err
}

Expand Down
54 changes: 25 additions & 29 deletions page_eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,13 @@ type EvalOptions struct {

// Whether execution should be treated as initiated by user in the UI.
UserGesture bool

jsHelper *js.Function
}

// Eval creates a EvalOptions with ByValue set to true.
//
// When an arg in the args is a *js.Function, the arg will be cached on the page's js context.
// When the arg.Name exists in the page's cache, it reuse the cache without sending the definition to the browser again.
// Useful when you need to eval a huge js expression many times.
func Eval(js string, args ...interface{}) *EvalOptions {
return &EvalOptions{
ByValue: true,
Expand All @@ -57,38 +59,35 @@ func Eval(js string, args ...interface{}) *EvalOptions {
JS: js,
JSArgs: args,
UserGesture: false,
jsHelper: nil,
}
}

// EvalHelper creates a special EvalOptions that will eval and cache the fn on the page's js context.
// When the fn.Name exists in the page's cache, it reuse the cache without sending the definition to the browser again.
// Useful when you need to eval a huge js expression many times.
func EvalHelper(fn *js.Function, args ...interface{}) *EvalOptions {
func evalHelper(fn *js.Function, args ...interface{}) *EvalOptions {
return &EvalOptions{
ByValue: true,
JSArgs: args,
JS: `({fn}, ...args) => fn.apply(this, args)`,
jsHelper: fn,
ByValue: true,
JSArgs: append([]interface{}{fn}, args...),
JS: `(f, ...args) => f.apply(this, args)`,
}
}

// String interface
func (e *EvalOptions) String() string {
fn := e.JS

if e.jsHelper != nil {
fn = "rod." + e.jsHelper.Name
}
args := e.JSArgs

paramsStr := ""
thisStr := ""

if e.ThisObj != nil {
thisStr = e.ThisObj.Description
}
if len(e.JSArgs) > 0 {
paramsStr = strings.Trim(mustToJSONForDev(e.JSArgs), "[]\r\n")
if len(args) > 0 {
if f, ok := args[0].(*js.Function); ok {
fn = "rod." + f.Name
args = e.JSArgs[1:]
}

paramsStr = strings.Trim(mustToJSONForDev(args), "[]\r\n")
}

return fmt.Sprintf("%s(%s) %s", fn, paramsStr, thisStr)
Expand Down Expand Up @@ -243,20 +242,17 @@ func (p *Page) formatArgs(opts *EvalOptions) ([]*proto.RuntimeCallArgument, erro
for _, arg := range opts.JSArgs {
if obj, ok := arg.(*proto.RuntimeRemoteObject); ok { // remote object
formated = append(formated, &proto.RuntimeCallArgument{ObjectID: obj.ObjectID})
} else if obj, ok := arg.(*js.Function); ok { // js helper
id, err := p.ensureJSHelper(obj)
if err != nil {
return nil, err
}
formated = append(formated, &proto.RuntimeCallArgument{ObjectID: id})
} else { // plain json data
formated = append(formated, &proto.RuntimeCallArgument{Value: gson.New(arg)})
}
}

if opts.jsHelper != nil {
id, err := p.ensureJSHelper(opts.jsHelper)
if err != nil {
return nil, err
}

formated = append([]*proto.RuntimeCallArgument{{ObjectID: id}}, formated...)
}

return formated, nil
}

Expand Down Expand Up @@ -304,10 +300,10 @@ func (p *Page) ensureJSHelper(fn *js.Function) (proto.RuntimeRemoteObjectID, err
Arguments: []*proto.RuntimeCallArgument{{ObjectID: fns}},

FunctionDeclaration: fmt.Sprintf(
// We wrap an extra {fn: fn} here to reduce the response body size,
// we only need the object id, but the cdp will return the whole function string.
"functions => { functions.%s = %s; return { fn: functions.%s } }",
fn.Name, fn.Definition, fn.Name,
// So we override the toString to reduce the overhead.
"functions => { const f = functions.%s = %s; f.toString = () => 'fn'; return f }",
fn.Name, fn.Definition,
),
}.Call(p)
if err != nil {
Expand Down
3 changes: 1 addition & 2 deletions page_eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (

"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/cdp"
"github.com/go-rod/rod/lib/js"
"github.com/go-rod/rod/lib/proto"
"github.com/go-rod/rod/lib/utils"
"github.com/ysmood/gson"
Expand Down Expand Up @@ -237,7 +236,7 @@ func (t T) EnsureJSHelperErr() {
p := t.page.MustNavigate(t.blank())

t.mc.stubErr(2, proto.RuntimeCallFunctionOn{})
t.Err(p.Evaluate(rod.EvalHelper(js.Overlay, "test", 0, 0, 10, 10, "msg")))
t.Err(p.Elements(`button`))
}

func (t T) EvalOptionsString() {
Expand Down
Loading

0 comments on commit 52b64f0

Please sign in to comment.