1// Package port defines application-layer interfaces for external capabilities.
2// Ports abstract infrastructure concerns, allowing the application layer to
3// remain independent of specific implementations (WebKit, GTK, etc.).
4package port
5
6import (
7 "context"
8
9 "github.com/bnema/dumber/internal/application/dto"
10)
11
12// WebViewID uniquely identifies a WebView instance.
13type WebViewID uint64
14
15// LoadEvent represents page load state transitions.
16type LoadEvent int
17
18const (
19 // LoadStarted indicates navigation has begun.
20 LoadStarted LoadEvent = iota
21 // LoadRedirected indicates a redirect occurred.
22 LoadRedirected
23 // LoadCommitted indicates content is being received.
24 LoadCommitted
25 // LoadFinished indicates the page has fully loaded.
26 LoadFinished
27)
28
29// String returns a human-readable representation of the load event.
30func (e LoadEvent) String() string {
31 switch e {
32 case LoadStarted:
33 return "started"
34 case LoadRedirected:
35 return "redirected"
36 case LoadCommitted:
37 return "committed"
38 case LoadFinished:
39 return "finished"
40 default:
41 return "unknown"
42 }
43}
44
45// WebViewState represents a snapshot of the current WebView state.
46// This is an immutable struct that can be safely passed between components.
47type WebViewState struct {
48 URI string
49 Title string
50 IsLoading bool
51 Progress float64 // 0.0 to 1.0
52 CanGoBack bool
53 CanGoFwd bool
54 ZoomLevel float64
55}
56
57// WebProcessTerminationReason describes why the web process terminated.
58type WebProcessTerminationReason int
59
60const (
61 WebProcessTerminationUnknown WebProcessTerminationReason = iota
62 WebProcessTerminationCrashed
63 WebProcessTerminationExceededMemory
64 WebProcessTerminationByAPI
65)
66
67// PopupRequest contains metadata about a popup window request.
68type PopupRequest struct {
69 TargetURI string
70 FrameName string // e.g., "_blank", custom name, or empty
71 IsUserGesture bool
72 NoJavaScriptAccess bool // true for noopener/noreferrer-style popups with no opener access
73 ParentViewID WebViewID
74
75 SourceBrowserID int32
76 SourceFrameID string
77 SourceFrameURL string
78
79 TargetDisposition dto.WindowDisposition
80 WindowFeatures string
81}
82
83// Texture represents a graphics texture returned by the engine.
84// GoPointer returns a native toolkit pointer (e.g. *gdk.Texture in GTK engines).
85// Engine implementations are responsible for the concrete type.
86type Texture interface {
87 GoPointer() uintptr
88}
89
90// WebViewCallbacks defines callback handlers for WebView events.
91// Implementations should invoke these on the main thread/goroutine.
92type WebViewCallbacks struct {
93 // OnLoadChanged is called when load state changes.
94 OnLoadChanged func(event LoadEvent)
95 // OnTitleChanged is called when the page title changes.
96 OnTitleChanged func(title string)
97 // OnURIChanged is called when the URI changes.
98 OnURIChanged func(uri string)
99 // OnProgressChanged is called during page load with progress 0.0-1.0.
100 OnProgressChanged func(progress float64)
101 // OnFaviconChanged is called when the page favicon changes.
102 // The parameter is a *gdk.Texture (passed as Texture interface to avoid GTK import in port layer).
103 OnFaviconChanged func(favicon Texture)
104 // OnClose is called when the WebView requests to close.
105 OnClose func()
106 // OnCreate is called when a popup window is requested.
107 // Return a WebView to allow the popup, or nil to block it.
108 OnCreate func(request PopupRequest) WebView
109 // OnReadyToShow is called when a popup WebView is ready to display.
110 OnReadyToShow func()
111 // OnLinkHover is called when hovering over a link, image, or media element.
112 // The uri parameter contains the target URL, or empty string when leaving.
113 OnLinkHover func(uri string)
114 // OnWebProcessTerminated is called when the web process exits unexpectedly.
115 OnWebProcessTerminated func(reason WebProcessTerminationReason, reasonLabel string, uri string)
116
117 // OnPermissionRequest is called when a site requests permission (mic, camera, screen sharing).
118 // Return true to indicate the request was handled. Call allow()/deny() to respond.
119 // The permission types are passed as strings ("microphone", "camera", "display", etc.).
120 // The metadata map carries permission-type-specific context; for website_data_access both
121 // entity.PermissionMetadataKeyRequestingDomain and entity.PermissionMetadataKeyCurrentDomain must be populated.
122 OnPermissionRequest func(origin string, permTypes []string, metadata map[string]string, allow, deny func()) bool
123
124 // OnLinkMiddleClick is called when a link is middle-clicked.
125 // Return true if handled (blocks default navigation).
126 OnLinkMiddleClick func(uri string) bool
127
128 // OnEnterFullscreen is called when the WebView requests fullscreen mode.
129 // Return true to prevent fullscreen.
130 OnEnterFullscreen func() bool
131
132 // OnLeaveFullscreen is called when the WebView exits fullscreen mode.
133 // Return true to prevent leaving fullscreen.
134 OnLeaveFullscreen func() bool
135
136 // OnAudioStateChanged is called when audio playback starts or stops.
137 OnAudioStateChanged func(playing bool)
138}
139
140// FindOptions configures search behavior.
141type FindOptions struct {
142 CaseInsensitive bool
143 AtWordStarts bool
144 WrapAround bool
145}
146
147// FindController abstracts WebKit's FindController for clean architecture.
148type FindController interface {
149 Search(text string, options FindOptions, maxMatches uint)
150 CountMatches(text string, options FindOptions, maxMatches uint)
151 SearchNext()
152 SearchPrevious()
153 SearchFinish()
154 GetSearchText() string
155
156 // Signal connections
157 OnFoundText(callback func(matchCount uint)) uint
158 OnFailedToFindText(callback func()) uint
159 OnCountedMatches(callback func(matchCount uint)) uint
160 DisconnectSignal(id uint)
161}
162
163// WebView defines the port interface for browser view operations.
164// This interface abstracts the underlying browser engine (WebKit, etc.)
165// and exposes only the navigation and state capabilities needed by
166// the application layer.
167type WebView interface {
168 // ID returns the unique identifier for this WebView.
169 ID() WebViewID
170
171 // --- Navigation ---
172
173 // LoadURI navigates to the specified URI.
174 LoadURI(ctx context.Context, uri string) error
175
176 // LoadHTML loads HTML content with an optional base URI for relative links.
177 LoadHTML(ctx context.Context, content, baseURI string) error
178
179 // Reload reloads the current page.
180 Reload(ctx context.Context) error
181
182 // ReloadBypassCache reloads the current page, bypassing cache.
183 ReloadBypassCache(ctx context.Context) error
184
185 // Stop stops the current page load.
186 Stop(ctx context.Context) error
187
188 // GoBack navigates back in history.
189 // Uses WebKit native navigation if available, falls back to JavaScript history.back() for SPA compatibility.
190 GoBack(ctx context.Context) error
191
192 // GoForward navigates forward in history.
193 // Uses WebKit native navigation if available, falls back to JavaScript history.forward() for SPA compatibility.
194 GoForward(ctx context.Context) error
195
196 // --- State Queries ---
197
198 // State returns the current WebView state as a snapshot.
199 State() WebViewState
200
201 // URI returns the current URI.
202 URI() string
203
204 // Title returns the current page title.
205 Title() string
206
207 // IsLoading returns true if a page is currently loading.
208 IsLoading() bool
209
210 // EstimatedProgress returns the load progress (0.0 to 1.0).
211 EstimatedProgress() float64
212
213 // CanGoBack returns true if back navigation is available.
214 CanGoBack() bool
215
216 // CanGoForward returns true if forward navigation is available.
217 CanGoForward() bool
218
219 // --- Zoom ---
220
221 // SetZoomLevel sets the zoom level (1.0 = 100%).
222 SetZoomLevel(ctx context.Context, level float64) error
223
224 // GetZoomLevel returns the current zoom level.
225 GetZoomLevel() float64
226
227 // --- Find ---
228
229 // GetFindController returns the find controller for text search.
230 // Returns nil if find is not supported.
231 GetFindController() FindController
232
233 // --- Callbacks ---
234
235 // SetCallbacks registers callback handlers for WebView events.
236 // Pass nil to clear all callbacks.
237 SetCallbacks(callbacks *WebViewCallbacks)
238
239 // --- Appearance ---
240
241 // RunJavaScript executes a script in the main world. Fire-and-forget.
242 RunJavaScript(ctx context.Context, script string)
243
244 // SetBackgroundColor sets the background color shown before content paints (0.0-1.0 RGBA).
245 SetBackgroundColor(r, g, b, a float64)
246
247 // ResetBackgroundToDefault resets the background to white (browser default).
248 ResetBackgroundToDefault()
249
250 // --- Favicon ---
251
252 // Favicon returns the current page favicon, or nil if unavailable.
253 Favicon() Texture
254
255 // Generation returns a monotonic counter incremented on pool reuse.
256 // Used to detect stale callbacks after a WebView is recycled.
257 Generation() uint64
258
259 // --- Media/Fullscreen State ---
260
261 // IsFullscreen returns true if the WebView is currently in fullscreen mode.
262 IsFullscreen() bool
263
264 // IsPlayingAudio returns true if the WebView is currently playing audio.
265 IsPlayingAudio() bool
266
267 // --- Lifecycle ---
268
269 // IsDestroyed returns true if the WebView has been destroyed.
270 IsDestroyed() bool
271
272 // Destroy releases all resources associated with this WebView.
273 // After calling Destroy, the WebView should not be used.
274 Destroy()
275}
276
277// WebViewPool defines the port interface for WebView pooling.
278// Pools maintain pre-created WebViews for fast tab creation.
279type WebViewPool interface {
280 // Acquire obtains a WebView from the pool or creates a new one.
281 // The context can be used for cancellation.
282 Acquire(ctx context.Context) (WebView, error)
283
284 // Release returns a WebView to the pool for reuse.
285 // The WebView will be reset to a blank state.
286 // If the pool is full, the WebView will be destroyed.
287 Release(wv WebView)
288
289 // Prewarm creates WebViews synchronously to populate the pool.
290 // Pass count <= 0 to use the default prewarm count.
291 Prewarm(count int)
292
293 // PrewarmAsync schedules WebView creation on the toolkit idle loop,
294 // avoiding blocking startup while still warming up WebViews.
295 // Pass count <= 0 to use the default prewarm count.
296 PrewarmAsync(ctx context.Context, count int)
297
298 // Size returns the current number of available WebViews in the pool.
299 Size() int
300
301 // Close shuts down the pool and destroys all pooled WebViews.
302 Close()
303}
304
305// WebViewFactory creates new WebView instances.
306// This is used when direct WebView creation is needed without pooling.
307type WebViewFactory interface {
308 // Create creates a new WebView instance.
309 Create(ctx context.Context) (WebView, error)
310
311 // CreateRelated creates a WebView that shares session/cookies with parent.
312 // This is required for popup windows to maintain authentication state.
313 // Popup WebViews bypass the pool since they must be related to a specific parent.
314 CreateRelated(ctx context.Context, parentID WebViewID) (WebView, error)
315}
316
317// DevToolsOpener is an optional capability for WebViews that support developer tools.
318type DevToolsOpener interface {
319 OpenDevTools()
320}
321
322// Printer is an optional capability for WebViews that support printing.
323type Printer interface {
324 PrintPage()
325}
326
327// PopupLifecycleCapable is implemented by WebViews that support the full popup
328// pane lifecycle. SetOnClose composes the provided function with any existing
329// close handler so multiple callers can register close hooks without
330// overwriting one another. PrimePopupNavigation lets engines preserve the popup
331// target URI before the browser is fully created.
332type PopupLifecycleCapable interface {
333 SetOnReadyToShow(fn func())
334 SetOnClose(fn func())
335 Show()
336 PrimePopupNavigation(uri string)
337}
338
339// PopupOpenerCapable is implemented by popup WebViews that can emulate opener
340// semantics when a popup must be created directly instead of through a native
341// related-popup lifecycle, and that can report or observe synthetic opener
342// traffic needed by OAuth flows.
343type PopupOpenerCapable interface {
344 EnablePopupOpenerBridge(parent WebView, noJavaScriptAccess bool)
345 AddOpenerMessageCallback(fn func())
346 AddOpenerNavigationCallback(fn func(uri string))
347 HasActivePopupOpenerBridge() bool
348}
349
350// ViewportSyncCapable is implemented by WebViews that can explicitly resync
351// their internal viewport state after UI lifecycle events such as reparenting,
352// visibility toggles, or layout promotion that may not emit a size change.
353//
354// Implementations should make this cheap, safe to call often, and resilient to
355// duplicate requests.
356type ViewportSyncCapable interface {
357 SyncViewport(ctx context.Context, reason string)
358}
359
360// OAuthCallbackCapable is implemented by WebViews that support OAuth auto-close callbacks.
361// It is an optional capability; callers should type-assert port.WebView to this interface
362// before use and degrade gracefully when it is absent.
363type OAuthCallbackCapable interface {
364 // AddCloseCallback registers fn to be called when the WebView is programmatically closed.
365 // Multiple callbacks may be registered; each will be called in registration order.
366 AddCloseCallback(fn func())
367 // AddNavigationCallback registers fn to be called whenever the URI changes or a
368 // committed load is detected. The uri parameter contains the new URL.
369 // Multiple callbacks may be registered; each will be called in registration order.
370 AddNavigationCallback(fn func(uri string))
371 // Close triggers a programmatic close of the WebView (equivalent to window.close()).
372 Close()
373}
374
375// TextInputTargetProvider is an optional capability for WebViews that support
376// text input method integration.
377type TextInputTargetProvider interface {
378 TextInputTarget() TextInputTarget
379}