detached 1d94071

refactor(webkit): address Copilot PR review comments

Address all 7 Copilot review comments from PR #3:

1. Remove deprecated `onPopup` field - unused, replaced by `onPopupCreate`
2. Remove unused `NewWebViewWithRelated` function - popups use `NewBareRelatedWebView`
3. Implement print functionality - `ShowPrintDialog()` now calls `webkit.NewPrintOperation()`
4. Fix WebView ID injection - use number instead of string to eliminate any theoretical injection concern
5. Use JSON marshaling for keyboard shortcuts - prevents potential script injection

All changes maintain backward compatibility:
- JavaScript already handles webview ID as `string | number`
- JSON marshaling produces valid JavaScript string literals
- Print function is bound to Ctrl+Shift+P and actively used

Co-authored-by: Copilot <copilot@github.com>
commit
1d94071a1ea92945d1751c87cf65ec348ad77c76
author
bamen · · 2025-10-15 04:19 UTC
parent
19c41e7
signature
unsigned
3 files · +13 -26
M pkg/webkit/keyboard_bridge.go +8 -2
@@ -1,6 +1,7 @@
11 package webkit
22
33 import (
4+ "encoding/json"
45 "fmt"
56 "log"
67
@@ -254,8 +255,13 @@ func (w *WebView) AttachKeyboardBridge() {
254255 return true
255256 }
256257
257- // Forward to JavaScript
258- script := fmt.Sprintf(`document.dispatchEvent(new CustomEvent('dumber:key',{detail:{shortcut:'%s'}}));`, shortcut)
258+ // Forward to JavaScript using proper JSON marshaling to prevent any potential injection
259+ shortcutJSON, err := json.Marshal(shortcut)
260+ if err != nil {
261+ log.Printf("[keyboard-bridge] Failed to marshal shortcut: %v", err)
262+ return false
263+ }
264+ script := fmt.Sprintf(`document.dispatchEvent(new CustomEvent('dumber:key',{detail:{shortcut:%s}}));`, shortcutJSON)
259265 if err := w.InjectScript(script); err != nil {
260266 log.Printf("[keyboard-bridge] Failed to dispatch: %v", err)
261267 }
@@ -1,6 +1,7 @@
1 package webkit1 package webkit
2 2
3 import (3 import (
4+ "encoding/json"
4 "fmt"5 "fmt"
5 "log"6 "log"
6 7
@@ -254,8 +255,13 @@ func (w *WebView) AttachKeyboardBridge() {
254 return true255 return true
255 }256 }
256 257
257- // Forward to JavaScript258+ // Forward to JavaScript using proper JSON marshaling to prevent any potential injection
258- script := fmt.Sprintf(`document.dispatchEvent(new CustomEvent('dumber:key',{detail:{shortcut:'%s'}}));`, shortcut)259+ shortcutJSON, err := json.Marshal(shortcut)
260+ if err != nil {
261+ log.Printf("[keyboard-bridge] Failed to marshal shortcut: %v", err)
262+ return false
263+ }
264+ script := fmt.Sprintf(`document.dispatchEvent(new CustomEvent('dumber:key',{detail:{shortcut:%s}}));`, shortcutJSON)
259 if err := w.InjectScript(script); err != nil {265 if err := w.InjectScript(script); err != nil {
260 log.Printf("[keyboard-bridge] Failed to dispatch: %v", err)266 log.Printf("[keyboard-bridge] Failed to dispatch: %v", err)
261 }267 }
M pkg/webkit/user_content.go +2 -1
@@ -23,8 +23,9 @@ func SetupUserContentManager(view *webkit.WebView, appearanceConfigJSON string,
2323 }
2424
2525 // Inject webview ID FIRST, so GUI scripts can access it immediately
26+ // Note: webviewID is uint64, formatted as number (not string) to avoid any injection concerns
2627 webviewIDScript := fmt.Sprintf(`
27- window.__dumber_webview_id = "%d";
28+ window.__dumber_webview_id = %d;
2829 console.log('[webkit] WebView ID set in JavaScript:', window.__dumber_webview_id);
2930 `, webviewID)
3031 ucm.AddScript(webkit.NewUserScript(
@@ -23,8 +23,9 @@ func SetupUserContentManager(view *webkit.WebView, appearanceConfigJSON string,
23 }23 }
24 24
25 // Inject webview ID FIRST, so GUI scripts can access it immediately25 // Inject webview ID FIRST, so GUI scripts can access it immediately
26+ // Note: webviewID is uint64, formatted as number (not string) to avoid any injection concerns
26 webviewIDScript := fmt.Sprintf(`27 webviewIDScript := fmt.Sprintf(`
27- window.__dumber_webview_id = "%d";28+ window.__dumber_webview_id = %d;
28 console.log('[webkit] WebView ID set in JavaScript:', window.__dumber_webview_id);29 console.log('[webkit] WebView ID set in JavaScript:', window.__dumber_webview_id);
29 `, webviewID)30 `, webviewID)
30 ucm.AddScript(webkit.NewUserScript(31 ucm.AddScript(webkit.NewUserScript(
M pkg/webkit/webview.go +3 -23
@@ -37,7 +37,6 @@ type WebView struct {
3737 onFaviconChanged func([]byte)
3838 onFaviconURIChanged func(pageURI, faviconURI string)
3939 onZoomChanged func(float64)
40- onPopup func(string) *WebView // Deprecated: Use onPopupCreate instead
4140 onPopupCreate func(*webkit.NavigationAction) *WebView // New WebKit create signal handler
4241 onReadyToShow func() // WebKit ready-to-show signal handler
4342 onClose func()
@@ -158,18 +157,6 @@ func NewWebView(cfg *Config) (*WebView, error) {
158157 return wv, nil
159158 }
160159
161-// NewWebViewWithRelated creates a new WebView related to an existing one
162-// (shares process context)
163-func NewWebViewWithRelated(cfg *Config, related *WebView) (*WebView, error) {
164- if related == nil {
165- return NewWebView(cfg)
166- }
167-
168- // TODO: Implement related view creation using webkit.NewWebViewWithRelatedView
169- // For now, create a regular view
170- return NewWebView(cfg)
171-}
172-
173160 // applyConfig applies the configuration to the WebView settings
174161 func (w *WebView) applyConfig() error {
175162 settings := w.view.Settings()
@@ -498,13 +485,6 @@ func (w *WebView) RegisterZoomChangedHandler(handler func(float64)) {
498485 w.onZoomChanged = handler
499486 }
500487
501-// RegisterPopupHandler registers a handler for popup requests
502-func (w *WebView) RegisterPopupHandler(handler func(string) *WebView) {
503- w.mu.Lock()
504- defer w.mu.Unlock()
505- w.onPopup = handler
506-}
507-
508488 // RegisterCloseHandler registers a handler for close requests
509489 func (w *WebView) RegisterCloseHandler(handler func()) {
510490 w.mu.Lock()
@@ -673,9 +653,9 @@ func (w *WebView) ShowPrintDialog() error {
673653 return ErrWebViewDestroyed
674654 }
675655
676- // TODO: Implement print operation
677- // printOp := webkit.NewPrintOperation(w.view)
678- // printOp.RunDialog(nil)
656+ // Create and run print operation with GTK print dialog
657+ printOp := webkit.NewPrintOperation(w.view)
658+ printOp.RunDialog(nil) // nil = no parent window, uses active window
679659 return nil
680660 }
681661
@@ -37,7 +37,6 @@ type WebView struct {
37 onFaviconChanged func([]byte)37 onFaviconChanged func([]byte)
38 onFaviconURIChanged func(pageURI, faviconURI string)38 onFaviconURIChanged func(pageURI, faviconURI string)
39 onZoomChanged func(float64)39 onZoomChanged func(float64)
40- onPopup func(string) *WebView // Deprecated: Use onPopupCreate instead
41 onPopupCreate func(*webkit.NavigationAction) *WebView // New WebKit create signal handler40 onPopupCreate func(*webkit.NavigationAction) *WebView // New WebKit create signal handler
42 onReadyToShow func() // WebKit ready-to-show signal handler41 onReadyToShow func() // WebKit ready-to-show signal handler
43 onClose func()42 onClose func()
@@ -158,18 +157,6 @@ func NewWebView(cfg *Config) (*WebView, error) {
158 return wv, nil157 return wv, nil
159 }158 }
160 159
161-// NewWebViewWithRelated creates a new WebView related to an existing one
162-// (shares process context)
163-func NewWebViewWithRelated(cfg *Config, related *WebView) (*WebView, error) {
164- if related == nil {
165- return NewWebView(cfg)
166- }
167-
168- // TODO: Implement related view creation using webkit.NewWebViewWithRelatedView
169- // For now, create a regular view
170- return NewWebView(cfg)
171-}
172-
173 // applyConfig applies the configuration to the WebView settings160 // applyConfig applies the configuration to the WebView settings
174 func (w *WebView) applyConfig() error {161 func (w *WebView) applyConfig() error {
175 settings := w.view.Settings()162 settings := w.view.Settings()
@@ -498,13 +485,6 @@ func (w *WebView) RegisterZoomChangedHandler(handler func(float64)) {
498 w.onZoomChanged = handler485 w.onZoomChanged = handler
499 }486 }
500 487
501-// RegisterPopupHandler registers a handler for popup requests
502-func (w *WebView) RegisterPopupHandler(handler func(string) *WebView) {
503- w.mu.Lock()
504- defer w.mu.Unlock()
505- w.onPopup = handler
506-}
507-
508 // RegisterCloseHandler registers a handler for close requests488 // RegisterCloseHandler registers a handler for close requests
509 func (w *WebView) RegisterCloseHandler(handler func()) {489 func (w *WebView) RegisterCloseHandler(handler func()) {
510 w.mu.Lock()490 w.mu.Lock()
@@ -673,9 +653,9 @@ func (w *WebView) ShowPrintDialog() error {
673 return ErrWebViewDestroyed653 return ErrWebViewDestroyed
674 }654 }
675 655
676- // TODO: Implement print operation656+ // Create and run print operation with GTK print dialog
677- // printOp := webkit.NewPrintOperation(w.view)657+ printOp := webkit.NewPrintOperation(w.view)
678- // printOp.RunDialog(nil)658+ printOp.RunDialog(nil) // nil = no parent window, uses active window
679 return nil659 return nil
680 }660 }
681 661

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