From a7dbbd4fbc322a94e15426ddc913956ef1438031 Mon Sep 17 00:00:00 2001
From: Pandelis Zembashis
Date: Sun, 22 Feb 2026 02:57:39 +0000
Subject: [PATCH 1/6] feat: webcontentsview
---
v3/pkg/application/browser_window.go | 1 +
v3/pkg/application/window.go | 1 +
v3/pkg/webcontentsview/webcontentsview.go | 70 ++++++++++
.../webcontentsview_android.go | 23 +++
.../webcontentsview/webcontentsview_darwin.go | 93 ++++++++++++
.../webcontentsview/webcontentsview_darwin.h | 28 ++++
.../webcontentsview/webcontentsview_darwin.m | 86 ++++++++++++
.../webcontentsview_darwin_test.go | 78 +++++++++++
v3/pkg/webcontentsview/webcontentsview_ios.go | 23 +++
.../webcontentsview/webcontentsview_linux.go | 132 ++++++++++++++++++
.../webcontentsview_windows.go | 98 +++++++++++++
v3/pkg/webcontentsview/webpreferences.go | 48 +++++++
12 files changed, 681 insertions(+)
create mode 100644 v3/pkg/webcontentsview/webcontentsview.go
create mode 100644 v3/pkg/webcontentsview/webcontentsview_android.go
create mode 100644 v3/pkg/webcontentsview/webcontentsview_darwin.go
create mode 100644 v3/pkg/webcontentsview/webcontentsview_darwin.h
create mode 100644 v3/pkg/webcontentsview/webcontentsview_darwin.m
create mode 100644 v3/pkg/webcontentsview/webcontentsview_darwin_test.go
create mode 100644 v3/pkg/webcontentsview/webcontentsview_ios.go
create mode 100644 v3/pkg/webcontentsview/webcontentsview_linux.go
create mode 100644 v3/pkg/webcontentsview/webcontentsview_windows.go
create mode 100644 v3/pkg/webcontentsview/webpreferences.go
diff --git a/v3/pkg/application/browser_window.go b/v3/pkg/application/browser_window.go
index 5d3749db0..f3f9d2918 100644
--- a/v3/pkg/application/browser_window.go
+++ b/v3/pkg/application/browser_window.go
@@ -55,6 +55,7 @@ func (b *BrowserWindow) Info(message string, args ...any) {
// No-op methods - these don't apply to browser windows
+
func (b *BrowserWindow) Center() {}
func (b *BrowserWindow) Close() {}
func (b *BrowserWindow) DisableSizeConstraints() {}
diff --git a/v3/pkg/application/window.go b/v3/pkg/application/window.go
index 3f4949b16..46aee1619 100644
--- a/v3/pkg/application/window.go
+++ b/v3/pkg/application/window.go
@@ -7,6 +7,7 @@ import (
)
type Window interface {
+
Center()
Close()
DisableSizeConstraints()
diff --git a/v3/pkg/webcontentsview/webcontentsview.go b/v3/pkg/webcontentsview/webcontentsview.go
new file mode 100644
index 000000000..30602e720
--- /dev/null
+++ b/v3/pkg/webcontentsview/webcontentsview.go
@@ -0,0 +1,70 @@
+package webcontentsview
+
+import (
+ "sync/atomic"
+ "unsafe"
+ "github.com/wailsapp/wails/v3/pkg/application"
+)
+
+// WebContentsViewOptions represents the options for creating a WebContentsView.
+type WebContentsViewOptions struct {
+ Name string
+ URL string
+ HTML string
+ Bounds application.Rect
+ WebPreferences WebPreferences
+}
+
+// WebContentsView represents a native webview that can be embedded into a window.
+type WebContentsView struct {
+ options WebContentsViewOptions
+ id uint
+ impl webContentsViewImpl
+}
+
+var webContentsViewID uintptr
+
+// NewWebContentsView creates a new WebContentsView with the given options.
+func NewWebContentsView(options WebContentsViewOptions) *WebContentsView {
+ result := &WebContentsView{
+ id: uint(atomic.AddUintptr(&webContentsViewID, 1)),
+ options: options,
+ }
+ result.impl = newWebContentsViewImpl(result)
+ return result
+}
+
+// SetBounds sets the position and size of the WebContentsView relative to its parent.
+func (v *WebContentsView) SetBounds(bounds application.Rect) {
+ v.impl.setBounds(bounds)
+}
+
+// SetURL loads the given URL into the WebContentsView.
+func (v *WebContentsView) SetURL(url string) {
+ v.impl.setURL(url)
+}
+
+// ExecJS executes the given javascript in the WebContentsView.
+func (v *WebContentsView) ExecJS(js string) {
+ v.impl.execJS(js)
+}
+
+// Attach binds the WebContentsView to a Wails Window.
+func (v *WebContentsView) Attach(window application.Window) {
+ v.impl.attach(window)
+}
+
+// Detach removes the WebContentsView from the Wails Window.
+func (v *WebContentsView) Detach() {
+ v.impl.detach()
+}
+
+// webContentsViewImpl is the interface that platform-specific implementations must satisfy.
+type webContentsViewImpl interface {
+ setBounds(bounds application.Rect)
+ setURL(url string)
+ execJS(js string)
+ attach(window application.Window)
+ detach()
+ nativeView() unsafe.Pointer
+}
diff --git a/v3/pkg/webcontentsview/webcontentsview_android.go b/v3/pkg/webcontentsview/webcontentsview_android.go
new file mode 100644
index 000000000..2148191ae
--- /dev/null
+++ b/v3/pkg/webcontentsview/webcontentsview_android.go
@@ -0,0 +1,23 @@
+//go:build android
+
+package webcontentsview
+
+import (
+ "unsafe"
+ "github.com/wailsapp/wails/v3/pkg/application"
+)
+
+type androidWebContentsView struct {
+ parent *WebContentsView
+}
+
+func newWebContentsViewImpl(parent *WebContentsView) webContentsViewImpl {
+ return &androidWebContentsView{parent: parent}
+}
+
+func (w *androidWebContentsView) setBounds(bounds application.Rect) {}
+func (w *androidWebContentsView) setURL(url string) {}
+func (w *androidWebContentsView) execJS(js string) {}
+func (w *androidWebContentsView) attach(window application.Window) {}
+func (w *androidWebContentsView) detach() {}
+func (w *androidWebContentsView) nativeView() unsafe.Pointer { return nil }
diff --git a/v3/pkg/webcontentsview/webcontentsview_darwin.go b/v3/pkg/webcontentsview/webcontentsview_darwin.go
new file mode 100644
index 000000000..34044a2e4
--- /dev/null
+++ b/v3/pkg/webcontentsview/webcontentsview_darwin.go
@@ -0,0 +1,93 @@
+//go:build darwin
+
+package webcontentsview
+
+/*
+#cgo CFLAGS: -x objective-c
+#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
+#import "webcontentsview_darwin.h"
+#include
+*/
+import "C"
+import (
+ "unsafe"
+ "github.com/wailsapp/wails/v3/pkg/application"
+)
+
+type macosWebContentsView struct {
+ parent *WebContentsView
+ nsView unsafe.Pointer
+ nsWindow unsafe.Pointer
+}
+
+func newWebContentsViewImpl(parent *WebContentsView) webContentsViewImpl {
+ // Setup preferences
+ prefs := C.WebContentsViewPreferences{
+ devTools: C.bool(parent.options.WebPreferences.DevTools != application.Disabled),
+ javascript: C.bool(parent.options.WebPreferences.Javascript != application.Disabled),
+ webSecurity: C.bool(parent.options.WebPreferences.WebSecurity != application.Disabled),
+ images: C.bool(parent.options.WebPreferences.Images != application.Disabled),
+ plugins: C.bool(parent.options.WebPreferences.Plugins == application.Enabled),
+ zoomFactor: C.double(parent.options.WebPreferences.ZoomFactor),
+ defaultFontSize: C.int(parent.options.WebPreferences.DefaultFontSize),
+ defaultMonospaceFontSize: C.int(parent.options.WebPreferences.DefaultMonospaceFontSize),
+ minimumFontSize: C.int(parent.options.WebPreferences.MinimumFontSize),
+ }
+
+ if prefs.zoomFactor == 0 {
+ prefs.zoomFactor = 1.0
+ }
+
+ var view = C.createWebContentsView(
+ C.int(parent.options.Bounds.X),
+ C.int(parent.options.Bounds.Y),
+ C.int(parent.options.Bounds.Width),
+ C.int(parent.options.Bounds.Height),
+ prefs,
+ )
+
+ result := &macosWebContentsView{
+ parent: parent,
+ nsView: view,
+ }
+
+ if parent.options.URL != "" {
+ result.setURL(parent.options.URL)
+ }
+
+ return result
+}
+
+func (w *macosWebContentsView) setBounds(bounds application.Rect) {
+ C.webContentsViewSetBounds(w.nsView, C.int(bounds.X), C.int(bounds.Y), C.int(bounds.Width), C.int(bounds.Height))
+}
+
+func (w *macosWebContentsView) setURL(url string) {
+ cUrl := C.CString(url)
+ defer C.free(unsafe.Pointer(cUrl))
+ C.webContentsViewSetURL(w.nsView, cUrl)
+}
+
+func (w *macosWebContentsView) execJS(js string) {
+ cJs := C.CString(js)
+ defer C.free(unsafe.Pointer(cJs))
+ C.webContentsViewExecJS(w.nsView, cJs)
+}
+
+func (w *macosWebContentsView) attach(window application.Window) {
+ w.nsWindow = window.NativeWindow()
+ if w.nsWindow != nil {
+ C.windowAddWebContentsView(w.nsWindow, w.nsView)
+ }
+}
+
+func (w *macosWebContentsView) detach() {
+ if w.nsWindow != nil {
+ C.windowRemoveWebContentsView(w.nsWindow, w.nsView)
+ w.nsWindow = nil
+ }
+}
+
+func (w *macosWebContentsView) nativeView() unsafe.Pointer {
+ return w.nsView
+}
diff --git a/v3/pkg/webcontentsview/webcontentsview_darwin.h b/v3/pkg/webcontentsview/webcontentsview_darwin.h
new file mode 100644
index 000000000..5ac03217b
--- /dev/null
+++ b/v3/pkg/webcontentsview/webcontentsview_darwin.h
@@ -0,0 +1,28 @@
+#ifndef webcontentsview_darwin_h
+#define webcontentsview_darwin_h
+
+#import
+#import
+#import
+
+typedef struct {
+ bool devTools;
+ bool javascript;
+ bool webSecurity;
+ bool images;
+ bool plugins;
+ double zoomFactor;
+ int defaultFontSize;
+ int defaultMonospaceFontSize;
+ int minimumFontSize;
+} WebContentsViewPreferences;
+
+extern void* createWebContentsView(int x, int y, int w, int h, WebContentsViewPreferences prefs);
+extern void webContentsViewSetBounds(void* view, int x, int y, int w, int h);
+extern void webContentsViewSetURL(void* view, const char* url);
+extern void webContentsViewExecJS(void* view, const char* js);
+
+extern void windowAddWebContentsView(void* nsWindow, void* view);
+extern void windowRemoveWebContentsView(void* nsWindow, void* view);
+
+#endif /* webcontentsview_darwin_h */
diff --git a/v3/pkg/webcontentsview/webcontentsview_darwin.m b/v3/pkg/webcontentsview/webcontentsview_darwin.m
new file mode 100644
index 000000000..ef43baf86
--- /dev/null
+++ b/v3/pkg/webcontentsview/webcontentsview_darwin.m
@@ -0,0 +1,86 @@
+#import "webcontentsview_darwin.h"
+
+void* createWebContentsView(int x, int y, int w, int h, WebContentsViewPreferences prefs) {
+ NSRect frame = NSMakeRect(x, y, w, h);
+ WKWebViewConfiguration* config = [[WKWebViewConfiguration alloc] init];
+
+ WKPreferences *preferences = [[WKPreferences alloc] init];
+
+ // Apply preferences
+ if (@available(macOS 10.11, *)) {
+ [preferences setValue:@(prefs.devTools) forKey:@"developerExtrasEnabled"];
+ }
+
+ // JavaScript
+ if (@available(macOS 11.0, *)) {
+ WKWebpagePreferences *webpagePreferences = [[WKWebpagePreferences alloc] init];
+ webpagePreferences.allowsContentJavaScript = prefs.javascript;
+ config.defaultWebpagePreferences = webpagePreferences;
+ } else {
+ preferences.javaScriptEnabled = prefs.javascript;
+ }
+
+ // WebSecurity / CORS
+ if (!prefs.webSecurity) {
+ [preferences setValue:@YES forKey:@"allowFileAccessFromFileURLs"];
+ [preferences setValue:@YES forKey:@"allowUniversalAccessFromFileURLs"];
+ }
+
+ // Images
+ [preferences setValue:@(prefs.images) forKey:@"loadsImagesAutomatically"];
+
+ // Plugins
+ [preferences setValue:@(prefs.plugins) forKey:@"plugInsEnabled"];
+
+ // Fonts
+ if (prefs.defaultFontSize > 0) {
+ [preferences setValue:@(prefs.defaultFontSize) forKey:@"defaultFontSize"];
+ }
+ if (prefs.minimumFontSize > 0) {
+ preferences.minimumFontSize = prefs.minimumFontSize;
+ }
+
+ config.preferences = preferences;
+
+ WKWebView* webView = [[WKWebView alloc] initWithFrame:frame configuration:config];
+
+ // Zoom factor
+ if (prefs.zoomFactor != 1.0 && prefs.zoomFactor > 0) {
+ if (@available(macOS 11.0, *)) {
+ [webView setPageZoom:prefs.zoomFactor];
+ } else {
+ [webView setMagnification:prefs.zoomFactor];
+ }
+ }
+
+ return webView;
+}
+
+void webContentsViewSetBounds(void* view, int x, int y, int w, int h) {
+ WKWebView* webView = (WKWebView*)view;
+ [webView setFrame:NSMakeRect(x, y, w, h)];
+}
+
+void webContentsViewSetURL(void* view, const char* url) {
+ WKWebView* webView = (WKWebView*)view;
+ NSString* nsURL = [NSString stringWithUTF8String:url];
+ NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:nsURL]];
+ [webView loadRequest:request];
+}
+
+void webContentsViewExecJS(void* view, const char* js) {
+ WKWebView* webView = (WKWebView*)view;
+ NSString* script = [NSString stringWithUTF8String:js];
+ [webView evaluateJavaScript:script completionHandler:nil];
+}
+
+void windowAddWebContentsView(void* nsWindow, void* view) {
+ NSWindow* window = (NSWindow*)nsWindow;
+ WKWebView* webView = (WKWebView*)view;
+ [window.contentView addSubview:webView positioned:NSWindowAbove relativeTo:nil];
+}
+
+void windowRemoveWebContentsView(void* nsWindow, void* view) {
+ WKWebView* webView = (WKWebView*)view;
+ [webView removeFromSuperview];
+}
diff --git a/v3/pkg/webcontentsview/webcontentsview_darwin_test.go b/v3/pkg/webcontentsview/webcontentsview_darwin_test.go
new file mode 100644
index 000000000..fc145b0e4
--- /dev/null
+++ b/v3/pkg/webcontentsview/webcontentsview_darwin_test.go
@@ -0,0 +1,78 @@
+//go:build darwin
+
+package webcontentsview
+
+import (
+ "testing"
+ "unsafe"
+
+ "github.com/wailsapp/wails/v3/pkg/application"
+)
+
+// Dummy mock window that satisfies the interface
+// and returns a nil NativeWindow so we can test the Attach nil-handling safely
+// without spinning up the full NSApplication runloop in a headless test environment.
+type mockWindow struct {
+ application.Window
+}
+
+func (m *mockWindow) NativeWindow() unsafe.Pointer {
+ return nil
+}
+
+func TestWebContentsView_APISurface(t *testing.T) {
+ // We primarily want to ensure that the API surface compiles and functions
+ // correctly at a struct level. Note: Full WKWebView instantiation without an NSApplication
+ // runloop will crash on macOS, so we test the struct wiring here instead of the native allocations.
+
+ options := WebContentsViewOptions{
+ Name: "TestBrowser",
+ URL: "https://example.com",
+ Bounds: application.Rect{
+ X: 0,
+ Y: 0,
+ Width: 800,
+ Height: 600,
+ },
+ WebPreferences: WebPreferences{
+ DevTools: application.Enabled,
+ Javascript: application.Enabled,
+ WebSecurity: application.Disabled, // Disable CORS
+ ZoomFactor: 1.2,
+ },
+ }
+
+ // Because calling NewWebContentsView invokes C.createWebContentsView which
+ // traps without a runloop during go test, we will just manually instantiate
+ // the Go wrapper to verify the methods.
+ view := &WebContentsView{
+ id: 1,
+ options: options,
+ impl: &mockWebContentsViewImpl{}, // Mock the impl to bypass Objective-C in headless test
+ }
+
+ // 2. Test SetBounds
+ view.SetBounds(application.Rect{X: 10, Y: 10, Width: 400, Height: 400})
+
+ // 3. Test SetURL
+ view.SetURL("https://google.com")
+
+ // 4. Test ExecJS
+ view.ExecJS("console.log('test');")
+
+ // 5. Test Attach and Detach using a mock window
+ win := &mockWindow{}
+ view.Attach(win)
+ view.Detach()
+
+ t.Log("macOS WebContentsView API surface tests passed successfully.")
+}
+
+type mockWebContentsViewImpl struct{}
+
+func (m *mockWebContentsViewImpl) setBounds(bounds application.Rect) {}
+func (m *mockWebContentsViewImpl) setURL(url string) {}
+func (m *mockWebContentsViewImpl) execJS(js string) {}
+func (m *mockWebContentsViewImpl) attach(window application.Window) {}
+func (m *mockWebContentsViewImpl) detach() {}
+func (m *mockWebContentsViewImpl) nativeView() unsafe.Pointer { return nil }
diff --git a/v3/pkg/webcontentsview/webcontentsview_ios.go b/v3/pkg/webcontentsview/webcontentsview_ios.go
new file mode 100644
index 000000000..076a3c267
--- /dev/null
+++ b/v3/pkg/webcontentsview/webcontentsview_ios.go
@@ -0,0 +1,23 @@
+//go:build ios
+
+package webcontentsview
+
+import (
+ "unsafe"
+ "github.com/wailsapp/wails/v3/pkg/application"
+)
+
+type iosWebContentsView struct {
+ parent *WebContentsView
+}
+
+func newWebContentsViewImpl(parent *WebContentsView) webContentsViewImpl {
+ return &iosWebContentsView{parent: parent}
+}
+
+func (w *iosWebContentsView) setBounds(bounds application.Rect) {}
+func (w *iosWebContentsView) setURL(url string) {}
+func (w *iosWebContentsView) execJS(js string) {}
+func (w *iosWebContentsView) attach(window application.Window) {}
+func (w *iosWebContentsView) detach() {}
+func (w *iosWebContentsView) nativeView() unsafe.Pointer { return nil }
diff --git a/v3/pkg/webcontentsview/webcontentsview_linux.go b/v3/pkg/webcontentsview/webcontentsview_linux.go
new file mode 100644
index 000000000..660ce6101
--- /dev/null
+++ b/v3/pkg/webcontentsview/webcontentsview_linux.go
@@ -0,0 +1,132 @@
+//go:build linux && cgo && !gtk4 && !android && !server
+
+package webcontentsview
+
+/*
+#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.1 gdk-3.0
+#include
+#include
+
+static void* createWebContentsView_linux(int x, int y, int w, int h, int devTools, int js, int images) {
+ WebKitSettings *settings = webkit_settings_new();
+
+ webkit_settings_set_enable_developer_extras(settings, devTools ? TRUE : FALSE);
+ webkit_settings_set_enable_javascript(settings, js ? TRUE : FALSE);
+ webkit_settings_set_auto_load_images(settings, images ? TRUE : FALSE);
+
+ GtkWidget *webview = webkit_web_view_new_with_settings(settings);
+ gtk_widget_set_size_request(webview, w, h);
+ return webview;
+}
+
+static void webContentsViewSetBounds_linux(void* view, void* parentFixed, int x, int y, int w, int h) {
+ GtkWidget *webview = (GtkWidget*)view;
+ gtk_widget_set_size_request(webview, w, h);
+ if (parentFixed != NULL) {
+ gtk_fixed_move(GTK_FIXED(parentFixed), webview, x, y);
+ }
+}
+
+static void webContentsViewSetURL_linux(void* view, const char* url) {
+ webkit_web_view_load_uri(WEBKIT_WEB_VIEW((GtkWidget*)view), url);
+}
+
+static void webContentsViewExecJS_linux(void* view, const char* js) {
+ webkit_web_view_run_javascript(WEBKIT_WEB_VIEW((GtkWidget*)view), js, NULL, NULL, NULL);
+}
+
+static void webContentsViewAttach_linux(void* window, void* view) {
+ // Attempt to add to the main container. Wails v3 usually uses a vbox.
+ GtkWindow *gtkWindow = GTK_WINDOW(window);
+ GtkWidget *child = gtk_bin_get_child(GTK_BIN(gtkWindow));
+ if (child != NULL && GTK_IS_BOX(child)) {
+ gtk_box_pack_start(GTK_BOX(child), GTK_WIDGET(view), FALSE, FALSE, 0);
+ gtk_widget_show(GTK_WIDGET(view));
+ }
+}
+
+static void webContentsViewDetach_linux(void* view) {
+ GtkWidget *webview = (GtkWidget*)view;
+ GtkWidget *parent = gtk_widget_get_parent(webview);
+ if (parent != NULL) {
+ gtk_container_remove(GTK_CONTAINER(parent), webview);
+ }
+}
+*/
+import "C"
+import (
+ "unsafe"
+ "github.com/wailsapp/wails/v3/pkg/application"
+)
+
+type linuxWebContentsView struct {
+ parent *WebContentsView
+ widget unsafe.Pointer
+}
+
+func newWebContentsViewImpl(parent *WebContentsView) webContentsViewImpl {
+ devTools := 1
+ if parent.options.WebPreferences.DevTools == application.Disabled {
+ devTools = 0
+ }
+
+ js := 1
+ if parent.options.WebPreferences.Javascript == application.Disabled {
+ js = 0
+ }
+
+ images := 1
+ if parent.options.WebPreferences.Images == application.Disabled {
+ images = 0
+ }
+
+ view := C.createWebContentsView_linux(
+ C.int(parent.options.Bounds.X),
+ C.int(parent.options.Bounds.Y),
+ C.int(parent.options.Bounds.Width),
+ C.int(parent.options.Bounds.Height),
+ C.int(devTools),
+ C.int(js),
+ C.int(images),
+ )
+
+ result := &linuxWebContentsView{
+ parent: parent,
+ widget: view,
+ }
+
+ return result
+}
+
+func (w *linuxWebContentsView) setBounds(bounds application.Rect) {
+ C.webContentsViewSetBounds_linux(w.widget, nil, C.int(bounds.X), C.int(bounds.Y), C.int(bounds.Width), C.int(bounds.Height))
+}
+
+func (w *linuxWebContentsView) setURL(url string) {
+ cUrl := C.CString(url)
+ defer C.free(unsafe.Pointer(cUrl))
+ C.webContentsViewSetURL_linux(w.widget, cUrl)
+}
+
+func (w *linuxWebContentsView) execJS(js string) {
+ cJs := C.CString(js)
+ defer C.free(unsafe.Pointer(cJs))
+ C.webContentsViewExecJS_linux(w.widget, cJs)
+}
+
+func (w *linuxWebContentsView) attach(window application.Window) {
+ if window.NativeWindow() != nil {
+ C.webContentsViewAttach_linux(window.NativeWindow(), w.widget)
+ if w.parent.options.URL != "" {
+ w.setURL(w.parent.options.URL)
+ }
+ }
+}
+
+func (w *linuxWebContentsView) detach() {
+ C.webContentsViewDetach_linux(w.widget)
+}
+
+func (w *linuxWebContentsView) nativeView() unsafe.Pointer {
+ return w.widget
+}
diff --git a/v3/pkg/webcontentsview/webcontentsview_windows.go b/v3/pkg/webcontentsview/webcontentsview_windows.go
new file mode 100644
index 000000000..6018db59e
--- /dev/null
+++ b/v3/pkg/webcontentsview/webcontentsview_windows.go
@@ -0,0 +1,98 @@
+//go:build windows
+
+package webcontentsview
+
+import (
+ "unsafe"
+
+ "github.com/wailsapp/go-webview2/pkg/edge"
+ "github.com/wailsapp/wails/v3/pkg/application"
+ "github.com/wailsapp/wails/v3/pkg/w32"
+)
+
+type windowsWebContentsView struct {
+ parent *WebContentsView
+ chromium *edge.Chromium
+ hwnd w32.HWND
+}
+
+func newWebContentsViewImpl(parent *WebContentsView) webContentsViewImpl {
+ chromium := edge.NewChromium()
+
+ result := &windowsWebContentsView{
+ parent: parent,
+ chromium: chromium,
+ }
+
+ // Default preferences based on the parent's options
+ settings, err := chromium.GetSettings()
+ if err == nil {
+ if parent.options.WebPreferences.DevTools != application.Disabled {
+ settings.PutAreDevToolsEnabled(true)
+ settings.PutAreDefaultContextMenusEnabled(true)
+ } else {
+ settings.PutAreDevToolsEnabled(false)
+ settings.PutAreDefaultContextMenusEnabled(false)
+ }
+
+ if parent.options.WebPreferences.Javascript != application.Disabled {
+ settings.PutIsScriptEnabled(true)
+ } else {
+ settings.PutIsScriptEnabled(false)
+ }
+
+ if parent.options.WebPreferences.ZoomFactor > 0 {
+ chromium.PutZoomFactor(parent.options.WebPreferences.ZoomFactor)
+ }
+ }
+
+ return result
+}
+
+func (w *windowsWebContentsView) setBounds(bounds application.Rect) {
+ if w.chromium != nil {
+ edgeBounds := edge.Rect{
+ Left: int32(bounds.X),
+ Top: int32(bounds.Y),
+ Right: int32(bounds.X + bounds.Width),
+ Bottom: int32(bounds.Y + bounds.Height),
+ }
+ w.chromium.ResizeWithBounds(edgeBounds)
+ }
+}
+
+func (w *windowsWebContentsView) setURL(url string) {
+ if w.chromium != nil {
+ w.chromium.Navigate(url)
+ }
+}
+
+func (w *windowsWebContentsView) execJS(js string) {
+ if w.chromium != nil {
+ w.chromium.Eval(js)
+ }
+}
+
+func (w *windowsWebContentsView) attach(window application.Window) {
+ if window.NativeWindow() != nil {
+ w.hwnd = w32.HWND(window.NativeWindow())
+ w.chromium.Embed(w.hwnd)
+
+ w.chromium.Resize()
+ w.chromium.Show()
+
+ if w.parent.options.URL != "" {
+ w.chromium.Navigate(w.parent.options.URL)
+ }
+ }
+}
+
+func (w *windowsWebContentsView) detach() {
+ if w.chromium != nil {
+ w.chromium.Hide()
+ }
+}
+
+func (w *windowsWebContentsView) nativeView() unsafe.Pointer {
+ return unsafe.Pointer(w.chromium)
+}
diff --git a/v3/pkg/webcontentsview/webpreferences.go b/v3/pkg/webcontentsview/webpreferences.go
new file mode 100644
index 000000000..00c3c5864
--- /dev/null
+++ b/v3/pkg/webcontentsview/webpreferences.go
@@ -0,0 +1,48 @@
+package webcontentsview
+
+import "github.com/leaanthony/u"
+
+// WebPreferences closely mirrors Electron's webPreferences for WebContentsView.
+type WebPreferences struct {
+ // DevTools enables or disables the developer tools. Default is true.
+ DevTools u.Bool
+
+ // Javascript enables or disables javascript execution. Default is true.
+ Javascript u.Bool
+
+ // WebSecurity enables or disables web security (CORS, etc.). Default is true.
+ WebSecurity u.Bool
+
+ // AllowRunningInsecureContent allows an https page to run http code. Default is false.
+ AllowRunningInsecureContent u.Bool
+
+ // Images enables or disables image loading. Default is true.
+ Images u.Bool
+
+ // TextAreasAreResizable controls whether text areas can be resized. Default is true.
+ TextAreasAreResizable u.Bool
+
+ // WebGL enables or disables WebGL. Default is true.
+ WebGL u.Bool
+
+ // Plugins enables or disables plugins. Default is false.
+ Plugins u.Bool
+
+ // ZoomFactor sets the default zoom factor of the page. Default is 1.0.
+ ZoomFactor float64
+
+ // NavigateOnDragDrop controls whether dropping files triggers navigation. Default is false.
+ NavigateOnDragDrop u.Bool
+
+ // DefaultFontSize sets the default font size. Default is 16.
+ DefaultFontSize int
+
+ // DefaultMonospaceFontSize sets the default monospace font size. Default is 13.
+ DefaultMonospaceFontSize int
+
+ // MinimumFontSize sets the minimum font size. Default is 0.
+ MinimumFontSize int
+
+ // DefaultEncoding sets the default character encoding. Default is "UTF-8".
+ DefaultEncoding string
+}
From 77b2cfdd2b48a16f1ff92c9398dbc8651845c221 Mon Sep 17 00:00:00 2001
From: Pandelis Zembashis
Date: Sun, 22 Feb 2026 03:35:04 +0000
Subject: [PATCH 2/6] feat: working
---
v3/WEBCONTENTSVIEW.md | 142 ++++++++++++++++++
v3/pkg/webcontentsview/README.md | 142 ++++++++++++++++++
.../webcontentsview/webcontentsview_darwin.m | 93 ++++++------
3 files changed, 329 insertions(+), 48 deletions(-)
create mode 100644 v3/WEBCONTENTSVIEW.md
create mode 100644 v3/pkg/webcontentsview/README.md
diff --git a/v3/WEBCONTENTSVIEW.md b/v3/WEBCONTENTSVIEW.md
new file mode 100644
index 000000000..88979f9f6
--- /dev/null
+++ b/v3/WEBCONTENTSVIEW.md
@@ -0,0 +1,142 @@
+# WebContentsView for Wails v3
+
+`WebContentsView` is an implementation of Electron's `WebContentsView` (formerly `BrowserView`) for Wails v3. It allows you to embed a fully native, secondary OS-level Webview directly over your Wails application UI.
+
+Unlike a standard HTML `