detached 1f794d3

chore: merge debug/gtk-segv into dev-test

commit
1f794d32d6be346fc840e30aaeab01cc6e2d9012
author
bnema · · 2025-12-08 19:02 UTC
merge of
985dbf4 + dd6edd4 · diff against the first parent
signature
unsigned
6 files · +94 -38
M internal/app/browser/browser.go +12 -7
@@ -422,15 +422,20 @@ func (app *BrowserApp) setupSignalHandling() {
422422 // runMainLoop starts the appropriate main loop based on WebKit availability
423423 func (app *BrowserApp) runMainLoop() {
424424 if webkit.IsNativeAvailable() {
425- // Schedule content blocking initialization to run on the first main loop iteration
426- // This ensures GTK is fully ready before we start loading filters
427- webkit.RunOnMainThread(func() {
428- if err := app.setupContentBlocking(); err != nil {
429- logging.Warn(fmt.Sprintf("Warning: failed to setup content blocking: %v", err))
430- }
431- })
425+ if os.Getenv("DUMBER_DISABLE_CONTENT_BLOCKING") != "1" {
426+ // Schedule content blocking initialization to run on the first main loop iteration
427+ // This ensures GTK is fully ready before we start loading filters
428+ webkit.RunOnMainThread(func() {
429+ if err := app.setupContentBlocking(); err != nil {
430+ logging.Warn(fmt.Sprintf("Warning: failed to setup content blocking: %v", err))
431+ }
432+ })
433+ } else {
434+ logging.Warn("Content blocking skipped (DUMBER_DISABLE_CONTENT_BLOCKING=1)")
435+ }
432436
433437 logging.Info(fmt.Sprintf("Entering GTK main loop…"))
438+ logging.Debug("[browser] Calling webkit.RunMainLoop()")
434439 webkit.RunMainLoop()
435440 logging.Info(fmt.Sprintf("GTK main loop exited"))
436441
@@ -422,15 +422,20 @@ func (app *BrowserApp) setupSignalHandling() {
422 // runMainLoop starts the appropriate main loop based on WebKit availability422 // runMainLoop starts the appropriate main loop based on WebKit availability
423 func (app *BrowserApp) runMainLoop() {423 func (app *BrowserApp) runMainLoop() {
424 if webkit.IsNativeAvailable() {424 if webkit.IsNativeAvailable() {
425- // Schedule content blocking initialization to run on the first main loop iteration425+ if os.Getenv("DUMBER_DISABLE_CONTENT_BLOCKING") != "1" {
426- // This ensures GTK is fully ready before we start loading filters426+ // Schedule content blocking initialization to run on the first main loop iteration
427- webkit.RunOnMainThread(func() {427+ // This ensures GTK is fully ready before we start loading filters
428- if err := app.setupContentBlocking(); err != nil {428+ webkit.RunOnMainThread(func() {
429- logging.Warn(fmt.Sprintf("Warning: failed to setup content blocking: %v", err))429+ if err := app.setupContentBlocking(); err != nil {
430- }430+ logging.Warn(fmt.Sprintf("Warning: failed to setup content blocking: %v", err))
431- })431+ }
432+ })
433+ } else {
434+ logging.Warn("Content blocking skipped (DUMBER_DISABLE_CONTENT_BLOCKING=1)")
435+ }
432 436
433 logging.Info(fmt.Sprintf("Entering GTK main loop…"))437 logging.Info(fmt.Sprintf("Entering GTK main loop…"))
438+ logging.Debug("[browser] Calling webkit.RunMainLoop()")
434 webkit.RunMainLoop()439 webkit.RunMainLoop()
435 logging.Info(fmt.Sprintf("GTK main loop exited"))440 logging.Info(fmt.Sprintf("GTK main loop exited"))
436 441
M internal/app/browser/webview.go +7 -0
@@ -373,6 +373,13 @@ func (app *BrowserApp) initializeFaviconService() error {
373373 func (app *BrowserApp) setupContentBlocking() error {
374374 logging.Info(fmt.Sprintf("Initializing content blocking system..."))
375375
376+ // Temporary kill-switch for crash triage: set env DUMBER_DISABLE_CONTENT_BLOCKING=1
377+ // to skip all blocking setup and run bare WebViews.
378+ if os.Getenv("DUMBER_DISABLE_CONTENT_BLOCKING") == "1" {
379+ logging.Warn("Content blocking disabled via DUMBER_DISABLE_CONTENT_BLOCKING=1 (test mode)")
380+ return nil
381+ }
382+
376383 // Enable WebKit debug logging if requested
377384 webkit.SetupWebKitDebugLogging()
378385
@@ -373,6 +373,13 @@ func (app *BrowserApp) initializeFaviconService() error {
373 func (app *BrowserApp) setupContentBlocking() error {373 func (app *BrowserApp) setupContentBlocking() error {
374 logging.Info(fmt.Sprintf("Initializing content blocking system..."))374 logging.Info(fmt.Sprintf("Initializing content blocking system..."))
375 375
376+ // Temporary kill-switch for crash triage: set env DUMBER_DISABLE_CONTENT_BLOCKING=1
377+ // to skip all blocking setup and run bare WebViews.
378+ if os.Getenv("DUMBER_DISABLE_CONTENT_BLOCKING") == "1" {
379+ logging.Warn("Content blocking disabled via DUMBER_DISABLE_CONTENT_BLOCKING=1 (test mode)")
380+ return nil
381+ }
382+
376 // Enable WebKit debug logging if requested383 // Enable WebKit debug logging if requested
377 webkit.SetupWebKitDebugLogging()384 webkit.SetupWebKitDebugLogging()
378 385
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 +23 -8
@@ -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 )
@@ -10,6 +11,7 @@ import (
1011 var (
1112 mainLoop *glib.MainLoop
1213 isInitialized bool
14+ mainContext *glib.MainContext
1315 )
1416
1517 // InitMainThread locks the current goroutine to the OS thread for GTK operations
@@ -21,6 +23,9 @@ func InitMainThread() {
2123 // Initialize GTK - this is required before creating any GTK widgets
2224 gtk.Init()
2325
26+ // Cache the default main context so we can check ownership later.
27+ mainContext = glib.MainContextDefault()
28+
2429 isInitialized = true
2530 }
2631 }
@@ -34,7 +39,9 @@ func RunMainLoop() {
3439 mainLoop = glib.NewMainLoop(nil, false)
3540 }
3641
42+ logging.Debug("[webkit] Starting GLib main loop (mainLoop.Run)")
3743 mainLoop.Run()
44+ logging.Debug("[webkit] GLib main loop exited")
3845 }
3946
4047 // QuitMainLoop stops the GTK main event loop.
@@ -44,18 +51,26 @@ func QuitMainLoop() {
4451 }
4552 }
4653
47-// IsMainThread returns true if GTK has been initialized.
48-// Note: This doesn't truly detect the main thread, but is used as a guard
49-// for whether GTK operations are safe. All GTK operations should use
50-// RunOnMainThread or IdleAdd to be thread-safe.
54+// IsMainThread returns true if called from the GTK main thread.
5155 func IsMainThread() bool {
52- return isInitialized
56+ if !isInitialized {
57+ return false
58+ }
59+ if mainContext == nil {
60+ mainContext = glib.MainContextDefault()
61+ }
62+ return mainContext != nil && mainContext.IsOwner()
5363 }
5464
55-// RunOnMainThread schedules a function to run on the GTK main thread.
56-// Always uses glib.IdleAdd to ensure thread safety.
57-// The function will execute during the next main loop iteration.
65+// RunOnMainThread executes a function on the GTK main thread.
66+// If already on the main thread, executes immediately.
67+// Otherwise, schedules the function via glib.IdleAdd.
5868 func RunOnMainThread(fn func()) {
69+ if IsMainThread() {
70+ fn()
71+ return
72+ }
73+
5974 glib.IdleAdd(func() bool {
6075 fn()
6176 return false // Remove the idle handler after execution
@@ -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 )
@@ -10,6 +11,7 @@ import (
10 var (11 var (
11 mainLoop *glib.MainLoop12 mainLoop *glib.MainLoop
12 isInitialized bool13 isInitialized bool
14+ mainContext *glib.MainContext
13 )15 )
14 16
15 // InitMainThread locks the current goroutine to the OS thread for GTK operations17 // InitMainThread locks the current goroutine to the OS thread for GTK operations
@@ -21,6 +23,9 @@ func InitMainThread() {
21 // Initialize GTK - this is required before creating any GTK widgets23 // Initialize GTK - this is required before creating any GTK widgets
22 gtk.Init()24 gtk.Init()
23 25
26+ // Cache the default main context so we can check ownership later.
27+ mainContext = glib.MainContextDefault()
28+
24 isInitialized = true29 isInitialized = true
25 }30 }
26 }31 }
@@ -34,7 +39,9 @@ func RunMainLoop() {
34 mainLoop = glib.NewMainLoop(nil, false)39 mainLoop = glib.NewMainLoop(nil, false)
35 }40 }
36 41
42+ logging.Debug("[webkit] Starting GLib main loop (mainLoop.Run)")
37 mainLoop.Run()43 mainLoop.Run()
44+ logging.Debug("[webkit] GLib main loop exited")
38 }45 }
39 46
40 // QuitMainLoop stops the GTK main event loop.47 // QuitMainLoop stops the GTK main event loop.
@@ -44,18 +51,26 @@ func QuitMainLoop() {
44 }51 }
45 }52 }
46 53
47-// IsMainThread returns true if GTK has been initialized.54+// IsMainThread returns true if called from the GTK main thread.
48-// Note: This doesn't truly detect the main thread, but is used as a guard
49-// for whether GTK operations are safe. All GTK operations should use
50-// RunOnMainThread or IdleAdd to be thread-safe.
51 func IsMainThread() bool {55 func IsMainThread() bool {
52- return isInitialized56+ if !isInitialized {
57+ return false
58+ }
59+ if mainContext == nil {
60+ mainContext = glib.MainContextDefault()
61+ }
62+ return mainContext != nil && mainContext.IsOwner()
53 }63 }
54 64
55-// RunOnMainThread schedules a function to run on the GTK main thread.65+// RunOnMainThread executes a function on the GTK main thread.
56-// Always uses glib.IdleAdd to ensure thread safety.66+// If already on the main thread, executes immediately.
57-// The function will execute during the next main loop iteration.67+// Otherwise, schedules the function via glib.IdleAdd.
58 func RunOnMainThread(fn func()) {68 func RunOnMainThread(fn func()) {
69+ if IsMainThread() {
70+ fn()
71+ return
72+ }
73+
59 glib.IdleAdd(func() bool {74 glib.IdleAdd(func() bool {
60 fn()75 fn()
61 return false // Remove the idle handler after execution76 return false // Remove the idle handler after execution
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