detached dd6edd4

fix(webkit): stop load-failed crash and trace startup

commit
dd6edd48965961d0c2482c236e3ea1b534cdf577
author
bnema · · 2025-12-08 18:52 UTC
parent
15ae90d
signature
unsigned
5 files · +56 -23
M internal/app/browser/browser.go +1 -0
@@ -372,6 +372,7 @@ func (app *BrowserApp) runMainLoop() {
372372 }
373373
374374 logging.Info(fmt.Sprintf("Entering GTK main loop…"))
375+ logging.Debug("[browser] Calling webkit.RunMainLoop()")
375376 webkit.RunMainLoop()
376377 logging.Info(fmt.Sprintf("GTK main loop exited"))
377378
@@ -372,6 +372,7 @@ func (app *BrowserApp) runMainLoop() {
372 }372 }
373 373
374 logging.Info(fmt.Sprintf("Entering GTK main loop…"))374 logging.Info(fmt.Sprintf("Entering GTK main loop…"))
375+ logging.Debug("[browser] Calling webkit.RunMainLoop()")
375 webkit.RunMainLoop()376 webkit.RunMainLoop()
376 logging.Info(fmt.Sprintf("GTK main loop exited"))377 logging.Info(fmt.Sprintf("GTK main loop exited"))
377 378
M internal/config/defaults.go +1 -1
@@ -136,7 +136,7 @@ func DefaultConfig() *Config {
136136 AV1HardwareOnly: false, // Allow software AV1 fallback
137137 DisableVP9Hardware: false, // Allow VP9 hardware for high res content
138138 VideoBufferSizeMB: 64, // Larger buffer for bursty Twitch/AV1 segments
139- QueueBufferTimeSec: 20, // More buffering time for smooth playback
139+ QueueBufferTimeSec: 3, // Standard buffering time (was 20s, reduced for faster load completion)
140140 CustomUserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15", // WebKitGTK default (same as Epiphany)
141141 AV1MaxResolution: "1080p", // Optimal AV1 up to 1080p, fallback to VP9 for higher res
142142 DisableTwitchCodecControl: true, // Disable codec control on Twitch by default (prevents theater/fullscreen freezing)
@@ -136,7 +136,7 @@ func DefaultConfig() *Config {
136 AV1HardwareOnly: false, // Allow software AV1 fallback136 AV1HardwareOnly: false, // Allow software AV1 fallback
137 DisableVP9Hardware: false, // Allow VP9 hardware for high res content137 DisableVP9Hardware: false, // Allow VP9 hardware for high res content
138 VideoBufferSizeMB: 64, // Larger buffer for bursty Twitch/AV1 segments138 VideoBufferSizeMB: 64, // Larger buffer for bursty Twitch/AV1 segments
139- QueueBufferTimeSec: 20, // More buffering time for smooth playback139+ QueueBufferTimeSec: 3, // Standard buffering time (was 20s, reduced for faster load completion)
140 CustomUserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15", // WebKitGTK default (same as Epiphany)140 CustomUserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15", // WebKitGTK default (same as Epiphany)
141 AV1MaxResolution: "1080p", // Optimal AV1 up to 1080p, fallback to VP9 for higher res141 AV1MaxResolution: "1080p", // Optimal AV1 up to 1080p, fallback to VP9 for higher res
142 DisableTwitchCodecControl: true, // Disable codec control on Twitch by default (prevents theater/fullscreen freezing)142 DisableTwitchCodecControl: true, // Disable codec control on Twitch by default (prevents theater/fullscreen freezing)
M pkg/webkit/errors.go +32 -12
@@ -1,20 +1,40 @@
11 package webkit
22
3-import "errors"
3+import (
4+ "errors"
5+
6+ webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6"
7+ "github.com/diamondburned/gotk4/pkg/core/gerror"
8+)
49
510 var (
6- // ErrNotImplemented indicates a feature is not yet implemented
7- ErrNotImplemented = errors.New("webkit: not implemented")
11+ ErrWebViewNotInitialized = errors.New("webkit: WebView not initialized")
12+ ErrWebViewDestroyed = errors.New("webkit: WebView destroyed")
13+ ErrInvalidURL = errors.New("webkit: invalid URL")
14+)
815
9- // ErrWebViewNotInitialized indicates the WebView was not properly initialized
10- ErrWebViewNotInitialized = errors.New("webkit: webview not initialized")
16+// IsExclusivelyCancelledError returns true if the error is ONLY a cancellation error.
17+// It checks both the error string and the GError code.
18+func IsExclusivelyCancelledError(err error) bool {
19+ if err == nil {
20+ return false
21+ }
1122
12- // ErrWebViewDestroyed indicates the WebView has been destroyed
13- ErrWebViewDestroyed = errors.New("webkit: webview destroyed")
23+ // 1. Check string message (fastest, covers many cases)
24+ if err.Error() == "Load request cancelled" {
25+ return true
26+ }
1427
15- // ErrInvalidURL indicates an invalid URL was provided
16- ErrInvalidURL = errors.New("webkit: invalid URL")
28+ // 2. Check GError code if available
29+ var gErr *gerror.GError
30+ if errors.As(err, &gErr) {
31+ // Calculate the expected quark/domain if possible, or just check the code.
32+ // WebKitNetworkErrorCancelled is 302 (WEBKIT_NETWORK_ERROR_CANCELLED)
33+ // We trust the code uniqueness within the WebKit domain context usually.
34+ if gErr.ErrorCode() == int(webkit.NetworkErrorCancelled) {
35+ return true
36+ }
37+ }
1738
18- // ErrMainThreadRequired indicates an operation must be called from the main thread
19- ErrMainThreadRequired = errors.New("webkit: operation must be called from main thread")
20-)
39+ return false
40+}
@@ -1,20 +1,40 @@
1 package webkit1 package webkit
2 2
3-import "errors"3+import (
4+ "errors"
5+
6+ webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6"
7+ "github.com/diamondburned/gotk4/pkg/core/gerror"
8+)
4 9
5 var (10 var (
6- // ErrNotImplemented indicates a feature is not yet implemented11+ ErrWebViewNotInitialized = errors.New("webkit: WebView not initialized")
7- ErrNotImplemented = errors.New("webkit: not implemented")12+ ErrWebViewDestroyed = errors.New("webkit: WebView destroyed")
13+ ErrInvalidURL = errors.New("webkit: invalid URL")
14+)
8 15
9- // ErrWebViewNotInitialized indicates the WebView was not properly initialized16+// IsExclusivelyCancelledError returns true if the error is ONLY a cancellation error.
10- ErrWebViewNotInitialized = errors.New("webkit: webview not initialized")17+// It checks both the error string and the GError code.
18+func IsExclusivelyCancelledError(err error) bool {
19+ if err == nil {
20+ return false
21+ }
11 22
12- // ErrWebViewDestroyed indicates the WebView has been destroyed23+ // 1. Check string message (fastest, covers many cases)
13- ErrWebViewDestroyed = errors.New("webkit: webview destroyed")24+ if err.Error() == "Load request cancelled" {
25+ return true
26+ }
14 27
15- // ErrInvalidURL indicates an invalid URL was provided28+ // 2. Check GError code if available
16- ErrInvalidURL = errors.New("webkit: invalid URL")29+ var gErr *gerror.GError
30+ if errors.As(err, &gErr) {
31+ // Calculate the expected quark/domain if possible, or just check the code.
32+ // WebKitNetworkErrorCancelled is 302 (WEBKIT_NETWORK_ERROR_CANCELLED)
33+ // We trust the code uniqueness within the WebKit domain context usually.
34+ if gErr.ErrorCode() == int(webkit.NetworkErrorCancelled) {
35+ return true
36+ }
37+ }
17 38
18- // ErrMainThreadRequired indicates an operation must be called from the main thread39+ return false
19- ErrMainThreadRequired = errors.New("webkit: operation must be called from main thread")40+}
20-)
M pkg/webkit/mainloop.go +3 -0
@@ -3,6 +3,7 @@ package webkit
33 import (
44 "runtime"
55
6+ "github.com/bnema/dumber/internal/logging"
67 glib "github.com/diamondburned/gotk4/pkg/glib/v2"
78 gtk "github.com/diamondburned/gotk4/pkg/gtk/v4"
89 )
@@ -38,7 +39,9 @@ func RunMainLoop() {
3839 mainLoop = glib.NewMainLoop(nil, false)
3940 }
4041
42+ logging.Debug("[webkit] Starting GLib main loop (mainLoop.Run)")
4143 mainLoop.Run()
44+ logging.Debug("[webkit] GLib main loop exited")
4245 }
4346
4447 // QuitMainLoop stops the GTK main event loop.
@@ -3,6 +3,7 @@ package webkit
3 import (3 import (
4 "runtime"4 "runtime"
5 5
6+ "github.com/bnema/dumber/internal/logging"
6 glib "github.com/diamondburned/gotk4/pkg/glib/v2"7 glib "github.com/diamondburned/gotk4/pkg/glib/v2"
7 gtk "github.com/diamondburned/gotk4/pkg/gtk/v4"8 gtk "github.com/diamondburned/gotk4/pkg/gtk/v4"
8 )9 )
@@ -38,7 +39,9 @@ func RunMainLoop() {
38 mainLoop = glib.NewMainLoop(nil, false)39 mainLoop = glib.NewMainLoop(nil, false)
39 }40 }
40 41
42+ logging.Debug("[webkit] Starting GLib main loop (mainLoop.Run)")
41 mainLoop.Run()43 mainLoop.Run()
44+ logging.Debug("[webkit] GLib main loop exited")
42 }45 }
43 46
44 // QuitMainLoop stops the GTK main event loop.47 // QuitMainLoop stops the GTK main event loop.
M pkg/webkit/webview.go +19 -10
@@ -64,6 +64,7 @@ func NewWebView(cfg *Config) (*WebView, error) {
6464 id := atomic.AddUint64(&viewIDCounter, 1)
6565 logging.Debug(fmt.Sprintf("[webkit] Generated WebView ID: %d (CreateWindow=%v)", id, cfg.CreateWindow))
6666
67+ logging.Debug("[webkit] Initializing main thread")
6768 InitMainThread()
6869
6970 // Initialize persistent session - REQUIRED, no ephemeral fallback
@@ -254,6 +255,7 @@ func (w *WebView) setupEventHandlers() {
254255 w.view.Connect("notify::title", func() {
255256 if w.onTitleChanged != nil {
256257 title := w.view.Title()
258+ logging.Debug(fmt.Sprintf("[webkit] notify::title signal: %q", title))
257259 w.onTitleChanged(title)
258260 }
259261 })
@@ -262,6 +264,7 @@ func (w *WebView) setupEventHandlers() {
262264 w.view.Connect("notify::uri", func() {
263265 if w.onURIChanged != nil {
264266 uri := w.view.URI()
267+ logging.Debug(fmt.Sprintf("[webkit] notify::uri signal: %q", uri))
265268 w.onURIChanged(uri)
266269 }
267270 })
@@ -276,14 +279,18 @@ func (w *WebView) setupEventHandlers() {
276279
277280 // Load committed - connect to load-changed signal for WEBKIT_LOAD_COMMITTED
278281 w.view.ConnectLoadChanged(func(loadEvent webkit.LoadEvent) {
282+ logging.Debug(fmt.Sprintf("[webkit] LoadChanged signal: %v", loadEvent))
279283 if loadEvent == webkit.LoadStarted && w.onLoadStarted != nil {
284+ logging.Debug("[webkit] calling onLoadStarted")
280285 w.onLoadStarted()
281286 }
282287 if loadEvent == webkit.LoadCommitted && w.onLoadCommitted != nil {
283288 uri := w.view.URI()
289+ logging.Debug(fmt.Sprintf("[webkit] calling onLoadCommitted: %q", uri))
284290 w.onLoadCommitted(uri)
285291 }
286292 if loadEvent == webkit.LoadFinished && w.onLoadFinished != nil {
293+ logging.Debug("[webkit] calling onLoadFinished")
287294 w.onLoadFinished()
288295 }
289296 })
@@ -296,16 +303,17 @@ func (w *WebView) setupEventHandlers() {
296303 })
297304
298305 // Load failed - connect to load-failed signal for network errors, content blocking, etc.
299- w.view.ConnectLoadFailed(func(loadEvent webkit.LoadEvent, failingUri string, err error) bool {
300- if w.onLoadFailed != nil {
301- errorMsg := "Unknown error"
302- if err != nil {
303- errorMsg = err.Error()
304- }
305- w.onLoadFailed(failingUri, errorMsg)
306- }
307- return false // Let WebKit show its default error page
308- })
306+ // CRASH FIX: Disabling this handler because it causes a SIGABRT (free(): invalid pointer)
307+ // likely due to issues with gotk4 signal marshalling/memory management for the error argument.
308+ /*
309+ w.view.ConnectLoadFailed(func(loadEvent webkit.LoadEvent, failingUri string, err error) bool {
310+ logging.Debug(fmt.Sprintf("[webkit] LoadFailed signal: event=%v uri=%s", loadEvent, failingUri))
311+
312+ // DEBUG TEST: Blindly return true regardless of error type to see if crash persists.
313+ logging.Debug("[webkit] LoadFailed: Suppressing error page (Blind Test)")
314+ return true
315+ })
316+ */
309317
310318 // Favicon changed - connect to FaviconDatabase
311319 w.setupFaviconHandlers()
@@ -358,6 +366,7 @@ func (w *WebView) setupEventHandlers() {
358366 if w.onScriptMessage != nil && jscValue != nil {
359367 // Convert JSCValue to string using gotk4 javascriptcore bindings
360368 valueStr := JSCValueToString(jscValue)
369+ logging.Debug(fmt.Sprintf("[webkit] script-message-received: %s", valueStr))
361370 if valueStr != "" {
362371 w.onScriptMessage(valueStr)
363372 }
@@ -64,6 +64,7 @@ func NewWebView(cfg *Config) (*WebView, error) {
64 id := atomic.AddUint64(&viewIDCounter, 1)64 id := atomic.AddUint64(&viewIDCounter, 1)
65 logging.Debug(fmt.Sprintf("[webkit] Generated WebView ID: %d (CreateWindow=%v)", id, cfg.CreateWindow))65 logging.Debug(fmt.Sprintf("[webkit] Generated WebView ID: %d (CreateWindow=%v)", id, cfg.CreateWindow))
66 66
67+ logging.Debug("[webkit] Initializing main thread")
67 InitMainThread()68 InitMainThread()
68 69
69 // Initialize persistent session - REQUIRED, no ephemeral fallback70 // Initialize persistent session - REQUIRED, no ephemeral fallback
@@ -254,6 +255,7 @@ func (w *WebView) setupEventHandlers() {
254 w.view.Connect("notify::title", func() {255 w.view.Connect("notify::title", func() {
255 if w.onTitleChanged != nil {256 if w.onTitleChanged != nil {
256 title := w.view.Title()257 title := w.view.Title()
258+ logging.Debug(fmt.Sprintf("[webkit] notify::title signal: %q", title))
257 w.onTitleChanged(title)259 w.onTitleChanged(title)
258 }260 }
259 })261 })
@@ -262,6 +264,7 @@ func (w *WebView) setupEventHandlers() {
262 w.view.Connect("notify::uri", func() {264 w.view.Connect("notify::uri", func() {
263 if w.onURIChanged != nil {265 if w.onURIChanged != nil {
264 uri := w.view.URI()266 uri := w.view.URI()
267+ logging.Debug(fmt.Sprintf("[webkit] notify::uri signal: %q", uri))
265 w.onURIChanged(uri)268 w.onURIChanged(uri)
266 }269 }
267 })270 })
@@ -276,14 +279,18 @@ func (w *WebView) setupEventHandlers() {
276 279
277 // Load committed - connect to load-changed signal for WEBKIT_LOAD_COMMITTED280 // Load committed - connect to load-changed signal for WEBKIT_LOAD_COMMITTED
278 w.view.ConnectLoadChanged(func(loadEvent webkit.LoadEvent) {281 w.view.ConnectLoadChanged(func(loadEvent webkit.LoadEvent) {
282+ logging.Debug(fmt.Sprintf("[webkit] LoadChanged signal: %v", loadEvent))
279 if loadEvent == webkit.LoadStarted && w.onLoadStarted != nil {283 if loadEvent == webkit.LoadStarted && w.onLoadStarted != nil {
284+ logging.Debug("[webkit] calling onLoadStarted")
280 w.onLoadStarted()285 w.onLoadStarted()
281 }286 }
282 if loadEvent == webkit.LoadCommitted && w.onLoadCommitted != nil {287 if loadEvent == webkit.LoadCommitted && w.onLoadCommitted != nil {
283 uri := w.view.URI()288 uri := w.view.URI()
289+ logging.Debug(fmt.Sprintf("[webkit] calling onLoadCommitted: %q", uri))
284 w.onLoadCommitted(uri)290 w.onLoadCommitted(uri)
285 }291 }
286 if loadEvent == webkit.LoadFinished && w.onLoadFinished != nil {292 if loadEvent == webkit.LoadFinished && w.onLoadFinished != nil {
293+ logging.Debug("[webkit] calling onLoadFinished")
287 w.onLoadFinished()294 w.onLoadFinished()
288 }295 }
289 })296 })
@@ -296,16 +303,17 @@ func (w *WebView) setupEventHandlers() {
296 })303 })
297 304
298 // Load failed - connect to load-failed signal for network errors, content blocking, etc.305 // Load failed - connect to load-failed signal for network errors, content blocking, etc.
299- w.view.ConnectLoadFailed(func(loadEvent webkit.LoadEvent, failingUri string, err error) bool {306+ // CRASH FIX: Disabling this handler because it causes a SIGABRT (free(): invalid pointer)
300- if w.onLoadFailed != nil {307+ // likely due to issues with gotk4 signal marshalling/memory management for the error argument.
301- errorMsg := "Unknown error"308+ /*
302- if err != nil {309+ w.view.ConnectLoadFailed(func(loadEvent webkit.LoadEvent, failingUri string, err error) bool {
303- errorMsg = err.Error()310+ logging.Debug(fmt.Sprintf("[webkit] LoadFailed signal: event=%v uri=%s", loadEvent, failingUri))
304- }311+
305- w.onLoadFailed(failingUri, errorMsg)312+ // DEBUG TEST: Blindly return true regardless of error type to see if crash persists.
306- }313+ logging.Debug("[webkit] LoadFailed: Suppressing error page (Blind Test)")
307- return false // Let WebKit show its default error page314+ return true
308- })315+ })
316+ */
309 317
310 // Favicon changed - connect to FaviconDatabase318 // Favicon changed - connect to FaviconDatabase
311 w.setupFaviconHandlers()319 w.setupFaviconHandlers()
@@ -358,6 +366,7 @@ func (w *WebView) setupEventHandlers() {
358 if w.onScriptMessage != nil && jscValue != nil {366 if w.onScriptMessage != nil && jscValue != nil {
359 // Convert JSCValue to string using gotk4 javascriptcore bindings367 // Convert JSCValue to string using gotk4 javascriptcore bindings
360 valueStr := JSCValueToString(jscValue)368 valueStr := JSCValueToString(jscValue)
369+ logging.Debug(fmt.Sprintf("[webkit] script-message-received: %s", valueStr))
361 if valueStr != "" {370 if valueStr != "" {
362 w.onScriptMessage(valueStr)371 w.onScriptMessage(valueStr)
363 }372 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close