diff --git a/v2/internal/frontend/desktop/windows/frontend.go b/v2/internal/frontend/desktop/windows/frontend.go index ed77c204cc5..cf6da9760a8 100644 --- a/v2/internal/frontend/desktop/windows/frontend.go +++ b/v2/internal/frontend/desktop/windows/frontend.go @@ -12,6 +12,7 @@ import ( "net/http" "net/http/httptest" "net/url" + "os" "runtime" "strings" "sync" @@ -428,6 +429,9 @@ func (f *Frontend) setupChromium() { if opts.WebviewGpuIsDisabled { chromium.AdditionalBrowserArgs = append(chromium.AdditionalBrowserArgs, "--disable-gpu") } + if opts.WebviewDisableRendererCodeIntegrity { + disableFeatues = append(disableFeatues, "RendererCodeIntegrity") + } } if len(disableFeatues) > 0 { @@ -442,6 +446,37 @@ func (f *Frontend) setupChromium() { w32.PostMessage(f.mainWindow.Handle(), w32.WM_KEYDOWN, uintptr(vkey), 0) return false } + chromium.ProcessFailedCallback = func(sender *edge.ICoreWebView2, args *edge.ICoreWebView2ProcessFailedEventArgs) { + kind, err := args.GetProcessFailedKind() + if err != nil { + f.logger.Error("GetProcessFailedKind: %s", err) + return + } + + f.logger.Error("WebVie2wProcess failed with kind %d", kind) + switch kind { + case edge.COREWEBVIEW2_PROCESS_FAILED_KIND_BROWSER_PROCESS_EXITED: + // => The app has to recreate a new WebView to recover from this failure. + messages := windows.DefaultMessages() + if f.frontendOptions.Windows != nil && f.frontendOptions.Windows.Messages != nil { + messages = f.frontendOptions.Windows.Messages + } + winc.Errorf(f.mainWindow, messages.WebView2ProcessCrash) + os.Exit(-1) + case edge.COREWEBVIEW2_PROCESS_FAILED_KIND_RENDER_PROCESS_EXITED, + edge.COREWEBVIEW2_PROCESS_FAILED_KIND_FRAME_RENDER_PROCESS_EXITED: + // => A new render process is created automatically and navigated to an error page. + // => Make sure that the error page is shown. + if !f.hasStarted { + // NavgiationCompleted didn't come in, make sure the chromium is shown + chromium.Show() + } + if !f.mainWindow.hasBeenShown { + // The window has never been shown, make sure to show it + f.ShowWindow() + } + } + } chromium.Embed(f.mainWindow.Handle()) chromium.Resize() diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/COREWEBVIEW2_PROCESS_FAILED_KIND.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/COREWEBVIEW2_PROCESS_FAILED_KIND.go new file mode 100644 index 00000000000..a7e9aa33985 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/COREWEBVIEW2_PROCESS_FAILED_KIND.go @@ -0,0 +1,49 @@ +//go:build windows + +package edge + +type COREWEBVIEW2_PROCESS_FAILED_KIND uint32 + +const ( + // Indicates that the browser process ended unexpectedly. The WebView + // automatically moves to the Closed state. The app has to recreate a new + // WebView to recover from this failure. + COREWEBVIEW2_PROCESS_FAILED_KIND_BROWSER_PROCESS_EXITED = 0 + + // Indicates that the main frame's render process ended unexpectedly. A new + // render process is created automatically and navigated to an error page. + // You can use the `Reload` method to try to reload the page that failed. + COREWEBVIEW2_PROCESS_FAILED_KIND_RENDER_PROCESS_EXITED = 1 + + // Indicates that the main frame's render process is unresponsive. + // + // Note that this does not seem to work right now. + // Does not fire for simple long running script case, the only related test + // SitePerProcessBrowserTest::NoCommitTimeoutForInvisibleWebContents is + // disabled. + COREWEBVIEW2_PROCESS_FAILED_KIND_RENDER_PROCESS_UNRESPONSIVE = 2 + + // Indicates that a frame-only render process ended unexpectedly. The process + // exit does not affect the top-level document, only a subset of the + // subframes within it. The content in these frames is replaced with an error + // page in the frame. + COREWEBVIEW2_PROCESS_FAILED_KIND_FRAME_RENDER_PROCESS_EXITED = 3 + + // Indicates that a utility process ended unexpectedly. + COREWEBVIEW2_PROCESS_FAILED_KIND_UTILITY_PROCESS_EXITED = 4 + + // Indicates that a sandbox helper process ended unexpectedly. + COREWEBVIEW2_PROCESS_FAILED_KIND_SANDBOX_HELPER_PROCESS_EXITED = 5 + + // Indicates that the GPU process ended unexpectedly. + COREWEBVIEW2_PROCESS_FAILED_KIND_GPU_PROCESS_EXITED = 6 + + // Indicates that a PPAPI plugin process ended unexpectedly. + COREWEBVIEW2_PROCESS_FAILED_KIND_PPAPI_PLUGIN_PROCESS_EXITED = 7 + + // Indicates that a PPAPI plugin broker process ended unexpectedly. + COREWEBVIEW2_PROCESS_FAILED_KIND_PPAPI_BROKER_PROCESS_EXITED = 8 + + // Indicates that a process of unspecified kind ended unexpectedly. + COREWEBVIEW2_PROCESS_FAILED_KIND_UNKNOWN_PROCESS_EXITED = 9 +) diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2ProcessFailedEventArgs.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2ProcessFailedEventArgs.go new file mode 100644 index 00000000000..b6d3cda1bcd --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2ProcessFailedEventArgs.go @@ -0,0 +1,41 @@ +//go:build windows + +package edge + +import ( + "fmt" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +type _ICoreWebView2ProcessFailedEventArgsVtbl struct { + _IUnknownVtbl + GetProcessFailedKind ComProc +} + +type ICoreWebView2ProcessFailedEventArgs struct { + vtbl *_ICoreWebView2ProcessFailedEventArgsVtbl +} + +func (i *ICoreWebView2ProcessFailedEventArgs) GetProcessFailedKind() (COREWEBVIEW2_PROCESS_FAILED_KIND, error) { + kind := COREWEBVIEW2_PROCESS_FAILED_KIND(0xffffffff) + hr, _, err := i.vtbl.GetProcessFailedKind.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&kind)), + ) + + if windows.Handle(hr) != windows.S_OK { + return 0, syscall.Errno(hr) + } + + if kind == 0xffffffff { + if err == nil { + err = fmt.Errorf("unknown error") + } + return 0, err + } + + return kind, nil +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2ProcessFailedEventHandler.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2ProcessFailedEventHandler.go new file mode 100644 index 00000000000..fc8c7369c2c --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2ProcessFailedEventHandler.go @@ -0,0 +1,53 @@ +//go:build windows + +package edge + +type _ICoreWebView2ProcessFailedEventHandlerVtbl struct { + _IUnknownVtbl + Invoke ComProc +} + +type ICoreWebView2ProcessFailedEventHandler struct { + vtbl *_ICoreWebView2ProcessFailedEventHandlerVtbl + impl _ICoreWebView2ProcessFailedEventHandlerImpl +} + +func (i *ICoreWebView2ProcessFailedEventHandler) AddRef() uintptr { + return i.AddRef() +} +func _ICoreWebView2ProcessFailedEventHandlerIUnknownQueryInterface(this *ICoreWebView2ProcessFailedEventHandler, refiid, object uintptr) uintptr { + return this.impl.QueryInterface(refiid, object) +} + +func _ICoreWebView2ProcessFailedEventHandlerIUnknownAddRef(this *ICoreWebView2ProcessFailedEventHandler) uintptr { + return this.impl.AddRef() +} + +func _ICoreWebView2ProcessFailedEventHandlerIUnknownRelease(this *ICoreWebView2ProcessFailedEventHandler) uintptr { + return this.impl.Release() +} + +func _ICoreWebView2ProcessFailedEventHandlerInvoke(this *ICoreWebView2ProcessFailedEventHandler, sender *ICoreWebView2, args *ICoreWebView2ProcessFailedEventArgs) uintptr { + return this.impl.ProcessFailed(sender, args) +} + +type _ICoreWebView2ProcessFailedEventHandlerImpl interface { + _IUnknownImpl + ProcessFailed(sender *ICoreWebView2, args *ICoreWebView2ProcessFailedEventArgs) uintptr +} + +var _ICoreWebView2ProcessFailedEventHandlerFn = _ICoreWebView2ProcessFailedEventHandlerVtbl{ + _IUnknownVtbl{ + NewComProc(_ICoreWebView2ProcessFailedEventHandlerIUnknownQueryInterface), + NewComProc(_ICoreWebView2ProcessFailedEventHandlerIUnknownAddRef), + NewComProc(_ICoreWebView2ProcessFailedEventHandlerIUnknownRelease), + }, + NewComProc(_ICoreWebView2ProcessFailedEventHandlerInvoke), +} + +func newICoreWebView2ProcessFailedEventHandler(impl _ICoreWebView2ProcessFailedEventHandlerImpl) *ICoreWebView2ProcessFailedEventHandler { + return &ICoreWebView2ProcessFailedEventHandler{ + vtbl: &_ICoreWebView2ProcessFailedEventHandlerFn, + impl: impl, + } +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/chromium.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/chromium.go index 219aca35cbc..376891bb1cf 100644 --- a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/chromium.go +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/chromium.go @@ -31,6 +31,7 @@ type Chromium struct { webResourceRequested *iCoreWebView2WebResourceRequestedEventHandler acceleratorKeyPressed *ICoreWebView2AcceleratorKeyPressedEventHandler navigationCompleted *ICoreWebView2NavigationCompletedEventHandler + processFailed *ICoreWebView2ProcessFailedEventHandler environment *ICoreWebView2Environment @@ -50,6 +51,7 @@ type Chromium struct { MessageCallback func(string) WebResourceRequestedCallback func(request *ICoreWebView2WebResourceRequest, args *ICoreWebView2WebResourceRequestedEventArgs) NavigationCompletedCallback func(sender *ICoreWebView2, args *ICoreWebView2NavigationCompletedEventArgs) + ProcessFailedCallback func(sender *ICoreWebView2, args *ICoreWebView2ProcessFailedEventArgs) AcceleratorKeyCallback func(uint) bool } @@ -73,6 +75,7 @@ func NewChromium() *Chromium { e.webResourceRequested = newICoreWebView2WebResourceRequestedEventHandler(e) e.acceleratorKeyPressed = newICoreWebView2AcceleratorKeyPressedEventHandler(e) e.navigationCompleted = newICoreWebView2NavigationCompletedEventHandler(e) + e.processFailed = newICoreWebView2ProcessFailedEventHandler(e) e.permissions = make(map[CoreWebView2PermissionKind]CoreWebView2PermissionState) return e @@ -253,6 +256,11 @@ func (e *Chromium) CreateCoreWebView2ControllerCompleted(res uintptr, controller uintptr(unsafe.Pointer(e.navigationCompleted)), uintptr(unsafe.Pointer(&token)), ) + e.webview.vtbl.AddProcessFailed.Call( + uintptr(unsafe.Pointer(e.webview)), + uintptr(unsafe.Pointer(e.processFailed)), + uintptr(unsafe.Pointer(&token)), + ) e.controller.AddAcceleratorKeyPressed(e.acceleratorKeyPressed, &token) @@ -376,6 +384,13 @@ func (e *Chromium) NavigationCompleted(sender *ICoreWebView2, args *ICoreWebView return 0 } +func (e *Chromium) ProcessFailed(sender *ICoreWebView2, args *ICoreWebView2ProcessFailedEventArgs) uintptr { + if e.ProcessFailedCallback != nil { + e.ProcessFailedCallback(sender, args) + } + return 0 +} + func (e *Chromium) NotifyParentWindowPositionChanged() error { //It looks like the wndproc function is called before the controller initialization is complete. //Because of this the controller is nil diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/corewebview2.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/corewebview2.go index c507d28ae20..6f1afbd87e7 100644 --- a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/corewebview2.go +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/corewebview2.go @@ -478,6 +478,19 @@ func (i *ICoreWebView2) AddNavigationCompleted(eventHandler *ICoreWebView2Naviga return nil } +func (i *ICoreWebView2) AddProcessFailed(eventHandler *ICoreWebView2ProcessFailedEventHandler, token *_EventRegistrationToken) error { + var err error + _, _, err = i.vtbl.AddProcessFailed.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(eventHandler)), + uintptr(unsafe.Pointer(&token)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + func (i *ICoreWebView2) OpenDevToolsWindow() error { var err error _, _, err = i.vtbl.OpenDevToolsWindow.Call( diff --git a/v2/pkg/options/windows/windows.go b/v2/pkg/options/windows/windows.go index 9a0f5c733c4..ea6fdc35e1d 100644 --- a/v2/pkg/options/windows/windows.go +++ b/v2/pkg/options/windows/windows.go @@ -13,6 +13,7 @@ type Messages struct { PressOKToInstall string ContactAdmin string InvalidFixedWebview2 string + WebView2ProcessCrash string } const ( @@ -102,6 +103,16 @@ type Options struct { // WebviewGpuIsDisabled is used to enable / disable GPU acceleration for the webview WebviewGpuIsDisabled bool + + // WebviewDisableRendererCodeIntegrity disables the `RendererCodeIntegrity` of WebView2. Some Security Endpoint + // Protection Software inject themself into the WebView2 with unsigned or wrongly signed dlls, which is not allowed + // and will stop the WebView2 processes. Those security software need an update to fix this issue or one can disable + // the integrity check with this flag. + // + // The event viewer log contains `Code Integrity Errors` like mentioned here: https://github.com/MicrosoftEdge/WebView2Feedback/issues/2051 + // + // !! Please keep in mind when disabling this feature, this also allows malicious software to inject into the WebView2 !! + WebviewDisableRendererCodeIntegrity bool } func DefaultMessages() *Messages { @@ -116,5 +127,6 @@ func DefaultMessages() *Messages { PressOKToInstall: "Press Ok to install.", ContactAdmin: "The WebView2 runtime is required to run this application. Please contact your system administrator.", InvalidFixedWebview2: "The WebView2 runtime is manually specified, but It is not valid. Check minimum required version and webview2 path.", + WebView2ProcessCrash: "The WebView2 process crashed and the application needs to be restarted.", } } diff --git a/website/src/pages/changelog.mdx b/website/src/pages/changelog.mdx index 513759161ef..015f2e3f12e 100644 --- a/website/src/pages/changelog.mdx +++ b/website/src/pages/changelog.mdx @@ -24,6 +24,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added support for WebKit2GTK 2.40+ on Linux. This brings additional features for the [AssetServer](/docs/reference/options#assetserver), like support for HTTP Request Bodies. The app must be compiled with the Go build tag `webkit2_40` to activate support for this features. This also bumps the minimum requirement of WebKit2GTK to 2.40 for your app. Added by @stffabi in this [PR](https://github.com/wailsapp/wails/pull/2592) - macOS: Added Window menu role with well known shortcuts "Minimize, Full-Screen and Zoom". Added by @stffabi in [PR](https://github.com/wailsapp/wails/pull/2586) - macOS: Added "Hide, Hide Others, Show All“ to appmenu. Added by @stffabi in [PR](https://github.com/wailsapp/wails/pull/2586) +- Windows: Added flag to disable the WebView2 `RendererCodeIntegrity` checks, please see the comment on the flag for more information. Added by @stffabi in [PR](https://github.com/wailsapp/wails/pull/2627) +- Windows: Added handling of WebView2 process crashes, for unrecoverable errors an error message is shown that the app needs to be restarted. If an error occurs that shows a chromium error page, make sure the Window and the WebView2 is visible. Added by @stffabi in [PR](https://github.com/wailsapp/wails/pull/2627) ### Changed