fix(turnstile): 浏览器 CDP 等待 / 关闭与 Windows 降权分裂修复
- --do-not-de-elevate:修复管理员父进程下 Chromium/Edge 自行降权导致的 窗口残留 / CDP 连不上 / 进程孤儿 - DevTools 等待窗口不再把启动器退出当失败;关闭走 CDP Browser.close 兜底
This commit is contained in:
1 parent
97a59a2dfb
commit
69743d53bd
1 file changed
+49
-5
@@ -938,6 +938,10 @@ func launchBrowser(ctx context.Context, exe string, cfg config.Turnstile, locale
|
||||
"--user-data-dir=" + profile,
|
||||
"--no-first-run",
|
||||
"--no-default-browser-check",
|
||||
// Chromium/Edge re-launch themselves de-elevated when the parent runs
|
||||
// as Administrator, orphaning the real browser and breaking CDP;
|
||||
// keep the browser inside our process tree instead.
|
||||
"--do-not-de-elevate",
|
||||
"--disable-blink-features=AutomationControlled",
|
||||
"--disable-background-networking",
|
||||
"--disable-sync",
|
||||
@@ -989,13 +993,12 @@ func (b *browserProc) waitDevTools(ctx context.Context) error {
|
||||
deadline := time.Now().Add(30 * time.Second)
|
||||
var lastErr error
|
||||
for time.Now().Before(deadline) {
|
||||
select {
|
||||
case <-b.exited:
|
||||
return fmt.Errorf("browser exited early (%v): %s", b.waitErr, b.stderr.String())
|
||||
case <-ctx.Done():
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
// A Windows browser launcher may hand off to a longer-lived process
|
||||
// and exit 0; that is not a failure while the DevTools endpoint is
|
||||
// still coming up, so the exit check happens only at timeout.
|
||||
resp, err := client.Get(endpoint)
|
||||
if err == nil {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
@@ -1015,10 +1018,17 @@ func (b *browserProc) waitDevTools(ctx context.Context) error {
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-b.exited:
|
||||
return fmt.Errorf("browser exited early (%v): %s", b.waitErr, b.stderr.String())
|
||||
default:
|
||||
}
|
||||
return fmt.Errorf("devtools never came up: %v; stderr: %s", lastErr, b.stderr.String())
|
||||
}
|
||||
|
||||
// Close kills the browser if it is still alive and drops the temp profile.
|
||||
// On Windows the spawned launcher may have exited while the real browser
|
||||
// lives on, so a DevTools close is attempted before giving up.
|
||||
func (b *browserProc) Close() {
|
||||
select {
|
||||
case <-b.exited:
|
||||
@@ -1031,6 +1041,7 @@ func (b *browserProc) Close() {
|
||||
case <-time.After(3 * time.Second):
|
||||
}
|
||||
}
|
||||
b.closeViaDevTools(2 * time.Second)
|
||||
if b.tempDir != "" {
|
||||
for i := 0; i < 3; i++ {
|
||||
if err := os.RemoveAll(b.tempDir); err == nil {
|
||||
@@ -1041,6 +1052,39 @@ func (b *browserProc) Close() {
|
||||
}
|
||||
}
|
||||
|
||||
// closeViaDevTools asks the browser to shut down over the DevTools endpoint.
|
||||
// It is the only handle left when the original launcher process has exited.
|
||||
func (b *browserProc) closeViaDevTools(timeout time.Duration) {
|
||||
ws := b.wsURL
|
||||
if ws == "" {
|
||||
client := &http.Client{Timeout: timeout}
|
||||
resp, err := client.Get(fmt.Sprintf("http://127.0.0.1:%d/json/version", b.port))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return
|
||||
}
|
||||
var info struct {
|
||||
WebSocketDebuggerURL string `json:"webSocketDebuggerUrl"`
|
||||
}
|
||||
if json.Unmarshal(body, &info) != nil || info.WebSocketDebuggerURL == "" {
|
||||
return
|
||||
}
|
||||
ws = info.WebSocketDebuggerURL
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
conn, err := dialCDP(ctx, ws, fmt.Sprintf("http://127.0.0.1:%d", b.port))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
_, _ = conn.call(ctx, "", "Browser.close", nil)
|
||||
}
|
||||
|
||||
func freePort() (int, error) {
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
|
||||
Reference in new issue
Block a user