package browser import ( "context" "encoding/json" "fmt" ) // EvaluateResult representa el resultado de una evaluación de JavaScript. type EvaluateResult struct { Type string `json:"type"` Value interface{} `json:"value"` Description string `json:"description"` ObjectID string `json:"objectId,omitempty"` SubType string `json:"subtype,omitempty"` RawResult json.RawMessage `json:"-"` } // Evaluate ejecuta código JavaScript en el contexto de la página. func (b *Browser) Evaluate(ctx context.Context, expression string) (*EvaluateResult, error) { params := map[string]interface{}{ "expression": expression, "returnByValue": true, "awaitPromise": true, "userGesture": true, } var response struct { Result struct { Type string `json:"type"` Value interface{} `json:"value"` Description string `json:"description"` ObjectID string `json:"objectId"` SubType string `json:"subtype"` } `json:"result"` ExceptionDetails *struct { Text string `json:"text"` Exception struct { Description string `json:"description"` } `json:"exception"` } `json:"exceptionDetails"` } if err := b.cdpClient.Execute(ctx, "Runtime.evaluate", params, &response); err != nil { return nil, fmt.Errorf("failed to evaluate: %w", err) } if response.ExceptionDetails != nil { return nil, fmt.Errorf("JavaScript exception: %s - %s", response.ExceptionDetails.Text, response.ExceptionDetails.Exception.Description) } result := &EvaluateResult{ Type: response.Result.Type, Value: response.Result.Value, Description: response.Result.Description, ObjectID: response.Result.ObjectID, SubType: response.Result.SubType, } return result, nil } // EvaluateOnNode ejecuta JavaScript en el contexto de un nodo específico. func (b *Browser) EvaluateOnNode(ctx context.Context, nodeID int64, expression string) (*EvaluateResult, error) { // Primero obtener el objectId del nodo var objResult struct { Object struct { ObjectID string `json:"objectId"` } `json:"object"` } params := map[string]interface{}{ "nodeId": nodeID, } if err := b.cdpClient.Execute(ctx, "DOM.resolveNode", params, &objResult); err != nil { return nil, fmt.Errorf("failed to resolve node: %w", err) } // Ejecutar función en el objeto callParams := map[string]interface{}{ "functionDeclaration": fmt.Sprintf("function() { return (%s); }", expression), "objectId": objResult.Object.ObjectID, "returnByValue": true, "awaitPromise": true, } var response struct { Result struct { Type string `json:"type"` Value interface{} `json:"value"` Description string `json:"description"` } `json:"result"` ExceptionDetails *struct { Text string `json:"text"` } `json:"exceptionDetails"` } if err := b.cdpClient.Execute(ctx, "Runtime.callFunctionOn", callParams, &response); err != nil { return nil, fmt.Errorf("failed to call function: %w", err) } if response.ExceptionDetails != nil { return nil, fmt.Errorf("JavaScript exception: %s", response.ExceptionDetails.Text) } return &EvaluateResult{ Type: response.Result.Type, Value: response.Result.Value, Description: response.Result.Description, }, nil } // EvaluateAsync ejecuta JavaScript de forma asíncrona (retorna Promise). func (b *Browser) EvaluateAsync(ctx context.Context, expression string) (*EvaluateResult, error) { params := map[string]interface{}{ "expression": expression, "returnByValue": true, "awaitPromise": true, "userGesture": true, } var response struct { Result struct { Type string `json:"type"` Value interface{} `json:"value"` Description string `json:"description"` ObjectID string `json:"objectId"` } `json:"result"` ExceptionDetails *struct { Text string `json:"text"` Exception struct { Description string `json:"description"` } `json:"exception"` } `json:"exceptionDetails"` } if err := b.cdpClient.Execute(ctx, "Runtime.evaluate", params, &response); err != nil { return nil, fmt.Errorf("failed to evaluate async: %w", err) } if response.ExceptionDetails != nil { return nil, fmt.Errorf("JavaScript exception: %s - %s", response.ExceptionDetails.Text, response.ExceptionDetails.Exception.Description) } return &EvaluateResult{ Type: response.Result.Type, Value: response.Result.Value, Description: response.Result.Description, ObjectID: response.Result.ObjectID, }, nil } // CallFunction ejecuta una función JavaScript con argumentos. func (b *Browser) CallFunction(ctx context.Context, functionDeclaration string, args ...interface{}) (*EvaluateResult, error) { // Convertir args a formato CDP cdpArgs := make([]map[string]interface{}, len(args)) for i, arg := range args { cdpArgs[i] = map[string]interface{}{ "value": arg, } } params := map[string]interface{}{ "functionDeclaration": functionDeclaration, "arguments": cdpArgs, "returnByValue": true, "awaitPromise": true, "userGesture": true, } var response struct { Result struct { Type string `json:"type"` Value interface{} `json:"value"` Description string `json:"description"` } `json:"result"` ExceptionDetails *struct { Text string `json:"text"` } `json:"exceptionDetails"` } if err := b.cdpClient.Execute(ctx, "Runtime.callFunctionOn", params, &response); err != nil { return nil, fmt.Errorf("failed to call function: %w", err) } if response.ExceptionDetails != nil { return nil, fmt.Errorf("JavaScript exception: %s", response.ExceptionDetails.Text) } return &EvaluateResult{ Type: response.Result.Type, Value: response.Result.Value, Description: response.Result.Description, }, nil } // GetProperty obtiene una propiedad de un objeto. func (b *Browser) GetProperty(ctx context.Context, objectID string, propertyName string) (*EvaluateResult, error) { params := map[string]interface{}{ "objectId": objectID, } var response struct { Result []struct { Name string `json:"name"` Value struct { Type string `json:"type"` Value interface{} `json:"value"` Description string `json:"description"` } `json:"value"` } `json:"result"` } if err := b.cdpClient.Execute(ctx, "Runtime.getProperties", params, &response); err != nil { return nil, fmt.Errorf("failed to get properties: %w", err) } for _, prop := range response.Result { if prop.Name == propertyName { return &EvaluateResult{ Type: prop.Value.Type, Value: prop.Value.Value, Description: prop.Value.Description, }, nil } } return nil, fmt.Errorf("property not found: %s", propertyName) } // AddBinding agrega un binding (función JS que llama a Go). type BindingCallback func(args []interface{}) interface{} // AddBinding expone una función Go al contexto JavaScript. func (b *Browser) AddBinding(ctx context.Context, name string, callback BindingCallback) error { // Agregar binding en Runtime params := map[string]interface{}{ "name": name, } if err := b.cdpClient.Execute(ctx, "Runtime.addBinding", params, nil); err != nil { return fmt.Errorf("failed to add binding: %w", err) } // Registrar evento para manejar llamadas b.cdpClient.On("Runtime.bindingCalled", func(eventParams json.RawMessage) { var event struct { Name string `json:"name"` Payload string `json:"payload"` ExecutionContextID int64 `json:"executionContextId"` } if err := json.Unmarshal(eventParams, &event); err != nil { return } if event.Name != name { return } // Parsear args var args []interface{} if err := json.Unmarshal([]byte(event.Payload), &args); err != nil { return } // Ejecutar callback result := callback(args) // Devolver resultado (evaluando código que lo retorna) returnScript := fmt.Sprintf("window.%s_result = %v", name, result) b.Evaluate(ctx, returnScript) }) // Inyectar wrapper en JavaScript wrapperScript := fmt.Sprintf(` window.%s = async (...args) => { const payload = JSON.stringify(args); window.%s_result = undefined; await window.chrome.runtime.sendMessage({ type: 'binding', name: '%s', payload: payload }); // Esperar resultado (polling simple) while (window.%s_result === undefined) { await new Promise(r => setTimeout(r, 10)); } return window.%s_result; }; `, name, name, name, name, name) _, err := b.Evaluate(ctx, wrapperScript) return err } // ConsoleMessage representa un mensaje de consola. type ConsoleMessage struct { Type string `json:"type"` Args []interface{} `json:"args"` Text string `json:"text"` URL string `json:"url"` Line int `json:"lineNumber"` Column int `json:"columnNumber"` } // OnConsole registra un handler para mensajes de consola. func (b *Browser) OnConsole(handler func(msg *ConsoleMessage)) { b.cdpClient.On("Runtime.consoleAPICalled", func(params json.RawMessage) { var event struct { Type string `json:"type"` Args []struct { Type string `json:"type"` Value interface{} `json:"value"` } `json:"args"` StackTrace struct { CallFrames []struct { URL string `json:"url"` LineNumber int `json:"lineNumber"` ColumnNumber int `json:"columnNumber"` } `json:"callFrames"` } `json:"stackTrace"` } if err := json.Unmarshal(params, &event); err != nil { return } msg := &ConsoleMessage{ Type: event.Type, Args: make([]interface{}, len(event.Args)), } // Construir texto del mensaje text := "" for i, arg := range event.Args { msg.Args[i] = arg.Value if i > 0 { text += " " } text += fmt.Sprintf("%v", arg.Value) } msg.Text = text // Agregar info de stack trace si existe if len(event.StackTrace.CallFrames) > 0 { frame := event.StackTrace.CallFrames[0] msg.URL = frame.URL msg.Line = frame.LineNumber msg.Column = frame.ColumnNumber } handler(msg) }) } // EnableConsole habilita eventos de consola. func (b *Browser) EnableConsole(ctx context.Context) error { return b.cdpClient.Execute(ctx, "Runtime.enable", nil, nil) } // QuerySelector helper para ejecutar querySelector desde JavaScript. func (b *Browser) QuerySelector(ctx context.Context, selector string) (*EvaluateResult, error) { script := fmt.Sprintf(`document.querySelector('%s')`, selector) return b.Evaluate(ctx, script) } // QuerySelectorAll ejecuta querySelectorAll y retorna array de elementos. func (b *Browser) QuerySelectorAll(ctx context.Context, selector string) (*EvaluateResult, error) { script := fmt.Sprintf(`Array.from(document.querySelectorAll('%s'))`, selector) return b.Evaluate(ctx, script) } // WaitForFunction espera a que una función JavaScript retorne true. func (b *Browser) WaitForFunction(ctx context.Context, function string, pollInterval int) error { script := fmt.Sprintf(` new Promise((resolve) => { const check = () => { if (%s) { resolve(true); } else { setTimeout(check, %d); } }; check(); }) `, function, pollInterval) _, err := b.EvaluateAsync(ctx, script) return err }