feat: thread requesting_domain/current_domain to permission dialog
- commit
- b3d303062506e14da58ca484ae9244e0e8f174aa
- author
- brice · · 2026-03-25 12:43 UTC
- committer
- brice · · 2026-03-25 12:47 UTC
- parent
- 784ca95
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
10 files · +98 -52
M internal/application/port/mocks/mocks.go +14 -8
| @@ -8211,8 +8211,8 @@ func (_m *MockPermissionDialogPresenter) EXPECT() *MockPermissionDialogPresenter | |||
| 8211 | 8211 | } | |
| 8212 | 8212 | ||
| 8213 | 8213 | // ShowPermissionDialog provides a mock function for the type MockPermissionDialogPresenter | |
| 8214 | - | func (_mock *MockPermissionDialogPresenter) ShowPermissionDialog(ctx context.Context, origin string, permTypes []entity.PermissionType, callback func(result port.PermissionDialogResult)) { | |
| 8215 | - | _mock.Called(ctx, origin, permTypes, callback) | |
| 8214 | + | func (_mock *MockPermissionDialogPresenter) ShowPermissionDialog(ctx context.Context, origin string, permTypes []entity.PermissionType, metadata entity.PermissionMetadata, callback func(result port.PermissionDialogResult)) { | |
| 8215 | + | _mock.Called(ctx, origin, permTypes, metadata, callback) | |
| 8216 | 8216 | return | |
| 8217 | 8217 | } | |
| 8218 | 8218 | ||
| @@ -8225,12 +8225,13 @@ type MockPermissionDialogPresenter_ShowPermissionDialog_Call struct { | |||
| 8225 | 8225 | // - ctx context.Context | |
| 8226 | 8226 | // - origin string | |
| 8227 | 8227 | // - permTypes []entity.PermissionType | |
| 8228 | + | // - metadata entity.PermissionMetadata | |
| 8228 | 8229 | // - callback func(result port.PermissionDialogResult) | |
| 8229 | - | func (_e *MockPermissionDialogPresenter_Expecter) ShowPermissionDialog(ctx interface{}, origin interface{}, permTypes interface{}, callback interface{}) *MockPermissionDialogPresenter_ShowPermissionDialog_Call { | |
| 8230 | - | return &MockPermissionDialogPresenter_ShowPermissionDialog_Call{Call: _e.mock.On("ShowPermissionDialog", ctx, origin, permTypes, callback)} | |
| 8230 | + | func (_e *MockPermissionDialogPresenter_Expecter) ShowPermissionDialog(ctx interface{}, origin interface{}, permTypes interface{}, metadata interface{}, callback interface{}) *MockPermissionDialogPresenter_ShowPermissionDialog_Call { | |
| 8231 | + | return &MockPermissionDialogPresenter_ShowPermissionDialog_Call{Call: _e.mock.On("ShowPermissionDialog", ctx, origin, permTypes, metadata, callback)} | |
| 8231 | 8232 | } | |
| 8232 | 8233 | ||
| 8233 | - | func (_c *MockPermissionDialogPresenter_ShowPermissionDialog_Call) Run(run func(ctx context.Context, origin string, permTypes []entity.PermissionType, callback func(result port.PermissionDialogResult))) *MockPermissionDialogPresenter_ShowPermissionDialog_Call { | |
| 8234 | + | func (_c *MockPermissionDialogPresenter_ShowPermissionDialog_Call) Run(run func(ctx context.Context, origin string, permTypes []entity.PermissionType, metadata entity.PermissionMetadata, callback func(result port.PermissionDialogResult))) *MockPermissionDialogPresenter_ShowPermissionDialog_Call { | |
| 8234 | 8235 | _c.Call.Run(func(args mock.Arguments) { | |
| 8235 | 8236 | var arg0 context.Context | |
| 8236 | 8237 | if args[0] != nil { | |
| @@ -8244,15 +8245,20 @@ func (_c *MockPermissionDialogPresenter_ShowPermissionDialog_Call) Run(run func( | |||
| 8244 | 8245 | if args[2] != nil { | |
| 8245 | 8246 | arg2 = args[2].([]entity.PermissionType) | |
| 8246 | 8247 | } | |
| 8247 | - | var arg3 func(result port.PermissionDialogResult) | |
| 8248 | + | var arg3 entity.PermissionMetadata | |
| 8248 | 8249 | if args[3] != nil { | |
| 8249 | - | arg3 = args[3].(func(result port.PermissionDialogResult)) | |
| 8250 | + | arg3 = args[3].(entity.PermissionMetadata) | |
| 8251 | + | } | |
| 8252 | + | var arg4 func(result port.PermissionDialogResult) | |
| 8253 | + | if args[4] != nil { | |
| 8254 | + | arg4 = args[4].(func(result port.PermissionDialogResult)) | |
| 8250 | 8255 | } | |
| 8251 | 8256 | run( | |
| 8252 | 8257 | arg0, | |
| 8253 | 8258 | arg1, | |
| 8254 | 8259 | arg2, | |
| 8255 | 8260 | arg3, | |
| 8261 | + | arg4, | |
| 8256 | 8262 | ) | |
| 8257 | 8263 | }) | |
| 8258 | 8264 | return _c | |
| @@ -8263,7 +8269,7 @@ func (_c *MockPermissionDialogPresenter_ShowPermissionDialog_Call) Return() *Moc | |||
| 8263 | 8269 | return _c | |
| 8264 | 8270 | } | |
| 8265 | 8271 | ||
| 8266 | - | func (_c *MockPermissionDialogPresenter_ShowPermissionDialog_Call) RunAndReturn(run func(ctx context.Context, origin string, permTypes []entity.PermissionType, callback func(result port.PermissionDialogResult))) *MockPermissionDialogPresenter_ShowPermissionDialog_Call { | |
| 8272 | + | func (_c *MockPermissionDialogPresenter_ShowPermissionDialog_Call) RunAndReturn(run func(ctx context.Context, origin string, permTypes []entity.PermissionType, metadata entity.PermissionMetadata, callback func(result port.PermissionDialogResult))) *MockPermissionDialogPresenter_ShowPermissionDialog_Call { | |
| 8267 | 8273 | _c.Run(run) | |
| 8268 | 8274 | return _c | |
| 8269 | 8275 | } | |
| @@ -8211,8 +8211,8 @@ func (_m *MockPermissionDialogPresenter) EXPECT() *MockPermissionDialogPresenter | |||||
| 8211 | } | 8211 | } | ||
| 8212 | 8212 | ||||
| 8213 | // ShowPermissionDialog provides a mock function for the type MockPermissionDialogPresenter | 8213 | // ShowPermissionDialog provides a mock function for the type MockPermissionDialogPresenter | ||
| 8214 | - | func (_mock *MockPermissionDialogPresenter) ShowPermissionDialog(ctx context.Context, origin string, permTypes []entity.PermissionType, callback func(result port.PermissionDialogResult)) { | 8214 | + | func (_mock *MockPermissionDialogPresenter) ShowPermissionDialog(ctx context.Context, origin string, permTypes []entity.PermissionType, metadata entity.PermissionMetadata, callback func(result port.PermissionDialogResult)) { |
| 8215 | - | _mock.Called(ctx, origin, permTypes, callback) | 8215 | + | _mock.Called(ctx, origin, permTypes, metadata, callback) |
| 8216 | return | 8216 | return | ||
| 8217 | } | 8217 | } | ||
| 8218 | 8218 | ||||
| @@ -8225,12 +8225,13 @@ type MockPermissionDialogPresenter_ShowPermissionDialog_Call struct { | |||||
| 8225 | // - ctx context.Context | 8225 | // - ctx context.Context | ||
| 8226 | // - origin string | 8226 | // - origin string | ||
| 8227 | // - permTypes []entity.PermissionType | 8227 | // - permTypes []entity.PermissionType | ||
| 8228 | + | // - metadata entity.PermissionMetadata | |||
| 8228 | // - callback func(result port.PermissionDialogResult) | 8229 | // - callback func(result port.PermissionDialogResult) | ||
| 8229 | - | func (_e *MockPermissionDialogPresenter_Expecter) ShowPermissionDialog(ctx interface{}, origin interface{}, permTypes interface{}, callback interface{}) *MockPermissionDialogPresenter_ShowPermissionDialog_Call { | 8230 | + | func (_e *MockPermissionDialogPresenter_Expecter) ShowPermissionDialog(ctx interface{}, origin interface{}, permTypes interface{}, metadata interface{}, callback interface{}) *MockPermissionDialogPresenter_ShowPermissionDialog_Call { |
| 8230 | - | return &MockPermissionDialogPresenter_ShowPermissionDialog_Call{Call: _e.mock.On("ShowPermissionDialog", ctx, origin, permTypes, callback)} | 8231 | + | return &MockPermissionDialogPresenter_ShowPermissionDialog_Call{Call: _e.mock.On("ShowPermissionDialog", ctx, origin, permTypes, metadata, callback)} |
| 8231 | } | 8232 | } | ||
| 8232 | 8233 | ||||
| 8233 | - | func (_c *MockPermissionDialogPresenter_ShowPermissionDialog_Call) Run(run func(ctx context.Context, origin string, permTypes []entity.PermissionType, callback func(result port.PermissionDialogResult))) *MockPermissionDialogPresenter_ShowPermissionDialog_Call { | 8234 | + | func (_c *MockPermissionDialogPresenter_ShowPermissionDialog_Call) Run(run func(ctx context.Context, origin string, permTypes []entity.PermissionType, metadata entity.PermissionMetadata, callback func(result port.PermissionDialogResult))) *MockPermissionDialogPresenter_ShowPermissionDialog_Call { |
| 8234 | _c.Call.Run(func(args mock.Arguments) { | 8235 | _c.Call.Run(func(args mock.Arguments) { | ||
| 8235 | var arg0 context.Context | 8236 | var arg0 context.Context | ||
| 8236 | if args[0] != nil { | 8237 | if args[0] != nil { | ||
| @@ -8244,15 +8245,20 @@ func (_c *MockPermissionDialogPresenter_ShowPermissionDialog_Call) Run(run func( | |||||
| 8244 | if args[2] != nil { | 8245 | if args[2] != nil { | ||
| 8245 | arg2 = args[2].([]entity.PermissionType) | 8246 | arg2 = args[2].([]entity.PermissionType) | ||
| 8246 | } | 8247 | } | ||
| 8247 | - | var arg3 func(result port.PermissionDialogResult) | 8248 | + | var arg3 entity.PermissionMetadata |
| 8248 | if args[3] != nil { | 8249 | if args[3] != nil { | ||
| 8249 | - | arg3 = args[3].(func(result port.PermissionDialogResult)) | 8250 | + | arg3 = args[3].(entity.PermissionMetadata) |
| 8251 | + | } | |||
| 8252 | + | var arg4 func(result port.PermissionDialogResult) | |||
| 8253 | + | if args[4] != nil { | |||
| 8254 | + | arg4 = args[4].(func(result port.PermissionDialogResult)) | |||
| 8250 | } | 8255 | } | ||
| 8251 | run( | 8256 | run( | ||
| 8252 | arg0, | 8257 | arg0, | ||
| 8253 | arg1, | 8258 | arg1, | ||
| 8254 | arg2, | 8259 | arg2, | ||
| 8255 | arg3, | 8260 | arg3, | ||
| 8261 | + | arg4, | |||
| 8256 | ) | 8262 | ) | ||
| 8257 | }) | 8263 | }) | ||
| 8258 | return _c | 8264 | return _c | ||
| @@ -8263,7 +8269,7 @@ func (_c *MockPermissionDialogPresenter_ShowPermissionDialog_Call) Return() *Moc | |||||
| 8263 | return _c | 8269 | return _c | ||
| 8264 | } | 8270 | } | ||
| 8265 | 8271 | ||||
| 8266 | - | func (_c *MockPermissionDialogPresenter_ShowPermissionDialog_Call) RunAndReturn(run func(ctx context.Context, origin string, permTypes []entity.PermissionType, callback func(result port.PermissionDialogResult))) *MockPermissionDialogPresenter_ShowPermissionDialog_Call { | 8272 | + | func (_c *MockPermissionDialogPresenter_ShowPermissionDialog_Call) RunAndReturn(run func(ctx context.Context, origin string, permTypes []entity.PermissionType, metadata entity.PermissionMetadata, callback func(result port.PermissionDialogResult))) *MockPermissionDialogPresenter_ShowPermissionDialog_Call { |
| 8267 | _c.Run(run) | 8273 | _c.Run(run) | ||
| 8268 | return _c | 8274 | return _c | ||
| 8269 | } | 8275 | } | ||
M internal/application/port/permission.go +2 -0
| @@ -26,11 +26,13 @@ type PermissionDialogPresenter interface { | |||
| 26 | 26 | // - ctx: context for cancellation and logging | |
| 27 | 27 | // - origin: the website origin requesting permission (e.g., "https://meet.example.com") | |
| 28 | 28 | // - permTypes: the types of permissions being requested | |
| 29 | + | // - metadata: permission-type-specific context (e.g., "requesting_domain" for website_data_access) | |
| 29 | 30 | // - callback: invoked when the user makes a decision or dialog is dismissed | |
| 30 | 31 | ShowPermissionDialog( | |
| 31 | 32 | ctx context.Context, | |
| 32 | 33 | origin string, | |
| 33 | 34 | permTypes []entity.PermissionType, | |
| 35 | + | metadata entity.PermissionMetadata, | |
| 34 | 36 | callback func(result PermissionDialogResult), | |
| 35 | 37 | ) | |
| 36 | 38 | } | |
| @@ -26,11 +26,13 @@ type PermissionDialogPresenter interface { | |||||
| 26 | // - ctx: context for cancellation and logging | 26 | // - ctx: context for cancellation and logging | ||
| 27 | // - origin: the website origin requesting permission (e.g., "https://meet.example.com") | 27 | // - origin: the website origin requesting permission (e.g., "https://meet.example.com") | ||
| 28 | // - permTypes: the types of permissions being requested | 28 | // - permTypes: the types of permissions being requested | ||
| 29 | + | // - metadata: permission-type-specific context (e.g., "requesting_domain" for website_data_access) | |||
| 29 | // - callback: invoked when the user makes a decision or dialog is dismissed | 30 | // - callback: invoked when the user makes a decision or dialog is dismissed | ||
| 30 | ShowPermissionDialog( | 31 | ShowPermissionDialog( | ||
| 31 | ctx context.Context, | 32 | ctx context.Context, | ||
| 32 | origin string, | 33 | origin string, | ||
| 33 | permTypes []entity.PermissionType, | 34 | permTypes []entity.PermissionType, | ||
| 35 | + | metadata entity.PermissionMetadata, | |||
| 34 | callback func(result PermissionDialogResult), | 36 | callback func(result PermissionDialogResult), | ||
| 35 | ) | 37 | ) | ||
| 36 | } | 38 | } | ||
M internal/application/port/webview.go +2 -1
| @@ -107,7 +107,8 @@ type WebViewCallbacks struct { | |||
| 107 | 107 | // OnPermissionRequest is called when a site requests permission (mic, camera, screen sharing). | |
| 108 | 108 | // Return true to indicate the request was handled. Call allow()/deny() to respond. | |
| 109 | 109 | // The permission types are passed as strings ("microphone", "camera", "display", etc.). | |
| 110 | - | OnPermissionRequest func(origin string, permTypes []string, allow, deny func()) bool | |
| 110 | + | // The metadata map carries permission-type-specific context (e.g., "requesting_domain" for website_data_access). | |
| 111 | + | OnPermissionRequest func(origin string, permTypes []string, metadata map[string]string, allow, deny func()) bool | |
| 111 | 112 | ||
| 112 | 113 | // OnLinkMiddleClick is called when a link is middle-clicked. | |
| 113 | 114 | // Return true if handled (blocks default navigation). | |
| @@ -107,7 +107,8 @@ type WebViewCallbacks struct { | |||||
| 107 | // OnPermissionRequest is called when a site requests permission (mic, camera, screen sharing). | 107 | // OnPermissionRequest is called when a site requests permission (mic, camera, screen sharing). | ||
| 108 | // Return true to indicate the request was handled. Call allow()/deny() to respond. | 108 | // Return true to indicate the request was handled. Call allow()/deny() to respond. | ||
| 109 | // The permission types are passed as strings ("microphone", "camera", "display", etc.). | 109 | // The permission types are passed as strings ("microphone", "camera", "display", etc.). | ||
| 110 | - | OnPermissionRequest func(origin string, permTypes []string, allow, deny func()) bool | 110 | + | // The metadata map carries permission-type-specific context (e.g., "requesting_domain" for website_data_access). |
| 111 | + | OnPermissionRequest func(origin string, permTypes []string, metadata map[string]string, allow, deny func()) bool | |||
| 111 | 112 | ||||
| 112 | // OnLinkMiddleClick is called when a link is middle-clicked. | 113 | // OnLinkMiddleClick is called when a link is middle-clicked. | ||
| 113 | // Return true if handled (blocks default navigation). | 114 | // Return true if handled (blocks default navigation). | ||
M internal/application/usecase/handle_permission.go +5 -2
| @@ -79,11 +79,13 @@ func (uc *HandlePermissionUseCase) getDialog() port.PermissionDialogPresenter { | |||
| 79 | 79 | // - ctx: context for cancellation and logging | |
| 80 | 80 | // - origin: the website origin (URI) requesting permission | |
| 81 | 81 | // - permTypes: the types of permissions being requested | |
| 82 | + | // - metadata: permission-type-specific context (e.g., "requesting_domain" for website_data_access) | |
| 82 | 83 | // - callback: callbacks to allow or deny the request (must call one) | |
| 83 | 84 | func (uc *HandlePermissionUseCase) HandlePermissionRequest( | |
| 84 | 85 | ctx context.Context, | |
| 85 | 86 | origin string, | |
| 86 | 87 | permTypes []entity.PermissionType, | |
| 88 | + | metadata entity.PermissionMetadata, | |
| 87 | 89 | callback PermissionCallback, | |
| 88 | 90 | ) { | |
| 89 | 91 | log := uc.logger(ctx).With(). | |
| @@ -135,7 +137,7 @@ func (uc *HandlePermissionUseCase) HandlePermissionRequest( | |||
| 135 | 137 | } | |
| 136 | 138 | ||
| 137 | 139 | // Show dialog for undecided permissions | |
| 138 | - | uc.showPermissionDialog(ctx, origin, permTypes, callback) | |
| 140 | + | uc.showPermissionDialog(ctx, origin, permTypes, metadata, callback) | |
| 139 | 141 | } | |
| 140 | 142 | ||
| 141 | 143 | // QueryPermissionState returns the current permission state for the W3C Permissions API. | |
| @@ -249,6 +251,7 @@ func (uc *HandlePermissionUseCase) showPermissionDialog( | |||
| 249 | 251 | ctx context.Context, | |
| 250 | 252 | origin string, | |
| 251 | 253 | permTypes []entity.PermissionType, | |
| 254 | + | metadata entity.PermissionMetadata, | |
| 252 | 255 | callback PermissionCallback, | |
| 253 | 256 | ) { | |
| 254 | 257 | log := uc.logger(ctx) | |
| @@ -262,7 +265,7 @@ func (uc *HandlePermissionUseCase) showPermissionDialog( | |||
| 262 | 265 | return | |
| 263 | 266 | } | |
| 264 | 267 | ||
| 265 | - | dialog.ShowPermissionDialog(ctx, origin, permTypes, func(result port.PermissionDialogResult) { | |
| 268 | + | dialog.ShowPermissionDialog(ctx, origin, permTypes, metadata, func(result port.PermissionDialogResult) { | |
| 266 | 269 | if result.Allowed { | |
| 267 | 270 | log.Debug().Bool("persistent", result.Persistent).Msg("user allowed permission") | |
| 268 | 271 | callback.Allow() | |
| @@ -79,11 +79,13 @@ func (uc *HandlePermissionUseCase) getDialog() port.PermissionDialogPresenter { | |||||
| 79 | // - ctx: context for cancellation and logging | 79 | // - ctx: context for cancellation and logging | ||
| 80 | // - origin: the website origin (URI) requesting permission | 80 | // - origin: the website origin (URI) requesting permission | ||
| 81 | // - permTypes: the types of permissions being requested | 81 | // - permTypes: the types of permissions being requested | ||
| 82 | + | // - metadata: permission-type-specific context (e.g., "requesting_domain" for website_data_access) | |||
| 82 | // - callback: callbacks to allow or deny the request (must call one) | 83 | // - callback: callbacks to allow or deny the request (must call one) | ||
| 83 | func (uc *HandlePermissionUseCase) HandlePermissionRequest( | 84 | func (uc *HandlePermissionUseCase) HandlePermissionRequest( | ||
| 84 | ctx context.Context, | 85 | ctx context.Context, | ||
| 85 | origin string, | 86 | origin string, | ||
| 86 | permTypes []entity.PermissionType, | 87 | permTypes []entity.PermissionType, | ||
| 88 | + | metadata entity.PermissionMetadata, | |||
| 87 | callback PermissionCallback, | 89 | callback PermissionCallback, | ||
| 88 | ) { | 90 | ) { | ||
| 89 | log := uc.logger(ctx).With(). | 91 | log := uc.logger(ctx).With(). | ||
| @@ -135,7 +137,7 @@ func (uc *HandlePermissionUseCase) HandlePermissionRequest( | |||||
| 135 | } | 137 | } | ||
| 136 | 138 | ||||
| 137 | // Show dialog for undecided permissions | 139 | // Show dialog for undecided permissions | ||
| 138 | - | uc.showPermissionDialog(ctx, origin, permTypes, callback) | 140 | + | uc.showPermissionDialog(ctx, origin, permTypes, metadata, callback) |
| 139 | } | 141 | } | ||
| 140 | 142 | ||||
| 141 | // QueryPermissionState returns the current permission state for the W3C Permissions API. | 143 | // QueryPermissionState returns the current permission state for the W3C Permissions API. | ||
| @@ -249,6 +251,7 @@ func (uc *HandlePermissionUseCase) showPermissionDialog( | |||||
| 249 | ctx context.Context, | 251 | ctx context.Context, | ||
| 250 | origin string, | 252 | origin string, | ||
| 251 | permTypes []entity.PermissionType, | 253 | permTypes []entity.PermissionType, | ||
| 254 | + | metadata entity.PermissionMetadata, | |||
| 252 | callback PermissionCallback, | 255 | callback PermissionCallback, | ||
| 253 | ) { | 256 | ) { | ||
| 254 | log := uc.logger(ctx) | 257 | log := uc.logger(ctx) | ||
| @@ -262,7 +265,7 @@ func (uc *HandlePermissionUseCase) showPermissionDialog( | |||||
| 262 | return | 265 | return | ||
| 263 | } | 266 | } | ||
| 264 | 267 | ||||
| 265 | - | dialog.ShowPermissionDialog(ctx, origin, permTypes, func(result port.PermissionDialogResult) { | 268 | + | dialog.ShowPermissionDialog(ctx, origin, permTypes, metadata, func(result port.PermissionDialogResult) { |
| 266 | if result.Allowed { | 269 | if result.Allowed { | ||
| 267 | log.Debug().Bool("persistent", result.Persistent).Msg("user allowed permission") | 270 | log.Debug().Bool("persistent", result.Persistent).Msg("user allowed permission") | ||
| 268 | callback.Allow() | 271 | callback.Allow() | ||
M internal/application/usecase/handle_permission_test.go +25 -19
| @@ -39,7 +39,7 @@ func TestHandlePermissionUseCase_AutoAllowDisplayCapture(t *testing.T) { | |||
| 39 | 39 | // Display capture should be auto-allowed (portal handles UI) | |
| 40 | 40 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | |
| 41 | 41 | entity.PermissionTypeDisplay, | |
| 42 | - | }, callback) | |
| 42 | + | }, nil, callback) | |
| 43 | 43 | ||
| 44 | 44 | assert.True(t, allowed, "display capture should be auto-allowed") | |
| 45 | 45 | assert.False(t, denied) | |
| @@ -65,7 +65,7 @@ func TestHandlePermissionUseCase_AutoAllowDeviceInfo(t *testing.T) { | |||
| 65 | 65 | // Device info should be auto-allowed | |
| 66 | 66 | uc.HandlePermissionRequest(ctx, "https://example.com", []entity.PermissionType{ | |
| 67 | 67 | entity.PermissionTypeDeviceInfo, | |
| 68 | - | }, callback) | |
| 68 | + | }, nil, callback) | |
| 69 | 69 | ||
| 70 | 70 | assert.True(t, allowed, "device info should be auto-allowed") | |
| 71 | 71 | } | |
| @@ -94,7 +94,7 @@ func TestHandlePermissionUseCase_StoredPermissionGranted(t *testing.T) { | |||
| 94 | 94 | ||
| 95 | 95 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | |
| 96 | 96 | entity.PermissionTypeMicrophone, | |
| 97 | - | }, callback) | |
| 97 | + | }, nil, callback) | |
| 98 | 98 | ||
| 99 | 99 | assert.True(t, allowed, "should use stored granted permission") | |
| 100 | 100 | dialog.AssertNotCalled(t, "ShowPermissionDialog") | |
| @@ -124,7 +124,7 @@ func TestHandlePermissionUseCase_StoredPermissionDenied(t *testing.T) { | |||
| 124 | 124 | ||
| 125 | 125 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | |
| 126 | 126 | entity.PermissionTypeCamera, | |
| 127 | - | }, callback) | |
| 127 | + | }, nil, callback) | |
| 128 | 128 | ||
| 129 | 129 | assert.True(t, denied, "should use stored denied permission") | |
| 130 | 130 | dialog.AssertNotCalled(t, "ShowPermissionDialog") | |
| @@ -150,13 +150,13 @@ func TestHandlePermissionUseCase_ShowDialogForPrompt(t *testing.T) { | |||
| 150 | 150 | // Dialog shows and user clicks Allow (not persistent) | |
| 151 | 151 | dialog.EXPECT().ShowPermissionDialog(mock.Anything, "https://meet.example.com", []entity.PermissionType{ | |
| 152 | 152 | entity.PermissionTypeMicrophone, | |
| 153 | - | }, mock.Anything).Run(func(_ context.Context, _ string, _ []entity.PermissionType, callback func(port.PermissionDialogResult)) { | |
| 153 | + | }, mock.Anything, mock.Anything).Run(func(_ context.Context, _ string, _ []entity.PermissionType, _ entity.PermissionMetadata, callback func(port.PermissionDialogResult)) { | |
| 154 | 154 | callback(port.PermissionDialogResult{Allowed: true, Persistent: false}) | |
| 155 | 155 | }) | |
| 156 | 156 | ||
| 157 | 157 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | |
| 158 | 158 | entity.PermissionTypeMicrophone, | |
| 159 | - | }, callback) | |
| 159 | + | }, nil, callback) | |
| 160 | 160 | ||
| 161 | 161 | assert.True(t, allowed, "user allowed permission") | |
| 162 | 162 | // Should not persist since Persistent=false | |
| @@ -183,7 +183,7 @@ func TestHandlePermissionUseCase_PersistAllowed(t *testing.T) { | |||
| 183 | 183 | // Dialog shows and user clicks "Always Allow" | |
| 184 | 184 | dialog.EXPECT().ShowPermissionDialog(mock.Anything, "https://meet.example.com", []entity.PermissionType{ | |
| 185 | 185 | entity.PermissionTypeMicrophone, | |
| 186 | - | }, mock.Anything).Run(func(_ context.Context, _ string, _ []entity.PermissionType, cb func(port.PermissionDialogResult)) { | |
| 186 | + | }, mock.Anything, mock.Anything).Run(func(_ context.Context, _ string, _ []entity.PermissionType, _ entity.PermissionMetadata, cb func(port.PermissionDialogResult)) { | |
| 187 | 187 | cb(port.PermissionDialogResult{Allowed: true, Persistent: true}) | |
| 188 | 188 | }) | |
| 189 | 189 | ||
| @@ -197,7 +197,7 @@ func TestHandlePermissionUseCase_PersistAllowed(t *testing.T) { | |||
| 197 | 197 | ||
| 198 | 198 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | |
| 199 | 199 | entity.PermissionTypeMicrophone, | |
| 200 | - | }, callback) | |
| 200 | + | }, nil, callback) | |
| 201 | 201 | ||
| 202 | 202 | assert.True(t, allowed) | |
| 203 | 203 | } | |
| @@ -222,7 +222,7 @@ func TestHandlePermissionUseCase_PersistDenied(t *testing.T) { | |||
| 222 | 222 | // Dialog shows and user clicks "Always Deny" | |
| 223 | 223 | dialog.EXPECT().ShowPermissionDialog(mock.Anything, "https://meet.example.com", []entity.PermissionType{ | |
| 224 | 224 | entity.PermissionTypeCamera, | |
| 225 | - | }, mock.Anything).Run(func(_ context.Context, _ string, _ []entity.PermissionType, cb func(port.PermissionDialogResult)) { | |
| 225 | + | }, mock.Anything, mock.Anything).Run(func(_ context.Context, _ string, _ []entity.PermissionType, _ entity.PermissionMetadata, cb func(port.PermissionDialogResult)) { | |
| 226 | 226 | // User denied but wants to persist this decision | |
| 227 | 227 | cb(port.PermissionDialogResult{Allowed: false, Persistent: true}) | |
| 228 | 228 | }) | |
| @@ -235,7 +235,7 @@ func TestHandlePermissionUseCase_PersistDenied(t *testing.T) { | |||
| 235 | 235 | ||
| 236 | 236 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | |
| 237 | 237 | entity.PermissionTypeCamera, | |
| 238 | - | }, callback) | |
| 238 | + | }, nil, callback) | |
| 239 | 239 | ||
| 240 | 240 | assert.True(t, denied) | |
| 241 | 241 | } | |
| @@ -256,7 +256,7 @@ func TestHandlePermissionUseCase_EmptyOrigin(t *testing.T) { | |||
| 256 | 256 | // Empty origin should be denied | |
| 257 | 257 | uc.HandlePermissionRequest(ctx, "", []entity.PermissionType{ | |
| 258 | 258 | entity.PermissionTypeMicrophone, | |
| 259 | - | }, callback) | |
| 259 | + | }, nil, callback) | |
| 260 | 260 | ||
| 261 | 261 | assert.True(t, denied, "empty origin should be denied") | |
| 262 | 262 | } | |
| @@ -292,7 +292,7 @@ func TestHandlePermissionUseCase_CombinedPermissions(t *testing.T) { | |||
| 292 | 292 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | |
| 293 | 293 | entity.PermissionTypeMicrophone, | |
| 294 | 294 | entity.PermissionTypeCamera, | |
| 295 | - | }, callback) | |
| 295 | + | }, nil, callback) | |
| 296 | 296 | ||
| 297 | 297 | assert.True(t, denied, "if any permission is denied, whole request is denied") | |
| 298 | 298 | } | |
| @@ -398,7 +398,7 @@ func TestHandlePermissionUseCase_NonPersistableNotSaved(t *testing.T) { | |||
| 398 | 398 | ||
| 399 | 399 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | |
| 400 | 400 | entity.PermissionTypeDisplay, | |
| 401 | - | }, callback) | |
| 401 | + | }, nil, callback) | |
| 402 | 402 | ||
| 403 | 403 | assert.True(t, allowed) | |
| 404 | 404 | permRepo.AssertNotCalled(t, "Set") | |
| @@ -423,7 +423,7 @@ func TestHandlePermissionUseCase_NoDialogPresenter(t *testing.T) { | |||
| 423 | 423 | ||
| 424 | 424 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | |
| 425 | 425 | entity.PermissionTypeMicrophone, | |
| 426 | - | }, callback) | |
| 426 | + | }, nil, callback) | |
| 427 | 427 | ||
| 428 | 428 | assert.True(t, denied, "should deny when no dialog presenter is available") | |
| 429 | 429 | } | |
| @@ -450,7 +450,7 @@ func TestHandlePermissionUseCase_DisplayOverrideDenied(t *testing.T) { | |||
| 450 | 450 | ||
| 451 | 451 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | |
| 452 | 452 | entity.PermissionTypeDisplay, | |
| 453 | - | }, callback) | |
| 453 | + | }, nil, callback) | |
| 454 | 454 | ||
| 455 | 455 | assert.True(t, denied, "display should be denied when manual override is denied") | |
| 456 | 456 | dialog.AssertNotCalled(t, "ShowPermissionDialog") | |
| @@ -526,7 +526,7 @@ func TestHandlePermissionUseCase_UsesInjectedLoggerFactory(t *testing.T) { | |||
| 526 | 526 | } | |
| 527 | 527 | ||
| 528 | 528 | uc := usecase.NewHandlePermissionUseCase(permRepo, dialog, loggerFactory) | |
| 529 | - | uc.HandlePermissionRequest(ctx, "", []entity.PermissionType{entity.PermissionTypeMicrophone}, usecase.PermissionCallback{ | |
| 529 | + | uc.HandlePermissionRequest(ctx, "", []entity.PermissionType{entity.PermissionTypeMicrophone}, nil, usecase.PermissionCallback{ | |
| 530 | 530 | Allow: func() {}, | |
| 531 | 531 | Deny: func() {}, | |
| 532 | 532 | }) | |
| @@ -551,13 +551,19 @@ func TestHandlePermissionUseCase_WebsiteDataAccess_ShowsDialogAndPersists(t *tes | |||
| 551 | 551 | Deny: func() {}, | |
| 552 | 552 | } | |
| 553 | 553 | ||
| 554 | + | meta := entity.PermissionMetadata{ | |
| 555 | + | "requesting_domain": "accounts.google.com", | |
| 556 | + | "current_domain": "shop.example.com", | |
| 557 | + | } | |
| 558 | + | ||
| 554 | 559 | // Dialog shows and user clicks "Always Allow" | |
| 555 | 560 | dialog.EXPECT().ShowPermissionDialog( | |
| 556 | 561 | mock.Anything, | |
| 557 | 562 | "https://shop.example.com", | |
| 558 | 563 | []entity.PermissionType{entity.PermissionTypeWebsiteDataAccess}, | |
| 564 | + | meta, | |
| 559 | 565 | mock.Anything, | |
| 560 | - | ).Run(func(_ context.Context, _ string, _ []entity.PermissionType, cb func(port.PermissionDialogResult)) { | |
| 566 | + | ).Run(func(_ context.Context, _ string, _ []entity.PermissionType, _ entity.PermissionMetadata, cb func(port.PermissionDialogResult)) { | |
| 561 | 567 | cb(port.PermissionDialogResult{Allowed: true, Persistent: true}) | |
| 562 | 568 | }) | |
| 563 | 569 | ||
| @@ -571,7 +577,7 @@ func TestHandlePermissionUseCase_WebsiteDataAccess_ShowsDialogAndPersists(t *tes | |||
| 571 | 577 | ||
| 572 | 578 | uc.HandlePermissionRequest(ctx, "https://shop.example.com", []entity.PermissionType{ | |
| 573 | 579 | entity.PermissionTypeWebsiteDataAccess, | |
| 574 | - | }, callback) | |
| 580 | + | }, meta, callback) | |
| 575 | 581 | ||
| 576 | 582 | assert.True(t, allowed) | |
| 577 | 583 | } | |
| @@ -599,7 +605,7 @@ func TestHandlePermissionUseCase_WebsiteDataAccess_UsesStoredGrant(t *testing.T) | |||
| 599 | 605 | ||
| 600 | 606 | uc.HandlePermissionRequest(ctx, "https://shop.example.com", []entity.PermissionType{ | |
| 601 | 607 | entity.PermissionTypeWebsiteDataAccess, | |
| 602 | - | }, callback) | |
| 608 | + | }, entity.PermissionMetadata{"requesting_domain": "accounts.google.com", "current_domain": "shop.example.com"}, callback) | |
| 603 | 609 | ||
| 604 | 610 | assert.True(t, allowed, "should use stored granted permission without dialog") | |
| 605 | 611 | dialog.AssertNotCalled(t, "ShowPermissionDialog") | |
| @@ -39,7 +39,7 @@ func TestHandlePermissionUseCase_AutoAllowDisplayCapture(t *testing.T) { | |||||
| 39 | // Display capture should be auto-allowed (portal handles UI) | 39 | // Display capture should be auto-allowed (portal handles UI) | ||
| 40 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | 40 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | ||
| 41 | entity.PermissionTypeDisplay, | 41 | entity.PermissionTypeDisplay, | ||
| 42 | - | }, callback) | 42 | + | }, nil, callback) |
| 43 | 43 | ||||
| 44 | assert.True(t, allowed, "display capture should be auto-allowed") | 44 | assert.True(t, allowed, "display capture should be auto-allowed") | ||
| 45 | assert.False(t, denied) | 45 | assert.False(t, denied) | ||
| @@ -65,7 +65,7 @@ func TestHandlePermissionUseCase_AutoAllowDeviceInfo(t *testing.T) { | |||||
| 65 | // Device info should be auto-allowed | 65 | // Device info should be auto-allowed | ||
| 66 | uc.HandlePermissionRequest(ctx, "https://example.com", []entity.PermissionType{ | 66 | uc.HandlePermissionRequest(ctx, "https://example.com", []entity.PermissionType{ | ||
| 67 | entity.PermissionTypeDeviceInfo, | 67 | entity.PermissionTypeDeviceInfo, | ||
| 68 | - | }, callback) | 68 | + | }, nil, callback) |
| 69 | 69 | ||||
| 70 | assert.True(t, allowed, "device info should be auto-allowed") | 70 | assert.True(t, allowed, "device info should be auto-allowed") | ||
| 71 | } | 71 | } | ||
| @@ -94,7 +94,7 @@ func TestHandlePermissionUseCase_StoredPermissionGranted(t *testing.T) { | |||||
| 94 | 94 | ||||
| 95 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | 95 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | ||
| 96 | entity.PermissionTypeMicrophone, | 96 | entity.PermissionTypeMicrophone, | ||
| 97 | - | }, callback) | 97 | + | }, nil, callback) |
| 98 | 98 | ||||
| 99 | assert.True(t, allowed, "should use stored granted permission") | 99 | assert.True(t, allowed, "should use stored granted permission") | ||
| 100 | dialog.AssertNotCalled(t, "ShowPermissionDialog") | 100 | dialog.AssertNotCalled(t, "ShowPermissionDialog") | ||
| @@ -124,7 +124,7 @@ func TestHandlePermissionUseCase_StoredPermissionDenied(t *testing.T) { | |||||
| 124 | 124 | ||||
| 125 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | 125 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | ||
| 126 | entity.PermissionTypeCamera, | 126 | entity.PermissionTypeCamera, | ||
| 127 | - | }, callback) | 127 | + | }, nil, callback) |
| 128 | 128 | ||||
| 129 | assert.True(t, denied, "should use stored denied permission") | 129 | assert.True(t, denied, "should use stored denied permission") | ||
| 130 | dialog.AssertNotCalled(t, "ShowPermissionDialog") | 130 | dialog.AssertNotCalled(t, "ShowPermissionDialog") | ||
| @@ -150,13 +150,13 @@ func TestHandlePermissionUseCase_ShowDialogForPrompt(t *testing.T) { | |||||
| 150 | // Dialog shows and user clicks Allow (not persistent) | 150 | // Dialog shows and user clicks Allow (not persistent) | ||
| 151 | dialog.EXPECT().ShowPermissionDialog(mock.Anything, "https://meet.example.com", []entity.PermissionType{ | 151 | dialog.EXPECT().ShowPermissionDialog(mock.Anything, "https://meet.example.com", []entity.PermissionType{ | ||
| 152 | entity.PermissionTypeMicrophone, | 152 | entity.PermissionTypeMicrophone, | ||
| 153 | - | }, mock.Anything).Run(func(_ context.Context, _ string, _ []entity.PermissionType, callback func(port.PermissionDialogResult)) { | 153 | + | }, mock.Anything, mock.Anything).Run(func(_ context.Context, _ string, _ []entity.PermissionType, _ entity.PermissionMetadata, callback func(port.PermissionDialogResult)) { |
| 154 | callback(port.PermissionDialogResult{Allowed: true, Persistent: false}) | 154 | callback(port.PermissionDialogResult{Allowed: true, Persistent: false}) | ||
| 155 | }) | 155 | }) | ||
| 156 | 156 | ||||
| 157 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | 157 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | ||
| 158 | entity.PermissionTypeMicrophone, | 158 | entity.PermissionTypeMicrophone, | ||
| 159 | - | }, callback) | 159 | + | }, nil, callback) |
| 160 | 160 | ||||
| 161 | assert.True(t, allowed, "user allowed permission") | 161 | assert.True(t, allowed, "user allowed permission") | ||
| 162 | // Should not persist since Persistent=false | 162 | // Should not persist since Persistent=false | ||
| @@ -183,7 +183,7 @@ func TestHandlePermissionUseCase_PersistAllowed(t *testing.T) { | |||||
| 183 | // Dialog shows and user clicks "Always Allow" | 183 | // Dialog shows and user clicks "Always Allow" | ||
| 184 | dialog.EXPECT().ShowPermissionDialog(mock.Anything, "https://meet.example.com", []entity.PermissionType{ | 184 | dialog.EXPECT().ShowPermissionDialog(mock.Anything, "https://meet.example.com", []entity.PermissionType{ | ||
| 185 | entity.PermissionTypeMicrophone, | 185 | entity.PermissionTypeMicrophone, | ||
| 186 | - | }, mock.Anything).Run(func(_ context.Context, _ string, _ []entity.PermissionType, cb func(port.PermissionDialogResult)) { | 186 | + | }, mock.Anything, mock.Anything).Run(func(_ context.Context, _ string, _ []entity.PermissionType, _ entity.PermissionMetadata, cb func(port.PermissionDialogResult)) { |
| 187 | cb(port.PermissionDialogResult{Allowed: true, Persistent: true}) | 187 | cb(port.PermissionDialogResult{Allowed: true, Persistent: true}) | ||
| 188 | }) | 188 | }) | ||
| 189 | 189 | ||||
| @@ -197,7 +197,7 @@ func TestHandlePermissionUseCase_PersistAllowed(t *testing.T) { | |||||
| 197 | 197 | ||||
| 198 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | 198 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | ||
| 199 | entity.PermissionTypeMicrophone, | 199 | entity.PermissionTypeMicrophone, | ||
| 200 | - | }, callback) | 200 | + | }, nil, callback) |
| 201 | 201 | ||||
| 202 | assert.True(t, allowed) | 202 | assert.True(t, allowed) | ||
| 203 | } | 203 | } | ||
| @@ -222,7 +222,7 @@ func TestHandlePermissionUseCase_PersistDenied(t *testing.T) { | |||||
| 222 | // Dialog shows and user clicks "Always Deny" | 222 | // Dialog shows and user clicks "Always Deny" | ||
| 223 | dialog.EXPECT().ShowPermissionDialog(mock.Anything, "https://meet.example.com", []entity.PermissionType{ | 223 | dialog.EXPECT().ShowPermissionDialog(mock.Anything, "https://meet.example.com", []entity.PermissionType{ | ||
| 224 | entity.PermissionTypeCamera, | 224 | entity.PermissionTypeCamera, | ||
| 225 | - | }, mock.Anything).Run(func(_ context.Context, _ string, _ []entity.PermissionType, cb func(port.PermissionDialogResult)) { | 225 | + | }, mock.Anything, mock.Anything).Run(func(_ context.Context, _ string, _ []entity.PermissionType, _ entity.PermissionMetadata, cb func(port.PermissionDialogResult)) { |
| 226 | // User denied but wants to persist this decision | 226 | // User denied but wants to persist this decision | ||
| 227 | cb(port.PermissionDialogResult{Allowed: false, Persistent: true}) | 227 | cb(port.PermissionDialogResult{Allowed: false, Persistent: true}) | ||
| 228 | }) | 228 | }) | ||
| @@ -235,7 +235,7 @@ func TestHandlePermissionUseCase_PersistDenied(t *testing.T) { | |||||
| 235 | 235 | ||||
| 236 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | 236 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | ||
| 237 | entity.PermissionTypeCamera, | 237 | entity.PermissionTypeCamera, | ||
| 238 | - | }, callback) | 238 | + | }, nil, callback) |
| 239 | 239 | ||||
| 240 | assert.True(t, denied) | 240 | assert.True(t, denied) | ||
| 241 | } | 241 | } | ||
| @@ -256,7 +256,7 @@ func TestHandlePermissionUseCase_EmptyOrigin(t *testing.T) { | |||||
| 256 | // Empty origin should be denied | 256 | // Empty origin should be denied | ||
| 257 | uc.HandlePermissionRequest(ctx, "", []entity.PermissionType{ | 257 | uc.HandlePermissionRequest(ctx, "", []entity.PermissionType{ | ||
| 258 | entity.PermissionTypeMicrophone, | 258 | entity.PermissionTypeMicrophone, | ||
| 259 | - | }, callback) | 259 | + | }, nil, callback) |
| 260 | 260 | ||||
| 261 | assert.True(t, denied, "empty origin should be denied") | 261 | assert.True(t, denied, "empty origin should be denied") | ||
| 262 | } | 262 | } | ||
| @@ -292,7 +292,7 @@ func TestHandlePermissionUseCase_CombinedPermissions(t *testing.T) { | |||||
| 292 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | 292 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | ||
| 293 | entity.PermissionTypeMicrophone, | 293 | entity.PermissionTypeMicrophone, | ||
| 294 | entity.PermissionTypeCamera, | 294 | entity.PermissionTypeCamera, | ||
| 295 | - | }, callback) | 295 | + | }, nil, callback) |
| 296 | 296 | ||||
| 297 | assert.True(t, denied, "if any permission is denied, whole request is denied") | 297 | assert.True(t, denied, "if any permission is denied, whole request is denied") | ||
| 298 | } | 298 | } | ||
| @@ -398,7 +398,7 @@ func TestHandlePermissionUseCase_NonPersistableNotSaved(t *testing.T) { | |||||
| 398 | 398 | ||||
| 399 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | 399 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | ||
| 400 | entity.PermissionTypeDisplay, | 400 | entity.PermissionTypeDisplay, | ||
| 401 | - | }, callback) | 401 | + | }, nil, callback) |
| 402 | 402 | ||||
| 403 | assert.True(t, allowed) | 403 | assert.True(t, allowed) | ||
| 404 | permRepo.AssertNotCalled(t, "Set") | 404 | permRepo.AssertNotCalled(t, "Set") | ||
| @@ -423,7 +423,7 @@ func TestHandlePermissionUseCase_NoDialogPresenter(t *testing.T) { | |||||
| 423 | 423 | ||||
| 424 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | 424 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | ||
| 425 | entity.PermissionTypeMicrophone, | 425 | entity.PermissionTypeMicrophone, | ||
| 426 | - | }, callback) | 426 | + | }, nil, callback) |
| 427 | 427 | ||||
| 428 | assert.True(t, denied, "should deny when no dialog presenter is available") | 428 | assert.True(t, denied, "should deny when no dialog presenter is available") | ||
| 429 | } | 429 | } | ||
| @@ -450,7 +450,7 @@ func TestHandlePermissionUseCase_DisplayOverrideDenied(t *testing.T) { | |||||
| 450 | 450 | ||||
| 451 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | 451 | uc.HandlePermissionRequest(ctx, "https://meet.example.com", []entity.PermissionType{ | ||
| 452 | entity.PermissionTypeDisplay, | 452 | entity.PermissionTypeDisplay, | ||
| 453 | - | }, callback) | 453 | + | }, nil, callback) |
| 454 | 454 | ||||
| 455 | assert.True(t, denied, "display should be denied when manual override is denied") | 455 | assert.True(t, denied, "display should be denied when manual override is denied") | ||
| 456 | dialog.AssertNotCalled(t, "ShowPermissionDialog") | 456 | dialog.AssertNotCalled(t, "ShowPermissionDialog") | ||
| @@ -526,7 +526,7 @@ func TestHandlePermissionUseCase_UsesInjectedLoggerFactory(t *testing.T) { | |||||
| 526 | } | 526 | } | ||
| 527 | 527 | ||||
| 528 | uc := usecase.NewHandlePermissionUseCase(permRepo, dialog, loggerFactory) | 528 | uc := usecase.NewHandlePermissionUseCase(permRepo, dialog, loggerFactory) | ||
| 529 | - | uc.HandlePermissionRequest(ctx, "", []entity.PermissionType{entity.PermissionTypeMicrophone}, usecase.PermissionCallback{ | 529 | + | uc.HandlePermissionRequest(ctx, "", []entity.PermissionType{entity.PermissionTypeMicrophone}, nil, usecase.PermissionCallback{ |
| 530 | Allow: func() {}, | 530 | Allow: func() {}, | ||
| 531 | Deny: func() {}, | 531 | Deny: func() {}, | ||
| 532 | }) | 532 | }) | ||
| @@ -551,13 +551,19 @@ func TestHandlePermissionUseCase_WebsiteDataAccess_ShowsDialogAndPersists(t *tes | |||||
| 551 | Deny: func() {}, | 551 | Deny: func() {}, | ||
| 552 | } | 552 | } | ||
| 553 | 553 | ||||
| 554 | + | meta := entity.PermissionMetadata{ | |||
| 555 | + | "requesting_domain": "accounts.google.com", | |||
| 556 | + | "current_domain": "shop.example.com", | |||
| 557 | + | } | |||
| 558 | + | ||||
| 554 | // Dialog shows and user clicks "Always Allow" | 559 | // Dialog shows and user clicks "Always Allow" | ||
| 555 | dialog.EXPECT().ShowPermissionDialog( | 560 | dialog.EXPECT().ShowPermissionDialog( | ||
| 556 | mock.Anything, | 561 | mock.Anything, | ||
| 557 | "https://shop.example.com", | 562 | "https://shop.example.com", | ||
| 558 | []entity.PermissionType{entity.PermissionTypeWebsiteDataAccess}, | 563 | []entity.PermissionType{entity.PermissionTypeWebsiteDataAccess}, | ||
| 564 | + | meta, | |||
| 559 | mock.Anything, | 565 | mock.Anything, | ||
| 560 | - | ).Run(func(_ context.Context, _ string, _ []entity.PermissionType, cb func(port.PermissionDialogResult)) { | 566 | + | ).Run(func(_ context.Context, _ string, _ []entity.PermissionType, _ entity.PermissionMetadata, cb func(port.PermissionDialogResult)) { |
| 561 | cb(port.PermissionDialogResult{Allowed: true, Persistent: true}) | 567 | cb(port.PermissionDialogResult{Allowed: true, Persistent: true}) | ||
| 562 | }) | 568 | }) | ||
| 563 | 569 | ||||
| @@ -571,7 +577,7 @@ func TestHandlePermissionUseCase_WebsiteDataAccess_ShowsDialogAndPersists(t *tes | |||||
| 571 | 577 | ||||
| 572 | uc.HandlePermissionRequest(ctx, "https://shop.example.com", []entity.PermissionType{ | 578 | uc.HandlePermissionRequest(ctx, "https://shop.example.com", []entity.PermissionType{ | ||
| 573 | entity.PermissionTypeWebsiteDataAccess, | 579 | entity.PermissionTypeWebsiteDataAccess, | ||
| 574 | - | }, callback) | 580 | + | }, meta, callback) |
| 575 | 581 | ||||
| 576 | assert.True(t, allowed) | 582 | assert.True(t, allowed) | ||
| 577 | } | 583 | } | ||
| @@ -599,7 +605,7 @@ func TestHandlePermissionUseCase_WebsiteDataAccess_UsesStoredGrant(t *testing.T) | |||||
| 599 | 605 | ||||
| 600 | uc.HandlePermissionRequest(ctx, "https://shop.example.com", []entity.PermissionType{ | 606 | uc.HandlePermissionRequest(ctx, "https://shop.example.com", []entity.PermissionType{ | ||
| 601 | entity.PermissionTypeWebsiteDataAccess, | 607 | entity.PermissionTypeWebsiteDataAccess, | ||
| 602 | - | }, callback) | 608 | + | }, entity.PermissionMetadata{"requesting_domain": "accounts.google.com", "current_domain": "shop.example.com"}, callback) |
| 603 | 609 | ||||
| 604 | assert.True(t, allowed, "should use stored granted permission without dialog") | 610 | assert.True(t, allowed, "should use stored granted permission without dialog") | ||
| 605 | dialog.AssertNotCalled(t, "ShowPermissionDialog") | 611 | dialog.AssertNotCalled(t, "ShowPermissionDialog") | ||
M internal/domain/entity/permission.go +4 -0
| @@ -38,6 +38,10 @@ const ( | |||
| 38 | 38 | PermissionTypeWebsiteDataAccess PermissionType = "website_data_access" | |
| 39 | 39 | ) | |
| 40 | 40 | ||
| 41 | + | // PermissionMetadata carries extra context for specific permission types. | |
| 42 | + | // Keys are permission-type-specific (e.g., "requesting_domain" for website_data_access). | |
| 43 | + | type PermissionMetadata map[string]string | |
| 44 | + | ||
| 41 | 45 | // PermissionDecision represents the user's decision for a permission. | |
| 42 | 46 | type PermissionDecision string | |
| 43 | 47 | ||
| @@ -38,6 +38,10 @@ const ( | |||||
| 38 | PermissionTypeWebsiteDataAccess PermissionType = "website_data_access" | 38 | PermissionTypeWebsiteDataAccess PermissionType = "website_data_access" | ||
| 39 | ) | 39 | ) | ||
| 40 | 40 | ||||
| 41 | + | // PermissionMetadata carries extra context for specific permission types. | |||
| 42 | + | // Keys are permission-type-specific (e.g., "requesting_domain" for website_data_access). | |||
| 43 | + | type PermissionMetadata map[string]string | |||
| 44 | + | ||||
| 41 | // PermissionDecision represents the user's decision for a permission. | 45 | // PermissionDecision represents the user's decision for a permission. | ||
| 42 | type PermissionDecision string | 46 | type PermissionDecision string | ||
| 43 | 47 | ||||
M internal/infrastructure/webkit/webview.go +15 -13
| @@ -162,7 +162,8 @@ type WebView struct { | |||
| 162 | 162 | // PermissionRequest is called when a site requests permission (mic, camera, screen sharing). | |
| 163 | 163 | // Return true to indicate the request was handled. Call allow()/deny() to respond. | |
| 164 | 164 | // The permission types are determined from the request object. | |
| 165 | - | OnPermissionRequest func(origin string, permTypes []string, allow, deny func()) bool | |
| 165 | + | // The metadata map carries permission-type-specific context (e.g., "requesting_domain" for website_data_access). | |
| 166 | + | OnPermissionRequest func(origin string, permTypes []string, metadata map[string]string, allow, deny func()) bool | |
| 166 | 167 | ||
| 167 | 168 | logger zerolog.Logger | |
| 168 | 169 | mu sync.RWMutex | |
| @@ -998,14 +999,11 @@ func (wv *WebView) connectPermissionRequestSignal() { | |||
| 998 | 999 | } | |
| 999 | 1000 | ||
| 1000 | 1001 | // Determine permission types from the request | |
| 1001 | - | permTypes, originOverride := wv.determinePermissionTypes(ctx, requestPtr) | |
| 1002 | + | permTypes, metadata := wv.determinePermissionTypes(ctx, requestPtr) | |
| 1002 | 1003 | if len(permTypes) == 0 { | |
| 1003 | 1004 | wv.logger.Warn().Msg("permission request with unknown type, denying") | |
| 1004 | 1005 | return false | |
| 1005 | 1006 | } | |
| 1006 | - | if originOverride != "" { | |
| 1007 | - | origin = originOverride | |
| 1008 | - | } | |
| 1009 | 1007 | ||
| 1010 | 1008 | // Ref the request object to prevent use-after-free | |
| 1011 | 1009 | // The request may outlive the signal handler if we show a dialog | |
| @@ -1039,7 +1037,7 @@ func (wv *WebView) connectPermissionRequestSignal() { | |||
| 1039 | 1037 | } | |
| 1040 | 1038 | ||
| 1041 | 1039 | // Call the handler | |
| 1042 | - | return wv.OnPermissionRequest(origin, permTypes, allow, deny) | |
| 1040 | + | return wv.OnPermissionRequest(origin, permTypes, metadata, allow, deny) | |
| 1043 | 1041 | } | |
| 1044 | 1042 | ||
| 1045 | 1043 | sigID := wv.inner.ConnectPermissionRequest(&permissionCb) | |
| @@ -1051,13 +1049,13 @@ func (wv *WebView) connectPermissionRequestSignal() { | |||
| 1051 | 1049 | // We use GObject property accessors for audio/video request flags. Display detection | |
| 1052 | 1050 | // uses the dedicated WebKit API because this WebKit build does not expose an | |
| 1053 | 1051 | // "is-for-display-device" GObject property. | |
| 1054 | - | func (wv *WebView) determinePermissionTypes(ctx context.Context, requestPtr uintptr) (types []string, originOverride string) { | |
| 1052 | + | func (wv *WebView) determinePermissionTypes(ctx context.Context, requestPtr uintptr) (types []string, metadata map[string]string) { | |
| 1055 | 1053 | requestKind := detectPermissionRequestKind(ctx, requestPtr) | |
| 1056 | 1054 | switch requestKind { | |
| 1057 | 1055 | case permissionRequestKindUserMedia: | |
| 1058 | 1056 | userMediaReq := webkit.UserMediaPermissionRequestNewFromInternalPtr(requestPtr) | |
| 1059 | 1057 | if userMediaReq == nil { | |
| 1060 | - | return nil, "" | |
| 1058 | + | return nil, nil | |
| 1061 | 1059 | } | |
| 1062 | 1060 | ||
| 1063 | 1061 | // Use GObject property accessors — more reliable than the C function wrappers | |
| @@ -1072,13 +1070,13 @@ func (wv *WebView) determinePermissionTypes(ctx context.Context, requestPtr uint | |||
| 1072 | 1070 | Bool("is_display", isDisplay). | |
| 1073 | 1071 | Msg("permission request type detection") | |
| 1074 | 1072 | ||
| 1075 | - | return classifyPermissionRequestTypes(ctx, requestKind, isAudio, isVideo, isDisplay), "" | |
| 1073 | + | return classifyPermissionRequestTypes(ctx, requestKind, isAudio, isVideo, isDisplay), nil | |
| 1076 | 1074 | case permissionRequestKindDeviceInfo: | |
| 1077 | - | return classifyPermissionRequestTypes(ctx, requestKind, false, false, false), "" | |
| 1075 | + | return classifyPermissionRequestTypes(ctx, requestKind, false, false, false), nil | |
| 1078 | 1076 | case permissionRequestKindWebsiteDataAccess: | |
| 1079 | 1077 | wdaReq := webkit.WebsiteDataAccessPermissionRequestNewFromInternalPtr(requestPtr) | |
| 1080 | 1078 | if wdaReq == nil { | |
| 1081 | - | return nil, "" | |
| 1079 | + | return nil, nil | |
| 1082 | 1080 | } | |
| 1083 | 1081 | currentDomain := wdaReq.GetCurrentDomain() | |
| 1084 | 1082 | requestingDomain := wdaReq.GetRequestingDomain() | |
| @@ -1086,7 +1084,11 @@ func (wv *WebView) determinePermissionTypes(ctx context.Context, requestPtr uint | |||
| 1086 | 1084 | Str("current_domain", currentDomain). | |
| 1087 | 1085 | Str("requesting_domain", requestingDomain). | |
| 1088 | 1086 | Msg("website data access permission request") | |
| 1089 | - | return classifyPermissionRequestTypes(ctx, requestKind, false, false, false), "" | |
| 1087 | + | meta := map[string]string{ | |
| 1088 | + | "requesting_domain": requestingDomain, | |
| 1089 | + | "current_domain": currentDomain, | |
| 1090 | + | } | |
| 1091 | + | return classifyPermissionRequestTypes(ctx, requestKind, false, false, false), meta | |
| 1090 | 1092 | default: | |
| 1091 | 1093 | if requestPtr != 0 { | |
| 1092 | 1094 | typeName := permissionRequestTypeName(ctx, requestPtr) | |
| @@ -1098,7 +1100,7 @@ func (wv *WebView) determinePermissionTypes(ctx context.Context, requestPtr uint | |||
| 1098 | 1100 | } | |
| 1099 | 1101 | // Unknown permission type - could be clipboard, notifications, geolocation, etc. | |
| 1100 | 1102 | // For now, return empty to trigger denial. Future phases will add these types. | |
| 1101 | - | return nil, "" | |
| 1103 | + | return nil, nil | |
| 1102 | 1104 | } | |
| 1103 | 1105 | } | |
| 1104 | 1106 | ||
| @@ -162,7 +162,8 @@ type WebView struct { | |||||
| 162 | // PermissionRequest is called when a site requests permission (mic, camera, screen sharing). | 162 | // PermissionRequest is called when a site requests permission (mic, camera, screen sharing). | ||
| 163 | // Return true to indicate the request was handled. Call allow()/deny() to respond. | 163 | // Return true to indicate the request was handled. Call allow()/deny() to respond. | ||
| 164 | // The permission types are determined from the request object. | 164 | // The permission types are determined from the request object. | ||
| 165 | - | OnPermissionRequest func(origin string, permTypes []string, allow, deny func()) bool | 165 | + | // The metadata map carries permission-type-specific context (e.g., "requesting_domain" for website_data_access). |
| 166 | + | OnPermissionRequest func(origin string, permTypes []string, metadata map[string]string, allow, deny func()) bool | |||
| 166 | 167 | ||||
| 167 | logger zerolog.Logger | 168 | logger zerolog.Logger | ||
| 168 | mu sync.RWMutex | 169 | mu sync.RWMutex | ||
| @@ -998,14 +999,11 @@ func (wv *WebView) connectPermissionRequestSignal() { | |||||
| 998 | } | 999 | } | ||
| 999 | 1000 | ||||
| 1000 | // Determine permission types from the request | 1001 | // Determine permission types from the request | ||
| 1001 | - | permTypes, originOverride := wv.determinePermissionTypes(ctx, requestPtr) | 1002 | + | permTypes, metadata := wv.determinePermissionTypes(ctx, requestPtr) |
| 1002 | if len(permTypes) == 0 { | 1003 | if len(permTypes) == 0 { | ||
| 1003 | wv.logger.Warn().Msg("permission request with unknown type, denying") | 1004 | wv.logger.Warn().Msg("permission request with unknown type, denying") | ||
| 1004 | return false | 1005 | return false | ||
| 1005 | } | 1006 | } | ||
| 1006 | - | if originOverride != "" { | |||
| 1007 | - | origin = originOverride | |||
| 1008 | - | } | |||
| 1009 | 1007 | ||||
| 1010 | // Ref the request object to prevent use-after-free | 1008 | // Ref the request object to prevent use-after-free | ||
| 1011 | // The request may outlive the signal handler if we show a dialog | 1009 | // The request may outlive the signal handler if we show a dialog | ||
| @@ -1039,7 +1037,7 @@ func (wv *WebView) connectPermissionRequestSignal() { | |||||
| 1039 | } | 1037 | } | ||
| 1040 | 1038 | ||||
| 1041 | // Call the handler | 1039 | // Call the handler | ||
| 1042 | - | return wv.OnPermissionRequest(origin, permTypes, allow, deny) | 1040 | + | return wv.OnPermissionRequest(origin, permTypes, metadata, allow, deny) |
| 1043 | } | 1041 | } | ||
| 1044 | 1042 | ||||
| 1045 | sigID := wv.inner.ConnectPermissionRequest(&permissionCb) | 1043 | sigID := wv.inner.ConnectPermissionRequest(&permissionCb) | ||
| @@ -1051,13 +1049,13 @@ func (wv *WebView) connectPermissionRequestSignal() { | |||||
| 1051 | // We use GObject property accessors for audio/video request flags. Display detection | 1049 | // We use GObject property accessors for audio/video request flags. Display detection | ||
| 1052 | // uses the dedicated WebKit API because this WebKit build does not expose an | 1050 | // uses the dedicated WebKit API because this WebKit build does not expose an | ||
| 1053 | // "is-for-display-device" GObject property. | 1051 | // "is-for-display-device" GObject property. | ||
| 1054 | - | func (wv *WebView) determinePermissionTypes(ctx context.Context, requestPtr uintptr) (types []string, originOverride string) { | 1052 | + | func (wv *WebView) determinePermissionTypes(ctx context.Context, requestPtr uintptr) (types []string, metadata map[string]string) { |
| 1055 | requestKind := detectPermissionRequestKind(ctx, requestPtr) | 1053 | requestKind := detectPermissionRequestKind(ctx, requestPtr) | ||
| 1056 | switch requestKind { | 1054 | switch requestKind { | ||
| 1057 | case permissionRequestKindUserMedia: | 1055 | case permissionRequestKindUserMedia: | ||
| 1058 | userMediaReq := webkit.UserMediaPermissionRequestNewFromInternalPtr(requestPtr) | 1056 | userMediaReq := webkit.UserMediaPermissionRequestNewFromInternalPtr(requestPtr) | ||
| 1059 | if userMediaReq == nil { | 1057 | if userMediaReq == nil { | ||
| 1060 | - | return nil, "" | 1058 | + | return nil, nil |
| 1061 | } | 1059 | } | ||
| 1062 | 1060 | ||||
| 1063 | // Use GObject property accessors — more reliable than the C function wrappers | 1061 | // Use GObject property accessors — more reliable than the C function wrappers | ||
| @@ -1072,13 +1070,13 @@ func (wv *WebView) determinePermissionTypes(ctx context.Context, requestPtr uint | |||||
| 1072 | Bool("is_display", isDisplay). | 1070 | Bool("is_display", isDisplay). | ||
| 1073 | Msg("permission request type detection") | 1071 | Msg("permission request type detection") | ||
| 1074 | 1072 | ||||
| 1075 | - | return classifyPermissionRequestTypes(ctx, requestKind, isAudio, isVideo, isDisplay), "" | 1073 | + | return classifyPermissionRequestTypes(ctx, requestKind, isAudio, isVideo, isDisplay), nil |
| 1076 | case permissionRequestKindDeviceInfo: | 1074 | case permissionRequestKindDeviceInfo: | ||
| 1077 | - | return classifyPermissionRequestTypes(ctx, requestKind, false, false, false), "" | 1075 | + | return classifyPermissionRequestTypes(ctx, requestKind, false, false, false), nil |
| 1078 | case permissionRequestKindWebsiteDataAccess: | 1076 | case permissionRequestKindWebsiteDataAccess: | ||
| 1079 | wdaReq := webkit.WebsiteDataAccessPermissionRequestNewFromInternalPtr(requestPtr) | 1077 | wdaReq := webkit.WebsiteDataAccessPermissionRequestNewFromInternalPtr(requestPtr) | ||
| 1080 | if wdaReq == nil { | 1078 | if wdaReq == nil { | ||
| 1081 | - | return nil, "" | 1079 | + | return nil, nil |
| 1082 | } | 1080 | } | ||
| 1083 | currentDomain := wdaReq.GetCurrentDomain() | 1081 | currentDomain := wdaReq.GetCurrentDomain() | ||
| 1084 | requestingDomain := wdaReq.GetRequestingDomain() | 1082 | requestingDomain := wdaReq.GetRequestingDomain() | ||
| @@ -1086,7 +1084,11 @@ func (wv *WebView) determinePermissionTypes(ctx context.Context, requestPtr uint | |||||
| 1086 | Str("current_domain", currentDomain). | 1084 | Str("current_domain", currentDomain). | ||
| 1087 | Str("requesting_domain", requestingDomain). | 1085 | Str("requesting_domain", requestingDomain). | ||
| 1088 | Msg("website data access permission request") | 1086 | Msg("website data access permission request") | ||
| 1089 | - | return classifyPermissionRequestTypes(ctx, requestKind, false, false, false), "" | 1087 | + | meta := map[string]string{ |
| 1088 | + | "requesting_domain": requestingDomain, | |||
| 1089 | + | "current_domain": currentDomain, | |||
| 1090 | + | } | |||
| 1091 | + | return classifyPermissionRequestTypes(ctx, requestKind, false, false, false), meta | |||
| 1090 | default: | 1092 | default: | ||
| 1091 | if requestPtr != 0 { | 1093 | if requestPtr != 0 { | ||
| 1092 | typeName := permissionRequestTypeName(ctx, requestPtr) | 1094 | typeName := permissionRequestTypeName(ctx, requestPtr) | ||
| @@ -1098,7 +1100,7 @@ func (wv *WebView) determinePermissionTypes(ctx context.Context, requestPtr uint | |||||
| 1098 | } | 1100 | } | ||
| 1099 | // Unknown permission type - could be clipboard, notifications, geolocation, etc. | 1101 | // Unknown permission type - could be clipboard, notifications, geolocation, etc. | ||
| 1100 | // For now, return empty to trigger denial. Future phases will add these types. | 1102 | // For now, return empty to trigger denial. Future phases will add these types. | ||
| 1101 | - | return nil, "" | 1103 | + | return nil, nil |
| 1102 | } | 1104 | } | ||
| 1103 | } | 1105 | } | ||
| 1104 | 1106 | ||||
M internal/ui/coordinator/content/callbacks.go +4 -3
| @@ -117,8 +117,8 @@ func (c *Coordinator) setupWebViewCallbacks(ctx context.Context, paneID entity.P | |||
| 117 | 117 | Msg("failed to load crash page after web process termination") | |
| 118 | 118 | } | |
| 119 | 119 | }, | |
| 120 | - | OnPermissionRequest: func(origin string, permTypes []string, allow, deny func()) bool { | |
| 121 | - | return c.handlePermissionRequest(ctx, origin, permTypes, allow, deny) | |
| 120 | + | OnPermissionRequest: func(origin string, permTypes []string, metadata map[string]string, allow, deny func()) bool { | |
| 121 | + | return c.handlePermissionRequest(ctx, origin, permTypes, metadata, allow, deny) | |
| 122 | 122 | }, | |
| 123 | 123 | } | |
| 124 | 124 | ||
| @@ -191,6 +191,7 @@ func (c *Coordinator) handlePermissionRequest( | |||
| 191 | 191 | ctx context.Context, | |
| 192 | 192 | origin string, | |
| 193 | 193 | permTypes []string, | |
| 194 | + | metadata map[string]string, | |
| 194 | 195 | allow, deny func(), | |
| 195 | 196 | ) bool { | |
| 196 | 197 | log := logging.FromContext(ctx) | |
| @@ -264,7 +265,7 @@ func (c *Coordinator) handlePermissionRequest( | |||
| 264 | 265 | Deny: wrappedDeny, | |
| 265 | 266 | } | |
| 266 | 267 | ||
| 267 | - | c.permissionUC.HandlePermissionRequest(ctx, origin, entityTypes, callback) | |
| 268 | + | c.permissionUC.HandlePermissionRequest(ctx, origin, entityTypes, entity.PermissionMetadata(metadata), callback) | |
| 268 | 269 | return true | |
| 269 | 270 | } | |
| 270 | 271 | ||
| @@ -117,8 +117,8 @@ func (c *Coordinator) setupWebViewCallbacks(ctx context.Context, paneID entity.P | |||||
| 117 | Msg("failed to load crash page after web process termination") | 117 | Msg("failed to load crash page after web process termination") | ||
| 118 | } | 118 | } | ||
| 119 | }, | 119 | }, | ||
| 120 | - | OnPermissionRequest: func(origin string, permTypes []string, allow, deny func()) bool { | 120 | + | OnPermissionRequest: func(origin string, permTypes []string, metadata map[string]string, allow, deny func()) bool { |
| 121 | - | return c.handlePermissionRequest(ctx, origin, permTypes, allow, deny) | 121 | + | return c.handlePermissionRequest(ctx, origin, permTypes, metadata, allow, deny) |
| 122 | }, | 122 | }, | ||
| 123 | } | 123 | } | ||
| 124 | 124 | ||||
| @@ -191,6 +191,7 @@ func (c *Coordinator) handlePermissionRequest( | |||||
| 191 | ctx context.Context, | 191 | ctx context.Context, | ||
| 192 | origin string, | 192 | origin string, | ||
| 193 | permTypes []string, | 193 | permTypes []string, | ||
| 194 | + | metadata map[string]string, | |||
| 194 | allow, deny func(), | 195 | allow, deny func(), | ||
| 195 | ) bool { | 196 | ) bool { | ||
| 196 | log := logging.FromContext(ctx) | 197 | log := logging.FromContext(ctx) | ||
| @@ -264,7 +265,7 @@ func (c *Coordinator) handlePermissionRequest( | |||||
| 264 | Deny: wrappedDeny, | 265 | Deny: wrappedDeny, | ||
| 265 | } | 266 | } | ||
| 266 | 267 | ||||
| 267 | - | c.permissionUC.HandlePermissionRequest(ctx, origin, entityTypes, callback) | 268 | + | c.permissionUC.HandlePermissionRequest(ctx, origin, entityTypes, entity.PermissionMetadata(metadata), callback) |
| 268 | return true | 269 | return true | ||
| 269 | } | 270 | } | ||
| 270 | 271 | ||||
M internal/ui/dialog/permission.go +13 -5
| @@ -21,6 +21,7 @@ type permissionDialogRequest struct { | |||
| 21 | 21 | ctx context.Context | |
| 22 | 22 | origin string | |
| 23 | 23 | permTypes []entity.PermissionType | |
| 24 | + | metadata entity.PermissionMetadata | |
| 24 | 25 | callback func(result port.PermissionDialogResult) | |
| 25 | 26 | } | |
| 26 | 27 | ||
| @@ -48,12 +49,14 @@ func (d *PermissionDialog) ShowPermissionDialog( | |||
| 48 | 49 | ctx context.Context, | |
| 49 | 50 | origin string, | |
| 50 | 51 | permTypes []entity.PermissionType, | |
| 52 | + | metadata entity.PermissionMetadata, | |
| 51 | 53 | callback func(result port.PermissionDialogResult), | |
| 52 | 54 | ) { | |
| 53 | 55 | req := permissionDialogRequest{ | |
| 54 | 56 | ctx: ctx, | |
| 55 | 57 | origin: origin, | |
| 56 | 58 | permTypes: permTypes, | |
| 59 | + | metadata: metadata, | |
| 57 | 60 | callback: callback, | |
| 58 | 61 | } | |
| 59 | 62 | ||
| @@ -94,7 +97,7 @@ func (d *PermissionDialog) showRequest(req permissionDialogRequest) { | |||
| 94 | 97 | ||
| 95 | 98 | // Build dialog text | |
| 96 | 99 | heading := d.buildHeading(permTypes) | |
| 97 | - | body := d.buildBody(origin, permTypes) | |
| 100 | + | body := d.buildBody(origin, permTypes, req.metadata) | |
| 98 | 101 | ||
| 99 | 102 | d.popup.Show(ctx, heading, body, func(allowed, persistent bool) { | |
| 100 | 103 | log.Debug(). | |
| @@ -198,7 +201,7 @@ func (d *PermissionDialog) buildHeading( | |||
| 198 | 201 | ||
| 199 | 202 | // buildBody creates the dialog body text. | |
| 200 | 203 | func (d *PermissionDialog) buildBody( | |
| 201 | - | origin string, permTypes []entity.PermissionType, | |
| 204 | + | origin string, permTypes []entity.PermissionType, metadata entity.PermissionMetadata, | |
| 202 | 205 | ) string { | |
| 203 | 206 | f := parsePermFlags(permTypes) | |
| 204 | 207 | var parts []string | |
| @@ -213,9 +216,14 @@ func (d *PermissionDialog) buildBody( | |||
| 213 | 216 | parts = append(parts, "share your screen") | |
| 214 | 217 | } | |
| 215 | 218 | if f.dataAccess { | |
| 216 | - | if len(parts) == 0 { | |
| 217 | - | parts = append(parts, | |
| 218 | - | "access its stored data while you browse this site") | |
| 219 | + | reqDomain := metadata["requesting_domain"] | |
| 220 | + | curDomain := metadata["current_domain"] | |
| 221 | + | if reqDomain != "" && curDomain != "" { | |
| 222 | + | parts = append(parts, fmt.Sprintf( | |
| 223 | + | "allow %s to access its data (including cookies) while you browse %s", | |
| 224 | + | reqDomain, curDomain)) | |
| 225 | + | } else if len(parts) == 0 { | |
| 226 | + | parts = append(parts, "access its stored data while you browse this site") | |
| 219 | 227 | } else { | |
| 220 | 228 | parts = append(parts, "access its stored data") | |
| 221 | 229 | } | |
| @@ -21,6 +21,7 @@ type permissionDialogRequest struct { | |||||
| 21 | ctx context.Context | 21 | ctx context.Context | ||
| 22 | origin string | 22 | origin string | ||
| 23 | permTypes []entity.PermissionType | 23 | permTypes []entity.PermissionType | ||
| 24 | + | metadata entity.PermissionMetadata | |||
| 24 | callback func(result port.PermissionDialogResult) | 25 | callback func(result port.PermissionDialogResult) | ||
| 25 | } | 26 | } | ||
| 26 | 27 | ||||
| @@ -48,12 +49,14 @@ func (d *PermissionDialog) ShowPermissionDialog( | |||||
| 48 | ctx context.Context, | 49 | ctx context.Context, | ||
| 49 | origin string, | 50 | origin string, | ||
| 50 | permTypes []entity.PermissionType, | 51 | permTypes []entity.PermissionType, | ||
| 52 | + | metadata entity.PermissionMetadata, | |||
| 51 | callback func(result port.PermissionDialogResult), | 53 | callback func(result port.PermissionDialogResult), | ||
| 52 | ) { | 54 | ) { | ||
| 53 | req := permissionDialogRequest{ | 55 | req := permissionDialogRequest{ | ||
| 54 | ctx: ctx, | 56 | ctx: ctx, | ||
| 55 | origin: origin, | 57 | origin: origin, | ||
| 56 | permTypes: permTypes, | 58 | permTypes: permTypes, | ||
| 59 | + | metadata: metadata, | |||
| 57 | callback: callback, | 60 | callback: callback, | ||
| 58 | } | 61 | } | ||
| 59 | 62 | ||||
| @@ -94,7 +97,7 @@ func (d *PermissionDialog) showRequest(req permissionDialogRequest) { | |||||
| 94 | 97 | ||||
| 95 | // Build dialog text | 98 | // Build dialog text | ||
| 96 | heading := d.buildHeading(permTypes) | 99 | heading := d.buildHeading(permTypes) | ||
| 97 | - | body := d.buildBody(origin, permTypes) | 100 | + | body := d.buildBody(origin, permTypes, req.metadata) |
| 98 | 101 | ||||
| 99 | d.popup.Show(ctx, heading, body, func(allowed, persistent bool) { | 102 | d.popup.Show(ctx, heading, body, func(allowed, persistent bool) { | ||
| 100 | log.Debug(). | 103 | log.Debug(). | ||
| @@ -198,7 +201,7 @@ func (d *PermissionDialog) buildHeading( | |||||
| 198 | 201 | ||||
| 199 | // buildBody creates the dialog body text. | 202 | // buildBody creates the dialog body text. | ||
| 200 | func (d *PermissionDialog) buildBody( | 203 | func (d *PermissionDialog) buildBody( | ||
| 201 | - | origin string, permTypes []entity.PermissionType, | 204 | + | origin string, permTypes []entity.PermissionType, metadata entity.PermissionMetadata, |
| 202 | ) string { | 205 | ) string { | ||
| 203 | f := parsePermFlags(permTypes) | 206 | f := parsePermFlags(permTypes) | ||
| 204 | var parts []string | 207 | var parts []string | ||
| @@ -213,9 +216,14 @@ func (d *PermissionDialog) buildBody( | |||||
| 213 | parts = append(parts, "share your screen") | 216 | parts = append(parts, "share your screen") | ||
| 214 | } | 217 | } | ||
| 215 | if f.dataAccess { | 218 | if f.dataAccess { | ||
| 216 | - | if len(parts) == 0 { | 219 | + | reqDomain := metadata["requesting_domain"] |
| 217 | - | parts = append(parts, | 220 | + | curDomain := metadata["current_domain"] |
| 218 | - | "access its stored data while you browse this site") | 221 | + | if reqDomain != "" && curDomain != "" { |
| 222 | + | parts = append(parts, fmt.Sprintf( | |||
| 223 | + | "allow %s to access its data (including cookies) while you browse %s", | |||
| 224 | + | reqDomain, curDomain)) | |||
| 225 | + | } else if len(parts) == 0 { | |||
| 226 | + | parts = append(parts, "access its stored data while you browse this site") | |||
| 219 | } else { | 227 | } else { | ||
| 220 | parts = append(parts, "access its stored data") | 228 | parts = append(parts, "access its stored data") | ||
| 221 | } | 229 | } | ||
M internal/ui/dialog/permission_test.go +14 -1
| @@ -51,6 +51,7 @@ func TestPermissionDialog_QueuesRequestsWhilePopupVisible(t *testing.T) { | |||
| 51 | 51 | context.Background(), | |
| 52 | 52 | "https://example.com", | |
| 53 | 53 | []entity.PermissionType{entity.PermissionTypeMicrophone}, | |
| 54 | + | nil, | |
| 54 | 55 | func(result port.PermissionDialogResult) { | |
| 55 | 56 | firstCalled = true | |
| 56 | 57 | firstResult = result | |
| @@ -63,6 +64,7 @@ func TestPermissionDialog_QueuesRequestsWhilePopupVisible(t *testing.T) { | |||
| 63 | 64 | context.Background(), | |
| 64 | 65 | "https://example.com", | |
| 65 | 66 | []entity.PermissionType{entity.PermissionTypeCamera}, | |
| 67 | + | nil, | |
| 66 | 68 | func(result port.PermissionDialogResult) { | |
| 67 | 69 | secondCalled = true | |
| 68 | 70 | secondResult = result | |
| @@ -98,6 +100,7 @@ func TestPermissionDialog_NoPopup_DeniesRequest(t *testing.T) { | |||
| 98 | 100 | context.Background(), | |
| 99 | 101 | "https://example.com", | |
| 100 | 102 | []entity.PermissionType{entity.PermissionTypeMicrophone}, | |
| 103 | + | nil, | |
| 101 | 104 | func(r port.PermissionDialogResult) { | |
| 102 | 105 | called = true | |
| 103 | 106 | result = r | |
| @@ -153,7 +156,17 @@ func TestPermissionDialog_BuildHeadingAndBody_DisplayCombinations(t *testing.T) | |||
| 153 | 156 | for _, tt := range tests { | |
| 154 | 157 | t.Run(tt.name, func(t *testing.T) { | |
| 155 | 158 | assert.Equal(t, tt.expectHeading, d.buildHeading(tt.permTypes)) | |
| 156 | - | assert.Equal(t, origin+" wants to "+tt.expectedAction+".", d.buildBody(origin, tt.permTypes)) | |
| 159 | + | assert.Equal(t, origin+" wants to "+tt.expectedAction+".", d.buildBody(origin, tt.permTypes, nil)) | |
| 157 | 160 | }) | |
| 158 | 161 | } | |
| 162 | + | ||
| 163 | + | // Test website_data_access with populated metadata (Epiphany-style rich message) | |
| 164 | + | t.Run("website data access with both domains", func(t *testing.T) { | |
| 165 | + | meta := entity.PermissionMetadata{ | |
| 166 | + | "requesting_domain": "accounts.google.com", | |
| 167 | + | "current_domain": "shop.example.com", | |
| 168 | + | } | |
| 169 | + | body := d.buildBody(origin, []entity.PermissionType{entity.PermissionTypeWebsiteDataAccess}, meta) | |
| 170 | + | assert.Equal(t, origin+" wants to allow accounts.google.com to access its data (including cookies) while you browse shop.example.com.", body) | |
| 171 | + | }) | |
| 159 | 172 | } | |
| @@ -51,6 +51,7 @@ func TestPermissionDialog_QueuesRequestsWhilePopupVisible(t *testing.T) { | |||||
| 51 | context.Background(), | 51 | context.Background(), | ||
| 52 | "https://example.com", | 52 | "https://example.com", | ||
| 53 | []entity.PermissionType{entity.PermissionTypeMicrophone}, | 53 | []entity.PermissionType{entity.PermissionTypeMicrophone}, | ||
| 54 | + | nil, | |||
| 54 | func(result port.PermissionDialogResult) { | 55 | func(result port.PermissionDialogResult) { | ||
| 55 | firstCalled = true | 56 | firstCalled = true | ||
| 56 | firstResult = result | 57 | firstResult = result | ||
| @@ -63,6 +64,7 @@ func TestPermissionDialog_QueuesRequestsWhilePopupVisible(t *testing.T) { | |||||
| 63 | context.Background(), | 64 | context.Background(), | ||
| 64 | "https://example.com", | 65 | "https://example.com", | ||
| 65 | []entity.PermissionType{entity.PermissionTypeCamera}, | 66 | []entity.PermissionType{entity.PermissionTypeCamera}, | ||
| 67 | + | nil, | |||
| 66 | func(result port.PermissionDialogResult) { | 68 | func(result port.PermissionDialogResult) { | ||
| 67 | secondCalled = true | 69 | secondCalled = true | ||
| 68 | secondResult = result | 70 | secondResult = result | ||
| @@ -98,6 +100,7 @@ func TestPermissionDialog_NoPopup_DeniesRequest(t *testing.T) { | |||||
| 98 | context.Background(), | 100 | context.Background(), | ||
| 99 | "https://example.com", | 101 | "https://example.com", | ||
| 100 | []entity.PermissionType{entity.PermissionTypeMicrophone}, | 102 | []entity.PermissionType{entity.PermissionTypeMicrophone}, | ||
| 103 | + | nil, | |||
| 101 | func(r port.PermissionDialogResult) { | 104 | func(r port.PermissionDialogResult) { | ||
| 102 | called = true | 105 | called = true | ||
| 103 | result = r | 106 | result = r | ||
| @@ -153,7 +156,17 @@ func TestPermissionDialog_BuildHeadingAndBody_DisplayCombinations(t *testing.T) | |||||
| 153 | for _, tt := range tests { | 156 | for _, tt := range tests { | ||
| 154 | t.Run(tt.name, func(t *testing.T) { | 157 | t.Run(tt.name, func(t *testing.T) { | ||
| 155 | assert.Equal(t, tt.expectHeading, d.buildHeading(tt.permTypes)) | 158 | assert.Equal(t, tt.expectHeading, d.buildHeading(tt.permTypes)) | ||
| 156 | - | assert.Equal(t, origin+" wants to "+tt.expectedAction+".", d.buildBody(origin, tt.permTypes)) | 159 | + | assert.Equal(t, origin+" wants to "+tt.expectedAction+".", d.buildBody(origin, tt.permTypes, nil)) |
| 157 | }) | 160 | }) | ||
| 158 | } | 161 | } | ||
| 162 | + | ||||
| 163 | + | // Test website_data_access with populated metadata (Epiphany-style rich message) | |||
| 164 | + | t.Run("website data access with both domains", func(t *testing.T) { | |||
| 165 | + | meta := entity.PermissionMetadata{ | |||
| 166 | + | "requesting_domain": "accounts.google.com", | |||
| 167 | + | "current_domain": "shop.example.com", | |||
| 168 | + | } | |||
| 169 | + | body := d.buildBody(origin, []entity.PermissionType{entity.PermissionTypeWebsiteDataAccess}, meta) | |||
| 170 | + | assert.Equal(t, origin+" wants to allow accounts.google.com to access its data (including cookies) while you browse shop.example.com.", body) | |||
| 171 | + | }) | |||
| 159 | } | 172 | } | ||