forked from chromedp/chromedp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
poll.go
181 lines (159 loc) · 5.46 KB
/
poll.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
package chromedp
import (
"context"
"fmt"
"time"
"github.com/chromedp/cdproto/cdp"
"github.com/chromedp/cdproto/runtime"
)
// PollAction are actions that will wait for a general Javascript predicate.
//
// See Poll for details on building poll tasks.
type PollAction Action
// pollTask holds information pertaining to an poll task.
//
// See Poll for details on building poll tasks.
type pollTask struct {
frame *cdp.Node // the frame to evaluate the predicate, defaults to the root page
predicate string
polling string // the polling mode, defaults to "raf" (triggered by requestAnimationFrame)
interval time.Duration // the interval when the poll is triggered by a timer
timeout time.Duration // the poll timeout, defaults to 30 seconds
args []interface{}
res interface{}
}
// Do executes the poll task in the browser,
// until the predicate either returns truthy value or the timeout happens.
func (p *pollTask) Do(ctx context.Context) error {
t := cdp.ExecutorFromContext(ctx).(*Target)
if t == nil {
return ErrInvalidTarget
}
var (
execCtx runtime.ExecutionContextID
ok bool
)
for {
_, _, execCtx, ok = t.ensureFrame()
if ok {
break
}
if err := sleepContext(ctx, 5*time.Millisecond); err != nil {
return err
}
}
if p.frame != nil {
t.frameMu.RLock()
frameID := t.enclosingFrame(p.frame)
execCtx = t.execContexts[frameID]
t.frameMu.RUnlock()
}
args := make([]interface{}, 0, len(p.args)+3)
args = append(args, p.predicate)
if p.interval > 0 {
args = append(args, p.interval.Milliseconds())
} else {
args = append(args, p.polling)
}
args = append(args, p.timeout.Milliseconds())
args = append(args, p.args...)
undefined, err := callFunctionOn(ctx, waitForPredicatePageFunction, p.res,
func(p *runtime.CallFunctionOnParams) *runtime.CallFunctionOnParams {
return p.WithExecutionContextID(execCtx).
WithAwaitPromise(true).
WithUserGesture(true)
},
args...,
)
if undefined {
return ErrPollingTimeout
}
return err
}
// Poll is a poll action that will wait for a general Javascript predicate.
// It builds the predicate from a Javascript expression.
//
// This is a copy of puppeteer's page.waitForFunction.
// see https://github.com/puppeteer/puppeteer/blob/v8.0.0/docs/api.md#pagewaitforfunctionpagefunction-options-args.
// It's named Poll intentionally to avoid messing up with the Wait* query actions.
// The behavior is not guaranteed to be compatible.
// For example, our implementation makes the poll task not survive from a navigation,
// and an error is raised in this case (see unit test TestPoll/NotSurviveNavigation).
//
// Polling Options
//
// The default polling mode is "raf", to constantly execute pageFunction in requestAnimationFrame callback.
// This is the tightest polling mode which is suitable to observe styling changes.
// The WithPollingInterval option makes it to poll the predicate with a specified interval.
// The WithPollingMutation option makes it to poll the predicate on every DOM mutation.
//
// The WithPollingTimeout option specifies the maximum time to wait for the predicate returns truthy value.
// It defaults to 30 seconds. Pass 0 to disable timeout.
//
// The WithPollingInFrame option specifies the frame in which to evaluate the predicate.
// If not specified, it will be evaluated in the root page of the current tab.
//
// The WithPollingArgs option provides extra arguments to pass to the predicate.
// Only apply this option when the predicate is built from a function.
// See PollFunction.
func Poll(expression string, res interface{}, opts ...PollOption) PollAction {
predicate := fmt.Sprintf(`return (%s);`, expression)
return poll(predicate, res, opts...)
}
// PollFunction is a poll action that will wait for a general Javascript predicate.
// It builds the predicate from a Javascript function.
//
// See Poll for details on building poll tasks.
func PollFunction(pageFunction string, res interface{}, opts ...PollOption) PollAction {
predicate := fmt.Sprintf(`return (%s)(...args);`, pageFunction)
return poll(predicate, res, opts...)
}
func poll(predicate string, res interface{}, opts ...PollOption) PollAction {
p := &pollTask{
predicate: predicate,
polling: "raf",
timeout: 30 * time.Second,
res: res,
}
// apply options
for _, o := range opts {
o(p)
}
return p
}
// PollOption is an poll task option.
type PollOption = func(task *pollTask)
// WithPollingInterval makes it to poll the predicate with the specified interval.
func WithPollingInterval(interval time.Duration) PollOption {
return func(w *pollTask) {
w.polling = ""
w.interval = interval
}
}
// WithPollingMutation makes it to poll the predicate on every DOM mutation.
func WithPollingMutation() PollOption {
return func(w *pollTask) {
w.polling = "mutation"
w.interval = 0
}
}
// WithPollingTimeout specifies the maximum time to wait for the predicate returns truthy value.
// It defaults to 30 seconds. Pass 0 to disable timeout.
func WithPollingTimeout(timeout time.Duration) PollOption {
return func(w *pollTask) {
w.timeout = timeout
}
}
// WithPollingInFrame specifies the frame in which to evaluate the predicate.
// If not specified, it will be evaluated in the root page of the current tab.
func WithPollingInFrame(frame *cdp.Node) PollOption {
return func(w *pollTask) {
w.frame = frame
}
}
// WithPollingArgs provides extra arguments to pass to the predicate.
func WithPollingArgs(args ...interface{}) PollOption {
return func(w *pollTask) {
w.args = args
}
}