v0.7.0 31c7fae

webview.go RawHistoryFolder

1package browser
2
3import (
4	"context"
5	"log"
6	"os"
7	"path/filepath"
8	"strings"
9	"time"
10
11	"github.com/bnema/dumber/internal/app/constants"
12	"github.com/bnema/dumber/internal/app/control"
13	"github.com/bnema/dumber/internal/app/environment"
14	"github.com/bnema/dumber/internal/config"
15	"github.com/bnema/dumber/internal/filtering"
16	"github.com/bnema/dumber/pkg/webkit"
17)
18
19// buildMemoryConfig converts config.WebkitMemoryConfig to webkit.MemoryConfig
20func buildMemoryConfig(cfg config.WebkitMemoryConfig) webkit.MemoryConfig {
21	mc := webkit.MemoryConfig{
22		MemoryLimitMB:           cfg.MemoryLimitMB,
23		ConservativeThreshold:   cfg.ConservativeThreshold,
24		StrictThreshold:         cfg.StrictThreshold,
25		KillThreshold:           cfg.KillThreshold,
26		PollIntervalSeconds:     cfg.PollIntervalSeconds,
27		EnableGCInterval:        cfg.EnableGCInterval,
28		ProcessRecycleThreshold: cfg.ProcessRecycleThreshold,
29		EnablePageCache:         cfg.EnablePageCache,
30		EnableMemoryMonitoring:  cfg.EnableMemoryMonitoring,
31	}
32
33	// Convert string cache model to webkit constant
34	switch strings.ToLower(cfg.CacheModel) {
35	case "document_viewer", "documentviewer", "doc":
36		mc.CacheModel = webkit.CacheModelDocumentViewer
37	case "primary_web_browser", "primary", "primarywebbrowser":
38		mc.CacheModel = webkit.CacheModelPrimaryWebBrowser
39	default:
40		mc.CacheModel = webkit.CacheModelWebBrowser
41	}
42
43	return mc
44}
45
46// createWebView creates and configures the WebView
47func (app *BrowserApp) createWebView() error {
48	log.Printf("Creating WebView (native backend expected: %v)", webkit.IsNativeAvailable())
49	dataDir, _ := config.GetDataDir()
50	stateDir, _ := config.GetStateDir()
51	webkitData := filepath.Join(dataDir, "webkit")
52	webkitCache := filepath.Join(stateDir, "webkit-cache")
53	_ = os.MkdirAll(webkitData, constants.DirPerm)
54	_ = os.MkdirAll(webkitCache, constants.DirPerm)
55
56	view, err := webkit.NewWebView(&webkit.Config{
57		InitialURL:            "dumb://homepage",
58		ZoomDefault:           1.0,
59		EnableDeveloperExtras: true,
60		DataDir:               webkitData,
61		CacheDir:              webkitCache,
62		DefaultSansFont:       app.config.Appearance.SansFont,
63		DefaultSerifFont:      app.config.Appearance.SerifFont,
64		DefaultMonospaceFont:  app.config.Appearance.MonospaceFont,
65		DefaultFontSize:       app.config.Appearance.DefaultFontSize,
66		Rendering:             webkit.RenderingConfig{Mode: string(app.config.RenderingMode)},
67		VideoAcceleration: webkit.VideoAccelerationConfig{
68			EnableVAAPI:      app.config.VideoAcceleration.EnableVAAPI,
69			AutoDetectGPU:    app.config.VideoAcceleration.AutoDetectGPU,
70			VAAPIDriverName:  app.config.VideoAcceleration.VAAPIDriverName,
71			EnableAllDrivers: app.config.VideoAcceleration.EnableAllDrivers,
72			LegacyVAAPI:      app.config.VideoAcceleration.LegacyVAAPI,
73		},
74		Memory: buildMemoryConfig(app.config.WebkitMemory),
75		CodecPreferences: webkit.CodecPreferencesConfig{
76			PreferredCodecs:           strings.Split(app.config.CodecPreferences.PreferredCodecs, ","),
77			BlockedCodecs:             environment.BuildBlockedCodecsList(app.config.CodecPreferences),
78			ForceAV1:                  app.config.CodecPreferences.ForceAV1,
79			CustomUserAgent:           app.config.CodecPreferences.CustomUserAgent,
80			DisableTwitchCodecControl: app.config.CodecPreferences.DisableTwitchCodecControl,
81		},
82	})
83
84	if err != nil {
85		return err
86	}
87
88	app.webView = view
89	app.setupWebViewIntegration()
90	app.setupWebViewHandlers()
91	app.setupControllers()
92	app.setupKeyboardShortcuts()
93
94	// Apply initial zoom and show window
95	app.zoomController.ApplyInitialZoom()
96
97	log.Printf("Showing WebView window…")
98	if err := view.Show(); err != nil {
99		log.Printf("Warning: failed to show WebView: %v", err)
100	} else if !webkit.IsNativeAvailable() {
101		log.Printf("Notice: running without webkit_cgo tag — no native window will be displayed.")
102	}
103
104	return nil
105}
106
107// setupWebViewIntegration connects the WebView to browser services
108func (app *BrowserApp) setupWebViewIntegration() {
109	app.browserService.AttachWebView(app.webView)
110	// Use native window as title updater
111	if win := app.webView.Window(); win != nil {
112		app.browserService.SetWindowTitleUpdater(win)
113	}
114}
115
116// setupWebViewHandlers configures WebView event handlers
117func (app *BrowserApp) setupWebViewHandlers() {
118	// Persist page titles to DB when they change
119	app.webView.RegisterTitleChangedHandler(func(title string) {
120		ctx := context.Background()
121		url := app.webView.GetCurrentURL()
122		if url != "" && title != "" {
123			if err := app.browserService.UpdatePageTitle(ctx, url, title); err != nil {
124				log.Printf("Warning: failed to update page title: %v", err)
125			}
126		}
127	})
128
129	// Set WebView reference for message handler and register script messages
130	app.messageHandler.SetWebView(app.webView)
131	app.webView.RegisterScriptMessageHandler(app.messageHandler.Handle)
132}
133
134// setupControllers initializes and configures controller objects
135func (app *BrowserApp) setupControllers() {
136	// Initialize controllers
137	app.zoomController = control.NewZoomController(app.browserService, app.webView)
138	app.navigationController = control.NewNavigationController(
139		app.parserService,
140		app.browserService,
141		app.webView,
142		app.zoomController,
143	)
144	app.clipboardController = control.NewClipboardController(app.webView)
145
146	// Register controller handlers
147	app.zoomController.RegisterHandlers()
148}
149
150// setupKeyboardShortcuts registers keyboard shortcuts
151func (app *BrowserApp) setupKeyboardShortcuts() {
152	app.shortcutHandler = NewShortcutHandler(app.webView, app.clipboardController)
153	app.shortcutHandler.RegisterShortcuts()
154}
155
156// setupContentBlocking initializes the content blocking system with proper timing
157func (app *BrowserApp) setupContentBlocking() error {
158	log.Printf("Initializing content blocking system...")
159
160	// Enable WebKit debug logging if requested
161	webkit.SetupWebKitDebugLogging(app.config)
162
163	defer func() {
164		if r := recover(); r != nil {
165			log.Printf("Content blocking setup panic recovered: %v", r)
166		}
167	}()
168
169	// Setup filter system
170	filterManager, err := filtering.SetupFilterSystem()
171	if err != nil {
172		log.Printf("Warning: Failed to setup filter system: %v", err)
173		return nil // Don't fail browser startup, continue without filters
174	}
175
176	// Store reference to filter manager
177	app.filterManager = filterManager
178
179	// Set up callback to re-apply network filters when they become ready
180	// This callback will be called from the async filter loading process
181	filterManager.SetFiltersReadyCallback(func() {
182		// Add small delay to avoid race conditions with WebView initialization
183		go func() {
184			time.Sleep(300 * time.Millisecond)
185			if app.webView != nil {
186				if err := app.webView.UpdateContentFilters(filterManager); err != nil {
187					log.Printf("Failed to apply filters after loading: %v", err)
188				} else {
189					log.Printf("Successfully applied filters after async loading")
190				}
191			}
192		}()
193	})
194
195	// Start async filter loading early (before WebView content blocking initialization)
196	// This allows filters to be ready when the WebView needs them
197	go func() {
198		if err := filtering.InitializeFiltersAsync(filterManager); err != nil {
199			log.Printf("Warning: failed to initialize filters asynchronously: %v", err)
200		}
201	}()
202
203	// Initialize content blocking in WebView with delay
204	// Wait for WebView to be fully loaded before setting up content blocking
205	go func() {
206		// Wait extra time for the first navigation to avoid preconnect interference
207		log.Printf("Waiting for WebView to complete initial load before enabling content blocking...")
208		time.Sleep(3000 * time.Millisecond) // 3 seconds for first load stability
209
210		if err := app.webView.InitializeContentBlocking(filterManager); err != nil {
211			log.Printf("Warning: Failed to initialize content blocking: %v", err)
212			// Continue without content blocking rather than failing
213		}
214
215		// Register navigation handler for domain-specific filtering
216		// Only register after content blocking is initialized
217		app.webView.RegisterURIChangedHandler(func(uri string) {
218			// Add small delay to avoid conflicts with page load
219			go func() {
220				time.Sleep(200 * time.Millisecond) // Slightly longer delay for stability
221				if app.filterManager != nil {
222					app.webView.OnNavigate(uri, app.filterManager)
223				}
224			}()
225		})
226	}()
227
228	log.Printf("Content blocking system initialization started")
229	return nil
230}

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