//go:build windows && !native_webview2loader package webviewloader import ( "fmt" "os" "path/filepath" "syscall" "unsafe" "github.com/wailsapp/go-webview2/pkg/combridge" "golang.org/x/sys/windows" ) func init() { UsingGoWebview2Loader = true preventEnvAndRegistryOverrides() } type webView2RunTimeType int32 const ( webView2RunTimeTypeInstalled webView2RunTimeType = 0x00 webView2RunTimeTypeRedistributable webView2RunTimeType = 0x01 ) // CreateCoreWebView2Environment creates an evergreen WebView2 Environment using the installed WebView2 Runtime version. // // This is equivalent to running CreateCoreWebView2EnvironmentWithOptions without any options. // For more information, see CreateCoreWebView2EnvironmentWithOptions. func CreateCoreWebView2Environment(environmentCompletedHandler ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler) error { return CreateCoreWebView2EnvironmentWithOptions(environmentCompletedHandler) } // CreateCoreWebView2EnvironmentWithOptions creates an environment with a custom version of WebView2 Runtime, // user data folder, and with or without additional options. // // See https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?#createcorewebview2environmentwithoptions func CreateCoreWebView2EnvironmentWithOptions(environmentCompletedHandler ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler, opts ...option) error { var params environmentOptions for _, opt := range opts { opt(¶ms) } var err error var dllPath string var runtimeType webView2RunTimeType if browserExecutableFolder := params.browserExecutableFolder; browserExecutableFolder != "" { runtimeType = webView2RunTimeTypeRedistributable dllPath, err = findEmbeddedClientDll(browserExecutableFolder) } else { runtimeType = webView2RunTimeTypeInstalled dllPath, _, err = findInstalledClientDll(params.preferCanary) } if err != nil { return err } return createWebViewEnvironmentWithClientDll(dllPath, runtimeType, params.userDataFolder, ¶ms, environmentCompletedHandler) } func createWebViewEnvironmentWithClientDll(lpLibFileName string, runtimeType webView2RunTimeType, userDataFolder string, envOptions *environmentOptions, envCompletedHandler ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler) error { if !filepath.IsAbs(lpLibFileName) { return fmt.Errorf("lpLibFileName must be absolute") } dll, err := windows.LoadDLL(lpLibFileName) if err != nil { return fmt.Errorf("Loading DLL failed: %w", err) } defer func() { canUnloadProc, err := dll.FindProc("DllCanUnloadNow") if err != nil { return } if r1, _, _ := canUnloadProc.Call(); r1 != windows.NO_ERROR { return } dll.Release() }() createProc, err := dll.FindProc("CreateWebViewEnvironmentWithOptionsInternal") if err != nil { return fmt.Errorf("Unable to find CreateWebViewEnvironmentWithOptionsInternal entrypoint: %w", err) } userDataPtr, err := windows.UTF16PtrFromString(userDataFolder) if err != nil { return err } envOptionsCom := combridge.New2[iCoreWebView2EnvironmentOptions, iCoreWebView2EnvironmentOptions2]( envOptions, envOptions) defer envOptionsCom.Close() envCompletedHandler = &environmentCreatedHandler{envCompletedHandler} envCompletedCom := combridge.New[iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandler](envCompletedHandler) defer envCompletedCom.Close() preventEnvAndRegistryOverrides() const unknown = 1 hr, _, err := createProc.Call( uintptr(unknown), uintptr(runtimeType), uintptr(unsafe.Pointer(userDataPtr)), uintptr(envOptionsCom.Ref()), uintptr(envCompletedCom.Ref())) if hr != 0 { if err == nil || err == windows.ERROR_SUCCESS { err = syscall.Errno(hr) } return err } return nil } type environmentCreatedHandler struct { originalHandler ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler } func (r *environmentCreatedHandler) EnvironmentCompleted(errorCode HRESULT, createdEnvironment *ICoreWebView2Environment) HRESULT { // The OpenWebview2Loader has some retry logic and retries once, didn't encounter any case when this would have been // needed during the development: https://github.com/jchv/OpenWebView2Loader/blob/master/Source/WebView2Loader.cpp#L202 if createdEnvironment != nil { // May or may not be necessary, but the official WebView2Loader seems to do it. iidICoreWebView2Environment := windows.GUID{ Data1: 0xb96d755e, Data2: 0x0319, Data3: 0x4e92, Data4: [8]byte{0xa2, 0x96, 0x23, 0x43, 0x6f, 0x46, 0xa1, 0xfc}, } if err := createdEnvironment.QueryInterface(&iidICoreWebView2Environment, &createdEnvironment); err != nil { createdEnvironment = nil errNo, ok := err.(syscall.Errno) if !ok { errNo = syscall.Errno(windows.E_FAIL) } errorCode = HRESULT(errNo) } } r.originalHandler.EnvironmentCompleted(errorCode, createdEnvironment) if createdEnvironment != nil { createdEnvironment.Release() } return HRESULT(windows.S_OK) } func preventEnvAndRegistryOverrides() { // Setting these env variables to empty string also prevents registry overrides because webview2 // checks for existence and not for empty value os.Setenv("WEBVIEW2_PIPE_FOR_SCRIPT_DEBUGGER", "") os.Setenv("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "") os.Setenv("WEBVIEW2_RELEASE_CHANNEL_PREFERENCE", "0") // The following seems not be be required because those are only used by the webview2loader which // in this case is implemented on our own. But nevertheless set them to empty to be consistent. os.Setenv("WEBVIEW2_BROWSER_EXECUTABLE_FOLDER", "") os.Setenv("WEBVIEW2_USER_DATA_FOLDER", "") }