v0.29.0 21b3150

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)
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}

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