Documentation
¶
Overview ¶
Package launcher for launching browser utils.
Example (Custom_launch) ¶
package main
import (
"fmt"
"os/exec"
"github.com/headlesslab/wand"
"github.com/headlesslab/wand/lib/launcher"
"github.com/headlesslab/wand/lib/utils"
)
func main() {
// get the browser executable path
path := launcher.NewBrowser().MustGet()
l := launcher.New()
defer l.Cleanup()
// use the FormatArgs to construct args, this line is optional, you can construct the args manually
args := l.FormatArgs()
// A browser started this way has no Orphan guard: it outlives a wand
// process that dies without killing it. launcher.New().Launch() adds the guard.
cmd := exec.Command(path, args...)
parser := launcher.NewURLParser()
cmd.Stderr = parser
utils.E(cmd.Start())
// Reap the browser process once it has exited.
defer func() { _ = cmd.Wait() }()
u := launcher.MustResolveURL(<-parser.URL)
browser := wand.New().ControlURL(u).MustConnect()
defer browser.MustClose()
fmt.Println(browser.MustVersion().ProtocolVersion)
}
Output: 1.3
Example (Print_browser_CLI_output) ¶
This example has no output comment, so it is compiled but never run: what it prints is the browser's own command line output, which is not the same twice.
package main
import (
"os"
"github.com/headlesslab/wand"
"github.com/headlesslab/wand/lib/launcher"
)
func main() {
l := launcher.New().Logger(os.Stdout)
defer l.Cleanup()
// Pipe the browser stderr and stdout to os.Stdout .
browser := wand.New().ControlURL(l.MustLaunch()).MustConnect()
defer browser.MustClose()
}
Output:
Example (Use_system_browser) ¶
This example has no output comment, so it is compiled but never run: it needs a System browser installed, which a machine may not have.
package main
import (
"github.com/headlesslab/wand"
"github.com/headlesslab/wand/lib/launcher"
)
func main() {
if path, exists := launcher.LookPath(); exists {
l := launcher.New().Bin(path)
defer l.Cleanup()
browser := wand.New().ControlURL(l.MustLaunch()).MustConnect()
defer browser.MustClose()
}
}
Output:
Index ¶
- Constants
- Variables
- func DefaultHosts(source Source) []string
- func LookPath() (found string, has bool)
- func MustResolveURL(u string) string
- func Open(url string)
- func ResolveURL(u string) (string, error)
- type Binary
- type Browser
- type Launcher
- func (l *Launcher) AlwaysOpenPDFExternally() *Launcher
- func (l *Launcher) Append(name flags.Flag, values ...string) *Launcher
- func (l *Launcher) Bin(path string) *Launcher
- func (l *Launcher) Binary(binary Binary) *Launcher
- func (l *Launcher) Cleanup()
- func (l *Launcher) Client() (*cdp.Client, error)
- func (l *Launcher) ClientHeader() (string, http.Header)
- func (l *Launcher) Context(ctx context.Context) *Launcher
- func (l *Launcher) Delete(name flags.Flag) *Launcher
- func (l *Launcher) Devtools(autoOpenForTabs bool) *Launcher
- func (l *Launcher) Download(enable bool) *Launcher
- func (l *Launcher) Env(env ...string) *Launcher
- func (l *Launcher) FormatArgs() []string
- func (l *Launcher) Get(name flags.Flag) string
- func (l *Launcher) GetFlags(name flags.Flag) ([]string, bool)
- func (l *Launcher) Has(name flags.Flag) bool
- func (l *Launcher) Headless(enable bool) *Launcher
- func (l *Launcher) HeadlessNew(enable bool) *Launcher
- func (l *Launcher) Hosts(templates ...string) *Launcher
- func (l *Launcher) IgnoreCerts(pks []crypto.PublicKey) error
- func (l *Launcher) JSON() []byte
- func (l *Launcher) KeepUserDataDir() *Launcher
- func (l *Launcher) Kill()
- func (l *Launcher) Launch() (string, error)
- func (l *Launcher) Leakless(enable bool) *Launcher
- func (l *Launcher) Logger(w io.Writer) *Launcher
- func (l *Launcher) MustClient() *cdp.Client
- func (l *Launcher) MustLaunch() string
- func (l *Launcher) NoSandbox(enable bool) *Launcher
- func (l *Launcher) PID() int
- func (l *Launcher) Preferences(pref string) *Launcher
- func (l *Launcher) ProfileDir(dir string) *Launcher
- func (l *Launcher) Proxy(host string) *Launcher
- func (l *Launcher) RemoteDebuggingPort(port int) *Launcher
- func (l *Launcher) ResolveBin() (string, error)
- func (l *Launcher) Revision(rev int) *Launcher
- func (l *Launcher) Set(name flags.Flag, values ...string) *Launcher
- func (l *Launcher) Source(source Source) *Launcher
- func (l *Launcher) StartURL(u string) *Launcher
- func (l *Launcher) UserDataDir(dir string) *Launcher
- func (l *Launcher) Version(version string) *Launcher
- func (l *Launcher) WindowPosition(x, y int) *Launcher
- func (l *Launcher) WindowSize(x, y int) *Launcher
- func (l *Launcher) WorkingDir(path string) *Launcher
- func (l *Launcher) XVFB(args ...string) *Launcher
- type Manager
- type Source
- type URLParser
Examples ¶
Constants ¶
const ( // EnvBrowserBin is the browser binary to launch, as [Launcher.Bin] and // -wand=bin= name one; when set, Browser resolution goes no further. EnvBrowserBin = "WAND_BROWSER_BIN" // EnvBrowserCache sets the browser cache, DefaultBrowserDir, when the // process starts. EnvBrowserCache = "WAND_BROWSER_CACHE" // EnvBrowserHosts overrides the Download hosts: URL templates separated // by commas, in the form DefaultHosts describes. EnvBrowserHosts = "WAND_BROWSER_HOSTS" // EnvBrowserDownload set to "0" keeps Browser resolution from downloading // a Managed browser, as [Launcher.Download] does; any other value, or // none, leaves the download on. EnvBrowserDownload = "WAND_BROWSER_DOWNLOAD" // EnvBrowserSource selects the Browser source: "chrome" for Chrome for // Testing, the default, or "chromium" for Chromium trunk builds. EnvBrowserSource = "WAND_BROWSER_SOURCE" // EnvBrowserBinary selects the Chrome for Testing binary: "chrome", the // default, or "chrome-headless-shell". EnvBrowserBinary = "WAND_BROWSER_BINARY" )
The WAND_BROWSER_* environment variables: the defaults, for a whole process, of the launcher options of the same name. The order of precedence is the option set in code, then the -wand= flag of lib/defaults, then these variables, then the launcher's own discovery (ADR-0005).
const (
// HeaderName for remote launch.
HeaderName = "Wand-Launcher"
)
Variables ¶
var DefaultBrowserDir = cacheDir()
DefaultBrowserDir is the browser cache: EnvBrowserCache when set, otherwise wand/browser under os.UserCacheDir ($XDG_CACHE_HOME or ~/.cache on Linux, ~/Library/Caches on macOS, %LocalAppData% on Windows), or under the temporary directory for a user without a cache directory. Each Managed browser lives in its own subdirectory: chrome-<version>, chrome-headless-shell-<version> or chromium-<revision>.
var DefaultUserDataDirPrefix = filepath.Join(os.TempDir(), "wand", "user-data")
DefaultUserDataDirPrefix ...
var DefaultUserModeDir = userModeDir()
DefaultUserModeDir is the profile directory of User mode, the default of Launcher.UserDataDir in NewUserMode: wand/user-mode under os.UserConfigDir ($XDG_CONFIG_HOME or ~/.config on Linux, ~/Library/Application Support on macOS, %AppData% on Windows), or under the temporary directory for a user without a configuration directory. It is a persistent directory wand owns, apart from Chrome's own profile and from the browser cache: a profile is user data, not a re-downloadable artefact (ADR-0010).
var ErrAlreadyLaunched = errors.New("already launched")
ErrAlreadyLaunched is an error that indicates the launcher has already been launched.
var ErrNoBrowser = errors.New("[launcher] no browser found")
ErrNoBrowser is the error of a Browser resolution that found nothing: no explicit path, no System browser, no Managed browser in the cache and no download, either switched off or failed. The error wrapping it lists every step tried.
Functions ¶
func DefaultHosts ¶
DefaultHosts are the Download hosts of a Browser source: Google's bucket and npmmirror, as URL templates. A template names the archive with three placeholders: {version}, the Chrome for Testing version or the Chromium trunk position; {platform}, the Chrome for Testing platform such as linux64 or the Chromium bucket prefix such as Linux_x64; and {archive}, the file name such as chrome-linux64.zip or chrome-linux.zip. Every host is probed concurrently, the first to answer serves the download and the others are its fallbacks.
func LookPath ¶
LookPath searches for a System browser at the paths systemBrowsers lists for the current operating system: the first that exists wins, whatever its version.
func ResolveURL ¶
ResolveURL by requesting the u, it will try best to normalize the u. The format of u can be "9222", ":9222", "host:9222", "ws://host:9222", "wss://host:9222", "https://host:9222" "http://host:9222". The return string will look like: "ws://host:9222/devtools/browser/4371405f-84df-4ad6-9e0f-eab81f7521cc"
An answer that is not a browser's is an error: a status other than 200, as a proxy in the way of the port gives, or a body without a webSocketDebuggerUrl, as a web server that happens to listen on the port gives. The Snapshot took both for a browser and returned a URL with "<nil>" for a path (rod #1176).
Types ¶
type Browser ¶
type Browser struct {
Context context.Context
// Source of the archive: SourceChrome, the default, or SourceChromium.
// EnvBrowserSource sets the default.
Source Source
// Binary of a Chrome for Testing archive: BinaryChrome, the default, or
// BinaryHeadlessShell. A Chromium trunk build holds only BinaryChrome.
// EnvBrowserBinary sets the default.
Binary Binary
// Version of Chrome for Testing to use, the Target Chrome by default.
Version string
// Revision is the trunk position of the Chromium trunk build to use, the
// Companion Chromium by default.
Revision int
// Hosts to download the archive from, as the URL templates DefaultHosts
// describes; empty means DefaultHosts of the Source. EnvBrowserHosts
// sets the default.
Hosts []string
// RootDir is the browser cache, DefaultBrowserDir by default.
RootDir string
// Logger to print output
Logger utils.Logger
// HTTPClient to download the browser
HTTPClient *http.Client
}
Browser is a helper to download a Managed browser: the Target Chrome from Chrome for Testing by default, or the Companion Chromium from Chromium trunk builds, verified against the pins and cached under RootDir.
func NewBrowser ¶
func NewBrowser() *Browser
NewBrowser with default values, the environment variables applied.
func (*Browser) Download ¶
Download the browser from the first of the Hosts to answer, verified against the pins before extraction, into Browser.Dir. When that directory exists already nothing is downloaded.
func (*Browser) Get ¶
Get is a smart helper to get the browser executable path. If Browser.BinPath is not valid it will auto download the browser to Browser.BinPath. Concurrent downloads of the same browser, in this process or another, serialize behind a file lock next to Browser.Dir, and the ones that waited find it in place.
type Launcher ¶
type Launcher struct {
Flags map[flags.Flag][]string `json:"flags"`
// contains filtered or unexported fields
}
Launcher is a helper to launch browser binary smartly.
func MustNewManaged ¶
MustNewManaged is similar to NewManaged.
func New ¶
func New() *Launcher
New returns the default arguments to start browser. Headless will be enabled by default. The Orphan guard (Launcher.Leakless) will be enabled by default. UserDataDir will use OS tmp dir by default, this folder will usually be cleaned up by the OS after reboot. The browser comes from Browser resolution at launch: an explicit path, then a System browser, then a Managed browser, downloaded only as a last resort; check Launcher.Bin, LookPath, Launcher.Download and Launcher.Source for more info.
func NewAppMode ¶
NewAppMode is a preset to run the browser like a native application. The u should be a URL.
func NewManaged ¶
NewManaged creates a default Launcher instance from launcher.Manager. The serviceURL must point to a launcher.Manager. It will send a http request to the serviceURL to get the default settings of the Launcher instance. For example if the launcher.Manager running on a Linux machine will return different default settings from the one on Mac. If Launcher.Leakless is enabled, the remote browser will be killed after the websocket is closed.
func NewUserMode ¶
func NewUserMode() *Launcher
NewUserMode is the User mode preset: a visible browser on a persistent profile wand owns, so that logins, extensions and settings survive between runs; useful for the automation of a personal browser. The profile is DefaultUserModeDir, made at launch if missing, and Launcher.UserDataDir overrides it. Branded Google Chrome of 136 or later refuses remote debugging on its own default profile, so a launcher pointed at Chrome's profile directory gets that refusal (ADR-0010). The Orphan guard is off, so the browser survives the wand process; a browser already listening on the port is reused rather than launched again, and a launch that fails on a running browser's port is resolved by closing that browser. After a browser this launcher started exits, Launcher.Cleanup removes the profile like any user data directory; Launcher.Kill leaves it. The browser comes from the same Browser resolution as New, so a System browser is preferred to a Managed one, and in a container the sandbox is off for the same reason it is in New.
func (*Launcher) AlwaysOpenPDFExternally ¶
AlwaysOpenPDFExternally switch. It will set chromium user preferences to enable the always_open_pdf_externally option.
func (*Launcher) Bin ¶
Bin of the browser binary to launch: the first step of Browser resolution, which then goes no further. It beats the -wand=bin= flag, which beats WAND_BROWSER_BIN; an empty path leaves the search to the steps below: a System browser from LookPath, then the Managed browser, cached or downloaded.
func (*Launcher) Binary ¶
Binary of the Chrome for Testing archive the Managed browser comes from: the full browser, the default, or chrome-headless-shell. WAND_BROWSER_BINARY sets the default.
func (*Launcher) Cleanup ¶
func (l *Launcher) Cleanup()
Cleanup waits for the browser to exit and removes flags.UserDataDir. Neither wait is unbounded: a browser still running cleanupBound after the call is killed, and the removal, which the helper processes of a browser can hold up for a moment (a crash handler on Windows for seconds), is retried for as long. A launcher that never launched has nothing to wait for; it removes the temporary directory New made up, in case a failed launch wrote the preferences into it, and leaves a directory the caller named alone.
func (*Launcher) ClientHeader ¶
ClientHeader for launching browser remotely via the launcher.Manager.
func (*Launcher) Download ¶
Download switch: whether Browser resolution may download the Managed browser when no explicit path, System browser or cached Managed browser is found. On by default; WAND_BROWSER_DOWNLOAD=0 switches it off for a whole process. With it off, a launch that finds nothing fails with ErrNoBrowser. Like Launcher.Bin it is a flag, so it reaches a remote launcher too.
func (*Launcher) Env ¶
Env to launch the browser process. The default value is os.Environ(). Usually you use it to set the timezone env. Such as:
Env(append(os.Environ(), "TZ=Asia/Tokyo")...)
func (*Launcher) FormatArgs ¶
FormatArgs returns the formatted arg list for cli.
func (*Launcher) Headless ¶
Headless switch. Whether to run browser in headless mode. A mode without visible UI.
func (*Launcher) HeadlessNew ¶
HeadlessNew switch is the "--headless=new" switch: https://developer.chrome.com/docs/chromium/new-headless
func (*Launcher) Hosts ¶
Hosts to download the Managed browser from, as the URL templates DefaultHosts describes, for Download hosts of your own. WAND_BROWSER_HOSTS sets the default.
func (*Launcher) IgnoreCerts ¶
IgnoreCerts configure the Chrome's ignore-certificate-errors-spki-list argument with the public keys.
func (*Launcher) KeepUserDataDir ¶
KeepUserDataDir after remote browser is closed. By default launcher.FlagUserDataDir will be removed.
func (*Launcher) Launch ¶
Launch a standalone temp browser instance and returns the debug url. bin and profileDir are optional, set them to empty to use the default values. If you want to reuse sessions, such as cookies, set the Launcher.UserDataDir to the same location.
With the Orphan guard off (Launcher.Leakless) and a browser already listening on Launcher.RemoteDebuggingPort, Launch attaches to it and returns its URL without starting anything. Such a browser is not the launcher's own: Launcher.PID is zero, and Launcher.Kill and Launcher.Cleanup have nothing to do, waiting for nothing and leaving it and its user data directory alone.
Please note launcher can only be used once.
func (*Launcher) Leakless ¶
Leakless switch is the Orphan guard: whether the browser dies with the wand process, however that process dies, with no helper process and nothing written at launch. It is on in New and off in NewUserMode. On Windows the browser joins a job object that the kernel closes, and so kills, with the wand process; on Linux it is started with the parent-death signal SIGKILL; on every POSIX platform it is also started with --remote-debugging-pipe on descriptors 3 and 4 that wand opens and never speaks on, so that a Chromium of 89 or later exits by itself when they close. CDP stays on the WebSocket Launcher.Launch returns. The pipe flag is added at launch, with its descriptors, and is not part of Launcher.FormatArgs. When disabled, Launcher.Kill and Launcher.Cleanup still kill the browser's process group.
func (*Launcher) Logger ¶
Logger to handle stdout and stderr from browser. For example, pipe all browser output to stdout:
launcher.New().Logger(os.Stdout)
func (*Launcher) MustClient ¶
MustClient similar to Launcher.Client.
func (*Launcher) MustLaunch ¶
MustLaunch is similar to Launch.
func (*Launcher) NoSandbox ¶
NoSandbox switch. Whether to run browser in no-sandbox mode. Linux users may face "running as root without --no-sandbox is not supported" in some Linux/Chrome combinations. This function helps switch mode easily. Be aware disabling sandbox is not trivial. Use at your own risk. Related doc: https://bugs.chromium.org/p/chromium/issues/detail?id=638180
func (*Launcher) Preferences ¶
Preferences set chromium user preferences, such as set the default search engine or disable the pdf viewer. The pref is a json string, the doc is here https://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/pref_names.cc
func (*Launcher) ProfileDir ¶
ProfileDir is the browser profile the browser will use. When set to empty, the profile 'Default' is used. Related article: https://superuser.com/a/377195
func (*Launcher) RemoteDebuggingPort ¶
RemoteDebuggingPort to launch the browser. Zero for a random port. Zero is the default value. If it's not zero and the Launcher.Leakless is disabled, the launcher will try to reconnect to it first, if the reconnection fails it will launch a new browser.
func (*Launcher) ResolveBin ¶
ResolveBin runs Browser resolution (ADR-0005) and returns the browser binary Launcher.Launch would start, without starting it: the explicit path from Launcher.Bin, the -wand=bin= flag or EnvBrowserBin, then a System browser from LookPath, then the Managed browser already in the cache, then its download unless Launcher.Download switched that off. An explicit path is taken as it is; nothing is compared by version. When every step finds nothing the error, wrapping ErrNoBrowser, lists them all. Launch runs it again, so a caller that wants the path for a message, or wants one download to serve many launches, calls it first and passes the result to Launcher.Bin.
func (*Launcher) Revision ¶
Revision of the Chromium trunk build the Managed browser is, the Companion Chromium by default. It selects the Chromium source. A revision other than the Companion Chromium has no recorded hash, so its download is not verified.
func (*Launcher) Set ¶
Set a command line argument when launching the browser. Be careful the first argument is a flag name, it shouldn't contain values. The values the will be joined with comma. A flag can have multiple values. If no values are provided the flag will be a boolean flag. You can use the Launcher.FormatArgs to debug the final CLI arguments. List of available flags: https://peter.sh/experiments/chromium-command-line-switches
func (*Launcher) Source ¶
Source of the Managed browser, the last steps of Browser resolution, cached or downloaded: Chrome for Testing at the Target Chrome, the default, or Chromium trunk builds at the Companion Chromium. WAND_BROWSER_SOURCE sets the default.
func (*Launcher) UserDataDir ¶
UserDataDir is where the browser will look for all of its state, such as cookie and cache. When set to empty, browser will use current OS home dir. Related doc: https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md
func (*Launcher) Version ¶
Version of Chrome for Testing the Managed browser is, the Target Chrome by default. It selects the Chrome for Testing source. A version other than the Target Chrome has no recorded hash, so its download is not verified.
func (*Launcher) WindowPosition ¶
WindowPosition for the browser.
func (*Launcher) WindowSize ¶
WindowSize for the browser.
func (*Launcher) WorkingDir ¶
WorkingDir to launch the browser process.
type Manager ¶
type Manager struct {
// Logger for key events
Logger utils.Logger
// Defaults should return the default Launcher settings
Defaults func(http.ResponseWriter, *http.Request) *Launcher
// BeforeLaunch hook is called right before the launching with the Launcher instance that will be used
// to launch the browser.
// Such as use it to filter malicious values of Launcher.UserDataDir, Launcher.Bin, or Launcher.WorkingDir.
BeforeLaunch func(*Launcher, http.ResponseWriter, *http.Request)
}
Manager is used to launch browsers via http server on another machine. The reason why we have Manager is after we launcher a browser, we can't dynamically change its CLI arguments, such as "--headless". The Manager allows us to decide what CLI arguments to pass to the browser when launch it remotely. The work flow looks like:
| Machine X | Machine Y |
| NewManaged("a.com") -|-> http.ListenAndServe("a.com", launcher.NewManager()) --> launch browser |
1. X send a http request to Y, Y respond default Launcher settings based the OS of Y.
2. X start a websocket connect to Y with the Launcher settings
3. Y launches a browser with the Launcher settings X
4. Y transparently proxy the websocket connect between X and the launched browser
type Source ¶
type Source string
Source is a Browser source: the archive family a Managed browser is downloaded from.
const ( // SourceChrome is Chrome for Testing, the default: Google's build of the // Chrome stable branch, pinned to the Target Chrome. SourceChrome Source = "chrome" // SourceChromium is Chromium trunk builds from Google's continuous-build // archive, pinned to the Companion Chromium: BSD-licensed Chromium for // deployments that cannot accept Google Chrome's terms. SourceChromium Source = "chromium" )
type URLParser ¶
type URLParser struct {
URL chan string
Buffer string // buffer for the browser stdout
// contains filtered or unexported fields
}
URLParser to get control url from stderr.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
fixtures
|
|
|
chrome-empty
command
Package main ...
|
Package main ... |
|
chrome-exit-err
command
Package main ...
|
Package main ... |
|
chrome-headless
command
Package main is a fake browser that answers --dump-dom the way a headless browser does, for the launcher's validation test.
|
Package main is a fake browser that answers --dump-dom the way a headless browser does, for the launcher's validation test. |
|
chrome-lib-missing
command
Package main ...
|
Package main ... |
|
Package flags ...
|
Package flags ... |
|
Package pins holds the Target Chrome and everything that rolls with it: the Chrome for Testing Stable version wand is aligned to and its Chromium main branch position, the Protocol roll the protocol layer is generated from, the Companion Chromium trunk build, and the SHA-256 of every Managed browser archive the launcher may download.
|
Package pins holds the Target Chrome and everything that rolls with it: the Chrome for Testing Stable version wand is aligned to and its Chromium main branch position, the Protocol roll the protocol layer is generated from, the Companion Chromium trunk build, and the SHA-256 of every Managed browser archive the launcher may download. |
|
generate
command
Package main is the Roll tool: it computes everything lib/launcher/pins holds and rewrites every place the pins appear, so the launcher, the protocol generator, the Roll, the release workflow and the READMEs all carry the same numbers.
|
Package main is the Roll tool: it computes everything lib/launcher/pins holds and rewrites every place the pins appear, so the launcher, the protocol generator, the Roll, the release workflow and the READMEs all carry the same numbers. |