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}