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) { | |||
| 91 | 91 | cookieManager.SetPersistentStorage(cookiePath, webkit.CookiePersistentStorageSqlite) | |
| 92 | 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 | 100 | // Generate unique ID | |
| 95 | 101 | id := atomic.AddUint64(&viewIDCounter, 1) | |
| 96 | 102 | ||
| @@ -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 ID | 100 | // Generate unique ID | ||
| 95 | id := atomic.AddUint64(&viewIDCounter, 1) | 101 | id := atomic.AddUint64(&viewIDCounter, 1) | ||
| 96 | 102 | ||||