Skip to content

Commit 1de6866

Browse files
authored
Merge pull request #19 from rusq/list-browsers
Add support for custom browser executable
2 parents 38e469e + 561e7f5 commit 1de6866

File tree

5 files changed

+299
-99
lines changed

5 files changed

+299
-99
lines changed

browser.go

Lines changed: 123 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ import (
1313
"github.com/go-rod/rod/lib/proto"
1414
)
1515

16-
// newBrwsrLauncher creates a new browser launcher with the given headless
17-
// mode.
16+
// newBrwsrLauncher creates a new incognito browser launcher with the given
17+
// headless mode.
1818
func (c *Client) newBrwsrLauncher(headless bool) *launcher.Launcher {
1919
l := launcher.New().Headless(headless).Leakless(isLeaklessEnabled).Devtools(false)
20-
if binpath, ok := lookPath(); !c.opts.useBundledBrwsr && ok {
20+
if binpath, ok := c.opts.browserPath(); ok {
2121
l = l.Bin(binpath)
2222
}
2323
return l
@@ -26,60 +26,116 @@ func (c *Client) newBrwsrLauncher(headless bool) *launcher.Launcher {
2626
// usrBrwsrLauncher creates a new user-mode browser launcher.
2727
func (c *Client) usrBrwsrLauncher() *launcher.Launcher {
2828
l := launcher.NewUserMode().Headless(false).Leakless(isLeaklessEnabled).Devtools(false)
29-
if binpath, ok := lookPath(); !c.opts.useBundledBrwsr && ok {
29+
if binpath, ok := c.opts.browserPath(); ok {
3030
l = l.Bin(binpath)
3131
}
3232
return l
3333
}
3434

35+
// browserPath returns the path to the browser executable and a boolean
36+
// indicating whether the path is valid.
37+
func (o options) browserPath() (path string, ok bool) {
38+
if o.useBundledBrwsr && !o.forceUser {
39+
// bundled browser can't operate in the user mode. forceUser overrides
40+
// useBundledBrwsr.
41+
return "", false
42+
}
43+
if o.localBrowser != "" {
44+
if p, err := exec.LookPath(o.localBrowser); err == nil {
45+
return p, true
46+
}
47+
}
48+
return lookPath()
49+
}
50+
51+
var ErrNoBrowsers = fmt.Errorf("no browsers found")
52+
53+
// ListBrowsers returns a list of browsers that are installed on the system.
54+
func ListBrowsers() ([]LocalBrowser, error) {
55+
LocalBrowsers, ok := discover()
56+
if !ok {
57+
return nil, ErrNoBrowsers
58+
}
59+
return LocalBrowsers, nil
60+
}
61+
62+
const (
63+
bChrome = "Google Chrome"
64+
bChromium = "Chromium"
65+
bEdge = "Microsoft Edge"
66+
bBrave = "Brave"
67+
)
68+
69+
// LocalBrowser represents a browser that is installed on the system.
70+
type LocalBrowser struct {
71+
Name string
72+
Path string
73+
}
74+
75+
// discover returns the list of browsers that are installed on the system and a
76+
// boolean indicating whether any browsers were found.
77+
func discover() (found []LocalBrowser, has bool) {
78+
for _, br := range browserList {
79+
var err error
80+
p, err := exec.LookPath(br.Path)
81+
if err == nil {
82+
found = append(found, LocalBrowser{br.Name, p})
83+
}
84+
}
85+
86+
return found, len(found) > 0
87+
}
88+
89+
var browserList = map[string][]LocalBrowser{
90+
"darwin": {
91+
{bBrave, "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"},
92+
{bEdge, "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"},
93+
{bChrome, "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"},
94+
{bChromium, "/Applications/Chromium.app/Contents/MacOS/Chromium"},
95+
{bChrome, "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary"},
96+
{bChrome, "/usr/bin/google-chrome-stable"},
97+
{bChrome, "/usr/bin/google-chrome"},
98+
{bChromium, "/usr/bin/chromium"},
99+
{bChromium, "/usr/bin/chromium-browser"},
100+
},
101+
"linux": {
102+
{bChrome, "brave-browser"},
103+
{bChrome, "chrome"},
104+
{bChrome, "google-chrome"},
105+
{bChrome, "/usr/bin/google-chrome"},
106+
{bChrome, "/usr/bin/brave-browser"},
107+
{bChrome, "microsoft-edge"},
108+
{bChrome, "/usr/bin/microsoft-edge"},
109+
{bChrome, "chromium"},
110+
{bChrome, "chromium-browser"},
111+
{bChrome, "/usr/bin/google-chrome-stable"},
112+
{bChrome, "/usr/bin/chromium"},
113+
{bChrome, "/usr/bin/chromium-browser"},
114+
{bChrome, "/snap/bin/chromium"},
115+
{bChrome, "/data/data/com.termux/files/usr/bin/chromium-browser"},
116+
},
117+
"openbsd": {
118+
{bChrome, "chrome"},
119+
{bChrome, "chromium"},
120+
},
121+
"windows": append(
122+
[]LocalBrowser{{bEdge, "edge"}, {bBrave, "brave"}, {bChrome, "chrome"}},
123+
expandWindowsExePaths(
124+
LocalBrowser{bEdge, `Microsoft\Edge\Application\msedge.exe`},
125+
LocalBrowser{bEdge, `BraveSoftware\Brave-Browser\Application\brave.exe`},
126+
LocalBrowser{bEdge, `Google\Chrome\Application\chrome.exe`},
127+
LocalBrowser{bEdge, `Chromium\Application\chrome.exe`},
128+
)...),
129+
}[runtime.GOOS]
130+
35131
// lookPath is extended launcher.LookPath that includes support for Brave
36132
// browser.
37133
//
38134
// (c) MIT license: Copyright 2019 Yad Smood
39135
func lookPath() (found string, has bool) {
40-
list := map[string][]string{
41-
"darwin": {
42-
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
43-
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
44-
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
45-
"/Applications/Chromium.app/Contents/MacOS/Chromium",
46-
"/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
47-
"/usr/bin/google-chrome-stable",
48-
"/usr/bin/google-chrome",
49-
"/usr/bin/chromium",
50-
"/usr/bin/chromium-browser",
51-
},
52-
"linux": {
53-
"brave-browser",
54-
"chrome",
55-
"google-chrome",
56-
"/usr/bin/google-chrome",
57-
"/usr/bin/brave-browser",
58-
"microsoft-edge",
59-
"/usr/bin/microsoft-edge",
60-
"chromium",
61-
"chromium-browser",
62-
"/usr/bin/google-chrome-stable",
63-
"/usr/bin/chromium",
64-
"/usr/bin/chromium-browser",
65-
"/snap/bin/chromium",
66-
"/data/data/com.termux/files/usr/bin/chromium-browser",
67-
},
68-
"openbsd": {
69-
"chrome",
70-
"chromium",
71-
},
72-
"windows": append([]string{"chrome", "edge"}, expandWindowsExePaths(
73-
`Microsoft\Edge\Application\msedge.exe`,
74-
`BraveSoftware\Brave-Browser\Application\brave.exe`,
75-
`Google\Chrome\Application\chrome.exe`,
76-
`Chromium\Application\chrome.exe`,
77-
)...),
78-
}[runtime.GOOS]
79-
80-
for _, path := range list {
136+
for _, b := range browserList {
81137
var err error
82-
found, err = exec.LookPath(path)
138+
found, err = exec.LookPath(b.Path)
83139
has = err == nil
84140
if has {
85141
break
@@ -89,18 +145,17 @@ func lookPath() (found string, has bool) {
89145
return
90146
}
91147

92-
// expandWindowsExePaths is a verbatim copy of the function from rod's
93-
// browser.go.
148+
// expandWindowsExePaths is based on the same function from rod's browser.go.
94149
//
95150
// (c) MIT license: Copyright 2019 Yad Smood
96-
func expandWindowsExePaths(list ...string) []string {
97-
newList := []string{}
151+
func expandWindowsExePaths(list ...LocalBrowser) []LocalBrowser {
152+
newList := []LocalBrowser{}
98153
for _, p := range list {
99154
newList = append(
100155
newList,
101-
filepath.Join(os.Getenv("ProgramFiles"), p),
102-
filepath.Join(os.Getenv("ProgramFiles(x86)"), p),
103-
filepath.Join(os.Getenv("LocalAppData"), p),
156+
LocalBrowser{p.Name, filepath.Join(os.Getenv("ProgramFiles"), p.Path)},
157+
LocalBrowser{p.Name, filepath.Join(os.Getenv("ProgramFiles(x86)"), p.Path)},
158+
LocalBrowser{p.Name, filepath.Join(os.Getenv("LocalAppData"), p.Path)},
104159
)
105160
}
106161

@@ -120,3 +175,18 @@ func setCookies(browser *rod.Browser, cookies []*http.Cookie) error {
120175
}
121176
return nil
122177
}
178+
179+
// RemveBundled removes the bundled browser from the system.
180+
func RemoveBrowser() error {
181+
bpath := launcher.DefaultBrowserDir
182+
if bpath == "" {
183+
return nil
184+
}
185+
if _, err := os.Stat(bpath); os.IsNotExist(err) {
186+
return nil
187+
}
188+
if err := os.RemoveAll(bpath); err != nil {
189+
return fmt.Errorf("failed to remove bundled browser: %w", err)
190+
}
191+
return nil
192+
}

browser_test.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package slackauth
2+
3+
import (
4+
"net/http"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
"time"
9+
)
10+
11+
func Test_options_browserPath(t *testing.T) {
12+
// create a fake browser
13+
tmp := t.TempDir()
14+
fakeChromeBin := filepath.Join(tmp, "chrome")
15+
if err := os.WriteFile(fakeChromeBin, nil, 0o755); err != nil {
16+
t.Fatal(err)
17+
}
18+
19+
lookPathBin, lookPathOk := lookPath() // whatever is discovered here is fine
20+
21+
type fields struct {
22+
cookies []*http.Cookie
23+
userAgent string
24+
autoTimeout time.Duration
25+
forceUser bool
26+
useBundledBrwsr bool
27+
localBrowser string
28+
codeFn func(email string) (code int, err error)
29+
debug bool
30+
lg Logger
31+
}
32+
tests := []struct {
33+
name string
34+
fields fields
35+
wantPath string
36+
wantOk bool
37+
}{
38+
{
39+
name: "bundled browser",
40+
fields: fields{
41+
useBundledBrwsr: true,
42+
},
43+
wantPath: "",
44+
wantOk: false,
45+
},
46+
{
47+
name: "local browser",
48+
fields: fields{
49+
localBrowser: fakeChromeBin,
50+
},
51+
wantPath: fakeChromeBin,
52+
wantOk: true,
53+
},
54+
{
55+
name: "look path browser",
56+
fields: fields{},
57+
wantPath: lookPathBin,
58+
wantOk: lookPathOk,
59+
},
60+
{
61+
name: "force user overrides the use of bundled browser",
62+
fields: fields{
63+
useBundledBrwsr: true,
64+
forceUser: true,
65+
localBrowser: fakeChromeBin,
66+
},
67+
wantPath: fakeChromeBin,
68+
wantOk: true,
69+
},
70+
}
71+
for _, tt := range tests {
72+
t.Run(tt.name, func(t *testing.T) {
73+
o := options{
74+
cookies: tt.fields.cookies,
75+
userAgent: tt.fields.userAgent,
76+
autoTimeout: tt.fields.autoTimeout,
77+
forceUser: tt.fields.forceUser,
78+
useBundledBrwsr: tt.fields.useBundledBrwsr,
79+
localBrowser: tt.fields.localBrowser,
80+
codeFn: tt.fields.codeFn,
81+
debug: tt.fields.debug,
82+
lg: tt.fields.lg,
83+
}
84+
gotPath, gotOk := o.browserPath()
85+
if gotPath != tt.wantPath {
86+
t.Errorf("options.browserPath() gotPath = %v, want %v", gotPath, tt.wantPath)
87+
}
88+
if gotOk != tt.wantOk {
89+
t.Errorf("options.browserPath() gotOk = %v, want %v", gotOk, tt.wantOk)
90+
}
91+
})
92+
}
93+
}
94+
95+
func TestRemoveBrowser(t *testing.T) {
96+
tests := []struct {
97+
name string
98+
wantErr bool
99+
}{
100+
{
101+
name: "no error",
102+
wantErr: false,
103+
},
104+
}
105+
for _, tt := range tests {
106+
t.Run(tt.name, func(t *testing.T) {
107+
if err := RemoveBrowser(); (err != nil) != tt.wantErr {
108+
t.Errorf("RemoveBrowser() error = %v, wantErr %v", err, tt.wantErr)
109+
}
110+
})
111+
}
112+
}

cmd/playground/main.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,15 @@ func run(ctx context.Context) error {
7979
}
8080
defer c.Close()
8181

82+
if b, err := slackauth.ListBrowsers(); err != nil {
83+
slog.Warn("no browsers found on the system, using built-in", "err", err)
84+
} else {
85+
fmt.Println("Available browsers on the system:")
86+
for _, br := range b {
87+
fmt.Printf("%s:\t%s\n", br.Name, br.Path)
88+
}
89+
}
90+
8291
var (
8392
token string
8493
cookies []*http.Cookie

login_auto.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ func enterCode(page elementer, code int) error {
120120
return nil
121121
}
122122

123-
// startPuppet starts a new browser instance and returns a handle to it.
123+
// startPuppet starts a new browser instance and returns a handle to it. It ignores
124+
// user browser flag and always starts an incognito browser.
124125
func (c *Client) startPuppet(ctx context.Context, headless bool) (*rod.Browser, error) {
125126
ctx, task := trace.NewTask(ctx, "startPuppet")
126127
defer task.End()

0 commit comments

Comments
 (0)