From 622c90c82cac39a3f2be4376f4ea298c8d651df4 Mon Sep 17 00:00:00 2001 From: Kenneth Shaw Date: Fri, 13 Jul 2018 10:57:20 +0700 Subject: [PATCH] Cleaning up runner API prior to package rewrite --- chromedp.go | 2 +- pool.go | 6 +- runner/path_darwin.go | 9 +- runner/path_unix.go | 22 ++--- runner/path_windows.go | 28 +----- runner/runner.go | 192 ++++++++++++++++++++--------------------- 6 files changed, 116 insertions(+), 143 deletions(-) diff --git a/chromedp.go b/chromedp.go index 3ae31ff..d8661f9 100644 --- a/chromedp.go +++ b/chromedp.go @@ -90,7 +90,7 @@ func New(ctxt context.Context, opts ...Option) (*CDP, error) { // watch handlers if c.watch == nil { - c.watch = c.r.WatchPageTargets(ctxt) + c.watch = c.r.Client().WatchPageTargets(ctxt) } go func() { diff --git a/pool.go b/pool.go index 628da7e..28480bf 100644 --- a/pool.go +++ b/pool.go @@ -74,7 +74,11 @@ func (p *Pool) Allocate(ctxt context.Context, opts ...runner.CommandLineOption) // create runner r.r, err = runner.New(append([]runner.CommandLineOption{ - runner.HeadlessPathPort("", r.port), + runner.ExecPath(runner.LookChromeNames("headless_shell")), + runner.RemoteDebuggingPort(r.port), + runner.NoDefaultBrowserCheck, + runner.NoFirstRun, + runner.Headless, }, opts...)...) if err != nil { defer r.Release() diff --git a/runner/path_darwin.go b/runner/path_darwin.go index 5e420a3..8bf4446 100644 --- a/runner/path_darwin.go +++ b/runner/path_darwin.go @@ -3,10 +3,11 @@ package runner const ( - // DefaultChromePath is the default path to the Chrome application. + // DefaultChromePath is the default path to use for Chrome if the + // executable is not in $PATH. DefaultChromePath = `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome` ) -func findChromePath() string { - return DefaultChromePath -} +// DefaultChromeNames are the default Chrome executable names to look for in +// $PATH. +var DefaultChromeNames []string diff --git a/runner/path_unix.go b/runner/path_unix.go index 2272107..4b1af77 100644 --- a/runner/path_unix.go +++ b/runner/path_unix.go @@ -2,30 +2,18 @@ package runner -import "os/exec" - const ( - // DefaultChromePath is the default path to the google-chrome executable if - // a variant cannot be found on $PATH. + // DefaultChromePath is the default path to use for Chrome if the + // executable is not in $PATH. DefaultChromePath = "/usr/bin/google-chrome" ) -// chromeNames are the Chrome executable names to search for in the path. -var chromeNames = []string{ +// DefaultChromeNames are the default Chrome executable names to look for in +// $PATH. +var DefaultChromeNames = []string{ "google-chrome", "chromium-browser", "chromium", "google-chrome-beta", "google-chrome-unstable", } - -func findChromePath() string { - for _, p := range chromeNames { - path, err := exec.LookPath(p) - if err == nil { - return path - } - } - - return DefaultChromePath -} diff --git a/runner/path_windows.go b/runner/path_windows.go index ecd5b33..2a6413a 100644 --- a/runner/path_windows.go +++ b/runner/path_windows.go @@ -2,32 +2,12 @@ package runner -import "os/exec" - const ( - // DefaultChromePath is the default path to use for Google Chrome if the + // DefaultChromePath is the default path to use for Chrome if the // executable is not in %PATH%. DefaultChromePath = `C:\Program Files (x86)\Google\Chrome\Application\chrome.exe` - - // DefaultEdgeDiagnosticsAdapterPath is the default path to use for the - // Microsoft Edge Diagnostics Adapter if the executable is not in %PATH%. - DefaultEdgeDiagnosticsAdapterPath = `c:\Edge\EdgeDiagnosticsAdapter\x64\EdgeDiagnosticsAdapter.exe` ) -func findChromePath() string { - path, err := exec.LookPath(`chrome.exe`) - if err == nil { - return path - } - - return DefaultChromePath -} - -func findEdgePath() string { - path, err := exec.LookPath(`EdgeDiagnosticsAdapter.exe`) - if err == nil { - return path - } - - return DefaultEdgeDiagnosticsAdapterPath -} +// DefaultChromeNames are the default Chrome executable names to look for in +// %PATH%. +var DefaultChromeNames = []string{`chrome.exe`} diff --git a/runner/runner.go b/runner/runner.go index c2623c2..c16cd8f 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -5,7 +5,6 @@ import ( "context" "fmt" "io/ioutil" - "net/url" "os" "os/exec" "regexp" @@ -16,6 +15,11 @@ import ( "github.com/chromedp/chromedp/client" ) +const ( + // DefaultUserDataDirPrefix is the default user data directory prefix. + DefaultUserDataDirPrefix = "chromedp-runner.%d." +) + // Error is a runner error. type Error string @@ -32,19 +36,17 @@ const ( // ErrAlreadyWaiting is the already waiting error. ErrAlreadyWaiting Error = "already waiting" + // ErrInvalidURLs is the invalid url-opts error. + ErrInvalidURLOpts Error = "invalid url-opts" + // ErrInvalidCmdOpts is the invalid cmd-opts error. ErrInvalidCmdOpts Error = "invalid cmd-opts" // ErrInvalidProcessOpts is the invalid process-opts error. ErrInvalidProcessOpts Error = "invalid process-opts" - // ErrExecPathNotSet is the exec-path not set set error. - ErrExecPathNotSet Error = "exec-path command line option not set, or chrome executable not found in PATH" -) - -const ( - // DefaultUserDataDirPrefix is the default user data directory prefix. - DefaultUserDataDirPrefix = "chromedp-runner.%d." + // ErrInvalidExecPath is the invalid exec-path error. + ErrInvalidExecPath Error = "invalid exec-path" ) // Runner holds information about a running Chrome process. @@ -59,19 +61,18 @@ type Runner struct { func New(opts ...CommandLineOption) (*Runner, error) { var err error - cliOpts := map[string]interface{}{} + cliOpts := make(map[string]interface{}) // apply opts for _, o := range opts { - err = o(cliOpts) - if err != nil { + if err = o(cliOpts); err != nil { return nil, err } } // set default Chrome options if exec-path not provided if _, ok := cliOpts["exec-path"]; !ok { - cliOpts["exec-path"] = findChromePath() + cliOpts["exec-path"] = LookChromeNames() for k, v := range map[string]interface{}{ "no-first-run": true, "no-default-browser-check": true, @@ -86,8 +87,7 @@ func New(opts ...CommandLineOption) (*Runner, error) { // add KillProcessGroup and ForceKill if no other cmd opts provided if _, ok := cliOpts["cmd-opts"]; !ok { for _, o := range []CommandLineOption{KillProcessGroup, ForceKill} { - err = o(cliOpts) - if err != nil { + if err = o(cliOpts); err != nil { return nil, err } } @@ -104,9 +104,9 @@ var cliOptRE = regexp.MustCompile(`^[a-z0-9\-]+$`) // buildOpts generates the command line options for Chrome. func (r *Runner) buildOpts() []string { var opts []string + var urls []string - // process options - var urlstr string + // process opts for k, v := range r.opts { if !cliOptRE.MatchString(k) || v == nil { continue @@ -116,8 +116,8 @@ func (r *Runner) buildOpts() []string { case "exec-path", "cmd-opts", "process-opts": continue - case "start-url": - urlstr = v.(string) + case "url-opts": + urls = v.([]string) default: switch z := v.(type) { @@ -135,16 +135,16 @@ func (r *Runner) buildOpts() []string { } } - if urlstr == "" { - urlstr = "about:blank" + if urls == nil { + urls = append(urls, "about:blank") } - return append(opts, urlstr) + return append(opts, urls...) } // Start starts a Chrome process using the specified context. The Chrome // process can be terminated by closing the passed context. -func (r *Runner) Start(ctxt context.Context) error { +func (r *Runner) Start(ctxt context.Context, opts ...string) error { var err error var ok bool @@ -156,11 +156,6 @@ func (r *Runner) Start(ctxt context.Context) error { return ErrAlreadyStarted } - // setup context - if ctxt == nil { - ctxt = context.Background() - } - // set user data dir, if not provided _, ok = r.opts["user-data-dir"] if !ok { @@ -172,36 +167,41 @@ func (r *Runner) Start(ctxt context.Context) error { } } - // ensure exec-path set - execPath, ok := r.opts["exec-path"] - if !ok { - return ErrExecPathNotSet + // get exec path + var execPath string + if p, ok := r.opts["exec-path"]; ok { + execPath, ok = p.(string) + if !ok { + return ErrInvalidExecPath + } + } + + // ensure execPath is valid + if execPath == "" { + return ErrInvalidExecPath } // create cmd - r.cmd = exec.CommandContext(ctxt, execPath.(string), r.buildOpts()...) + r.cmd = exec.CommandContext(ctxt, execPath, append(r.buildOpts(), opts...)...) // apply cmd opts if cmdOpts, ok := r.opts["cmd-opts"]; ok { for _, co := range cmdOpts.([]func(*exec.Cmd) error) { - err = co(r.cmd) - if err != nil { + if err = co(r.cmd); err != nil { return err } } } // start process - err = r.cmd.Start() - if err != nil { + if err = r.cmd.Start(); err != nil { return err } // apply process opts if processOpts, ok := r.opts["process-opts"]; ok { for _, po := range processOpts.([]func(*os.Process) error) { - err = po(r.cmd.Process) - if err != nil { + if err = po(r.cmd.Process); err != nil { // TODO: do something better here, as we want to kill // the child process, do cleanup, etc. panic(err) @@ -305,12 +305,6 @@ func (r *Runner) Client(opts ...client.Option) *client.Client { )...) } -// WatchPageTargets returns a channel that will receive new page targets as -// they are created. -func (r *Runner) WatchPageTargets(ctxt context.Context, opts ...client.Option) <-chan client.Target { - return r.Client(opts...).WatchPageTargets(ctxt) -} - // Run starts a new Chrome process runner, using the provided context and // command line options. func Run(ctxt context.Context, opts ...CommandLineOption) (*Runner, error) { @@ -323,20 +317,19 @@ func Run(ctxt context.Context, opts ...CommandLineOption) (*Runner, error) { } // start - err = r.Start(ctxt) - if err != nil { + if err = r.Start(ctxt); err != nil { return nil, err } return r, nil } -// CommandLineOption is a Chrome command line option. +// CommandLineOption is a runner command line option. // // see: http://peter.sh/experiments/chromium-command-line-switches/ type CommandLineOption func(map[string]interface{}) error -// Flag is a generic Chrome command line option to pass a name=value flag to +// Flag is a generic command line option to pass a name=value flag to // Chrome. func Flag(name string, value interface{}) CommandLineOption { return func(m map[string]interface{}) error { @@ -360,33 +353,12 @@ func Path(path string) CommandLineOption { } } -// HeadlessPathPort is the Chrome command line option to set the default -// settings for running the headless_shell executable. If path is empty, then -// an attempt will be made to find headless_shell on the path. -func HeadlessPathPort(path string, port int) CommandLineOption { - if path == "" { - path, _ = exec.LookPath("headless_shell") - } - - return func(m map[string]interface{}) error { - m["exec-path"] = path - m["remote-debugging-port"] = port - m["headless"] = true - return nil - } -} - -// ExecPath is a Chrome command line option to set the exec path. +// ExecPath is a command line option to set the exec path. func ExecPath(path string) CommandLineOption { return Flag("exec-path", path) } -// Port is the Chrome command line option to set the remote debugging port. -func Port(port int) CommandLineOption { - return Flag("remote-debugging-port", port) -} - -// UserDataDir is the Chrome command line option to set the user data dir. +// UserDataDir is the command line option to set the user data dir. // // Note: set this option to manually set the profile directory used by Chrome. // When this is not set, then a default path will be created in the /tmp @@ -395,27 +367,17 @@ func UserDataDir(dir string) CommandLineOption { return Flag("user-data-dir", dir) } -// StartURL is the Chrome command line option to set the initial URL. -func StartURL(urlstr string) CommandLineOption { - return Flag("start-url", urlstr) -} - -// Proxy is the Chrome command line option to set the outbound proxy. -func Proxy(proxy string) CommandLineOption { +// ProxyServer is the command line option to set the outbound proxy server. +func ProxyServer(proxy string) CommandLineOption { return Flag("proxy-server", proxy) } -// ProxyPacURL is the Chrome command line option to set the URL of a proxy PAC file. -func ProxyPacURL(pacURL url.URL) CommandLineOption { - return Flag("proxy-pac-url", pacURL.String()) -} - -// WindowSize is the Chrome command line option to set the initial window size. +// WindowSize is the command line option to set the initial window size. func WindowSize(width, height int) CommandLineOption { return Flag("window-size", fmt.Sprintf("%d,%d", width, height)) } -// UserAgent is the Chrome command line option to set the default User-Agent +// UserAgent is the command line option to set the default User-Agent // header. func UserAgent(userAgent string) CommandLineOption { return Flag("user-agent", userAgent) @@ -438,45 +400,83 @@ func NoDefaultBrowserCheck(m map[string]interface{}) error { return Flag("no-default-browser-check", true)(m) } -// DisableGPU is the Chrome command line option to disable the GPU process. +// RemoteDebuggingPort is the command line option to set the remote +// debugging port. +func RemoteDebuggingPort(port int) CommandLineOption { + return Flag("remote-debugging-port", port) +} + +// Headless is the command line option to run in headless mode. +func Headless(m map[string]interface{}) error { + return Flag("headless", true)(m) +} + +// DisableGPU is the command line option to disable the GPU process. func DisableGPU(m map[string]interface{}) error { return Flag("disable-gpu", true)(m) } -// CmdOpt is a Chrome command line option to modify the underlying exec.Cmd -// prior to invocation. +// URL is the command line option to add a URL to open on process start. +// +// Note: this can be specified multiple times, and each URL will be opened in a +// new tab. +func URL(urlstr string) CommandLineOption { + return func(m map[string]interface{}) error { + var urls []string + if u, ok := m["url-opts"]; ok { + urls, ok = u.([]string) + if !ok { + return ErrInvalidURLOpts + } + } + m["url-opts"] = append(urls, urlstr) + return nil + } +} + +// CmdOpt is a command line option to modify the underlying exec.Cmd +// prior to the call to exec.Cmd.Start in Run. func CmdOpt(o func(*exec.Cmd) error) CommandLineOption { return func(m map[string]interface{}) error { var opts []func(*exec.Cmd) error - if e, ok := m["cmd-opts"]; ok { opts, ok = e.([]func(*exec.Cmd) error) if !ok { return ErrInvalidCmdOpts } } - m["cmd-opts"] = append(opts, o) - return nil } } -// ProcessOpt is a Chrome command line option to modify the child os.Process -// after started exec.Cmd.Start. +// ProcessOpt is a command line option to modify the child os.Process +// after the call to exec.Cmd.Start in Run. func ProcessOpt(o func(*os.Process) error) CommandLineOption { return func(m map[string]interface{}) error { var opts []func(*os.Process) error - if e, ok := m["process-opts"]; ok { opts, ok = e.([]func(*os.Process) error) if !ok { return ErrInvalidProcessOpts } } - m["process-opts"] = append(opts, o) - return nil } } + +// LookChromeNames looks for the platform's DefaultChromeNames and any +// additional names using exec.LookPath, returning the first encountered +// location or the platform's DefaultChromePath if no names are found on the +// path. +func LookChromeNames(additional ...string) string { + for _, p := range append(additional, DefaultChromeNames...) { + path, err := exec.LookPath(p) + if err == nil { + return path + } + } + + return DefaultChromePath +}