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 "fmt"
9
10 "github.com/bnema/dumber/internal/application/dto"
11 "github.com/bnema/dumber/internal/domain/entity"
12)
13
14// WebViewID uniquely identifies a WebView instance.
15type WebViewID uint64
16
17// LoadEvent represents page load state transitions.
18type LoadEvent int
19
20const (
21 // LoadStarted indicates navigation has begun.
22 LoadStarted LoadEvent = iota
23 // LoadRedirected indicates a redirect occurred.
24 LoadRedirected
25 // LoadCommitted indicates content is being received.
26 LoadCommitted
27 // LoadFinished indicates the page has fully loaded.
28 LoadFinished
29)
30
31// String returns a human-readable representation of the load event.
32func (e LoadEvent) String() string {
33 switch e {
34 case LoadStarted:
35 return "started"
36 case LoadRedirected:
37 return "redirected"
38 case LoadCommitted:
39 return "committed"
40 case LoadFinished:
41 return "finished"
42 default:
43 return "unknown"
44 }
45}
46
47// WebViewState represents a snapshot of the current WebView state.
48// This is an immutable struct that can be safely passed between components.
49type WebViewState struct {
50 URI string
51 Title string
52 IsLoading bool
53 Progress float64 // 0.0 to 1.0
54 CanGoBack bool
55 CanGoFwd bool
56 ZoomLevel float64
57}
58
59// WebProcessTerminationReason describes why the web process terminated.
60type WebProcessTerminationReason int
61
62const (
63 WebProcessTerminationUnknown WebProcessTerminationReason = iota
64 WebProcessTerminationCrashed
65 WebProcessTerminationExceededMemory
66 WebProcessTerminationByAPI
67)
68
69// PopupRequest contains metadata about a popup window request.
70type PopupRequest struct {
71 TargetURI string
72 FrameName string // e.g., "_blank", custom name, or empty
73 IsUserGesture bool
74 NoJavaScriptAccess bool // true for noopener/noreferrer-style popups with no opener access
75 ParentViewID WebViewID
76
77 SourceBrowserID int32
78 SourceFrameID string
79 SourceFrameURL string
80
81 Engine dto.BrowserEngineKind
82 TargetDisposition dto.WindowDisposition
83 PopupFeatures dto.PopupFeatures
84}
85
86// Texture represents a graphics texture returned by the engine.
87// GoPointer returns a native toolkit pointer (e.g. *gdk.Texture in GTK engines).
88// Engine implementations are responsible for the concrete type.
89type Texture interface {
90 GoPointer() uintptr
91}
92
93// WebViewCallbacks defines callback handlers for WebView events.
94// Implementations should invoke these on the main thread/goroutine.
95type WebViewCallbacks struct {
96 // OnLoadChanged is called when load state changes.
97 OnLoadChanged func(event LoadEvent)
98 // OnTitleChanged is called when the page title changes.
99 OnTitleChanged func(title string)
100 // OnURIChanged is called when the URI changes.
101 OnURIChanged func(uri string)
102 // OnProgressChanged is called during page load with progress 0.0-1.0.
103 OnProgressChanged func(progress float64)
104 // OnFaviconChanged is called when the page favicon changes.
105 // The parameter is a *gdk.Texture (passed as Texture interface to avoid GTK import in port layer).
106 OnFaviconChanged func(favicon Texture)
107 // OnFaviconURLChanged is called when the engine discovers ordered favicon URL candidates for a page.
108 OnFaviconURLChanged func(pageURL string, iconURLs []string)
109 // OnClose is called when the WebView requests to close.
110 OnClose func()
111 // OnCreate is called when a popup window is requested.
112 // Return a WebView to allow the popup, or nil to block it.
113 OnCreate func(request PopupRequest) WebView
114 // OnReadyToShow is called when a popup WebView is ready to display.
115 OnReadyToShow func()
116 // OnLinkHover is called when hovering over a link, image, or media element.
117 // The uri parameter contains the target URL, or empty string when leaving.
118 OnLinkHover func(uri string)
119 // OnWebProcessTerminated is called when the web process exits unexpectedly.
120 OnWebProcessTerminated func(reason WebProcessTerminationReason, reasonLabel string, uri string)
121
122 // OnPermissionRequest is called when a site requests permission (mic, camera, screen sharing).
123 // Return true to indicate the request was handled. Call allow()/deny() to respond.
124 // The permission types are passed as strings ("microphone", "camera", "display", etc.).
125 // The metadata map carries permission-type-specific context; for website_data_access both
126 // entity.PermissionMetadataKeyRequestingDomain and entity.PermissionMetadataKeyCurrentDomain must be populated.
127 OnPermissionRequest func(origin string, permTypes []string, metadata map[string]string, allow, deny func()) bool
128
129 // OnLinkMiddleClick is called when a link is middle-clicked.
130 // Return true if handled (blocks default navigation).
131 OnLinkMiddleClick func(uri string) bool
132
133 // OnEnterFullscreen is called when the WebView requests fullscreen mode.
134 // Return true to prevent fullscreen.
135 OnEnterFullscreen func() bool
136
137 // OnLeaveFullscreen is called when the WebView exits fullscreen mode.
138 // Return true to prevent leaving fullscreen.
139 OnLeaveFullscreen func() bool
140
141 // OnAudioStateChanged is called when audio playback starts or stops.
142 OnAudioStateChanged func(playing bool)
143 // OnTouchpadNavigationGesture is called while a two-finger touchpad history
144 // navigation gesture is progressing or finishing.
145 OnTouchpadNavigationGesture func(gesture entity.TouchpadNavigationGesture)
146
147 // OnEditableFocusChanged is called when the page focus enters or leaves an
148 // editable target such as input, textarea, or contenteditable content.
149 OnEditableFocusChanged func(editable bool)
150
151 // OnJSDialog is called when the page requests a JavaScript dialog (alert,
152 // confirm, prompt) or the beforeunload "Leave page?" confirmation. The UI
153 // shows the dialog scoped to this WebView and calls respond exactly once
154 // when the user answers (ok=false for cancel/dismiss; input is only
155 // meaningful for prompts). Return false if no dialog could be shown; the
156 // engine then answers with a cancel itself. respond is idempotent and safe
157 // to call from the GTK thread.
158 OnJSDialog func(req JSDialogRequest, respond func(ok bool, input string)) bool
159 // OnJSDialogReset is called when the engine canceled a pending dialog (page
160 // navigation, WebView destruction). The UI must hide any dialog shown for
161 // this WebView without invoking respond.
162 OnJSDialogReset func()
163
164 // OnVimPageInteractionEnded is called when an in-page Vim interaction
165 // (link hints or visual selection) finishes or is canceled by the page.
166 OnVimPageInteractionEnded func()
167
168 // OnVimPageModeChanged is called when a key-capturing in-page Vim
169 // interaction moves to another sub-mode (hints, caret, visual) without
170 // ending.
171 OnVimPageModeChanged func(mode dto.VimPageMode)
172}
173
174// FindOptions configures search behavior.
175type FindOptions struct {
176 CaseInsensitive bool
177 AtWordStarts bool
178 WrapAround bool
179}
180
181// FindController abstracts WebKit's FindController for clean architecture.
182type FindController interface {
183 Search(text string, options FindOptions, maxMatches uint)
184 CountMatches(text string, options FindOptions, maxMatches uint)
185 SearchNext()
186 SearchPrevious()
187 SearchFinish()
188 GetSearchText() string
189
190 // Signal connections
191 OnFoundText(callback func(matchCount uint)) uint
192 OnFailedToFindText(callback func()) uint
193 OnCountedMatches(callback func(matchCount uint)) uint
194 DisconnectSignal(id uint)
195}
196
197// PageNavigator is the navigation subset of WebView. Consumers that only
198// load, reload, or stop pages depend on this instead of the full WebView.
199type PageNavigator interface {
200 // LoadURI navigates to the specified URI.
201 LoadURI(ctx context.Context, uri string) error
202
203 // LoadHTML loads HTML content with an optional base URI for relative links.
204 LoadHTML(ctx context.Context, content, baseURI string) error
205
206 // Reload reloads the current page.
207 Reload(ctx context.Context) error
208
209 // ReloadBypassCache reloads the current page, bypassing cache.
210 ReloadBypassCache(ctx context.Context) error
211
212 // Stop stops the current page load.
213 Stop(ctx context.Context) error
214
215 // GoBack navigates back in history.
216 // Uses WebKit native navigation if available, falls back to JavaScript history.back() for SPA compatibility.
217 GoBack(ctx context.Context) error
218
219 // GoForward navigates forward in history.
220 // Uses WebKit native navigation if available, falls back to JavaScript history.forward() for SPA compatibility.
221 GoForward(ctx context.Context) error
222}
223
224// WebView defines the port interface for browser view operations.
225// This interface abstracts the underlying browser engine (WebKit, etc.)
226// and exposes only the navigation and state capabilities needed by
227// the application layer.
228type WebView interface {
229 PageNavigator
230
231 // ID returns the unique identifier for this WebView.
232 ID() WebViewID
233
234 // --- State Queries ---
235
236 // State returns the current WebView state as a snapshot.
237 State() WebViewState
238
239 // URI returns the current URI.
240 URI() string
241
242 // Title returns the current page title.
243 Title() string
244
245 // IsLoading returns true if a page is currently loading.
246 IsLoading() bool
247
248 // EstimatedProgress returns the load progress (0.0 to 1.0).
249 EstimatedProgress() float64
250
251 // CanGoBack returns true if back navigation is available.
252 CanGoBack() bool
253
254 // CanGoForward returns true if forward navigation is available.
255 CanGoForward() bool
256
257 // --- Zoom ---
258
259 // SetZoomLevel sets the zoom level (1.0 = 100%).
260 SetZoomLevel(ctx context.Context, level float64) error
261
262 // GetZoomLevel returns the current zoom level.
263 GetZoomLevel() float64
264
265 // --- Find ---
266
267 // GetFindController returns the find controller for text search.
268 // Returns nil if find is not supported.
269 GetFindController() FindController
270
271 // --- Callbacks ---
272
273 // SetCallbacks registers callback handlers for WebView events.
274 // Pass nil to clear all callbacks.
275 SetCallbacks(callbacks *WebViewCallbacks)
276
277 // --- Appearance ---
278
279 // RunJavaScript executes a script in the main world. Fire-and-forget.
280 RunJavaScript(ctx context.Context, script string)
281
282 // SetBackgroundColor sets the background color shown before content paints (0.0-1.0 RGBA).
283 SetBackgroundColor(r, g, b, a float64)
284
285 // ResetBackgroundToDefault resets the background to white (browser default).
286 ResetBackgroundToDefault()
287
288 // --- Favicon ---
289
290 // Favicon returns the current page favicon, or nil if unavailable.
291 Favicon() Texture
292
293 // Generation returns a monotonic counter incremented on pool reuse.
294 // Used to detect stale callbacks after a WebView is recycled.
295 Generation() uint64
296
297 // --- Media/Fullscreen State ---
298
299 // IsFullscreen returns true if the WebView is currently in fullscreen mode.
300 IsFullscreen() bool
301
302 // IsPlayingAudio returns true if the WebView is currently playing audio.
303 IsPlayingAudio() bool
304
305 // --- Lifecycle ---
306
307 // IsDestroyed returns true if the WebView has been destroyed.
308 IsDestroyed() bool
309
310 // Destroy releases all resources associated with this WebView.
311 // After calling Destroy, the WebView should not be used.
312 Destroy()
313}
314
315// WebViewPool defines the port interface for WebView pooling.
316// Pools maintain pre-created WebViews for fast tab creation.
317type WebViewPool interface {
318 // Acquire obtains a WebView from the pool or creates a new one.
319 // The context can be used for cancellation.
320 Acquire(ctx context.Context) (WebView, error)
321
322 // Release returns a WebView to the pool for reuse.
323 // The WebView will be reset to a blank state.
324 // If the pool is full, the WebView will be destroyed.
325 Release(wv WebView)
326
327 // Prewarm creates WebViews synchronously to populate the pool.
328 // Pass count <= 0 to use the default prewarm count.
329 Prewarm(count int)
330
331 // PrewarmAsync schedules WebView creation on the toolkit idle loop,
332 // avoiding blocking startup while still warming up WebViews.
333 // Pass count <= 0 to use the default prewarm count.
334 PrewarmAsync(ctx context.Context, count int)
335
336 // Size returns the current number of available WebViews in the pool.
337 Size() int
338
339 // Close shuts down the pool and destroys all pooled WebViews.
340 Close()
341}
342
343// WebViewFactory creates new WebView instances.
344// This is used when direct WebView creation is needed without pooling.
345type WebViewFactory interface {
346 // Create creates a new WebView instance.
347 Create(ctx context.Context) (WebView, error)
348
349 // CreateRelated creates a WebView that shares session/cookies with parent.
350 // This is required for popup windows to maintain authentication state.
351 // Popup WebViews bypass the pool since they must be related to a specific parent.
352 CreateRelated(ctx context.Context, parentID WebViewID) (WebView, error)
353}
354
355// DevToolsOpener is an optional capability for WebViews that support developer tools.
356type DevToolsOpener interface {
357 OpenDevTools()
358}
359
360// Printer is an optional capability for WebViews that support printing.
361type Printer interface {
362 PrintPage()
363}
364
365// PopupLifecycleCapable is implemented by WebViews that support the full popup
366// pane lifecycle. SetOnClose composes the provided function with any existing
367// close handler so multiple callers can register close hooks without
368// overwriting one another. PrimePopupNavigation lets engines preserve the popup
369// target URI before the browser is fully created.
370type PopupLifecycleCapable interface {
371 SetOnReadyToShow(fn func())
372 SetOnClose(fn func())
373 Show()
374 PrimePopupNavigation(uri string)
375}
376
377// PopupFeatureResolver is implemented by engines that can only resolve popup
378// window properties after the related WebView reaches ready-to-show.
379type PopupFeatureResolver interface {
380 ResolvePopupFeatures() dto.PopupFeatures
381}
382
383// PopupOpenerCapable is implemented by popup WebViews that can emulate opener
384// semantics when a popup must be created directly instead of through a native
385// related-popup lifecycle, and that can report or observe synthetic opener
386// traffic needed by OAuth flows.
387type PopupOpenerCapable interface {
388 EnablePopupOpenerBridge(parent WebView, noJavaScriptAccess bool)
389 AddOpenerMessageCallback(fn func())
390 AddOpenerNavigationCallback(fn func(uri string))
391 HasActivePopupOpenerBridge() bool
392}
393
394// ViewportSyncCapable is implemented by WebViews that can explicitly resync
395// their internal viewport state after UI lifecycle events such as reparenting,
396// visibility toggles, or layout promotion that may not emit a size change.
397//
398// Implementations should make this cheap, safe to call often, and resilient to
399// duplicate requests.
400type ViewportSyncCapable interface {
401 SyncViewport(ctx context.Context, reason string)
402}
403
404// OAuthCallbackCapable is implemented by WebViews that support OAuth auto-close callbacks.
405// It is an optional capability; callers should type-assert port.WebView to this interface
406// before use and degrade gracefully when it is absent.
407type OAuthCallbackCapable interface {
408 // AddCloseCallback registers fn to be called when the WebView is programmatically closed.
409 // Multiple callbacks may be registered; each will be called in registration order.
410 AddCloseCallback(fn func())
411 // AddNavigationCallback registers fn to be called whenever the URI changes or a
412 // committed load is detected. The uri parameter contains the new URL.
413 // Multiple callbacks may be registered; each will be called in registration order.
414 AddNavigationCallback(fn func(uri string))
415 // Close triggers a programmatic close of the WebView (equivalent to window.close()).
416 Close()
417}
418
419// TextInputTargetProvider is an optional capability for WebViews that support
420// text input method integration.
421type TextInputTargetProvider interface {
422 TextInputTarget() TextInputTarget
423}
424
425// PageScrollCommand identifies a semantic vim-scroll request at the
426// application boundary without depending on usecase package enums.
427type PageScrollCommand int
428
429const (
430 PageScrollCommandLeft PageScrollCommand = iota
431 PageScrollCommandRight
432 PageScrollCommandUp
433 PageScrollCommandDown
434 PageScrollCommandUpFast
435 PageScrollCommandDownFast
436)
437
438// PageScrollRequest carries a semantic vim-scroll command identity and its
439// fallback pixel deltas. Engines own the execution strategy and may use the
440// fallback delta when no native mechanism exists for the command.
441type PageScrollRequest struct {
442 Command PageScrollCommand
443 FallbackDX int
444 FallbackDY int
445 // Continuous distinguishes autonomous held-key steps from the guaranteed
446 // tap step so adapters can cancel queued held work without dropping taps.
447 Continuous bool
448}
449
450// IsValid reports whether c is a supported semantic page-scroll command.
451func (c PageScrollCommand) IsValid() bool {
452 return c >= PageScrollCommandLeft && c <= PageScrollCommandDownFast
453}
454
455// Validate checks the command identity carried by a page-scroll request.
456func (r PageScrollRequest) Validate() error {
457 if !r.Command.IsValid() {
458 return fmt.Errorf("unsupported page scroll command %d", r.Command)
459 }
460 return nil
461}
462
463// PageScrollable is an optional capability for WebViews that support semantic
464// page scrolling. Page scrolling is used by keyboard-driven navigation modes
465// such as Vim mode.
466//
467// Engines own the execution strategy. When a native mechanism exists for the
468// requested command, the engine may ignore the fallback delta. Otherwise it
469// should use the fallback delta as a JavaScript-driven scroll amount.
470type PageScrollable interface {
471 ScrollPage(ctx context.Context, request PageScrollRequest) error
472}
473
474// PageScrollCanceler is an optional capability for adapters that queue held
475// vim-scroll work. Implementations discard unconsumed continuous deltas while
476// preserving any guaranteed tap already accepted.
477type PageScrollCanceler interface {
478 CancelPageScroll(ctx context.Context)
479}
480
481// AccessibilityEnabler is an optional capability for adapters that can enable
482// browser accessibility tooling once per WebView. Engines that do not support
483// accessibility leave this unimplemented so callers can type-assert safely.
484type AccessibilityEnabler interface {
485 EnableAccessibility()
486}
487
488// SemanticNavigable is an optional WebView capability for visible structural
489// navigation. Unsupported engines safely leave it unimplemented.
490type SemanticNavigable interface {
491 NavigateSemantic(ctx context.Context, request dto.SemanticNavigationRequest) error
492}
493
494// SemanticNavigationActivator is an optional WebView capability for activating
495// the link associated with the current semantic navigation target.
496type SemanticNavigationActivator interface {
497 ActivateSemanticNavigationTarget(ctx context.Context) error
498}
499
500// SemanticNavigationHighlightClearer is an optional WebView capability for
501// removing the visual target left by semantic navigation.
502type SemanticNavigationHighlightClearer interface {
503 ClearSemanticNavigationHighlight(ctx context.Context) error
504}
505
506// PageInputFocuser is an optional WebView capability for moving Vim Mode
507// input focus to the next eligible page input. With no active eligible input,
508// implementations focus the first one. Unsupported engines safely leave it
509// unimplemented.
510type PageInputFocuser interface {
511 FocusNextInput()
512}
513
514// VimPageInteractor is an optional WebView capability for in-page Vim
515// interactions: link hints, visual selection, and text-object yanks. Keys are
516// canonical vimkeys strings such as "j", "J", or "<Esc>". Implementations
517// report the end of a key-capturing interaction through
518// WebViewCallbacks.OnVimPageInteractionEnded.
519type VimPageInteractor interface {
520 StartVimPageInteraction(ctx context.Context, request dto.VimPageInteractionRequest) error
521 SendVimPageKey(ctx context.Context, key string) error
522 CancelVimPageInteraction(ctx context.Context) error
523}
524
525// PageFocusNavigator is an optional WebView capability for keeping native-like
526// focus traversal inside the active page when the host window would otherwise
527// move focus outside the WebView.
528type PageFocusNavigator interface {
529 NavigatePageFocus(backward bool)
530}