feat(webkit): enable favicon support and expose FaviconDatabase
Enable favicon collection in WebKit context, expose FaviconDatabase reference, add notify::favicon signal handler to WebView, and implement Favicon() getter to retrieve current page favicon as GdkTexture.
- commit
- 4130d3edf4d82280d4063b8552e9d5cee5cb8b0f
- author
- brice · · 2025-12-15 21:38 UTC
- committer
- brice · · 2025-12-15 18:16 UTC
- parent
- cb46c3d
- signature
- unsigned
2 files · +43 -0
M internal/infrastructure/webkit/context.go +15 -0
| @@ -16,6 +16,7 @@ import ( | |||
| 16 | 16 | type WebKitContext struct { | |
| 17 | 17 | webContext *webkit.WebContext | |
| 18 | 18 | networkSession *webkit.NetworkSession | |
| 19 | + | faviconDB *webkit.FaviconDatabase | |
| 19 | 20 | ||
| 20 | 21 | dataDir string | |
| 21 | 22 | cacheDir string | |
| @@ -93,6 +94,13 @@ func (c *WebKitContext) initNetworkSession() error { | |||
| 93 | 94 | return fmt.Errorf("website data manager is ephemeral") | |
| 94 | 95 | } | |
| 95 | 96 | ||
| 97 | + | // Enable favicon collection and get database reference | |
| 98 | + | dataManager.SetFaviconsEnabled(true) | |
| 99 | + | c.faviconDB = dataManager.GetFaviconDatabase() | |
| 100 | + | if c.faviconDB != nil { | |
| 101 | + | c.logger.Debug().Msg("favicon database enabled") | |
| 102 | + | } | |
| 103 | + | ||
| 96 | 104 | // Configure cookie storage | |
| 97 | 105 | cookieManager := session.GetCookieManager() | |
| 98 | 106 | if cookieManager == nil { | |
| @@ -139,6 +147,13 @@ func (c *WebKitContext) NetworkSession() *webkit.NetworkSession { | |||
| 139 | 147 | return c.networkSession | |
| 140 | 148 | } | |
| 141 | 149 | ||
| 150 | + | // FaviconDatabase returns the favicon database for persistent favicon storage. | |
| 151 | + | func (c *WebKitContext) FaviconDatabase() *webkit.FaviconDatabase { | |
| 152 | + | c.mu.RLock() | |
| 153 | + | defer c.mu.RUnlock() | |
| 154 | + | return c.faviconDB | |
| 155 | + | } | |
| 156 | + | ||
| 142 | 157 | // DataDir returns the data directory path. | |
| 143 | 158 | func (c *WebKitContext) DataDir() string { | |
| 144 | 159 | return c.dataDir | |
| @@ -16,6 +16,7 @@ import ( | |||||
| 16 | type WebKitContext struct { | 16 | type WebKitContext struct { | ||
| 17 | webContext *webkit.WebContext | 17 | webContext *webkit.WebContext | ||
| 18 | networkSession *webkit.NetworkSession | 18 | networkSession *webkit.NetworkSession | ||
| 19 | + | faviconDB *webkit.FaviconDatabase | |||
| 19 | 20 | ||||
| 20 | dataDir string | 21 | dataDir string | ||
| 21 | cacheDir string | 22 | cacheDir string | ||
| @@ -93,6 +94,13 @@ func (c *WebKitContext) initNetworkSession() error { | |||||
| 93 | return fmt.Errorf("website data manager is ephemeral") | 94 | return fmt.Errorf("website data manager is ephemeral") | ||
| 94 | } | 95 | } | ||
| 95 | 96 | ||||
| 97 | + | // Enable favicon collection and get database reference | |||
| 98 | + | dataManager.SetFaviconsEnabled(true) | |||
| 99 | + | c.faviconDB = dataManager.GetFaviconDatabase() | |||
| 100 | + | if c.faviconDB != nil { | |||
| 101 | + | c.logger.Debug().Msg("favicon database enabled") | |||
| 102 | + | } | |||
| 103 | + | ||||
| 96 | // Configure cookie storage | 104 | // Configure cookie storage | ||
| 97 | cookieManager := session.GetCookieManager() | 105 | cookieManager := session.GetCookieManager() | ||
| 98 | if cookieManager == nil { | 106 | if cookieManager == nil { | ||
| @@ -139,6 +147,13 @@ func (c *WebKitContext) NetworkSession() *webkit.NetworkSession { | |||||
| 139 | return c.networkSession | 147 | return c.networkSession | ||
| 140 | } | 148 | } | ||
| 141 | 149 | ||||
| 150 | + | // FaviconDatabase returns the favicon database for persistent favicon storage. | |||
| 151 | + | func (c *WebKitContext) FaviconDatabase() *webkit.FaviconDatabase { | |||
| 152 | + | c.mu.RLock() | |||
| 153 | + | defer c.mu.RUnlock() | |||
| 154 | + | return c.faviconDB | |||
| 155 | + | } | |||
| 156 | + | ||||
| 142 | // DataDir returns the data directory path. | 157 | // DataDir returns the data directory path. | ||
| 143 | func (c *WebKitContext) DataDir() string { | 158 | func (c *WebKitContext) DataDir() string { | ||
| 144 | return c.dataDir | 159 | return c.dataDir | ||
M internal/infrastructure/webkit/webview.go +28 -0
| @@ -9,6 +9,7 @@ import ( | |||
| 9 | 9 | "github.com/bnema/dumber/internal/application/port" | |
| 10 | 10 | "github.com/bnema/dumber/internal/logging" | |
| 11 | 11 | "github.com/bnema/puregotk-webkit/webkit" | |
| 12 | + | "github.com/jwijenbergh/puregotk/v4/gdk" | |
| 12 | 13 | "github.com/jwijenbergh/puregotk/v4/gio" | |
| 13 | 14 | "github.com/jwijenbergh/puregotk/v4/glib" | |
| 14 | 15 | "github.com/jwijenbergh/puregotk/v4/gobject" | |
| @@ -101,6 +102,7 @@ type WebView struct { | |||
| 101 | 102 | OnTitleChanged func(string) | |
| 102 | 103 | OnURIChanged func(string) | |
| 103 | 104 | OnProgressChanged func(float64) | |
| 105 | + | OnFaviconChanged func(*gdk.Texture) // Called when page favicon changes | |
| 104 | 106 | OnClose func() | |
| 105 | 107 | OnCreate func(PopupRequest) *WebView // Return new WebView or nil to block popup | |
| 106 | 108 | OnReadyToShow func() // Called when popup is ready to display | |
| @@ -293,6 +295,17 @@ func (wv *WebView) connectSignals() { | |||
| 293 | 295 | } | |
| 294 | 296 | sigID = gobject.SignalConnect(wv.inner.GoPointer(), "notify::title", glib.NewCallback(&titleCb)) | |
| 295 | 297 | wv.signalIDs = append(wv.signalIDs, sigID) | |
| 298 | + | ||
| 299 | + | // notify::favicon signal for favicon changes | |
| 300 | + | // This fires when the page favicon changes (after page load or dynamic updates) | |
| 301 | + | faviconCb := func() { | |
| 302 | + | favicon := wv.inner.GetFavicon() | |
| 303 | + | if wv.OnFaviconChanged != nil { | |
| 304 | + | wv.OnFaviconChanged(favicon) | |
| 305 | + | } | |
| 306 | + | } | |
| 307 | + | sigID = gobject.SignalConnect(wv.inner.GoPointer(), "notify::favicon", glib.NewCallback(&faviconCb)) | |
| 308 | + | wv.signalIDs = append(wv.signalIDs, sigID) | |
| 296 | 309 | } | |
| 297 | 310 | ||
| 298 | 311 | // ID returns the unique identifier for this WebView. | |
| @@ -412,6 +425,15 @@ func (wv *WebView) Title() string { | |||
| 412 | 425 | return wv.title | |
| 413 | 426 | } | |
| 414 | 427 | ||
| 428 | + | // Favicon returns the current page favicon as a GdkTexture. | |
| 429 | + | // Returns nil if no favicon is available. | |
| 430 | + | func (wv *WebView) Favicon() *gdk.Texture { | |
| 431 | + | if wv.destroyed.Load() { | |
| 432 | + | return nil | |
| 433 | + | } | |
| 434 | + | return wv.inner.GetFavicon() | |
| 435 | + | } | |
| 436 | + | ||
| 415 | 437 | // IsLoading returns true if a page is currently loading. | |
| 416 | 438 | func (wv *WebView) IsLoading() bool { | |
| 417 | 439 | wv.mu.RLock() | |
| @@ -464,6 +486,7 @@ func (wv *WebView) SetCallbacks(callbacks *port.WebViewCallbacks) { | |||
| 464 | 486 | wv.OnTitleChanged = nil | |
| 465 | 487 | wv.OnURIChanged = nil | |
| 466 | 488 | wv.OnProgressChanged = nil | |
| 489 | + | wv.OnFaviconChanged = nil | |
| 467 | 490 | wv.OnClose = nil | |
| 468 | 491 | wv.OnCreate = nil | |
| 469 | 492 | return | |
| @@ -478,6 +501,11 @@ func (wv *WebView) SetCallbacks(callbacks *port.WebViewCallbacks) { | |||
| 478 | 501 | wv.OnTitleChanged = callbacks.OnTitleChanged | |
| 479 | 502 | wv.OnURIChanged = callbacks.OnURIChanged | |
| 480 | 503 | wv.OnProgressChanged = callbacks.OnProgressChanged | |
| 504 | + | if callbacks.OnFaviconChanged != nil { | |
| 505 | + | wv.OnFaviconChanged = func(texture *gdk.Texture) { | |
| 506 | + | callbacks.OnFaviconChanged(texture) | |
| 507 | + | } | |
| 508 | + | } | |
| 481 | 509 | wv.OnClose = callbacks.OnClose | |
| 482 | 510 | if callbacks.OnCreate != nil { | |
| 483 | 511 | wv.OnCreate = func(req PopupRequest) *WebView { | |
| @@ -9,6 +9,7 @@ import ( | |||||
| 9 | "github.com/bnema/dumber/internal/application/port" | 9 | "github.com/bnema/dumber/internal/application/port" | ||
| 10 | "github.com/bnema/dumber/internal/logging" | 10 | "github.com/bnema/dumber/internal/logging" | ||
| 11 | "github.com/bnema/puregotk-webkit/webkit" | 11 | "github.com/bnema/puregotk-webkit/webkit" | ||
| 12 | + | "github.com/jwijenbergh/puregotk/v4/gdk" | |||
| 12 | "github.com/jwijenbergh/puregotk/v4/gio" | 13 | "github.com/jwijenbergh/puregotk/v4/gio" | ||
| 13 | "github.com/jwijenbergh/puregotk/v4/glib" | 14 | "github.com/jwijenbergh/puregotk/v4/glib" | ||
| 14 | "github.com/jwijenbergh/puregotk/v4/gobject" | 15 | "github.com/jwijenbergh/puregotk/v4/gobject" | ||
| @@ -101,6 +102,7 @@ type WebView struct { | |||||
| 101 | OnTitleChanged func(string) | 102 | OnTitleChanged func(string) | ||
| 102 | OnURIChanged func(string) | 103 | OnURIChanged func(string) | ||
| 103 | OnProgressChanged func(float64) | 104 | OnProgressChanged func(float64) | ||
| 105 | + | OnFaviconChanged func(*gdk.Texture) // Called when page favicon changes | |||
| 104 | OnClose func() | 106 | OnClose func() | ||
| 105 | OnCreate func(PopupRequest) *WebView // Return new WebView or nil to block popup | 107 | OnCreate func(PopupRequest) *WebView // Return new WebView or nil to block popup | ||
| 106 | OnReadyToShow func() // Called when popup is ready to display | 108 | OnReadyToShow func() // Called when popup is ready to display | ||
| @@ -293,6 +295,17 @@ func (wv *WebView) connectSignals() { | |||||
| 293 | } | 295 | } | ||
| 294 | sigID = gobject.SignalConnect(wv.inner.GoPointer(), "notify::title", glib.NewCallback(&titleCb)) | 296 | sigID = gobject.SignalConnect(wv.inner.GoPointer(), "notify::title", glib.NewCallback(&titleCb)) | ||
| 295 | wv.signalIDs = append(wv.signalIDs, sigID) | 297 | wv.signalIDs = append(wv.signalIDs, sigID) | ||
| 298 | + | ||||
| 299 | + | // notify::favicon signal for favicon changes | |||
| 300 | + | // This fires when the page favicon changes (after page load or dynamic updates) | |||
| 301 | + | faviconCb := func() { | |||
| 302 | + | favicon := wv.inner.GetFavicon() | |||
| 303 | + | if wv.OnFaviconChanged != nil { | |||
| 304 | + | wv.OnFaviconChanged(favicon) | |||
| 305 | + | } | |||
| 306 | + | } | |||
| 307 | + | sigID = gobject.SignalConnect(wv.inner.GoPointer(), "notify::favicon", glib.NewCallback(&faviconCb)) | |||
| 308 | + | wv.signalIDs = append(wv.signalIDs, sigID) | |||
| 296 | } | 309 | } | ||
| 297 | 310 | ||||
| 298 | // ID returns the unique identifier for this WebView. | 311 | // ID returns the unique identifier for this WebView. | ||
| @@ -412,6 +425,15 @@ func (wv *WebView) Title() string { | |||||
| 412 | return wv.title | 425 | return wv.title | ||
| 413 | } | 426 | } | ||
| 414 | 427 | ||||
| 428 | + | // Favicon returns the current page favicon as a GdkTexture. | |||
| 429 | + | // Returns nil if no favicon is available. | |||
| 430 | + | func (wv *WebView) Favicon() *gdk.Texture { | |||
| 431 | + | if wv.destroyed.Load() { | |||
| 432 | + | return nil | |||
| 433 | + | } | |||
| 434 | + | return wv.inner.GetFavicon() | |||
| 435 | + | } | |||
| 436 | + | ||||
| 415 | // IsLoading returns true if a page is currently loading. | 437 | // IsLoading returns true if a page is currently loading. | ||
| 416 | func (wv *WebView) IsLoading() bool { | 438 | func (wv *WebView) IsLoading() bool { | ||
| 417 | wv.mu.RLock() | 439 | wv.mu.RLock() | ||
| @@ -464,6 +486,7 @@ func (wv *WebView) SetCallbacks(callbacks *port.WebViewCallbacks) { | |||||
| 464 | wv.OnTitleChanged = nil | 486 | wv.OnTitleChanged = nil | ||
| 465 | wv.OnURIChanged = nil | 487 | wv.OnURIChanged = nil | ||
| 466 | wv.OnProgressChanged = nil | 488 | wv.OnProgressChanged = nil | ||
| 489 | + | wv.OnFaviconChanged = nil | |||
| 467 | wv.OnClose = nil | 490 | wv.OnClose = nil | ||
| 468 | wv.OnCreate = nil | 491 | wv.OnCreate = nil | ||
| 469 | return | 492 | return | ||
| @@ -478,6 +501,11 @@ func (wv *WebView) SetCallbacks(callbacks *port.WebViewCallbacks) { | |||||
| 478 | wv.OnTitleChanged = callbacks.OnTitleChanged | 501 | wv.OnTitleChanged = callbacks.OnTitleChanged | ||
| 479 | wv.OnURIChanged = callbacks.OnURIChanged | 502 | wv.OnURIChanged = callbacks.OnURIChanged | ||
| 480 | wv.OnProgressChanged = callbacks.OnProgressChanged | 503 | wv.OnProgressChanged = callbacks.OnProgressChanged | ||
| 504 | + | if callbacks.OnFaviconChanged != nil { | |||
| 505 | + | wv.OnFaviconChanged = func(texture *gdk.Texture) { | |||
| 506 | + | callbacks.OnFaviconChanged(texture) | |||
| 507 | + | } | |||
| 508 | + | } | |||
| 481 | wv.OnClose = callbacks.OnClose | 509 | wv.OnClose = callbacks.OnClose | ||
| 482 | if callbacks.OnCreate != nil { | 510 | if callbacks.OnCreate != nil { | ||
| 483 | wv.OnCreate = func(req PopupRequest) *WebView { | 511 | wv.OnCreate = func(req PopupRequest) *WebView { | ||