mirror of
https://github.com/moonD4rk/HackBrowserData.git
synced 2026-05-19 18:58:03 +02:00
feat: support MSIX/UWP browsers on Windows (Arc, DuckDuckGo) (#563)
* chore: remove redundant separator comments in browser_test.go
This commit is contained in:
@@ -50,6 +50,8 @@ func pickFromConfigs(configs []types.BrowserConfig, opts PickOptions) ([]Browser
|
||||
// it's harmless (DPAPI reads Local State per-profile, D-Bus is stateless).
|
||||
retriever := keyretriever.DefaultRetriever(opts.KeychainPassword)
|
||||
|
||||
configs = resolveGlobs(configs)
|
||||
|
||||
var browsers []Browser
|
||||
for _, cfg := range configs {
|
||||
if name != "all" && cfg.Key != name {
|
||||
@@ -94,6 +96,34 @@ type retrieverSetter interface {
|
||||
SetRetriever(keyretriever.KeyRetriever)
|
||||
}
|
||||
|
||||
// resolveGlobs expands glob patterns in browser configs' UserDataDir.
|
||||
// This supports MSIX/UWP browsers on Windows whose package directories
|
||||
// contain a dynamic publisher hash suffix (e.g., "TheBrowserCompany.Arc_*").
|
||||
//
|
||||
// For literal paths (no glob metacharacters), Glob returns the path itself
|
||||
// when it exists, so the config passes through unchanged. When a path does
|
||||
// not exist and contains no metacharacters, Glob returns nil and the
|
||||
// original config is preserved — the main loop handles "not found" as usual.
|
||||
//
|
||||
// When a glob matches multiple directories, the config is duplicated so
|
||||
// each resolved path is treated as a separate browser data directory.
|
||||
func resolveGlobs(configs []types.BrowserConfig) []types.BrowserConfig {
|
||||
var out []types.BrowserConfig
|
||||
for _, cfg := range configs {
|
||||
matches, _ := filepath.Glob(cfg.UserDataDir)
|
||||
if len(matches) == 0 {
|
||||
out = append(out, cfg)
|
||||
continue
|
||||
}
|
||||
for _, dir := range matches {
|
||||
c := cfg
|
||||
c.UserDataDir = dir
|
||||
out = append(out, c)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// newBrowsers dispatches to the correct engine based on BrowserKind
|
||||
// and converts engine-specific types to the Browser interface.
|
||||
func newBrowsers(cfg types.BrowserConfig) ([]Browser, error) {
|
||||
|
||||
+264
-133
@@ -25,57 +25,33 @@ func TestListBrowsers(t *testing.T) {
|
||||
assert.True(t, sort.StringsAreSorted(list))
|
||||
}
|
||||
|
||||
func TestPickFromConfigs_NameFilter(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
mkFile(t, dir, "Default", "Preferences")
|
||||
mkFile(t, dir, "Default", "Login Data")
|
||||
mkFile(t, dir, "Default", "History")
|
||||
|
||||
configs := []types.BrowserConfig{
|
||||
{Key: "chrome", Name: "Chrome", Kind: types.Chromium, UserDataDir: dir},
|
||||
{Key: "edge", Name: "Edge", Kind: types.Chromium, UserDataDir: dir},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
pickName string
|
||||
wantNames []string
|
||||
wantProfiles []string
|
||||
}{
|
||||
{
|
||||
name: "exact match",
|
||||
pickName: "chrome",
|
||||
wantNames: []string{"Chrome"},
|
||||
wantProfiles: []string{"Default"},
|
||||
},
|
||||
{
|
||||
name: "case insensitive",
|
||||
pickName: "Chrome",
|
||||
wantNames: []string{"Chrome"},
|
||||
wantProfiles: []string{"Default"},
|
||||
},
|
||||
{
|
||||
name: "all returns both",
|
||||
pickName: "all",
|
||||
wantNames: []string{"Chrome", "Edge"},
|
||||
wantProfiles: []string{"Default", "Default"},
|
||||
},
|
||||
{
|
||||
name: "unknown returns empty",
|
||||
pickName: "safari",
|
||||
},
|
||||
}
|
||||
type pickTest struct {
|
||||
name string
|
||||
configs []types.BrowserConfig
|
||||
opts PickOptions
|
||||
wantNames []string
|
||||
wantProfiles []string
|
||||
}
|
||||
|
||||
func runPickTests(t *testing.T, tests []pickTest) {
|
||||
t.Helper()
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
browsers, err := pickFromConfigs(configs, PickOptions{Name: tt.pickName})
|
||||
browsers, err := pickFromConfigs(tt.configs, tt.opts)
|
||||
require.NoError(t, err)
|
||||
assertBrowsers(t, browsers, tt.wantNames, tt.wantProfiles)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPickFromConfigs_BrowserKind(t *testing.T) {
|
||||
func TestPickFromConfigs(t *testing.T) {
|
||||
// --- fixtures: single-profile chromium (for name filter tests) ---
|
||||
singleDir := t.TempDir()
|
||||
mkFile(t, singleDir, "Default", "Preferences")
|
||||
mkFile(t, singleDir, "Default", "Login Data")
|
||||
mkFile(t, singleDir, "Default", "History")
|
||||
|
||||
// --- fixtures: multi-profile chromium ---
|
||||
chromeDir := t.TempDir()
|
||||
mkFile(t, chromeDir, "Default", "Preferences")
|
||||
mkFile(t, chromeDir, "Default", "Login Data")
|
||||
@@ -84,128 +60,287 @@ func TestPickFromConfigs_BrowserKind(t *testing.T) {
|
||||
mkFile(t, chromeDir, "Profile 1", "Login Data")
|
||||
mkFile(t, chromeDir, "Profile 1", "History")
|
||||
|
||||
// --- fixtures: firefox ---
|
||||
firefoxDir := t.TempDir()
|
||||
mkFile(t, firefoxDir, "abc123.default-release", "logins.json")
|
||||
mkFile(t, firefoxDir, "abc123.default-release", "places.sqlite")
|
||||
|
||||
// --- fixtures: yandex ---
|
||||
yandexDir := t.TempDir()
|
||||
mkFile(t, yandexDir, "Default", "Preferences")
|
||||
mkFile(t, yandexDir, "Default", "Ya Passman Data")
|
||||
mkFile(t, yandexDir, "Default", "History")
|
||||
|
||||
// --- fixtures: glob (MSIX-like package directories) ---
|
||||
globBase := t.TempDir()
|
||||
mkFile(t, globBase, "App.Browser_abc123", "UserData", "Default", "Preferences")
|
||||
mkFile(t, globBase, "App.Browser_abc123", "UserData", "Default", "History")
|
||||
mkFile(t, globBase, "App.Browser_def456", "UserData", "Default", "Preferences")
|
||||
mkFile(t, globBase, "App.Browser_def456", "UserData", "Default", "History")
|
||||
mkFile(t, globBase, "Solo.Browser_xyz789", "UserData", "Default", "Preferences")
|
||||
mkFile(t, globBase, "Solo.Browser_xyz789", "UserData", "Default", "History")
|
||||
|
||||
nameFilterConfigs := []types.BrowserConfig{
|
||||
{Key: "chrome", Name: "Chrome", Kind: types.Chromium, UserDataDir: singleDir},
|
||||
{Key: "edge", Name: "Edge", Kind: types.Chromium, UserDataDir: singleDir},
|
||||
}
|
||||
|
||||
t.Run("NameFilter", func(t *testing.T) {
|
||||
runPickTests(t, []pickTest{
|
||||
{
|
||||
name: "exact match",
|
||||
configs: nameFilterConfigs,
|
||||
opts: PickOptions{Name: "chrome"},
|
||||
wantNames: []string{"Chrome"},
|
||||
wantProfiles: []string{"Default"},
|
||||
},
|
||||
{
|
||||
name: "case insensitive",
|
||||
configs: nameFilterConfigs,
|
||||
opts: PickOptions{Name: "Chrome"},
|
||||
wantNames: []string{"Chrome"},
|
||||
wantProfiles: []string{"Default"},
|
||||
},
|
||||
{
|
||||
name: "all returns both",
|
||||
configs: nameFilterConfigs,
|
||||
opts: PickOptions{Name: "all"},
|
||||
wantNames: []string{"Chrome", "Edge"},
|
||||
wantProfiles: []string{"Default", "Default"},
|
||||
},
|
||||
{
|
||||
name: "unknown returns empty",
|
||||
configs: nameFilterConfigs,
|
||||
opts: PickOptions{Name: "safari"},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("BrowserKind", func(t *testing.T) {
|
||||
runPickTests(t, []pickTest{
|
||||
{
|
||||
name: "chromium multi-profile",
|
||||
configs: []types.BrowserConfig{
|
||||
{Key: "chrome", Name: "Chrome", Kind: types.Chromium, UserDataDir: chromeDir},
|
||||
},
|
||||
opts: PickOptions{Name: "all"},
|
||||
wantNames: []string{"Chrome", "Chrome"},
|
||||
wantProfiles: []string{"Default", "Profile 1"},
|
||||
},
|
||||
{
|
||||
name: "firefox random dir",
|
||||
configs: []types.BrowserConfig{
|
||||
{Key: "firefox", Name: "Firefox", Kind: types.Firefox, UserDataDir: firefoxDir},
|
||||
},
|
||||
opts: PickOptions{Name: "all"},
|
||||
wantNames: []string{"Firefox"},
|
||||
wantProfiles: []string{"abc123.default-release"},
|
||||
},
|
||||
{
|
||||
name: "yandex variant",
|
||||
configs: []types.BrowserConfig{
|
||||
{Key: "yandex", Name: "Yandex", Kind: types.ChromiumYandex, UserDataDir: yandexDir},
|
||||
},
|
||||
opts: PickOptions{Name: "all"},
|
||||
wantNames: []string{"Yandex"},
|
||||
wantProfiles: []string{"Default"},
|
||||
},
|
||||
{
|
||||
name: "nonexistent dir",
|
||||
configs: []types.BrowserConfig{
|
||||
{Key: "chrome", Name: "Chrome", Kind: types.Chromium, UserDataDir: "/nonexistent"},
|
||||
},
|
||||
opts: PickOptions{Name: "all"},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("ProfilePath", func(t *testing.T) {
|
||||
runPickTests(t, []pickTest{
|
||||
{
|
||||
name: "chromium uses path directly",
|
||||
configs: []types.BrowserConfig{
|
||||
{Key: "chrome", Name: "Chrome", Kind: types.Chromium, UserDataDir: "/wrong"},
|
||||
},
|
||||
opts: PickOptions{Name: "chrome", ProfilePath: filepath.Join(chromeDir, "Default")},
|
||||
wantNames: []string{"Chrome"},
|
||||
wantProfiles: []string{"Default"},
|
||||
},
|
||||
{
|
||||
name: "firefox uses parent dir",
|
||||
configs: []types.BrowserConfig{
|
||||
{Key: "firefox", Name: "Firefox", Kind: types.Firefox, UserDataDir: "/wrong"},
|
||||
},
|
||||
opts: PickOptions{Name: "firefox", ProfilePath: filepath.Join(firefoxDir, "abc123.default-release")},
|
||||
wantNames: []string{"Firefox"},
|
||||
wantProfiles: []string{"abc123.default-release"},
|
||||
},
|
||||
{
|
||||
name: "ignored when name is all",
|
||||
configs: []types.BrowserConfig{
|
||||
{Key: "chrome", Name: "Chrome", Kind: types.Chromium, UserDataDir: chromeDir},
|
||||
},
|
||||
opts: PickOptions{Name: "all", ProfilePath: "/some/override"},
|
||||
wantNames: []string{"Chrome", "Chrome"},
|
||||
wantProfiles: []string{"Default", "Profile 1"},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Glob", func(t *testing.T) {
|
||||
runPickTests(t, []pickTest{
|
||||
{
|
||||
name: "single match",
|
||||
configs: []types.BrowserConfig{
|
||||
{Key: "solo", Name: "Solo", Kind: types.Chromium, UserDataDir: filepath.Join(globBase, "Solo.Browser_*", "UserData")},
|
||||
},
|
||||
opts: PickOptions{Name: "all"},
|
||||
wantNames: []string{"Solo"},
|
||||
wantProfiles: []string{"Default"},
|
||||
},
|
||||
{
|
||||
name: "multiple matches",
|
||||
configs: []types.BrowserConfig{
|
||||
{Key: "arc", Name: "Arc", Kind: types.Chromium, UserDataDir: filepath.Join(globBase, "App.Browser_*", "UserData")},
|
||||
},
|
||||
opts: PickOptions{Name: "all"},
|
||||
wantNames: []string{"Arc", "Arc"},
|
||||
wantProfiles: []string{"Default", "Default"},
|
||||
},
|
||||
{
|
||||
name: "no match",
|
||||
configs: []types.BrowserConfig{
|
||||
{Key: "missing", Name: "Missing", Kind: types.Chromium, UserDataDir: filepath.Join(globBase, "NoSuch_*", "UserData")},
|
||||
},
|
||||
opts: PickOptions{Name: "all"},
|
||||
},
|
||||
{
|
||||
name: "mixed with literal",
|
||||
configs: []types.BrowserConfig{
|
||||
{Key: "chrome", Name: "Chrome", Kind: types.Chromium, UserDataDir: singleDir},
|
||||
{Key: "arc", Name: "Arc", Kind: types.Chromium, UserDataDir: filepath.Join(globBase, "Solo.Browser_*", "UserData")},
|
||||
},
|
||||
opts: PickOptions{Name: "all"},
|
||||
wantNames: []string{"Arc", "Chrome"},
|
||||
wantProfiles: []string{"Default", "Default"},
|
||||
},
|
||||
{
|
||||
name: "with name filter",
|
||||
configs: []types.BrowserConfig{
|
||||
{Key: "chrome", Name: "Chrome", Kind: types.Chromium, UserDataDir: singleDir},
|
||||
{Key: "arc", Name: "Arc", Kind: types.Chromium, UserDataDir: filepath.Join(globBase, "App.Browser_*", "UserData")},
|
||||
},
|
||||
opts: PickOptions{Name: "arc"},
|
||||
wantNames: []string{"Arc", "Arc"},
|
||||
wantProfiles: []string{"Default", "Default"},
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestResolveGlobs(t *testing.T) {
|
||||
// Create directories for glob matching:
|
||||
// base/
|
||||
// ├── App.Browser_abc123/UserData/ (match 1)
|
||||
// ├── App.Browser_def456/UserData/ (match 2)
|
||||
// └── ExactBrowser/UserData/ (literal path)
|
||||
base := t.TempDir()
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(base, "App.Browser_abc123", "UserData"), 0o755))
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(base, "App.Browser_def456", "UserData"), 0o755))
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(base, "ExactBrowser", "UserData"), 0o755))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
configs []types.BrowserConfig
|
||||
wantNames []string
|
||||
wantProfiles []string
|
||||
name string
|
||||
configs []types.BrowserConfig
|
||||
wantDirs []string // expected UserDataDir values after resolution
|
||||
}{
|
||||
{
|
||||
name: "chromium multi-profile",
|
||||
name: "literal path exists",
|
||||
configs: []types.BrowserConfig{
|
||||
{Key: "chrome", Name: "Chrome", Kind: types.Chromium, UserDataDir: chromeDir},
|
||||
{Key: "exact", UserDataDir: filepath.Join(base, "ExactBrowser", "UserData")},
|
||||
},
|
||||
wantNames: []string{"Chrome", "Chrome"},
|
||||
wantProfiles: []string{"Default", "Profile 1"},
|
||||
wantDirs: []string{filepath.Join(base, "ExactBrowser", "UserData")},
|
||||
},
|
||||
{
|
||||
name: "firefox random dir",
|
||||
name: "literal path not exists preserved",
|
||||
configs: []types.BrowserConfig{
|
||||
{Key: "firefox", Name: "Firefox", Kind: types.Firefox, UserDataDir: firefoxDir},
|
||||
{Key: "missing", UserDataDir: filepath.Join(base, "NoSuchBrowser", "UserData")},
|
||||
},
|
||||
wantNames: []string{"Firefox"},
|
||||
wantProfiles: []string{"abc123.default-release"},
|
||||
wantDirs: []string{filepath.Join(base, "NoSuchBrowser", "UserData")},
|
||||
},
|
||||
{
|
||||
name: "yandex variant",
|
||||
name: "glob single match",
|
||||
configs: []types.BrowserConfig{
|
||||
{Key: "yandex", Name: "Yandex", Kind: types.ChromiumYandex, UserDataDir: yandexDir},
|
||||
{Key: "single", UserDataDir: filepath.Join(base, "ExactBrow*", "UserData")},
|
||||
},
|
||||
wantNames: []string{"Yandex"},
|
||||
wantProfiles: []string{"Default"},
|
||||
wantDirs: []string{filepath.Join(base, "ExactBrowser", "UserData")},
|
||||
},
|
||||
{
|
||||
name: "nonexistent dir",
|
||||
name: "glob multiple matches",
|
||||
configs: []types.BrowserConfig{
|
||||
{Key: "chrome", Name: "Chrome", Kind: types.Chromium, UserDataDir: "/nonexistent"},
|
||||
{Key: "multi", UserDataDir: filepath.Join(base, "App.Browser_*", "UserData")},
|
||||
},
|
||||
wantDirs: []string{
|
||||
filepath.Join(base, "App.Browser_abc123", "UserData"),
|
||||
filepath.Join(base, "App.Browser_def456", "UserData"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "glob no match preserved",
|
||||
configs: []types.BrowserConfig{
|
||||
{Key: "nomatch", UserDataDir: filepath.Join(base, "NoSuch_*", "UserData")},
|
||||
},
|
||||
wantDirs: []string{filepath.Join(base, "NoSuch_*", "UserData")},
|
||||
},
|
||||
{
|
||||
name: "mixed literal and glob",
|
||||
configs: []types.BrowserConfig{
|
||||
{Key: "chrome", UserDataDir: filepath.Join(base, "ExactBrowser", "UserData")},
|
||||
{Key: "arc", UserDataDir: filepath.Join(base, "App.Browser_*", "UserData")},
|
||||
},
|
||||
wantDirs: []string{
|
||||
filepath.Join(base, "ExactBrowser", "UserData"),
|
||||
filepath.Join(base, "App.Browser_abc123", "UserData"),
|
||||
filepath.Join(base, "App.Browser_def456", "UserData"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty input",
|
||||
configs: nil,
|
||||
wantDirs: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
browsers, err := pickFromConfigs(tt.configs, PickOptions{Name: "all"})
|
||||
require.NoError(t, err)
|
||||
assertBrowsers(t, browsers, tt.wantNames, tt.wantProfiles)
|
||||
got := resolveGlobs(tt.configs)
|
||||
|
||||
var gotDirs []string
|
||||
for _, cfg := range got {
|
||||
gotDirs = append(gotDirs, cfg.UserDataDir)
|
||||
}
|
||||
sort.Strings(gotDirs)
|
||||
sort.Strings(tt.wantDirs)
|
||||
assert.Equal(t, tt.wantDirs, gotDirs)
|
||||
|
||||
// Verify non-UserDataDir fields are preserved.
|
||||
for _, cfg := range got {
|
||||
found := false
|
||||
for _, orig := range tt.configs {
|
||||
if cfg.Key != orig.Key {
|
||||
continue
|
||||
}
|
||||
found = true
|
||||
assert.Equal(t, orig.Name, cfg.Name)
|
||||
assert.Equal(t, orig.Kind, cfg.Kind)
|
||||
break
|
||||
}
|
||||
assert.True(t, found, "unexpected key %q in output", cfg.Key)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPickFromConfigs_ProfilePath(t *testing.T) {
|
||||
chromeDir := t.TempDir()
|
||||
mkFile(t, chromeDir, "Default", "Preferences")
|
||||
mkFile(t, chromeDir, "Default", "Login Data")
|
||||
mkFile(t, chromeDir, "Default", "History")
|
||||
mkFile(t, chromeDir, "Profile 1", "Preferences")
|
||||
mkFile(t, chromeDir, "Profile 1", "Login Data")
|
||||
mkFile(t, chromeDir, "Profile 1", "History")
|
||||
|
||||
firefoxDir := t.TempDir()
|
||||
mkFile(t, firefoxDir, "abc123.default-release", "logins.json")
|
||||
mkFile(t, firefoxDir, "abc123.default-release", "places.sqlite")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
configs []types.BrowserConfig
|
||||
pickName string
|
||||
profilePath string
|
||||
wantNames []string
|
||||
wantProfiles []string
|
||||
}{
|
||||
{
|
||||
name: "chromium uses path directly",
|
||||
configs: []types.BrowserConfig{
|
||||
{Key: "chrome", Name: "Chrome", Kind: types.Chromium, UserDataDir: "/wrong"},
|
||||
},
|
||||
pickName: "chrome",
|
||||
profilePath: filepath.Join(chromeDir, "Default"),
|
||||
wantNames: []string{"Chrome"},
|
||||
wantProfiles: []string{"Default"},
|
||||
},
|
||||
{
|
||||
name: "firefox uses parent dir",
|
||||
configs: []types.BrowserConfig{
|
||||
{Key: "firefox", Name: "Firefox", Kind: types.Firefox, UserDataDir: "/wrong"},
|
||||
},
|
||||
pickName: "firefox",
|
||||
profilePath: filepath.Join(firefoxDir, "abc123.default-release"),
|
||||
wantNames: []string{"Firefox"},
|
||||
wantProfiles: []string{"abc123.default-release"},
|
||||
},
|
||||
{
|
||||
name: "ignored when name is all",
|
||||
configs: []types.BrowserConfig{
|
||||
{Key: "chrome", Name: "Chrome", Kind: types.Chromium, UserDataDir: chromeDir},
|
||||
},
|
||||
pickName: "all",
|
||||
profilePath: "/some/override",
|
||||
wantNames: []string{"Chrome", "Chrome"},
|
||||
wantProfiles: []string{"Default", "Profile 1"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
browsers, err := pickFromConfigs(tt.configs, PickOptions{Name: tt.pickName, ProfilePath: tt.profilePath})
|
||||
require.NoError(t, err)
|
||||
assertBrowsers(t, browsers, tt.wantNames, tt.wantProfiles)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// newBrowsers dispatcher
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestNewBrowsersDispatch(t *testing.T) {
|
||||
chromiumDir := t.TempDir()
|
||||
mkFile(t, chromiumDir, "Default", "Preferences")
|
||||
@@ -267,10 +402,6 @@ func TestNewBrowsersDispatch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// assertBrowsers verifies browser names and profiles match expectations (order-independent).
|
||||
func assertBrowsers(t *testing.T, browsers []Browser, wantNames, wantProfiles []string) {
|
||||
t.Helper()
|
||||
|
||||
@@ -98,6 +98,18 @@ func platformBrowsers() []types.BrowserConfig {
|
||||
Kind: types.Chromium,
|
||||
UserDataDir: homeDir + "/AppData/Local/Sogou/SogouExplorer/User Data",
|
||||
},
|
||||
{
|
||||
Key: "arc",
|
||||
Name: arcName,
|
||||
Kind: types.Chromium,
|
||||
UserDataDir: homeDir + "/AppData/Local/Packages/TheBrowserCompany.Arc_*/LocalCache/Local/Arc/User Data",
|
||||
},
|
||||
{
|
||||
Key: "duckduckgo",
|
||||
Name: duckduckgoName,
|
||||
Kind: types.Chromium,
|
||||
UserDataDir: homeDir + "/AppData/Local/Packages/DuckDuckGo.DesktopBrowser_*/LocalState/EBWebView",
|
||||
},
|
||||
{
|
||||
Key: "firefox",
|
||||
Name: firefoxName,
|
||||
|
||||
@@ -25,4 +25,5 @@ const (
|
||||
dcName = "DC"
|
||||
sogouName = "Sogou"
|
||||
arcName = "Arc"
|
||||
duckduckgoName = "DuckDuckGo"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user