fix(zoom): prevent visual glitch during page navigation
Zoom level for the next page was being applied before navigation completed, causing a visible zoom change on the current page. Solution: Apply zoom on load-committed event instead of URI change. - Added RegisterLoadCommittedHandler to WebView - handleURIChange now only tracks URL without applying zoom - handleLoadCommitted applies zoom when page load actually starts This ensures zoom is applied at the right time, eliminating the visual glitch.
- commit
- e23b0ff634f1f426eb9402f53f6a77aaf8f9a138
- author
- bnema · · 2025-11-16 19:00 UTC
- committer
- bnema · · 2025-11-16 22:17 UTC
- parent
- a2289aa
- signature
- unsigned
2 files · +25 -1
M internal/app/control/zoom.go +8 -1
| @@ -26,6 +26,7 @@ type WebViewInterface interface { | |||
| 26 | 26 | InjectScript(script string) error | |
| 27 | 27 | RegisterZoomChangedHandler(handler func(float64)) | |
| 28 | 28 | RegisterURIChangedHandler(handler func(string)) | |
| 29 | + | RegisterLoadCommittedHandler(handler func(string)) | |
| 29 | 30 | } | |
| 30 | 31 | ||
| 31 | 32 | // NewZoomController creates a new zoom controller | |
| @@ -41,11 +42,17 @@ func NewZoomController(browserService *services.BrowserService, webView *webkit. | |||
| 41 | 42 | func (z *ZoomController) RegisterHandlers() { | |
| 42 | 43 | z.webView.RegisterZoomChangedHandler(z.handleZoomChange) | |
| 43 | 44 | z.webView.RegisterURIChangedHandler(z.handleURIChange) | |
| 45 | + | z.webView.RegisterLoadCommittedHandler(z.handleLoadCommitted) | |
| 44 | 46 | } | |
| 45 | 47 | ||
| 46 | - | // handleURIChange responds to URL changes and applies saved zoom levels | |
| 48 | + | // handleURIChange tracks URL changes but doesn't apply zoom (wait for load-committed) | |
| 47 | 49 | func (z *ZoomController) handleURIChange(url string) { | |
| 48 | 50 | z.currentURL = url | |
| 51 | + | } | |
| 52 | + | ||
| 53 | + | // handleLoadCommitted responds to load committed events and applies saved zoom levels | |
| 54 | + | // This fires when the page actually starts loading content, preventing visual glitches | |
| 55 | + | func (z *ZoomController) handleLoadCommitted(url string) { | |
| 49 | 56 | if url == "" { | |
| 50 | 57 | return | |
| 51 | 58 | } | |
| @@ -26,6 +26,7 @@ type WebViewInterface interface { | |||||
| 26 | InjectScript(script string) error | 26 | InjectScript(script string) error | ||
| 27 | RegisterZoomChangedHandler(handler func(float64)) | 27 | RegisterZoomChangedHandler(handler func(float64)) | ||
| 28 | RegisterURIChangedHandler(handler func(string)) | 28 | RegisterURIChangedHandler(handler func(string)) | ||
| 29 | + | RegisterLoadCommittedHandler(handler func(string)) | |||
| 29 | } | 30 | } | ||
| 30 | 31 | ||||
| 31 | // NewZoomController creates a new zoom controller | 32 | // NewZoomController creates a new zoom controller | ||
| @@ -41,11 +42,17 @@ func NewZoomController(browserService *services.BrowserService, webView *webkit. | |||||
| 41 | func (z *ZoomController) RegisterHandlers() { | 42 | func (z *ZoomController) RegisterHandlers() { | ||
| 42 | z.webView.RegisterZoomChangedHandler(z.handleZoomChange) | 43 | z.webView.RegisterZoomChangedHandler(z.handleZoomChange) | ||
| 43 | z.webView.RegisterURIChangedHandler(z.handleURIChange) | 44 | z.webView.RegisterURIChangedHandler(z.handleURIChange) | ||
| 45 | + | z.webView.RegisterLoadCommittedHandler(z.handleLoadCommitted) | |||
| 44 | } | 46 | } | ||
| 45 | 47 | ||||
| 46 | - | // handleURIChange responds to URL changes and applies saved zoom levels | 48 | + | // handleURIChange tracks URL changes but doesn't apply zoom (wait for load-committed) |
| 47 | func (z *ZoomController) handleURIChange(url string) { | 49 | func (z *ZoomController) handleURIChange(url string) { | ||
| 48 | z.currentURL = url | 50 | z.currentURL = url | ||
| 51 | + | } | |||
| 52 | + | ||||
| 53 | + | // handleLoadCommitted responds to load committed events and applies saved zoom levels | |||
| 54 | + | // This fires when the page actually starts loading content, preventing visual glitches | |||
| 55 | + | func (z *ZoomController) handleLoadCommitted(url string) { | |||
| 49 | if url == "" { | 56 | if url == "" { | ||
| 50 | return | 57 | return | ||
| 51 | } | 58 | } | ||
M pkg/webkit/webview.go +17 -0
| @@ -38,6 +38,7 @@ type WebView struct { | |||
| 38 | 38 | onFaviconChanged func([]byte) | |
| 39 | 39 | onFaviconURIChanged func(pageURI, faviconURI string) | |
| 40 | 40 | onZoomChanged func(float64) | |
| 41 | + | onLoadCommitted func(string) // Called when page load is committed (safe to apply zoom) | |
| 41 | 42 | onPopupCreate func(*webkit.NavigationAction) *WebView // New WebKit create signal handler | |
| 42 | 43 | onReadyToShow func() // WebKit ready-to-show signal handler | |
| 43 | 44 | onClose func() | |
| @@ -234,6 +235,14 @@ func (w *WebView) setupEventHandlers() { | |||
| 234 | 235 | } | |
| 235 | 236 | }) | |
| 236 | 237 | ||
| 238 | + | // Load committed - connect to load-changed signal for WEBKIT_LOAD_COMMITTED | |
| 239 | + | w.view.ConnectLoadChanged(func(loadEvent webkit.LoadEvent) { | |
| 240 | + | if loadEvent == webkit.LoadCommitted && w.onLoadCommitted != nil { | |
| 241 | + | uri := w.view.URI() | |
| 242 | + | w.onLoadCommitted(uri) | |
| 243 | + | } | |
| 244 | + | }) | |
| 245 | + | ||
| 237 | 246 | // Favicon changed - connect to FaviconDatabase | |
| 238 | 247 | w.setupFaviconHandlers() | |
| 239 | 248 | ||
| @@ -605,6 +614,14 @@ func (w *WebView) RegisterZoomChangedHandler(handler func(float64)) { | |||
| 605 | 614 | w.onZoomChanged = handler | |
| 606 | 615 | } | |
| 607 | 616 | ||
| 617 | + | // RegisterLoadCommittedHandler registers a handler for load committed events | |
| 618 | + | // This fires when the page actually starts loading new content (after URI change) | |
| 619 | + | func (w *WebView) RegisterLoadCommittedHandler(handler func(string)) { | |
| 620 | + | w.mu.Lock() | |
| 621 | + | defer w.mu.Unlock() | |
| 622 | + | w.onLoadCommitted = handler | |
| 623 | + | } | |
| 624 | + | ||
| 608 | 625 | // RegisterCloseHandler registers a handler for close requests | |
| 609 | 626 | func (w *WebView) RegisterCloseHandler(handler func()) { | |
| 610 | 627 | w.mu.Lock() | |
| @@ -38,6 +38,7 @@ type WebView struct { | |||||
| 38 | onFaviconChanged func([]byte) | 38 | onFaviconChanged func([]byte) | ||
| 39 | onFaviconURIChanged func(pageURI, faviconURI string) | 39 | onFaviconURIChanged func(pageURI, faviconURI string) | ||
| 40 | onZoomChanged func(float64) | 40 | onZoomChanged func(float64) | ||
| 41 | + | onLoadCommitted func(string) // Called when page load is committed (safe to apply zoom) | |||
| 41 | onPopupCreate func(*webkit.NavigationAction) *WebView // New WebKit create signal handler | 42 | onPopupCreate func(*webkit.NavigationAction) *WebView // New WebKit create signal handler | ||
| 42 | onReadyToShow func() // WebKit ready-to-show signal handler | 43 | onReadyToShow func() // WebKit ready-to-show signal handler | ||
| 43 | onClose func() | 44 | onClose func() | ||
| @@ -234,6 +235,14 @@ func (w *WebView) setupEventHandlers() { | |||||
| 234 | } | 235 | } | ||
| 235 | }) | 236 | }) | ||
| 236 | 237 | ||||
| 238 | + | // Load committed - connect to load-changed signal for WEBKIT_LOAD_COMMITTED | |||
| 239 | + | w.view.ConnectLoadChanged(func(loadEvent webkit.LoadEvent) { | |||
| 240 | + | if loadEvent == webkit.LoadCommitted && w.onLoadCommitted != nil { | |||
| 241 | + | uri := w.view.URI() | |||
| 242 | + | w.onLoadCommitted(uri) | |||
| 243 | + | } | |||
| 244 | + | }) | |||
| 245 | + | ||||
| 237 | // Favicon changed - connect to FaviconDatabase | 246 | // Favicon changed - connect to FaviconDatabase | ||
| 238 | w.setupFaviconHandlers() | 247 | w.setupFaviconHandlers() | ||
| 239 | 248 | ||||
| @@ -605,6 +614,14 @@ func (w *WebView) RegisterZoomChangedHandler(handler func(float64)) { | |||||
| 605 | w.onZoomChanged = handler | 614 | w.onZoomChanged = handler | ||
| 606 | } | 615 | } | ||
| 607 | 616 | ||||
| 617 | + | // RegisterLoadCommittedHandler registers a handler for load committed events | |||
| 618 | + | // This fires when the page actually starts loading new content (after URI change) | |||
| 619 | + | func (w *WebView) RegisterLoadCommittedHandler(handler func(string)) { | |||
| 620 | + | w.mu.Lock() | |||
| 621 | + | defer w.mu.Unlock() | |||
| 622 | + | w.onLoadCommitted = handler | |||
| 623 | + | } | |||
| 624 | + | ||||
| 608 | // RegisterCloseHandler registers a handler for close requests | 625 | // RegisterCloseHandler registers a handler for close requests | ||
| 609 | func (w *WebView) RegisterCloseHandler(handler func()) { | 626 | func (w *WebView) RegisterCloseHandler(handler func()) { | ||
| 610 | w.mu.Lock() | 627 | w.mu.Lock() | ||