feat(browser): auto-commit con 178 cambios

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-20 18:22:23 +02:00
parent 7d100e7f3e
commit 763e06c127
178 changed files with 19917 additions and 317 deletions
@@ -0,0 +1,66 @@
package browser
import "fmt"
// CdpNewTabBackground abre una pestaña nueva via Target.createTarget con el
// parametro "background": true, de forma que la pestaña se crea SIN activarse y
// SIN elevar la ventana del navegador (no roba el foco del WM).
//
// Es el drop-in sin-foco de CdpNewTab: misma firma, mismo CdpTab de retorno.
// La diferencia tecnica es el mecanismo:
// - CdpNewTab usa el endpoint HTTP PUT /json/new, que NO admite background y
// por tanto SIEMPRE eleva la ventana (roba foco al usuario).
// - Aqui usamos el comando CDP browser-level Target.createTarget con
// "background": true, que en Linux/Chromium crea la pestaña en segundo plano.
//
// host vacio = "localhost". startURL vacio = "about:blank".
func CdpNewTabBackground(host string, port int, startURL string) (CdpTab, error) {
if host == "" {
host = "localhost"
}
if startURL == "" {
startURL = "about:blank"
}
// Target.createTarget debe ejecutarse contra el browser target (no una page),
// por eso resolvemos el webSocketDebuggerUrl browser-level via /json/version.
wsURL, err := cdpGetWSURL(port)
if err != nil {
return CdpTab{}, fmt.Errorf("cdp new tab background: %w", err)
}
conn, err := cdpConnectWS(wsURL, port)
if err != nil {
return CdpTab{}, fmt.Errorf("cdp new tab background: conectar: %w", err)
}
// Soltar solo el WebSocket; dejar el navegador vivo.
defer CdpDisconnect(conn)
res, err := conn.sendCDP("Target.createTarget", map[string]any{
"url": startURL,
"background": true,
})
if err != nil {
return CdpTab{}, fmt.Errorf("cdp new tab background: createTarget: %w", err)
}
targetID, _ := res["targetId"].(string)
if targetID == "" {
return CdpTab{}, fmt.Errorf("cdp new tab background: createTarget no devolvio targetId")
}
// Resolver el CdpTab completo (con webSocketDebuggerUrl, title, etc.) buscando
// el target recien creado en /json.
tabs, err := CdpListTabs(host, port)
if err == nil {
for _, t := range tabs {
if t.ID == targetID {
return t, nil
}
}
}
// Fallback en caso de carrera (el target aun no aparece en /json): devolvemos
// un CdpTab minimo con el id, tipo y URL inicial conocidos.
return CdpTab{ID: targetID, Type: "page", URL: startURL}, nil
}