// Package port defines application-layer interfaces for external capabilities. // Ports abstract infrastructure concerns, allowing the application layer to // remain independent of specific implementations (WebKit, GTK, etc.). package port import ( "context" "fmt" "github.com/bnema/dumber/internal/application/dto" "github.com/bnema/dumber/internal/domain/entity" ) // WebViewID uniquely identifies a WebView instance. type WebViewID uint64 // LoadEvent represents page load state transitions. type LoadEvent int const ( // LoadStarted indicates navigation has begun. LoadStarted LoadEvent = iota // LoadRedirected indicates a redirect occurred. LoadRedirected // LoadCommitted indicates content is being received. LoadCommitted // LoadFinished indicates the page has fully loaded. LoadFinished ) // String returns a human-readable representation of the load event. func (e LoadEvent) String() string { switch e { case LoadStarted: return "started" case LoadRedirected: return "redirected" case LoadCommitted: return "committed" case LoadFinished: return "finished" default: return "unknown" } } // WebViewState represents a snapshot of the current WebView state. // This is an immutable struct that can be safely passed between components. type WebViewState struct { URI string Title string IsLoading bool Progress float64 // 0.0 to 1.0 CanGoBack bool CanGoFwd bool ZoomLevel float64 } // WebProcessTerminationReason describes why the web process terminated. type WebProcessTerminationReason int const ( WebProcessTerminationUnknown WebProcessTerminationReason = iota WebProcessTerminationCrashed WebProcessTerminationExceededMemory WebProcessTerminationByAPI ) // PopupRequest contains metadata about a popup window request. type PopupRequest struct { TargetURI string FrameName string // e.g., "_blank", custom name, or empty IsUserGesture bool NoJavaScriptAccess bool // true for noopener/noreferrer-style popups with no opener access ParentViewID WebViewID SourceBrowserID int32 SourceFrameID string SourceFrameURL string Engine dto.BrowserEngineKind TargetDisposition dto.WindowDisposition PopupFeatures dto.PopupFeatures } // Texture represents a graphics texture returned by the engine. // GoPointer returns a native toolkit pointer (e.g. *gdk.Texture in GTK engines). // Engine implementations are responsible for the concrete type. type Texture interface { GoPointer() uintptr } // WebViewCallbacks defines callback handlers for WebView events. // Implementations should invoke these on the main thread/goroutine. type WebViewCallbacks struct { // OnLoadChanged is called when load state changes. OnLoadChanged func(event LoadEvent) // OnTitleChanged is called when the page title changes. OnTitleChanged func(title string) // OnURIChanged is called when the URI changes. OnURIChanged func(uri string) // OnProgressChanged is called during page load with progress 0.0-1.0. OnProgressChanged func(progress float64) // OnFaviconChanged is called when the page favicon changes. // The parameter is a *gdk.Texture (passed as Texture interface to avoid GTK import in port layer). OnFaviconChanged func(favicon Texture) // OnFaviconURLChanged is called when the engine discovers ordered favicon URL candidates for a page. OnFaviconURLChanged func(pageURL string, iconURLs []string) // OnClose is called when the WebView requests to close. OnClose func() // OnCreate is called when a popup window is requested. // Return a WebView to allow the popup, or nil to block it. OnCreate func(request PopupRequest) WebView // OnReadyToShow is called when a popup WebView is ready to display. OnReadyToShow func() // OnLinkHover is called when hovering over a link, image, or media element. // The uri parameter contains the target URL, or empty string when leaving. OnLinkHover func(uri string) // OnWebProcessTerminated is called when the web process exits unexpectedly. OnWebProcessTerminated func(reason WebProcessTerminationReason, reasonLabel string, uri string) // OnPermissionRequest is called when a site requests permission (mic, camera, screen sharing). // Return true to indicate the request was handled. Call allow()/deny() to respond. // The permission types are passed as strings ("microphone", "camera", "display", etc.). // The metadata map carries permission-type-specific context; for website_data_access both // entity.PermissionMetadataKeyRequestingDomain and entity.PermissionMetadataKeyCurrentDomain must be populated. OnPermissionRequest func(origin string, permTypes []string, metadata map[string]string, allow, deny func()) bool // OnLinkMiddleClick is called when a link is middle-clicked. // Return true if handled (blocks default navigation). OnLinkMiddleClick func(uri string) bool // OnEnterFullscreen is called when the WebView requests fullscreen mode. // Return true to prevent fullscreen. OnEnterFullscreen func() bool // OnLeaveFullscreen is called when the WebView exits fullscreen mode. // Return true to prevent leaving fullscreen. OnLeaveFullscreen func() bool // OnAudioStateChanged is called when audio playback starts or stops. OnAudioStateChanged func(playing bool) // OnTouchpadNavigationGesture is called while a two-finger touchpad history // navigation gesture is progressing or finishing. OnTouchpadNavigationGesture func(gesture entity.TouchpadNavigationGesture) // OnEditableFocusChanged is called when the page focus enters or leaves an // editable target such as input, textarea, or contenteditable content. OnEditableFocusChanged func(editable bool) // OnJSDialog is called when the page requests a JavaScript dialog (alert, // confirm, prompt) or the beforeunload "Leave page?" confirmation. The UI // shows the dialog scoped to this WebView and calls respond exactly once // when the user answers (ok=false for cancel/dismiss; input is only // meaningful for prompts). Return false if no dialog could be shown; the // engine then answers with a cancel itself. respond is idempotent and safe // to call from the GTK thread. OnJSDialog func(req JSDialogRequest, respond func(ok bool, input string)) bool // OnJSDialogReset is called when the engine canceled a pending dialog (page // navigation, WebView destruction). The UI must hide any dialog shown for // this WebView without invoking respond. OnJSDialogReset func() } // FindOptions configures search behavior. type FindOptions struct { CaseInsensitive bool AtWordStarts bool WrapAround bool } // FindController abstracts WebKit's FindController for clean architecture. type FindController interface { Search(text string, options FindOptions, maxMatches uint) CountMatches(text string, options FindOptions, maxMatches uint) SearchNext() SearchPrevious() SearchFinish() GetSearchText() string // Signal connections OnFoundText(callback func(matchCount uint)) uint OnFailedToFindText(callback func()) uint OnCountedMatches(callback func(matchCount uint)) uint DisconnectSignal(id uint) } // PageNavigator is the navigation subset of WebView. Consumers that only // load, reload, or stop pages depend on this instead of the full WebView. type PageNavigator interface { // LoadURI navigates to the specified URI. LoadURI(ctx context.Context, uri string) error // LoadHTML loads HTML content with an optional base URI for relative links. LoadHTML(ctx context.Context, content, baseURI string) error // Reload reloads the current page. Reload(ctx context.Context) error // ReloadBypassCache reloads the current page, bypassing cache. ReloadBypassCache(ctx context.Context) error // Stop stops the current page load. Stop(ctx context.Context) error // GoBack navigates back in history. // Uses WebKit native navigation if available, falls back to JavaScript history.back() for SPA compatibility. GoBack(ctx context.Context) error // GoForward navigates forward in history. // Uses WebKit native navigation if available, falls back to JavaScript history.forward() for SPA compatibility. GoForward(ctx context.Context) error } // WebView defines the port interface for browser view operations. // This interface abstracts the underlying browser engine (WebKit, etc.) // and exposes only the navigation and state capabilities needed by // the application layer. type WebView interface { PageNavigator // ID returns the unique identifier for this WebView. ID() WebViewID // --- State Queries --- // State returns the current WebView state as a snapshot. State() WebViewState // URI returns the current URI. URI() string // Title returns the current page title. Title() string // IsLoading returns true if a page is currently loading. IsLoading() bool // EstimatedProgress returns the load progress (0.0 to 1.0). EstimatedProgress() float64 // CanGoBack returns true if back navigation is available. CanGoBack() bool // CanGoForward returns true if forward navigation is available. CanGoForward() bool // --- Zoom --- // SetZoomLevel sets the zoom level (1.0 = 100%). SetZoomLevel(ctx context.Context, level float64) error // GetZoomLevel returns the current zoom level. GetZoomLevel() float64 // --- Find --- // GetFindController returns the find controller for text search. // Returns nil if find is not supported. GetFindController() FindController // --- Callbacks --- // SetCallbacks registers callback handlers for WebView events. // Pass nil to clear all callbacks. SetCallbacks(callbacks *WebViewCallbacks) // --- Appearance --- // RunJavaScript executes a script in the main world. Fire-and-forget. RunJavaScript(ctx context.Context, script string) // SetBackgroundColor sets the background color shown before content paints (0.0-1.0 RGBA). SetBackgroundColor(r, g, b, a float64) // ResetBackgroundToDefault resets the background to white (browser default). ResetBackgroundToDefault() // --- Favicon --- // Favicon returns the current page favicon, or nil if unavailable. Favicon() Texture // Generation returns a monotonic counter incremented on pool reuse. // Used to detect stale callbacks after a WebView is recycled. Generation() uint64 // --- Media/Fullscreen State --- // IsFullscreen returns true if the WebView is currently in fullscreen mode. IsFullscreen() bool // IsPlayingAudio returns true if the WebView is currently playing audio. IsPlayingAudio() bool // --- Lifecycle --- // IsDestroyed returns true if the WebView has been destroyed. IsDestroyed() bool // Destroy releases all resources associated with this WebView. // After calling Destroy, the WebView should not be used. Destroy() } // WebViewPool defines the port interface for WebView pooling. // Pools maintain pre-created WebViews for fast tab creation. type WebViewPool interface { // Acquire obtains a WebView from the pool or creates a new one. // The context can be used for cancellation. Acquire(ctx context.Context) (WebView, error) // Release returns a WebView to the pool for reuse. // The WebView will be reset to a blank state. // If the pool is full, the WebView will be destroyed. Release(wv WebView) // Prewarm creates WebViews synchronously to populate the pool. // Pass count <= 0 to use the default prewarm count. Prewarm(count int) // PrewarmAsync schedules WebView creation on the toolkit idle loop, // avoiding blocking startup while still warming up WebViews. // Pass count <= 0 to use the default prewarm count. PrewarmAsync(ctx context.Context, count int) // Size returns the current number of available WebViews in the pool. Size() int // Close shuts down the pool and destroys all pooled WebViews. Close() } // WebViewFactory creates new WebView instances. // This is used when direct WebView creation is needed without pooling. type WebViewFactory interface { // Create creates a new WebView instance. Create(ctx context.Context) (WebView, error) // CreateRelated creates a WebView that shares session/cookies with parent. // This is required for popup windows to maintain authentication state. // Popup WebViews bypass the pool since they must be related to a specific parent. CreateRelated(ctx context.Context, parentID WebViewID) (WebView, error) } // DevToolsOpener is an optional capability for WebViews that support developer tools. type DevToolsOpener interface { OpenDevTools() } // Printer is an optional capability for WebViews that support printing. type Printer interface { PrintPage() } // PopupLifecycleCapable is implemented by WebViews that support the full popup // pane lifecycle. SetOnClose composes the provided function with any existing // close handler so multiple callers can register close hooks without // overwriting one another. PrimePopupNavigation lets engines preserve the popup // target URI before the browser is fully created. type PopupLifecycleCapable interface { SetOnReadyToShow(fn func()) SetOnClose(fn func()) Show() PrimePopupNavigation(uri string) } // PopupFeatureResolver is implemented by engines that can only resolve popup // window properties after the related WebView reaches ready-to-show. type PopupFeatureResolver interface { ResolvePopupFeatures() dto.PopupFeatures } // PopupOpenerCapable is implemented by popup WebViews that can emulate opener // semantics when a popup must be created directly instead of through a native // related-popup lifecycle, and that can report or observe synthetic opener // traffic needed by OAuth flows. type PopupOpenerCapable interface { EnablePopupOpenerBridge(parent WebView, noJavaScriptAccess bool) AddOpenerMessageCallback(fn func()) AddOpenerNavigationCallback(fn func(uri string)) HasActivePopupOpenerBridge() bool } // ViewportSyncCapable is implemented by WebViews that can explicitly resync // their internal viewport state after UI lifecycle events such as reparenting, // visibility toggles, or layout promotion that may not emit a size change. // // Implementations should make this cheap, safe to call often, and resilient to // duplicate requests. type ViewportSyncCapable interface { SyncViewport(ctx context.Context, reason string) } // OAuthCallbackCapable is implemented by WebViews that support OAuth auto-close callbacks. // It is an optional capability; callers should type-assert port.WebView to this interface // before use and degrade gracefully when it is absent. type OAuthCallbackCapable interface { // AddCloseCallback registers fn to be called when the WebView is programmatically closed. // Multiple callbacks may be registered; each will be called in registration order. AddCloseCallback(fn func()) // AddNavigationCallback registers fn to be called whenever the URI changes or a // committed load is detected. The uri parameter contains the new URL. // Multiple callbacks may be registered; each will be called in registration order. AddNavigationCallback(fn func(uri string)) // Close triggers a programmatic close of the WebView (equivalent to window.close()). Close() } // TextInputTargetProvider is an optional capability for WebViews that support // text input method integration. type TextInputTargetProvider interface { TextInputTarget() TextInputTarget } // PageScrollCommand identifies a semantic vim-scroll request at the // application boundary without depending on usecase package enums. type PageScrollCommand int const ( PageScrollCommandLeft PageScrollCommand = iota PageScrollCommandRight PageScrollCommandUp PageScrollCommandDown PageScrollCommandUpFast PageScrollCommandDownFast ) // PageScrollRequest carries a semantic vim-scroll command identity and its // fallback pixel deltas. Engines own the execution strategy and may use the // fallback delta when no native mechanism exists for the command. type PageScrollRequest struct { Command PageScrollCommand FallbackDX int FallbackDY int // Continuous distinguishes autonomous held-key steps from the guaranteed // tap step so adapters can cancel queued held work without dropping taps. Continuous bool } // IsValid reports whether c is a supported semantic page-scroll command. func (c PageScrollCommand) IsValid() bool { return c >= PageScrollCommandLeft && c <= PageScrollCommandDownFast } // Validate checks the command identity carried by a page-scroll request. func (r PageScrollRequest) Validate() error { if !r.Command.IsValid() { return fmt.Errorf("unsupported page scroll command %d", r.Command) } return nil } // PageScrollable is an optional capability for WebViews that support semantic // page scrolling. Page scrolling is used by keyboard-driven navigation modes // such as Vim mode. // // Engines own the execution strategy. When a native mechanism exists for the // requested command, the engine may ignore the fallback delta. Otherwise it // should use the fallback delta as a JavaScript-driven scroll amount. type PageScrollable interface { ScrollPage(ctx context.Context, request PageScrollRequest) error } // PageScrollCanceler is an optional capability for adapters that queue held // vim-scroll work. Implementations discard unconsumed continuous deltas while // preserving any guaranteed tap already accepted. type PageScrollCanceler interface { CancelPageScroll(ctx context.Context) } // AccessibilityEnabler is an optional capability for adapters that can enable // browser accessibility tooling once per WebView. Engines that do not support // accessibility leave this unimplemented so callers can type-assert safely. type AccessibilityEnabler interface { EnableAccessibility() } // SemanticNavigable is an optional WebView capability for visible structural // navigation. Unsupported engines safely leave it unimplemented. type SemanticNavigable interface { NavigateSemantic(ctx context.Context, request dto.SemanticNavigationRequest) error } // SemanticNavigationActivator is an optional WebView capability for activating // the link associated with the current semantic navigation target. type SemanticNavigationActivator interface { ActivateSemanticNavigationTarget(ctx context.Context) error } // SemanticNavigationHighlightClearer is an optional WebView capability for // removing the visual target left by semantic navigation. type SemanticNavigationHighlightClearer interface { ClearSemanticNavigationHighlight(ctx context.Context) error } // PageInputFocuser is an optional WebView capability for moving Vim Mode // input focus to the next eligible page input. With no active eligible input, // implementations focus the first one. Unsupported engines safely leave it // unimplemented. type PageInputFocuser interface { FocusNextInput() } // PageFocusNavigator is an optional WebView capability for keeping native-like // focus traversal inside the active page when the host window would otherwise // move focus outside the WebView. type PageFocusNavigator interface { NavigatePageFocus(backward bool) }