feat(debug): trace stacked click routing (#446)
* feat(debug): trace stacked click routing * fix(debug): spell canceled consistently
- commit
- 74ee7c46c08a5806489c603244478514b77a5564
- author
- bnema · · 2026-09-23 05:50 UTC
- committer
- GitHub · · 2026-09-23 05:50 UTC
- parent
- f97971b
- signature
- GPG Unverified
B5690EEEBB952194The key is unknown
7 files · +201 -14
M go.mod +1 -1
| @@ -7,7 +7,7 @@ require ( | |||
| 7 | 7 | github.com/andybalholm/brotli v1.2.4 | |
| 8 | 8 | github.com/bnema/purego v0.11.0-bnema.4 | |
| 9 | 9 | github.com/bnema/purego-cef v0.14.2 | |
| 10 | - | github.com/bnema/purego-cef2gtk v0.11.1 | |
| 10 | + | github.com/bnema/purego-cef2gtk v0.11.1-0.20260922042635-bb6ad3b440c6 | |
| 11 | 11 | github.com/bnema/purego-pipewire v0.1.6 | |
| 12 | 12 | github.com/bnema/purego-sqlite v0.1.5 | |
| 13 | 13 | github.com/bnema/purego-webp v0.2.1 | |
| @@ -7,7 +7,7 @@ require ( | |||||
| 7 | github.com/andybalholm/brotli v1.2.4 | 7 | github.com/andybalholm/brotli v1.2.4 | ||
| 8 | github.com/bnema/purego v0.11.0-bnema.4 | 8 | github.com/bnema/purego v0.11.0-bnema.4 | ||
| 9 | github.com/bnema/purego-cef v0.14.2 | 9 | github.com/bnema/purego-cef v0.14.2 | ||
| 10 | - | github.com/bnema/purego-cef2gtk v0.11.1 | 10 | + | github.com/bnema/purego-cef2gtk v0.11.1-0.20260922042635-bb6ad3b440c6 |
| 11 | github.com/bnema/purego-pipewire v0.1.6 | 11 | github.com/bnema/purego-pipewire v0.1.6 | ||
| 12 | github.com/bnema/purego-sqlite v0.1.5 | 12 | github.com/bnema/purego-sqlite v0.1.5 | ||
| 13 | github.com/bnema/purego-webp v0.2.1 | 13 | github.com/bnema/purego-webp v0.2.1 | ||
M go.sum +2 -0 generated file: collapsed
A internal/infrastructure/cef/click_diagnostics.go +97 -0
| @@ -0,0 +1,97 @@ | |||
| 1 | + | package cef | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "fmt" | |
| 5 | + | ||
| 6 | + | "github.com/bnema/dumber/internal/logging" | |
| 7 | + | cef2gtk "github.com/bnema/purego-cef2gtk" | |
| 8 | + | "github.com/rs/zerolog" | |
| 9 | + | ) | |
| 10 | + | ||
| 11 | + | func clickDiagnosticPhaseName(phase cef2gtk.ClickDiagnosticPhase) string { | |
| 12 | + | switch phase { | |
| 13 | + | case cef2gtk.ClickDiagnosticPressed: | |
| 14 | + | return "pressed" | |
| 15 | + | case cef2gtk.ClickDiagnosticReleased: | |
| 16 | + | return "released" | |
| 17 | + | case cef2gtk.ClickDiagnosticCancelled: | |
| 18 | + | return "canceled" | |
| 19 | + | case cef2gtk.ClickDiagnosticForwarded: | |
| 20 | + | return "forwarded" | |
| 21 | + | case cef2gtk.ClickDiagnosticConsumed: | |
| 22 | + | return "consumed" | |
| 23 | + | case cef2gtk.ClickDiagnosticDropped: | |
| 24 | + | return "dropped" | |
| 25 | + | default: | |
| 26 | + | return fmt.Sprintf("unknown(%d)", phase) | |
| 27 | + | } | |
| 28 | + | } | |
| 29 | + | ||
| 30 | + | func clickDropReasonName(reason cef2gtk.ClickDropReason) string { | |
| 31 | + | switch reason { | |
| 32 | + | case cef2gtk.ClickDropReasonNone: | |
| 33 | + | return "none" | |
| 34 | + | case cef2gtk.ClickDropReasonMissingHost: | |
| 35 | + | return "missing_host" | |
| 36 | + | case cef2gtk.ClickDropReasonDetached: | |
| 37 | + | return "detached" | |
| 38 | + | case cef2gtk.ClickDropReasonMissingInputState: | |
| 39 | + | return "missing_input_state" | |
| 40 | + | default: | |
| 41 | + | return fmt.Sprintf("unknown(%d)", reason) | |
| 42 | + | } | |
| 43 | + | } | |
| 44 | + | ||
| 45 | + | func clickButtonName(button uint) string { | |
| 46 | + | switch button { | |
| 47 | + | case 1: | |
| 48 | + | return "primary" | |
| 49 | + | case 2: | |
| 50 | + | return "middle" | |
| 51 | + | case 3: | |
| 52 | + | return "secondary" | |
| 53 | + | default: | |
| 54 | + | return "other" | |
| 55 | + | } | |
| 56 | + | } | |
| 57 | + | ||
| 58 | + | func (wv *WebView) logClickDiagnostic(event cef2gtk.ClickDiagnosticEvent) { | |
| 59 | + | if wv == nil || wv.ctx == nil { | |
| 60 | + | return | |
| 61 | + | } | |
| 62 | + | wv.mu.RLock() | |
| 63 | + | browserID := int32(0) | |
| 64 | + | if wv.browser != nil { | |
| 65 | + | browserID = wv.browser.GetIdentifier() | |
| 66 | + | } | |
| 67 | + | inputAttached := wv.inputAttached | |
| 68 | + | visibilityKnown := wv.effectiveVisibilityKnown | |
| 69 | + | visible := wv.effectiveVisible | |
| 70 | + | bridge := wv.viewBridge | |
| 71 | + | bridgePresent := bridge != nil | |
| 72 | + | wv.mu.RUnlock() | |
| 73 | + | bridgeFocused := bridgePresent && bridge.HasFocus() | |
| 74 | + | ||
| 75 | + | logger := logging.FromContext(wv.ctx) | |
| 76 | + | var logEvent *zerolog.Event | |
| 77 | + | if event.Phase == cef2gtk.ClickDiagnosticDropped || event.Phase == cef2gtk.ClickDiagnosticCancelled { | |
| 78 | + | logEvent = logger.Warn() | |
| 79 | + | } else { | |
| 80 | + | logEvent = logger.Debug() | |
| 81 | + | } | |
| 82 | + | logEvent. | |
| 83 | + | Uint64("webview_id", uint64(wv.id)). | |
| 84 | + | Int32("browser_id", browserID). | |
| 85 | + | Str("phase", clickDiagnosticPhaseName(event.Phase)). | |
| 86 | + | Uint("button", event.Button). | |
| 87 | + | Str("button_type", clickButtonName(event.Button)). | |
| 88 | + | Int("click_count", event.ClickCount). | |
| 89 | + | Str("drop_reason", clickDropReasonName(event.DropReason)). | |
| 90 | + | Bool("input_attached", inputAttached). | |
| 91 | + | Bool("effective_visibility_known", visibilityKnown). | |
| 92 | + | Bool("effective_visible", visible). | |
| 93 | + | Bool("destroyed", wv.destroyed.Load()). | |
| 94 | + | Bool("bridge_present", bridgePresent). | |
| 95 | + | Bool("bridge_focused", bridgeFocused). | |
| 96 | + | Msg("cef: click routing diagnostic") | |
| 97 | + | } | |
| @@ -0,0 +1,97 @@ | |||||
| 1 | + | package cef | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "fmt" | |||
| 5 | + | ||||
| 6 | + | "github.com/bnema/dumber/internal/logging" | |||
| 7 | + | cef2gtk "github.com/bnema/purego-cef2gtk" | |||
| 8 | + | "github.com/rs/zerolog" | |||
| 9 | + | ) | |||
| 10 | + | ||||
| 11 | + | func clickDiagnosticPhaseName(phase cef2gtk.ClickDiagnosticPhase) string { | |||
| 12 | + | switch phase { | |||
| 13 | + | case cef2gtk.ClickDiagnosticPressed: | |||
| 14 | + | return "pressed" | |||
| 15 | + | case cef2gtk.ClickDiagnosticReleased: | |||
| 16 | + | return "released" | |||
| 17 | + | case cef2gtk.ClickDiagnosticCancelled: | |||
| 18 | + | return "canceled" | |||
| 19 | + | case cef2gtk.ClickDiagnosticForwarded: | |||
| 20 | + | return "forwarded" | |||
| 21 | + | case cef2gtk.ClickDiagnosticConsumed: | |||
| 22 | + | return "consumed" | |||
| 23 | + | case cef2gtk.ClickDiagnosticDropped: | |||
| 24 | + | return "dropped" | |||
| 25 | + | default: | |||
| 26 | + | return fmt.Sprintf("unknown(%d)", phase) | |||
| 27 | + | } | |||
| 28 | + | } | |||
| 29 | + | ||||
| 30 | + | func clickDropReasonName(reason cef2gtk.ClickDropReason) string { | |||
| 31 | + | switch reason { | |||
| 32 | + | case cef2gtk.ClickDropReasonNone: | |||
| 33 | + | return "none" | |||
| 34 | + | case cef2gtk.ClickDropReasonMissingHost: | |||
| 35 | + | return "missing_host" | |||
| 36 | + | case cef2gtk.ClickDropReasonDetached: | |||
| 37 | + | return "detached" | |||
| 38 | + | case cef2gtk.ClickDropReasonMissingInputState: | |||
| 39 | + | return "missing_input_state" | |||
| 40 | + | default: | |||
| 41 | + | return fmt.Sprintf("unknown(%d)", reason) | |||
| 42 | + | } | |||
| 43 | + | } | |||
| 44 | + | ||||
| 45 | + | func clickButtonName(button uint) string { | |||
| 46 | + | switch button { | |||
| 47 | + | case 1: | |||
| 48 | + | return "primary" | |||
| 49 | + | case 2: | |||
| 50 | + | return "middle" | |||
| 51 | + | case 3: | |||
| 52 | + | return "secondary" | |||
| 53 | + | default: | |||
| 54 | + | return "other" | |||
| 55 | + | } | |||
| 56 | + | } | |||
| 57 | + | ||||
| 58 | + | func (wv *WebView) logClickDiagnostic(event cef2gtk.ClickDiagnosticEvent) { | |||
| 59 | + | if wv == nil || wv.ctx == nil { | |||
| 60 | + | return | |||
| 61 | + | } | |||
| 62 | + | wv.mu.RLock() | |||
| 63 | + | browserID := int32(0) | |||
| 64 | + | if wv.browser != nil { | |||
| 65 | + | browserID = wv.browser.GetIdentifier() | |||
| 66 | + | } | |||
| 67 | + | inputAttached := wv.inputAttached | |||
| 68 | + | visibilityKnown := wv.effectiveVisibilityKnown | |||
| 69 | + | visible := wv.effectiveVisible | |||
| 70 | + | bridge := wv.viewBridge | |||
| 71 | + | bridgePresent := bridge != nil | |||
| 72 | + | wv.mu.RUnlock() | |||
| 73 | + | bridgeFocused := bridgePresent && bridge.HasFocus() | |||
| 74 | + | ||||
| 75 | + | logger := logging.FromContext(wv.ctx) | |||
| 76 | + | var logEvent *zerolog.Event | |||
| 77 | + | if event.Phase == cef2gtk.ClickDiagnosticDropped || event.Phase == cef2gtk.ClickDiagnosticCancelled { | |||
| 78 | + | logEvent = logger.Warn() | |||
| 79 | + | } else { | |||
| 80 | + | logEvent = logger.Debug() | |||
| 81 | + | } | |||
| 82 | + | logEvent. | |||
| 83 | + | Uint64("webview_id", uint64(wv.id)). | |||
| 84 | + | Int32("browser_id", browserID). | |||
| 85 | + | Str("phase", clickDiagnosticPhaseName(event.Phase)). | |||
| 86 | + | Uint("button", event.Button). | |||
| 87 | + | Str("button_type", clickButtonName(event.Button)). | |||
| 88 | + | Int("click_count", event.ClickCount). | |||
| 89 | + | Str("drop_reason", clickDropReasonName(event.DropReason)). | |||
| 90 | + | Bool("input_attached", inputAttached). | |||
| 91 | + | Bool("effective_visibility_known", visibilityKnown). | |||
| 92 | + | Bool("effective_visible", visible). | |||
| 93 | + | Bool("destroyed", wv.destroyed.Load()). | |||
| 94 | + | Bool("bridge_present", bridgePresent). | |||
| 95 | + | Bool("bridge_focused", bridgeFocused). | |||
| 96 | + | Msg("cef: click routing diagnostic") | |||
| 97 | + | } | |||
A internal/infrastructure/cef/click_diagnostics_test.go +49 -0
| @@ -0,0 +1,49 @@ | |||
| 1 | + | package cef | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "testing" | |
| 5 | + | ||
| 6 | + | cef2gtk "github.com/bnema/purego-cef2gtk" | |
| 7 | + | ) | |
| 8 | + | ||
| 9 | + | func TestClickDiagnosticNamesAreStable(t *testing.T) { | |
| 10 | + | phases := map[cef2gtk.ClickDiagnosticPhase]string{ | |
| 11 | + | cef2gtk.ClickDiagnosticPressed: "pressed", | |
| 12 | + | cef2gtk.ClickDiagnosticReleased: "released", | |
| 13 | + | cef2gtk.ClickDiagnosticCancelled: "canceled", | |
| 14 | + | cef2gtk.ClickDiagnosticForwarded: "forwarded", | |
| 15 | + | cef2gtk.ClickDiagnosticConsumed: "consumed", | |
| 16 | + | cef2gtk.ClickDiagnosticDropped: "dropped", | |
| 17 | + | } | |
| 18 | + | for value, want := range phases { | |
| 19 | + | if got := clickDiagnosticPhaseName(value); got != want { | |
| 20 | + | t.Fatalf("phase %d: got %q, want %q", value, got, want) | |
| 21 | + | } | |
| 22 | + | } | |
| 23 | + | if got := clickDiagnosticPhaseName(99); got != "unknown(99)" { | |
| 24 | + | t.Fatalf("unknown phase: got %q", got) | |
| 25 | + | } | |
| 26 | + | ||
| 27 | + | reasons := map[cef2gtk.ClickDropReason]string{ | |
| 28 | + | cef2gtk.ClickDropReasonNone: "none", | |
| 29 | + | cef2gtk.ClickDropReasonMissingHost: "missing_host", | |
| 30 | + | cef2gtk.ClickDropReasonDetached: "detached", | |
| 31 | + | cef2gtk.ClickDropReasonMissingInputState: "missing_input_state", | |
| 32 | + | } | |
| 33 | + | for value, want := range reasons { | |
| 34 | + | if got := clickDropReasonName(value); got != want { | |
| 35 | + | t.Fatalf("reason %d: got %q, want %q", value, got, want) | |
| 36 | + | } | |
| 37 | + | } | |
| 38 | + | if got := clickDropReasonName(99); got != "unknown(99)" { | |
| 39 | + | t.Fatalf("unknown reason: got %q", got) | |
| 40 | + | } | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | func TestClickButtonNamesArePrivacySafeAndBounded(t *testing.T) { | |
| 44 | + | for button, want := range map[uint]string{0: "other", 1: "primary", 2: "middle", 3: "secondary", 8: "other"} { | |
| 45 | + | if got := clickButtonName(button); got != want { | |
| 46 | + | t.Fatalf("button %d: got %q, want %q", button, got, want) | |
| 47 | + | } | |
| 48 | + | } | |
| 49 | + | } | |
| @@ -0,0 +1,49 @@ | |||||
| 1 | + | package cef | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "testing" | |||
| 5 | + | ||||
| 6 | + | cef2gtk "github.com/bnema/purego-cef2gtk" | |||
| 7 | + | ) | |||
| 8 | + | ||||
| 9 | + | func TestClickDiagnosticNamesAreStable(t *testing.T) { | |||
| 10 | + | phases := map[cef2gtk.ClickDiagnosticPhase]string{ | |||
| 11 | + | cef2gtk.ClickDiagnosticPressed: "pressed", | |||
| 12 | + | cef2gtk.ClickDiagnosticReleased: "released", | |||
| 13 | + | cef2gtk.ClickDiagnosticCancelled: "canceled", | |||
| 14 | + | cef2gtk.ClickDiagnosticForwarded: "forwarded", | |||
| 15 | + | cef2gtk.ClickDiagnosticConsumed: "consumed", | |||
| 16 | + | cef2gtk.ClickDiagnosticDropped: "dropped", | |||
| 17 | + | } | |||
| 18 | + | for value, want := range phases { | |||
| 19 | + | if got := clickDiagnosticPhaseName(value); got != want { | |||
| 20 | + | t.Fatalf("phase %d: got %q, want %q", value, got, want) | |||
| 21 | + | } | |||
| 22 | + | } | |||
| 23 | + | if got := clickDiagnosticPhaseName(99); got != "unknown(99)" { | |||
| 24 | + | t.Fatalf("unknown phase: got %q", got) | |||
| 25 | + | } | |||
| 26 | + | ||||
| 27 | + | reasons := map[cef2gtk.ClickDropReason]string{ | |||
| 28 | + | cef2gtk.ClickDropReasonNone: "none", | |||
| 29 | + | cef2gtk.ClickDropReasonMissingHost: "missing_host", | |||
| 30 | + | cef2gtk.ClickDropReasonDetached: "detached", | |||
| 31 | + | cef2gtk.ClickDropReasonMissingInputState: "missing_input_state", | |||
| 32 | + | } | |||
| 33 | + | for value, want := range reasons { | |||
| 34 | + | if got := clickDropReasonName(value); got != want { | |||
| 35 | + | t.Fatalf("reason %d: got %q, want %q", value, got, want) | |||
| 36 | + | } | |||
| 37 | + | } | |||
| 38 | + | if got := clickDropReasonName(99); got != "unknown(99)" { | |||
| 39 | + | t.Fatalf("unknown reason: got %q", got) | |||
| 40 | + | } | |||
| 41 | + | } | |||
| 42 | + | ||||
| 43 | + | func TestClickButtonNamesArePrivacySafeAndBounded(t *testing.T) { | |||
| 44 | + | for button, want := range map[uint]string{0: "other", 1: "primary", 2: "middle", 3: "secondary", 8: "other"} { | |||
| 45 | + | if got := clickButtonName(button); got != want { | |||
| 46 | + | t.Fatalf("button %d: got %q, want %q", button, got, want) | |||
| 47 | + | } | |||
| 48 | + | } | |||
| 49 | + | } | |||
M internal/infrastructure/cef/webview.go +2 -1
| @@ -1737,7 +1737,8 @@ func (wv *WebView) bridgeInputOptions() cef2gtk.InputOptions { | |||
| 1737 | 1737 | OnMiddleClick: func(_, _ float64) bool { | |
| 1738 | 1738 | return wv.handleMiddleClickFromBridge() | |
| 1739 | 1739 | }, | |
| 1740 | - | OnScroll: wv.handleScrollInput, | |
| 1740 | + | OnClickDiagnostic: wv.logClickDiagnostic, | |
| 1741 | + | OnScroll: wv.handleScrollInput, | |
| 1741 | 1742 | NavigationSwipe: cef2gtk.NavigationSwipeOptions{ | |
| 1742 | 1743 | // Dumber handles thresholding and progress UI locally from OnScroll so | |
| 1743 | 1744 | // horizontal scrolling continues to reach CEF while history navigation | |
| @@ -1737,7 +1737,8 @@ func (wv *WebView) bridgeInputOptions() cef2gtk.InputOptions { | |||||
| 1737 | OnMiddleClick: func(_, _ float64) bool { | 1737 | OnMiddleClick: func(_, _ float64) bool { | ||
| 1738 | return wv.handleMiddleClickFromBridge() | 1738 | return wv.handleMiddleClickFromBridge() | ||
| 1739 | }, | 1739 | }, | ||
| 1740 | - | OnScroll: wv.handleScrollInput, | 1740 | + | OnClickDiagnostic: wv.logClickDiagnostic, |
| 1741 | + | OnScroll: wv.handleScrollInput, | |||
| 1741 | NavigationSwipe: cef2gtk.NavigationSwipeOptions{ | 1742 | NavigationSwipe: cef2gtk.NavigationSwipeOptions{ | ||
| 1742 | // Dumber handles thresholding and progress UI locally from OnScroll so | 1743 | // Dumber handles thresholding and progress UI locally from OnScroll so | ||
| 1743 | // horizontal scrolling continues to reach CEF while history navigation | 1744 | // horizontal scrolling continues to reach CEF while history navigation | ||
M internal/infrastructure/cef/webview_handlers.go +38 -4
| @@ -990,21 +990,44 @@ func (h *handlerSet) attachAfterCreatedBrowser( | |||
| 990 | 990 | ||
| 991 | 991 | if bridge != nil { | |
| 992 | 992 | wv := h.wv | |
| 993 | + | log := logging.FromContext(wv.ctx) | |
| 994 | + | log.Debug(). | |
| 995 | + | Uint64("webview_id", uint64(wv.id)). | |
| 996 | + | Int32("browser_id", browserID). | |
| 997 | + | Bool("destroyed", wv.destroyed.Load()). | |
| 998 | + | Bool("bridge_present", true). | |
| 999 | + | Msg("cef: scheduling input bridge attachment") | |
| 993 | 1000 | wv.runOnGTK(func() { | |
| 994 | - | if wv.destroyed.Load() || wv.viewBridge == nil { | |
| 1001 | + | currentBridge := wv.viewBridge | |
| 1002 | + | log.Debug(). | |
| 1003 | + | Uint64("webview_id", uint64(wv.id)). | |
| 1004 | + | Int32("browser_id", browserID). | |
| 1005 | + | Bool("destroyed", wv.destroyed.Load()). | |
| 1006 | + | Bool("bridge_present", currentBridge != nil). | |
| 1007 | + | Bool("bridge_stale", currentBridge != bridge). | |
| 1008 | + | Msg("cef: starting input bridge attachment") | |
| 1009 | + | if wv.destroyed.Load() || currentBridge == nil || currentBridge != bridge || bridge.IsDestroyed() { | |
| 995 | 1010 | wv.handleInputAttachFailure(ErrAdapterDestroyed, host) | |
| 996 | 1011 | return | |
| 997 | 1012 | } | |
| 998 | - | if err := wv.viewBridge.AttachInputToWidget(host, wv.nativeWidget, wv.bridgeInputOptions()); err != nil { | |
| 1013 | + | if err := bridge.AttachInputToWidget(host, wv.nativeWidget, wv.bridgeInputOptions()); err != nil { | |
| 999 | 1014 | wv.handleInputAttachFailure(err, host) | |
| 1000 | 1015 | return | |
| 1001 | 1016 | } | |
| 1002 | - | if wv.viewBridge != nil && wv.viewBridge.HasFocus() { | |
| 1017 | + | bridgeFocused := bridge.HasFocus() | |
| 1018 | + | if bridgeFocused { | |
| 1003 | 1019 | syncWindowlessBrowserFocus(host) | |
| 1004 | 1020 | } else { | |
| 1005 | 1021 | host.Invalidate(purecef.PaintElementTypePetView) | |
| 1006 | 1022 | } | |
| 1007 | 1023 | wv.markInputAttached() | |
| 1024 | + | log.Debug(). | |
| 1025 | + | Uint64("webview_id", uint64(wv.id)). | |
| 1026 | + | Int32("browser_id", browserID). | |
| 1027 | + | Bool("destroyed", wv.destroyed.Load()). | |
| 1028 | + | Bool("bridge_present", wv.viewBridge != nil). | |
| 1029 | + | Bool("bridge_focused", bridgeFocused). | |
| 1030 | + | Msg("cef: input bridge attachment succeeded") | |
| 1008 | 1031 | }) | |
| 1009 | 1032 | } | |
| 1010 | 1033 | ||
| @@ -1026,7 +1049,18 @@ func (wv *WebView) handleInputAttachFailure(err error, host purecef.BrowserHost) | |||
| 1026 | 1049 | return | |
| 1027 | 1050 | } | |
| 1028 | 1051 | if wv.ctx != nil { | |
| 1029 | - | logging.FromContext(wv.ctx).Warn().Err(err).Msg("cef: failed to attach input to cef2gtk bridge") | |
| 1052 | + | browserID := int32(0) | |
| 1053 | + | if host != nil { | |
| 1054 | + | if browser := host.GetBrowser(); browser != nil { | |
| 1055 | + | browserID = browser.GetIdentifier() | |
| 1056 | + | } | |
| 1057 | + | } | |
| 1058 | + | logging.FromContext(wv.ctx).Warn().Err(err). | |
| 1059 | + | Uint64("webview_id", uint64(wv.id)). | |
| 1060 | + | Int32("browser_id", browserID). | |
| 1061 | + | Bool("destroyed", wv.destroyed.Load()). | |
| 1062 | + | Bool("bridge_present", wv.viewBridge != nil). | |
| 1063 | + | Msg("cef: input bridge attachment failed") | |
| 1030 | 1064 | } | |
| 1031 | 1065 | if host != nil && !wv.destroyed.Load() { | |
| 1032 | 1066 | host.CloseBrowser(1) | |
| @@ -990,21 +990,44 @@ func (h *handlerSet) attachAfterCreatedBrowser( | |||||
| 990 | 990 | ||||
| 991 | if bridge != nil { | 991 | if bridge != nil { | ||
| 992 | wv := h.wv | 992 | wv := h.wv | ||
| 993 | + | log := logging.FromContext(wv.ctx) | |||
| 994 | + | log.Debug(). | |||
| 995 | + | Uint64("webview_id", uint64(wv.id)). | |||
| 996 | + | Int32("browser_id", browserID). | |||
| 997 | + | Bool("destroyed", wv.destroyed.Load()). | |||
| 998 | + | Bool("bridge_present", true). | |||
| 999 | + | Msg("cef: scheduling input bridge attachment") | |||
| 993 | wv.runOnGTK(func() { | 1000 | wv.runOnGTK(func() { | ||
| 994 | - | if wv.destroyed.Load() || wv.viewBridge == nil { | 1001 | + | currentBridge := wv.viewBridge |
| 1002 | + | log.Debug(). | |||
| 1003 | + | Uint64("webview_id", uint64(wv.id)). | |||
| 1004 | + | Int32("browser_id", browserID). | |||
| 1005 | + | Bool("destroyed", wv.destroyed.Load()). | |||
| 1006 | + | Bool("bridge_present", currentBridge != nil). | |||
| 1007 | + | Bool("bridge_stale", currentBridge != bridge). | |||
| 1008 | + | Msg("cef: starting input bridge attachment") | |||
| 1009 | + | if wv.destroyed.Load() || currentBridge == nil || currentBridge != bridge || bridge.IsDestroyed() { | |||
| 995 | wv.handleInputAttachFailure(ErrAdapterDestroyed, host) | 1010 | wv.handleInputAttachFailure(ErrAdapterDestroyed, host) | ||
| 996 | return | 1011 | return | ||
| 997 | } | 1012 | } | ||
| 998 | - | if err := wv.viewBridge.AttachInputToWidget(host, wv.nativeWidget, wv.bridgeInputOptions()); err != nil { | 1013 | + | if err := bridge.AttachInputToWidget(host, wv.nativeWidget, wv.bridgeInputOptions()); err != nil { |
| 999 | wv.handleInputAttachFailure(err, host) | 1014 | wv.handleInputAttachFailure(err, host) | ||
| 1000 | return | 1015 | return | ||
| 1001 | } | 1016 | } | ||
| 1002 | - | if wv.viewBridge != nil && wv.viewBridge.HasFocus() { | 1017 | + | bridgeFocused := bridge.HasFocus() |
| 1018 | + | if bridgeFocused { | |||
| 1003 | syncWindowlessBrowserFocus(host) | 1019 | syncWindowlessBrowserFocus(host) | ||
| 1004 | } else { | 1020 | } else { | ||
| 1005 | host.Invalidate(purecef.PaintElementTypePetView) | 1021 | host.Invalidate(purecef.PaintElementTypePetView) | ||
| 1006 | } | 1022 | } | ||
| 1007 | wv.markInputAttached() | 1023 | wv.markInputAttached() | ||
| 1024 | + | log.Debug(). | |||
| 1025 | + | Uint64("webview_id", uint64(wv.id)). | |||
| 1026 | + | Int32("browser_id", browserID). | |||
| 1027 | + | Bool("destroyed", wv.destroyed.Load()). | |||
| 1028 | + | Bool("bridge_present", wv.viewBridge != nil). | |||
| 1029 | + | Bool("bridge_focused", bridgeFocused). | |||
| 1030 | + | Msg("cef: input bridge attachment succeeded") | |||
| 1008 | }) | 1031 | }) | ||
| 1009 | } | 1032 | } | ||
| 1010 | 1033 | ||||
| @@ -1026,7 +1049,18 @@ func (wv *WebView) handleInputAttachFailure(err error, host purecef.BrowserHost) | |||||
| 1026 | return | 1049 | return | ||
| 1027 | } | 1050 | } | ||
| 1028 | if wv.ctx != nil { | 1051 | if wv.ctx != nil { | ||
| 1029 | - | logging.FromContext(wv.ctx).Warn().Err(err).Msg("cef: failed to attach input to cef2gtk bridge") | 1052 | + | browserID := int32(0) |
| 1053 | + | if host != nil { | |||
| 1054 | + | if browser := host.GetBrowser(); browser != nil { | |||
| 1055 | + | browserID = browser.GetIdentifier() | |||
| 1056 | + | } | |||
| 1057 | + | } | |||
| 1058 | + | logging.FromContext(wv.ctx).Warn().Err(err). | |||
| 1059 | + | Uint64("webview_id", uint64(wv.id)). | |||
| 1060 | + | Int32("browser_id", browserID). | |||
| 1061 | + | Bool("destroyed", wv.destroyed.Load()). | |||
| 1062 | + | Bool("bridge_present", wv.viewBridge != nil). | |||
| 1063 | + | Msg("cef: input bridge attachment failed") | |||
| 1030 | } | 1064 | } | ||
| 1031 | if host != nil && !wv.destroyed.Load() { | 1065 | if host != nil && !wv.destroyed.Load() { | ||
| 1032 | host.CloseBrowser(1) | 1066 | host.CloseBrowser(1) | ||
M internal/ui/layout/stacked.go +12 -8
| @@ -131,7 +131,7 @@ func (sv *StackedView) AddPane(ctx context.Context, paneID, title, faviconIconNa | |||
| 131 | 131 | defer sv.mu.Unlock() | |
| 132 | 132 | ||
| 133 | 133 | log.Debug(). | |
| 134 | - | Str("title", title). | |
| 134 | + | Str("pane_id", paneID). | |
| 135 | 135 | Bool("container_nil", container == nil). | |
| 136 | 136 | Int("current_count", len(sv.panes)). | |
| 137 | 137 | Msg("StackedView.AddPane called") | |
| @@ -299,7 +299,7 @@ func (sv *StackedView) InsertPaneAfter( | |||
| 299 | 299 | insertIndex := afterIndex + 1 | |
| 300 | 300 | ||
| 301 | 301 | log.Debug(). | |
| 302 | - | Str("title", title). | |
| 302 | + | Str("pane_id", paneID). | |
| 303 | 303 | Int("after_index", afterIndex). | |
| 304 | 304 | Int("insert_index", insertIndex). | |
| 305 | 305 | Int("current_count", len(sv.panes)). | |
| @@ -478,22 +478,26 @@ func (sv *StackedView) updateVisibilityInternal(ctx context.Context) { | |||
| 478 | 478 | if pane.titleBar != nil { | |
| 479 | 479 | pane.titleBar.SetVisible(!isActive) | |
| 480 | 480 | log.Debug(). | |
| 481 | + | Str("pane_id", pane.paneID). | |
| 481 | 482 | Int("pane_index", i). | |
| 482 | - | Str("title", pane.title). | |
| 483 | + | Int("active_index", sv.activeIndex). | |
| 484 | + | Int("pane_count", len(sv.panes)). | |
| 483 | 485 | Bool("is_active", isActive). | |
| 484 | - | Bool("titlebar_visible", !isActive). | |
| 485 | - | Msg("StackedView: set titlebar visibility") | |
| 486 | + | Bool("requested_visible", !isActive). | |
| 487 | + | Msg("StackedView: titlebar visibility changed") | |
| 486 | 488 | } | |
| 487 | 489 | ||
| 488 | 490 | // Container is visible only for active pane | |
| 489 | 491 | if pane.container != nil { | |
| 490 | 492 | pane.container.SetVisible(isActive) | |
| 491 | 493 | log.Debug(). | |
| 494 | + | Str("pane_id", pane.paneID). | |
| 492 | 495 | Int("pane_index", i). | |
| 493 | - | Str("title", pane.title). | |
| 496 | + | Int("active_index", sv.activeIndex). | |
| 497 | + | Int("pane_count", len(sv.panes)). | |
| 494 | 498 | Bool("is_active", isActive). | |
| 495 | - | Bool("container_visible", isActive). | |
| 496 | - | Msg("StackedView: set container visibility") | |
| 499 | + | Bool("requested_visible", isActive). | |
| 500 | + | Msg("StackedView: container visibility changed") | |
| 497 | 501 | } | |
| 498 | 502 | ||
| 499 | 503 | // Update CSS classes | |
| @@ -131,7 +131,7 @@ func (sv *StackedView) AddPane(ctx context.Context, paneID, title, faviconIconNa | |||||
| 131 | defer sv.mu.Unlock() | 131 | defer sv.mu.Unlock() | ||
| 132 | 132 | ||||
| 133 | log.Debug(). | 133 | log.Debug(). | ||
| 134 | - | Str("title", title). | 134 | + | Str("pane_id", paneID). |
| 135 | Bool("container_nil", container == nil). | 135 | Bool("container_nil", container == nil). | ||
| 136 | Int("current_count", len(sv.panes)). | 136 | Int("current_count", len(sv.panes)). | ||
| 137 | Msg("StackedView.AddPane called") | 137 | Msg("StackedView.AddPane called") | ||
| @@ -299,7 +299,7 @@ func (sv *StackedView) InsertPaneAfter( | |||||
| 299 | insertIndex := afterIndex + 1 | 299 | insertIndex := afterIndex + 1 | ||
| 300 | 300 | ||||
| 301 | log.Debug(). | 301 | log.Debug(). | ||
| 302 | - | Str("title", title). | 302 | + | Str("pane_id", paneID). |
| 303 | Int("after_index", afterIndex). | 303 | Int("after_index", afterIndex). | ||
| 304 | Int("insert_index", insertIndex). | 304 | Int("insert_index", insertIndex). | ||
| 305 | Int("current_count", len(sv.panes)). | 305 | Int("current_count", len(sv.panes)). | ||
| @@ -478,22 +478,26 @@ func (sv *StackedView) updateVisibilityInternal(ctx context.Context) { | |||||
| 478 | if pane.titleBar != nil { | 478 | if pane.titleBar != nil { | ||
| 479 | pane.titleBar.SetVisible(!isActive) | 479 | pane.titleBar.SetVisible(!isActive) | ||
| 480 | log.Debug(). | 480 | log.Debug(). | ||
| 481 | + | Str("pane_id", pane.paneID). | |||
| 481 | Int("pane_index", i). | 482 | Int("pane_index", i). | ||
| 482 | - | Str("title", pane.title). | 483 | + | Int("active_index", sv.activeIndex). |
| 484 | + | Int("pane_count", len(sv.panes)). | |||
| 483 | Bool("is_active", isActive). | 485 | Bool("is_active", isActive). | ||
| 484 | - | Bool("titlebar_visible", !isActive). | 486 | + | Bool("requested_visible", !isActive). |
| 485 | - | Msg("StackedView: set titlebar visibility") | 487 | + | Msg("StackedView: titlebar visibility changed") |
| 486 | } | 488 | } | ||
| 487 | 489 | ||||
| 488 | // Container is visible only for active pane | 490 | // Container is visible only for active pane | ||
| 489 | if pane.container != nil { | 491 | if pane.container != nil { | ||
| 490 | pane.container.SetVisible(isActive) | 492 | pane.container.SetVisible(isActive) | ||
| 491 | log.Debug(). | 493 | log.Debug(). | ||
| 494 | + | Str("pane_id", pane.paneID). | |||
| 492 | Int("pane_index", i). | 495 | Int("pane_index", i). | ||
| 493 | - | Str("title", pane.title). | 496 | + | Int("active_index", sv.activeIndex). |
| 497 | + | Int("pane_count", len(sv.panes)). | |||
| 494 | Bool("is_active", isActive). | 498 | Bool("is_active", isActive). | ||
| 495 | - | Bool("container_visible", isActive). | 499 | + | Bool("requested_visible", isActive). |
| 496 | - | Msg("StackedView: set container visibility") | 500 | + | Msg("StackedView: container visibility changed") |
| 497 | } | 501 | } | ||
| 498 | 502 | ||||
| 499 | // Update CSS classes | 503 | // Update CSS classes | ||