From 8a116ca78dd029f4e4e095b3e617ec81ac276ba3 Mon Sep 17 00:00:00 2001 From: Snider Date: Thu, 16 Apr 2026 00:19:04 +0100 Subject: [PATCH] fix(console): bound exception retention Co-Authored-By: Virgil --- console.go | 16 ++++++++++++++++ console_test.go | 31 +++++++++++++++++++++++++++++++ specs/RFC.md | 3 ++- 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/console.go b/console.go index 5437172..713ee48 100644 --- a/console.go +++ b/console.go @@ -547,6 +547,7 @@ type ExceptionWatcher struct { mu sync.RWMutex wv *Webview exceptions []ExceptionInfo + limit int handlers []exceptionHandlerRegistration waiters []exceptionWaiter nextHandlerID atomic.Int64 @@ -569,6 +570,7 @@ func NewExceptionWatcher(wv *Webview) *ExceptionWatcher { ew := &ExceptionWatcher{ wv: wv, exceptions: make([]ExceptionInfo, 0), + limit: 1000, handlers: make([]exceptionHandlerRegistration, 0), } @@ -735,6 +737,7 @@ func (ew *ExceptionWatcher) handleException(params map[string]any) { ew.mu.Lock() ew.exceptions = append(ew.exceptions, info) + ew.exceptions = trimExceptionInfos(ew.exceptions, ew.limit) handlers := slices.Clone(ew.handlers) waiters := slices.Clone(ew.waiters) ew.mu.Unlock() @@ -775,6 +778,19 @@ func FormatConsoleOutput(messages []ConsoleMessage) string { return output.String() } +func trimExceptionInfos(exceptions []ExceptionInfo, limit int) []ExceptionInfo { + if limit < 0 { + limit = 0 + } + + if overflow := len(exceptions) - limit; overflow > 0 { + copy(exceptions, exceptions[overflow:]) + exceptions = exceptions[:len(exceptions)-overflow] + } + + return exceptions +} + func sanitizeConsoleText(text string) string { var b strings.Builder b.Grow(len(text)) diff --git a/console_test.go b/console_test.go index 3746d20..4891604 100644 --- a/console_test.go +++ b/console_test.go @@ -260,6 +260,9 @@ func TestConsole_NewExceptionWatcher_Good(t *testing.T) { if watcher.Count() != 0 { t.Fatalf("NewExceptionWatcher count = %d, want 0", watcher.Count()) } + if watcher.limit != 1000 { + t.Fatalf("NewExceptionWatcher limit = %d, want 1000", watcher.limit) + } } func TestConsole_NewExceptionWatcher_Good_SubscribesToClient(t *testing.T) { @@ -287,6 +290,34 @@ func TestConsole_NewExceptionWatcher_Good_SubscribesToClient(t *testing.T) { } } +func TestConsole_ExceptionWatcherTrimsOldExceptions_Good(t *testing.T) { + watcher := &ExceptionWatcher{ + exceptions: make([]ExceptionInfo, 0), + limit: 2, + handlers: make([]exceptionHandlerRegistration, 0), + } + + for i := range 3 { + watcher.handleException(map[string]any{ + "exceptionDetails": map[string]any{ + "text": string(rune('a' + i)), + "lineNumber": float64(i + 1), + "columnNumber": float64(1), + "url": "https://example.com/app.js", + }, + }) + } + + if got := watcher.Count(); got != 2 { + t.Fatalf("ExceptionWatcher count = %d, want 2", got) + } + + excs := watcher.Exceptions() + if len(excs) != 2 || excs[0].Text != "b" || excs[1].Text != "c" { + t.Fatalf("ExceptionWatcher retained %#v, want [b c]", excs) + } +} + func TestConsole_isWarningType_Good(t *testing.T) { tests := map[string]bool{ "warn": true, diff --git a/specs/RFC.md b/specs/RFC.md index 96be2e1..e6a78d9 100644 --- a/specs/RFC.md +++ b/specs/RFC.md @@ -219,6 +219,7 @@ Fields: Declaration: `type ExceptionWatcher struct` Collector for JavaScript exceptions emitted by the bound `Webview`. All fields are unexported. +New watchers start with an empty exception buffer and a default limit of 1000 stored exceptions. When the limit is exceeded, the oldest entries are trimmed on later writes. Methods: - `AddHandler(handler func(ExceptionInfo))`: Registers a callback for future exception events. @@ -487,7 +488,7 @@ Creates a `ConsoleWatcher`, initialises an empty message buffer with a 1000-mess ### NewExceptionWatcher `func NewExceptionWatcher(wv *Webview) *ExceptionWatcher` -Creates an `ExceptionWatcher`, initialises an empty exception buffer, and subscribes it to `Runtime.exceptionThrown` events on `wv.client`. +Creates an `ExceptionWatcher`, initialises an empty exception buffer with a 1000-exception limit, and subscribes it to `Runtime.exceptionThrown` events on `wv.client`. ### WithConsoleLimit `func WithConsoleLimit(limit int) Option`