fix(cef): bind permission dialog for floating panes
Resolve the owning window with browserWindowForAnyPane for both the permission dialog binding and the WebRTC indicator. When no window owns the pane the presenter is cleared so the request is denied instead of being shown in a stale or wrong window.
- commit
- 8fbbe5e19fab1b03610ce2a91ffb1246c2e8b01a
- author
- brice · · 2026-10-03 07:04 UTC
- committer
- brice · · 2026-10-03 07:50 UTC
- parent
- 45bd51f
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +41 -4
M internal/ui/app.go +14 -4
| @@ -3589,6 +3589,16 @@ func (a *App) wireKeyboardActions() { | |||
| 3589 | 3589 | }) | |
| 3590 | 3590 | } | |
| 3591 | 3591 | ||
| 3592 | + | // permissionDialogForPane returns the permission dialog of the window owning the | |
| 3593 | + | // pane (including floating panes), or nil when no window or dialog is available. | |
| 3594 | + | func (a *App) permissionDialogForPane(paneID entity.PaneID) port.PermissionDialogPresenter { | |
| 3595 | + | bw := a.browserWindowForAnyPane(paneID) | |
| 3596 | + | if bw == nil || bw.permissionDialog == nil { | |
| 3597 | + | return nil | |
| 3598 | + | } | |
| 3599 | + | return bw.permissionDialog | |
| 3600 | + | } | |
| 3601 | + | ||
| 3592 | 3602 | func (a *App) wireWebRTCPermissionIndicator() { | |
| 3593 | 3603 | if a.contentCoord == nil { | |
| 3594 | 3604 | return | |
| @@ -3602,9 +3612,9 @@ func (a *App) wireWebRTCPermissionIndicator() { | |||
| 3602 | 3612 | if a.deps == nil || a.deps.PermissionUC == nil { | |
| 3603 | 3613 | return | |
| 3604 | 3614 | } | |
| 3605 | - | if bw := a.browserWindowForPane(paneID); bw != nil && bw.permissionDialog != nil { | |
| 3606 | - | a.deps.PermissionUC.SetDialogPresenter(bw.permissionDialog) | |
| 3607 | - | } | |
| 3615 | + | // Clears the presenter when no window owns the pane, so the request is | |
| 3616 | + | // denied instead of being shown in a stale or wrong window. | |
| 3617 | + | a.deps.PermissionUC.SetDialogPresenter(a.permissionDialogForPane(paneID)) | |
| 3608 | 3618 | }) | |
| 3609 | 3619 | ||
| 3610 | 3620 | a.contentCoord.SetOnPermissionActivity(func( | |
| @@ -3613,7 +3623,7 @@ func (a *App) wireWebRTCPermissionIndicator() { | |||
| 3613 | 3623 | permTypes []entity.PermissionType, | |
| 3614 | 3624 | state content.PermissionActivityState, | |
| 3615 | 3625 | ) { | |
| 3616 | - | bw := a.browserWindowForPane(paneID) | |
| 3626 | + | bw := a.browserWindowForAnyPane(paneID) | |
| 3617 | 3627 | if bw == nil || bw.webrtcIndicator == nil { | |
| 3618 | 3628 | return | |
| 3619 | 3629 | } | |
| @@ -3589,6 +3589,16 @@ func (a *App) wireKeyboardActions() { | |||||
| 3589 | }) | 3589 | }) | ||
| 3590 | } | 3590 | } | ||
| 3591 | 3591 | ||||
| 3592 | + | // permissionDialogForPane returns the permission dialog of the window owning the | |||
| 3593 | + | // pane (including floating panes), or nil when no window or dialog is available. | |||
| 3594 | + | func (a *App) permissionDialogForPane(paneID entity.PaneID) port.PermissionDialogPresenter { | |||
| 3595 | + | bw := a.browserWindowForAnyPane(paneID) | |||
| 3596 | + | if bw == nil || bw.permissionDialog == nil { | |||
| 3597 | + | return nil | |||
| 3598 | + | } | |||
| 3599 | + | return bw.permissionDialog | |||
| 3600 | + | } | |||
| 3601 | + | ||||
| 3592 | func (a *App) wireWebRTCPermissionIndicator() { | 3602 | func (a *App) wireWebRTCPermissionIndicator() { | ||
| 3593 | if a.contentCoord == nil { | 3603 | if a.contentCoord == nil { | ||
| 3594 | return | 3604 | return | ||
| @@ -3602,9 +3612,9 @@ func (a *App) wireWebRTCPermissionIndicator() { | |||||
| 3602 | if a.deps == nil || a.deps.PermissionUC == nil { | 3612 | if a.deps == nil || a.deps.PermissionUC == nil { | ||
| 3603 | return | 3613 | return | ||
| 3604 | } | 3614 | } | ||
| 3605 | - | if bw := a.browserWindowForPane(paneID); bw != nil && bw.permissionDialog != nil { | 3615 | + | // Clears the presenter when no window owns the pane, so the request is |
| 3606 | - | a.deps.PermissionUC.SetDialogPresenter(bw.permissionDialog) | 3616 | + | // denied instead of being shown in a stale or wrong window. |
| 3607 | - | } | 3617 | + | a.deps.PermissionUC.SetDialogPresenter(a.permissionDialogForPane(paneID)) |
| 3608 | }) | 3618 | }) | ||
| 3609 | 3619 | ||||
| 3610 | a.contentCoord.SetOnPermissionActivity(func( | 3620 | a.contentCoord.SetOnPermissionActivity(func( | ||
| @@ -3613,7 +3623,7 @@ func (a *App) wireWebRTCPermissionIndicator() { | |||||
| 3613 | permTypes []entity.PermissionType, | 3623 | permTypes []entity.PermissionType, | ||
| 3614 | state content.PermissionActivityState, | 3624 | state content.PermissionActivityState, | ||
| 3615 | ) { | 3625 | ) { | ||
| 3616 | - | bw := a.browserWindowForPane(paneID) | 3626 | + | bw := a.browserWindowForAnyPane(paneID) |
| 3617 | if bw == nil || bw.webrtcIndicator == nil { | 3627 | if bw == nil || bw.webrtcIndicator == nil { | ||
| 3618 | return | 3628 | return | ||
| 3619 | } | 3629 | } | ||
M internal/ui/browser_window_test.go +27 -0
| @@ -1159,3 +1159,30 @@ func TestRestoreSession_ActiveWindowIndexSyncsState(t *testing.T) { | |||
| 1159 | 1159 | assert.Equal(t, entity.WindowID("active-w2"), result[idx].WindowID, | |
| 1160 | 1160 | "window at active index must match focused window ID") | |
| 1161 | 1161 | } | |
| 1162 | + | ||
| 1163 | + | func TestApp_PermissionDialogForPane(t *testing.T) { | |
| 1164 | + | tabID := entity.TabID("tab-1") | |
| 1165 | + | tabPane := entity.PaneID("pane-in-tab") | |
| 1166 | + | floatingPane := entity.PaneID("pane-floating") | |
| 1167 | + | tab := entity.NewTab(tabID, entity.WorkspaceID("ws-1"), entity.NewPane(tabPane)) | |
| 1168 | + | tabs := entity.NewTabList() | |
| 1169 | + | tabs.Add(tab) | |
| 1170 | + | dialogA := &testPermissionDialogPresenter{} | |
| 1171 | + | owner := &browserWindow{id: "window-1", tabs: tabs, permissionDialog: dialogA} | |
| 1172 | + | noDialog := &browserWindow{id: "window-2", tabs: entity.NewTabList()} | |
| 1173 | + | app := &App{ | |
| 1174 | + | browserWindows: map[string]*browserWindow{owner.id: owner, noDialog.id: noDialog}, | |
| 1175 | + | windowForTab: map[entity.TabID]*browserWindow{tabID: owner}, | |
| 1176 | + | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{ | |
| 1177 | + | {tabID: tabID, sessionID: "profile"}: {paneID: floatingPane}, | |
| 1178 | + | }, | |
| 1179 | + | } | |
| 1180 | + | ||
| 1181 | + | assert.Same(t, dialogA, app.permissionDialogForPane(tabPane), "tiled pane") | |
| 1182 | + | assert.Same(t, dialogA, app.permissionDialogForPane(floatingPane), "floating pane uses its tab's window") | |
| 1183 | + | assert.Nil(t, app.permissionDialogForPane("unknown"), "unowned pane clears the presenter") | |
| 1184 | + | assert.Nil(t, app.permissionDialogForPane("")) | |
| 1185 | + | ||
| 1186 | + | owner.permissionDialog = nil | |
| 1187 | + | assert.Nil(t, app.permissionDialogForPane(tabPane), "window without dialog") | |
| 1188 | + | } | |
| @@ -1159,3 +1159,30 @@ func TestRestoreSession_ActiveWindowIndexSyncsState(t *testing.T) { | |||||
| 1159 | assert.Equal(t, entity.WindowID("active-w2"), result[idx].WindowID, | 1159 | assert.Equal(t, entity.WindowID("active-w2"), result[idx].WindowID, | ||
| 1160 | "window at active index must match focused window ID") | 1160 | "window at active index must match focused window ID") | ||
| 1161 | } | 1161 | } | ||
| 1162 | + | ||||
| 1163 | + | func TestApp_PermissionDialogForPane(t *testing.T) { | |||
| 1164 | + | tabID := entity.TabID("tab-1") | |||
| 1165 | + | tabPane := entity.PaneID("pane-in-tab") | |||
| 1166 | + | floatingPane := entity.PaneID("pane-floating") | |||
| 1167 | + | tab := entity.NewTab(tabID, entity.WorkspaceID("ws-1"), entity.NewPane(tabPane)) | |||
| 1168 | + | tabs := entity.NewTabList() | |||
| 1169 | + | tabs.Add(tab) | |||
| 1170 | + | dialogA := &testPermissionDialogPresenter{} | |||
| 1171 | + | owner := &browserWindow{id: "window-1", tabs: tabs, permissionDialog: dialogA} | |||
| 1172 | + | noDialog := &browserWindow{id: "window-2", tabs: entity.NewTabList()} | |||
| 1173 | + | app := &App{ | |||
| 1174 | + | browserWindows: map[string]*browserWindow{owner.id: owner, noDialog.id: noDialog}, | |||
| 1175 | + | windowForTab: map[entity.TabID]*browserWindow{tabID: owner}, | |||
| 1176 | + | floatingSessions: map[floatingSessionKey]*floatingWorkspaceSession{ | |||
| 1177 | + | {tabID: tabID, sessionID: "profile"}: {paneID: floatingPane}, | |||
| 1178 | + | }, | |||
| 1179 | + | } | |||
| 1180 | + | ||||
| 1181 | + | assert.Same(t, dialogA, app.permissionDialogForPane(tabPane), "tiled pane") | |||
| 1182 | + | assert.Same(t, dialogA, app.permissionDialogForPane(floatingPane), "floating pane uses its tab's window") | |||
| 1183 | + | assert.Nil(t, app.permissionDialogForPane("unknown"), "unowned pane clears the presenter") | |||
| 1184 | + | assert.Nil(t, app.permissionDialogForPane("")) | |||
| 1185 | + | ||||
| 1186 | + | owner.permissionDialog = nil | |||
| 1187 | + | assert.Nil(t, app.permissionDialogForPane(tabPane), "window without dialog") | |||
| 1188 | + | } | |||