detached a999d8a

feat(webkit): implement UserContentManager with GUI script injection

- Add assets/gui_embed.go to embed GUI scripts at compile time
- Create SetupUserContentManager() to inject scripts at document-start
- Inject color-scheme, main-world, and GUI control scripts
- Register 'dumber' script message handler for JS communication
- Wire up UserContentManager in NewWebView before page loads

This restores GUI elements (omnibox, controls) lost in gotk4 migration
commit
a999d8a33cc6215718fe66907b4dabaab8d05e35
author
bamen · · 2025-10-08 18:22 UTC
committer
bamen · · 2025-10-08 21:26 UTC
parent
737b447
signature
unsigned
3 files · +89 -0
A assets/gui_embed.go +14 -0
@@ -0,0 +1,14 @@
1+package assets
2+
3+import _ "embed"
4+
5+// GUI scripts embedded at compile time
6+
7+//go:embed gui/main-world.min.js
8+var MainWorldScript string
9+
10+//go:embed gui/gui.min.js
11+var GUIScript string
12+
13+//go:embed gui/color-scheme.js
14+var ColorSchemeScript string
@@ -0,0 +1,14 @@
1+package assets
2+
3+import _ "embed"
4+
5+// GUI scripts embedded at compile time
6+
7+//go:embed gui/main-world.min.js
8+var MainWorldScript string
9+
10+//go:embed gui/gui.min.js
11+var GUIScript string
12+
13+//go:embed gui/color-scheme.js
14+var ColorSchemeScript string
A pkg/webkit/user_content.go +69 -0
@@ -0,0 +1,69 @@
1+package webkit
2+
3+import (
4+ "log"
5+
6+ "github.com/bnema/dumber/assets"
7+ webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6"
8+)
9+
10+// SetupUserContentManager configures UserContentManager for the WebView
11+// This injects GUI scripts at document-start and registers message handlers
12+func SetupUserContentManager(view *webkit.WebView) error {
13+ if view == nil {
14+ return nil
15+ }
16+
17+ // Get the UserContentManager from the WebView
18+ ucm := view.UserContentManager()
19+ if ucm == nil {
20+ log.Printf("[webkit] UserContentManager is nil, skipping script injection")
21+ return nil
22+ }
23+
24+ // Inject color-scheme script at document-start
25+ if assets.ColorSchemeScript != "" {
26+ ucm.AddScript(webkit.NewUserScript(
27+ assets.ColorSchemeScript,
28+ webkit.UserContentInjectAllFrames,
29+ webkit.UserScriptInjectAtDocumentStart,
30+ nil, // whitelist (nil = all)
31+ nil, // blacklist (nil = none)
32+ ))
33+ log.Printf("[webkit] Injected color-scheme script (%d bytes)", len(assets.ColorSchemeScript))
34+ }
35+
36+ // Inject main-world script at document-start
37+ if assets.MainWorldScript != "" {
38+ ucm.AddScript(webkit.NewUserScript(
39+ assets.MainWorldScript,
40+ webkit.UserContentInjectAllFrames,
41+ webkit.UserScriptInjectAtDocumentStart,
42+ nil,
43+ nil,
44+ ))
45+ log.Printf("[webkit] Injected main-world script (%d bytes)", len(assets.MainWorldScript))
46+ }
47+
48+ // Inject GUI controls script at document-start
49+ if assets.GUIScript != "" {
50+ ucm.AddScript(webkit.NewUserScript(
51+ assets.GUIScript,
52+ webkit.UserContentInjectAllFrames,
53+ webkit.UserScriptInjectAtDocumentStart,
54+ nil,
55+ nil,
56+ ))
57+ log.Printf("[webkit] Injected GUI controls script (%d bytes)", len(assets.GUIScript))
58+ }
59+
60+ // Register script message handler "dumber" for communication from JS
61+ // Pass empty string for worldName to use the default world
62+ if !ucm.RegisterScriptMessageHandler("dumber", "") {
63+ log.Printf("[webkit] Warning: failed to register 'dumber' script message handler")
64+ } else {
65+ log.Printf("[webkit] Registered 'dumber' script message handler")
66+ }
67+
68+ return nil
69+}
@@ -0,0 +1,69 @@
1+package webkit
2+
3+import (
4+ "log"
5+
6+ "github.com/bnema/dumber/assets"
7+ webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6"
8+)
9+
10+// SetupUserContentManager configures UserContentManager for the WebView
11+// This injects GUI scripts at document-start and registers message handlers
12+func SetupUserContentManager(view *webkit.WebView) error {
13+ if view == nil {
14+ return nil
15+ }
16+
17+ // Get the UserContentManager from the WebView
18+ ucm := view.UserContentManager()
19+ if ucm == nil {
20+ log.Printf("[webkit] UserContentManager is nil, skipping script injection")
21+ return nil
22+ }
23+
24+ // Inject color-scheme script at document-start
25+ if assets.ColorSchemeScript != "" {
26+ ucm.AddScript(webkit.NewUserScript(
27+ assets.ColorSchemeScript,
28+ webkit.UserContentInjectAllFrames,
29+ webkit.UserScriptInjectAtDocumentStart,
30+ nil, // whitelist (nil = all)
31+ nil, // blacklist (nil = none)
32+ ))
33+ log.Printf("[webkit] Injected color-scheme script (%d bytes)", len(assets.ColorSchemeScript))
34+ }
35+
36+ // Inject main-world script at document-start
37+ if assets.MainWorldScript != "" {
38+ ucm.AddScript(webkit.NewUserScript(
39+ assets.MainWorldScript,
40+ webkit.UserContentInjectAllFrames,
41+ webkit.UserScriptInjectAtDocumentStart,
42+ nil,
43+ nil,
44+ ))
45+ log.Printf("[webkit] Injected main-world script (%d bytes)", len(assets.MainWorldScript))
46+ }
47+
48+ // Inject GUI controls script at document-start
49+ if assets.GUIScript != "" {
50+ ucm.AddScript(webkit.NewUserScript(
51+ assets.GUIScript,
52+ webkit.UserContentInjectAllFrames,
53+ webkit.UserScriptInjectAtDocumentStart,
54+ nil,
55+ nil,
56+ ))
57+ log.Printf("[webkit] Injected GUI controls script (%d bytes)", len(assets.GUIScript))
58+ }
59+
60+ // Register script message handler "dumber" for communication from JS
61+ // Pass empty string for worldName to use the default world
62+ if !ucm.RegisterScriptMessageHandler("dumber", "") {
63+ log.Printf("[webkit] Warning: failed to register 'dumber' script message handler")
64+ } else {
65+ log.Printf("[webkit] Registered 'dumber' script message handler")
66+ }
67+
68+ return nil
69+}
M pkg/webkit/webview.go +6 -0
@@ -91,6 +91,12 @@ func NewWebView(cfg *Config) (*WebView, error) {
9191 cookieManager.SetPersistentStorage(cookiePath, webkit.CookiePersistentStorageSqlite)
9292 cookieManager.SetAcceptPolicy(webkit.CookiePolicyAcceptNoThirdParty)
9393
94+ // Setup UserContentManager and inject GUI scripts
95+ // This must be done BEFORE any pages are loaded
96+ if err := SetupUserContentManager(wkView); err != nil {
97+ return nil, fmt.Errorf("failed to setup user content manager: %w", err)
98+ }
99+
94100 // Generate unique ID
95101 id := atomic.AddUint64(&viewIDCounter, 1)
96102
@@ -91,6 +91,12 @@ func NewWebView(cfg *Config) (*WebView, error) {
91 cookieManager.SetPersistentStorage(cookiePath, webkit.CookiePersistentStorageSqlite)91 cookieManager.SetPersistentStorage(cookiePath, webkit.CookiePersistentStorageSqlite)
92 cookieManager.SetAcceptPolicy(webkit.CookiePolicyAcceptNoThirdParty)92 cookieManager.SetAcceptPolicy(webkit.CookiePolicyAcceptNoThirdParty)
93 93
94+ // Setup UserContentManager and inject GUI scripts
95+ // This must be done BEFORE any pages are loaded
96+ if err := SetupUserContentManager(wkView); err != nil {
97+ return nil, fmt.Errorf("failed to setup user content manager: %w", err)
98+ }
99+
94 // Generate unique ID100 // Generate unique ID
95 id := atomic.AddUint64(&viewIDCounter, 1)101 id := atomic.AddUint64(&viewIDCounter, 1)
96 102

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