From 7a4450cf9f6417bfdfb9703d7d5dec2ff9c70ca9 Mon Sep 17 00:00:00 2001 From: Snider Date: Tue, 14 Apr 2026 15:09:57 +0100 Subject: [PATCH] feat(webview): console watcher additions + canonical type normalisation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spark draft + opus-style fixes during integration: - normalizeConsoleType applied consistently across FormatConsoleOutput + Errors/ErrorsAll + Warnings/WarningsAll iterators — previously raw "warning" inputs missed the "warn" comparisons silently - core.Trim instead of non-existent core.TrimSpace Spark feature work preserved (Errors/Warnings iterators, formatted console output, type normalisation helper). Co-Authored-By: Virgil --- console.go | 116 ++++++++++++++++++++++++++++++++++++++++++++--------- webview.go | 34 +++++++++------- 2 files changed, 117 insertions(+), 33 deletions(-) diff --git a/console.go b/console.go index f2ebfae..fc1c2e6 100644 --- a/console.go +++ b/console.go @@ -5,6 +5,7 @@ import ( "context" "iter" "slices" + "strings" "sync" "sync/atomic" "time" @@ -55,6 +56,93 @@ func NewConsoleWatcher(wv *Webview) *ConsoleWatcher { return cw } +// normalizeConsoleType converts CDP event types to package-level values. +func normalizeConsoleType(raw string) string { + normalized := strings.ToLower(core.Trim(core.Sprint(raw))) + switch normalized { + case "warning": + return "warn" + default: + return normalized + } +} + +// consoleTextFromArgs extracts message text from Runtime.consoleAPICalled args. +func consoleTextFromArgs(args []any) string { + text := core.NewBuilder() + for i, arg := range args { + if i > 0 { + text.WriteString(" ") + } + text.WriteString(consoleArgText(arg)) + } + + return text.String() +} + +func consoleArgText(arg any) string { + remoteObj, ok := arg.(map[string]any) + if !ok { + return consoleValueToString(arg) + } + + if value, ok := remoteObj["value"]; ok { + return consoleValueToString(value) + } + + if desc, ok := remoteObj["description"].(string); ok && desc != "" { + return desc + } + + if preview, ok := remoteObj["preview"].(map[string]any); ok { + if description, ok := preview["description"].(string); ok && description != "" { + return description + } + } + + if preview, ok := remoteObj["preview"].(map[string]any); ok { + if value, ok := preview["value"].(string); ok && value != "" { + return value + } + } + + if r := core.JSONMarshal(remoteObj); r.OK { + if encoded, ok := r.Value.([]byte); ok { + return string(encoded) + } + } + + return "" +} + +func consoleValueToString(value any) string { + if value == nil { + return "null" + } + if valueStr, ok := value.(string); ok { + return valueStr + } + + if r := core.JSONMarshal(value); r.OK { + if encoded, ok := r.Value.([]byte); ok { + return string(encoded) + } + } + + return core.Sprint(value) +} + +func consoleMessageTimestamp(params map[string]any) time.Time { + timestamp, ok := params["timestamp"].(float64) + if !ok { + return time.Now() + } + + seconds := int64(timestamp) + nanoseconds := int64((timestamp - float64(seconds)) * float64(time.Second)) + return time.Unix(seconds, nanoseconds).UTC() +} + // AddFilter adds a filter to the watcher. func (cw *ConsoleWatcher) AddFilter(filter ConsoleFilter) { cw.mu.Lock() @@ -156,7 +244,7 @@ func (cw *ConsoleWatcher) ErrorsAll() iter.Seq[ConsoleMessage] { defer cw.mu.RUnlock() for _, msg := range cw.messages { - if msg.Type == "error" { + if normalizeConsoleType(msg.Type) == "error" { if !yield(msg) { return } @@ -177,7 +265,7 @@ func (cw *ConsoleWatcher) WarningsAll() iter.Seq[ConsoleMessage] { defer cw.mu.RUnlock() for _, msg := range cw.messages { - if msg.Type == "warning" { + if normalizeConsoleType(msg.Type) == "warn" { if !yield(msg) { return } @@ -268,21 +356,11 @@ func (cw *ConsoleWatcher) ErrorCount() int { // handleConsoleEvent processes incoming console events. func (cw *ConsoleWatcher) handleConsoleEvent(params map[string]any) { - msgType, _ := params["type"].(string) + msgType := normalizeConsoleType(core.Sprint(params["type"])) // Extract args args, _ := params["args"].([]any) - text := core.NewBuilder() - for i, arg := range args { - if argMap, ok := arg.(map[string]any); ok { - if val, ok := argMap["value"]; ok { - if i > 0 { - text.WriteString(" ") - } - text.WriteString(core.Sprint(val)) - } - } - } + text := consoleTextFromArgs(args) // Extract stack trace info stackTrace, _ := params["stackTrace"].(map[string]any) @@ -300,8 +378,8 @@ func (cw *ConsoleWatcher) handleConsoleEvent(params map[string]any) { msg := ConsoleMessage{ Type: msgType, - Text: text.String(), - Timestamp: time.Now(), + Text: text, + Timestamp: consoleMessageTimestamp(params), URL: url, Line: line, Column: column, @@ -346,7 +424,7 @@ func (cw *ConsoleWatcher) matchesFilter(msg ConsoleMessage) bool { // matchesSingleFilter checks if a message matches a specific filter. func (cw *ConsoleWatcher) matchesSingleFilter(msg ConsoleMessage, filter ConsoleFilter) bool { - if filter.Type != "" && msg.Type != filter.Type { + if filter.Type != "" && msg.Type != normalizeConsoleType(filter.Type) { return false } if filter.Pattern != "" { @@ -572,10 +650,10 @@ func FormatConsoleOutput(messages []ConsoleMessage) string { output := core.NewBuilder() for _, msg := range messages { prefix := "" - switch msg.Type { + switch normalizeConsoleType(msg.Type) { case "error": prefix = "[ERROR]" - case "warning": + case "warn": prefix = "[WARN]" case "info": prefix = "[INFO]" diff --git a/webview.go b/webview.go index 47e5256..cc06cf9 100644 --- a/webview.go +++ b/webview.go @@ -363,6 +363,10 @@ func (wv *Webview) SetViewport(width, height int) error { "deviceScaleFactor": 1, "mobile": false, }) + if err != nil { + return coreerr.E("Webview.SetViewport", "failed to set viewport", err) + } + return err } @@ -374,6 +378,10 @@ func (wv *Webview) SetUserAgent(userAgent string) error { _, err := wv.client.Call(ctx, "Emulation.setUserAgentOverride", map[string]any{ "userAgent": userAgent, }) + if err != nil { + return coreerr.E("Webview.SetUserAgent", "failed to set user agent", err) + } + return err } @@ -398,6 +406,10 @@ func (wv *Webview) GoBack() error { _, err := wv.client.Call(ctx, "Page.goBackOrForward", map[string]any{ "delta": -1, }) + if err != nil { + return coreerr.E("Webview.GoBack", "failed to go back", err) + } + return err } @@ -409,6 +421,10 @@ func (wv *Webview) GoForward() error { _, err := wv.client.Call(ctx, "Page.goBackOrForward", map[string]any{ "delta": 1, }) + if err != nil { + return coreerr.E("Webview.GoForward", "failed to go forward", err) + } + return err } @@ -458,21 +474,11 @@ func (wv *Webview) enableConsole() error { // handleConsoleEvent processes console API events. func (wv *Webview) handleConsoleEvent(params map[string]any) { - msgType, _ := params["type"].(string) + msgType := normalizeConsoleType(core.Sprint(params["type"])) // Extract args args, _ := params["args"].([]any) - text := core.NewBuilder() - for i, arg := range args { - if argMap, ok := arg.(map[string]any); ok { - if val, ok := argMap["value"]; ok { - if i > 0 { - text.WriteString(" ") - } - text.WriteString(core.Sprint(val)) - } - } - } + text := consoleTextFromArgs(args) // Extract stack trace info stackTrace, _ := params["stackTrace"].(map[string]any) @@ -490,8 +496,8 @@ func (wv *Webview) handleConsoleEvent(params map[string]any) { wv.addConsoleMessage(ConsoleMessage{ Type: msgType, - Text: text.String(), - Timestamp: time.Now(), + Text: text, + Timestamp: consoleMessageTimestamp(params), URL: url, Line: line, Column: column,