fix(webkit): inject palette config and GTK theme at document-start
Fixes appearance config injection timing issue where palette was being injected after page load, causing race condition with GUI initialization. Changes: - Add GTK theme detection using PrefersDarkTheme() from GTK4 settings - Inject window.__dumber_gtk_prefers_dark before color-scheme script - Inject window.__dumber_palette at document-start via UserContentManager - Remove legacy exposeAppearanceConfig() that used post-load injection - Pass appearance config through webkit.Config to UserContentManager This ensures GUI scripts have immediate access to palette and theme data, resolving "[dumber-palette] ERROR: No palette found" console errors.
- commit
- 4be3d7e02bf8f7e71d80e72878f4565b93cf8db9
- author
- bamen · · 2025-10-08 20:03 UTC
- committer
- bamen · · 2025-10-08 21:28 UTC
- parent
- a999d8a
- signature
- unsigned
4 files · +76 -65
M internal/app/browser/webview.go +42 -63
| @@ -51,6 +51,7 @@ func (app *BrowserApp) buildWebkitConfig() (*webkit.Config, error) { | |||
| 51 | 51 | MinimumFontSize: 8, | |
| 52 | 52 | DataDir: webkitData, | |
| 53 | 53 | CacheDir: webkitCache, | |
| 54 | + | AppearanceConfigJSON: app.buildAppearanceConfigJSON(), | |
| 54 | 55 | } | |
| 55 | 56 | ||
| 56 | 57 | return cfg, nil | |
| @@ -65,6 +66,45 @@ func buildUserAgent(cfg *config.Config) string { | |||
| 65 | 66 | return "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" | |
| 66 | 67 | } | |
| 67 | 68 | ||
| 69 | + | // buildAppearanceConfigJSON builds the palette configuration as JSON | |
| 70 | + | // The GUI expects window.__dumber_palette with just the palette data | |
| 71 | + | func (app *BrowserApp) buildAppearanceConfigJSON() string { | |
| 72 | + | if app.config == nil { | |
| 73 | + | return "" | |
| 74 | + | } | |
| 75 | + | ||
| 76 | + | // Build palette config in the format main-world.ts expects | |
| 77 | + | // It looks for window.__dumber_palette = { "light": {...}, "dark": {...} } | |
| 78 | + | paletteConfig := map[string]interface{}{ | |
| 79 | + | "light": map[string]string{ | |
| 80 | + | "background": app.config.Appearance.LightPalette.Background, | |
| 81 | + | "surface": app.config.Appearance.LightPalette.Surface, | |
| 82 | + | "surface_variant": app.config.Appearance.LightPalette.SurfaceVariant, | |
| 83 | + | "text": app.config.Appearance.LightPalette.Text, | |
| 84 | + | "muted": app.config.Appearance.LightPalette.Muted, | |
| 85 | + | "accent": app.config.Appearance.LightPalette.Accent, | |
| 86 | + | "border": app.config.Appearance.LightPalette.Border, | |
| 87 | + | }, | |
| 88 | + | "dark": map[string]string{ | |
| 89 | + | "background": app.config.Appearance.DarkPalette.Background, | |
| 90 | + | "surface": app.config.Appearance.DarkPalette.Surface, | |
| 91 | + | "surface_variant": app.config.Appearance.DarkPalette.SurfaceVariant, | |
| 92 | + | "text": app.config.Appearance.DarkPalette.Text, | |
| 93 | + | "muted": app.config.Appearance.DarkPalette.Muted, | |
| 94 | + | "accent": app.config.Appearance.DarkPalette.Accent, | |
| 95 | + | "border": app.config.Appearance.DarkPalette.Border, | |
| 96 | + | }, | |
| 97 | + | } | |
| 98 | + | ||
| 99 | + | payload, err := json.Marshal(paletteConfig) | |
| 100 | + | if err != nil { | |
| 101 | + | log.Printf("[webview] Failed to marshal palette config: %v", err) | |
| 102 | + | return "" | |
| 103 | + | } | |
| 104 | + | ||
| 105 | + | return string(payload) | |
| 106 | + | } | |
| 107 | + | ||
| 68 | 108 | func (app *BrowserApp) buildPane(view *webkit.WebView) (*BrowserPane, error) { | |
| 69 | 109 | pane := &BrowserPane{ | |
| 70 | 110 | webView: view, | |
| @@ -239,74 +279,13 @@ func (app *BrowserApp) createWebView() error { | |||
| 239 | 279 | func (app *BrowserApp) setupWebViewIntegration() { | |
| 240 | 280 | app.browserService.AttachWebView(app.webView) | |
| 241 | 281 | ||
| 242 | - | // GUI bundle is loaded via WebKit user scripts in enableUserContentManager | |
| 243 | - | // No need to load separately in browser service | |
| 282 | + | // GUI bundle is loaded via WebKit user scripts in SetupUserContentManager | |
| 283 | + | // Appearance config is also injected at document-start via UserContentManager | |
| 244 | 284 | ||
| 245 | 285 | // Use native window as title updater | |
| 246 | 286 | if win := app.webView.Window(); win != nil { | |
| 247 | 287 | app.browserService.SetWindowTitleUpdater(win) | |
| 248 | 288 | } | |
| 249 | - | ||
| 250 | - | // Expose appearance configuration to the injected GUI modules | |
| 251 | - | app.exposeAppearanceConfig(app.webView) | |
| 252 | - | } | |
| 253 | - | ||
| 254 | - | // exposeAppearanceConfig injects appearance configuration as a typed global | |
| 255 | - | // This makes color palettes and font preferences available to TypeScript modules | |
| 256 | - | func (app *BrowserApp) exposeAppearanceConfig(view *webkit.WebView) { | |
| 257 | - | if view == nil || app.config == nil { | |
| 258 | - | return | |
| 259 | - | } | |
| 260 | - | ||
| 261 | - | // Build appearance config matching the TypeScript AppearanceConfig interface | |
| 262 | - | // Note: Theme preference comes from GTK system settings (via color-scheme.ts), | |
| 263 | - | // not from config file. We default to "light" here. | |
| 264 | - | appearanceConfig := map[string]interface{}{ | |
| 265 | - | "theme": "light", // Default theme - color-scheme.ts will detect actual GTK preference | |
| 266 | - | "palettes": map[string]interface{}{ | |
| 267 | - | "light": map[string]string{ | |
| 268 | - | "background": app.config.Appearance.LightPalette.Background, | |
| 269 | - | "surface": app.config.Appearance.LightPalette.Surface, | |
| 270 | - | "surfaceVariant": app.config.Appearance.LightPalette.SurfaceVariant, | |
| 271 | - | "text": app.config.Appearance.LightPalette.Text, | |
| 272 | - | "muted": app.config.Appearance.LightPalette.Muted, | |
| 273 | - | "accent": app.config.Appearance.LightPalette.Accent, | |
| 274 | - | "border": app.config.Appearance.LightPalette.Border, | |
| 275 | - | }, | |
| 276 | - | "dark": map[string]string{ | |
| 277 | - | "background": app.config.Appearance.DarkPalette.Background, | |
| 278 | - | "surface": app.config.Appearance.DarkPalette.Surface, | |
| 279 | - | "surfaceVariant": app.config.Appearance.DarkPalette.SurfaceVariant, | |
| 280 | - | "text": app.config.Appearance.DarkPalette.Text, | |
| 281 | - | "muted": app.config.Appearance.DarkPalette.Muted, | |
| 282 | - | "accent": app.config.Appearance.DarkPalette.Accent, | |
| 283 | - | "border": app.config.Appearance.DarkPalette.Border, | |
| 284 | - | }, | |
| 285 | - | }, | |
| 286 | - | "fonts": map[string]interface{}{ | |
| 287 | - | "sans": app.config.Appearance.SansFont, | |
| 288 | - | "serif": app.config.Appearance.SerifFont, | |
| 289 | - | "monospace": app.config.Appearance.MonospaceFont, | |
| 290 | - | "defaultSize": app.config.Appearance.DefaultFontSize, | |
| 291 | - | }, | |
| 292 | - | } | |
| 293 | - | ||
| 294 | - | // Convert to JSON | |
| 295 | - | payload, err := json.Marshal(appearanceConfig) | |
| 296 | - | if err != nil { | |
| 297 | - | log.Printf("[webview] Failed to marshal appearance config: %v", err) | |
| 298 | - | return | |
| 299 | - | } | |
| 300 | - | ||
| 301 | - | // Inject as global variable before any modules load | |
| 302 | - | // The appearance-config.ts module will detect and apply this | |
| 303 | - | script := fmt.Sprintf(`window.__dumber_appearance_config = %s;`, string(payload)) | |
| 304 | - | ||
| 305 | - | if err := view.InjectScript(script); err != nil { | |
| 306 | - | log.Printf("[webview] Failed to expose appearance config: %v", err) | |
| 307 | - | } else { | |
| 308 | - | log.Printf("[webview] Appearance config exposed with default theme=light") | |
| 309 | - | } | |
| 310 | 289 | } | |
| 311 | 290 | ||
| 312 | 291 | // setupContentBlocking initializes the content blocking system with proper timing | |
| @@ -51,6 +51,7 @@ func (app *BrowserApp) buildWebkitConfig() (*webkit.Config, error) { | |||||
| 51 | MinimumFontSize: 8, | 51 | MinimumFontSize: 8, | ||
| 52 | DataDir: webkitData, | 52 | DataDir: webkitData, | ||
| 53 | CacheDir: webkitCache, | 53 | CacheDir: webkitCache, | ||
| 54 | + | AppearanceConfigJSON: app.buildAppearanceConfigJSON(), | |||
| 54 | } | 55 | } | ||
| 55 | 56 | ||||
| 56 | return cfg, nil | 57 | return cfg, nil | ||
| @@ -65,6 +66,45 @@ func buildUserAgent(cfg *config.Config) string { | |||||
| 65 | return "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" | 66 | return "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" | ||
| 66 | } | 67 | } | ||
| 67 | 68 | ||||
| 69 | + | // buildAppearanceConfigJSON builds the palette configuration as JSON | |||
| 70 | + | // The GUI expects window.__dumber_palette with just the palette data | |||
| 71 | + | func (app *BrowserApp) buildAppearanceConfigJSON() string { | |||
| 72 | + | if app.config == nil { | |||
| 73 | + | return "" | |||
| 74 | + | } | |||
| 75 | + | ||||
| 76 | + | // Build palette config in the format main-world.ts expects | |||
| 77 | + | // It looks for window.__dumber_palette = { "light": {...}, "dark": {...} } | |||
| 78 | + | paletteConfig := map[string]interface{}{ | |||
| 79 | + | "light": map[string]string{ | |||
| 80 | + | "background": app.config.Appearance.LightPalette.Background, | |||
| 81 | + | "surface": app.config.Appearance.LightPalette.Surface, | |||
| 82 | + | "surface_variant": app.config.Appearance.LightPalette.SurfaceVariant, | |||
| 83 | + | "text": app.config.Appearance.LightPalette.Text, | |||
| 84 | + | "muted": app.config.Appearance.LightPalette.Muted, | |||
| 85 | + | "accent": app.config.Appearance.LightPalette.Accent, | |||
| 86 | + | "border": app.config.Appearance.LightPalette.Border, | |||
| 87 | + | }, | |||
| 88 | + | "dark": map[string]string{ | |||
| 89 | + | "background": app.config.Appearance.DarkPalette.Background, | |||
| 90 | + | "surface": app.config.Appearance.DarkPalette.Surface, | |||
| 91 | + | "surface_variant": app.config.Appearance.DarkPalette.SurfaceVariant, | |||
| 92 | + | "text": app.config.Appearance.DarkPalette.Text, | |||
| 93 | + | "muted": app.config.Appearance.DarkPalette.Muted, | |||
| 94 | + | "accent": app.config.Appearance.DarkPalette.Accent, | |||
| 95 | + | "border": app.config.Appearance.DarkPalette.Border, | |||
| 96 | + | }, | |||
| 97 | + | } | |||
| 98 | + | ||||
| 99 | + | payload, err := json.Marshal(paletteConfig) | |||
| 100 | + | if err != nil { | |||
| 101 | + | log.Printf("[webview] Failed to marshal palette config: %v", err) | |||
| 102 | + | return "" | |||
| 103 | + | } | |||
| 104 | + | ||||
| 105 | + | return string(payload) | |||
| 106 | + | } | |||
| 107 | + | ||||
| 68 | func (app *BrowserApp) buildPane(view *webkit.WebView) (*BrowserPane, error) { | 108 | func (app *BrowserApp) buildPane(view *webkit.WebView) (*BrowserPane, error) { | ||
| 69 | pane := &BrowserPane{ | 109 | pane := &BrowserPane{ | ||
| 70 | webView: view, | 110 | webView: view, | ||
| @@ -239,74 +279,13 @@ func (app *BrowserApp) createWebView() error { | |||||
| 239 | func (app *BrowserApp) setupWebViewIntegration() { | 279 | func (app *BrowserApp) setupWebViewIntegration() { | ||
| 240 | app.browserService.AttachWebView(app.webView) | 280 | app.browserService.AttachWebView(app.webView) | ||
| 241 | 281 | ||||
| 242 | - | // GUI bundle is loaded via WebKit user scripts in enableUserContentManager | 282 | + | // GUI bundle is loaded via WebKit user scripts in SetupUserContentManager |
| 243 | - | // No need to load separately in browser service | 283 | + | // Appearance config is also injected at document-start via UserContentManager |
| 244 | 284 | ||||
| 245 | // Use native window as title updater | 285 | // Use native window as title updater | ||
| 246 | if win := app.webView.Window(); win != nil { | 286 | if win := app.webView.Window(); win != nil { | ||
| 247 | app.browserService.SetWindowTitleUpdater(win) | 287 | app.browserService.SetWindowTitleUpdater(win) | ||
| 248 | } | 288 | } | ||
| 249 | - | ||||
| 250 | - | // Expose appearance configuration to the injected GUI modules | |||
| 251 | - | app.exposeAppearanceConfig(app.webView) | |||
| 252 | - | } | |||
| 253 | - | ||||
| 254 | - | // exposeAppearanceConfig injects appearance configuration as a typed global | |||
| 255 | - | // This makes color palettes and font preferences available to TypeScript modules | |||
| 256 | - | func (app *BrowserApp) exposeAppearanceConfig(view *webkit.WebView) { | |||
| 257 | - | if view == nil || app.config == nil { | |||
| 258 | - | return | |||
| 259 | - | } | |||
| 260 | - | ||||
| 261 | - | // Build appearance config matching the TypeScript AppearanceConfig interface | |||
| 262 | - | // Note: Theme preference comes from GTK system settings (via color-scheme.ts), | |||
| 263 | - | // not from config file. We default to "light" here. | |||
| 264 | - | appearanceConfig := map[string]interface{}{ | |||
| 265 | - | "theme": "light", // Default theme - color-scheme.ts will detect actual GTK preference | |||
| 266 | - | "palettes": map[string]interface{}{ | |||
| 267 | - | "light": map[string]string{ | |||
| 268 | - | "background": app.config.Appearance.LightPalette.Background, | |||
| 269 | - | "surface": app.config.Appearance.LightPalette.Surface, | |||
| 270 | - | "surfaceVariant": app.config.Appearance.LightPalette.SurfaceVariant, | |||
| 271 | - | "text": app.config.Appearance.LightPalette.Text, | |||
| 272 | - | "muted": app.config.Appearance.LightPalette.Muted, | |||
| 273 | - | "accent": app.config.Appearance.LightPalette.Accent, | |||
| 274 | - | "border": app.config.Appearance.LightPalette.Border, | |||
| 275 | - | }, | |||
| 276 | - | "dark": map[string]string{ | |||
| 277 | - | "background": app.config.Appearance.DarkPalette.Background, | |||
| 278 | - | "surface": app.config.Appearance.DarkPalette.Surface, | |||
| 279 | - | "surfaceVariant": app.config.Appearance.DarkPalette.SurfaceVariant, | |||
| 280 | - | "text": app.config.Appearance.DarkPalette.Text, | |||
| 281 | - | "muted": app.config.Appearance.DarkPalette.Muted, | |||
| 282 | - | "accent": app.config.Appearance.DarkPalette.Accent, | |||
| 283 | - | "border": app.config.Appearance.DarkPalette.Border, | |||
| 284 | - | }, | |||
| 285 | - | }, | |||
| 286 | - | "fonts": map[string]interface{}{ | |||
| 287 | - | "sans": app.config.Appearance.SansFont, | |||
| 288 | - | "serif": app.config.Appearance.SerifFont, | |||
| 289 | - | "monospace": app.config.Appearance.MonospaceFont, | |||
| 290 | - | "defaultSize": app.config.Appearance.DefaultFontSize, | |||
| 291 | - | }, | |||
| 292 | - | } | |||
| 293 | - | ||||
| 294 | - | // Convert to JSON | |||
| 295 | - | payload, err := json.Marshal(appearanceConfig) | |||
| 296 | - | if err != nil { | |||
| 297 | - | log.Printf("[webview] Failed to marshal appearance config: %v", err) | |||
| 298 | - | return | |||
| 299 | - | } | |||
| 300 | - | ||||
| 301 | - | // Inject as global variable before any modules load | |||
| 302 | - | // The appearance-config.ts module will detect and apply this | |||
| 303 | - | script := fmt.Sprintf(`window.__dumber_appearance_config = %s;`, string(payload)) | |||
| 304 | - | ||||
| 305 | - | if err := view.InjectScript(script); err != nil { | |||
| 306 | - | log.Printf("[webview] Failed to expose appearance config: %v", err) | |||
| 307 | - | } else { | |||
| 308 | - | log.Printf("[webview] Appearance config exposed with default theme=light") | |||
| 309 | - | } | |||
| 310 | } | 289 | } | ||
| 311 | 290 | ||||
| 312 | // setupContentBlocking initializes the content blocking system with proper timing | 291 | // setupContentBlocking initializes the content blocking system with proper timing | ||
M pkg/webkit/types.go +4 -0
| @@ -50,6 +50,10 @@ type Config struct { | |||
| 50 | 50 | ||
| 51 | 51 | // CacheDir is the directory for HTTP cache | |
| 52 | 52 | CacheDir string | |
| 53 | + | ||
| 54 | + | // AppearanceConfigJSON is the JSON string for appearance configuration | |
| 55 | + | // This will be injected at document-start via UserContentManager | |
| 56 | + | AppearanceConfigJSON string | |
| 53 | 57 | } | |
| 54 | 58 | ||
| 55 | 59 | // GetDefaultConfig returns a Config with sensible defaults | |
| @@ -50,6 +50,10 @@ type Config struct { | |||||
| 50 | 50 | ||||
| 51 | // CacheDir is the directory for HTTP cache | 51 | // CacheDir is the directory for HTTP cache | ||
| 52 | CacheDir string | 52 | CacheDir string | ||
| 53 | + | ||||
| 54 | + | // AppearanceConfigJSON is the JSON string for appearance configuration | |||
| 55 | + | // This will be injected at document-start via UserContentManager | |||
| 56 | + | AppearanceConfigJSON string | |||
| 53 | } | 57 | } | ||
| 54 | 58 | ||||
| 55 | // GetDefaultConfig returns a Config with sensible defaults | 59 | // GetDefaultConfig returns a Config with sensible defaults | ||
M pkg/webkit/user_content.go +29 -1
| @@ -1,6 +1,7 @@ | |||
| 1 | 1 | package webkit | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "fmt" | |
| 4 | 5 | "log" | |
| 5 | 6 | ||
| 6 | 7 | "github.com/bnema/dumber/assets" | |
| @@ -9,7 +10,7 @@ import ( | |||
| 9 | 10 | ||
| 10 | 11 | // SetupUserContentManager configures UserContentManager for the WebView | |
| 11 | 12 | // This injects GUI scripts at document-start and registers message handlers | |
| 12 | - | func SetupUserContentManager(view *webkit.WebView) error { | |
| 13 | + | func SetupUserContentManager(view *webkit.WebView, appearanceConfigJSON string) error { | |
| 13 | 14 | if view == nil { | |
| 14 | 15 | return nil | |
| 15 | 16 | } | |
| @@ -21,6 +22,33 @@ func SetupUserContentManager(view *webkit.WebView) error { | |||
| 21 | 22 | return nil | |
| 22 | 23 | } | |
| 23 | 24 | ||
| 25 | + | // Inject GTK theme detection FIRST, before color-scheme script | |
| 26 | + | // The color-scheme.ts expects window.__dumber_gtk_prefers_dark to be set | |
| 27 | + | prefersDark := PrefersDarkTheme() | |
| 28 | + | gtkThemeScript := fmt.Sprintf(`window.__dumber_gtk_prefers_dark = %t;`, prefersDark) | |
| 29 | + | ucm.AddScript(webkit.NewUserScript( | |
| 30 | + | gtkThemeScript, | |
| 31 | + | webkit.UserContentInjectAllFrames, | |
| 32 | + | webkit.UserScriptInjectAtDocumentStart, | |
| 33 | + | nil, | |
| 34 | + | nil, | |
| 35 | + | )) | |
| 36 | + | log.Printf("[webkit] Injected GTK theme preference: prefersDark=%t", prefersDark) | |
| 37 | + | ||
| 38 | + | // Inject palette config SECOND, before GUI scripts | |
| 39 | + | // The GUI expects window.__dumber_palette = { "light": {...}, "dark": {...} } | |
| 40 | + | if appearanceConfigJSON != "" { | |
| 41 | + | paletteScript := fmt.Sprintf(`window.__dumber_palette = %s;`, appearanceConfigJSON) | |
| 42 | + | ucm.AddScript(webkit.NewUserScript( | |
| 43 | + | paletteScript, | |
| 44 | + | webkit.UserContentInjectAllFrames, | |
| 45 | + | webkit.UserScriptInjectAtDocumentStart, | |
| 46 | + | nil, | |
| 47 | + | nil, | |
| 48 | + | )) | |
| 49 | + | log.Printf("[webkit] Injected palette config at document-start (%d bytes)", len(paletteScript)) | |
| 50 | + | } | |
| 51 | + | ||
| 24 | 52 | // Inject color-scheme script at document-start | |
| 25 | 53 | if assets.ColorSchemeScript != "" { | |
| 26 | 54 | ucm.AddScript(webkit.NewUserScript( | |
| @@ -1,6 +1,7 @@ | |||||
| 1 | package webkit | 1 | package webkit | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "fmt" | |||
| 4 | "log" | 5 | "log" | ||
| 5 | 6 | ||||
| 6 | "github.com/bnema/dumber/assets" | 7 | "github.com/bnema/dumber/assets" | ||
| @@ -9,7 +10,7 @@ import ( | |||||
| 9 | 10 | ||||
| 10 | // SetupUserContentManager configures UserContentManager for the WebView | 11 | // SetupUserContentManager configures UserContentManager for the WebView | ||
| 11 | // This injects GUI scripts at document-start and registers message handlers | 12 | // This injects GUI scripts at document-start and registers message handlers | ||
| 12 | - | func SetupUserContentManager(view *webkit.WebView) error { | 13 | + | func SetupUserContentManager(view *webkit.WebView, appearanceConfigJSON string) error { |
| 13 | if view == nil { | 14 | if view == nil { | ||
| 14 | return nil | 15 | return nil | ||
| 15 | } | 16 | } | ||
| @@ -21,6 +22,33 @@ func SetupUserContentManager(view *webkit.WebView) error { | |||||
| 21 | return nil | 22 | return nil | ||
| 22 | } | 23 | } | ||
| 23 | 24 | ||||
| 25 | + | // Inject GTK theme detection FIRST, before color-scheme script | |||
| 26 | + | // The color-scheme.ts expects window.__dumber_gtk_prefers_dark to be set | |||
| 27 | + | prefersDark := PrefersDarkTheme() | |||
| 28 | + | gtkThemeScript := fmt.Sprintf(`window.__dumber_gtk_prefers_dark = %t;`, prefersDark) | |||
| 29 | + | ucm.AddScript(webkit.NewUserScript( | |||
| 30 | + | gtkThemeScript, | |||
| 31 | + | webkit.UserContentInjectAllFrames, | |||
| 32 | + | webkit.UserScriptInjectAtDocumentStart, | |||
| 33 | + | nil, | |||
| 34 | + | nil, | |||
| 35 | + | )) | |||
| 36 | + | log.Printf("[webkit] Injected GTK theme preference: prefersDark=%t", prefersDark) | |||
| 37 | + | ||||
| 38 | + | // Inject palette config SECOND, before GUI scripts | |||
| 39 | + | // The GUI expects window.__dumber_palette = { "light": {...}, "dark": {...} } | |||
| 40 | + | if appearanceConfigJSON != "" { | |||
| 41 | + | paletteScript := fmt.Sprintf(`window.__dumber_palette = %s;`, appearanceConfigJSON) | |||
| 42 | + | ucm.AddScript(webkit.NewUserScript( | |||
| 43 | + | paletteScript, | |||
| 44 | + | webkit.UserContentInjectAllFrames, | |||
| 45 | + | webkit.UserScriptInjectAtDocumentStart, | |||
| 46 | + | nil, | |||
| 47 | + | nil, | |||
| 48 | + | )) | |||
| 49 | + | log.Printf("[webkit] Injected palette config at document-start (%d bytes)", len(paletteScript)) | |||
| 50 | + | } | |||
| 51 | + | ||||
| 24 | // Inject color-scheme script at document-start | 52 | // Inject color-scheme script at document-start | ||
| 25 | if assets.ColorSchemeScript != "" { | 53 | if assets.ColorSchemeScript != "" { | ||
| 26 | ucm.AddScript(webkit.NewUserScript( | 54 | ucm.AddScript(webkit.NewUserScript( | ||
M pkg/webkit/webview.go +1 -1
| @@ -93,7 +93,7 @@ func NewWebView(cfg *Config) (*WebView, error) { | |||
| 93 | 93 | ||
| 94 | 94 | // Setup UserContentManager and inject GUI scripts | |
| 95 | 95 | // This must be done BEFORE any pages are loaded | |
| 96 | - | if err := SetupUserContentManager(wkView); err != nil { | |
| 96 | + | if err := SetupUserContentManager(wkView, cfg.AppearanceConfigJSON); err != nil { | |
| 97 | 97 | return nil, fmt.Errorf("failed to setup user content manager: %w", err) | |
| 98 | 98 | } | |
| 99 | 99 | ||
| @@ -93,7 +93,7 @@ func NewWebView(cfg *Config) (*WebView, error) { | |||||
| 93 | 93 | ||||
| 94 | // Setup UserContentManager and inject GUI scripts | 94 | // Setup UserContentManager and inject GUI scripts | ||
| 95 | // This must be done BEFORE any pages are loaded | 95 | // This must be done BEFORE any pages are loaded | ||
| 96 | - | if err := SetupUserContentManager(wkView); err != nil { | 96 | + | if err := SetupUserContentManager(wkView, cfg.AppearanceConfigJSON); err != nil { |
| 97 | return nil, fmt.Errorf("failed to setup user content manager: %w", err) | 97 | return nil, fmt.Errorf("failed to setup user content manager: %w", err) | ||
| 98 | } | 98 | } | ||
| 99 | 99 | ||||