-
Notifications
You must be signed in to change notification settings - Fork 2
/
tab.go
117 lines (106 loc) · 2.99 KB
/
tab.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
package chromebot
import (
"context"
"errors"
"sync"
"github.com/chromedp/cdproto/cdp"
"github.com/chromedp/chromedp"
"github.com/nanitefactory/chromebot/domain"
)
// Tab represents a running tab.
type Tab struct {
finMu sync.Mutex
fin chan struct{}
ctx context.Context
cancel context.CancelFunc
closeStrategy func(ctx context.Context) // inject close method other than cancel()
}
func newTab(ctx context.Context, cancel context.CancelFunc, closeStrategy func(ctx context.Context)) *Tab {
if closeStrategy == nil {
closeStrategy = func(ctx context.Context) { cancel() }
}
ret := &Tab{
finMu: sync.Mutex{},
fin: make(chan struct{}),
ctx: ctx,
cancel: cancel,
closeStrategy: closeStrategy,
}
go func() {
<-ctx.Done()
defer func() { // DCL
ret.finMu.Lock()
defer ret.finMu.Unlock()
select {
case <-ret.fin:
return
default:
close(ret.fin)
}
}()
}()
return ret
}
func newMainTab(ctx context.Context, cancel context.CancelFunc) *Tab {
return newTab(ctx, cancel, func(ctx context.Context) {
domain.Do(cdp.WithExecutor(ctx, chromedp.FromContext(ctx).Target)).Page().Close()
})
}
func newExtraTab(ctx context.Context, cancel context.CancelFunc) *Tab {
return newTab(ctx, cancel, nil)
}
// Close closes this tab.
func (t *Tab) Close() {
defer func() { // DCL
t.finMu.Lock()
defer t.finMu.Unlock()
select {
case <-t.fin:
return
default:
t.closeStrategy(t.ctx)
close(t.fin)
}
}()
}
// Dead returns a channel that's closed when work done on behalf of the tab should be dead.
// This notifies a user if and only if the browser is dead or `(*Tab).Close()` has been called.
func (t *Tab) Dead() <-chan struct{} {
return t.fin
}
// Do runs a cdproto command on this tab.
func (t *Tab) Do() domain.Domain {
return domain.Do(cdp.WithExecutor(t.ctx, chromedp.FromContext(t.ctx).Target))
}
// Run runs a chromedp action on this tab.
func (t *Tab) Run(actions ...chromedp.Action) error {
return chromedp.Run(t.ctx, actions...)
}
// Listen adds a callback function which will be called whenever an event is received on this tab.
//
// Usage:
// t.Listen(func(ev interface{}) {
// switch ev := ev.(type) {
// case *page.EventJavascriptDialogOpening:
// // do something with ev
// case *runtime.EventConsoleAPICalled:
// // do something with ev
// case *runtime.EventExceptionThrown:
// // do something with ev
// }
// })
//
// Note that the function is called synchronously when handling events.
// The function should avoid blocking at all costs.
// For example, any Actions must be run via a separate goroutine.
func (t *Tab) Listen(onEvent func(ev interface{})) error {
select {
case <-t.ctx.Done():
return errors.New("tab closed: " + t.ctx.Err().Error())
case <-t.fin:
return errors.New("tab closed")
default:
}
chromedp.ListenTarget(t.ctx, onEvent)
return nil
}