webview.go RawHistoryFolder

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
152// FindOptions configures search behavior.
153type FindOptions struct {
154	CaseInsensitive bool
155	AtWordStarts    bool
156	WrapAround      bool
157}
158
159// FindController abstracts WebKit's FindController for clean architecture.
160type FindController interface {
161	Search(text string, options FindOptions, maxMatches uint)
162	CountMatches(text string, options FindOptions, maxMatches uint)
163	SearchNext()
164	SearchPrevious()
165	SearchFinish()
166	GetSearchText() string
167
168	// Signal connections
169	OnFoundText(callback func(matchCount uint)) uint
170	OnFailedToFindText(callback func()) uint
171	OnCountedMatches(callback func(matchCount uint)) uint
172	DisconnectSignal(id uint)
173}
174
175// PageNavigator is the navigation subset of WebView. Consumers that only
176// load, reload, or stop pages depend on this instead of the full WebView.
177type PageNavigator interface {
178	// LoadURI navigates to the specified URI.
179	LoadURI(ctx context.Context, uri string) error
180
181	// LoadHTML loads HTML content with an optional base URI for relative links.
182	LoadHTML(ctx context.Context, content, baseURI string) error
183
184	// Reload reloads the current page.
185	Reload(ctx context.Context) error
186
187	// ReloadBypassCache reloads the current page, bypassing cache.
188	ReloadBypassCache(ctx context.Context) error
189
190	// Stop stops the current page load.
191	Stop(ctx context.Context) error
192
193	// GoBack navigates back in history.
194	// Uses WebKit native navigation if available, falls back to JavaScript history.back() for SPA compatibility.
195	GoBack(ctx context.Context) error
196
197	// GoForward navigates forward in history.
198	// Uses WebKit native navigation if available, falls back to JavaScript history.forward() for SPA compatibility.
199	GoForward(ctx context.Context) error
200}
201
202// WebView defines the port interface for browser view operations.
203// This interface abstracts the underlying browser engine (WebKit, etc.)
204// and exposes only the navigation and state capabilities needed by
205// the application layer.
206type WebView interface {
207	PageNavigator
208
209	// ID returns the unique identifier for this WebView.
210	ID() WebViewID
211
212	// --- State Queries ---
213
214	// State returns the current WebView state as a snapshot.
215	State() WebViewState
216
217	// URI returns the current URI.
218	URI() string
219
220	// Title returns the current page title.
221	Title() string
222
223	// IsLoading returns true if a page is currently loading.
224	IsLoading() bool
225
226	// EstimatedProgress returns the load progress (0.0 to 1.0).
227	EstimatedProgress() float64
228
229	// CanGoBack returns true if back navigation is available.
230	CanGoBack() bool
231
232	// CanGoForward returns true if forward navigation is available.
233	CanGoForward() bool
234
235	// --- Zoom ---
236
237	// SetZoomLevel sets the zoom level (1.0 = 100%).
238	SetZoomLevel(ctx context.Context, level float64) error
239
240	// GetZoomLevel returns the current zoom level.
241	GetZoomLevel() float64
242
243	// --- Find ---
244
245	// GetFindController returns the find controller for text search.
246	// Returns nil if find is not supported.
247	GetFindController() FindController
248
249	// --- Callbacks ---
250
251	// SetCallbacks registers callback handlers for WebView events.
252	// Pass nil to clear all callbacks.
253	SetCallbacks(callbacks *WebViewCallbacks)
254
255	// --- Appearance ---
256
257	// RunJavaScript executes a script in the main world. Fire-and-forget.
258	RunJavaScript(ctx context.Context, script string)
259
260	// SetBackgroundColor sets the background color shown before content paints (0.0-1.0 RGBA).
261	SetBackgroundColor(r, g, b, a float64)
262
263	// ResetBackgroundToDefault resets the background to white (browser default).
264	ResetBackgroundToDefault()
265
266	// --- Favicon ---
267
268	// Favicon returns the current page favicon, or nil if unavailable.
269	Favicon() Texture
270
271	// Generation returns a monotonic counter incremented on pool reuse.
272	// Used to detect stale callbacks after a WebView is recycled.
273	Generation() uint64
274
275	// --- Media/Fullscreen State ---
276
277	// IsFullscreen returns true if the WebView is currently in fullscreen mode.
278	IsFullscreen() bool
279
280	// IsPlayingAudio returns true if the WebView is currently playing audio.
281	IsPlayingAudio() bool
282
283	// --- Lifecycle ---
284
285	// IsDestroyed returns true if the WebView has been destroyed.
286	IsDestroyed() bool
287
288	// Destroy releases all resources associated with this WebView.
289	// After calling Destroy, the WebView should not be used.
290	Destroy()
291}
292
293// WebViewPool defines the port interface for WebView pooling.
294// Pools maintain pre-created WebViews for fast tab creation.
295type WebViewPool interface {
296	// Acquire obtains a WebView from the pool or creates a new one.
297	// The context can be used for cancellation.
298	Acquire(ctx context.Context) (WebView, error)
299
300	// Release returns a WebView to the pool for reuse.
301	// The WebView will be reset to a blank state.
302	// If the pool is full, the WebView will be destroyed.
303	Release(wv WebView)
304
305	// Prewarm creates WebViews synchronously to populate the pool.
306	// Pass count <= 0 to use the default prewarm count.
307	Prewarm(count int)
308
309	// PrewarmAsync schedules WebView creation on the toolkit idle loop,
310	// avoiding blocking startup while still warming up WebViews.
311	// Pass count <= 0 to use the default prewarm count.
312	PrewarmAsync(ctx context.Context, count int)
313
314	// Size returns the current number of available WebViews in the pool.
315	Size() int
316
317	// Close shuts down the pool and destroys all pooled WebViews.
318	Close()
319}
320
321// WebViewFactory creates new WebView instances.
322// This is used when direct WebView creation is needed without pooling.
323type WebViewFactory interface {
324	// Create creates a new WebView instance.
325	Create(ctx context.Context) (WebView, error)
326
327	// CreateRelated creates a WebView that shares session/cookies with parent.
328	// This is required for popup windows to maintain authentication state.
329	// Popup WebViews bypass the pool since they must be related to a specific parent.
330	CreateRelated(ctx context.Context, parentID WebViewID) (WebView, error)
331}
332
333// DevToolsOpener is an optional capability for WebViews that support developer tools.
334type DevToolsOpener interface {
335	OpenDevTools()
336}
337
338// Printer is an optional capability for WebViews that support printing.
339type Printer interface {
340	PrintPage()
341}
342
343// PopupLifecycleCapable is implemented by WebViews that support the full popup
344// pane lifecycle. SetOnClose composes the provided function with any existing
345// close handler so multiple callers can register close hooks without
346// overwriting one another. PrimePopupNavigation lets engines preserve the popup
347// target URI before the browser is fully created.
348type PopupLifecycleCapable interface {
349	SetOnReadyToShow(fn func())
350	SetOnClose(fn func())
351	Show()
352	PrimePopupNavigation(uri string)
353}
354
355// PopupFeatureResolver is implemented by engines that can only resolve popup
356// window properties after the related WebView reaches ready-to-show.
357type PopupFeatureResolver interface {
358	ResolvePopupFeatures() dto.PopupFeatures
359}
360
361// PopupOpenerCapable is implemented by popup WebViews that can emulate opener
362// semantics when a popup must be created directly instead of through a native
363// related-popup lifecycle, and that can report or observe synthetic opener
364// traffic needed by OAuth flows.
365type PopupOpenerCapable interface {
366	EnablePopupOpenerBridge(parent WebView, noJavaScriptAccess bool)
367	AddOpenerMessageCallback(fn func())
368	AddOpenerNavigationCallback(fn func(uri string))
369	HasActivePopupOpenerBridge() bool
370}
371
372// ViewportSyncCapable is implemented by WebViews that can explicitly resync
373// their internal viewport state after UI lifecycle events such as reparenting,
374// visibility toggles, or layout promotion that may not emit a size change.
375//
376// Implementations should make this cheap, safe to call often, and resilient to
377// duplicate requests.
378type ViewportSyncCapable interface {
379	SyncViewport(ctx context.Context, reason string)
380}
381
382// OAuthCallbackCapable is implemented by WebViews that support OAuth auto-close callbacks.
383// It is an optional capability; callers should type-assert port.WebView to this interface
384// before use and degrade gracefully when it is absent.
385type OAuthCallbackCapable interface {
386	// AddCloseCallback registers fn to be called when the WebView is programmatically closed.
387	// Multiple callbacks may be registered; each will be called in registration order.
388	AddCloseCallback(fn func())
389	// AddNavigationCallback registers fn to be called whenever the URI changes or a
390	// committed load is detected. The uri parameter contains the new URL.
391	// Multiple callbacks may be registered; each will be called in registration order.
392	AddNavigationCallback(fn func(uri string))
393	// Close triggers a programmatic close of the WebView (equivalent to window.close()).
394	Close()
395}
396
397// TextInputTargetProvider is an optional capability for WebViews that support
398// text input method integration.
399type TextInputTargetProvider interface {
400	TextInputTarget() TextInputTarget
401}
402
403// PageScrollCommand identifies a semantic vim-scroll request at the
404// application boundary without depending on usecase package enums.
405type PageScrollCommand int
406
407const (
408	PageScrollCommandLeft PageScrollCommand = iota
409	PageScrollCommandRight
410	PageScrollCommandUp
411	PageScrollCommandDown
412	PageScrollCommandUpFast
413	PageScrollCommandDownFast
414)
415
416// PageScrollRequest carries a semantic vim-scroll command identity and its
417// fallback pixel deltas. Engines own the execution strategy and may use the
418// fallback delta when no native mechanism exists for the command.
419type PageScrollRequest struct {
420	Command    PageScrollCommand
421	FallbackDX int
422	FallbackDY int
423	// Continuous distinguishes autonomous held-key steps from the guaranteed
424	// tap step so adapters can cancel queued held work without dropping taps.
425	Continuous bool
426}
427
428// IsValid reports whether c is a supported semantic page-scroll command.
429func (c PageScrollCommand) IsValid() bool {
430	return c >= PageScrollCommandLeft && c <= PageScrollCommandDownFast
431}
432
433// Validate checks the command identity carried by a page-scroll request.
434func (r PageScrollRequest) Validate() error {
435	if !r.Command.IsValid() {
436		return fmt.Errorf("unsupported page scroll command %d", r.Command)
437	}
438	return nil
439}
440
441// PageScrollable is an optional capability for WebViews that support semantic
442// page scrolling. Page scrolling is used by keyboard-driven navigation modes
443// such as Vim mode.
444//
445// Engines own the execution strategy. When a native mechanism exists for the
446// requested command, the engine may ignore the fallback delta. Otherwise it
447// should use the fallback delta as a JavaScript-driven scroll amount.
448type PageScrollable interface {
449	ScrollPage(ctx context.Context, request PageScrollRequest) error
450}
451
452// PageScrollCanceler is an optional capability for adapters that queue held
453// vim-scroll work. Implementations discard unconsumed continuous deltas while
454// preserving any guaranteed tap already accepted.
455type PageScrollCanceler interface {
456	CancelPageScroll(ctx context.Context)
457}
458
459// AccessibilityEnabler is an optional capability for adapters that can enable
460// browser accessibility tooling once per WebView. Engines that do not support
461// accessibility leave this unimplemented so callers can type-assert safely.
462type AccessibilityEnabler interface {
463	EnableAccessibility()
464}
465
466// SemanticNavigable is an optional WebView capability for visible structural
467// navigation. Unsupported engines safely leave it unimplemented.
468type SemanticNavigable interface {
469	NavigateSemantic(ctx context.Context, request dto.SemanticNavigationRequest) error
470}
471
472// SemanticNavigationActivator is an optional WebView capability for activating
473// the link associated with the current semantic navigation target.
474type SemanticNavigationActivator interface {
475	ActivateSemanticNavigationTarget(ctx context.Context) error
476}
477
478// SemanticNavigationHighlightClearer is an optional WebView capability for
479// removing the visual target left by semantic navigation.
480type SemanticNavigationHighlightClearer interface {
481	ClearSemanticNavigationHighlight(ctx context.Context) error
482}
483
484// PageInputFocuser is an optional WebView capability for moving Vim Mode
485// input focus to the next eligible page input. With no active eligible input,
486// implementations focus the first one. Unsupported engines safely leave it
487// unimplemented.
488type PageInputFocuser interface {
489	FocusNextInput()
490}
491
492// PageFocusNavigator is an optional WebView capability for keeping native-like
493// focus traversal inside the active page when the host window would otherwise
494// move focus outside the WebView.
495type PageFocusNavigator interface {
496	NavigatePageFocus(backward bool)
497}

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