81a48280ef
Use a single websocket connection per browser, removing the need for an extra websocket connection per target. This is thanks to the Target.sendMessageToTarget command to send messages to each target, and the Target.receivedMessageFromTarget event to receive messages back. The browser handles activity via a single worker goroutine, and the same technique is used for each target. This means that commands and events are dealt with in order, and we can do away with some complexity like mutexes and extra go statements.
128 lines
2.7 KiB
Go
128 lines
2.7 KiB
Go
package chromedp
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/chromedp/cdproto/css"
|
|
"github.com/chromedp/cdproto/dom"
|
|
"github.com/chromedp/cdproto/inspector"
|
|
"github.com/chromedp/cdproto/log"
|
|
"github.com/chromedp/cdproto/page"
|
|
"github.com/chromedp/cdproto/runtime"
|
|
"github.com/chromedp/cdproto/target"
|
|
)
|
|
|
|
// Executor
|
|
type Executor interface {
|
|
Execute(context.Context, string, json.Marshaler, json.Unmarshaler) error
|
|
}
|
|
|
|
// Context
|
|
type Context struct {
|
|
Allocator Allocator
|
|
|
|
browser *Browser
|
|
|
|
sessionID target.SessionID
|
|
}
|
|
|
|
// Wait can be called after cancelling the context containing Context, to block
|
|
// until all the underlying resources have been cleaned up.
|
|
func (c *Context) Wait() {
|
|
if c.Allocator != nil {
|
|
c.Allocator.Wait()
|
|
}
|
|
}
|
|
|
|
// NewContext creates a browser context using the parent context.
|
|
func NewContext(parent context.Context, opts ...ContextOption) (context.Context, context.CancelFunc) {
|
|
ctx, cancel := context.WithCancel(parent)
|
|
|
|
c := &Context{}
|
|
if pc := FromContext(parent); pc != nil {
|
|
c.Allocator = pc.Allocator
|
|
}
|
|
|
|
for _, o := range opts {
|
|
o(c)
|
|
}
|
|
if c.Allocator == nil {
|
|
WithExecAllocator(
|
|
NoFirstRun,
|
|
NoDefaultBrowserCheck,
|
|
Headless,
|
|
)(&c.Allocator)
|
|
}
|
|
|
|
ctx = context.WithValue(ctx, contextKey{}, c)
|
|
return ctx, cancel
|
|
}
|
|
|
|
type contextKey struct{}
|
|
|
|
// FromContext creates a new browser context from the provided context.
|
|
func FromContext(ctx context.Context) *Context {
|
|
c, _ := ctx.Value(contextKey{}).(*Context)
|
|
return c
|
|
}
|
|
|
|
// Run runs the action against the provided browser context.
|
|
func Run(ctx context.Context, action Action) error {
|
|
c := FromContext(ctx)
|
|
if c == nil || c.Allocator == nil {
|
|
return ErrInvalidContext
|
|
}
|
|
if c.browser == nil {
|
|
browser, err := c.Allocator.Allocate(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.browser = browser
|
|
}
|
|
if c.sessionID == "" {
|
|
if err := c.newSession(ctx); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return action.Do(ctx, c.browser.executorForTarget(ctx, c.sessionID))
|
|
}
|
|
|
|
func (c *Context) newSession(ctx context.Context) error {
|
|
create := target.CreateTarget("about:blank")
|
|
targetID, err := create.Do(ctx, c.browser)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
attach := target.AttachToTarget(targetID)
|
|
sessionID, err := attach.Do(ctx, c.browser)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
target := c.browser.executorForTarget(ctx, sessionID)
|
|
|
|
// enable domains
|
|
for _, enable := range []Action{
|
|
log.Enable(),
|
|
runtime.Enable(),
|
|
//network.Enable(),
|
|
inspector.Enable(),
|
|
page.Enable(),
|
|
dom.Enable(),
|
|
css.Enable(),
|
|
} {
|
|
if err := enable.Do(ctx, target); err != nil {
|
|
return fmt.Errorf("unable to execute %T: %v", enable, err)
|
|
}
|
|
}
|
|
|
|
c.sessionID = sessionID
|
|
return nil
|
|
}
|
|
|
|
// ContextOption
|
|
type ContextOption func(*Context)
|