From 2b176054607d8239c3f02181c2d52ef9967e71f1 Mon Sep 17 00:00:00 2001 From: voidvon Date: Fri, 3 Apr 2026 18:33:41 +0800 Subject: [PATCH] fix(browser): support macOS core bundle paths --- backend/internal/browser/core_binary.go | 63 ++++++++++++++++++++ backend/internal/browser/core_test.go | 79 +++++++++++++++++++++++++ 2 files changed, 142 insertions(+) diff --git a/backend/internal/browser/core_binary.go b/backend/internal/browser/core_binary.go index 102a68c5..e3354d92 100644 --- a/backend/internal/browser/core_binary.go +++ b/backend/internal/browser/core_binary.go @@ -31,6 +31,12 @@ func FindCoreExecutable(baseDir string) (string, string, bool) { if baseDir == "" { return "", "", false } + if directPath, directCandidate, ok := findDirectCoreExecutable(baseDir); ok { + return directPath, directCandidate, true + } + if bundlePath, bundleCandidate, ok := findAppBundleExecutable(baseDir); ok { + return bundlePath, bundleCandidate, true + } for _, candidate := range CoreExecutableCandidates() { p := filepath.Join(baseDir, filepath.FromSlash(candidate)) if _, err := os.Stat(p); err == nil { @@ -39,3 +45,60 @@ func FindCoreExecutable(baseDir string) (string, string, bool) { } return "", "", false } + +func findDirectCoreExecutable(path string) (string, string, bool) { + info, err := os.Stat(path) + if err != nil || info.IsDir() { + return "", "", false + } + + normalized := filepath.ToSlash(filepath.Clean(path)) + for _, candidate := range CoreExecutableCandidates() { + candidatePath := filepath.ToSlash(candidate) + if strings.HasSuffix(normalized, candidatePath) || filepath.Base(normalized) == filepath.Base(candidatePath) { + return path, candidate, true + } + } + + return "", "", false +} + +func findAppBundleExecutable(path string) (string, string, bool) { + if goruntime.GOOS != "darwin" { + return "", "", false + } + + info, err := os.Stat(path) + if err != nil || !info.IsDir() { + return "", "", false + } + + normalized := filepath.ToSlash(filepath.Clean(path)) + if !strings.HasSuffix(strings.ToLower(normalized), ".app") { + return "", "", false + } + + for _, candidate := range CoreExecutableCandidates() { + candidatePath := filepath.ToSlash(candidate) + appMarker := ".app/" + index := strings.Index(strings.ToLower(candidatePath), appMarker) + if index < 0 { + continue + } + if !strings.EqualFold(filepath.Base(normalized), filepath.Base(candidatePath[:index+len(".app")])) { + continue + } + + relativeExecutable := candidatePath[index+len(appMarker):] + if relativeExecutable == "" { + continue + } + + p := filepath.Join(path, filepath.FromSlash(relativeExecutable)) + if _, err := os.Stat(p); err == nil { + return p, candidate, true + } + } + + return "", "", false +} diff --git a/backend/internal/browser/core_test.go b/backend/internal/browser/core_test.go index 5de500b7..4bbcb290 100644 --- a/backend/internal/browser/core_test.go +++ b/backend/internal/browser/core_test.go @@ -4,6 +4,7 @@ import ( "ant-chrome/backend/internal/config" "os" "path/filepath" + "runtime" "testing" ) @@ -103,6 +104,84 @@ func TestResolveChromeBinaryNormalizesWindowsStyleRelativeCorePath(t *testing.T) } } +func TestResolveChromeBinaryAcceptsDirectExecutablePath(t *testing.T) { + t.Parallel() + + root := t.TempDir() + coreDir := filepath.Join(root, "chrome-direct") + if err := os.MkdirAll(coreDir, 0o755); err != nil { + t.Fatalf("创建内核目录失败: %v", err) + } + + exePath := filepath.Join(coreDir, filepath.FromSlash(CoreExecutableCandidates()[0])) + if err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil { + t.Fatalf("创建可执行文件目录失败: %v", err) + } + if err := os.WriteFile(exePath, []byte("stub"), 0o755); err != nil { + t.Fatalf("写入可执行文件失败: %v", err) + } + + cfg := config.DefaultConfig() + mgr := NewManager(cfg, root) + mgr.CoreDAO = &coreDAOStub{ + list: []Core{ + { + CoreId: "core-direct", + CoreName: "Chrome Direct", + CorePath: exePath, + IsDefault: true, + }, + }, + } + + got, err := mgr.ResolveChromeBinary(&Profile{}) + if err != nil { + t.Fatalf("ResolveChromeBinary 返回错误: %v", err) + } + if got != exePath { + t.Fatalf("ResolveChromeBinary 路径错误: got=%q want=%q", got, exePath) + } +} + +func TestResolveChromeBinaryAcceptsDarwinAppBundlePath(t *testing.T) { + t.Parallel() + + if runtime.GOOS != "darwin" { + t.Skip("仅验证 darwin .app 路径解析") + } + + root := t.TempDir() + appDir := filepath.Join(root, "Chromium.app") + exePath := filepath.Join(appDir, "Contents", "MacOS", "Chromium") + if err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil { + t.Fatalf("创建可执行文件目录失败: %v", err) + } + if err := os.WriteFile(exePath, []byte("stub"), 0o755); err != nil { + t.Fatalf("写入可执行文件失败: %v", err) + } + + cfg := config.DefaultConfig() + mgr := NewManager(cfg, root) + mgr.CoreDAO = &coreDAOStub{ + list: []Core{ + { + CoreId: "core-app", + CoreName: "Chromium App", + CorePath: appDir, + IsDefault: true, + }, + }, + } + + got, err := mgr.ResolveChromeBinary(&Profile{}) + if err != nil { + t.Fatalf("ResolveChromeBinary 返回错误: %v", err) + } + if got != exePath { + t.Fatalf("ResolveChromeBinary 路径错误: got=%q want=%q", got, exePath) + } +} + func TestCountInstancesByCoreTreatsLegacyDefaultReferenceAsDefault(t *testing.T) { t.Parallel()