browser

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 17, 2026 License: MIT Imports: 48 Imported by: 0

Documentation

Overview

Package browser — chromeHandleImpl: cross-platform ChromeHandle owning *exec.Cmd.

Used by:

  • All Workspace implementations (darwin/linux/windows/other) — wraps the Chrome fork they spawn and exposes lifecycle to caller via ChromeHandle.
  • NoopWorkspace fallback for tests.

Invariants:

  • cmd.Process != nil after successful startChromeProcess return.
  • doneCh is closed when cmd.Wait returns (any cause).
  • Kill is idempotent — multiple callers wait on the same doneCh.
  • Wait blocks until process exits and returns cmd.Wait's error.

[Ref: TH-0419 — Workspace as visible-Chrome SSOT]

compat.go provides backward-compatible stubs for types that were in the old go-rod-based browser package and are still referenced by internal/desktop (Wails UI). These types are deprecated and will be removed after the desktop package is migrated to BS-09 BrowserCore API.

DO NOT add new usages of these types. Use BrowserCore instead.

Package browser — CookieImporter: 从本机浏览器导入 Cookie。 [Ref: CAP-BS09-C4 §3.2b, SC-22, TC-C4-07~11, r2 Delta-REQ TH-0418-c9x]

铁律 IR-01: 本包零依赖 Deepwork 上下文。 安全约束: 只读打开源 Cookie 文件,不修改用户浏览器数据。

Package browser — Cookie 加解密共享工具函数。 [Ref: CAP-BS09-C4 §3.2b, TC-C4-07]

Package browser 实现 BS-09 Browser Runtime。 以 A11y+Element Refs 为核心,为 LLM 提供低 token 高精度 Web 感知通道。

铁律 IR-01: 本包零依赖 Deepwork 上下文(conversation/topic/webui/memory/agent/llm 均不得引入)。 铁律 IR-02: 使用 chromedp Go 直连 CDP,不引入 Node.js/TS 运行时。

Package browser — DisplayManager: 虚拟 display 生命周期管理 (跨平台)。 从 BrowserPool 提取,供 Pool 和 dw-browser CLI (NewBrowserCore) 共享。

Package browser implements BS-09 Browser Runtime. 旧错误定义已迁移到 core.go,此文件保留以兼容旧引用。 新错误变量定义见 core.go。

Package browser — Browser Fingerprint Presets. 消除 headless Chrome 指纹,模拟真实浏览器环境。 设计依据: TH-0405-k8r + CAP-BS09-C4 Profile Management

Package browser — identity 三元组类型定义 [v2 Phase_v2_1, TH-0419-w3p MERGED]

来源:

  • CAP-BS09-C4 §2.bis (IdentityRegistry / IsolationPolicy interface 契约)
  • T5-BS-09 §0.2 (identity 三元组 (Profile, Preset, IsolationPolicy) → IdentityKey)
  • BP-BS-09 §V2.B.1 (Phase_v2_1 必读数据契约)

范围 (Phase_v2_1):

  • 类型定义: IdentityKey / Preset / Viewport / IdentityDescriptor / IsolationPolicy / NoopPolicy / Identity / BrowserSessionHandle 占位
  • IdentityKey() 工厂函数: 三元组 → 确定性 hash

不在范围 (deferred to Phase_v2_2/v2_3/v2_4):

  • Pool 集成 (Phase_v2_2): identity-keyed Chrome 实例池
  • 6 入口迁移 (Phase_v2_3): webui/tool/dw-browser/council/live_sync/desktop 走 Pool API
  • ProxyPolicy 首例 (Phase_v2_4 P1): 当前只有 NoopPolicy stub (D9 SL-2)

推迟标记 (BP §V2.B.3, T5 §0 D9 SL-2):

  • 推迟项: ProxyPolicy 实装
  • 理由分类: 认知未闭合 (J3 默认值待 Round 7+ 冻结)
  • 触发条件: Phase_v2_4 启动 + J3 默认值确认
  • 验证标准: TC-09-U-48 之外新增 ProxyPolicy.Apply 走 CDP Network.setProxy

Package browser — IdentityRegistry: identity 三元组解析与归一化 [v2 Phase_v2_1]

来源:

  • CAP-BS09-C4 §2.bis (IdentityRegistry interface 契约)
  • T5-BS-09 §0.2 + BP §V2.C.1 (Phase_v2_1 必须实现)

范围 (Phase_v2_1):

  • 接口 + 内存实现 (sync.Map 后备 store).
  • Resolve / Inspect / List 三方法.
  • 不做 Pool 集成 (Phase_v2_2 才把 IdentityKey 传给 BrowserPool.AcquireTab).

推迟标记:

  • 推迟项: 持久化 (跨进程 IdentityRegistry 共享).
  • 理由分类: 技术依赖 — 需要 BS-09 Pool 重构后才能确定持久化粒度.
  • 触发条件: Phase_v2_2 启动 + Pool snapshot 持久化协议确认.
  • 验证标准: TC-09-I-50 (重启后 Pool 状态恢复 + IdentityKey 同前一致).

Package browser — observability: STG constants, package logger, metrics, ValidateDeps. [T5-BS-09, pkg/obs, IR-01: zero deepwork context dependency]

Package browser — BrowserPool: identity-keyed Chrome 实例池 [v2 Phase_v2_2]

设计依据:

  • r1/r2: TH-0405-k8r (8 轮碰撞, 14 DDC, 10 要点冻结) — 单 Chrome 多 Tab
  • v2: TH-0419-w3p MERGED — identity-keyed 多 Chrome 多 Tab

核心架构 (v2 Phase_v2_2):

  • 每个 IdentityKey 对应独立 chromePoolEntry (含独立 allocCtx + profileDir + chromePath)
  • 每个 entry 内多 Tab (chromedp NewContext per consumer)
  • lazy init: 首次该 IdentityKey 的 AcquireTab 时启动对应 Chrome
  • graceful shutdown: 通过 GracefulShutdown 接口暴露 (Phase_v2_2 内部仍 SIGTERM-only, 双阶段 bounded wait + SIGKILL fallback 由 Phase_v2_4 实装 — 接口已 final)
  • 孤儿清理: 启动时删除旧 SingletonLock
  • maxTabs 保护: 防 OOM (全局上限, 跨 entry 计数)
  • 跨平台 dataDir: {dataDir}/browser-data/profiles/{profile}/{preset-vN}/

Caller 复用语义 (v2):

  • 旧 r1/r2 LegacyAcquireTab(purpose) 已删除. 上层调用方 (webui-panel/tool-default 等) 自己缓存 TabHandle 实现"同 caller 同 Tab"语义 (见 webui/browser_routes.go 的 panelHandle, internal/tool/browser_tools.go 的 toolTabHandle).
  • SwitchProfile/UpdateViewport — 对 default identity entry 操作 (UpdateViewport 通过 handle.WorkspaceID == "webui-panel" 反查 panel Tab).

CAP 锚点: CAP-BS09-C4 §2.bis (BrowserPool interface) + §3.5/§3.6 + T5 §0.1, §0.3

Package browser — startup recovery: 启动期 Chrome profile 健康检查 [v2 Phase_v2_4]

来源:

  • CAP-BS09-C4 §3.5 (启动期 Recovery 4 步协议)
  • BP-BS-09 §V2.D (Phase_v2_4 实施范围)

协议 (4 步, 在 startChromeLocked 启动 Chrome 之前执行):

  1. Singleton lock 残留检测: 解析 SingletonLock symlink 拿 PID; PID 已死 → 删除残留; PID 仍活 → 强杀 (orphan, 上次崩溃未清理)
  2. profile health check: stat user-data-dir/Cookies + 校验 SQLite header (前 16 字节)
  3. 损坏 → 整体重命名为 user-data-dir.broken/{timestamp}/ (隔离不污染下次) ProfileManager 会在下次自动重建 (空 profile_dir → Chrome 首次启动行为)
  4. 返回 nil (健康) 或 error (隔离/清理失败)

不在范围:

  • .dw-pid 文件机制 — chromedp 不暴露 Chrome 进程 PID; 当前用 SingletonLock 反查代替
  • ProfileManager.Repair — 隔离后重建逻辑由 Pool 自然 lazy launch 接管

audit 事件 (log only, 与 GracefulShutdown 风格对齐):

  • "startup_recovery_lock_cleaned" — Singleton 残留已删除
  • "startup_recovery_orphan_killed" — orphan Chrome 强杀
  • "startup_recovery_quarantined" — profile 损坏隔离

Package browser — startup recovery 平台 dispatch (Unix: macOS / Linux / *BSD).

用 syscall.Kill(pid, signal) 实现:

  • signal 0 → 仅校验 PID 是否合法 (kill -0 语义)
  • SIGTERM / SIGKILL → 双阶段终止

Package browser — TabIndex: Tab 四元身份注册表 [v2 Phase_v2_2]

来源:

  • CAP-BS09-C4 §2.bis (TabHandle / TabRole / PoolSnapshot 类型契约)
  • T5-BS-09 §0.3 (Tab 四元身份: TargetID, IdentityKey, WorkspaceID, Role)
  • BP-BS-09 §V2.B.2 + §V2.E Phase_v2_2 (TabIndex 新增)

范围 (Phase_v2_2):

  • TabHandle 类型 + 4 Role 枚举 (CAP §2.bis lines 127-150)
  • TabIndex 接口 + 内存实现: Register / Lookup / Unregister / ByIdentity / ByWorkspace
  • PoolSnapshot / IdentityPoolStatus 类型 (CAP §2.bis lines 143-150)

不在范围:

  • Pool 主流程集成由 pool.go 完成 (本文件仅提供索引数据结构)
  • first-claim-wins 协调逻辑在 input_gateway.go (Phase_v2_3+, J4 默认值)

设计说明:

  • TabIndex 是 BrowserPool 内部依赖, 不直接暴露给业务调用方.
  • 业务调用方拿到 *TabHandle 后通过 TargetID 反查 (Pool.Inspect / Pool.ReleaseTab).
  • "唯一性"约束: 同 TargetID 不可重复 Register (TC-09-U-43 守护).

关于 BrowserCore 句柄:

  • Phase_v2_2: TabHandle 暂不携带 BrowserCore 引用 (CAP §2.bis 字段集 final).
  • Pool 内部维护并行 map[TargetID]BrowserCore (通过 BrowserPool.GetCore(targetID) 暴露) (Phase_v2_3 4 入口迁移完成后, BrowserCore 句柄会从业务消失 — 走 SessionCore 模型).

Package browser — Workspace: Cross-Platform Isolated Workspace abstraction.

Phase 0 (terminal architecture v7) — refined 2026-04-19 (TH-0419): Workspace is the SSOT for **visible** Chrome launches. Headless Chrome callers do NOT go through Workspace (no NSWindow → no Space binding to manage). Mode-conditional split lives at every caller.

Strategy (DDC-I-21, BRR-12, TH-0418-c9x PoC verified 2026-04-19):

  • macOS: own-process SLSManagedDisplaySetCurrentSpace switch (SIP-safe). The cross-process SLSMoveWindowsToManagedSpace is silent no-op under SIP; instead we switch view → fork Chrome (it inherits current Space) → wait window committed → switch back.
  • Linux: Xvfb already isolates; LaunchChromeInSpace just exec's Chrome (Chrome inherits DISPLAY=:99 from parent process env).
  • Windows: stub — direct exec (TODO: IVirtualDesktopManager COM bridge).

Lifecycle: ChromeHandle owns the spawned process. Caller attaches via chromedp.NewRemoteAllocator(ctx, h.WSURL()). Caller MUST call h.Kill() (or rely on process-exit) for explicit teardown — chromedp.Cancel only closes the CDP session, not the process (RemoteAllocator does not own the fork).

[Ref: DDC-I-11, DDC-I-21, BRR-12, BRR-MODE-1]

Package browser — Linux Workspace (no-op: Xvfb provides isolation).

On Linux, display isolation is already achieved by running Chrome inside an Xvfb virtual display (managed by DisplayManager.ensureDisplayLinux). The Workspace abstraction is a no-op on Linux — Xvfb IS the isolated workspace.

[Ref: DDC-I-11 — Linux Xvfb / macOS Spaces are isomorphic Isolated Workspace primitives] [Ref: Iron Rule — Linux display_manager.go::ensureDisplayLinux() MUST NOT be modified]

Index

Constants

View Source
const (
	PresetWindowsChrome  = "windows-chrome"
	PresetLinuxChrome    = "linux-chrome"
	PresetMacOSChrome    = "macos-chrome"
	PresetAndroidChrome  = "android-chrome"
	PresetMacOSSafariUA  = "macos-safari-ua"
	PresetIPhoneSafariUA = "iphone-safari-ua"
)
View Source
const (
	// STGSessionCreate is the stage for acquiring/creating a browser tab or session.
	STGSessionCreate = "browser/session/create"

	// STGSessionNavigate is the stage for Navigate calls (URL load + settle wait).
	STGSessionNavigate = "browser/session/navigate"

	// STGSessionAction is the stage for Act / ActWithSessionMode calls.
	STGSessionAction = "browser/session/action"

	// STGSnapshot is the stage for Snap / SnapWithSessionMode A11y snapshot capture.
	STGSnapshot = "browser/snapshot"

	// STGLiveView is the stage for StartLiveView / Screencast frame pipeline.
	STGLiveView = "browser/liveview"
)
View Source
const (
	// BrowserProfileWorkspace 供所有 Workspace AI session 共用(持久,共享站点登录态)。
	BrowserProfileWorkspace = "workspace"
	// BrowserProfileWebChat 供 Council / WebChat AI 使用(持久,保持 Gemini/ChatGPT 等登录)。
	BrowserProfileWebChat = "webchat"
)

系统级 Browser Profile 常量 — 三域隔离模型。 Human 浏览(Browser Portal)使用单独的 browser-sidebar-main,由各路由自行维护。

View Source
const (
	DefaultViewportWidth  = 1920
	DefaultViewportHeight = 1080
	DefaultMaxTabs        = 10

	// ChromeInitialPageURL is Chrome/CDP's startup sentinel page. It is a
	// runtime state, not product state: callers must not use it to infer user
	// intent, session progress, or tab ownership.
	ChromeInitialPageURL = "about:blank"
)
View Source
const (
	BrowserPoolDefaultIdleTimeout = 5 * time.Minute
	BrowserPoolShutdownGrace      = 5 * time.Second
	BrowserPoolMaxShutdownGrace   = 30 * time.Second
	BrowserPoolMinReapInterval    = 15 * time.Second
	BrowserPoolReleaseTimeout     = 5 * time.Second
	BrowserPoolChromeWarmup       = 15 * time.Second
	BrowserPoolCDPActionTimeout   = 5 * time.Second
	ProfileOwnerMuxHostKillGrace  = 2 * time.Second
	ProfileOwnerChromeKillGrace   = 1 * time.Second
	ProcessExitPollInterval       = 50 * time.Millisecond
	ChromeReadyPollInterval       = 150 * time.Millisecond
	ChromeVersionRequestTimeout   = 2 * time.Second
	ChromeTargetsRequestTimeout   = 3 * time.Second
	ChromeCDPStartupAttempts      = 20
	ChromeCDPStartupPollInterval  = 500 * time.Millisecond

	BrowserMuxHostDefaultIdleTTL             = 10 * time.Minute
	BrowserMuxHostReadyTimeout               = 45 * time.Second
	BrowserMuxHostLaunchReadyTimeout         = 30 * time.Second
	BrowserMuxHostControlRequestTimeout      = 5 * time.Second
	BrowserMuxHostShutdownTimeout            = 3 * time.Second
	BrowserMuxHostReadyPollInterval          = 250 * time.Millisecond
	BrowserMuxHostProcessPollInterval        = 100 * time.Millisecond
	BrowserMuxHostHealthPollInterval         = 2 * time.Second
	BrowserMuxHostIdleCheckInterval          = 5 * time.Second
	BrowserMuxHostWindowEnforceTimeout       = 10 * time.Second
	BrowserMuxHostWindowContainmentTimeout   = 2500 * time.Millisecond
	BrowserMuxHostForegroundGuardTimeout     = 1500 * time.Millisecond
	BrowserMuxHostForegroundContainmentCheck = 750 * time.Millisecond
	BrowserMuxHostSnapshotContainmentCheck   = 200 * time.Millisecond
	VirtualDisplayRegistrationDelay          = 1 * time.Second
	VirtualDisplayContainmentPollInterval    = 75 * time.Millisecond
)
View Source
const (
	TargetClaimWindow              = 2 * time.Second
	TargetWindowOpenHintTTL        = 2 * time.Second
	TargetMaxAttributionHints      = 8
	TargetWarmTimeout              = 10 * time.Second
	TargetPageListenerTimeout      = 5 * time.Second
	TargetActivateTimeout          = 2500 * time.Millisecond
	TargetMaterializeTimeout       = 1500 * time.Millisecond
	TargetDiscoveryTimeout         = 8 * time.Second
	TargetCreateTimeout            = 5 * time.Second
	TargetCreateWaitTimeout        = 12 * time.Second
	TargetCloseLocalWaitTimeout    = 1200 * time.Millisecond
	TargetCloseLocalPollInterval   = 25 * time.Millisecond
	TargetBootstrapCleanupWindow   = 5 * time.Second
	TargetPostActionDiscoveryDelay = 500 * time.Millisecond
	TargetWindowOpenRefreshDelay   = 250 * time.Millisecond
	DevToolsRequestTimeout         = 5 * time.Second
)
View Source
const (
	SessionOwnerAgent   = "agent"
	SessionOwnerHuman   = "human"
	SessionOwnerService = "service"

	SessionIsolationEphemeral = "ephemeral"
	SessionIsolationDedicated = "dedicated"
	SessionIsolationPool      = "profile-pool"

	AuthorityAgentic = "agentic"
	AuthorityHuman   = "human"
	AuthorityShared  = "shared"
)
View Source
const DefaultGraceTimeout = 5 * time.Second

DefaultGraceTimeout 是断连 grace 超时(5秒),超时前重连可恢复。

View Source
const DefaultIdleTimeout = 5 * time.Minute

DefaultIdleTimeout 是默认 idle 超时(5分钟),每次 accepted input 后重置。

View Source
const DefaultTakeoverTimeout = 5 * time.Minute

DefaultTakeoverTimeout 是默认接管超时时间(5分钟)[TC-09-L4-10]。

View Source
const MinimalWebdriverStealthScript = `` /* 694-byte string literal not displayed */

MinimalWebdriverStealthScript is used only for real headed Chrome (headed/visible modes). In those modes the browser should expose its native WebGL, screen, fonts, canvas, audio, languages, and hardware surfaces. The only automation residue we remove is navigator.webdriver, because some bot tests fail on property presence even when the value is not truthy.

Variables

View Source
var (
	// ErrBrowserNotFound — 无 Chrome/Edge 安装。
	ErrBrowserNotFound = errors.New("browser: no Chrome/Edge found")

	// ErrBrowserCrashed — Chrome 进程崩溃。
	ErrBrowserCrashed = errors.New("browser: Chrome process crashed")

	// ErrCDPDisconnected — CDP 连接断开。
	ErrCDPDisconnected = errors.New("browser: CDP connection lost")

	// ErrActFailed — 操作执行失败。
	ErrActFailed = errors.New("browser: action execution failed")

	// ErrRefNotFound — Element Ref 不存在。
	ErrRefNotFound = errors.New("browser: element ref not found")

	// ErrTakeoverActive — 接管模式激活,AI 操作被拒绝。
	ErrTakeoverActive = errors.New("browser: takeover mode active")

	// ErrPasswordField — 密码字段需要安全输入,不经 LLM/WS 明文通道处理 [IR-03]。
	ErrPasswordField = errors.New("browser: password field requires secure input")

	// ErrSnapshotEmpty — A11y 快照返回空。
	ErrSnapshotEmpty = errors.New("browser: A11y snapshot returned empty")

	// ErrAmbiguousLocator — 定位器匹配多个元素。
	ErrAmbiguousLocator = errors.New("browser: ambiguous locator matches multiple elements")

	// ErrStaleRef — ref 已失效(页面自上次 snap 后已变化)。
	ErrStaleRef = errors.New("browser: ref is stale (page has changed since last snap)")

	// ErrSessionNotFound — 会话不存在。
	ErrSessionNotFound = errors.New("browser: session not found")

	// ErrInvalidRefInOneShot — @rN ref 在无 session 模式下不可用。
	ErrInvalidRefInOneShot = errors.New("browser: @rN refs require --session mode")

	// ErrSelectorNotFound — CSS 选择器无匹配元素。
	ErrSelectorNotFound = errors.New("browser: CSS selector matched no elements")

	// ErrEvalFailed — JavaScript 求值失败。
	ErrEvalFailed = errors.New("browser: JavaScript evaluation failed")

	// ErrCookieDecryptFailed — Cookie 解密失败(macOS Keychain/Linux SecretService 拒绝)。
	ErrCookieDecryptFailed = errors.New("browser: cookie decryption failed")

	// ErrCookieDBLocked — Cookie 数据库锁定且无法复制。
	ErrCookieDBLocked = errors.New("browser: cookie database locked")
)
View Source
var BuiltinPresets = map[string]*FingerprintPreset{
	PresetWindowsChrome: {
		ID:            PresetWindowsChrome,
		Name:          "Windows 11 · Chrome 133",
		UserAgent:     "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36",
		Platform:      "Win32",
		Vendor:        "Google Inc.",
		Languages:     "['en-US','en']",
		WebGLVendor:   "Google Inc. (NVIDIA)",
		WebGLRenderer: "ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 Direct3D11 vs_5_0 ps_5_0, D3D11)",
		ViewportW:     1920, ViewportH: 1080,
		DeviceScaleFactor: 1.0,
	},
	PresetLinuxChrome: {
		ID:            PresetLinuxChrome,
		Name:          "Ubuntu 24.04 · Chrome 133",
		UserAgent:     "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36",
		Platform:      "Linux x86_64",
		Vendor:        "Google Inc.",
		Languages:     "['en-US','en']",
		WebGLVendor:   "Google Inc. (NVIDIA)",
		WebGLRenderer: "ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 OpenGL ES 3.2, OpenGL 4.6.0)",
		ViewportW:     1920, ViewportH: 1080,
		DeviceScaleFactor: 1.0,
	},
	PresetMacOSChrome: {
		ID:            PresetMacOSChrome,
		Name:          "macOS Sequoia · Chrome 133",
		UserAgent:     "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36",
		Platform:      "MacIntel",
		Vendor:        "Google Inc.",
		Languages:     "['en-US','en']",
		WebGLVendor:   "Google Inc. (Apple)",
		WebGLRenderer: "ANGLE (Apple, ANGLE Metal Renderer: Apple M4 Pro, Unspecified Version)",
		ViewportW:     1512, ViewportH: 982,
		DeviceScaleFactor: 2.0,
	},
	PresetAndroidChrome: {
		ID:            PresetAndroidChrome,
		Name:          "Android · Chrome 133",
		UserAgent:     "Mozilla/5.0 (Linux; Android 15; Pixel 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Mobile Safari/537.36",
		Platform:      "Linux armv8l",
		Vendor:        "Google Inc.",
		Languages:     "['en-US','en']",
		WebGLVendor:   "Google Inc. (Qualcomm)",
		WebGLRenderer: "ANGLE (Qualcomm, Adreno 750, OpenGL ES 3.2)",
		ViewportW:     412, ViewportH: 923,
		DeviceScaleFactor: 2.625,
		Mobile:            true,
		Touch:             true,
		MaxTouchPoints:    5,
	},
	PresetMacOSSafariUA: {
		ID:            PresetMacOSSafariUA,
		Name:          "macOS Sequoia · Safari UA 模拟",
		UserAgent:     "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3 Safari/605.1.15",
		Platform:      "MacIntel",
		Vendor:        "Apple Computer, Inc.",
		Languages:     "['en-US','en']",
		WebGLVendor:   "Apple",
		WebGLRenderer: "Apple M4 Pro",
		ViewportW:     1512, ViewportH: 982,
		DeviceScaleFactor: 2.0,
	},
	PresetIPhoneSafariUA: {
		ID:            PresetIPhoneSafariUA,
		Name:          "iPhone · Safari UA 模拟",
		UserAgent:     "Mozilla/5.0 (iPhone; CPU iPhone OS 18_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3 Mobile/15E148 Safari/604.1",
		Platform:      "iPhone",
		Vendor:        "Apple Computer, Inc.",
		Languages:     "['en-US','en']",
		WebGLVendor:   "Apple Inc.",
		WebGLRenderer: "Apple GPU",

		ViewportW: 430, ViewportH: 932,
		DeviceScaleFactor: 3.0,
		Mobile:            true,
		Touch:             true,
		MaxTouchPoints:    5,
	},
}

BuiltinPresets 内置浏览器指纹预设 (2026-04 主流版本 + 主流硬件)。

Languages 选择: 仅 en-US/en — 中文 IP + Chromium + zh-CN Accept-Language 是 Cloudflare 反爬高危组合 (中国区 ASN 已被标记),反而拉低 Turnstile 通过率。 真实用户可以随时在 Chrome 设置切换语言,fingerprint 层不暴露 zh 即可。

View Source
var ErrBrowserUnavailable = errors.New("browser: unavailable (deprecated stub)")

ErrBrowserUnavailable is returned by deprecated stub methods.

View Source
var ErrIdentityNotFound = errors.New("identity not found in registry")

ErrIdentityNotFound — Inspect 未命中.

View Source
var ErrIdentityUnresolved = errors.New("browser pool: identity not resolved (call IdentityRegistry.Resolve first)")

ErrIdentityUnresolved — AcquireTabRequest.IdentityKey 未在 Registry 注册过.

View Source
var ErrInvalidRole = errors.New("browser pool: invalid role (allowed: human/agent/council/background)")

ErrInvalidRole — AcquireTabRequest.Role 不在 4 枚举矩阵 (TC-09-U-41).

View Source
var ErrInvalidRoleIdentity = errors.New("browser pool: council role requires council-* profile prefix")

ErrInvalidRoleIdentity — Role=council 但 IdentityKey 对应 Profile 非 council-* 前缀 (TC-09-U-42, P1).

View Source
var ErrMaxTabsReached = errors.New("browser pool: max tabs reached")

ErrMaxTabsReached — Tab 总数达上限且无可回收 entry.

View Source
var ErrPoolClosed = errors.New("browser pool: closed")

ErrPoolClosed — Pool 已 Shutdown.

View Source
var ErrTabAlreadyRegistered = errors.New("tab already registered for TargetID")

ErrTabAlreadyRegistered — Register 重复 TargetID.

View Source
var ErrTabNotFound = errors.New("tab not found in index")

ErrTabNotFound — Unregister / Lookup 未命中.

PresetOrder 前端展示顺序。

View Source
var TargetBootstrapCleanupDelays = []time.Duration{
	350 * time.Millisecond,
	1500 * time.Millisecond,
	3500 * time.Millisecond,
}

Functions

func ApplyDetachedProcAttr

func ApplyDetachedProcAttr(cmd *exec.Cmd)

ApplyDetachedProcAttr sets Setpgid so the spawned Chrome lives in its own process group. This decouples Chrome's lifetime from the parent process's signal-handling pgrp (important for dw-browser CLI which exits while Chrome must remain).

Exported so dw-browser CLI (headless path) can reuse the same cross-platform helper without re-implementing platform-specific syscall imports.

func BrowserMuxHostIDFromBrowserSessionID

func BrowserMuxHostIDFromBrowserSessionID(browserSessionID string) string

BrowserMuxHostIDFromBrowserSessionID returns the stable host key for a BrowserSession. New BrowserMuxHost manifests use this stable id so Deepwork and CLI clients can reconnect to the same muxhost after a client restart.

func BrowserMuxHostLogPath

func BrowserMuxHostLogPath(hostID string) string

func BrowserMuxHostManifestPath

func BrowserMuxHostManifestPath(hostID string) string

func BrowserMuxHostRootDir

func BrowserMuxHostRootDir() string

func BrowserMuxHostRuntimeDir

func BrowserMuxHostRuntimeDir(hostID string) string

func BrowserPoolProfileDir

func BrowserPoolProfileDir(dataDir, profileID, presetID string) string

BrowserPoolProfileDir returns the canonical on-disk profile directory used by BrowserPool for a logical profile and preset on the local runtime.

func BrowserRuntimeIDFromBrowserSessionID

func BrowserRuntimeIDFromBrowserSessionID(browserSessionID string) string

func BrowserRuntimeManifestPath

func BrowserRuntimeManifestPath(runtimeID string) string

func BrowserSessionIDFromPoolIdentity

func BrowserSessionIDFromPoolIdentity(identityKey IdentityKey) string

BrowserSessionIDFromPoolIdentity returns the stable BrowserSession id used by BrowserPool's legacy interactive coordination path for an identity.

func BrowserSessionIDFromSessionID

func BrowserSessionIDFromSessionID(sessionID string) string

BrowserSessionIDFromSessionID keeps old local session files readable while making the public identifier explicit.

func BuildDetachedChromeArgs

func BuildDetachedChromeArgs(opts DetachedChromeLaunchOptions) []string

BuildDetachedChromeArgs 生成 detached Chrome 的启动参数。 设计目标:

  • headless/headed/visible 三模式共用同一组生命周期必需参数
  • visible 保留本机 Chrome 的真实指纹面,不叠加自动化/CI flags
  • headed 使用真实 Chrome + 虚拟显示,并补最小反后台节流参数保证 LiveView 切 tab 稳定
  • headless 只在必要处补偿 UA/webdriver,不关闭 GPU/WebGL

func DefaultPresetID

func DefaultPresetID() string

DefaultPresetID 返回当前平台的默认桌面浏览器指纹。 目的: 避免 macOS/Windows 本地调试时仍落回 windows-chrome 的硬编码默认。

func DefaultProfileID

func DefaultProfileID() string

DefaultProfileID 返回逻辑默认 profile ID。

func DeleteSession

func DeleteSession(sessionID string) error

DeleteSession セッションファイルを削除する。

func ExecAllocatorOptionsFromArgs

func ExecAllocatorOptionsFromArgs(chromePath string, args []string) []chromedp.ExecAllocatorOption

ExecAllocatorOptionsFromArgs 将 detached Chrome 参数转换为 chromedp ExecAllocatorOption。 设计目标:

  • BrowserPool 与 detached dw-browser 复用同一套跨平台 launch args
  • 避免两条路径各维护一份 flags,导致 Cloudflare / Turnstile 行为分叉

func ExtractDevToolsTargetID

func ExtractDevToolsTargetID(target map[string]interface{}) string

func ExtractDevToolsTargetURL

func ExtractDevToolsTargetURL(target map[string]interface{}) string

func FetchChromeTargets

func FetchChromeTargets(port int) ([]map[string]interface{}, error)

FetchChromeTargets Chrome の /json エンドポイントからターゲット一覧を取得。

func FindFreePort

func FindFreePort() (int, error)

FindFreePort 利用可能な TCP ポートを探す。

func GenerateStealthScript

func GenerateStealthScript(p *FingerprintPreset) string

GenerateStealthScript 根据 Preset 生成定制化 Stealth 脚本。

func GetCDPVersion

func GetCDPVersion(port int) (*cdpVersionResponse, error)

GetCDPVersion 查询 CDP /json/version 返回版本信息。

func GlobalBrowserMuxHostID

func GlobalBrowserMuxHostID() string

func InjectCookiesDirectCDP

func InjectCookiesDirectCDP(ctx context.Context, cookies []*network.CookieParam) error

InjectCookiesDirectCDP 通过直接 CDP 协议注入 Cookie(需要 chromedp context)。 此函数供 cookie_importer_cdp.go 中的集成测试调用。

func IsBlankTargetURL

func IsBlankTargetURL(raw string) bool

func IsChromeInitialPageURL

func IsChromeInitialPageURL(raw string) bool

func IsDevToolsPageTarget

func IsDevToolsPageTarget(target map[string]interface{}) bool

func IsUserPageTargetURL

func IsUserPageTargetURL(raw string) bool

func IsValidRole

func IsValidRole(r TabRole) bool

IsValidRole 判断 role 是否在 4 枚举矩阵内.

func LegacyBrowserMuxHostIDFromBrowserSessionID

func LegacyBrowserMuxHostIDFromBrowserSessionID(browserSessionID string) string

func NewBrowserMuxHostID

func NewBrowserMuxHostID(browserSessionID string, pid int) string

func NewBrowserRunID

func NewBrowserRunID(browserSessionID string, chromePID int) string

func NewChromeLauncher

func NewChromeLauncher() *chromeLauncherImpl

NewChromeLauncher 返回默认 ChromeLauncher 实现。

func NewChromeSupervisor

func NewChromeSupervisor() *chromeSupervisorImpl

NewChromeSupervisor 返回默认 ChromeSupervisor 实现。

func NewCookieImporter

func NewCookieImporter(bc BrowserCore) *cookieImporter

NewCookieImporter 创建 CookieImporter 实例。 bc 用于通过 CDP 注入 Cookie(Network.setCookies)。

func NormalizePresetID

func NormalizePresetID(presetID string) string

NormalizePresetID 只处理空值与空白。 未知 ID 会原样返回,必须由 ValidatePresetID 或调用方报错,避免旧契约静默回退。

func NormalizeProfileID

func NormalizeProfileID(id string) string

NormalizeProfileID 规范化逻辑 profile ID。 允许用户直接输入显示名,最终转换为稳定的 ASCII slug。

func NormalizeSessionInfo

func NormalizeSessionInfo(info *SessionInfo)

NormalizeSessionInfo backfills the r8 BrowserSession contract on session files loaded from older dw-browser builds.

func NormalizeTargetCreateURL

func NormalizeTargetCreateURL(raw string) string

func PrepareProfileForControlledLaunch

func PrepareProfileForControlledLaunch(profileDir string) error

PrepareProfileForControlledLaunch removes Chrome session-restore artifacts without touching cookies, local storage, IndexedDB, passwords, or permissions.

This matters for invisible headed mode: a restored Chrome window can ignore the requested --window-position and briefly appear on the Human's main Space.

func RecoverBrowserRuntimeState

func RecoverBrowserRuntimeState(dataDir string) error

RecoverBrowserRuntimeState performs a startup-wide owner-marker sweep for all BrowserPool profile directories under dataDir. It is intentionally separate from RunStartupRecovery: the app calls this at process startup, before any Browser Portal is opened, so a SIGKILLed BrowserMuxHost cannot leave headed Chrome visible on the user's primary Space until the next lazy acquire.

func RemoveProfileOwnerMarker

func RemoveProfileOwnerMarker(profileDir string, identityKey IdentityKey)

func ResolveSessionTarget

func ResolveSessionTarget(session *SessionInfo) (string, error)

ResolveSessionTarget 解析有效的 target ID。 快速路径: 存储的 target_id 仍有效 → 直接返回。 恢复路径: target 不存在 → 查询 Chrome /json → 选最佳 page target → 更新 session 文件。

func RunStartupRecovery

func RunStartupRecovery(profileDir string, identityKey IdentityKey) error

RunStartupRecovery 在 Chrome 启动前对 profileDir 做 4 步健康检查 (CAP §3.5).

调用方:

  • BrowserPool.startChromeLocked (进程内 service 模式, IdentityKey 来自 Registry)
  • dw-browser open (CLI 短命模式, IdentityKey 用 "dw-cli-{sessionID}" 仅作 audit 标签)

返回 nil → profile 可直接启动 Chrome. 返回 err → 上层决定是否致命 (隔离失败应 abort, 清理失败可继续).

identityKey 仅用于 audit log (定位故障 Chrome 实例).

func SaveSession

func SaveSession(info *SessionInfo) error

SaveSession セッション情報をファイルに書き込む。

func ServeBrowserMuxHost

func ServeBrowserMuxHost(ctx context.Context, req BrowserMuxHostRequest) error

func URLOrigin

func URLOrigin(rawURL string) string

func URLsEquivalent

func URLsEquivalent(a, b string) bool

func ValidateCoreImpl

func ValidateCoreImpl(snapshotEng *snapshotEngine, liveEng *liveViewEngine)

ValidateCoreImpl panics if a BrowserCoreImpl's essential internal engines are nil. Pass the concrete impl fields; call after construction before serving operations.

func ValidatePoolConfig

func ValidatePoolConfig(cfg PoolConfig)

ValidatePoolConfig panics if the BrowserPool configuration violates required invariants. Call in NewBrowserPool after field defaults are applied.

func ValidatePresetID

func ValidatePresetID(presetID string) (string, error)

ValidatePresetID 返回已知 preset;未知值必须显式失败。

func WaitForChromeReady

func WaitForChromeReady(port int, timeout time.Duration) (string, error)

WaitForChromeReady Chrome の CDP エンドポイントが準備完了するまでポーリング。 成功時に WebSocket URL を返す。

func WriteProfileOwnerMarker

func WriteProfileOwnerMarker(profileDir string, identityKey IdentityKey, chromePID, cdpPort int) error

func WriteProfileOwnerMarkerWithMetadata

func WriteProfileOwnerMarkerWithMetadata(profileDir string, identityKey IdentityKey, chromePID, cdpPort int, meta ProfileOwnerMetadata) error

Types

type AcquireTabRequest

type AcquireTabRequest struct {
	IdentityKey IdentityKey // 来自 IdentityRegistry.Resolve, 必填; 不允许字符串拼装
	WorkspaceID string      // 任务隔离维度 (T5 §0.3, 不影响 Chrome 资源边界)
	Role        TabRole     // 4 枚举之一; 不在矩阵 → ErrInvalidRole
	InitialURL  string      // 可选, 创建 Tab 时直接 navigate (Phase_v2_2 暂不实装 navigate)
	Ephemeral   bool        // true → 复用 ephemeral profile (dw-browser --ephemeral)
	Mode        BrowserMode // 可选; 空值使用 PoolConfig.Mode. BS-15 Fast runtime 用 headless.

	// BrowserSession contract passthrough. These fields let service callers
	// preserve their owner contract when BrowserPool coordinates a headed
	// BrowserMuxHost. Empty values keep the legacy interactive pool defaults.
	BrowserSessionID string
	SessionKind      BrowserSessionKind
	Goal             string
	Owner            string
	Isolation        string
	ServiceName      string
	AccountID        string
}

AcquireTabRequest — BrowserPool.AcquireTab 入参 (CAP §2.bis lines 119-125).

type ActionResult

type ActionResult struct {
	Success  bool
	Error    string
	Duration time.Duration
}

ActionResult is a deprecated result type from the old browser package. Kept for backward compatibility.

type AgentState

type AgentState string

AgentState represents the visual state of the agent in the HUD. Deprecated.

const (
	// AgentStateThinking indicates the agent is thinking.
	AgentStateThinking AgentState = "thinking"
	// AgentStateActing indicates the agent is acting.
	AgentStateActing AgentState = "acting"
)

type Box

type Box struct {
	X      float64
	Y      float64
	Width  float64
	Height float64
}

Box represents a bounding box for an element. Deprecated: kept for compat with internal/desktop.

type BrowserCore

type BrowserCore interface {
	// Navigate 导航到 URL,等待页面加载完成,返回 A11y 快照。
	Navigate(ctx context.Context, url string) (*Snapshot, error)

	// Snap 获取当前页面 A11y 快照(不导航)。
	Snap(ctx context.Context) (*Snapshot, error)

	// Act 执行操作,返回操作后快照。
	// action 语法: "click e3" | "type e5 'hello'" | "scroll down" | "hover e7" | "select e4 'opt2'"
	// observe=false 时不返回 snap(连续操作优化)[D.2-C2]。
	Act(ctx context.Context, action string, observe bool) (*Snapshot, error)

	// Text 提取当前页面纯文本(~500-800 tok,focus 为可选 ref 或 CSS selector)。
	Text(ctx context.Context, focus *string) (string, error)

	// Screenshot 截图,annotate=true 时叠加 Element Ref 标注。
	Screenshot(ctx context.Context, annotate bool) ([]byte, error)

	// StartLiveView 启动 Screencast 帧推送,返回 FrameBroadcastHub。
	// 多次调用安全:已活跃时返回同一 hub,不重启 Screencast。
	// WS handler 通过 hub.Subscribe(connID) 获取独立帧 channel [CAP-BS09-C3 r2]。
	StartLiveView(ctx context.Context) (*FrameBroadcastHub, error)

	// StopLiveView 停止 Screencast。
	StopLiveView(ctx context.Context) error

	// EnableTakeover 切换到 Takeover 模式(Human 接管)。
	EnableTakeover(ctx context.Context) error

	// DisableTakeover 释放 Takeover,恢复 OBSERVE 模式。
	DisableTakeover(ctx context.Context) error

	// DispatchInput 在接管模式下转发输入事件。
	DispatchInput(ctx context.Context, event *InputEvent) error

	// Close 关闭 Browser,释放资源。
	Close(ctx context.Context) error

	// EvalJS 在当前活跃标签上执行 JavaScript 表达式,结果写入 result 指针。
	// 活跃标签由 TargetTracker 决定: 有新标签(如 target=_blank)时在新标签执行,
	// 否则在主标签(browserCtx)执行。[BUG-FIX + TH-0414-b3m]
	EvalJS(ctx context.Context, expr string, result interface{}) error

	// GetTargetTracker 返回 TargetTracker(多 Target 自动跟随)[r3]。
	// 返回 nil 表示不支持(如 session 模式)。
	GetTargetTracker() *TargetTracker
}

BrowserCore — internal/browser/ 对外服务接口(Core 层,零 Deepwork 依赖)。

func NewBrowserCore

func NewBrowserCore(ctx context.Context, profileID string, optFns ...BrowserOption) (BrowserCore, error)

NewBrowserCore 创建并初始化 BrowserCore 实例。 optFns 为可选参数: WithViewport / WithUserAgent / WithTouchEmulation / WithMode。

该 one-shot 入口必须与 BrowserPool 使用同一套三模式启动语义。所有 mode 都 先用 BuildDetachedChromeArgs fork 本机 Chrome,再通过 RemoteAllocator attach, 避免 CLI/测试入口绕过 CGVirtualDisplay/Workspace。

type BrowserEngine

type BrowserEngine string

BrowserEngine 标识浏览器引擎类型。

const (
	EngineChrome BrowserEngine = "chrome"
	EngineSafari BrowserEngine = "safari"
)

func NormalizeEngine

func NormalizeEngine(e BrowserEngine) BrowserEngine

NormalizeEngine 标准化引擎名称。空值和未知值默认为 Chrome。

type BrowserMode

type BrowserMode string

BrowserMode 定义浏览器运行模式 [TH-0414-b3m]。

const (
	// ModeHeadless 极速模式:Chrome --headless=new,零 display 依赖。
	ModeHeadless BrowserMode = "headless"

	// ModeHeaded 标准模式:真实 headed Chrome + 平台虚拟显示器,对 Human 不可见。
	ModeHeaded BrowserMode = "headed"

	// ModeVisible 可见模式:真实 headed Chrome 显示到用户可见工作区。
	ModeVisible BrowserMode = "visible"

	// Deprecated compatibility aliases.
	BrowserModeHeadless BrowserMode = ModeHeadless
	BrowserModeHuman    BrowserMode = "human"
)

func NormalizeBrowserMode

func NormalizeBrowserMode(mode BrowserMode, fallback BrowserMode) BrowserMode

type BrowserMuxHostRequest

type BrowserMuxHostRequest struct {
	BrowserSessionID string
	SessionKind      BrowserSessionKind
	MuxHostID        string
	RuntimeID        string
	MuxHostBinary    string
	IdentityKey      IdentityKey
	OwnerPID         int
	Goal             string
	Owner            string
	Isolation        string
	ServiceName      string
	AccountID        string

	ChromePath string
	ProfileID  string
	ProfileDir string
	DebugPort  int
	Mode       BrowserMode
	PresetID   string
	Width      int
	Height     int
	UserAgent  string
	Touch      bool
	IdleTTL    time.Duration
}

BrowserMuxHostRequest describes the runtime that should own Chrome and its display server. It is intentionally free of Deepwork product vocabulary so dw-browser can remain a general AOT/HTR runtime.

type BrowserMuxHostState

type BrowserMuxHostState struct {
	MuxHostID             string                  `json:"browser_mux_host_id"`
	MuxHostPID            int                     `json:"browser_mux_host_pid"`
	ControlURL            string                  `json:"control_url"`
	Token                 string                  `json:"token,omitempty"`
	RuntimeID             string                  `json:"runtime_id,omitempty"`
	RuntimeCount          int                     `json:"runtime_count,omitempty"`
	Runtimes              []BrowserRuntimeSummary `json:"runtimes,omitempty"`
	BrowserSessionID      string                  `json:"browser_session_id"`
	SessionKind           BrowserSessionKind      `json:"session_kind,omitempty"`
	Goal                  string                  `json:"goal,omitempty"`
	Owner                 string                  `json:"owner,omitempty"`
	OwnerPID              int                     `json:"owner_pid,omitempty"`
	Isolation             string                  `json:"isolation,omitempty"`
	ServiceName           string                  `json:"service,omitempty"`
	AccountID             string                  `json:"account_id,omitempty"`
	ProfileID             string                  `json:"profile_id"`
	ProfileDir            string                  `json:"profile_dir"`
	BrowserPID            int                     `json:"browser_pid,omitempty"`
	ChromePID             int                     `json:"chrome_pid"`
	WSURL                 string                  `json:"ws_url"`
	DebugPort             int                     `json:"debug_port"`
	Mode                  BrowserMode             `json:"mode"`
	PresetID              string                  `json:"preset_id,omitempty"`
	ViewportW             int                     `json:"viewport_w"`
	ViewportH             int                     `json:"viewport_h"`
	UserAgent             string                  `json:"user_agent,omitempty"`
	Touch                 bool                    `json:"touch,omitempty"`
	BrowserRunID          string                  `json:"browser_run_id"`
	DisplayBackend        string                  `json:"display_backend"`
	DisplayID             uint32                  `json:"display_id,omitempty"`
	DisplayVerified       bool                    `json:"display_verified"`
	ChromeWindowContained bool                    `json:"chrome_window_contained"`
	StartedAt             string                  `json:"started_at"`
	LastTouchedAt         string                  `json:"last_touched_at"`
	IdleTTLMillis         int64                   `json:"idle_ttl_ms"`
	MuxHostAlive          bool                    `json:"browser_mux_host_alive"`
	ChromeAlive           bool                    `json:"chrome_alive"`
	ReusedExisting        bool                    `json:"-"`
}

BrowserMuxHostState is the on-disk manifest and loopback API response for a BrowserMuxHost. It is the attach contract used by Deepwork, dw-browser CLI, and service adapters.

func BrowserMuxHostHealth

func BrowserMuxHostHealth(ctx context.Context, state *BrowserMuxHostState) (*BrowserMuxHostState, error)

func LoadBrowserMuxHostState

func LoadBrowserMuxHostState(hostID string) (*BrowserMuxHostState, error)

func LoadBrowserRuntimeState

func LoadBrowserRuntimeState(runtimeID string) (*BrowserMuxHostState, error)

func ReleaseBrowserMuxHost

func ReleaseBrowserMuxHost(ctx context.Context, state *BrowserMuxHostState) (*BrowserMuxHostState, error)

func ShutdownBrowserMuxHost

func ShutdownBrowserMuxHost(ctx context.Context, state *BrowserMuxHostState) (*BrowserMuxHostState, error)

func TouchBrowserMuxHost

func TouchBrowserMuxHost(ctx context.Context, state *BrowserMuxHostState, ownerPID int) (*BrowserMuxHostState, error)

type BrowserOption

type BrowserOption func(*browserOptions)

BrowserOption 是可选参数函数类型。

func WithFingerprintPreset

func WithFingerprintPreset(presetID string) BrowserOption

WithFingerprintPreset 设置运行时指纹 preset。 dw-browser 的 device/UA 模拟必须同时驱动启动参数与 JS/runtime 指纹。

func WithMode

func WithMode(mode BrowserMode) BrowserOption

WithMode 设置浏览器模式。默认 headless(CLI/测试场景)。

func WithTouchEmulation

func WithTouchEmulation(enabled bool) BrowserOption

WithTouchEmulation 设置触控模拟(Mobile 设备预设使用)。

func WithUserAgent

func WithUserAgent(ua string) BrowserOption

WithUserAgent 设置自定义 User-Agent。

func WithViewport

func WithViewport(w, h int) BrowserOption

WithViewport 设置视口大小。

type BrowserPool

type BrowserPool struct {
	// contains filtered or unexported fields
}

BrowserPool — identity-keyed Chrome 实例池 (实现 CAP §2.bis BrowserPool interface).

func NewBrowserPool

func NewBrowserPool(config PoolConfig) *BrowserPool

NewBrowserPool 创建 BrowserPool (不启动 Chrome — lazy init).

func (*BrowserPool) AcquireTab

func (p *BrowserPool) AcquireTab(ctx context.Context, req AcquireTabRequest) (*TabHandle, error)

AcquireTab 获取或创建一个 Tab (CAP §2.bis lines 99-102).

不变式:

  • 同 IdentityKey 多次 AcquireTab 复用同一 Chrome 实例 (TC-09-U-40, TC-09-I-47).
  • 不同 IdentityKey 的 AcquireTab 启动独立 Chrome 实例 (TC-09-U-40 反向断言).
  • Role 不在 4 枚举 → ErrInvalidRole + 不启动 Chrome (TC-09-U-41).
  • Role=council 但 IdentityKey 对应 Profile 非 council-* 前缀 → ErrInvalidRoleIdentity (TC-09-U-42, P1).
  • lazy launch: IdentityKey 未见过 → 调 ChromeLauncher + ProfileManager + IsolationPolicy.Apply (TC-09-I-48).

校验顺序:

  1. p.closed → ErrPoolClosed
  2. !IsValidRole → ErrInvalidRole (无副作用)
  3. registry.Inspect → ErrIdentityUnresolved (上层必先 Resolve)
  4. council role + 非 council-* profile → ErrInvalidRoleIdentity
  5. tab 上限检查 (跨 entry 总和)
  6. lazy launch + Tab 创建
  7. TabIndex.Register

返回值: TabHandle 副本 (调用方持有, 通过 TargetID 反向 ReleaseTab).

func (*BrowserPool) DataDir

func (p *BrowserPool) DataDir() string

DataDir 返回 BrowserPool 绑定的 deepwork dataDir (legacy).

func (*BrowserPool) DefaultIdentity

func (p *BrowserPool) DefaultIdentity() IdentityKey

DefaultIdentity 返回 Pool 在 NewBrowserPool 时由 PoolConfig.Profile/Preset + NoopPolicy 预解析的默认 IdentityKey. 上层调用方 (webui-panel / tool-default) 没有特定 identity 需求时 用此默认值, 保证与 PoolConfig 配置语义一致.

func (*BrowserPool) GetCore

func (p *BrowserPool) GetCore(targetID string) (BrowserCore, bool)

GetCore 返回指定 TargetID 对应的 BrowserCore (AcquireTab 之后取操作句柄).

设计: AcquireTab 只返回 *TabHandle (CAP §2.bis 冻结签名); 上层执行浏览器动作需要 BrowserCore. GetCore 把 TargetID → BrowserCore 反查暴露给调用方, 不引入新所有权.

返回 (nil, false) 当 TargetID 未在 TabIndex / entry.tabs 中找到 (Tab 已被 Release/Evict).

func (*BrowserPool) GetPresetID

func (p *BrowserPool) GetPresetID() string

GetPresetID 返回当前默认 Preset ID (legacy).

func (*BrowserPool) GetProfileID

func (p *BrowserPool) GetProfileID() string

GetProfileID 返回当前默认 Profile ID (legacy).

func (*BrowserPool) GracefulShutdown

func (p *BrowserPool) GracefulShutdown(ctx context.Context) error

GracefulShutdown 双阶段退出 (CAP §2.bis lines 109-113 + §3.5) [Phase_v2_4].

协议 (per entry):

  1. cancel 所有 tab context (释放 chromedp 资源)
  2. cancel browserCtx 异步触发 Chrome graceful close (Browser.close → flush cookie/storage)
  3. bounded wait (default 5s, env BS09_SHUTDOWN_GRACE_SEC, max 30s): - chromedp.Cancel 在 goroutine 中执行 graceful Browser.close + 等待清理 - 超时 → cancel allocCtx (chromedp 内部 SIGKILL fallback) + audit event
  4. 清理 entry 状态

audit event (CAP §3.5): wait 超时 → log "BROWSER-POOL/v2 AUDIT: chrome_killed_after_grace" 包含 identity + grace_sec, 便于运维定位 stuck Chrome.

注: 单 mutex 持有期间为所有 entry 串行执行 bounded wait. N 个 identity → 最多 N*grace 串行. 实际生产 N 通常 ≤2 (default + council), 串行 10s 上限可接受.

func (*BrowserPool) Inspect

func (p *BrowserPool) Inspect() PoolSnapshot

Inspect 返回 Pool 当前状态快照 (CAP §2.bis lines 116-117).

Identities 按 IdentityKey 字典序; 每 identity 内 Tabs 按 TargetID 字典序. ChromePID 当前由 Pool 反查 entry.allocCtx 推断 (Phase_v2_2 简化为 entry.started ? -1 : 0, 真实 PID 提取由 Phase_v2_4 双阶段退出实装时引入 process tracking).

func (*BrowserPool) IsStarted

func (p *BrowserPool) IsStarted() bool

IsStarted 返回 default identity 的 Chrome 是否已启动.

func (*BrowserPool) Registry

func (p *BrowserPool) Registry() IdentityRegistry

Registry 返回 Pool 内置的 IdentityRegistry (供上层 Resolve 后传入 AcquireTab).

设计: Pool 内部和外部共享同一 registry, 避免上层用独立 registry 导致 IdentityKey 来源 不一致 (registry.Inspect 反查会失败).

func (*BrowserPool) ReleaseTab

func (p *BrowserPool) ReleaseTab(ctx context.Context, targetID string) error

ReleaseTab 释放指定 TargetID 的 Tab (CAP §2.bis lines 105-107).

不立即关闭 Chrome — 由 Pool 自身决定 idle eviction (Phase_v2_4 实装 idle policy).

func (*BrowserPool) ResetDefaultIdentity

func (p *BrowserPool) ResetDefaultIdentity(reason string) bool

ResetDefaultIdentity forcefully tears down the current default-identity Chrome entry so callers can reacquire a clean BrowserCore after interruption or target/core corruption. It is a no-op when the default entry does not exist.

func (*BrowserPool) ResetIdentity

func (p *BrowserPool) ResetIdentity(identityKey IdentityKey, reason string) bool

ResetIdentity tears down a specific identity so callers can reacquire it with a different execution mode. This is the primitive BS-15 uses for Fast → Trusted BrowserRun migration; profile ownership remains with the identity.

func (*BrowserPool) ResolveProfileIdentity

func (p *BrowserPool) ResolveProfileIdentity(profileID string) (IdentityKey, error)

ResolveProfileIdentity resolves a profile against the pool's canonical default preset/policy. Callers that only need "this saved profile" must use this instead of Registry().Resolve(profile, Preset{FingerprintTag: ...}) so one physical profile dir maps to one BrowserPool entry.

func (*BrowserPool) Shutdown

func (p *BrowserPool) Shutdown(ctx context.Context)

Shutdown 是 GracefulShutdown 的 r1/r2 兼容签名 (无返回值, 不返回 error).

现有 cmd/deepwork/main.go 走此入口; Phase_v2_3+ 调用方应改用 GracefulShutdown(ctx) error.

func (*BrowserPool) SwitchProfile

func (p *BrowserPool) SwitchProfile(ctx context.Context, profileID string) (restarted bool, err error)

SwitchProfile 切换 default identity 的 Profile.

func (*BrowserPool) UpdateViewport

func (p *BrowserPool) UpdateViewport(ctx context.Context, width, height int, dpr float64, mobile, touch bool, maxTouchPoints int64) error

UpdateViewport 更新 default identity 上 webui-panel Tab 的 viewport.

func (*BrowserPool) UpdateViewportForIdentity

func (p *BrowserPool) UpdateViewportForIdentity(ctx context.Context, identityKey IdentityKey, workspaceID string, width, height int, dpr float64, mobile, touch bool, maxTouchPoints int64) error

UpdateViewportForIdentity updates the LiveView viewport for the tab owned by a scoped BrowserPool identity/workspace pair.

v2: 通过 entry.tabs 扫描 handle.WorkspaceID 定位 caller-owned Tab (legacy purpose index 已删除).

type BrowserRuntimeSummary

type BrowserRuntimeSummary struct {
	RuntimeID        string             `json:"runtime_id"`
	BrowserSessionID string             `json:"browser_session_id"`
	SessionKind      BrowserSessionKind `json:"session_kind,omitempty"`
	ServiceName      string             `json:"service,omitempty"`
	AccountID        string             `json:"account_id,omitempty"`
	ProfileID        string             `json:"profile_id,omitempty"`
	BrowserPID       int                `json:"browser_pid,omitempty"`
	ChromePID        int                `json:"chrome_pid,omitempty"`
	ChromeAlive      bool               `json:"chrome_alive"`
	DisplayBackend   string             `json:"display_backend,omitempty"`
	DisplayID        uint32             `json:"display_id,omitempty"`
}

type BrowserSession

type BrowserSession struct {
	FrameHub  *FrameBroadcastHub
	Authority *SessionAuthority
	InputGW   *InputGateway

	RecordBuf       *RecordBuffer // 当前录制缓冲区(nil = 未录制)[CAP-BS09-C3 §6.bis]
	RecordStartTime time.Time     // 录制开始时间(用于 elapsed 计算)
	// contains filtered or unexported fields
}

BrowserSession 是 LiveView 监督层的底座对象。 统一管理三层:

  • FrameHub (L1): 帧广播 — Screencast 帧 fan-out 给所有 WS subscriber
  • Authority (L2): 状态权威 — SessionState 单一权威 + 广播
  • InputGW (L3): 输入网关 — 接管模式 + 租约 + 输入转发

func NewBrowserSession

func NewBrowserSession(viewportW, viewportH int) *BrowserSession

NewBrowserSession 创建 BrowserSession 底座实例(不需要 cdpCtx)。

内部流程:

  1. 创建 SessionAuthority(viewportW/H 初始化)
  2. 创建 InputGateway(cdpCtx 为 nil,后续由 AttachBrowserCore / InputGW.SetCDPContext 注入)
  3. FrameHub 初始为 nil,由 AttachFrameHub(hub) 连接到 BrowserCore 内部 hub

启动时序:

NewBrowserSession → SetBrowserSession(bs) →(第一个 WS 连接到来时)→
bc.StartLiveView(ctx) → bs.AttachFrameHub(hub) → bs.AttachBrowserCore(bc)

func (*BrowserSession) AttachBrowserCore

func (bs *BrowserSession) AttachBrowserCore(bc BrowserCore)

AttachBrowserCore 注入 BrowserCore(SEV-H1 + SEV-H2 修复)。

  1. 注入 cdpCtx 给 InputGateway,使输入事件可以分发到 Chrome。
  2. SEV-H1: 将 InputGateway 的 onStateChange 回调扩展为同步调用 BrowserCore 的 EnableTakeover/DisableTakeover,消除 takeoverCtrl 与 InputGateway 的状态分裂。 InputGateway 是唯一入口,旧 takeoverCtrl 自动跟随。

func (*BrowserSession) AttachCursorDetection

func (bs *BrowserSession) AttachCursorDetection(bc BrowserCore)

AttachCursorDetection 向 BrowserCore 注入 cursor 检测,将 cursor 变更广播到 Authority。 如果 bc 未实现 CursorDetector 接口则静默忽略(向后兼容)。

func (*BrowserSession) AttachFrameHub

func (bs *BrowserSession) AttachFrameHub(hub *FrameBroadcastHub)

AttachFrameHub 连接 BrowserCore 内部帧广播 hub(问题 2 修复)。 必须在 BrowserCore.StartLiveView 返回后调用。 统一真相源: 帧由 BrowserCore 的 liveViewEngine 直接 Publish 到此 hub。

func (*BrowserSession) AttachedBrowserCore

func (bs *BrowserSession) AttachedBrowserCore() BrowserCore

AttachedBrowserCore 返回 Browser Portal LiveView 当前绑定的 canonical core。 REST API 必须优先复用它,否则 tabs/status 等读路径会创建第二个 TargetTracker, 造成 Chrome 已有标签页但 UI 投影不切换的状态分裂。

func (*BrowserSession) ResetAttach

func (bs *BrowserSession) ResetAttach()

ResetAttach 重置 attach 状态 + 恢复原始回调(不含旧 BrowserCore 包装)。 必须在 preset switch (Chrome 重启) 后调用。 Codex #1 修复: 恢复 baseCallback 而非在旧包装上再包装,防止回调链无限增长。

type BrowserSessionDefaults

type BrowserSessionDefaults struct {
	Kind           BrowserSessionKind
	Owner          string
	Mode           BrowserMode
	Isolation      string
	AuthorityState string
}

BrowserSessionDefaults captures the policy implied by a session kind.

func DefaultsForBrowserSessionKind

func DefaultsForBrowserSessionKind(kind BrowserSessionKind) BrowserSessionDefaults

DefaultsForBrowserSessionKind is the single policy table for kind-driven runtime defaults. Callers may override mode/profile/account explicitly.

type BrowserSessionHandle

type BrowserSessionHandle interface{}

BrowserSessionHandle — IsolationPolicy.Apply 的 session 句柄占位.

Phase_v2_1: 接口型空 marker, Phase_v2_4 由 BrowserSession 实现 (e.g. 暴露 CDP target / 配置写入接口). 这里仅声明类型, 让 IsolationPolicy 接口可编译, 不约束方法集.

type BrowserSessionKind

type BrowserSessionKind string

BrowserSessionKind is the public scenario identity for a browser session. It must stay domain-neutral: product terms such as portal or webchat belong to callers, not to dw-browser's public contract.

const (
	SessionKindTask        BrowserSessionKind = "task"
	SessionKindInteractive BrowserSessionKind = "interactive"
	SessionKindService     BrowserSessionKind = "service"
	SessionKindDebug       BrowserSessionKind = "debug"
	SessionKindTest        BrowserSessionKind = "test"
)

func NormalizeBrowserSessionKind

func NormalizeBrowserSessionKind(raw string, fallback BrowserSessionKind) BrowserSessionKind

NormalizeBrowserSessionKind accepts only public, general-purpose kind names.

type CDPConfig

type CDPConfig struct {
	Headless   bool
	ControlURL string
	DataDir    string
}

CDPConfig holds CDP connection configuration. Deprecated: use browser.BrowserCore instead.

func DefaultCDPConfig

func DefaultCDPConfig() CDPConfig

DefaultCDPConfig returns a default CDP configuration. Deprecated.

type CDPContextProvider

type CDPContextProvider interface {
	CDPContext() context.Context
}

CDPContextProvider 暴露 Chrome 的 CDP context 给外层(BrowserSession 注入 InputGateway 用)。 browserCoreImpl 实现此接口。

type CDPManager

type CDPManager struct{}

CDPManager is the old browser manager type. Deprecated: use BrowserCore instead.

func NewCDPManager

func NewCDPManager(cfg CDPConfig) *CDPManager

NewCDPManager creates a deprecated CDPManager stub.

func (*CDPManager) Connect

func (m *CDPManager) Connect(ctx context.Context) error

Connect is a no-op on the stub CDPManager.

func (*CDPManager) Disconnect

func (m *CDPManager) Disconnect() error

Disconnect is a no-op on the stub CDPManager.

func (*CDPManager) GetAllPages

func (m *CDPManager) GetAllPages() ([]*CompatPage, error)

GetAllPages returns empty on the stub CDPManager.

func (*CDPManager) GetCurrentPage

func (m *CDPManager) GetCurrentPage() (*CompatPage, error)

GetCurrentPage returns nil on the stub CDPManager.

func (*CDPManager) IsConnected

func (m *CDPManager) IsConnected() bool

IsConnected always returns false on the stub CDPManager.

func (*CDPManager) Navigate

func (m *CDPManager) Navigate(ctx context.Context, pageID, url string) error

Navigate is a no-op on the stub CDPManager.

func (*CDPManager) NewPage

func (m *CDPManager) NewPage(ctx context.Context, url string) (*CompatPage, string, error)

NewPage returns a stub page on the deprecated CDPManager.

type CanonicalFilter

type CanonicalFilter struct {
	Field string // "name", "placeholder", "testid"
	Op    string // "=" (exact), "*=" (contains), "^=" (prefix)
	Value string
}

CanonicalFilter canonical DSL 过滤器(name/placeholder/testid + op)。

type ChromeHandle

type ChromeHandle interface {
	WSURL() string         // CDP WebSocket URL (ready when LaunchChromeInSpace returns)
	PID() int              // Chrome process PID
	Kill() error           // SIGKILL the process and wait for exit (idempotent)
	Done() <-chan struct{} // closed when process exits (any cause)
	Wait() error           // block until process exits, return cmd.Wait error
}

ChromeHandle owns a Chrome process spawned via Workspace.LaunchChromeInSpace.

Invariants:

  • WSURL() is non-empty when LaunchChromeInSpace returns nil error.
  • PID() is the OS pid of the spawned chrome process.
  • Done() is closed when the process exits (any cause).
  • Kill() is idempotent and waits for process exit before returning.

func NewBrowserMuxHostChromeHandle

func NewBrowserMuxHostChromeHandle(state *BrowserMuxHostState) ChromeHandle

type ChromeLaunchSpec

type ChromeLaunchSpec struct {
	ChromePath   string        // absolute path to chrome binary
	Args         []string      // full Chrome args (must include --remote-debugging-port=N)
	DebugPort    int           // must match --remote-debugging-port in Args
	ReadyTimeout time.Duration // CDP ready timeout (default 30s if zero)
}

ChromeLaunchSpec — input to Workspace.LaunchChromeInSpace.

type CompatPage

type CompatPage struct{}

CompatPage is a stub page type for backward compatibility. Deprecated.

func (*CompatPage) Info

func (p *CompatPage) Info() (*CompatTargetInfo, error)

Info returns stub target info.

type CompatTargetInfo

type CompatTargetInfo struct {
	URL   string
	Title string
}

CompatTargetInfo is a stub for target info in compat layer.

type CookieImportResult

type CookieImportResult struct {
	TotalImported int            // 导入 Cookie 总数
	ByDomain      map[string]int // 按域名统计
	SourceBrowser string         // 实际使用的源浏览器
}

CookieImportResult 是 Cookie 导入的结果统计。

type CoreFactoryRequest

type CoreFactoryRequest struct {
	Engine      BrowserEngine   // EngineChrome | EngineSafari
	DeviceQuery string          // Safari: 设备名/预设名
	DeviceUDID  string          // Safari: 直接指定 UDID
	ProfileID   string          // Chrome: profile ID
	Options     []BrowserOption // Chrome: 浏览器选项
}

CoreFactoryRequest 统一 BrowserCore 创建请求。 实际的 factory 函数(switch-case)放在 CLI 层(cmd/dw-browser/main.go), 避免 internal/browser 导入子包 internal/browser/safari 造成循环依赖。

type CursorDetector

type CursorDetector interface {
	SetupCursorDetection(onCursor func(cursor string)) error
}

CursorDetector 是 cursor 检测接口。 browserCoreImpl 实现此接口;BrowserSession 通过类型断言调用。

type DetachedChromeLaunchOptions

type DetachedChromeLaunchOptions struct {
	DebugPort  int
	ProfileDir string
	Width      int
	Height     int
	PresetID   string
	UserAgent  string
	Touch      bool
	Mode       BrowserMode
}

DetachedChromeLaunchOptions 描述 detached Chrome 进程的启动参数。 用于需要独立 Chrome 生命周期的场景(如 dw-browser open/session)。

type DisplayManager

type DisplayManager struct {
	// contains filtered or unexported fields
}

DisplayManager 管理独立虚拟 display 的生命周期 (跨平台)。 Pool 和 NewBrowserCore 共享此组件。goroutine-safe。

func (*DisplayManager) ChromeGLOpts

func (dm *DisplayManager) ChromeGLOpts() []chromedp.ExecAllocatorOption

ChromeGLOpts 返回平台专用 GPU/display 的 chromedp ExecAllocatorOption。 human 模式下,各平台需要不同的 GL 渲染参数。

func (*DisplayManager) Close

func (dm *DisplayManager) Close() error

Close 清理 Xvfb 进程。实现 io.Closer 语义。

func (*DisplayManager) Display

func (dm *DisplayManager) Display() string

Display 返回当前 display 字符串 (e.g. ":99")。空字符串表示未启动。

func (*DisplayManager) EnsureDisplay

func (dm *DisplayManager) EnsureDisplay() bool

EnsureDisplay 确保虚拟 display 就绪 (幂等,可多次调用)。 仅在 human 模式下有意义;headless 模式调用为 no-op。 返回 true 表示 display 已就绪,false 表示应降级为 headless。

func (*DisplayManager) UsesHeadlessHumanMode

func (dm *DisplayManager) UsesHeadlessHumanMode() bool

UsesHeadlessHumanMode 返回当前平台的 human 模式是否底层使用 headless=new。 当前终局策略:

  • Linux: Xvfb + EGL (headed)
  • macOS: offscreen window + Metal (headed)
  • Windows: offscreen window + D3D11 (headed)

调用者在此返回 true 时应:

  1. 显式覆写 UA(移除 HeadlessChrome 签名)
  2. 注入完整 stealth 脚本(与 headless 模式一致)

func (*DisplayManager) XvfbPID

func (dm *DisplayManager) XvfbPID() int

XvfbPID 返回 Xvfb 进程 PID。0 表示未启动或非 Linux。

type ElementRef

type ElementRef struct {
	Ref                string      // "e1", "e2", ... 或 session 模式下 "@r1", "@r2", ...
	BackendNodeID      int64       // CDP BackendNodeID
	Locator            NodeLocator `json:"-"` // 引擎无关定位器(内部使用,不序列化到 CLI 输出)
	AXPath             string      `json:"-"` // Safari AX 树路径(便捷别名,Chrome 为空)
	Role               string      // ARIA role
	Name               string      // accessible name
	Placeholder        string      // input placeholder
	TestID             string      // data-testid 属性值
	Interactable       bool        // 是否可交互(button/input/link/select...)
	NameFull           string      // 完整 accessible name(不截断)
	NameShort          string      // 截断后的 name(≤50 字符,用于显示)
	RecommendedLocator string      // 推荐的 locator(供 Agent 直接使用)
	MatchCount         int         // 同 role+name 的元素数量
}

ElementRef 是可交互元素的语义引用(每次 snap 后重建)。

type FingerprintPreset

type FingerprintPreset struct {
	ID                string  `json:"id"`
	Name              string  `json:"name"`
	UserAgent         string  `json:"user_agent"`
	Platform          string  `json:"platform"`  // navigator.platform
	Vendor            string  `json:"vendor"`    // navigator.vendor
	Languages         string  `json:"languages"` // JS array literal
	WebGLVendor       string  `json:"webgl_vendor"`
	WebGLRenderer     string  `json:"webgl_renderer"`
	ViewportW         int     `json:"viewport_w"`
	ViewportH         int     `json:"viewport_h"`
	DeviceScaleFactor float64 `json:"device_scale_factor"`
	Mobile            bool    `json:"mobile"`
	Touch             bool    `json:"touch"`
	MaxTouchPoints    int     `json:"max_touch_points"`
}

FingerprintPreset 浏览器指纹预设。

func ResolveRuntimeFingerprintPreset

func ResolveRuntimeFingerprintPreset(presetID string, chromePath string) *FingerprintPreset

ResolveRuntimeFingerprintPreset returns a runtime copy of the preset with the local Chrome major version injected into UA/name for Chrome-based presets.

type FrameBroadcastHub

type FrameBroadcastHub struct {
	// contains filtered or unexported fields
}

FrameBroadcastHub 将 Screencast 帧 fan-out 给所有 WS subscriber。

设计要点:

  • 每个 subscriber (WS 连接) 持有独立的 1-slot channel,避免单 channel 竞争消费 [CAP-BS09-C3]
  • 保新丢旧: Publish 时 channel 满则替换为最新帧(非阻塞生产者)[TC-09-U-09]
  • fan-out: 一帧广播给所有 subscriber,互不干扰
  • close 安全: Unsubscribe 时安全关闭 channel,不会 panic

func NewFrameBroadcastHub

func NewFrameBroadcastHub() *FrameBroadcastHub

NewFrameBroadcastHub 创建 FrameBroadcastHub 实例。

func (*FrameBroadcastHub) Count

func (h *FrameBroadcastHub) Count() int

Count 返回当前 subscriber 数量。

func (*FrameBroadcastHub) Publish

func (h *FrameBroadcastHub) Publish(frame *ScreencastFrame)

Publish 将帧广播给所有 subscriber(非阻塞,保新丢旧)[TC-09-U-09]。

保新丢旧逻辑:

  1. 尝试直接写入 channel
  2. channel 满时先取出旧帧,再写入新帧 (内层 default 防止并发 Unsubscribe 导致 channel 被关闭后二次操作)

func (*FrameBroadcastHub) Subscribe

func (h *FrameBroadcastHub) Subscribe(connID string) chan *ScreencastFrame

Subscribe 为 connID 注册独立的 1-slot 帧 channel,返回 channel 本身(用于 Unsubscribe 匹配)。 重复注册同一 connID 会先关闭旧 channel 再创建新的(幂等注册)。

返回值是双向 channel,用于传给 Unsubscribe 做代次匹配。 读取端请用 <-chan *ScreencastFrame 类型接收。

func (*FrameBroadcastHub) Unsubscribe

func (h *FrameBroadcastHub) Unsubscribe(connID string, ch chan *ScreencastFrame)

Unsubscribe 用 channel 指针做代次匹配,只关闭与传入 ch 相同的 channel。 若 connID 已被新连接替换(ch 不匹配),静默忽略,不影响新连接。 这是 SEV-H2 的关键修复:防止旧连接的 defer 关掉新连接的 channel。

type HUDConfig

type HUDConfig struct {
	MaxNodes            int
	LabelFontSize       int
	ClickRippleDuration time.Duration
}

HUDConfig is the config for the Phantom HUD renderer. Deprecated: kept for compat with internal/desktop.

func DefaultHUDConfig

func DefaultHUDConfig() HUDConfig

DefaultHUDConfig returns the default HUD configuration. Deprecated.

type HUDRenderer

type HUDRenderer interface {
	Init(ctx context.Context, cfg HUDConfig) error
	Show() error
	Hide() error
	Render(nodes []SuperNode) error
	RenderRipple(x, y int, t RippleType) error
	SetStatus(status string) error
	Highlight(nodeID int) error
	ClearHighlight() error
	SetPosition(x, y, width, height int) error
	Close() error
}

HUDRenderer is the interface for Phantom HUD rendering. Deprecated: kept for compat with internal/desktop.

func NewCDPHUDRenderer

func NewCDPHUDRenderer(controlURL string) HUDRenderer

NewCDPHUDRenderer creates a deprecated HUD renderer stub. Deprecated: use LiveView WebSocket API instead.

type HumanOverride

type HumanOverride struct {
	// contains filtered or unexported fields
}

HumanOverride is a stub for the old human-override mechanism. Deprecated.

func NewHumanOverride

func NewHumanOverride() *HumanOverride

NewHumanOverride creates a new HumanOverride stub.

func (*HumanOverride) IsActive

func (h *HumanOverride) IsActive() bool

IsActive returns true if human override is active.

func (*HumanOverride) Toggle

func (h *HumanOverride) Toggle()

Toggle switches the override state.

type Identity

type Identity struct {
	Profile string
	Preset  Preset
	Policy  IsolationPolicy
}

Identity — 三元组逻辑聚合 (便于上层调用方按值传递).

注意: Identity 本身不参与 hash, IdentityKey 由 NewIdentityKey(profile, preset, policy) 三参数计算; 此结构体只是便利封装.

type IdentityDescriptor

type IdentityDescriptor struct {
	Key     IdentityKey
	Profile string          // 物理身份 (profileID, 与 user-data-dir 一一对应)
	Preset  Preset          // UA / viewport / locale / timezone / 指纹
	Policy  IsolationPolicy // 隔离策略 (Phase_v2_1: 仅 NoopPolicy)
}

IdentityDescriptor — IdentityKey 反查结果 (Inspect / List 用).

type IdentityKey

type IdentityKey string

IdentityKey — identity 三元组的确定性 hash, 稳定可比较 + 跨进程一致. 实现: SHA-256(profile|preset.canonical|policy.PolicyKey()) 截断 32 字节 hex.

不变式:

  • 同三元组多次调用 NewIdentityKey 必须返回同一 IdentityKey (字节相等).
  • 三个维度任一变化 → IdentityKey 必变 (D1 + DDC-10 3 维正交).

func NewIdentityKey

func NewIdentityKey(profile string, preset Preset, policy IsolationPolicy) IdentityKey

NewIdentityKey 把三元组归一化为 IdentityKey (确定性 SHA-256 hash 截断).

算法:

raw  = "profile=" + profile + "||preset=" + preset.canonical() + "||policy=" + policy.PolicyKey()
hash = sha256(raw)
key  = hex(hash[:16])  // 32 字符, 128 bit, 抗碰撞充足

约束 (TC-09-U-38 守护):

  • 同三元组多次调用必须返回字节相等的 IdentityKey.
  • policy=nil 时按 NoopPolicy 处理 (不会 panic), 但调用方应显式传 NoopPolicy{}.

type IdentityPoolStatus

type IdentityPoolStatus struct {
	Key       IdentityKey
	ChromePID int         // Chrome 主进程 PID; 0 = 尚未启动 / 已退出
	TabCount  int         // 当前活跃 Tab 数
	Mode      BrowserMode // 当前 Chrome 进程运行模式: human/headless
	Tabs      []TabHandle // 该 identity 下所有 Tab (按 TargetID 字典序)
}

IdentityPoolStatus — 单个 identity 在 Pool 中的状态.

type IdentityRegistry

type IdentityRegistry interface {
	// Resolve 把 (profile, preset, policy) 三元组归一化为 IdentityKey.
	// 同三元组多次 Resolve 必须返回同一 IdentityKey (确定性, TC-09-U-38).
	// 同时把 IdentityDescriptor 注册到内部 store (供 Inspect / List 反查).
	Resolve(profile string, preset Preset, policy IsolationPolicy) (IdentityKey, error)

	// Inspect 反查 IdentityKey 对应的三元组. 未注册 → ErrIdentityNotFound.
	Inspect(key IdentityKey) (*IdentityDescriptor, error)

	// List 列出当前 Registry 已知的所有 identity (按 IdentityKey 字典序).
	List() []IdentityDescriptor
}

IdentityRegistry — identity 三元组解析与归一化 (D1).

对照 Cap §2.bis:

Resolve(profile, preset, policy) → IdentityKey  // 确定性归一化
Inspect(key) → *IdentityDescriptor              // 反查
List() → []IdentityDescriptor                   // 全量列表 (诊断/可观测)

线程安全: 实现必须支持并发 Resolve / Inspect / List.

func NewIdentityRegistry

func NewIdentityRegistry() IdentityRegistry

NewIdentityRegistry 返回一个新的内存 IdentityRegistry.

初始为空; 首次 Resolve 即 lazy-register.

type InputAck

type InputAck struct {
	Type   string `json:"type"`             // 固定为 "input-ack"
	Seq    int64  `json:"seq"`              // 对应客户端 seq
	Status string `json:"status"`           // "accepted" / "rejected" / "error"
	Reason string `json:"reason,omitempty"` // 拒绝原因(仅 rejected/error 时填充)
}

InputAck 是服务端对 InputMessage 的 ACK 响应。

type InputEvent

type InputEvent struct {
	Type        string       `json:"type"`  // "mouse" | "keyboard" | "touch"
	Event       string       `json:"event"` // mousePressed/mouseReleased/mouseMoved/mouseWheel/keyDown/keyUp/char/touchStart/touchMove/touchEnd/touchCancel
	X           float64      `json:"x"`
	Y           float64      `json:"y"`
	Button      string       `json:"button"`
	ClickCount  int          `json:"clickCount"`
	DeltaX      float64      `json:"deltaX"` // scroll
	DeltaY      float64      `json:"deltaY"` // scroll
	Key         string       `json:"key"`
	Code        string       `json:"code"`
	Text        string       `json:"text"`
	Modifiers   int          `json:"modifiers"`             // bit flags: Alt=1, Ctrl=2, Meta=4, Shift=8
	TouchPoints []TouchPoint `json:"touchPoints,omitempty"` // touch 事件的触摸点列表
}

InputEvent 是前端接管模式下的输入事件。

type InputGateway

type InputGateway struct {
	// contains filtered or unexported fields
}

InputGateway 输入网关 — 替代 TakeoverController [CAP-BS09-C3 r2, DDC-I-06, DDC-I-07]

func NewInputGateway

func NewInputGateway(cdpCtx context.Context, onStateChange func()) *InputGateway

NewInputGateway 创建 InputGateway,初始状态为 OBSERVE。

cdpCtx: chromedp 会话 context,用于 CDP dispatch。 onStateChange: 模式变化时触发的回调(由 browser_session 注册,用于向前端广播状态)。

func (*InputGateway) GetOnStateChange

func (g *InputGateway) GetOnStateChange() func()

GetOnStateChange 返回当前 onStateChange 回调(供 AttachBrowserCore 链式包装用)。

func (*InputGateway) GetRecordBuffer

func (g *InputGateway) GetRecordBuffer() *RecordBuffer

GetRecordBuffer 返回当前注入的录制缓冲区(nil 表示未启用)。

func (*InputGateway) HandleInput

func (g *InputGateway) HandleInput(connID string, msg *InputMessage) *InputAck

HandleInput 校验 lease → CDP dispatch → 返回 ACK → 续租。

校验顺序:

  1. mode == TakeoverModeTakeover
  2. connID == g.owner
  3. msg.Lease == g.leaseToken
  4. time.Now() < g.leaseExpiry

任何校验失败返回 rejected + reason。 dispatch 成功返回 accepted,并在成功后续租(reset idle timer)。

func (*InputGateway) LeaseExpiry

func (g *InputGateway) LeaseExpiry() time.Time

LeaseExpiry 返回 lease 过期时间(OBSERVE 模式下返回零值)。

func (*InputGateway) LeaseToken

func (g *InputGateway) LeaseToken() string

LeaseToken 返回当前 lease token(OBSERVE 模式下返回空字符串)。

func (*InputGateway) Mode

func (g *InputGateway) Mode() TakeoverMode

Mode 返回当前接管模式。

func (*InputGateway) OnDisconnect

func (g *InputGateway) OnDisconnect(connID string)

OnDisconnect 断连处理 — 启动 grace timer。

5s 内重连(OnReconnect)可取消 grace timer 恢复接管。 超时后 auto-release + 合成释放所有按压输入。

func (*InputGateway) OnReconnect

func (g *InputGateway) OnReconnect(connID string)

OnReconnect 重连处理 — 取消 grace timer(如果 connID 匹配 owner)。

重连后 owner 可继续使用原有 lease token 发送输入。

func (*InputGateway) Owner

func (g *InputGateway) Owner() string

Owner 返回当前 owner connID(OBSERVE 模式下返回空字符串)。

func (*InputGateway) ReleaseTakeover

func (g *InputGateway) ReleaseTakeover(connID string) error

ReleaseTakeover 主动释放接管,恢复 OBSERVE 模式,并合成释放所有按压输入。

只有当前 owner 才能释放。

func (*InputGateway) RequestTakeover

func (g *InputGateway) RequestTakeover(connID string) (leaseToken string, expiresAt time.Time, err error)

RequestTakeover CAS 抢锁,切换到 TAKEOVER 模式。

已有 owner 时返回 error,包含 currentOwner 和 leaseRemaining 信息。 成功返回 leaseToken 和 expiresAt(= now + idleTimeout)。

func (*InputGateway) SetCDPContext

func (g *InputGateway) SetCDPContext(ctx context.Context)

SetCDPContext 延迟注入 Chrome CDP context(Chrome 启动后由 AttachBrowserCore 调用)。 允许 BrowserSession 在 Chrome 启动前创建,Chrome 就绪后再连接。

func (*InputGateway) SetCDPContextProvider

func (g *InputGateway) SetCDPContextProvider(fn func() context.Context)

SetCDPContextProvider 注册“当前活跃 Target 的 CDP context”解析器。 InputGateway 在每次 dispatch 时动态取值,避免持有已经被取消的快照 ctx。

func (*InputGateway) SetOnAcceptedInput

func (g *InputGateway) SetOnAcceptedInput(fn func(event *InputEvent))

SetOnAcceptedInput 注册”已通过接管校验的真实用户输入”回调。 用于把输入链与 TargetTracker 的 target-claim 协议衔接起来,避免 liveview 侧靠 heuristic 猜 target 归属。

func (*InputGateway) SetOnStateChange

func (g *InputGateway) SetOnStateChange(fn func())

SetOnStateChange 替换 onStateChange 回调(供 AttachBrowserCore 链式包装用)。 用于 SEV-H1: AttachBrowserCore 将原有回调包装后重新注入,实现 InputGateway → BrowserCore 状态同步。

func (*InputGateway) SetRecordBuffer

func (g *InputGateway) SetRecordBuffer(rb *RecordBuffer)

SetRecordBuffer 注入录制缓冲区。nil 表示禁用录制 tap。 Recording tap 在 dispatch 成功 (accepted) 后触发,零侵入主路径 (SK-D4)。

type InputMessage

type InputMessage struct {
	Type  string     `json:"type"`  // 固定为 "input"
	Seq   int64      `json:"seq"`   // 客户端单调递增序号
	Lease string     `json:"lease"` // 当前租约令牌(必须与 leaseToken 匹配)
	Event InputEvent `json:"event"` // 具体输入事件
}

InputMessage 是前端通过 WebSocket 发送的输入消息(接管模式)。

type IsolationPolicy

type IsolationPolicy interface {
	// PolicyKey 返回稳定 key 参与 IdentityKey hash.
	// 同一类型 policy 同一配置 → 必返回同一 key (确定性).
	PolicyKey() string

	// Apply 在 Chrome 启动后通过 CDP 把 policy 落地 (proxy / permission / download / extension / geo / compliance).
	// Phase_v2_1: NoopPolicy.Apply 直接返回 nil.
	Apply(ctx context.Context, session BrowserSessionHandle) error
}

IsolationPolicy — identity 第 3 正交维度 (DDC-10, BRR-07, D9 SL-2).

Phase_v2_1: 仅 NoopPolicy stub (P0 skeleton). Phase_v2_4: ProxyPolicy 首例 (J3 默认值, P1 deferred).

type JavaScriptDialogEvent

type JavaScriptDialogEvent struct {
	ID            string `json:"id"`
	TargetID      string `json:"target_id"`
	URL           string `json:"url"`
	FrameID       string `json:"frame_id"`
	Message       string `json:"message"`
	Type          string `json:"type"`
	DefaultPrompt string `json:"default_prompt,omitempty"`
}

JavaScriptDialogEvent describes a page-level alert/confirm/prompt that must be surfaced through LiveView when Chrome itself is running on a virtual display.

type LiveViewportSyncer

type LiveViewportSyncer interface {
	// SetLiveViewport 更新当前 liveview 目标 viewport 配置,并立即应用到活跃 target。
	SetLiveViewport(width, height int, dpr float64, mobile bool) error
	// SyncActiveTargetViewport 将最近一次 liveview viewport 配置重放到指定活跃 target。
	SyncActiveTargetViewport(targetCtx context.Context) error
}

LiveViewportSyncer 暴露 LiveView viewport 的跨 target 同步能力。 适用于 BrowserPool/WebUI 场景:前端真实容器尺寸变化后,当前活跃 target 与 后续切换到的新 target(新 tab / auth popup)都应继承同一套 viewport 语义。

设计目标:

  • PC / iOS 共用一套接口,不拆平台特判
  • 不仅同步 Screencast 分辨率,也同步 CDP DeviceMetrics / Touch 语义
  • 新 target 激活时可重放当前 liveview viewport,避免多 tab 出现黑边或错位

type MemoryHUDRenderer

type MemoryHUDRenderer struct {
	// contains filtered or unexported fields
}

MemoryHUDRenderer is an in-memory HUDRenderer for use in unit tests.

func NewMemoryHUDRenderer

func NewMemoryHUDRenderer() *MemoryHUDRenderer

NewMemoryHUDRenderer creates a new in-memory HUD renderer for testing.

func (*MemoryHUDRenderer) ClearHighlight

func (r *MemoryHUDRenderer) ClearHighlight() error

func (*MemoryHUDRenderer) Close

func (r *MemoryHUDRenderer) Close() error

func (*MemoryHUDRenderer) GetRippleHistory

func (r *MemoryHUDRenderer) GetRippleHistory() []MemoryRippleCall

GetRippleHistory returns a copy of all recorded RippleRender calls.

func (*MemoryHUDRenderer) Hide

func (r *MemoryHUDRenderer) Hide() error

func (*MemoryHUDRenderer) Highlight

func (r *MemoryHUDRenderer) Highlight(_ int) error

func (*MemoryHUDRenderer) Init

func (*MemoryHUDRenderer) Render

func (r *MemoryHUDRenderer) Render(nodes []SuperNode) error

func (*MemoryHUDRenderer) RenderRipple

func (r *MemoryHUDRenderer) RenderRipple(x, y int, t RippleType) error

func (*MemoryHUDRenderer) SetPosition

func (r *MemoryHUDRenderer) SetPosition(_, _, _, _ int) error

func (*MemoryHUDRenderer) SetStatus

func (r *MemoryHUDRenderer) SetStatus(status string) error

func (*MemoryHUDRenderer) Show

func (r *MemoryHUDRenderer) Show() error

type MemoryRippleCall

type MemoryRippleCall struct {
	X          int
	Y          int
	RippleType RippleType
}

MemoryRippleCall records a single RenderRipple call.

type NodeLocator

type NodeLocator struct {
	Engine        BrowserEngine `json:"engine"`
	BackendNodeID int64         `json:"backend_node_id,omitempty"` // Chrome only
	AXPath        string        `json:"ax_path,omitempty"`         // Safari: AX 树路径 (如 "0.3.1")
	StableKey     string        `json:"stable_key,omitempty"`      // Safari: role+name+identifier 组合,用于漂移重定位
	Ordinal       int           `json:"ordinal,omitempty"`         // DFS 序号
	Frame         Rect          `json:"frame,omitempty"`           // 元素位置 (Safari coordinate tap fallback)
}

NodeLocator 是引擎无关的元素定位器。Chrome 使用 BackendNodeID,Safari 使用 AXPath + StableKey。

type NoopPolicy

type NoopPolicy struct{}

NoopPolicy — Phase_v2_1 默认隔离策略, 不施加任何额外约束.

不变式: 所有 NoopPolicy 实例 PolicyKey 相同 (= "noop"), 因此 (profile, preset, NoopPolicy{}) 三元组的 IdentityKey 也相同, 实现"默认 identity 即 (profile, preset)"语义.

func (NoopPolicy) Apply

Apply 是 no-op, 直接返回 nil (不修改 session).

func (NoopPolicy) PolicyKey

func (NoopPolicy) PolicyKey() string

PolicyKey 返回稳定常量 "noop".

type NoopWorkspace

type NoopWorkspace struct{}

NoopWorkspace is a no-op for tests / unsupported platforms. LaunchChromeInSpace falls back to a direct fork (no Space switching).

func (*NoopWorkspace) Close

func (n *NoopWorkspace) Close() error

func (*NoopWorkspace) EnsureSpace

func (n *NoopWorkspace) EnsureSpace() (int64, error)

func (*NoopWorkspace) LaunchChromeInSpace

func (n *NoopWorkspace) LaunchChromeInSpace(spec ChromeLaunchSpec) (ChromeHandle, error)

type PageTargetSelection

type PageTargetSelection struct {
	ID     string
	URL    string
	Reason string
}

func SelectAttachablePageTarget

func SelectAttachablePageTarget(targets []map[string]interface{}, expectedURL, fallbackID string) PageTargetSelection

SelectAttachablePageTarget chooses the page target that best represents the user's current browser state. It never treats ChromeInitialPageURL as user intent; that URL is only a Chrome startup sentinel.

type ParsedAction

type ParsedAction struct {
	Op     string  // "click" | "clickat" | "tap" | "tapat" | "type" | "scroll" | "hover" | "select"
	Ref    string  // Element Ref(如 "e3")或语义选择器(如 "#testid", "button:'name'")
	Value  string  // type/select 的值
	CoordX float64 // clickat 的相对 X 坐标(0..1)
	CoordY float64 // clickat 的相对 Y 坐标(0..1)
}

ParsedAction 是解析后的操作结构。

func ParseAction

func ParseAction(action string) (*ParsedAction, error)

ParseAction 解析操作语法字符串 [TC-09-U-05, TC-09-U-06]。

支持操作:

  • "click #testid" | "click button:'名称'"
  • "clickat #canvas 92% 8%" — 对元素相对坐标执行真实鼠标点击
  • "tap button:'接管'" | "tapat #browser-liveview 92% 8%" — 对元素执行真实触控点击
  • "fill #input 'text'" — 清空后输入
  • "type textbox:'名称' 'hello'"
  • "press Enter" | "press Ctrl+A" | "press #btn Ctrl+K"
  • "hover button:'名称'"
  • "scroll down" | "scroll up"
  • "select e4 'opt2'"
  • "back" | "forward"
  • "focus #selector"
  • "scrollinto #selector"
  • "check #selector" | "uncheck #selector"

type ParsedSelector

type ParsedSelector struct {
	SType       SelectorType
	TestID      string
	Role        string
	Name        string
	NameOp      string            // "=" exact | "*=" contains | "^=" prefix
	Filters     []CanonicalFilter // canonical DSL 过滤器
	Nth         int               // nth filter (0 = no nth)
	ScopeParent *ParsedSelector   // A >> B 中的 A
	Raw         string            // 原始字符串
	SessionRef  int               // @rN 的 N
}

ParsedSelector 解析后的语义选择器。

func ParseSelector

func ParseSelector(selector string) (*ParsedSelector, error)

ParseSelector 解析选择器字符串。

优先级顺序:

  1. @rN — session ref(仅 session 模式有效)
  2. #testid — data-testid
  3. role=TYPE[...] — canonical DSL
  4. A >> B — scoped selector
  5. role:'name' — shorthand(contains)
  6. role="name" — shorthand(exact)
  7. css=... — explicit CSS
  8. e{N} — legacy ref(拒绝)
  9. pure identifier — bare role

10. everything else — CSS fallback

type PhantomHUD

type PhantomHUD struct {
	// contains filtered or unexported fields
}

PhantomHUD is a stub for the old Phantom HUD orchestrator. Deprecated.

func NewPhantomHUD

func NewPhantomHUD(cfg HUDConfig, renderer HUDRenderer) *PhantomHUD

NewPhantomHUD creates a new PhantomHUD stub.

func (*PhantomHUD) Update

func (p *PhantomHUD) Update(nodes []SuperNode) error

Update renders the given nodes via the underlying renderer.

type PoolConfig

type PoolConfig struct {
	DataDir     string        // deepwork 数据目录 (e.g. ~/.deepwork)
	MaxTabs     int           // Tab 上限 (default 10) — 全局, 跨所有 identity entry
	IdleTimeout time.Duration // Tab 空闲回收时间 (default 5min)
	PresetID    string        // 默认 preset (legacy 入口生效)
	ProfileID   string        // 默认 profile (legacy 入口生效)
	Mode        BrowserMode   // "headed"(默认) / "headless" / "visible"
}

PoolConfig 配置 BrowserPool 默认 (legacy default identity 用).

type PoolSnapshot

type PoolSnapshot struct {
	Identities []IdentityPoolStatus
	TotalTabs  int
}

PoolSnapshot — Pool 当前状态的只读视图 (诊断 / 可观测).

注: Identities 按 IdentityKey 字典序排序 (确定输出, 便于诊断对比).

type Preset

type Preset struct {
	UserAgent      string   // navigator.userAgent
	Viewport       Viewport // 视口尺寸 + DPR
	Locale         string   // navigator.language (e.g. "en-US")
	Timezone       string   // IANA tz name (e.g. "America/New_York")
	FingerprintTag string   // TH-0418-c9x 指纹 runtime tag (e.g. "macos-chrome-126")
}

Preset — 浏览器表观属性集 (UA / viewport / locale / timezone / 指纹 runtime tag).

注意: 字段顺序参与 canonical 序列化, 不可随意调整 (会破坏 IdentityKey 稳定性).

type Profile

type Profile struct {
	ID          string    `json:"id"`
	Name        string    `json:"name"`
	UserDataDir string    `json:"user_data_dir"`
	Status      string    `json:"status"` // "active" | "crashed" | "inactive"
	CreatedAt   time.Time `json:"created_at"`
}

Profile 是 Browser Profile 实体 [Ref: T5-B2.1]。

type ProfileManager

type ProfileManager struct {
	// contains filtered or unexported fields
}

ProfileManager 管理 Browser Profile 生命周期(元数据用 JSON 文件,不用 SQLite)[Ref: BP §B3]。

func NewProfileManager

func NewProfileManager() (*ProfileManager, error)

NewProfileManager 创建 ProfileManager 实例。

func NewProfileManagerForDataDir

func NewProfileManagerForDataDir(dataDir string) (*ProfileManager, error)

NewProfileManagerForDataDir 基于指定 deepwork dataDir 创建 ProfileManager。

func NewProfileManagerWithBase

func NewProfileManagerWithBase(baseDir string) (*ProfileManager, error)

NewProfileManagerWithBase 创建指定 baseDir 的 ProfileManager(测试用)。

func (*ProfileManager) Create

func (m *ProfileManager) Create(name string) (*Profile, error)

Create 创建新的逻辑 profile。 若同名 slug 已存在,则自动追加数值后缀,避免覆盖现有用户状态。

func (*ProfileManager) Delete

func (m *ProfileManager) Delete(id string) error

Delete 删除 Profile(元数据 + user-data-dir)[TC-09-I-14]。

func (*ProfileManager) EnsureDefault

func (m *ProfileManager) EnsureDefault() (*Profile, error)

EnsureDefault 确保默认 profile 存在。

func (*ProfileManager) GetOrCreate

func (m *ProfileManager) GetOrCreate(id string) (*Profile, error)

GetOrCreate 获取或创建 Profile,确保目录存在 [TC-09-U-12, TC-09-U-13]。

func (*ProfileManager) InjectStealth

func (m *ProfileManager) InjectStealth(ctx context.Context) error

InjectStealth 注入 Stealth 脚本(webdriver 伪装)[TC-09-U-15]。

func (*ProfileManager) List

func (m *ProfileManager) List() ([]*Profile, error)

List 返回所有 Profile [TC-09-I-14]。

func (*ProfileManager) Repair

func (m *ProfileManager) Repair(id string) (*Profile, error)

Repair 修复损坏的 Profile(备份旧目录 + 重建)[TC-09-U-14]。

type ProfileOwnerMetadata

type ProfileOwnerMetadata struct {
	BrowserSessionID      string
	SessionKind           BrowserSessionKind
	BrowserMuxHostID      string
	BrowserMuxHostPID     int
	RuntimeID             string
	BrowserRunID          string
	ProfileID             string
	DisplayBackend        string
	DisplayID             uint32
	DisplayVerified       bool
	ChromeWindowContained bool
}

type RecordBuffer

type RecordBuffer struct {
	// contains filtered or unexported fields
}

RecordBuffer 接收 InputGateway 事件 tap,按步骤组装录制 trace。 线程安全: 所有公开方法均加锁。

func NewRecordBuffer

func NewRecordBuffer() *RecordBuffer

NewRecordBuffer 创建 RecordBuffer 实例(初始非录制状态)。

func (*RecordBuffer) AppendKeyEvent

func (rb *RecordBuffer) AppendKeyEvent(event *InputEvent)

AppendKeyEvent 处理键盘事件 tap。 keyDown 事件: 可打印字符 → 合并 "type";特殊键 → "keypress"。 其他事件 (keyUp/char) 忽略。 调用前不需要持锁(内部加锁)。

func (*RecordBuffer) AppendMouseEvent

func (rb *RecordBuffer) AppendMouseEvent(event *InputEvent)

AppendMouseEvent 处理鼠标事件 tap。 仅 mousePressed 转为 "click" step(mouseReleased/mouseMoved 忽略)。 调用前不需要持锁(内部加锁)。

func (*RecordBuffer) ExportJSON

func (rb *RecordBuffer) ExportJSON() ([]byte, error)

ExportJSON 将当前 trace 序列化为 JSON。

func (*RecordBuffer) IsRecording

func (rb *RecordBuffer) IsRecording() bool

IsRecording 返回当前是否正在录制。

func (*RecordBuffer) SetSnapshotFn

func (rb *RecordBuffer) SetSnapshotFn(fn func(x, y float64) string)

SetSnapshotFn 注入 A11y 快照回调(DDC-I-24: 操作 target 最小子树)。

func (*RecordBuffer) Start

func (rb *RecordBuffer) Start(domain, url string)

Start 开始录制,重置状态。连续两次 Start 会重置为新录制。

func (*RecordBuffer) StepCount

func (rb *RecordBuffer) StepCount() int

StepCount 返回当前已记录的 step 数量。

func (*RecordBuffer) Stop

func (rb *RecordBuffer) Stop() RecordTrace

Stop 停止录制,计算 duration,返回 trace 快照。 未录制时返回空 trace,不 panic。

type RecordStep

type RecordStep struct {
	Seq            int           `json:"seq"`
	Action         string        `json:"action"` // "click" | "type" | "keypress" | "scroll"
	Target         *RecordTarget `json:"target,omitempty"`
	Text           string        `json:"text,omitempty"`
	Key            string        `json:"key,omitempty"`
	Coordinates    *RecordXY     `json:"coordinates,omitempty"`
	TimestampMs    int64         `json:"timestamp_ms"`
	SnapshotBefore string        `json:"snapshot_before,omitempty"`
}

RecordStep 是 trace 中的单个操作步骤。

type RecordTarget

type RecordTarget struct {
	Selector string `json:"selector"`
	Role     string `json:"role"`
	Name     string `json:"name"`
	Tag      string `json:"tag"`
}

RecordTarget 描述操作目标的 A11y 元数据。

type RecordTrace

type RecordTrace struct {
	Domain     string       `json:"domain"`
	StartURL   string       `json:"start_url"`
	StartTime  time.Time    `json:"start_time"`
	DurationMs int64        `json:"duration_ms"`
	Steps      []RecordStep `json:"steps"`
}

RecordTrace 是完整的操作录制记录。

type RecordXY

type RecordXY struct {
	X float64 `json:"x"`
	Y float64 `json:"y"`
}

RecordXY 记录操作坐标。

type Rect

type Rect struct {
	X      float64 `json:"x"`
	Y      float64 `json:"y"`
	Width  float64 `json:"width"`
	Height float64 `json:"height"`
}

Rect 表示元素矩形区域。

type RippleType

type RippleType string

RippleType is the type of ripple animation. Deprecated.

const (
	// RippleTypeClick indicates a click ripple.
	RippleTypeClick RippleType = "click"
	// RippleTypeDrag indicates a drag ripple.
	RippleTypeDrag RippleType = "drag"
	// RippleTypeKey indicates a keyboard ripple.
	RippleTypeKey RippleType = "key"
)

type ScreencastFrame

type ScreencastFrame struct {
	Data      []byte // JPEG 字节
	Timestamp int64  // Unix ms
	SessionID int64  // CDP SessionID(用于 ACK)
	TargetID  string // Chrome TargetID,用于前端确认首帧归属
}

ScreencastFrame 是 Screencast 帧(LiveView 推送)。

type SelectorType

type SelectorType int

SelectorType 语义选择器类型。

const (
	SelectorSessionRef    SelectorType = iota // @rN — session ref
	SelectorTestID                            // #testid
	SelectorCanonical                         // role=button[name*="x"][nth=3]
	SelectorScoped                            // A >> B
	SelectorRoleName                          // role:'name' (shorthand, contains)
	SelectorRoleNameExact                     // role="name" (shorthand, exact)
	SelectorRole                              // role (bare role, first match)
	SelectorCSS                               // css=... or fallback
	SelectorLegacyRef                         // e{N} — rejected
)

type SessionAuthority

type SessionAuthority struct {
	// contains filtered or unexported fields
}

SessionAuthority 管理 SessionState 并 fan-out 广播给所有 WS subscriber。

设计要点:

  • 1-slot channel 保新丢旧(与 FrameBroadcastHub 保持一致)
  • seq 原子递增,确保 subscriber 能检测到跳变

func NewSessionAuthority

func NewSessionAuthority(viewportW, viewportH int) *SessionAuthority

NewSessionAuthority 创建 SessionAuthority 实例,初始模式为 "idle"。

func (*SessionAuthority) GetState

func (a *SessionAuthority) GetState() SessionState

GetState 返回当前状态快照(深拷贝,避免调用方修改内部状态)。

func (*SessionAuthority) Subscribe

func (a *SessionAuthority) Subscribe(connID string) chan *SessionState

Subscribe 为 connID 注册 1-slot 状态 channel,返回 channel 本身(用于 Unsubscribe 匹配)。 重复注册同一 connID 会先关闭旧 channel 再创建新的(幂等注册)。

func (*SessionAuthority) Unsubscribe

func (a *SessionAuthority) Unsubscribe(connID string, ch chan *SessionState)

Unsubscribe 用 channel 指针做代次匹配,只关闭与传入 ch 相同的 channel。 若 connID 已被新连接替换(ch 不匹配),静默忽略,不影响新连接。 这是 SEV-H2 的关键修复:防止旧连接的 defer 关掉新连接的 channel。

func (*SessionAuthority) UpdateCursor

func (a *SessionAuthority) UpdateCursor(cursor string)

UpdateCursor 更新远端 cursor 样式 → seq++ → 广播。

func (*SessionAuthority) UpdateMode

func (a *SessionAuthority) UpdateMode(mode, owner, leaseToken string, leaseExpiry int64)

UpdateMode 更新模式(owner / leaseToken / leaseExpiry)→ seq++ → 广播。

func (*SessionAuthority) UpdateNavigation

func (a *SessionAuthority) UpdateNavigation(url, title string)

UpdateNavigation 更新 URL/Title → seq++ → 广播。

func (*SessionAuthority) UpdateRecording

func (a *SessionAuthority) UpdateRecording(recording bool, elapsedMs int64)

UpdateRecording 更新录制状态 → seq++ → 广播 [CAP-BS09-C3 §6.bis]。

func (*SessionAuthority) UpdateTargetInfo

func (a *SessionAuthority) UpdateTargetInfo(url, title string, targetCount int, tabs ...[]TabInfo)

UpdateTargetInfo 更新 Target 信息(URL/Title/Count/Tabs)→ seq++ → 广播 [r3, TH-0414-b3m]。

func (*SessionAuthority) UpdateViewport

func (a *SessionAuthority) UpdateViewport(cssW, cssH int)

UpdateViewport 更新 viewport 尺寸 → seq++ → 广播。

type SessionCore

type SessionCore interface {
	BrowserCore
	// SnapWithSessionMode 获取快照(session 模式,使用 @rN ref)。
	SnapWithSessionMode(ctx context.Context, snapEpoch int) (*Snapshot, error)
	// SnapWithOptions 获取快照并应用 SnapOptions 过滤(selector/compact/max-depth)[r2]。
	SnapWithOptions(ctx context.Context, opts SnapOptions) (*Snapshot, error)
	// ActWithSessionMode 执行操作(session 模式,允许 @rN ref)。
	ActWithSessionMode(ctx context.Context, action string, observe bool) (*Snapshot, error)
	// RestoreRefsFromSession 从 session 文件恢复 ref 表。
	RestoreRefsFromSession(refs []SessionRef)
}

SessionCore — BrowserCore 的 session 扩展接口(含 @rN ref 支持)。

func NewBrowserCoreFromSession

func NewBrowserCoreFromSession(ctx context.Context, wsURL string, targetID string, presetID string, modes ...BrowserMode) (SessionCore, error)

NewBrowserCoreFromSession 连接到已有 Chrome 实例(通过 CDP WebSocket URL)。 用于 session 模式:open 后 snap/act/get/wait 都连接到同一个 Chrome 进程。

type SessionInfo

type SessionInfo struct {
	SessionID        string             `json:"session_id"`
	BrowserSessionID string             `json:"browser_session_id,omitempty"`
	SessionKind      BrowserSessionKind `json:"session_kind,omitempty"`
	Goal             string             `json:"goal,omitempty"`
	Owner            string             `json:"owner,omitempty"`
	AuthorityState   string             `json:"authority_state,omitempty"`
	ProfileID        string             `json:"profile_id,omitempty"`
	Isolation        string             `json:"isolation,omitempty"`
	ServiceName      string             `json:"service,omitempty"`
	AccountID        string             `json:"account_id,omitempty"`
	ChromePID        int                `json:"chrome_pid"`
	WSURL            string             `json:"ws_url"` // CDP WebSocket URL
	DebugPort        int                `json:"debug_port"`
	TargetID         string             `json:"target_id"` // CDP target ID for the active page
	Mode             BrowserMode        `json:"mode,omitempty"`
	PresetID         string             `json:"preset_id,omitempty"`
	ProfileDir       string             `json:"profile_dir"`
	PageURL          string             `json:"page_url"`
	CreatedAt        string             `json:"created_at"`
	ViewportW        int                `json:"viewport_w"`
	ViewportH        int                `json:"viewport_h"`
	UserAgent        string             `json:"user_agent"`
	Touch            bool               `json:"touch"`
	SnapEpoch        int                `json:"snap_epoch"` // Incremented on each snap
	Refs             []SessionRef       `json:"refs"`       // Ref table from last snap
	Ephemeral        bool               `json:"ephemeral"`  // true if --ephemeral was used
	XvfbPID          int                `json:"xvfb_pid"`   // Xvfb process PID (headed mode, Linux)

	BrowserMuxHostID      string `json:"browser_mux_host_id,omitempty"`
	BrowserMuxHostPID     int    `json:"browser_mux_host_pid,omitempty"`
	RuntimeID             string `json:"runtime_id,omitempty"`
	BrowserRunID          string `json:"browser_run_id,omitempty"`
	DisplayBackend        string `json:"display_backend,omitempty"`
	DisplayID             uint32 `json:"display_id,omitempty"`
	DisplayVerified       bool   `json:"display_verified,omitempty"`
	ChromeWindowContained bool   `json:"chrome_window_contained,omitempty"`

	Engine     BrowserEngine `json:"engine,omitempty"`      // 空值 = chrome(向后兼容)
	DeviceUDID string        `json:"device_udid,omitempty"` // Safari Simulator UDID
	DeviceName string        `json:"device_name,omitempty"` // Safari 设备名
}

SessionInfo Chrome セッション情報(ファイルに永続化)。

func ListSessions

func ListSessions() ([]SessionInfo, error)

ListSessions 全アクティブセッションの一覧を返す。

func LoadSession

func LoadSession(sessionID string) (*SessionInfo, error)

LoadSession セッションファイルを読み込む。

type SessionRef

type SessionRef struct {
	Ref           string      `json:"ref"` // "@r1", "@r2", ...
	BackendNodeID int64       `json:"backend_node_id"`
	Role          string      `json:"role"`
	Name          string      `json:"name"`
	TestID        string      `json:"testid,omitempty"`
	Placeholder   string      `json:"placeholder,omitempty"`
	Locator       NodeLocator `json:"locator,omitempty"`
	AXPath        string      `json:"ax_path,omitempty"`
	StableKey     string      `json:"stable_key,omitempty"`
}

SessionRef セッション内の要素 ref エントリ。

type SessionState

type SessionState struct {
	Mode        string `json:"mode"`        // "observe" / "takeover" / "idle" / "loading"
	Owner       string `json:"owner"`       // 当前 takeover 持有者 connID
	LeaseToken  string `json:"leaseToken"`  // owner 的租约令牌
	LeaseExpiry int64  `json:"leaseExpiry"` // 租约过期时间戳(毫秒)
	Viewport    struct {
		CssW int `json:"cssW"`
		CssH int `json:"cssH"`
	} `json:"viewport"`
	Cursor          string    `json:"cursor"`                      // 远端 cursor 样式
	URL             string    `json:"url"`                         // 当前页面 URL
	Title           string    `json:"title"`                       // 当前页面标题
	Seq             int64     `json:"seq"`                         // 单调递增序列号(原子递增)
	TargetCount     int       `json:"targetCount,omitempty"`       // 已知 page Target 数量 [r3]
	Tabs            []TabInfo `json:"tabs,omitempty"`              // 完整 tab 列表 [TH-0414-b3m]
	Recording       bool      `json:"recording,omitempty"`         // 当前是否正在录制 [CAP-BS09-C3 §6.bis]
	RecordElapsedMs int64     `json:"record_elapsed_ms,omitempty"` // 录制已用时间(毫秒)
}

SessionState 是 BrowserSession 的权威状态。 由 SessionAuthority 持有并管理,通过 channel fan-out 广播给所有 WS subscriber。

type SiteDataClearResult

type SiteDataClearResult struct {
	Origin       string `json:"origin"`
	Cleared      bool   `json:"cleared"`
	StorageTypes string `json:"storage_types"`
}

type SiteDataInfo

type SiteDataInfo struct {
	URL                string `json:"url"`
	Origin             string `json:"origin"`
	Host               string `json:"host"`
	Protocol           string `json:"protocol"`
	Secure             bool   `json:"secure"`
	CookieCount        int    `json:"cookie_count"`
	CookieBytes        int64  `json:"cookie_bytes"`
	LocalStorageKeys   int    `json:"local_storage_keys"`
	SessionStorageKeys int    `json:"session_storage_keys"`
	Clearable          bool   `json:"clearable"`
	UnsupportedReason  string `json:"unsupported_reason,omitempty"`
}

SiteDataInfo describes storage tied to the active page origin. It is intentionally origin-scoped so Browser Portal can clear the current site's state without touching other profiles or unrelated domains.

type SnapOptions

type SnapOptions struct {
	// Selector: CSS 选择器,仅返回该元素的后代节点 (SC-20)。空字符串=全页面。
	Selector string
	// Compact: 仅保留可交互 role 的元素 (SC-21)。
	Compact bool
	// MaxDepth: A11y 树 DFS 最大深度 (SC-21),0=不限。
	MaxDepth int
	// SessionMode: true 时使用 @rN ref(session 模式)。
	SessionMode bool
	// SnapEpoch: session 模式快照序号(仅 SessionMode=true 时有效)。
	SnapEpoch int
}

SnapOptions 控制快照过滤和格式化行为。

type Snapshot

type Snapshot struct {
	PageTitle        string
	URL              string
	TargetID         string       // CDP target.ID that produced this snapshot, when known
	Text             string       // compact A11y 文本(含 Element Refs)
	Refs             []ElementRef // 可交互元素列表(DFS 顺序)
	SnapshotType     string       // "a11y" | "dom_fallback" | "screenshot_fallback" | "progressive_loading"
	TokenEst         int          // 估算 token 数
	Progressive      bool         // true 表示页面可观测但暂不可稳定操作,调用方应稍后重试 action view
	LoadState        string       // "actionable" | "readable" | "visual" | "loading" | "waiting_for_app" | "unavailable"
	ReadyState       string       // document.readyState: "loading" | "interactive" | "complete" | ""
	RetryAfterMillis int          // 建议调用方下一次刷新 action view 的最小等待时间
	ProgressReason   string       // 渐进状态原因,面向日志和 CLI 输出
	Diagnostics      map[string]interface{}
}

Snapshot 是页面语义快照(不可变)。

type StableKey

type StableKey struct {
	ID        string
	AriaLabel string
	Name      string
	Role      string
	Href      string
	Type      string
}

StableKey is the stable key descriptor for a SuperNode. Deprecated: kept for compat.

type SuperNode

type SuperNode struct {
	ID        int
	TagName   string
	Text      string
	Clickable bool
	Box       Box
	StableKey StableKey
}

SuperNode is a stable node descriptor from the old browser architecture. Deprecated: kept for compat with internal/desktop.

type TabHandle

type TabHandle struct {
	TargetID    string      // CDP TargetID
	IdentityKey IdentityKey // 来自 IdentityRegistry
	WorkspaceID string      // 任务隔离 (跨 ws 同 identity 复用 Chrome 但 Tab 隔离)
	Role        TabRole
	SessionID   string    // BrowserSession 三层架构 SessionID (CAP-C3); Phase_v2_2 暂为空串
	AcquiredAt  time.Time // Pool.AcquireTab 创建时戳 (诊断用)
}

TabHandle — Tab 四元身份 (CAP §2.bis lines 127-133).

不变式:

  • TargetID 唯一 (TabIndex 注册时校验, 重复 → ErrTabAlreadyRegistered).
  • IdentityKey 来自 IdentityRegistry.Resolve (上层拿到后透传, 不修改).
  • WorkspaceID 任务隔离维度 — 不影响 Chrome 资源边界 (T5 §0.3).
  • Role 必须 IsValidRole(r) == true (Pool 入口校验).

type TabIndex

type TabIndex interface {
	// Register 注册一个 Tab. 同 TargetID 第二次 Register → ErrTabAlreadyRegistered.
	Register(handle *TabHandle) error

	// Lookup 反查 TargetID 对应的 TabHandle. 不存在 → (nil, false).
	Lookup(targetID string) (*TabHandle, bool)

	// Unregister 注销 Tab. 不存在的 TargetID → ErrTabNotFound.
	Unregister(targetID string) error

	// ByIdentity 列出某 identity 下所有 Tab (按 TargetID 字典序; 副本).
	ByIdentity(key IdentityKey) []TabHandle

	// ByWorkspace 列出某 ws 下所有 Tab (按 TargetID 字典序; 副本).
	ByWorkspace(ws string) []TabHandle

	// Snapshot 全量索引快照 (按 IdentityKey 字典序 → 内层按 TargetID 字典序).
	// 不含 ChromePID (Pool 注入); IdentityPoolStatus.ChromePID 在 Pool.Inspect 阶段填充.
	Snapshot() []IdentityPoolStatus

	// Len 返回当前注册的 Tab 总数 (诊断用).
	Len() int
}

TabIndex — TargetID → *TabHandle 索引 (Pool 内部依赖, 业务不直接持有).

线程安全: 实现必须支持并发 Register / Lookup / Unregister / ByIdentity / ByWorkspace.

func NewTabIndex

func NewTabIndex() TabIndex

NewTabIndex 返回一个新的内存 TabIndex.

type TabInfo

type TabInfo struct {
	ID       string `json:"id"` // CDP target.ID
	Title    string `json:"title"`
	URL      string `json:"url"`
	Active   bool   `json:"active"`   // 当前 screencast 是否指向此 tab
	Closable bool   `json:"closable"` // false = browser root target, cannot be closed through tab UI
}

TabInfo 描述一个浏览器标签页的元数据 [TH-0414-b3m]。

type TabRole

type TabRole string

TabRole — Tab 角色枚举 (4 枚举之一, T5 §0.3 + D11 强约束).

不变式 (D11):

  • RoleHuman Tab: Agent ActionEngine 直调必须被 InputGateway 拒绝.
  • RoleAgent Tab: Human 可观察, 不可接管 (除 D10 受控 override).
  • RoleCouncil Tab: 必须配合 Profile 前缀 "council-*" (Pool 校验, TC-09-U-42 P1).
  • RoleBackground Tab: SyncManager 等长跑路径; 无 LiveView, 不接受 takeover.
const (
	RoleHuman      TabRole = "human"      // Human only — Agent 不可直调
	RoleAgent      TabRole = "agent"      // Agent only — Human 可观察, 不可接管 (除受控 override)
	RoleCouncil    TabRole = "council"    // Council Agent (per platform 限定, 必须配合 council-* identity)
	RoleBackground TabRole = "background" // SyncManager 长跑无 LiveView
)

type TakeoverMode

type TakeoverMode string

TakeoverMode 是接管模式枚举。

const (
	// TakeoverModeObserve — AI 控制,Human 监视。
	TakeoverModeObserve TakeoverMode = "observe"
	// TakeoverModeTakeover — Human 接管,AI 操作被拒绝。
	TakeoverModeTakeover TakeoverMode = "takeover"
)

type TargetTracker

type TargetTracker struct {
	// contains filtered or unexported fields
}

TargetTracker 管理所有已知的 page Target,自动跟随活跃 Target 的 Screencast。

func NewTargetTracker

func NewTargetTracker(browserCtx context.Context) *TargetTracker

NewTargetTracker 创建 TargetTracker。initialID 是 Chrome 启动时的第一个 tab。

func (*TargetTracker) ActiveTargetID

func (tt *TargetTracker) ActiveTargetID() target.ID

ActiveTargetID 返回当前活跃 Target ID。

func (*TargetTracker) ActiveTargetRef

func (tt *TargetTracker) ActiveTargetRef() (string, context.Context)

ActiveTargetRef returns the current target id and its CDP context atomically. Async operations must keep this id with their result; reading ActiveTargetID on completion can update the wrong tab if the user switched while the work ran.

func (*TargetTracker) CloseTarget

func (tt *TargetTracker) CloseTarget(targetID string) error

CloseTarget 关闭指定 Target [TH-0414-b3m]。 不能关闭不可关闭 Target。关闭后 TargetTracker 的 HandleTargetDestroyed 会自动处理 fallback。

func (*TargetTracker) CreateTab

func (tt *TargetTracker) CreateTab(url string) (string, error)

CreateTab 创建新标签页并切换 Screencast 到新 tab。 url 为空时默认 ChromeInitialPageURL。该 URL 只表示 Chrome 初始化页, 不能被上层当作用户导航意图。 返回新 Target ID。createTarget 产生的 tab 没有 OpenerID, HandleTargetCreated 不会自动切换 Screencast,因此显式调用 SwitchToTarget。

func (*TargetTracker) GetActiveCDPContext

func (tt *TargetTracker) GetActiveCDPContext() context.Context

GetActiveCDPContext 返回当前活跃 Target 的 CDP context。 activeID 有值时返回该 Target 的 chromedp context;否则返回 browserCtx(primary target)。

func (*TargetTracker) HandleTargetCreated

func (tt *TargetTracker) HandleTargetCreated(info *target.Info)

HandleTargetCreated 处理新 Target 创建事件。 仅跟踪 type=page 的 Target。自动跟随必须通过归属协议判定: 当前 source target 的 OpenerID、Page.windowOpen(userGesture=true) 或 dispatch 成功后的本地 gesture claim。

func (*TargetTracker) HandleTargetDestroyed

func (tt *TargetTracker) HandleTargetDestroyed(targetID target.ID)

HandleTargetDestroyed 处理 Target 关闭事件。活跃 Target 关闭时回退到上一个。

func (*TargetTracker) HandleTargetInfoChanged

func (tt *TargetTracker) HandleTargetInfoChanged(info *target.Info)

HandleTargetInfoChanged 更新 Target 元数据(URL/Title 变化)。 关键: 处理 window.open(”) pendingSwitch 模式 — 空 URL 新标签拿到真实 URL 后立即切换 Screencast。

func (*TargetTracker) ListTargets

func (tt *TargetTracker) ListTargets() []TabInfo

ListTargets 返回当前所有已知 page Target 的元数据列表 [TH-0414-b3m]。 返回值中 Active 标记当前 screencast 指向的 Target。

func (*TargetTracker) RecordUserGesture

func (tt *TargetTracker) RecordUserGesture(event *InputEvent)

RecordUserGesture 记录一次已成功 dispatch 的 takeover 用户手势。 它是 opener-less target 归属的弱证据兜底,只在没有 opener/windowOpen 证据时使用。

func (*TargetTracker) RefreshTargets

func (tt *TargetTracker) RefreshTargets() error

RefreshTargets reconciles TargetTracker state from Chrome's authoritative target list. It repairs missed TargetCreated/Destroyed events and is safe to call from REST handlers.

func (*TargetTracker) SetForegroundGuard

func (tt *TargetTracker) SetForegroundGuard(fn func(target.ID, string) error)

SetForegroundGuard registers a fail-closed safety check before a target is activated or brought to front. macOS headed mode uses this to prove Chrome is still contained by CGVirtualDisplay before any foregrounding CDP command.

func (*TargetTracker) SetLiveEngine

func (tt *TargetTracker) SetLiveEngine(engine *liveViewEngine, hub *FrameBroadcastHub)

SetLiveEngine 注入 liveViewEngine 和 hub(在 StartLiveView 后调用)。

func (*TargetTracker) SetOnCDPSwitch

func (tt *TargetTracker) SetOnCDPSwitch(fn func(newCtx context.Context))

SetOnCDPSwitch 注册 CDP context 切换回调。 每次 Screencast 切换到新 Target 时调用,newCtx 是新活跃 Target 的 chromedp context。 用途: 通知 InputGateway 更新 cdpCtx,确保输入事件分发到正确的 Target(修复 popup/new-tab 导航卡住)。

func (*TargetTracker) SetOnJavaScriptDialog

func (tt *TargetTracker) SetOnJavaScriptDialog(fn func(JavaScriptDialogEvent))

SetOnJavaScriptDialog registers the LiveView bridge for alert/confirm/prompt.

func (*TargetTracker) SetOnSwitch

func (tt *TargetTracker) SetOnSwitch(fn func(url, title string, targetCount int))

SetOnSwitch 注册 Target 切换回调。

func (*TargetTracker) SetTargetCloser

func (tt *TargetTracker) SetTargetCloser(fn func(target.ID) error)

func (*TargetTracker) SetupListeners

func (tt *TargetTracker) SetupListeners(browserCtx context.Context)

SetupListeners 注册两类监听:

  • browserCtx: Browser 级 TargetCreated/Destroyed/InfoChanged + discover/auto-attach
  • tt.browserCtx: 当前 liveview primary target 的 Page.windowOpen/source-page 监听

关键约束:

  • Browser 级 Target 事件必须绑定 browserCtx(entry.browserCtx),否则新 tab 可能漏收
  • source page 的 windowOpen 证据必须绑定 primary target ctx(tt.browserCtx/tabCtx), 不能错误绑到 browserCtx,否则 Baidu 这类 opener-less page-opened target 会失去归属证据, 只能在 RefreshTargets() 时被动补登记

func (*TargetTracker) SwitchToTarget

func (tt *TargetTracker) SwitchToTarget(targetID string) error

SwitchToTarget 手动切换 Screencast 到指定 Target [TH-0414-b3m]。 终局约束: Browser LiveView 的 tab 切换必须同步切换真实 Chrome 活跃 tab。 headed Chrome 的后台 tab 不稳定产帧,且 Input.dispatch* 在后台标签上不可依赖; 因此 Target.activateTarget 是语义的一部分。窗口隔离由 DisplayStrategy/CGVirtualDisplay 负责。

func (*TargetTracker) TargetCDPContext

func (tt *TargetTracker) TargetCDPContext(targetID string) context.Context

func (*TargetTracker) TargetCount

func (tt *TargetTracker) TargetCount() int

TargetCount 返回已知 page Target 数量。

func (*TargetTracker) UpdateTabInfo

func (tt *TargetTracker) UpdateTabInfo(targetID string, url, title string) bool

UpdateTabInfo updates one concrete target. Async operations must use this with the target id captured at operation start, so completion cannot write into a newly active tab.

type TouchPoint

type TouchPoint struct {
	X  float64 `json:"x"`
	Y  float64 `json:"y"`
	ID int64   `json:"id"` // 手指标识符
}

TouchPoint 代表一个触摸点(多指触控时区分)。

type Viewport

type Viewport struct {
	Width  int
	Height int
	DPR    float64
}

Viewport — 视口尺寸 + 设备像素比.

type VirtualDisplayManager

type VirtualDisplayManager struct{}

VirtualDisplayManager is a no-op stub on non-Darwin platforms.

On Linux, virtual display isolation is handled by Xvfb (:99) managed by DisplayManager. On Windows, the current strategy falls back to headless mode (CreateDesktop Win32 API is P2 scope — DELTA-BS09-THREE-MODE §8).

All methods are safe to call concurrently.

func (*VirtualDisplayManager) Close

func (vdm *VirtualDisplayManager) Close() error

Close is a no-op on non-Darwin platforms. Always returns nil.

func (*VirtualDisplayManager) CountWindowsOutsideDisplay

func (vdm *VirtualDisplayManager) CountWindowsOutsideDisplay(pid int) int

func (*VirtualDisplayManager) DisplayID

func (vdm *VirtualDisplayManager) DisplayID() uint32

DisplayID returns 0 on non-Darwin platforms.

func (*VirtualDisplayManager) Ensure

func (vdm *VirtualDisplayManager) Ensure() error

Ensure is a no-op on non-Darwin platforms. Always returns nil.

func (*VirtualDisplayManager) VerifyChromeContained

func (vdm *VirtualDisplayManager) VerifyChromeContained(pid int, timeout time.Duration) error

func (*VirtualDisplayManager) WindowPosition

func (vdm *VirtualDisplayManager) WindowPosition() (x, y int)

WindowPosition returns (0, 0) on non-Darwin platforms. Linux Chrome instances are positioned via DISPLAY=:99; absolute screen coordinates are managed by the window manager.

func (*VirtualDisplayManager) WindowPositionAt

func (vdm *VirtualDisplayManager) WindowPositionAt(offset int) (x, y int)

WindowPositionAt returns (offset, offset) — helper for tests.

type VirtualDisplayWindowRescueRecord

type VirtualDisplayWindowRescueRecord struct {
	WindowID     int     `json:"window_id"`
	PID          int     `json:"pid"`
	Owner        string  `json:"owner"`
	Title        string  `json:"title,omitempty"`
	X            int     `json:"x"`
	Y            int     `json:"y"`
	Width        int     `json:"width"`
	Height       int     `json:"height"`
	TargetX      int     `json:"target_x,omitempty"`
	TargetY      int     `json:"target_y,omitempty"`
	TargetWidth  int     `json:"target_width,omitempty"`
	TargetHeight int     `json:"target_height,omitempty"`
	Moved        bool    `json:"moved"`
	Reason       string  `json:"reason"`
	VirtualRatio float64 `json:"virtual_ratio"`
}

type VirtualDisplayWindowRescueResult

type VirtualDisplayWindowRescueResult struct {
	Platform             string                             `json:"platform"`
	DisplayID            uint32                             `json:"display_id,omitempty"`
	ProtectedBrowserPIDs []int                              `json:"protected_browser_pids,omitempty"`
	Scanned              int                                `json:"scanned"`
	Matched              int                                `json:"matched"`
	Moved                int                                `json:"moved"`
	Skipped              int                                `json:"skipped"`
	UnavailableReason    string                             `json:"unavailable_reason,omitempty"`
	Windows              []VirtualDisplayWindowRescueRecord `json:"windows"`
}

func RescueForeignWindowsFromVirtualDisplay

func RescueForeignWindowsFromVirtualDisplay() (*VirtualDisplayWindowRescueResult, error)

type Workspace

type Workspace interface {
	// EnsureSpace ensures an isolated workspace exists and returns its ID.
	// On macOS: returns a non-current Space ID (Human must have ≥2 Spaces).
	// On Linux/Windows: returns (0, nil) — no Space concept needed.
	// Idempotent.
	EnsureSpace() (int64, error)

	// LaunchChromeInSpace forks Chrome inside the isolated workspace,
	// waits for CDP ready (with postcondition window-bound check on macOS),
	// and returns a ChromeHandle owning the process.
	//
	// On error, no process is leaked (the partial fork is killed before return).
	LaunchChromeInSpace(spec ChromeLaunchSpec) (ChromeHandle, error)

	// Close releases workspace resources (does NOT destroy user Spaces and
	// does NOT kill outstanding ChromeHandles — caller owns those).
	Close() error
}

Workspace manages an isolated OS workspace (Space/VDesktop/Xvfb) for **visible** Chrome instances. Headless Chrome does not need this and MUST NOT go through this interface.

func NewWorkspace

func NewWorkspace() Workspace

NewWorkspace returns a no-op Workspace for Linux. Actual isolation is provided by DisplayManager.ensureDisplayLinux (Xvfb).

Directories

Path Synopsis
compat.go provides backward-compatible stubs for types used by internal/desktop and internal/skill that were in the old browser package.
compat.go provides backward-compatible stubs for types used by internal/desktop and internal/skill that were in the old browser package.
Package audit 提供引擎无关的浏览器审计框架。
Package audit 提供引擎无关的浏览器审计框架。
Package safari wraps the ax-bridge.swift native binary which reads iOS Simulator accessibility trees via the macOS AXUIElement API.
Package safari wraps the ax-bridge.swift native binary which reads iOS Simulator accessibility trees via the macOS AXUIElement API.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL