detached eac22c5

improve(navigation): cache zoom levels and reuse controller for omnibox navigations

commit
eac22c565723c0a1673ce8f77198ab383e55d280
author
bamen · · 2025-09-16 21:54 UTC
committer
bamen · · 2025-09-16 20:46 UTC
parent
8da98d2
signature
unsigned
2 files · +103 -16
M internal/app/control/navigation.go +60 -3
@@ -45,13 +45,43 @@ func (n *NavigationController) NavigateToURL(input string) error {
4545 log.Printf("Warning: failed to navigate to %s: %v", result.URL, navErr)
4646 }
4747
48+ var (
49+ zoomLevel float64
50+ haveZoom bool
51+ )
52+ if n.browserService != nil {
53+ if level, err := n.browserService.GetZoomLevel(ctx, result.URL); err == nil {
54+ zoomLevel = level
55+ haveZoom = true
56+ if n.webView != nil {
57+ if n.webView.UsesDomZoom() {
58+ n.webView.SeedDomZoom(zoomLevel)
59+ } else {
60+ n.webView.RunOnMainThread(func() {
61+ if err := n.webView.SetZoom(zoomLevel); err != nil {
62+ log.Printf("Warning: failed to prime native zoom for %s: %v", result.URL, err)
63+ }
64+ })
65+ }
66+ }
67+ } else {
68+ log.Printf("Warning: failed to lookup zoom for %s: %v", result.URL, err)
69+ }
70+ }
71+
4872 // Load URL in WebView
4973 if err := n.webView.LoadURL(result.URL); err != nil {
5074 return err
5175 }
5276
53- // Apply zoom for the new URL
54- n.zoomController.ApplyZoomForURL(result.URL)
77+ // Apply zoom for the new URL using the cached level when available.
78+ if n.zoomController != nil {
79+ if haveZoom {
80+ n.zoomController.ApplyZoomForURLWithLevel(result.URL, zoomLevel, false)
81+ } else {
82+ n.zoomController.ApplyZoomForURL(result.URL)
83+ }
84+ }
5585
5686 return nil
5787 }
@@ -69,10 +99,37 @@ func (n *NavigationController) HandleBrowseCommand() {
6999 }
70100 if n.webView != nil {
71101 log.Printf("Loading URL in WebView: %s", result.URL)
102+ var (
103+ zoomLevel float64
104+ haveZoom bool
105+ )
106+ if n.browserService != nil {
107+ if level, err := n.browserService.GetZoomLevel(ctx, result.URL); err == nil {
108+ zoomLevel = level
109+ haveZoom = true
110+ if n.webView.UsesDomZoom() {
111+ n.webView.SeedDomZoom(zoomLevel)
112+ } else {
113+ n.webView.RunOnMainThread(func() {
114+ if err := n.webView.SetZoom(zoomLevel); err != nil {
115+ log.Printf("Warning: failed to prime native zoom for %s: %v", result.URL, err)
116+ }
117+ })
118+ }
119+ } else {
120+ log.Printf("Warning: failed to lookup zoom for %s: %v", result.URL, err)
121+ }
122+ }
72123 if err := n.webView.LoadURL(result.URL); err != nil {
73124 log.Printf("Warning: failed to load URL: %v", err)
74125 }
75- n.zoomController.ApplyZoomForURL(result.URL)
126+ if n.zoomController != nil {
127+ if haveZoom {
128+ n.zoomController.ApplyZoomForURLWithLevel(result.URL, zoomLevel, false)
129+ } else {
130+ n.zoomController.ApplyZoomForURL(result.URL)
131+ }
132+ }
76133 }
77134 }
78135 }
@@ -45,13 +45,43 @@ func (n *NavigationController) NavigateToURL(input string) error {
45 log.Printf("Warning: failed to navigate to %s: %v", result.URL, navErr)45 log.Printf("Warning: failed to navigate to %s: %v", result.URL, navErr)
46 }46 }
47 47
48+ var (
49+ zoomLevel float64
50+ haveZoom bool
51+ )
52+ if n.browserService != nil {
53+ if level, err := n.browserService.GetZoomLevel(ctx, result.URL); err == nil {
54+ zoomLevel = level
55+ haveZoom = true
56+ if n.webView != nil {
57+ if n.webView.UsesDomZoom() {
58+ n.webView.SeedDomZoom(zoomLevel)
59+ } else {
60+ n.webView.RunOnMainThread(func() {
61+ if err := n.webView.SetZoom(zoomLevel); err != nil {
62+ log.Printf("Warning: failed to prime native zoom for %s: %v", result.URL, err)
63+ }
64+ })
65+ }
66+ }
67+ } else {
68+ log.Printf("Warning: failed to lookup zoom for %s: %v", result.URL, err)
69+ }
70+ }
71+
48 // Load URL in WebView72 // Load URL in WebView
49 if err := n.webView.LoadURL(result.URL); err != nil {73 if err := n.webView.LoadURL(result.URL); err != nil {
50 return err74 return err
51 }75 }
52 76
53- // Apply zoom for the new URL77+ // Apply zoom for the new URL using the cached level when available.
54- n.zoomController.ApplyZoomForURL(result.URL)78+ if n.zoomController != nil {
79+ if haveZoom {
80+ n.zoomController.ApplyZoomForURLWithLevel(result.URL, zoomLevel, false)
81+ } else {
82+ n.zoomController.ApplyZoomForURL(result.URL)
83+ }
84+ }
55 85
56 return nil86 return nil
57 }87 }
@@ -69,10 +99,37 @@ func (n *NavigationController) HandleBrowseCommand() {
69 }99 }
70 if n.webView != nil {100 if n.webView != nil {
71 log.Printf("Loading URL in WebView: %s", result.URL)101 log.Printf("Loading URL in WebView: %s", result.URL)
102+ var (
103+ zoomLevel float64
104+ haveZoom bool
105+ )
106+ if n.browserService != nil {
107+ if level, err := n.browserService.GetZoomLevel(ctx, result.URL); err == nil {
108+ zoomLevel = level
109+ haveZoom = true
110+ if n.webView.UsesDomZoom() {
111+ n.webView.SeedDomZoom(zoomLevel)
112+ } else {
113+ n.webView.RunOnMainThread(func() {
114+ if err := n.webView.SetZoom(zoomLevel); err != nil {
115+ log.Printf("Warning: failed to prime native zoom for %s: %v", result.URL, err)
116+ }
117+ })
118+ }
119+ } else {
120+ log.Printf("Warning: failed to lookup zoom for %s: %v", result.URL, err)
121+ }
122+ }
72 if err := n.webView.LoadURL(result.URL); err != nil {123 if err := n.webView.LoadURL(result.URL); err != nil {
73 log.Printf("Warning: failed to load URL: %v", err)124 log.Printf("Warning: failed to load URL: %v", err)
74 }125 }
75- n.zoomController.ApplyZoomForURL(result.URL)126+ if n.zoomController != nil {
127+ if haveZoom {
128+ n.zoomController.ApplyZoomForURLWithLevel(result.URL, zoomLevel, false)
129+ } else {
130+ n.zoomController.ApplyZoomForURL(result.URL)
131+ }
132+ }
76 }133 }
77 }134 }
78 }135 }
M internal/app/messaging/handler.go +43 -13
@@ -10,16 +10,18 @@ import (
1010 "strings"
1111
1212 "github.com/bnema/dumber/internal/app/constants"
13+ "github.com/bnema/dumber/internal/app/control"
1314 "github.com/bnema/dumber/pkg/webkit"
1415 "github.com/bnema/dumber/services"
1516 )
1617
1718 // Handler processes script messages from the WebView
1819 type Handler struct {
19- parserService *services.ParserService
20- browserService *services.BrowserService
21- webView *webkit.WebView
22- lastTheme string
20+ parserService *services.ParserService
21+ browserService *services.BrowserService
22+ webView *webkit.WebView
23+ navigationController *control.NavigationController
24+ lastTheme string
2325 }
2426
2527 // Message represents a script message from the WebView
@@ -47,6 +49,11 @@ func NewHandler(parserService *services.ParserService, browserService *services.
4749 }
4850 }
4951
52+// SetNavigationController injects the navigation controller for unified navigation flow.
53+func (h *Handler) SetNavigationController(controller *control.NavigationController) {
54+ h.navigationController = controller
55+}
56+
5057 // Handle processes incoming script messages
5158 func (h *Handler) Handle(payload string) {
5259 log.Printf("[DEBUG] Received message: %s", payload)
@@ -85,17 +92,40 @@ func (h *Handler) SetWebView(webView *webkit.WebView) {
8592
8693 // handleNavigation processes navigation requests from the frontend
8794 func (h *Handler) handleNavigation(msg Message) {
95+ if h.navigationController != nil {
96+ if err := h.navigationController.NavigateToURL(msg.URL); err != nil {
97+ log.Printf("[messaging] Navigation controller failed for input %q: %v", msg.URL, err)
98+ h.legacyNavigate(msg)
99+ }
100+ return
101+ }
102+
103+ h.legacyNavigate(msg)
104+}
105+
106+func (h *Handler) legacyNavigate(msg Message) {
88107 ctx := context.Background()
89108 res, err := h.parserService.ParseInput(ctx, msg.URL)
90- if err == nil {
91- if _, navErr := h.browserService.Navigate(ctx, res.URL); navErr != nil {
92- log.Printf("Warning: failed to navigate to %s: %v", res.URL, navErr)
93- }
94- if h.webView != nil {
95- _ = h.webView.LoadURL(res.URL)
96- if z, zerr := h.browserService.GetZoomLevel(ctx, res.URL); zerr == nil {
97- _ = h.webView.SetZoom(z)
98- }
109+ if err != nil {
110+ log.Printf("[messaging] Legacy navigation parse failed for %q: %v", msg.URL, err)
111+ return
112+ }
113+
114+ if _, navErr := h.browserService.Navigate(ctx, res.URL); navErr != nil {
115+ log.Printf("Warning: failed to navigate to %s: %v", res.URL, navErr)
116+ }
117+
118+ if h.webView == nil {
119+ return
120+ }
121+
122+ if err := h.webView.LoadURL(res.URL); err != nil {
123+ log.Printf("[messaging] Legacy LoadURL failed for %s: %v", res.URL, err)
124+ }
125+
126+ if z, zerr := h.browserService.GetZoomLevel(ctx, res.URL); zerr == nil {
127+ if err := h.webView.SetZoom(z); err != nil {
128+ log.Printf("[messaging] Legacy SetZoom failed for %s: %v", res.URL, err)
99129 }
100130 }
101131 }
@@ -10,16 +10,18 @@ import (
10 "strings"10 "strings"
11 11
12 "github.com/bnema/dumber/internal/app/constants"12 "github.com/bnema/dumber/internal/app/constants"
13+ "github.com/bnema/dumber/internal/app/control"
13 "github.com/bnema/dumber/pkg/webkit"14 "github.com/bnema/dumber/pkg/webkit"
14 "github.com/bnema/dumber/services"15 "github.com/bnema/dumber/services"
15 )16 )
16 17
17 // Handler processes script messages from the WebView18 // Handler processes script messages from the WebView
18 type Handler struct {19 type Handler struct {
19- parserService *services.ParserService20+ parserService *services.ParserService
20- browserService *services.BrowserService21+ browserService *services.BrowserService
21- webView *webkit.WebView22+ webView *webkit.WebView
22- lastTheme string23+ navigationController *control.NavigationController
24+ lastTheme string
23 }25 }
24 26
25 // Message represents a script message from the WebView27 // Message represents a script message from the WebView
@@ -47,6 +49,11 @@ func NewHandler(parserService *services.ParserService, browserService *services.
47 }49 }
48 }50 }
49 51
52+// SetNavigationController injects the navigation controller for unified navigation flow.
53+func (h *Handler) SetNavigationController(controller *control.NavigationController) {
54+ h.navigationController = controller
55+}
56+
50 // Handle processes incoming script messages57 // Handle processes incoming script messages
51 func (h *Handler) Handle(payload string) {58 func (h *Handler) Handle(payload string) {
52 log.Printf("[DEBUG] Received message: %s", payload)59 log.Printf("[DEBUG] Received message: %s", payload)
@@ -85,17 +92,40 @@ func (h *Handler) SetWebView(webView *webkit.WebView) {
85 92
86 // handleNavigation processes navigation requests from the frontend93 // handleNavigation processes navigation requests from the frontend
87 func (h *Handler) handleNavigation(msg Message) {94 func (h *Handler) handleNavigation(msg Message) {
95+ if h.navigationController != nil {
96+ if err := h.navigationController.NavigateToURL(msg.URL); err != nil {
97+ log.Printf("[messaging] Navigation controller failed for input %q: %v", msg.URL, err)
98+ h.legacyNavigate(msg)
99+ }
100+ return
101+ }
102+
103+ h.legacyNavigate(msg)
104+}
105+
106+func (h *Handler) legacyNavigate(msg Message) {
88 ctx := context.Background()107 ctx := context.Background()
89 res, err := h.parserService.ParseInput(ctx, msg.URL)108 res, err := h.parserService.ParseInput(ctx, msg.URL)
90- if err == nil {109+ if err != nil {
91- if _, navErr := h.browserService.Navigate(ctx, res.URL); navErr != nil {110+ log.Printf("[messaging] Legacy navigation parse failed for %q: %v", msg.URL, err)
92- log.Printf("Warning: failed to navigate to %s: %v", res.URL, navErr)111+ return
93- }112+ }
94- if h.webView != nil {113+
95- _ = h.webView.LoadURL(res.URL)114+ if _, navErr := h.browserService.Navigate(ctx, res.URL); navErr != nil {
96- if z, zerr := h.browserService.GetZoomLevel(ctx, res.URL); zerr == nil {115+ log.Printf("Warning: failed to navigate to %s: %v", res.URL, navErr)
97- _ = h.webView.SetZoom(z)116+ }
98- }117+
118+ if h.webView == nil {
119+ return
120+ }
121+
122+ if err := h.webView.LoadURL(res.URL); err != nil {
123+ log.Printf("[messaging] Legacy LoadURL failed for %s: %v", res.URL, err)
124+ }
125+
126+ if z, zerr := h.browserService.GetZoomLevel(ctx, res.URL); zerr == nil {
127+ if err := h.webView.SetZoom(z); err != nil {
128+ log.Printf("[messaging] Legacy SetZoom failed for %s: %v", res.URL, err)
99 }129 }
100 }130 }
101 }131 }

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