fix(console): bound exception retention
Some checks are pending
Security Scan / security (push) Waiting to run
Test / test (push) Waiting to run

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-04-16 00:19:04 +01:00
parent fd15f95ca9
commit 8a116ca78d
3 changed files with 49 additions and 1 deletions

View file

@ -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))

View file

@ -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,

View file

@ -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`