package browser import ( "fmt" ) // CdpFrame representa un frame/iframe del arbol de navegacion. type CdpFrame struct { ID string `json:"id"` ParentID string `json:"parentId"` URL string `json:"url"` Name string `json:"name"` } // CdpListFrames lista todos los frames de la pagina actual (frame raiz + iframes anidados) // usando Page.getFrameTree. Retorna el arbol aplanado con cada frame y su parentId. func CdpListFrames(c *CDPConn) ([]CdpFrame, error) { if c == nil { return nil, fmt.Errorf("cdp list frames: conexion nula") } // Page.enable es idempotente; necesario para que Page.getFrameTree funcione if _, err := c.sendCDP("Page.enable", nil); err != nil { return nil, fmt.Errorf("cdp list frames: Page.enable: %w", err) } result, err := c.sendCDP("Page.getFrameTree", nil) if err != nil { return nil, fmt.Errorf("cdp list frames: Page.getFrameTree: %w", err) } frameTree, ok := result["frameTree"].(map[string]any) if !ok { return nil, fmt.Errorf("cdp list frames: frameTree no encontrado en respuesta") } var frames []CdpFrame frameFlatten(frameTree, "", &frames) return frames, nil } // frameFlatten recorre recursivamente el arbol de frames CDP y acumula CdpFrame. // parentID es el ID del nodo padre; el frame raiz lo recibe vacio. func frameFlatten(node map[string]any, parentID string, acc *[]CdpFrame) { frameData, ok := node["frame"].(map[string]any) if !ok { return } f := CdpFrame{ ID: stringField(frameData, "id"), ParentID: parentID, URL: stringField(frameData, "url"), Name: stringField(frameData, "name"), } *acc = append(*acc, f) // Recorrer hijos children, _ := node["childFrames"].([]any) for _, child := range children { childNode, ok := child.(map[string]any) if !ok { continue } frameFlatten(childNode, f.ID, acc) } } // stringField extrae un campo string de un map[string]any de forma segura. func stringField(m map[string]any, key string) string { v, _ := m[key].(string) return v }