detached a6de727

fix: harden permission origin, metadata constants, and cookie policy default

- Use live URI from the signal's WebView (inner.GetUri()) instead of the
  cached wv.URI() in connectPermissionRequestSignal so permission lookups
  and persistence always use the current page origin
- Add exported PermissionMetadataKeyRequestingDomain and
  PermissionMetadataKeyCurrentDomain constants to entity.PermissionMetadata
  and replace all raw string literals across the codebase with those constants
- Update HandlePermissionRequest / ShowPermissionDialog docs to name both
  required metadata keys for website_data_access requests
- Fix normalizeEngineConfig: unset or invalid cookie_policy now defaults to
  CookiePolicyNoThirdParty when itp_enabled is false, instead of silently
  forcing CookiePolicyAlways regardless of the ITP setting
commit
a6de72741366d21902dadb618c528bab89a5c054
author
bnema · · 2026-03-25 21:14 UTC
parent
2aad925
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
9 files · +52 -20
M internal/application/port/permission.go +2 -1
@@ -26,7 +26,8 @@ type PermissionDialogPresenter interface {
2626 // - ctx: context for cancellation and logging
2727 // - origin: the website origin requesting permission (e.g., "https://meet.example.com")
2828 // - permTypes: the types of permissions being requested
29- // - metadata: permission-type-specific context (e.g., "requesting_domain" for website_data_access)
29+ // - metadata: permission-type-specific context; for website_data_access both
30+ // entity.PermissionMetadataKeyRequestingDomain and entity.PermissionMetadataKeyCurrentDomain must be set
3031 // - callback: invoked when the user makes a decision or dialog is dismissed
3132 ShowPermissionDialog(
3233 ctx context.Context,
@@ -26,7 +26,8 @@ type PermissionDialogPresenter interface {
26 // - ctx: context for cancellation and logging26 // - 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 requested28 // - permTypes: the types of permissions being requested
29- // - metadata: permission-type-specific context (e.g., "requesting_domain" for website_data_access)29+ // - metadata: permission-type-specific context; for website_data_access both
30+ // entity.PermissionMetadataKeyRequestingDomain and entity.PermissionMetadataKeyCurrentDomain must be set
30 // - callback: invoked when the user makes a decision or dialog is dismissed31 // - callback: invoked when the user makes a decision or dialog is dismissed
31 ShowPermissionDialog(32 ShowPermissionDialog(
32 ctx context.Context,33 ctx context.Context,
M internal/application/port/webview.go +2 -1
@@ -107,7 +107,8 @@ type WebViewCallbacks struct {
107107 // OnPermissionRequest is called when a site requests permission (mic, camera, screen sharing).
108108 // Return true to indicate the request was handled. Call allow()/deny() to respond.
109109 // The permission types are passed as strings ("microphone", "camera", "display", etc.).
110- // The metadata map carries permission-type-specific context (e.g., "requesting_domain" for website_data_access).
110+ // The metadata map carries permission-type-specific context; for website_data_access both
111+ // entity.PermissionMetadataKeyRequestingDomain and entity.PermissionMetadataKeyCurrentDomain must be populated.
111112 OnPermissionRequest func(origin string, permTypes []string, metadata map[string]string, allow, deny func()) bool
112113
113114 // OnLinkMiddleClick is called when a link is middle-clicked.
@@ -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- // The metadata map carries permission-type-specific context (e.g., "requesting_domain" for website_data_access).110+ // The metadata map carries permission-type-specific context; for website_data_access both
111+ // entity.PermissionMetadataKeyRequestingDomain and entity.PermissionMetadataKeyCurrentDomain must be populated.
111 OnPermissionRequest func(origin string, permTypes []string, metadata map[string]string, allow, deny func()) bool112 OnPermissionRequest func(origin string, permTypes []string, metadata map[string]string, allow, deny func()) bool
112 113
113 // OnLinkMiddleClick is called when a link is middle-clicked.114 // OnLinkMiddleClick is called when a link is middle-clicked.
M internal/application/usecase/handle_permission.go +3 -1
@@ -79,7 +79,9 @@ func (uc *HandlePermissionUseCase) getDialog() port.PermissionDialogPresenter {
7979 // - ctx: context for cancellation and logging
8080 // - origin: the website origin (URI) requesting permission
8181 // - permTypes: the types of permissions being requested
82-// - metadata: permission-type-specific context (e.g., "requesting_domain" for website_data_access)
82+// - metadata: permission-type-specific context; for website_data_access both keys must be present:
83+// entity.PermissionMetadataKeyRequestingDomain — the origin requesting cross-site data access
84+// entity.PermissionMetadataKeyCurrentDomain — the domain currently loaded in the WebView
8385 // - callback: callbacks to allow or deny the request (must call one)
8486 func (uc *HandlePermissionUseCase) HandlePermissionRequest(
8587 ctx context.Context,
@@ -79,7 +79,9 @@ func (uc *HandlePermissionUseCase) getDialog() port.PermissionDialogPresenter {
79 // - ctx: context for cancellation and logging79 // - ctx: context for cancellation and logging
80 // - origin: the website origin (URI) requesting permission80 // - origin: the website origin (URI) requesting permission
81 // - permTypes: the types of permissions being requested81 // - permTypes: the types of permissions being requested
82-// - metadata: permission-type-specific context (e.g., "requesting_domain" for website_data_access)82+// - metadata: permission-type-specific context; for website_data_access both keys must be present:
83+// entity.PermissionMetadataKeyRequestingDomain — the origin requesting cross-site data access
84+// entity.PermissionMetadataKeyCurrentDomain — the domain currently loaded in the WebView
83 // - callback: callbacks to allow or deny the request (must call one)85 // - callback: callbacks to allow or deny the request (must call one)
84 func (uc *HandlePermissionUseCase) HandlePermissionRequest(86 func (uc *HandlePermissionUseCase) HandlePermissionRequest(
85 ctx context.Context,87 ctx context.Context,
M internal/application/usecase/handle_permission_test.go +3 -3
@@ -552,8 +552,8 @@ func TestHandlePermissionUseCase_WebsiteDataAccess_ShowsDialogAndPersists(t *tes
552552 }
553553
554554 meta := entity.PermissionMetadata{
555- "requesting_domain": "accounts.google.com",
556- "current_domain": "shop.example.com",
555+ entity.PermissionMetadataKeyRequestingDomain: "accounts.google.com",
556+ entity.PermissionMetadataKeyCurrentDomain: "shop.example.com",
557557 }
558558
559559 // Dialog shows and user clicks "Always Allow"
@@ -605,7 +605,7 @@ func TestHandlePermissionUseCase_WebsiteDataAccess_UsesStoredGrant(t *testing.T)
605605
606606 uc.HandlePermissionRequest(ctx, "https://shop.example.com", []entity.PermissionType{
607607 entity.PermissionTypeWebsiteDataAccess,
608- }, entity.PermissionMetadata{"requesting_domain": "accounts.google.com", "current_domain": "shop.example.com"}, callback)
608+ }, entity.PermissionMetadata{entity.PermissionMetadataKeyRequestingDomain: "accounts.google.com", entity.PermissionMetadataKeyCurrentDomain: "shop.example.com"}, callback)
609609
610610 assert.True(t, allowed, "should use stored granted permission without dialog")
611611 dialog.AssertNotCalled(t, "ShowPermissionDialog")
@@ -552,8 +552,8 @@ func TestHandlePermissionUseCase_WebsiteDataAccess_ShowsDialogAndPersists(t *tes
552 }552 }
553 553
554 meta := entity.PermissionMetadata{554 meta := entity.PermissionMetadata{
555- "requesting_domain": "accounts.google.com",555+ entity.PermissionMetadataKeyRequestingDomain: "accounts.google.com",
556- "current_domain": "shop.example.com",556+ entity.PermissionMetadataKeyCurrentDomain: "shop.example.com",
557 }557 }
558 558
559 // Dialog shows and user clicks "Always Allow"559 // Dialog shows and user clicks "Always Allow"
@@ -605,7 +605,7 @@ func TestHandlePermissionUseCase_WebsiteDataAccess_UsesStoredGrant(t *testing.T)
605 605
606 uc.HandlePermissionRequest(ctx, "https://shop.example.com", []entity.PermissionType{606 uc.HandlePermissionRequest(ctx, "https://shop.example.com", []entity.PermissionType{
607 entity.PermissionTypeWebsiteDataAccess,607 entity.PermissionTypeWebsiteDataAccess,
608- }, entity.PermissionMetadata{"requesting_domain": "accounts.google.com", "current_domain": "shop.example.com"}, callback)608+ }, entity.PermissionMetadata{entity.PermissionMetadataKeyRequestingDomain: "accounts.google.com", entity.PermissionMetadataKeyCurrentDomain: "shop.example.com"}, callback)
609 609
610 assert.True(t, allowed, "should use stored granted permission without dialog")610 assert.True(t, allowed, "should use stored granted permission without dialog")
611 dialog.AssertNotCalled(t, "ShowPermissionDialog")611 dialog.AssertNotCalled(t, "ShowPermissionDialog")
M internal/domain/entity/permission.go +14 -1
@@ -38,8 +38,21 @@ const (
3838 PermissionTypeWebsiteDataAccess PermissionType = "website_data_access"
3939 )
4040
41+// Metadata keys for PermissionMetadata.
42+const (
43+ // PermissionMetadataKeyRequestingDomain is the origin that is requesting cross-site
44+ // data access (i.e. the third-party domain asking for its cookies/storage).
45+ PermissionMetadataKeyRequestingDomain = "requesting_domain"
46+
47+ // PermissionMetadataKeyCurrentDomain is the first-party domain currently loaded in
48+ // the WebView — the context in which the cross-site request is happening.
49+ PermissionMetadataKeyCurrentDomain = "current_domain"
50+)
51+
4152 // PermissionMetadata carries extra context for specific permission types.
42-// Keys are permission-type-specific (e.g., "requesting_domain" for website_data_access).
53+// For website_data_access, callers must populate both keys:
54+// - PermissionMetadataKeyRequestingDomain: the origin requesting cross-site data access
55+// - PermissionMetadataKeyCurrentDomain: the domain currently loaded in the WebView
4356 type PermissionMetadata map[string]string
4457
4558 // PermissionDecision represents the user's decision for a permission.
@@ -38,8 +38,21 @@ const (
38 PermissionTypeWebsiteDataAccess PermissionType = "website_data_access"38 PermissionTypeWebsiteDataAccess PermissionType = "website_data_access"
39 )39 )
40 40
41+// Metadata keys for PermissionMetadata.
42+const (
43+ // PermissionMetadataKeyRequestingDomain is the origin that is requesting cross-site
44+ // data access (i.e. the third-party domain asking for its cookies/storage).
45+ PermissionMetadataKeyRequestingDomain = "requesting_domain"
46+
47+ // PermissionMetadataKeyCurrentDomain is the first-party domain currently loaded in
48+ // the WebView — the context in which the cross-site request is happening.
49+ PermissionMetadataKeyCurrentDomain = "current_domain"
50+)
51+
41 // PermissionMetadata carries extra context for specific permission types.52 // PermissionMetadata carries extra context for specific permission types.
42-// Keys are permission-type-specific (e.g., "requesting_domain" for website_data_access).53+// For website_data_access, callers must populate both keys:
54+// - PermissionMetadataKeyRequestingDomain: the origin requesting cross-site data access
55+// - PermissionMetadataKeyCurrentDomain: the domain currently loaded in the WebView
43 type PermissionMetadata map[string]string56 type PermissionMetadata map[string]string
44 57
45 // PermissionDecision represents the user's decision for a permission.58 // PermissionDecision represents the user's decision for a permission.
M internal/infrastructure/config/loader.go +14 -3
@@ -224,16 +224,27 @@ func normalizeEngineConfig(config *Config) {
224224 config.Engine.WebKit.GSKRenderer = GSKRendererAuto
225225 }
226226
227- // Normalize cookie policy (engine)
227+ // Normalize cookie policy (engine).
228+ // When no value (or an invalid value) is set, respect the ITP setting: if ITP is
229+ // disabled the effective default is to block third-party cookies; if ITP is enabled
230+ // all cookies are accepted by default. An explicit "always" value always wins.
231+ itpDefault := func() CookiePolicy {
232+ if config.Engine.WebKit.ITPEnabled {
233+ return CookiePolicyAlways
234+ }
235+ return CookiePolicyNoThirdParty
236+ }
228237 switch strings.ToLower(string(config.Engine.CookiePolicy)) {
229- case "", string(CookiePolicyAlways):
238+ case "":
239+ config.Engine.CookiePolicy = itpDefault()
240+ case string(CookiePolicyAlways):
230241 config.Engine.CookiePolicy = CookiePolicyAlways
231242 case string(CookiePolicyNoThirdParty):
232243 config.Engine.CookiePolicy = CookiePolicyNoThirdParty
233244 case string(CookiePolicyNever):
234245 config.Engine.CookiePolicy = CookiePolicyNever
235246 default:
236- config.Engine.CookiePolicy = CookiePolicyAlways
247+ config.Engine.CookiePolicy = itpDefault()
237248 }
238249
239250 // Normalize performance profile (engine)
@@ -224,16 +224,27 @@ func normalizeEngineConfig(config *Config) {
224 config.Engine.WebKit.GSKRenderer = GSKRendererAuto224 config.Engine.WebKit.GSKRenderer = GSKRendererAuto
225 }225 }
226 226
227- // Normalize cookie policy (engine)227+ // Normalize cookie policy (engine).
228+ // When no value (or an invalid value) is set, respect the ITP setting: if ITP is
229+ // disabled the effective default is to block third-party cookies; if ITP is enabled
230+ // all cookies are accepted by default. An explicit "always" value always wins.
231+ itpDefault := func() CookiePolicy {
232+ if config.Engine.WebKit.ITPEnabled {
233+ return CookiePolicyAlways
234+ }
235+ return CookiePolicyNoThirdParty
236+ }
228 switch strings.ToLower(string(config.Engine.CookiePolicy)) {237 switch strings.ToLower(string(config.Engine.CookiePolicy)) {
229- case "", string(CookiePolicyAlways):238+ case "":
239+ config.Engine.CookiePolicy = itpDefault()
240+ case string(CookiePolicyAlways):
230 config.Engine.CookiePolicy = CookiePolicyAlways241 config.Engine.CookiePolicy = CookiePolicyAlways
231 case string(CookiePolicyNoThirdParty):242 case string(CookiePolicyNoThirdParty):
232 config.Engine.CookiePolicy = CookiePolicyNoThirdParty243 config.Engine.CookiePolicy = CookiePolicyNoThirdParty
233 case string(CookiePolicyNever):244 case string(CookiePolicyNever):
234 config.Engine.CookiePolicy = CookiePolicyNever245 config.Engine.CookiePolicy = CookiePolicyNever
235 default:246 default:
236- config.Engine.CookiePolicy = CookiePolicyAlways247+ config.Engine.CookiePolicy = itpDefault()
237 }248 }
238 249
239 // Normalize performance profile (engine)250 // Normalize performance profile (engine)
M internal/infrastructure/webkit/webview.go +10 -6
@@ -11,6 +11,7 @@ import (
1111 "time"
1212
1313 "github.com/bnema/dumber/internal/application/port"
14+ "github.com/bnema/dumber/internal/domain/entity"
1415 urlutil "github.com/bnema/dumber/internal/domain/url"
1516 "github.com/bnema/dumber/internal/infrastructure/desktop"
1617 "github.com/bnema/dumber/internal/logging"
@@ -162,7 +163,8 @@ type WebView struct {
162163 // PermissionRequest is called when a site requests permission (mic, camera, screen sharing).
163164 // Return true to indicate the request was handled. Call allow()/deny() to respond.
164165 // The permission types are determined from the request object.
165- // The metadata map carries permission-type-specific context (e.g., "requesting_domain" for website_data_access).
166+ // The metadata map carries permission-type-specific context; for website_data_access both
167+ // entity.PermissionMetadataKeyRequestingDomain and entity.PermissionMetadataKeyCurrentDomain are populated.
166168 OnPermissionRequest func(origin string, permTypes []string, metadata map[string]string, allow, deny func()) bool
167169
168170 logger zerolog.Logger
@@ -979,15 +981,17 @@ func (wv *WebView) connectWebProcessTerminatedSignal() {
979981 // connectPermissionRequestSignal sets up the permission-request signal handler.
980982 // This is emitted when a site calls getUserMedia() or getDisplayMedia().
981983 func (wv *WebView) connectPermissionRequestSignal() {
982- permissionCb := func(_ webkit.WebView, requestPtr uintptr) bool {
984+ permissionCb := func(inner webkit.WebView, requestPtr uintptr) bool {
983985 ctx := logging.WithContext(context.Background(), wv.logger)
984986
985987 if wv.OnPermissionRequest == nil {
986988 return false // Not handled, WebKit will deny by default
987989 }
988990
989- // Extract and normalize origin from current URI
990- uri := wv.URI()
991+ // Use the live URI from the signal's WebView rather than the cached wv.URI(),
992+ // so permission lookups use the current page origin even if navigation has
993+ // occurred since the last load-changed signal.
994+ uri := inner.GetUri()
991995 if uri == "" {
992996 wv.logger.Debug().Msg("permission request with empty origin, denying")
993997 return false
@@ -1085,8 +1089,8 @@ func (wv *WebView) determinePermissionTypes(ctx context.Context, requestPtr uint
10851089 Str("requesting_domain", requestingDomain).
10861090 Msg("website data access permission request")
10871091 meta := map[string]string{
1088- "requesting_domain": requestingDomain,
1089- "current_domain": currentDomain,
1092+ entity.PermissionMetadataKeyRequestingDomain: requestingDomain,
1093+ entity.PermissionMetadataKeyCurrentDomain: currentDomain,
10901094 }
10911095 return classifyPermissionRequestTypes(ctx, requestKind, false, false, false), meta
10921096 default:
@@ -11,6 +11,7 @@ import (
11 "time"11 "time"
12 12
13 "github.com/bnema/dumber/internal/application/port"13 "github.com/bnema/dumber/internal/application/port"
14+ "github.com/bnema/dumber/internal/domain/entity"
14 urlutil "github.com/bnema/dumber/internal/domain/url"15 urlutil "github.com/bnema/dumber/internal/domain/url"
15 "github.com/bnema/dumber/internal/infrastructure/desktop"16 "github.com/bnema/dumber/internal/infrastructure/desktop"
16 "github.com/bnema/dumber/internal/logging"17 "github.com/bnema/dumber/internal/logging"
@@ -162,7 +163,8 @@ type WebView struct {
162 // PermissionRequest is called when a site requests permission (mic, camera, screen sharing).163 // 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.164 // Return true to indicate the request was handled. Call allow()/deny() to respond.
164 // The permission types are determined from the request object.165 // The permission types are determined from the request object.
165- // The metadata map carries permission-type-specific context (e.g., "requesting_domain" for website_data_access).166+ // The metadata map carries permission-type-specific context; for website_data_access both
167+ // entity.PermissionMetadataKeyRequestingDomain and entity.PermissionMetadataKeyCurrentDomain are populated.
166 OnPermissionRequest func(origin string, permTypes []string, metadata map[string]string, allow, deny func()) bool168 OnPermissionRequest func(origin string, permTypes []string, metadata map[string]string, allow, deny func()) bool
167 169
168 logger zerolog.Logger170 logger zerolog.Logger
@@ -979,15 +981,17 @@ func (wv *WebView) connectWebProcessTerminatedSignal() {
979 // connectPermissionRequestSignal sets up the permission-request signal handler.981 // connectPermissionRequestSignal sets up the permission-request signal handler.
980 // This is emitted when a site calls getUserMedia() or getDisplayMedia().982 // This is emitted when a site calls getUserMedia() or getDisplayMedia().
981 func (wv *WebView) connectPermissionRequestSignal() {983 func (wv *WebView) connectPermissionRequestSignal() {
982- permissionCb := func(_ webkit.WebView, requestPtr uintptr) bool {984+ permissionCb := func(inner webkit.WebView, requestPtr uintptr) bool {
983 ctx := logging.WithContext(context.Background(), wv.logger)985 ctx := logging.WithContext(context.Background(), wv.logger)
984 986
985 if wv.OnPermissionRequest == nil {987 if wv.OnPermissionRequest == nil {
986 return false // Not handled, WebKit will deny by default988 return false // Not handled, WebKit will deny by default
987 }989 }
988 990
989- // Extract and normalize origin from current URI991+ // Use the live URI from the signal's WebView rather than the cached wv.URI(),
990- uri := wv.URI()992+ // so permission lookups use the current page origin even if navigation has
993+ // occurred since the last load-changed signal.
994+ uri := inner.GetUri()
991 if uri == "" {995 if uri == "" {
992 wv.logger.Debug().Msg("permission request with empty origin, denying")996 wv.logger.Debug().Msg("permission request with empty origin, denying")
993 return false997 return false
@@ -1085,8 +1089,8 @@ func (wv *WebView) determinePermissionTypes(ctx context.Context, requestPtr uint
1085 Str("requesting_domain", requestingDomain).1089 Str("requesting_domain", requestingDomain).
1086 Msg("website data access permission request")1090 Msg("website data access permission request")
1087 meta := map[string]string{1091 meta := map[string]string{
1088- "requesting_domain": requestingDomain,1092+ entity.PermissionMetadataKeyRequestingDomain: requestingDomain,
1089- "current_domain": currentDomain,1093+ entity.PermissionMetadataKeyCurrentDomain: currentDomain,
1090 }1094 }
1091 return classifyPermissionRequestTypes(ctx, requestKind, false, false, false), meta1095 return classifyPermissionRequestTypes(ctx, requestKind, false, false, false), meta
1092 default:1096 default:
M internal/ui/dialog/permission.go +2 -2
@@ -216,8 +216,8 @@ func (d *PermissionDialog) buildBody(
216216 parts = append(parts, "share your screen")
217217 }
218218 if f.dataAccess {
219- reqDomain := metadata["requesting_domain"]
220- curDomain := metadata["current_domain"]
219+ reqDomain := metadata[entity.PermissionMetadataKeyRequestingDomain]
220+ curDomain := metadata[entity.PermissionMetadataKeyCurrentDomain]
221221 if reqDomain != "" && curDomain != "" {
222222 parts = append(parts, fmt.Sprintf(
223223 "allow %s to access its data (including cookies) while you browse %s",
@@ -216,8 +216,8 @@ func (d *PermissionDialog) buildBody(
216 parts = append(parts, "share your screen")216 parts = append(parts, "share your screen")
217 }217 }
218 if f.dataAccess {218 if f.dataAccess {
219- reqDomain := metadata["requesting_domain"]219+ reqDomain := metadata[entity.PermissionMetadataKeyRequestingDomain]
220- curDomain := metadata["current_domain"]220+ curDomain := metadata[entity.PermissionMetadataKeyCurrentDomain]
221 if reqDomain != "" && curDomain != "" {221 if reqDomain != "" && curDomain != "" {
222 parts = append(parts, fmt.Sprintf(222 parts = append(parts, fmt.Sprintf(
223 "allow %s to access its data (including cookies) while you browse %s",223 "allow %s to access its data (including cookies) while you browse %s",
M internal/ui/dialog/permission_test.go +2 -2
@@ -163,8 +163,8 @@ func TestPermissionDialog_BuildHeadingAndBody_DisplayCombinations(t *testing.T)
163163 // Test website_data_access with populated metadata (Epiphany-style rich message)
164164 t.Run("website data access with both domains", func(t *testing.T) {
165165 meta := entity.PermissionMetadata{
166- "requesting_domain": "accounts.google.com",
167- "current_domain": "shop.example.com",
166+ entity.PermissionMetadataKeyRequestingDomain: "accounts.google.com",
167+ entity.PermissionMetadataKeyCurrentDomain: "shop.example.com",
168168 }
169169 body := d.buildBody(origin, []entity.PermissionType{entity.PermissionTypeWebsiteDataAccess}, meta)
170170 assert.Equal(t, origin+" wants to allow accounts.google.com to access its data (including cookies) while you browse shop.example.com.", body)
@@ -163,8 +163,8 @@ func TestPermissionDialog_BuildHeadingAndBody_DisplayCombinations(t *testing.T)
163 // Test website_data_access with populated metadata (Epiphany-style rich message)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) {164 t.Run("website data access with both domains", func(t *testing.T) {
165 meta := entity.PermissionMetadata{165 meta := entity.PermissionMetadata{
166- "requesting_domain": "accounts.google.com",166+ entity.PermissionMetadataKeyRequestingDomain: "accounts.google.com",
167- "current_domain": "shop.example.com",167+ entity.PermissionMetadataKeyCurrentDomain: "shop.example.com",
168 }168 }
169 body := d.buildBody(origin, []entity.PermissionType{entity.PermissionTypeWebsiteDataAccess}, meta)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)170 assert.Equal(t, origin+" wants to allow accounts.google.com to access its data (including cookies) while you browse shop.example.com.", body)

Keyboard shortcuts

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

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

Branches 8

Tags 42

↑↓ moveenter openesc close