wails/docs/src/content/docs/contributing/runtime-internals.mdx
Lea Anthony 53c2275fea
fix(v3): overhaul drag-and-drop for Linux reliability and simplify Windows implementation (#4848)
* fix(v3): overhaul drag-and-drop for Linux reliability and simplify Windows

This commit fixes drag-and-drop reliability on Linux and simplifies the
Windows implementation.

## Linux
- Rewrite GTK drag handlers to properly intercept external file drops
- Fix HTML5 internal drag-and-drop being broken when file drop enabled
- Add hover effects during file drag operations
- Fix multiple app instances interfering with each other

## Windows
- Remove native IDropTarget in favor of JavaScript approach (matches v2)
- File drops now handled via chrome.webview.postMessageWithAdditionalObjects

## All Platforms
- Rename EnableDragAndDrop to EnableFileDrop
- Rename data-wails-drop-target to data-file-drop-target
- Rename wails-drop-target-active to file-drop-target-active
- Add comprehensive drag-and-drop documentation

## Breaking Changes
- EnableDragAndDrop -> EnableFileDrop
- data-wails-dropzone -> data-file-drop-target
- wails-dropzone-hover -> file-drop-target-active
- DropZoneDetails -> DropTargetDetails
- Remove WindowDropZoneFilesDropped event (use WindowFilesDropped)

* feat(macos): optimize drag event performance with debouncing and caching

- Add 50ms debouncing to limit drag events to 20/sec (was 120/sec)
- Implement window implementation caching to avoid repeated lookups
- Maintain existing 5-pixel threshold for immediate response
- Keep zero-allocation path with pre-allocated buffers
- Rename linuxDragActive to nativeDragActive for clarity
- Update IMPLEMENTATION.md with optimization details and Windows guidance

Performance improvements:
- 83% reduction in event frequency
- ~6x reduction in CPU/memory usage during drag operations
- Maintains smooth visual feedback with InvokeSync for timer callbacks

* fix(windows): implement proper file drop support for Windows

- Remove incorrect AllowExternalDrag(false) call that was blocking file drops
- Fix message prefix from 'FilesDropped' to 'file:drop:' to match JS runtime
- Fix coordinate parsing for 'file:drop:x:y' format (indices 2,3 not 1,2)
- Add enableFileDrop flag injection to JS runtime during navigation
- Update JS runtime to check enableFileDrop flag before processing drops
- Always call preventDefault() to stop browser navigation on file drags
- Show 'no drop' cursor when file drops are disabled
- Update example to filter file drags from HTML drop zone handlers
- Add documentation for combining file drop with HTML drag-and-drop

* fix(v3): block file drops on Linux when EnableFileDrop is false

- Add disableDND() to intercept and reject external file drags at GTK level
- Show 'no drop' cursor when files are dragged over window
- Allow internal HTML5 drag-and-drop to work normally
- Initialize _wails.flags object in runtime core to prevent undefined errors
- Inject enableFileDrop flag on Linux and macOS (matching Windows)
- Fix bare _wails reference to use window._wails
- Update docs with info about blocked drops and combining with HTML DnD

* fix(darwin): add missing fmt import in webview_window_darwin.go

* fix(macOS): implement hover effects for file drag-and-drop with optimizations

- Added draggingUpdated: handler to track mouse movement during drag operations
- Implemented macosOnDragEnter/Exit/Over export functions for real-time hover state
- Fixed JS function call from '_wails.handlePlatformFileDrop' to correct 'wails.Window.HandlePlatformFileDrop'
- Added EnableFileDrop flag checks to prevent hover effects when file drops are disabled
- Renamed linuxDragActive to nativeDragActive for cross-platform consistency

Performance optimizations:
- Added 50ms debounce to reduce event frequency from ~120/sec to ~20/sec
- Implemented 5-pixel movement threshold for immediate response
- Added window caching with sync.Map to avoid repeated lookups
- Zero-allocation JavaScript calls with pre-allocated 128-byte buffer
- Reduced memory usage to ~18 bytes per event (6x reduction)

Build improvements:
- Updated runtime Taskfile to include documentation generation
- Added docs:build task to runtime build process
- Fixed build order: events → docs → runtime

Documentation:
- Added IMPLEMENTATION.md with optimization details
- Included guidance for Windows implementation

* chore(v3/examples): remove html-dnd-api example

The drag-n-drop example now demonstrates both external file drops
and internal HTML5 drag-and-drop, making this separate example redundant.

* docs(v3): move drag-and-drop implementation details to runtime-internals

- Add drag-and-drop section to contributing/runtime-internals.mdx
- Remove IMPLEMENTATION.md from example (content now in proper docs)
- Covers platform differences, debugging tips, and key files

* fix(v3): remove html-dnd-api from example build list

* fix(v3): remove duplicate json import in application_darwin.go

* fix(v3): address CodeRabbit review feedback

- Fix docs to use app.Window.NewWithOptions() instead of deprecated API
- Add mutex protection to dragOverJSBuffer to prevent race conditions
- Add mutex protection to dragThrottleState fields for thread safety

* docs: add coderabbit pre-push requirement to AGENTS.md

* fix(v3/test): use correct CSS class name file-drop-target-active

* chore(v3/test): remove dnd-test directory

This was a development test file that shouldn't be in the PR.
The drag-n-drop example serves as the proper test case.

* docs(v3): update Windows file drop comment to reflect implemented fix

Remove stale TODO - enableFileDrop flag is now injected in navigationCompleted

* refactor(v3): make handleDragAndDropMessage unexported

Internal method only called by application event loop, not part of public API.
2026-01-04 11:08:29 +11:00

249 lines
9.1 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: Runtime Internals
description: Deep-dive into how Wails v3 boots, runs, and talks to the OS
sidebar:
order: 3
---
The **runtime** is the layer that transforms ordinary Go functions into a
cross-platform desktop application.
This document explains the moving parts you will meet when tracing through the
source code.
---
## 1. Application Lifecycle
| Phase | Code Path | What Happens |
|-------|-----------|--------------|
| **Bootstrap** | `pkg/application/application.go:init()` | Registers build-time data, creates a global `application` singleton. |
| **New()** | `application.New(...)` | Validates `Options`, spins up the **AssetServer**, initialises logging. |
| **Run()** | `application.(*App).Run()` | 1. Calls platform `mainthread.X()` to enter the OS UI thread.<br />2. Boots the **runtime** (`internal/runtime`).<br />3. Blocks until the last window closes or `Quit()` is called. |
| **Shutdown** | `application.(*App).Quit()` | Broadcasts `application:shutdown` event, flushes log, tears down windows & services. |
The lifecycle is strictly **single-entry**: you may create many windows, but the
application object itself is initialised once.
---
## 2. Window Management
### Public API
```go
win := app.Window.New(&application.WebviewWindowOptions{
Title: "Dashboard",
Width: 1280,
Height: 720,
})
win.Show()
```
`NewWebviewWindow` delegates to `internal/runtime/webview_window_*.go` where
platform-specific constructors live:
```
internal/runtime/
├── webview_window_darwin.go
├── webview_window_linux.go
└── webview_window_windows.go
```
Each file:
1. Creates a native webview (WKWebView, WebKitGTK, WebView2).
2. Registers a **Message Processor** callback.
3. Maps Wails events (`WindowResized`, `Focus`, `DropFile`, …) to runtime
event IDs.
Windows are tracked by `screenmanager.go` which offers **query APIs** (all
displays, DPI, active window) and centralises resize / move bookkeeping.
---
## 3. Message Processing Pipeline
The bridge between JavaScript and Go is implemented by the **Message
Processor** family in `pkg/application/messageprocessor_*.go`.
Flow:
1. **JavaScript** calls `window.runtime.Invoke("Greet", "Alice")`.
2. The runtime serialises the request:
`{"t":"c","id":"123","m":"Greet","p":["Alice"]}`
(`t` = type, `c` = call).
3. **Go** receives this JSON via the webview callback.
4. `messageprocessor_call.go` looks up the bound method in the
generated table (`application.bindings.go`) and executes it.
5. The result or error is marshalled back to JS where a `Promise` resolves.
Specialised processors:
| File | Purpose |
|------|---------|
| `messageprocessor_window.go` | Window actions (hide, maximize, …) |
| `messageprocessor_dialog.go` | Native dialogs (`OpenFile`, `MessageBox`, …) |
| `messageprocessor_clipboard.go` | Clipboard read/write |
| `messageprocessor_events.go` | Event subscribe / emit |
| `messageprocessor_browser.go` | Browser navigation, devtools |
Processors are **stateless** they pull everything they need from the
`ApplicationContext` passed with each message.
---
## 4. Events System
Events are namespaced strings dispatched across three layers:
1. **Application events**: global lifecycle (`application:ready`,
`application:shutdown`).
2. **Window events**: per-window (`window:focus`, `window:resize`).
3. **Custom events**: user-defined (`chat:new-message`).
Implementation details:
* Constants are generated from `internal/events/defaults.go`.
* Go side:
`app.On("window:focus", func(e application.WindowEvent) {...})`
* JS side:
`window.runtime.EventsOn("chat:new-message", cb)`
Under the hood both map to the same **EventBus** in
`pkg/application/events.go`.
Subscribers are reference-counted; when a window closes, its callbacks are
auto-unregistered to avoid leaks.
---
## 5. Platform-Specific Implementations
Conditional compilation keeps the public API identical while hiding OS wrinkles.
| Concern | Darwin | Linux | Windows |
|---------|--------|-------|---------|
| Main Thread | `mainthread_darwin.go` (Cgo to Foundation) | `mainthread_linux.go` (GTK) | `mainthread_windows.go` (Win32 `AttachThreadInput`) |
| Dialogs | `dialogs_darwin.*` (NSAlert) | `dialogs_linux.go` (GtkFileChooser) | `dialogs_windows.go` (IFileOpenDialog) |
| Clipboard | `clipboard_darwin.go` | `clipboard_linux.go` | `clipboard_windows.go` |
| Tray Icons | `systemtray_darwin.*` | `systemtray_linux.go` (DBus) | `systemtray_windows.go` (Shell_NotifyIcon) |
Key principles:
* **No Cgo on Windows/Linux** unless unavoidable (performance, portability).
* Use **build tags** (`//go:build darwin && !production`) to keep files readable.
* Expose **capabilities** via `internal/capabilities` so higher layers can
degrade gracefully.
---
## 6. File Guide
| File | Why Youd Touch It |
|------|--------------------|
| `internal/runtime/runtime_*.go` | Change global startup logic, add debug hooks. |
| `internal/runtime/webview_window_*.go` | Implement a new window hint or behaviour. |
| `pkg/application/messageprocessor_*.go` | Add a new bridge command callable from JS. |
| `pkg/application/events_*.go` | Extend built-in event definitions. |
| `internal/assetserver/*` | Tweak dev/production asset handling. |
---
## 7. Debugging Tips
* Launch with `WAILS_LOG_LEVEL=debug` to print every message crossing the bridge.
* Use `wails3 dev -verbose` to see live reload & asset requests.
* On macOS run under `lldb --` to catch Objective-C exceptions early.
* For Windows Chromium issues, enable WebView2 debug logs:
`set WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--remote-debugging-port=9222`
---
## 8. Extending the Runtime
1. Define a **capability flag** in `internal/capabilities`.
2. Implement the feature in each platform file using build tags.
3. Add public API in `pkg/application`.
4. Register a new message type or event if JS needs to call it.
5. Update at least one example in `v3/examples/` exercising the feature.
Follow this checklist and you'll keep the cross-platform contract intact.
---
## 9. Drag-and-Drop
File drag-and-drop uses a **JavaScript-first approach** on all platforms. The native layer intercepts OS drag events, but the actual drop handling and DOM interaction happens in JavaScript.
### Flow
1. User drags files from OS over the Wails window
2. Native layer detects the drag and notifies JavaScript for hover effects
3. User drops files
4. Native layer sends file paths + coordinates to JavaScript
5. JavaScript finds the drop target element (`data-file-drop-target`)
6. JavaScript sends file paths + element details to Go backend
7. Go emits `WindowFilesDropped` event with full context
### Platform Implementations
| Platform | Native Layer | Key Challenge |
|----------|--------------|---------------|
| **Windows** | WebView2's built-in drag support | Coordinates in CSS pixels, no conversion needed |
| **macOS** | NSWindow drag delegates | Convert window-relative to webview-relative coords |
| **Linux** | GTK3 drag signals | Must distinguish file drags from internal HTML5 drags |
### Linux: Distinguishing Drag Types
GTK and WebKit both want to handle drag events. The key is checking the drag target type:
```c
static gboolean is_file_drag(GdkDragContext *context) {
GList *targets = gdk_drag_context_list_targets(context);
for (GList *l = targets; l != NULL; l = l->next) {
GdkAtom atom = GDK_POINTER_TO_ATOM(l->data);
gchar *name = gdk_atom_name(atom);
if (name && g_strcmp0(name, "text/uri-list") == 0) {
g_free(name);
return TRUE; // External file drag
}
g_free(name);
}
return FALSE; // Internal HTML5 drag
}
```
Signal handlers return `FALSE` for internal drags (letting WebKit handle them) and `TRUE` for file drags (handling them ourselves).
### Blocking File Drops
When `EnableFileDrop` is `false`, we still need to prevent the browser from navigating to dropped files. Each platform handles this differently:
- **Windows**: JavaScript calls `preventDefault()` on drag events
- **macOS**: JavaScript calls `preventDefault()` on drag events
- **Linux**: GTK signal handlers intercept and reject file drags at the native level
### Key Files
| File | Purpose |
|------|---------|
| `pkg/application/linux_cgo.go` | GTK drag signal handlers (C code in cgo preamble) |
| `pkg/application/webview_window_darwin.go` | macOS drag delegates |
| `pkg/application/webview_window_windows.go` | WebView2 message handling |
| `internal/runtime/desktop/@wailsio/runtime/src/window.ts` | JavaScript drop handling |
### Debugging
- **Linux**: Add `printf` in C code (remember `fflush(stdout)`)
- **Windows**: Use `globalApplication.debug()`
- **JavaScript**: Check browser console, enable debug mode
Common issues:
1. **Internal HTML5 drag not working**: Native handler intercepting it (return `FALSE` for non-file drags)
2. **Hover effects not showing**: JavaScript handlers not being called
3. **Wrong coordinates**: Check coordinate space conversions
---
You now have a guided tour of the runtime internals. Combine this knowledge with
the **Codebase Layout** map and the **Asset Server** docs to navigate confidently
and make impactful contributions. Happy coding!