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 `