v0.30.0 eb3068f

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

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