Skip to content

Conversation

@WCY-dt
Copy link

@WCY-dt WCY-dt commented Jan 14, 2026

Pull Request: WebviewPanel - Multiple Webviews in a Single Window

Summary

This PR implements the WebviewPanel feature, allowing multiple independent webview containers to be embedded within a single window. This addresses #1997 and provides functionality similar to Electron's BrowserView/WebContentsView.

Motivation

Many desktop applications require displaying multiple web views in a single window:

  • IDE-like layouts: Editor panel + preview panel + terminal panel
  • Email clients: Folder tree + message list + message preview
  • News aggregators: Article list + external website viewer
  • Browser-style apps: Tabs + content area

Previously, this was only achievable using iframes, which have significant limitations:

  • Many sites block iframe embedding via X-Frame-Options
  • Cannot execute JavaScript in the iframe's context
  • Cannot read properties like page title
  • Cross-origin restrictions

WebviewPanel solves these issues by providing true native webview embedding.

Design Decisions

API Design Philosophy

The API follows the design proposed in #1997 by @SamHennessy:

  1. Absolute Positioning: Panels use X, Y, Width, Height coordinates (like absolutely positioned divs)
  2. Z-Index Stacking: Higher z-index panels appear on top
  3. Parent-Child Relationship: Panels are owned by and positioned relative to their parent window

Why This Design?

Rather than complex flex/grid layouts, we chose simple absolute positioning because:

  • Most straightforward to implement across all platforms
  • Gives developers complete control
  • Easy to understand and debug
  • Layout helpers provide convenience for common patterns

Naming: "Panel" vs "Container" vs "Webview"

We chose "WebviewPanel" because:

  • "Panel" is familiar from UI frameworks (Qt, GTK, WinForms)
  • "Webview" clarifies it's not just a native panel
  • Distinguishes from WebviewWindow (top-level window)

Implementation

Core Files

File Purpose
webview_panel.go Core WebviewPanel struct, methods, and interface
webview_panel_options.go Configuration options with anchor types
webview_panel_windows.go Windows/WebView2 implementation
webview_panel_darwin.go macOS/WKWebView implementation
webview_panel_linux.go Linux/WebKit2GTK implementation
messageprocessor_panel.go Frontend API message handling
panel.ts TypeScript runtime API

Platform Implementations

Windows: Creates a child HWND window with WebView2/Chromium embedded
macOS: Adds WKWebView as a subview of the window's content view
Linux: Uses GtkFixed container with WebKitWebView child widgets

API Overview

// Create a panel
panel := window.NewPanel(application.WebviewPanelOptions{
    Name:   "content",
    X:      200, Y: 50,
    Width:  800, Height: 600,
    URL:    "https://example.com",
})

// Layout helpers
sidebar := window.NewPanel(opts).DockLeft(200)
content := window.NewPanel(opts).FillBeside(sidebar, "right")

// Manipulation
panel.SetURL("https://wails.io")
panel.SetHTML("<h1>Hello</h1>")
panel.ExecJS("console.log('Hi!')")
panel.Show()
panel.Hide()
panel.SetZoom(1.5)
panel.Focus()
panel.Destroy()
// TypeScript
const panel = Panel.Get("content");
await panel.SetURL("https://wails.io");
await panel.ExecJS("document.title");

Testing

  • Windows build passes
  • macOS build passes
  • Linux build passes
  • Example application works on all platforms
  • TypeScript runtime compiles

Breaking Changes

None. This is a new feature that doesn't affect existing APIs.

Checklist

  • Code compiles without errors
  • Added example application (examples/webview-panel/)
  • TypeScript runtime API added
  • Documentation in README
  • Tests
  • Documentation website update

Related Issues

Future Enhancements

Potential improvements for future PRs:

  • Panel resize events/callbacks
  • Inter-panel communication API
  • Panel navigation events
  • Automatic layout management (flex/grid-style)
  • Panel permission settings (disable JS, cookies, etc.)

Summary by CodeRabbit

  • New Features

    • WebviewPanel: embed and control multiple independent panels in a window (position, size, z-order, visibility, zoom, focus, devtools).
    • Layout helpers: DockLeft/Right/Top/Bottom, FillWindow, FillBeside for easy panel arrangement.
    • Frontend API exposure to manage panels from JavaScript/TypeScript.
  • Platform Support

    • Native implementations for macOS, Windows, Linux; Android/iOS provide build-time stubs.
  • Documentation & Examples

    • Added comprehensive README and example demonstrating multi-panel usage and APIs.

✏️ Tip: You can customize this high-level summary in your review settings.

This implements the feature requested in wailsapp#1997 - allowing multiple webview
containers (panels) within a single window.

## Features

- WebviewPanel: A new type for embedded webview panels within a window
- Cross-platform support: Windows (WebView2), macOS (WKWebView), Linux (WebKit2GTK)
- Absolute positioning with X, Y, Width, Height coordinates
- Z-index support for panel stacking order
- URL and HTML content loading
- JavaScript execution in panel context
- Show/Hide/Focus/Zoom controls
- Developer tools support
- Layout helper methods: DockLeft, DockRight, DockTop, DockBottom, FillWindow, FillBeside

## API

Go:
```go
panel := window.NewPanel(application.WebviewPanelOptions{
    Name:   "sidebar",
    X:      0, Y: 50,
    Width:  200, Height: 600,
    URL:    "https://example.com",
})
panel.SetBounds(rect).SetURL(url).Show()
```

TypeScript:
```typescript
const panel = Panel.Get("sidebar");
await panel.SetURL("https://wails.io");
```

## Files Added

- webview_panel.go - Core panel struct and methods
- webview_panel_options.go - Panel configuration options
- webview_panel_windows.go - Windows implementation
- webview_panel_darwin.go - macOS implementation
- webview_panel_linux.go - Linux implementation
- webview_panel_android.go - Android stub
- webview_panel_ios.go - iOS stub
- messageprocessor_panel.go - Frontend-to-backend communication
- panel.ts - TypeScript runtime API
- examples/webview-panel/ - Demo application

Closes wailsapp#1997
Copilot AI review requested due to automatic review settings January 14, 2026 09:38
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 14, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds WebView Panel support: multi-panel webview containers inside a single window with Go core abstractions, platform-specific implementations (Windows, macOS, Linux, iOS/Android stubs), TypeScript runtime bindings, message-processing plumbing, examples, and docs.

Changes

Cohort / File(s) Summary
Documentation & Example
v3/examples/webview-panel/README.md, v3/examples/webview-panel/main.go
New README and example app demonstrating panel creation, layout helpers, API usage, and sample sidebar/content panels.
Runtime TypeScript API
v3/internal/runtime/desktop/@wailsio/runtime/src/index.ts, v3/internal/runtime/desktop/@wailsio/runtime/src/panel.ts, v3/internal/runtime/desktop/@wailsio/runtime/src/runtime.ts
Exposes Panel in runtime exports; adds panel.ts implementing Panel class and Bounds interface; registers Panel object id in runtime mapping.
Message Processing
v3/pkg/application/messageprocessor.go, v3/pkg/application/messageprocessor_panel.go
Adds panelRequest routing and implements processPanelMethod with handlers for ~18 panel methods (bounds, z-index, URL/HTML, JS exec, reload, visibility, zoom, focus, DevTools, destroy, name).
Core Panel Abstraction & Options
v3/pkg/application/webview_panel.go, v3/pkg/application/webview_panel_options.go
New WebviewPanel type, constructor, lifecycle and manipulation APIs, layout helpers (FillWindow, Dock*, FillBeside), and WebviewPanelOptions + AnchorType definitions.
Window Integration
v3/pkg/application/webview_window.go
Adds panel tracking to WebviewWindow (map + lock), NewPanel/GetPanel/GetPanels/RemovePanel APIs, panel startup on Run(), and cleanup on window close.
Platform Implementations (Linux)
v3/pkg/application/webview_panel_linux.go
GTK/WebKit-based implementation via cgo providing create/destroy, bounds, URL/HTML, JS, zoom, DevTools, visibility, focus.
Platform Implementations (Windows)
v3/pkg/application/webview_panel_windows.go
WebView2-based Windows implementation: child window embedding, DIP↔physical scaling, content/JS/CSS injection, zoom, DevTools, focus, lifecycle.
Platform Implementations (macOS)
v3/pkg/application/webview_panel_darwin.go
WKWebView implementation via CGO/Objective‑C bridges: creation, navigation callback, bounds, content, JS, zoom, visibility, focus.
Platform Stubs (iOS/Android)
v3/pkg/application/webview_panel_ios.go, v3/pkg/application/webview_panel_android.go
Platform stubs implementing the panel interface as no-ops/defaults, allowing compilation on those targets.

Sequence Diagram(s)

sequenceDiagram
    participant Frontend as Frontend (TypeScript)
    participant Caller as Runtime Caller
    participant MP as Message Processor
    participant Impl as Platform Impl

    Frontend->>Caller: Panel.SetBounds({x,y,w,h})
    Caller->>MP: runtime call (panelRequest, PanelSetBounds)
    MP->>MP: processPanelMethod -> resolve panel
    MP->>Impl: setBounds(bounds)
    Impl-->>MP: success
    MP-->>Caller: void
    Caller-->>Frontend: Promise resolved
Loading
sequenceDiagram
    participant App as Go App
    participant Window as WebviewWindow
    participant Panel as WebviewPanel
    participant Impl as Platform Impl

    App->>Window: NewPanel(options)
    Window->>Panel: create WebviewPanel
    alt Window running
        Panel->>Impl: create native panel
        Impl-->>Panel: created
    else
        Panel->>Window: queued for runPanels
    end
    Window-->>App: *WebviewPanel
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

Enhancement, Documentation, go, bindings, templates, Windows, MacOS, Linux, v3-alpha, size:XXL

Suggested reviewers

  • leaanthony

Poem

🐰 In burrows bright I code and mull,

Panels stack and dock and lull,
Left and right and full-screen view,
A hopping rabbit says: hooray—new! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: adding WebviewPanel feature for embedding multiple webviews in a window.
Description check ✅ Passed The PR description is comprehensive, addressing motivation, design decisions, implementation details, API overview, testing status, and breaking changes. However, the provided PR description checklist shows incomplete items (e.g., macOS/Linux builds pending, documentation website update TODO), but the description itself is well-structured and mostly complete.
Linked Issues check ✅ Passed The PR successfully implements all primary objectives from #1997 and #3315: multiple webviews in a window with URL setting/changing, JS execution, show/hide, and layout positioning via layout helpers (DockLeft, DockRight, FillBeside).
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the WebviewPanel feature: core platform implementations, message processing, TypeScript runtime API, example application, and supporting infrastructure. No unrelated or out-of-scope modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
v3/pkg/application/messageprocessor.go (1)

169-202: Add missing panelRequest case in logRuntimeCall for complete panel request logging.

The logRuntimeCall switch statement doesn't handle panelRequest, which is a valid request type processed elsewhere in the codebase. Panel-related runtime calls will log with an empty methodName, reducing debugging visibility.

Fix
	case androidRequest:
		methodName = androidMethodNames[req.Method]
+	case panelRequest:
+		methodName = panelMethodNames[req.Method]
	}

	m.Debug("Runtime call:", "method", objectName+"."+methodName, "args", req.Args.String())
🤖 Fix all issues with AI agents
In `@v3/pkg/application/webview_panel_darwin.go`:
- Around line 45-77: The WebviewPanelDelegate instance created by
WebviewPanelDelegate* delegate = [[WebviewPanelDelegate alloc] init]; is missing
an autorelease call and will leak because WKUserContentController retains script
message handlers; add an autorelease for delegate (i.e., call [delegate
autorelease]; immediately after its init) to match the webview_window_darwin.go
pattern so the initial alloc is balanced and the delegate is released when the
autorelease pool drains.

In `@v3/pkg/application/webview_panel_linux.go`:
- Around line 186-190: The panel is being packed into the parent vbox (using
p.parent.vbox and gtk_box_pack_start) which places panels in the vertical layout
rather than layered overlays; change the window/container root to use a
GtkOverlay in windowNew() and add the main webview as the base child and panels
(p.fixed) as overlay children via gtk_overlay_add_overlay so
panel_fixed_put(p.fixed, p.webview, ...) positions remain absolute and panels
float above the webview; update any places that assume a vbox root (e.g., where
p.parent.vbox is set or used) to reference the new overlay container.

In `@v3/pkg/application/webview_panel_windows.go`:
- Around line 192-198: The CSS injection into JS in the navigation callback
currently only escapes single quotes and will break on backslashes or newlines;
update the code around p.execJS(...) that builds js to produce a properly
escaped JS string (e.g., use strconv.Quote or json.Marshal on
p.panel.options.CSS to get a safely quoted string) and then insert that quoted
value into the generated script so backslashes and newlines are preserved and
the injected style is robust.
- Around line 162-177: The CSS injection is unsafe because loadHTMLWithScripts
builds a JS string by only escaping double quotes; instead properly produce a
JS-safe string for p.panel.options.CSS (and similarly for JS/HTML if needed) by
using a robust string-quoting/escaping function (e.g. strconv.Quote or
encoding/json.Marshal) rather than manual ReplaceAll; update loadHTMLWithScripts
in windowsPanelImpl to call that quoting function on p.panel.options.CSS, insert
the quoted value into the injected script passed to p.chromium.Init, and ensure
backslashes, newlines, carriage returns and other special characters are escaped
so the injected JS cannot be broken out of.
- Around line 204-210: The destroy() method on windowsPanelImpl currently
destroys the native window without notifying the embedded Chromium instance;
call p.chromium.ShuttingDown() (guarded by a nil check) before invoking
w32.DestroyWindow to mirror the cleanup used in the main window; ensure you call
p.chromium.ShuttingDown() only if p.chromium != nil, then proceed to
DestroyWindow(p.hwnd) and clear p.hwnd and p.chromium as before.

In `@v3/pkg/application/webview_panel.go`:
- Around line 355-363: markRuntimeLoaded mutates runtimeLoaded and iterates
pendingJS without synchronization causing races with ExecJS and other accessors;
add a mutex on WebviewPanel (e.g., p.mu sync.Mutex or sync.RWMutex) and use it
to guard reads/writes to runtimeLoaded and pendingJS: in markRuntimeLoaded
acquire lock, set p.runtimeLoaded = true, copy p.pendingJS to a local slice and
set p.pendingJS = nil, then release the lock and iterate/local-execute the
copied slice (call p.ExecJS or execute directly) outside the lock; likewise
update ExecJS to acquire the mutex to check p.runtimeLoaded and append to
p.pendingJS when not loaded (or read the flag and decide to execute) ensuring no
long-running execution happens while holding the lock.
- Around line 222-233: Concurrent calls to ExecJS can race on the pendingJS
slice; add a mutex in the WebviewPanel struct (e.g., pendingMu sync.Mutex) and
use it to protect all accesses to pendingJS in ExecJS (lock before
checking/append/unlock) and wherever pendingJS is drained or read (e.g., the
code path that sets runtimeLoaded and invokes pending JS), ensuring you still
call impl.execJS under InvokeSync and preserve existing checks (p.impl,
p.isDestroyed, p.runtimeLoaded).

In `@v3/pkg/application/webview_window.go`:
- Around line 1636-1645: The runPanels method currently holds w.panelsLock.RLock
while iterating and calling panel.run(), which may deadlock because panel.run()
(and its InvokeSync) can contend for other locks or writers; change runPanels
(method on WebviewWindow) to snapshot the relevant panels while holding the
RLock into a local slice (filtering by panel.impl == nil if desired), release
the RLock, then iterate over that snapshot and call panel.run() outside the lock
so panel.run/InvokeSync cannot block writers trying to acquire panelsLock.
- Around line 1647-1660: The window's panels are never cleaned up because
destroyAllPanels() is never invoked; update the WindowClosing event handler to
call w.destroyAllPanels() before or as part of the window shutdown sequence
(e.g., alongside any existing cleanup/close logic in the WindowClosing handler)
so panels are destroyed and resources released; ensure you call the method on
the correct receiver (w.destroyAllPanels()) and keep the call synchronous with
the existing lock/unlock semantics already implemented by destroyAllPanels().
🧹 Nitpick comments (10)
v3/pkg/application/webview_panel_options.go (1)

82-100: AnchorType constant values may be unexpected due to iota positioning.

The iota resets at the start of the const block, so:

  • AnchorNone = 0 (iota=0)
  • AnchorTop = 1 << iota = 1 << 1 = 2 (iota=1)
  • AnchorBottom = 1 << 2 = 4
  • AnchorLeft = 1 << 3 = 8
  • AnchorRight = 1 << 4 = 16

This works correctly for bitwise operations, but if you intended the typical pattern (1, 2, 4, 8), consider restructuring:

♻️ Optional: Standard bitflag pattern
 const (
 	// AnchorNone - panel uses absolute positioning only (default)
 	AnchorNone AnchorType = 0
 
-	// AnchorTop - panel maintains distance from top edge
-	AnchorTop AnchorType = 1 << iota
+	// AnchorTop - panel maintains distance from top edge
+	AnchorTop AnchorType = 1 << iota // iota=0 here, so 1
 
-	// AnchorBottom - panel maintains distance from bottom edge
-	AnchorBottom
+	// AnchorBottom - panel maintains distance from bottom edge
+	AnchorBottom // 2
 
-	// AnchorLeft - panel maintains distance from left edge
-	AnchorLeft
+	// AnchorLeft - panel maintains distance from left edge
+	AnchorLeft // 4
 
-	// AnchorRight - panel maintains distance from right edge
-	AnchorRight
+	// AnchorRight - panel maintains distance from right edge
+	AnchorRight // 8
 
 	// AnchorFill - panel fills the entire window (anchored to all edges)
 	AnchorFill AnchorType = AnchorTop | AnchorBottom | AnchorLeft | AnchorRight
 )

Or keep as-is if the current values (2, 4, 8, 16) are intentional.

v3/examples/webview-panel/main.go (1)

201-203: Consider using a shared pointer helper if available.

The boolPtr helper is a common pattern. If the application package provides a generic pointer utility (e.g., application.Ptr[T] or similar), consider using it for consistency. Otherwise, this inline helper is fine for an example.

v3/pkg/application/webview_panel_darwin.go (1)

252-257: Empty navigation callback could be removed or implemented.

panelNavigationCompleted is exported and called from Objective-C on navigation completion but performs no action. Consider either implementing the callback (e.g., to update panel state) or removing the delegate method call to avoid unnecessary cross-language overhead.

v3/pkg/application/webview_panel_windows.go (1)

254-257: Unused variable clientPoint.

The clientPoint variable is declared and its fields are set to 0, but the values are never read. The actual conversion uses w32.ClientToScreen directly.

Remove unused variable
 	// Calculate position relative to parent's client area
-	var clientPoint w32.POINT
-	clientPoint.X = 0
-	clientPoint.Y = 0
 	parentClientX, parentClientY := w32.ClientToScreen(p.parent.hwnd, 0, 0)
v3/internal/runtime/desktop/@wailsio/runtime/src/panel.ts (2)

95-103: Consider adding input validation for bounds.

The SetBounds and GetBounds methods don't validate that bounds values are non-negative. While the backend may handle this, frontend validation could provide better error messages.

Also applies to: 110-114


277-281: Name() method makes unnecessary backend call.

The panel name is already stored locally in this[panelNameSym]. Consider returning it directly instead of making a round-trip to the backend.

Suggested optimization
     Name(): Promise<string> {
-        return this[callerSym](NameMethod, {
-            panel: this[panelNameSym]
-        });
+        return Promise.resolve(this[panelNameSym]);
     }
v3/pkg/application/webview_panel_android.go (1)

1-33: Clean stub implementation for unsupported platform.

The Android stub correctly implements all webviewPanelImpl interface methods with sensible defaults. Consider adding a log warning in create() to inform developers that panels are not supported on Android.

Optional: Add warning log
-func (p *androidPanelImpl) create()               {}
+func (p *androidPanelImpl) create() {
+	globalApplication.warning("WebviewPanel is not yet supported on Android")
+}
v3/pkg/application/messageprocessor_panel.go (1)

31-50: panelMethodNames map is defined but never used.

The map could improve the error message in the default case for unknown methods.

Use panelMethodNames in error message
 	default:
-		return nil, fmt.Errorf("unknown panel method: %d", req.Method)
+		methodName, ok := panelMethodNames[req.Method]
+		if ok {
+			return nil, fmt.Errorf("unhandled panel method: %s (%d)", methodName, req.Method)
+		}
+		return nil, fmt.Errorf("unknown panel method: %d", req.Method)
 	}

Also applies to: 184-186

v3/pkg/application/webview_panel.go (2)

44-51: Redundant locking for atomic operation.

panelIDLock mutex is used to protect an atomic.AddUint32 operation. The atomic operation is already thread-safe, making the mutex unnecessary.

Simplify ID generation
 var panelID uint32
-var panelIDLock sync.Mutex

 func getNextPanelID() uint {
-	panelIDLock.Lock()
-	defer panelIDLock.Unlock()
 	return uint(atomic.AddUint32(&panelID, 1))
 }

420-462: FillBeside uses magic strings for direction.

Consider using a typed constant for the direction parameter to provide compile-time safety and better documentation.

Optional: Use typed direction constant
type DockDirection string

const (
    DockDirectionRight DockDirection = "right"
    DockDirectionLeft  DockDirection = "left"
    DockDirectionBelow DockDirection = "below"
    DockDirectionAbove DockDirection = "above"
)

func (p *WebviewPanel) FillBeside(refPanel *WebviewPanel, direction DockDirection) *WebviewPanel {
    // ...
}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d7d6720 and 838b159.

📒 Files selected for processing (15)
  • v3/examples/webview-panel/README.md
  • v3/examples/webview-panel/main.go
  • v3/internal/runtime/desktop/@wailsio/runtime/src/index.ts
  • v3/internal/runtime/desktop/@wailsio/runtime/src/panel.ts
  • v3/internal/runtime/desktop/@wailsio/runtime/src/runtime.ts
  • v3/pkg/application/messageprocessor.go
  • v3/pkg/application/messageprocessor_panel.go
  • v3/pkg/application/webview_panel.go
  • v3/pkg/application/webview_panel_android.go
  • v3/pkg/application/webview_panel_darwin.go
  • v3/pkg/application/webview_panel_ios.go
  • v3/pkg/application/webview_panel_linux.go
  • v3/pkg/application/webview_panel_options.go
  • v3/pkg/application/webview_panel_windows.go
  • v3/pkg/application/webview_window.go
🧰 Additional context used
🧠 Learnings (12)
📓 Common learnings
Learnt from: APshenkin
Repo: wailsapp/wails PR: 4480
File: v2/internal/frontend/desktop/darwin/message.h:17-19
Timestamp: 2025-08-08T09:13:16.916Z
Learning: In Wails v2 bindings origin verification, processBindingMessage intentionally has different signatures across platforms: Darwin includes an isMainFrame bool (WKWebKit provides it), Linux uses two params (message, source) as WebKitGTK doesn’t expose main-frame info there, and Windows handles origin checks in Go via WebView2 sender/args without a C bridge. This divergence is acceptable/expected per maintainer (APshenkin).
Learnt from: nixpare
Repo: wailsapp/wails PR: 3763
File: v3/pkg/application/webview_panel_darwin.go:88-89
Timestamp: 2024-09-21T09:56:48.126Z
Learning: Safety checks for `p.nsPanel` are performed in the `SetFloating` method of `WebviewPanel`, following the `WebviewWindow` and `macosWebviewWindow` implementations and code style.
Learnt from: Sammy-T
Repo: wailsapp/wails PR: 4570
File: v2/internal/frontend/desktop/linux/window_webkit6.go:97-108
Timestamp: 2025-10-17T23:16:11.570Z
Learning: For webkit_6/GTK4 builds in v2/internal/frontend/desktop/linux/window_webkit6.go, GTK widget creation should not be wrapped in invokeOnMainThread. The activation mechanism (activateWg + onActivate export) already handles thread safety, and additional wrapping would cause issues.
Learnt from: leaanthony
Repo: wailsapp/wails PR: 4783
File: v3/pkg/events/events.go:72-100
Timestamp: 2025-12-13T19:52:13.812Z
Learning: In Wails v3, the linux:WindowLoadChanged event was intentionally removed as a breaking change and replaced with four granular WebKit2 load events: linux:WindowLoadStarted, linux:WindowLoadRedirected, linux:WindowLoadCommitted, and linux:WindowLoadFinished. Users should migrate to linux:WindowLoadFinished for detecting when the WebView has finished loading.
📚 Learning: 2025-12-29T08:02:15.134Z
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4839
File: docs/src/content/docs/reference/window.mdx:616-620
Timestamp: 2025-12-29T08:02:15.134Z
Learning: In Wails v3, the correct API for creating windows is `app.Window.New()` and `app.Window.NewWithOptions(...)`, not `app.NewWebviewWindow()` or `app.NewWebviewWindowWithOptions(...)`. The Application struct exposes a Window field of type *WindowManager that provides these methods.

Applied to files:

  • v3/examples/webview-panel/main.go
  • v3/pkg/application/webview_panel_options.go
  • v3/examples/webview-panel/README.md
  • v3/pkg/application/webview_panel_ios.go
  • v3/pkg/application/webview_window.go
  • v3/pkg/application/webview_panel_windows.go
  • v3/pkg/application/webview_panel_darwin.go
  • v3/pkg/application/webview_panel.go
  • v3/pkg/application/webview_panel_linux.go
📚 Learning: 2024-09-30T06:13:46.595Z
Learnt from: leaanthony
Repo: wailsapp/wails PR: 3763
File: v3/examples/window/main.go:472-475
Timestamp: 2024-09-30T06:13:46.595Z
Learning: In `v3/examples/window/main.go`, `time.Sleep` is used within a goroutine and does not block the UI thread.

Applied to files:

  • v3/examples/webview-panel/main.go
  • v3/examples/webview-panel/README.md
📚 Learning: 2025-10-17T23:16:11.570Z
Learnt from: Sammy-T
Repo: wailsapp/wails PR: 4570
File: v2/internal/frontend/desktop/linux/window_webkit6.go:97-108
Timestamp: 2025-10-17T23:16:11.570Z
Learning: For webkit_6/GTK4 builds in v2/internal/frontend/desktop/linux/window_webkit6.go, GTK widget creation should not be wrapped in invokeOnMainThread. The activation mechanism (activateWg + onActivate export) already handles thread safety, and additional wrapping would cause issues.

Applied to files:

  • v3/examples/webview-panel/main.go
  • v3/pkg/application/webview_panel_ios.go
  • v3/pkg/application/webview_window.go
  • v3/pkg/application/webview_panel_windows.go
  • v3/pkg/application/webview_panel_darwin.go
  • v3/pkg/application/webview_panel.go
  • v3/pkg/application/webview_panel_linux.go
📚 Learning: 2024-10-08T22:11:37.054Z
Learnt from: leaanthony
Repo: wailsapp/wails PR: 3763
File: v3/internal/commands/appimage_testfiles/main.go:295-299
Timestamp: 2024-10-08T22:11:37.054Z
Learning: In `v3/internal/commands/appimage_testfiles/main.go`, `time.Sleep` is used within a goroutine and does not block the UI thread.

Applied to files:

  • v3/examples/webview-panel/main.go
📚 Learning: 2024-09-20T23:34:29.841Z
Learnt from: nixpare
Repo: wailsapp/wails PR: 3763
File: v3/examples/keybindings/main.go:16-17
Timestamp: 2024-09-20T23:34:29.841Z
Learning: In the codebase, `application.Options.KeyBindings` uses the `application.Window` type, whereas `application.WebviewWindowOptions.KeyBindings` uses `*application.WebviewWindow`. This is intentional and acceptable.

Applied to files:

  • v3/examples/webview-panel/main.go
  • v3/pkg/application/webview_panel_options.go
  • v3/examples/webview-panel/README.md
  • v3/pkg/application/webview_window.go
  • v3/pkg/application/webview_panel_windows.go
  • v3/pkg/application/webview_panel_darwin.go
  • v3/pkg/application/webview_panel.go
📚 Learning: 2026-01-04T08:01:00.038Z
Learnt from: symball
Repo: wailsapp/wails PR: 4853
File: v2/internal/system/system.go:128-152
Timestamp: 2026-01-04T08:01:00.038Z
Learning: In v2/internal/system/system.go, shared functions like checkLibrary are defined without build tags but are only invoked from platform-specific files (system_linux.go, system_windows.go, system_darwin.go) that have build constraints. Reviewers should ensure there are no runtime OS checks in system.go and that platform-specific behavior is controlled via build tags. If runtime switches exist, remove them in favor of compile-time platform constraints to reduce overhead and improve correctness.

Applied to files:

  • v3/examples/webview-panel/main.go
  • v3/pkg/application/webview_panel_options.go
  • v3/pkg/application/messageprocessor.go
  • v3/pkg/application/webview_panel_ios.go
  • v3/pkg/application/messageprocessor_panel.go
  • v3/pkg/application/webview_panel_android.go
  • v3/pkg/application/webview_window.go
  • v3/pkg/application/webview_panel_windows.go
  • v3/pkg/application/webview_panel_darwin.go
  • v3/pkg/application/webview_panel.go
  • v3/pkg/application/webview_panel_linux.go
📚 Learning: 2024-10-08T22:11:37.054Z
Learnt from: nixpare
Repo: wailsapp/wails PR: 3763
File: v3/pkg/application/application_options.go:93-93
Timestamp: 2024-10-08T22:11:37.054Z
Learning: The `KeyBindings` property of `application.Option` uses the `application.Window` type, while `application.WebviewWindowOptions` still accepts `*application.WebviewWindow`, and this is intentional.

Applied to files:

  • v3/pkg/application/webview_panel_options.go
📚 Learning: 2025-01-15T22:33:30.639Z
Learnt from: fbbdev
Repo: wailsapp/wails PR: 4001
File: v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.ts:0-0
Timestamp: 2025-01-15T22:33:30.639Z
Learning: In the Wails framework's TypeScript bindings, the import path "/wails/runtime.js" is intentionally absolute and should not be changed to a relative path.

Applied to files:

  • v3/internal/runtime/desktop/@wailsio/runtime/src/index.ts
📚 Learning: 2024-09-21T09:56:48.126Z
Learnt from: nixpare
Repo: wailsapp/wails PR: 3763
File: v3/pkg/application/webview_panel_darwin.go:88-89
Timestamp: 2024-09-21T09:56:48.126Z
Learning: Safety checks for `p.nsPanel` are performed in the `SetFloating` method of `WebviewPanel`, following the `WebviewWindow` and `macosWebviewWindow` implementations and code style.

Applied to files:

  • v3/examples/webview-panel/README.md
  • v3/internal/runtime/desktop/@wailsio/runtime/src/panel.ts
  • v3/pkg/application/webview_window.go
  • v3/pkg/application/webview_panel_windows.go
  • v3/pkg/application/webview_panel_darwin.go
  • v3/pkg/application/webview_panel.go
  • v3/pkg/application/webview_panel_linux.go
📚 Learning: 2025-08-08T09:13:16.916Z
Learnt from: APshenkin
Repo: wailsapp/wails PR: 4480
File: v2/internal/frontend/desktop/darwin/message.h:17-19
Timestamp: 2025-08-08T09:13:16.916Z
Learning: In Wails v2 bindings origin verification, processBindingMessage intentionally has different signatures across platforms: Darwin includes an isMainFrame bool (WKWebKit provides it), Linux uses two params (message, source) as WebKitGTK doesn’t expose main-frame info there, and Windows handles origin checks in Go via WebView2 sender/args without a C bridge. This divergence is acceptable/expected per maintainer (APshenkin).

Applied to files:

  • v3/pkg/application/webview_panel_windows.go
  • v3/pkg/application/webview_panel_darwin.go
  • v3/pkg/application/webview_panel_linux.go
📚 Learning: 2025-12-13T19:52:13.812Z
Learnt from: leaanthony
Repo: wailsapp/wails PR: 4783
File: v3/pkg/events/events.go:72-100
Timestamp: 2025-12-13T19:52:13.812Z
Learning: In Wails v3, the linux:WindowLoadChanged event was intentionally removed as a breaking change and replaced with four granular WebKit2 load events: linux:WindowLoadStarted, linux:WindowLoadRedirected, linux:WindowLoadCommitted, and linux:WindowLoadFinished. Users should migrate to linux:WindowLoadFinished for detecting when the WebView has finished loading.

Applied to files:

  • v3/pkg/application/webview_panel_windows.go
  • v3/pkg/application/webview_panel_darwin.go
  • v3/pkg/application/webview_panel_linux.go
🧬 Code graph analysis (10)
v3/examples/webview-panel/main.go (3)
v3/pkg/application/webview_window_options.go (3)
  • WebviewWindowOptions (32-145)
  • BackgroundType (176-176)
  • BackgroundTypeSolid (179-179)
v3/pkg/application/webview_panel.go (1)
  • NewPanel (75-105)
v3/pkg/application/webview_panel_options.go (1)
  • WebviewPanelOptions (5-76)
v3/pkg/application/webview_panel_options.go (2)
v3/internal/runtime/desktop/@wailsio/runtime/src/panel.ts (1)
  • Name (277-281)
v3/internal/runtime/desktop/@wailsio/runtime/src/window.ts (2)
  • Width (597-599)
  • Height (309-311)
v3/pkg/application/webview_panel_ios.go (1)
v3/pkg/application/webview_panel.go (1)
  • WebviewPanel (57-71)
v3/internal/runtime/desktop/@wailsio/runtime/src/panel.ts (2)
v3/internal/runtime/desktop/@wailsio/runtime/src/index.ts (2)
  • Panel (41-41)
  • objectNames (63-63)
v3/internal/runtime/desktop/@wailsio/runtime/src/runtime.ts (2)
  • newRuntimeCaller (98-102)
  • objectNames (19-34)
v3/pkg/application/webview_panel_android.go (1)
v3/pkg/application/webview_panel.go (1)
  • WebviewPanel (57-71)
v3/pkg/application/webview_window.go (4)
v3/pkg/application/webview_panel.go (2)
  • WebviewPanel (57-71)
  • NewPanel (75-105)
v3/pkg/application/webview_panel_options.go (1)
  • WebviewPanelOptions (5-76)
v3/pkg/application/mainthread.go (1)
  • InvokeSync (23-32)
v3/internal/runtime/desktop/@wailsio/runtime/src/panel.ts (1)
  • Destroy (266-270)
v3/pkg/application/webview_panel_windows.go (4)
v3/pkg/application/webview_panel.go (1)
  • WebviewPanel (57-71)
v3/pkg/application/screenmanager.go (2)
  • DipToPhysicalRect (853-855)
  • PhysicalToDipRect (857-859)
v3/pkg/w32/window.go (1)
  • MustStringToUTF16Ptr (173-180)
v3/internal/assetserver/assetserver.go (1)
  • GetStartURL (149-175)
v3/pkg/application/webview_panel_darwin.go (2)
v3/pkg/application/webview_panel.go (1)
  • WebviewPanel (57-71)
v3/internal/runtime/desktop/@wailsio/runtime/src/window.ts (2)
  • Width (597-599)
  • Height (309-311)
v3/pkg/application/webview_panel.go (4)
v3/pkg/application/webview_panel_options.go (1)
  • WebviewPanelOptions (5-76)
v3/pkg/application/webview_window.go (1)
  • WebviewWindow (142-180)
v3/internal/runtime/desktop/@wailsio/runtime/src/panel.ts (18)
  • Name (277-281)
  • SetBounds (95-103)
  • Bounds (36-45)
  • SetZIndex (121-126)
  • SetURL (133-138)
  • SetHTML (145-150)
  • ExecJS (157-162)
  • Reload (167-171)
  • ForceReload (176-180)
  • Show (185-189)
  • Hide (194-198)
  • IsVisible (205-209)
  • SetZoom (216-221)
  • GetZoom (228-232)
  • OpenDevTools (257-261)
  • Focus (237-241)
  • IsFocused (248-252)
  • Destroy (266-270)
v3/pkg/application/mainthread.go (2)
  • InvokeSync (23-32)
  • InvokeSyncWithResult (34-44)
v3/pkg/application/webview_panel_linux.go (2)
v3/pkg/application/webview_panel.go (1)
  • WebviewPanel (57-71)
v3/internal/runtime/desktop/@wailsio/runtime/src/window.ts (2)
  • Width (597-599)
  • Height (309-311)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: CodeQL analysis (go)
  • GitHub Check: Agent
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (28)
v3/pkg/application/webview_panel_options.go (1)

3-76: Well-structured options type with clear documentation.

The WebviewPanelOptions struct is well-designed:

  • Pointer types for Visible and DevToolsEnabled appropriately distinguish "unset" from false
  • Defaults are clearly documented in comments
  • Field naming is consistent with WebviewWindowOptions
v3/internal/runtime/desktop/@wailsio/runtime/src/runtime.ts (1)

32-33: Object IDs correctly align with Go backend.

The Android: 12 and Panel: 13 entries match the corresponding constants in messageprocessor.go (androidRequest = 12, panelRequest = 13), ensuring proper IPC routing.

v3/examples/webview-panel/main.go (1)

9-198: Well-structured example demonstrating the WebviewPanel feature.

The example effectively showcases:

  • Creating a main window with embedded HTML header
  • Two panels with different content types (HTML sidebar, external URL content)
  • Explicit coordinate-based positioning
  • Commented alternatives showing layout helpers

The panel dimensions (Height: 650 + Y: 50 = 700) correctly fit within the window height (700).

v3/pkg/application/messageprocessor.go (3)

30-31: Constant correctly added for panel request routing.

The panelRequest = 13 constant is properly defined and sequential with other request types.


47-48: Object name mapping correctly added.

The panelRequest: "Panel" entry enables proper object name resolution for logging.


132-133: Panel request dispatch correctly integrated.

The routing to m.processPanelMethod(req, targetWindow) follows the established pattern for other request types.

v3/internal/runtime/desktop/@wailsio/runtime/src/index.ts (2)

29-29: Panel import correctly added.

The named import { Panel } from "./panel.js" follows the established pattern for class/object exports in this codebase.


41-41: Panel correctly exported in public API.

The Panel export is properly positioned in the export block, making it available to consumers of the runtime API.

v3/pkg/application/webview_panel_linux.go (3)

279-283: Z-index is a no-op on Linux - documented limitation.

The comment explains that GTK lacks a direct z-index concept. This is acceptable for the initial implementation. If z-ordering becomes important, gtk_box_reorder_child or switching to GtkOverlay with proper child ordering could be explored.


168-240: Panel creation implementation looks correct.

The create flow properly initializes the WebView, sets configuration options (size, devtools, background color, zoom, visibility), and loads initial content. Memory management with defer C.free for C strings is correct.


249-379: Panel lifecycle and manipulation methods are well implemented.

All methods include proper nil checks before operating on the webview/fixed pointers. The implementation follows the same patterns seen in other platform implementations.

v3/pkg/application/webview_panel_ios.go (1)

1-33: iOS stub implementation is correct.

This provides a proper no-op implementation for iOS where panels are not yet supported. The stub maintains API surface consistency and allows the codebase to compile for iOS without feature support.

v3/pkg/application/webview_panel_darwin.go (2)

193-198: DevTools limitation documented - acceptable for initial implementation.

Opening the WebKit inspector programmatically requires private APIs. The comment clearly documents that users can use right-click → Inspect Element if devtools are enabled.


259-448: Darwin panel implementation is well structured.

The Go wrapper methods properly:

  • Check for nil webview before CGO calls
  • Handle coordinate conversion for macOS bottom-left origin
  • Free C strings with defer
  • Follow the established patterns from WebviewWindow
v3/pkg/application/webview_panel_windows.go (3)

41-82: Panel creation implementation looks correct.

The child window is properly created with WS_CHILD | WS_CLIPSIBLINGS style, DIP-to-physical conversion is applied, and error handling is in place.


84-160: WebView2 setup is well implemented.

The chromium configuration properly handles:

  • Error callback registration
  • Data and browser paths from global options
  • Message and navigation callbacks
  • Settings for context menus and devtools
  • Zoom and background color
  • Initial content loading

320-327: The review comment's recommendation cannot be implemented. While Microsoft's WebView2 API does have a native Reload() method on ICoreWebView2, the go-webview2 v1.0.22 package used in this project does not expose it on the edge.Chromium binding. The available methods on edge.Chromium include Navigate(), Eval(), NavigateToString(), and others, but not Reload(). The current implementation using JavaScript injection (p.execJS("window.location.reload();")) is the appropriate approach given the available bindings.

Likely an incorrect or invalid review comment.

v3/examples/webview-panel/README.md (1)

1-143: Documentation is comprehensive and well-structured, with minor whitespace cleanup needed.

The README provides a clear overview of the WebviewPanel feature with practical examples covering Go and TypeScript APIs, use cases, and a helpful comparison table. The documentation accurately reflects the implemented API, including the GetPanelByID method and all other operations shown.

Minor nit: Line 26 has trailing whitespace that should be removed.

⛔ Skipped due to learnings
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4839
File: docs/src/content/docs/reference/window.mdx:616-620
Timestamp: 2025-12-29T08:02:15.134Z
Learning: In Wails v3, the correct API for creating windows is `app.Window.New()` and `app.Window.NewWithOptions(...)`, not `app.NewWebviewWindow()` or `app.NewWebviewWindowWithOptions(...)`. The Application struct exposes a Window field of type *WindowManager that provides these methods.
Learnt from: nixpare
Repo: wailsapp/wails PR: 3763
File: v3/pkg/application/webview_panel_darwin.go:88-89
Timestamp: 2024-09-21T09:56:48.126Z
Learning: Safety checks for `p.nsPanel` are performed in the `SetFloating` method of `WebviewPanel`, following the `WebviewWindow` and `macosWebviewWindow` implementations and code style.
Learnt from: leaanthony
Repo: wailsapp/wails PR: 3763
File: v3/examples/window/main.go:472-475
Timestamp: 2024-09-30T06:13:46.595Z
Learning: In `v3/examples/window/main.go`, `time.Sleep` is used within a goroutine and does not block the UI thread.
Learnt from: leaanthony
Repo: wailsapp/wails PR: 3763
File: v3/examples/window/main.go:472-475
Timestamp: 2024-10-08T22:11:37.054Z
Learning: In `v3/examples/window/main.go`, `time.Sleep` is used within a goroutine and does not block the UI thread.
Learnt from: APshenkin
Repo: wailsapp/wails PR: 4480
File: v2/internal/frontend/desktop/darwin/message.h:17-19
Timestamp: 2025-08-08T09:13:16.916Z
Learning: In Wails v2 bindings origin verification, processBindingMessage intentionally has different signatures across platforms: Darwin includes an isMainFrame bool (WKWebKit provides it), Linux uses two params (message, source) as WebKitGTK doesn’t expose main-frame info there, and Windows handles origin checks in Go via WebView2 sender/args without a C bridge. This divergence is acceptable/expected per maintainer (APshenkin).
Learnt from: nixpare
Repo: wailsapp/wails PR: 3763
File: v3/examples/keybindings/main.go:16-17
Timestamp: 2024-09-20T23:34:29.841Z
Learning: In the codebase, `application.Options.KeyBindings` uses the `application.Window` type, whereas `application.WebviewWindowOptions.KeyBindings` uses `*application.WebviewWindow`. This is intentional and acceptable.
Learnt from: leaanthony
Repo: wailsapp/wails PR: 4783
File: v3/pkg/events/events.go:72-100
Timestamp: 2025-12-13T19:52:13.812Z
Learning: In Wails v3, the linux:WindowLoadChanged event was intentionally removed as a breaking change and replaced with four granular WebKit2 load events: linux:WindowLoadStarted, linux:WindowLoadRedirected, linux:WindowLoadCommitted, and linux:WindowLoadFinished. Users should migrate to linux:WindowLoadFinished for detecting when the WebView has finished loading.
v3/internal/runtime/desktop/@wailsio/runtime/src/panel.ts (3)

13-31: Method constants alignment looks correct.

The method constants (0-17) match the corresponding Panel* constants in messageprocessor_panel.go. This is critical for the frontend-backend bridge to function correctly.


71-76: Method binding loop iterates over all prototype methods.

This pattern binds all instance methods to this, which is useful for event handlers. However, it uses Object.getOwnPropertyNames(Panel.prototype) which only gets own properties, not inherited ones—this is correct for this use case.


36-45: Bounds interface is well-defined.

The interface properly documents the position and size fields with JSDoc comments.

v3/pkg/application/messageprocessor_panel.go (3)

10-29: Constants match TypeScript frontend.

The Panel* constants correctly match the method constants in panel.ts, ensuring proper frontend-backend communication.


64-81: Panel lookup has redundant fallback logic.

The code first looks up by name, then falls back to ID lookup. However, if panelName is empty, the error at line 67 would already be returned before reaching the ID fallback. The ID fallback only activates when a name is provided but no panel is found with that name.

This is acceptable behavior but the comment at line 73 ("Try by ID") could be misleading since this isn't a true fallback for missing names.


83-93: PanelSetBounds validates all required parameters correctly.

The validation for x, y, width, and height being non-nil is appropriate.

v3/pkg/application/webview_window.go (2)

176-179: Panel management fields properly added.

The panels map and panelsLock RWMutex follow the existing pattern used for other window resources like eventListeners and menuBindings.


1557-1571: NewPanel correctly handles panel lifecycle.

The method properly:

  1. Creates the panel via NewPanel(options)
  2. Sets the parent reference
  3. Adds to the panels map under lock
  4. Starts the panel immediately if window is already running
v3/pkg/application/webview_panel.go (2)

318-336: Destroy method is well-implemented.

The method correctly:

  1. Checks if already destroyed (idempotent)
  2. Sets destroyed flag under lock
  3. Destroys the impl via InvokeSync
  4. Removes from parent

73-105: NewPanel factory has sensible defaults.

Good defensive defaults for Width (400), Height (300), ZIndex (1), Zoom (1.0), auto-generated Name, and Visible (true).

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements the WebviewPanel feature, enabling multiple independent webview containers to be embedded within a single window. This addresses issue #1997 and provides functionality similar to Electron's BrowserView/WebContentsView.

Changes:

  • Added core WebviewPanel API with platform-specific implementations for Windows, macOS, and Linux
  • Integrated panel management into WebviewWindow with methods to create, retrieve, and remove panels
  • Added TypeScript runtime API for controlling panels from the frontend
  • Included a comprehensive example application demonstrating multi-panel layouts

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
webview_panel.go Core panel struct and methods with lifecycle management
webview_panel_options.go Panel configuration options including positioning and appearance
webview_panel_windows.go Windows implementation using WebView2 in child windows
webview_panel_darwin.go macOS implementation using WKWebView as subviews
webview_panel_linux.go Linux implementation using WebKit2GTK with GtkFixed containers
webview_panel_ios.go, webview_panel_android.go Stub implementations for mobile platforms
webview_window.go Window integration with panel management methods
messageprocessor_panel.go Backend message handling for frontend panel API calls
messageprocessor.go Added panel request routing
panel.ts TypeScript Panel class for frontend control
runtime.ts, index.ts TypeScript runtime integration
examples/webview-panel/main.go Comprehensive example demonstrating panel usage
examples/webview-panel/README.md Example documentation with API overview

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Fix SonarCloud issues:
  - Add comments for empty stub functions on Android/iOS
  - Prefix unused parameters with underscore
  - Refactor processPanelMethod to use handler map (reduce cognitive complexity)
  - Use export...from syntax for Panel re-export in TypeScript

- Fix CodeRabbit/Copilot review issues:
  - Add autorelease for WebviewPanelDelegate in Darwin implementation
  - Add thread-safe access to pendingJS and runtimeLoaded with runtimeLock mutex
  - Add synchronization to panel.run() method
  - Call destroyAllPanels() in WindowClosing event handler
  - Call chromium.ShuttingDown() before DestroyWindow on Windows
  - Fix CSS escaping to handle backslashes and newlines
  - Remove redundant mutex around atomic operation in getNextPanelID
  - Fix runPanels to not hold lock during panel.run()
  - Fix documentation: HTML takes precedence over URL when both are set
@sonarqubecloud
Copy link

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In `@v3/pkg/application/webview_panel_darwin.go`:
- Around line 297-310: The code currently calls p.panel.markRuntimeLoaded()
immediately after starting URL navigation (when options.URL != ""), causing
runtime to be marked loaded before navigation finishes; move the
markRuntimeLoaded() call out of this init path and instead invoke
p.panel.markRuntimeLoaded() from the panelNavigationCompleted callback that runs
when navigation finishes (ensure panelNavigationCompleted handles both HTML and
URL cases or only call markRuntimeLoaded for URL loads), so any ExecJS queued
before page load will run after navigationCompleted; update or add logic in
panelNavigationCompleted to detect the relevant p.panel and call
markRuntimeLoaded() for URL-based loads.
- Around line 115-130: panelSetZIndex currently treats zIndex as binary
(above/below) which prevents granular stacking; change it to place the WKWebView
at a specific stack position based on the integer zIndex by inspecting and
manipulating the parent contentView's subviews. Locate panelSetZIndex
(WKWebView* webView, WebviewWindow* window, NSView* contentView) and replace the
binary removeFromSuperview/addSubview logic with code that: 1) clamp/normalize
the zIndex to a valid range (e.g., 0..(subviews.count)), 2) compute the target
sibling index (or target sibling view) corresponding to that zIndex, and 3)
insert the webView using addSubview:positioned:relativeTo: with the appropriate
relativeTo sibling (or reorder the subviews array) so multiple panels are
ordered according to their integer zIndex rather than only above/below.
- Around line 253-258: panelNavigationCompleted is a no-op and should locate the
panel by the exported ID and mark its runtime loaded like the Windows path does;
update the function to convert the C.uint ID to the Go panel ID type, look up
the panel via the shared registry/lookup (e.g., the same mechanism used
elsewhere to find panels), check the panel is non-nil, and call
p.panel.markRuntimeLoaded() (or the equivalent method on the panel object),
handling missing panels gracefully (log or ignore) and preserving any required
main-thread dispatch semantics for UI callbacks.

In `@v3/pkg/application/webview_panel_windows.go`:
- Around line 267-270: The variable clientPoint and its assignments (clientPoint
w32.POINT; clientPoint.X = 0; clientPoint.Y = 0) are unused dead code; remove
the clientPoint declaration and the two assignments and leave the existing call
that assigns parentClientX, parentClientY via w32.ClientToScreen(p.parent.hwnd,
0, 0) unchanged so only the needed variables remain.
♻️ Duplicate comments (2)
v3/pkg/application/webview_panel_options.go (1)

61-76: Frameless and Anchor fields are defined but not yet implemented.

These fields are documented but the platform implementations don't use them. This is acceptable if they're planned for future implementation.

v3/pkg/application/webview_panel_linux.go (1)

186-190: Panel overlay positioning doesn't work as intended.

The current implementation adds the GtkFixed container to the parent's vbox using gtk_box_pack_start, which places panels in the vertical layout flow rather than as true overlays above the main webview. The code comments acknowledge this limitation ("For now, let's use the overlay approach with GtkFixed"), but the implementation doesn't achieve absolute positioning relative to the window.

This means panels will be laid out sequentially in the vbox rather than floating at specific X,Y coordinates over the main content.

Consider refactoring to use GtkOverlay as the root container in the window implementation, adding the main webview as the base child and panels as overlay children via gtk_overlay_add_overlay.

🧹 Nitpick comments (4)
v3/pkg/application/webview_panel_options.go (1)

82-100: AnchorType bit values start at 2 instead of 1.

Due to AnchorNone = 0 consuming the first iota position, AnchorTop = 1 << iota evaluates to 1 << 1 = 2. The resulting values are:

  • AnchorTop = 2, AnchorBottom = 4, AnchorLeft = 8, AnchorRight = 16

This works correctly for bitwise operations but is unconventional. If canonical bit flag values (1, 2, 4, 8) are preferred:

Alternative pattern for conventional bit values
 const (
-	// AnchorNone - panel uses absolute positioning only (default)
-	AnchorNone AnchorType = 0
-
 	// AnchorTop - panel maintains distance from top edge
 	AnchorTop AnchorType = 1 << iota
 
 	// AnchorBottom - panel maintains distance from bottom edge
 	AnchorBottom
 
 	// AnchorLeft - panel maintains distance from left edge
 	AnchorLeft
 
 	// AnchorRight - panel maintains distance from right edge
 	AnchorRight
 
+	// AnchorNone - panel uses absolute positioning only (default)
+	AnchorNone AnchorType = 0
+
 	// AnchorFill - panel fills the entire window (anchored to all edges)
 	AnchorFill AnchorType = AnchorTop | AnchorBottom | AnchorLeft | AnchorRight
 )
v3/pkg/application/webview_panel.go (1)

432-477: Consider using typed constants for direction parameter.

The FillBeside method uses raw strings for the direction parameter. While functional, using typed constants would provide better compile-time safety and discoverability.

💡 Suggested improvement
type DockDirection string

const (
    DockRight DockDirection = "right"
    DockLeft  DockDirection = "left"
    DockBelow DockDirection = "below"
    DockAbove DockDirection = "above"
)

func (p *WebviewPanel) FillBeside(refPanel *WebviewPanel, direction DockDirection) *WebviewPanel {
    // ...
}
v3/pkg/application/webview_panel_linux.go (1)

279-283: Z-index is a no-op on Linux.

The comment correctly explains that GTK doesn't have a direct z-index concept. This is acceptable for now, but consider documenting this platform limitation in the public API documentation so users know that SetZIndex has no effect on Linux.

v3/pkg/application/messageprocessor_panel.go (1)

89-105: Panel lookup requires name first, panelId is only a fallback.

The current logic requires the panel name argument to be non-empty (line 91-93). The panelId is only used as a fallback when lookup by name fails (lines 97-101). This means if a frontend sends only panelId without a panel name, it will error with "panel name is required".

Consider whether this is intentional. If panelId should be an alternative to name, the logic should allow either:

💡 Allow lookup by ID when name is empty
 	// Get panel name from args
 	panelName := args.String("panel")
-	if panelName == nil || *panelName == "" {
-		return nil, errs.NewInvalidRuntimeCallErrorf("panel name is required")
-	}
 
 	// Get the panel by name or ID
-	panel := ww.GetPanel(*panelName)
-	if panel == nil {
+	var panel *WebviewPanel
+	if panelName != nil && *panelName != "" {
+		panel = ww.GetPanel(*panelName)
+	}
+	if panel == nil {
 		panelID := args.UInt("panelId")
 		if panelID != nil && *panelID > 0 {
 			panel = ww.GetPanelByID(uint(*panelID))
 		}
 	}
 	if panel == nil {
-		return nil, errs.NewInvalidRuntimeCallErrorf("panel not found: %s", *panelName)
+		return nil, errs.NewInvalidRuntimeCallErrorf("panel not found")
 	}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 838b159 and 48e93ea.

📒 Files selected for processing (10)
  • v3/internal/runtime/desktop/@wailsio/runtime/src/index.ts
  • v3/pkg/application/messageprocessor_panel.go
  • v3/pkg/application/webview_panel.go
  • v3/pkg/application/webview_panel_android.go
  • v3/pkg/application/webview_panel_darwin.go
  • v3/pkg/application/webview_panel_ios.go
  • v3/pkg/application/webview_panel_linux.go
  • v3/pkg/application/webview_panel_options.go
  • v3/pkg/application/webview_panel_windows.go
  • v3/pkg/application/webview_window.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • v3/internal/runtime/desktop/@wailsio/runtime/src/index.ts
🧰 Additional context used
🧠 Learnings (15)
📓 Common learnings
Learnt from: APshenkin
Repo: wailsapp/wails PR: 4480
File: v2/internal/frontend/desktop/darwin/message.h:17-19
Timestamp: 2025-08-08T09:13:16.916Z
Learning: In Wails v2 bindings origin verification, processBindingMessage intentionally has different signatures across platforms: Darwin includes an isMainFrame bool (WKWebKit provides it), Linux uses two params (message, source) as WebKitGTK doesn’t expose main-frame info there, and Windows handles origin checks in Go via WebView2 sender/args without a C bridge. This divergence is acceptable/expected per maintainer (APshenkin).
Learnt from: nixpare
Repo: wailsapp/wails PR: 3763
File: v3/pkg/application/webview_panel_darwin.go:88-89
Timestamp: 2024-09-21T09:56:48.126Z
Learning: Safety checks for `p.nsPanel` are performed in the `SetFloating` method of `WebviewPanel`, following the `WebviewWindow` and `macosWebviewWindow` implementations and code style.
Learnt from: Sammy-T
Repo: wailsapp/wails PR: 4570
File: v2/internal/frontend/desktop/linux/window_webkit6.go:97-108
Timestamp: 2025-10-17T23:16:11.570Z
Learning: For webkit_6/GTK4 builds in v2/internal/frontend/desktop/linux/window_webkit6.go, GTK widget creation should not be wrapped in invokeOnMainThread. The activation mechanism (activateWg + onActivate export) already handles thread safety, and additional wrapping would cause issues.
Learnt from: nixpare
Repo: wailsapp/wails PR: 3763
File: v3/examples/keybindings/main.go:16-17
Timestamp: 2024-09-20T23:34:29.841Z
Learning: In the codebase, `application.Options.KeyBindings` uses the `application.Window` type, whereas `application.WebviewWindowOptions.KeyBindings` uses `*application.WebviewWindow`. This is intentional and acceptable.
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4839
File: docs/src/content/docs/reference/window.mdx:616-620
Timestamp: 2025-12-29T08:02:15.134Z
Learning: In Wails v3, the correct API for creating windows is `app.Window.New()` and `app.Window.NewWithOptions(...)`, not `app.NewWebviewWindow()` or `app.NewWebviewWindowWithOptions(...)`. The Application struct exposes a Window field of type *WindowManager that provides these methods.
📚 Learning: 2026-01-04T08:01:00.038Z
Learnt from: symball
Repo: wailsapp/wails PR: 4853
File: v2/internal/system/system.go:128-152
Timestamp: 2026-01-04T08:01:00.038Z
Learning: In v2/internal/system/system.go, shared functions like checkLibrary are defined without build tags but are only invoked from platform-specific files (system_linux.go, system_windows.go, system_darwin.go) that have build constraints. Reviewers should ensure there are no runtime OS checks in system.go and that platform-specific behavior is controlled via build tags. If runtime switches exist, remove them in favor of compile-time platform constraints to reduce overhead and improve correctness.

Applied to files:

  • v3/pkg/application/messageprocessor_panel.go
  • v3/pkg/application/webview_panel_android.go
  • v3/pkg/application/webview_panel_options.go
  • v3/pkg/application/webview_panel_ios.go
  • v3/pkg/application/webview_panel_windows.go
  • v3/pkg/application/webview_panel_darwin.go
  • v3/pkg/application/webview_window.go
  • v3/pkg/application/webview_panel_linux.go
  • v3/pkg/application/webview_panel.go
📚 Learning: 2025-10-17T23:16:11.570Z
Learnt from: Sammy-T
Repo: wailsapp/wails PR: 4570
File: v2/internal/frontend/desktop/linux/window_webkit6.go:97-108
Timestamp: 2025-10-17T23:16:11.570Z
Learning: For webkit_6/GTK4 builds in v2/internal/frontend/desktop/linux/window_webkit6.go, GTK widget creation should not be wrapped in invokeOnMainThread. The activation mechanism (activateWg + onActivate export) already handles thread safety, and additional wrapping would cause issues.

Applied to files:

  • v3/pkg/application/webview_panel_android.go
  • v3/pkg/application/webview_panel_ios.go
  • v3/pkg/application/webview_panel_windows.go
  • v3/pkg/application/webview_panel_darwin.go
  • v3/pkg/application/webview_window.go
  • v3/pkg/application/webview_panel_linux.go
  • v3/pkg/application/webview_panel.go
📚 Learning: 2024-09-20T23:34:29.841Z
Learnt from: nixpare
Repo: wailsapp/wails PR: 3763
File: v3/examples/keybindings/main.go:16-17
Timestamp: 2024-09-20T23:34:29.841Z
Learning: In the codebase, `application.Options.KeyBindings` uses the `application.Window` type, whereas `application.WebviewWindowOptions.KeyBindings` uses `*application.WebviewWindow`. This is intentional and acceptable.

Applied to files:

  • v3/pkg/application/webview_panel_options.go
  • v3/pkg/application/webview_panel_windows.go
  • v3/pkg/application/webview_panel_darwin.go
  • v3/pkg/application/webview_window.go
  • v3/pkg/application/webview_panel.go
📚 Learning: 2025-12-29T08:02:15.134Z
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4839
File: docs/src/content/docs/reference/window.mdx:616-620
Timestamp: 2025-12-29T08:02:15.134Z
Learning: In Wails v3, the correct API for creating windows is `app.Window.New()` and `app.Window.NewWithOptions(...)`, not `app.NewWebviewWindow()` or `app.NewWebviewWindowWithOptions(...)`. The Application struct exposes a Window field of type *WindowManager that provides these methods.

Applied to files:

  • v3/pkg/application/webview_panel_options.go
  • v3/pkg/application/webview_panel_ios.go
  • v3/pkg/application/webview_panel_windows.go
  • v3/pkg/application/webview_panel_darwin.go
  • v3/pkg/application/webview_window.go
  • v3/pkg/application/webview_panel_linux.go
  • v3/pkg/application/webview_panel.go
📚 Learning: 2024-10-08T22:11:37.054Z
Learnt from: nixpare
Repo: wailsapp/wails PR: 3763
File: v3/pkg/application/application_options.go:93-93
Timestamp: 2024-10-08T22:11:37.054Z
Learning: The `KeyBindings` property of `application.Option` uses the `application.Window` type, while `application.WebviewWindowOptions` still accepts `*application.WebviewWindow`, and this is intentional.

Applied to files:

  • v3/pkg/application/webview_panel_options.go
📚 Learning: 2024-09-21T09:56:48.126Z
Learnt from: nixpare
Repo: wailsapp/wails PR: 3763
File: v3/pkg/application/webview_panel_darwin.go:88-89
Timestamp: 2024-09-21T09:56:48.126Z
Learning: Safety checks for `p.nsPanel` are performed in the `SetFloating` method of `WebviewPanel`, following the `WebviewWindow` and `macosWebviewWindow` implementations and code style.

Applied to files:

  • v3/pkg/application/webview_panel_options.go
  • v3/pkg/application/webview_panel_windows.go
  • v3/pkg/application/webview_panel_darwin.go
  • v3/pkg/application/webview_window.go
  • v3/pkg/application/webview_panel_linux.go
  • v3/pkg/application/webview_panel.go
📚 Learning: 2025-08-08T09:13:16.916Z
Learnt from: APshenkin
Repo: wailsapp/wails PR: 4480
File: v2/internal/frontend/desktop/darwin/message.h:17-19
Timestamp: 2025-08-08T09:13:16.916Z
Learning: In Wails v2 bindings origin verification, processBindingMessage intentionally has different signatures across platforms: Darwin includes an isMainFrame bool (WKWebKit provides it), Linux uses two params (message, source) as WebKitGTK doesn’t expose main-frame info there, and Windows handles origin checks in Go via WebView2 sender/args without a C bridge. This divergence is acceptable/expected per maintainer (APshenkin).

Applied to files:

  • v3/pkg/application/webview_panel_windows.go
  • v3/pkg/application/webview_panel_darwin.go
  • v3/pkg/application/webview_panel_linux.go
📚 Learning: 2025-12-13T19:52:13.812Z
Learnt from: leaanthony
Repo: wailsapp/wails PR: 4783
File: v3/pkg/events/events.go:72-100
Timestamp: 2025-12-13T19:52:13.812Z
Learning: In Wails v3, the linux:WindowLoadChanged event was intentionally removed as a breaking change and replaced with four granular WebKit2 load events: linux:WindowLoadStarted, linux:WindowLoadRedirected, linux:WindowLoadCommitted, and linux:WindowLoadFinished. Users should migrate to linux:WindowLoadFinished for detecting when the WebView has finished loading.

Applied to files:

  • v3/pkg/application/webview_panel_windows.go
  • v3/pkg/application/webview_panel_darwin.go
  • v3/pkg/application/webview_panel_linux.go
📚 Learning: 2025-08-08T13:15:37.785Z
Learnt from: APshenkin
Repo: wailsapp/wails PR: 4484
File: v2/internal/frontend/utils/urlValidator_test.go:152-155
Timestamp: 2025-08-08T13:15:37.785Z
Learning: In v2/internal/frontend/utils/urlValidator_test.go, the project accepts a literal '|' in the URL path (e.g., "https://example.com/user/123|admin") as valid for BrowserOpenURL. Maintainer (APshenkin) clarified that browsers will escape it automatically; thus, tests should not require percent-encoding '%7C' nor expect an error from utils.ValidateURL for this case. Avoid suggesting rejection or forced encoding of '|' in path in future reviews.

Applied to files:

  • v3/pkg/application/webview_panel_windows.go
📚 Learning: 2025-02-24T06:08:55.645Z
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4098
File: v3/pkg/services/notifications/notifications_windows.go:91-113
Timestamp: 2025-02-24T06:08:55.645Z
Learning: The JSON quote handling in Windows notifications' activation arguments (v3/pkg/services/notifications/notifications_windows.go) has a known limitation with single-quote collisions that needs to be addressed after initial testing.

Applied to files:

  • v3/pkg/application/webview_panel_windows.go
📚 Learning: 2025-04-29T23:54:07.488Z
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4256
File: v2/internal/frontend/desktop/linux/notifications.go:27-28
Timestamp: 2025-04-29T23:54:07.488Z
Learning: In Wails v2, unlike v3-alpha which has a `ServiceShutdown` method for services, there is no standardized teardown pattern for frontend implementations. When implementing features that require cleanup (like goroutines or resources), add explicit cleanup methods (e.g., `CleanupNotifications()`) that handle resource release, context cancellation, and connection closure.

Applied to files:

  • v3/pkg/application/webview_panel_windows.go
  • v3/pkg/application/webview_window.go
📚 Learning: 2025-01-25T04:10:54.008Z
Learnt from: leaanthony
Repo: wailsapp/wails PR: 4031
File: v3/pkg/application/menuitem_darwin.go:299-302
Timestamp: 2025-01-25T04:10:54.008Z
Learning: Memory release operations in Objective-C/Cocoa should be performed synchronously, not asynchronously via dispatch_async, as they don't require main thread coordination and should complete immediately to prevent memory leaks.

Applied to files:

  • v3/pkg/application/webview_panel_darwin.go
📚 Learning: 2024-09-30T06:13:46.595Z
Learnt from: leaanthony
Repo: wailsapp/wails PR: 3763
File: v3/examples/window/main.go:472-475
Timestamp: 2024-09-30T06:13:46.595Z
Learning: In `v3/examples/window/main.go`, `time.Sleep` is used within a goroutine and does not block the UI thread.

Applied to files:

  • v3/pkg/application/webview_window.go
📚 Learning: 2024-10-08T22:11:37.054Z
Learnt from: leaanthony
Repo: wailsapp/wails PR: 3763
File: v3/internal/commands/appimage_testfiles/main.go:295-299
Timestamp: 2024-10-08T22:11:37.054Z
Learning: In `v3/internal/commands/appimage_testfiles/main.go`, `time.Sleep` is used within a goroutine and does not block the UI thread.

Applied to files:

  • v3/pkg/application/webview_window.go
  • v3/pkg/application/webview_panel.go
🧬 Code graph analysis (7)
v3/pkg/application/webview_panel_android.go (1)
v3/pkg/application/webview_panel.go (1)
  • WebviewPanel (54-69)
v3/pkg/application/webview_panel_options.go (2)
v3/internal/runtime/desktop/@wailsio/runtime/src/panel.ts (1)
  • Name (277-281)
v3/internal/runtime/desktop/@wailsio/runtime/src/window.ts (2)
  • Width (597-599)
  • Height (309-311)
v3/pkg/application/webview_panel_ios.go (1)
v3/pkg/application/webview_panel.go (1)
  • WebviewPanel (54-69)
v3/pkg/application/webview_panel_windows.go (6)
v3/pkg/w32/typedef.go (2)
  • HWND (218-218)
  • POINT (231-233)
v3/pkg/w32/constants.go (9)
  • WS_CHILD (343-343)
  • WS_CLIPSIBLINGS (347-347)
  • WS_VISIBLE (345-345)
  • SWP_NOZORDER (2218-2218)
  • SWP_NOACTIVATE (2220-2220)
  • HWND_TOP (2238-2238)
  • HWND_BOTTOM (2236-2236)
  • SWP_NOMOVE (2217-2217)
  • SWP_NOSIZE (2216-2216)
v3/pkg/application/screenmanager.go (2)
  • DipToPhysicalRect (853-855)
  • PhysicalToDipRect (857-859)
v3/pkg/w32/window.go (1)
  • MustStringToUTF16Ptr (173-180)
v3/pkg/w32/kernel32.go (1)
  • GetModuleHandle (52-61)
v3/internal/assetserver/assetserver.go (1)
  • GetStartURL (149-175)
v3/pkg/application/webview_panel_darwin.go (2)
v3/pkg/application/webview_panel.go (1)
  • WebviewPanel (54-69)
v3/internal/runtime/desktop/@wailsio/runtime/src/window.ts (2)
  • Width (597-599)
  • Height (309-311)
v3/pkg/application/webview_panel_linux.go (1)
v3/pkg/application/webview_panel.go (1)
  • WebviewPanel (54-69)
v3/pkg/application/webview_panel.go (4)
v3/pkg/application/webview_panel_options.go (1)
  • WebviewPanelOptions (5-76)
v3/pkg/application/webview_window.go (1)
  • WebviewWindow (142-180)
v3/internal/runtime/desktop/@wailsio/runtime/src/panel.ts (7)
  • Name (277-281)
  • SetBounds (95-103)
  • Bounds (36-45)
  • SetZIndex (121-126)
  • SetURL (133-138)
  • SetHTML (145-150)
  • ExecJS (157-162)
v3/pkg/application/mainthread.go (2)
  • InvokeSync (23-32)
  • InvokeSyncWithResult (34-44)
🔇 Additional comments (24)
v3/pkg/application/webview_panel_android.go (1)

1-87: LGTM!

The Android stub implementation is well-structured with appropriate build tags, sensible default return values, and clear documentation indicating the platform is not yet supported.

v3/pkg/application/webview_panel_windows.go (3)

403-408: Verify focus detection accuracy for WebView2.

The isFocused() method checks if the container window (p.hwnd) has focus, but WebView2 manages its own focus internally. When the WebView2 content has keyboard focus, GetFocus() may return the WebView2's internal HWND rather than the container, causing this method to return false incorrectly.

Consider checking the chromium controller's focus state for more accurate detection, or verify this behavior works as expected in practice.


162-182: CSS escaping has been improved.

The CSS escaping now properly handles backslashes, double quotes, newlines, and carriage returns, addressing the previous review concerns.


214-223: Chromium cleanup sequence is now correct.

The destroy() method properly calls p.chromium.ShuttingDown() before destroying the window, consistent with the cleanup pattern in the main window implementation.

v3/pkg/application/webview_panel_darwin.go (1)

45-77: Memory management for Cocoa objects is now correct.

The autorelease calls for config, userContentController, and delegate properly balance the reference counts and prevent memory leaks. This addresses the previous review concerns.

v3/pkg/application/webview_panel_ios.go (1)

1-87: LGTM!

The iOS stub implementation follows the same clean pattern as the Android stub, with appropriate build tags, sensible defaults, and clear documentation indicating the platform is not yet supported.

v3/pkg/application/webview_panel.go (6)

44-48: LGTM - Simplified ID generation.

The previous redundant mutex around atomic operation has been correctly removed. Using only atomic.AddUint32 is sufficient and idiomatic for generating unique IDs.


54-103: LGTM - Well-structured panel definition and constructor.

The WebviewPanel struct properly separates concerns with dedicated locks for destruction state and runtime state. The constructor applies sensible defaults and follows the pattern established by WebviewWindow.


219-235: LGTM - Race condition addressed.

The ExecJS method now properly uses runtimeLock to synchronize access to runtimeLoaded and pendingJS. The lock is correctly released before calling InvokeSync to avoid potential deadlocks.


361-375: LGTM - Proper synchronization for runtime loading.

The markRuntimeLoaded method correctly snapshots pendingJS under lock and iterates outside the lock. This prevents race conditions with ExecJS and avoids deadlock since InvokeSync is called after releasing the lock.


347-359: LGTM - Synchronized panel initialization.

The run() method now uses destroyedLock to protect against concurrent initialization, addressing the previous race condition concern. The pattern correctly releases the lock before calling InvokeSync.


320-338: Minor TOCTOU window, but follows existing patterns.

There's a small window between isDestroyed() check and acquiring the write lock where another goroutine could set destroyed=true. However, this is benign since double-destruction is idempotent (impl.destroy() should handle this gracefully, and setting destroyed=true twice is harmless). This follows the same pattern as WebviewWindow.

v3/pkg/application/webview_panel_linux.go (3)

249-255: LGTM - Proper cleanup.

Destroying the GtkFixed container will also destroy its child webview. Setting both pointers to nil prevents use-after-free.


312-324: LGTM - Consistent null checks.

All methods properly check for nil pointers before CGO calls, preventing crashes on destroyed panels.


238-239: This concern is based on a misunderstanding of the intentional design difference between WebviewWindow and WebviewPanel. WebviewPanels do not inject the Wails runtime by default (as noted in the Darwin implementation), so they do not wait for the "wails:runtime:ready" message. The immediate markRuntimeLoaded() call at the end of create() is intentional and consistent across all platforms (Linux, Darwin, Windows). WebviewPanel still maintains the same pendingJS queue mechanism for thread-safe handling of any JavaScript sent before the panel is fully initialized.

Likely an incorrect or invalid review comment.

v3/pkg/application/messageprocessor_panel.go (2)

10-29: LGTM - Clean constant definitions.

The method constants are well-organized with sequential values. Using constants instead of strings is efficient for the message processor.


115-231: LGTM - Consistent handler implementations.

All handlers follow a clean pattern: validate required arguments, call the appropriate panel method, and return unit for void operations or the actual value for getters. Error messages are descriptive.

v3/pkg/application/webview_window.go (7)

176-179: LGTM - Proper initialization of panel management.

The panels map and panelsLock are correctly added to the struct and initialized in NewWindow. This follows the same pattern as other map fields in WebviewWindow.

Also applies to: 296-296


301-308: LGTM - Panel cleanup on window close.

The destroyAllPanels is now called during window closing, addressing the previous concern about resource leaks. The order (destroy panels → mark destroyed → close impl → remove) is correct.


1637-1651: LGTM - Race condition addressed.

The runPanels method now correctly snapshots panels under read lock and releases the lock before calling panel.run(). This prevents deadlocks since panel.run() internally calls InvokeSync.


1653-1666: LGTM - Proper panel cleanup implementation.

The method correctly snapshots panels under lock and destroys them outside the lock. This prevents deadlocks since panel.Destroy() will call removePanel() which needs to acquire the write lock.


1558-1572: LGTM - Well-implemented panel creation.

The method handles both cases: adding panels before the window runs (they'll be started by runPanels()) and after (started immediately). The lock usage is correct.


1574-1604: LGTM - Thread-safe panel lookups.

All lookup methods properly use read locks. GetPanel iterates to find by name (O(n) but acceptable for typical panel counts), while GetPanelByID uses direct map lookup. GetPanels returns a defensive copy to prevent external modification.


1606-1633: LGTM - Clean removal implementation.

The public RemovePanel and RemovePanelByID methods correctly delegate to panel.Destroy(), which handles cleanup and calls the internal removePanel() to remove from the map. This avoids duplicate cleanup logic.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +115 to +130
// Set panel z-index (bring to front or send to back)
void panelSetZIndex(void* panel, void* parentWindow, int zIndex) {
WKWebView* webView = (WKWebView*)panel;
WebviewWindow* window = (WebviewWindow*)parentWindow;
NSView* contentView = [window contentView];
if (zIndex > 0) {
// Bring to front
[webView removeFromSuperview];
[contentView addSubview:webView positioned:NSWindowAbove relativeTo:nil];
} else {
// Send to back (but above main webview which is at index 0)
[webView removeFromSuperview];
[contentView addSubview:webView positioned:NSWindowBelow relativeTo:nil];
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Z-index implementation uses same binary approach as Windows.

The panelSetZIndex function treats z-index as binary (above vs. below), which has the same limitation noted in the Windows implementation regarding granular stacking of multiple panels.

🤖 Prompt for AI Agents
In `@v3/pkg/application/webview_panel_darwin.go` around lines 115 - 130,
panelSetZIndex currently treats zIndex as binary (above/below) which prevents
granular stacking; change it to place the WKWebView at a specific stack position
based on the integer zIndex by inspecting and manipulating the parent
contentView's subviews. Locate panelSetZIndex (WKWebView* webView,
WebviewWindow* window, NSView* contentView) and replace the binary
removeFromSuperview/addSubview logic with code that: 1) clamp/normalize the
zIndex to a valid range (e.g., 0..(subviews.count)), 2) compute the target
sibling index (or target sibling view) corresponding to that zIndex, and 3)
insert the webView using addSubview:positioned:relativeTo: with the appropriate
relativeTo sibling (or reorder the subviews array) so multiple panels are
ordered according to their integer zIndex rather than only above/below.

Comment on lines +253 to +258
//export panelNavigationCompleted
func panelNavigationCompleted(_ C.uint) {
// Find the panel and mark runtime as loaded
// This is called from Objective-C when navigation completes
// For now, we'll handle this through the panel's own tracking
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Navigation completion callback is a no-op stub.

The panelNavigationCompleted export receives the panel ID from Objective-C but doesn't actually locate the panel or update its state. The comment says "For now, we'll handle this through the panel's own tracking," but this means the navigation completion event from webView:didFinishNavigation: is effectively discarded.

This is inconsistent with the Windows implementation where navigationCompletedCallback calls p.panel.markRuntimeLoaded().

Suggested implementation
 //export panelNavigationCompleted
-func panelNavigationCompleted(_ C.uint) {
-	// Find the panel and mark runtime as loaded
-	// This is called from Objective-C when navigation completes
-	// For now, we'll handle this through the panel's own tracking
+func panelNavigationCompleted(panelId C.uint) {
+	// Find the panel by ID and mark runtime as loaded
+	// This requires access to a panel registry or the parent window's panel map
+	// TODO: Implement panel lookup and call panel.markRuntimeLoaded()
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
//export panelNavigationCompleted
func panelNavigationCompleted(_ C.uint) {
// Find the panel and mark runtime as loaded
// This is called from Objective-C when navigation completes
// For now, we'll handle this through the panel's own tracking
}
//export panelNavigationCompleted
func panelNavigationCompleted(panelId C.uint) {
// Find the panel by ID and mark runtime as loaded
// This requires access to a panel registry or the parent window's panel map
// TODO: Implement panel lookup and call panel.markRuntimeLoaded()
}
🤖 Prompt for AI Agents
In `@v3/pkg/application/webview_panel_darwin.go` around lines 253 - 258,
panelNavigationCompleted is a no-op and should locate the panel by the exported
ID and mark its runtime loaded like the Windows path does; update the function
to convert the C.uint ID to the Go panel ID type, look up the panel via the
shared registry/lookup (e.g., the same mechanism used elsewhere to find panels),
check the panel is non-nil, and call p.panel.markRuntimeLoaded() (or the
equivalent method on the panel object), handling missing panels gracefully (log
or ignore) and preserving any required main-thread dispatch semantics for UI
callbacks.

Comment on lines +297 to +310
// Load initial content
if options.HTML != "" {
html := C.CString(options.HTML)
C.panelLoadHTML(p.webview, html)
C.free(unsafe.Pointer(html))
} else if options.URL != "" {
url := C.CString(options.URL)
C.panelLoadURL(p.webview, url)
C.free(unsafe.Pointer(url))
}

// Mark runtime as loaded (panels don't inject the wails runtime by default)
p.panel.markRuntimeLoaded()
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Runtime marked as loaded before URL navigation completes.

For URL-based content, markRuntimeLoaded() is called immediately after initiating navigation (line 309), not after navigation completes. This differs from the Windows implementation which waits for navigationCompletedCallback.

Any JavaScript queued via ExecJS before the page finishes loading may fail or execute on an incomplete page. Consider moving markRuntimeLoaded() to the panelNavigationCompleted callback for URL-based loading.

🤖 Prompt for AI Agents
In `@v3/pkg/application/webview_panel_darwin.go` around lines 297 - 310, The code
currently calls p.panel.markRuntimeLoaded() immediately after starting URL
navigation (when options.URL != ""), causing runtime to be marked loaded before
navigation finishes; move the markRuntimeLoaded() call out of this init path and
instead invoke p.panel.markRuntimeLoaded() from the panelNavigationCompleted
callback that runs when navigation finishes (ensure panelNavigationCompleted
handles both HTML and URL cases or only call markRuntimeLoaded for URL loads),
so any ExecJS queued before page load will run after navigationCompleted; update
or add logic in panelNavigationCompleted to detect the relevant p.panel and call
markRuntimeLoaded() for URL-based loads.

Comment on lines +267 to +270
var clientPoint w32.POINT
clientPoint.X = 0
clientPoint.Y = 0
parentClientX, parentClientY := w32.ClientToScreen(p.parent.hwnd, 0, 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove unused variable declaration.

The clientPoint variable is declared and initialized but never used. This is dead code.

Suggested fix
 	// Calculate position relative to parent's client area
-	var clientPoint w32.POINT
-	clientPoint.X = 0
-	clientPoint.Y = 0
 	parentClientX, parentClientY := w32.ClientToScreen(p.parent.hwnd, 0, 0)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var clientPoint w32.POINT
clientPoint.X = 0
clientPoint.Y = 0
parentClientX, parentClientY := w32.ClientToScreen(p.parent.hwnd, 0, 0)
// Calculate position relative to parent's client area
parentClientX, parentClientY := w32.ClientToScreen(p.parent.hwnd, 0, 0)
🤖 Prompt for AI Agents
In `@v3/pkg/application/webview_panel_windows.go` around lines 267 - 270, The
variable clientPoint and its assignments (clientPoint w32.POINT; clientPoint.X =
0; clientPoint.Y = 0) are unused dead code; remove the clientPoint declaration
and the two assignments and leave the existing call that assigns parentClientX,
parentClientY via w32.ClientToScreen(p.parent.hwnd, 0, 0) unchanged so only the
needed variables remain.

Comment on lines +282 to +302
func (p *windowsPanelImpl) setZIndex(zIndex int) {
if p.hwnd == 0 {
return
}

// Use SetWindowPos to change z-order
// Higher zIndex = on top
var insertAfter uintptr
if zIndex > 0 {
insertAfter = w32.HWND_TOP
} else {
insertAfter = w32.HWND_BOTTOM
}

w32.SetWindowPos(
p.hwnd,
insertAfter,
0, 0, 0, 0,
w32.SWP_NOMOVE|w32.SWP_NOSIZE|w32.SWP_NOACTIVATE,
)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Z-index implementation doesn't support granular stacking.

The current implementation treats z-index as binary (top vs. bottom), but the WebviewPanelOptions.ZIndex documentation states "Higher values appear on top of lower values." With this implementation, multiple panels with z-index values 1, 2, and 3 would all be placed at HWND_TOP without relative ordering between them.

Consider iterating sibling panels and using SetWindowPos with a reference HWND to achieve proper stacking order, or document this limitation.

@WCY-dt WCY-dt marked this pull request as draft January 14, 2026 14:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant