Files
Ant-Browser/backend/browser_start_targets.go

205 lines
5.8 KiB
Go

package backend
import (
"encoding/json"
"fmt"
"strings"
"time"
"ant-chrome/backend/internal/config"
"ant-chrome/backend/internal/logger"
)
func browserLightStartEnabled(cfg *config.Config) bool {
if cfg == nil || cfg.Browser.LightStartEnabled == nil {
return true
}
return *cfg.Browser.LightStartEnabled
}
func resolveConfiguredStartTargets(startURLs []string, defaultStartURLs []string, skipDefaultStartURLs bool) []string {
normalizedStartURLs := normalizeNonEmptyStrings(startURLs)
if len(normalizedStartURLs) > 0 {
return normalizedStartURLs
}
if skipDefaultStartURLs {
return nil
}
return normalizeNonEmptyStrings(defaultStartURLs)
}
func buildBrowserLaunchTargets(startURLs []string, defaultStartURLs []string, skipDefaultStartURLs bool, restoreLastSession bool, lightStartEnabled bool) ([]string, []string) {
configuredTargets := resolveConfiguredStartTargets(startURLs, defaultStartURLs, skipDefaultStartURLs)
if lightStartEnabled && len(configuredTargets) > 0 {
return []string{"about:blank"}, configuredTargets
}
if len(configuredTargets) > 0 {
return configuredTargets, nil
}
if !restoreLastSession {
return []string{"about:blank"}, nil
}
return nil, nil
}
func deferredStartTargetsWarning(targets []string, err error) string {
normalized := normalizeNonEmptyStrings(targets)
if len(normalized) <= 1 {
return fmt.Sprintf("浏览器已启动,但启动页未能自动打开:%v。可稍后手动打开。", err)
}
return fmt.Sprintf("浏览器已启动,但 %d 个启动页未能自动打开:%v。可稍后手动打开。", len(normalized), err)
}
func (a *App) storeDeferredStartTargets(profileId string, targets []string) {
if a == nil {
return
}
normalized := normalizeNonEmptyStrings(targets)
a.deferredStartTargetsMu.Lock()
defer a.deferredStartTargetsMu.Unlock()
if len(normalized) == 0 {
delete(a.deferredStartTargets, profileId)
return
}
a.deferredStartTargets[profileId] = append([]string{}, normalized...)
}
func (a *App) consumeDeferredStartTargets(profileId string) []string {
if a == nil {
return nil
}
a.deferredStartTargetsMu.Lock()
defer a.deferredStartTargetsMu.Unlock()
targets := append([]string{}, a.deferredStartTargets[profileId]...)
delete(a.deferredStartTargets, profileId)
return targets
}
func (a *App) clearDeferredStartTargets(profileId string) {
if a == nil {
return
}
a.deferredStartTargetsMu.Lock()
delete(a.deferredStartTargets, profileId)
a.deferredStartTargetsMu.Unlock()
}
func cdpBrowserCallResult(debugPort int, method string, params map[string]any) (map[string]any, error) {
body, err := cdpGetEndpointBody(debugPort, "/json/version")
if err != nil {
return nil, fmt.Errorf("CDP /json/version 请求失败: %w", err)
}
var version cdpBrowserVersion
if err := json.Unmarshal(body, &version); err != nil {
return nil, fmt.Errorf("CDP browser target 解析失败: %w", err)
}
wsURL := strings.TrimSpace(version.WebSocketDebuggerUrl)
if wsURL == "" {
return nil, fmt.Errorf("未找到浏览器级 WebSocket 调试地址")
}
conn, err := cdpDialWebSocket(wsURL)
if err != nil {
return nil, fmt.Errorf("浏览器级 WebSocket 连接失败: %w", err)
}
defer conn.Close()
conn.SetReadDeadline(time.Now().Add(cdpWebSocketReadTimeout))
msg := cdpMessage{Id: 1, Method: method, Params: params}
if err := conn.WriteJSON(msg); err != nil {
return nil, fmt.Errorf("浏览器级 CDP 命令发送失败: %w", err)
}
var cdpResp cdpResponse
if err := conn.ReadJSON(&cdpResp); err != nil {
return nil, fmt.Errorf("浏览器级 CDP 响应读取失败: %w", err)
}
if cdpResp.Error != nil {
return nil, fmt.Errorf("浏览器级 CDP 错误: %s", cdpResp.Error.Message)
}
return cdpResp.Result, nil
}
func createBrowserStartTarget(debugPort int, url string) error {
_, err := cdpBrowserCallResult(debugPort, "Target.createTarget", map[string]any{
"url": url,
})
return err
}
func openBrowserStartTargets(debugPort int, targets []string) error {
normalized := normalizeNonEmptyStrings(targets)
if len(normalized) == 0 {
return nil
}
_, navigateErr := cdpCall(debugPort, "Page.navigate", map[string]any{
"url": normalized[0],
})
if navigateErr == nil {
for _, url := range normalized[1:] {
if err := createBrowserStartTarget(debugPort, url); err != nil {
return err
}
}
return nil
}
for _, url := range normalized {
if err := createBrowserStartTarget(debugPort, url); err != nil {
return fmt.Errorf("首个页面改为新标签回退失败:navigate=%v, createTarget=%w", navigateErr, err)
}
}
return nil
}
func (a *App) setProfileRuntimeWarning(profileId string, debugPort int, warning string) (*BrowserProfile, bool) {
if a == nil || a.browserMgr == nil {
return nil, false
}
a.browserMgr.Mutex.Lock()
defer a.browserMgr.Mutex.Unlock()
profile, exists := a.browserMgr.Profiles[profileId]
if !exists || profile == nil || !profile.Running || profile.DebugPort != debugPort {
return nil, false
}
if profile.RuntimeWarning == warning && profile.LastError == "" {
return copyBrowserProfileSnapshot(profile), false
}
profile.RuntimeWarning = warning
profile.LastError = ""
return copyBrowserProfileSnapshot(profile), true
}
func (a *App) finalizeDeferredStartTargets(profileId string, debugPort int) (*BrowserProfile, bool) {
targets := a.consumeDeferredStartTargets(profileId)
if len(targets) == 0 {
return nil, false
}
if err := openBrowserStartTargets(debugPort, targets); err != nil {
warning := deferredStartTargetsWarning(targets, err)
snapshot, changed := a.setProfileRuntimeWarning(profileId, debugPort, warning)
logger.New("Browser").Warn("浏览器已就绪,但启动页延后打开失败",
logger.F("profile_id", profileId),
logger.F("debug_port", debugPort),
logger.F("target_count", len(targets)),
logger.F("error", err.Error()),
logger.F("warning", warning),
)
return snapshot, changed
}
return nil, false
}