diff --git a/v2/internal/frontend/desktop/windows/go-webview2/webview2loader/LICENSE b/v2/internal/frontend/desktop/windows/go-webview2/webview2loader/LICENSE new file mode 100644 index 000000000..af1894c87 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/webview2loader/LICENSE @@ -0,0 +1,16 @@ +ISC License (ISC) + +Copyright (c) 2020 John Chadwick +Copyright (c) 2022 Wails Project Developers + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/v2/internal/frontend/desktop/windows/go-webview2/webview2loader/README.md b/v2/internal/frontend/desktop/windows/go-webview2/webview2loader/README.md new file mode 100644 index 000000000..64d8c4a9a --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/webview2loader/README.md @@ -0,0 +1,15 @@ +# GoWebView2Loader + +GoWebView2Loader is a port of [OpenWebView2Loader](https://github.com/jchv/OpenWebView2Loader) to Go. + +It is intended to be feature-complete in the near future with the original WebView2Loader distributed with +the WebView2 NuGet package. + +## Status + +- [ ] CompareBrowserVersions +- [ ] CreateCoreWebView2Environment +- [ ] CreateCoreWebView2EnvironmentWithOptions +- [ ] GetAvailableCoreWebView2BrowserVersionString + - [ ] Feature Complete + - [x] Fixed Runtime support diff --git a/v2/internal/frontend/desktop/windows/go-webview2/webview2loader/syscall.go b/v2/internal/frontend/desktop/windows/go-webview2/webview2loader/syscall.go new file mode 100644 index 000000000..3b2102518 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/webview2loader/syscall.go @@ -0,0 +1,128 @@ +package webview2loader + +import ( + "fmt" + "syscall" + "unicode/utf16" + "unsafe" + + "golang.org/x/sys/windows" +) + +var ( + modkernel32 = windows.NewLazySystemDLL("kernel32.dll") + procGlobalAlloc = modkernel32.NewProc("GlobalAlloc") + procGlobalFree = modkernel32.NewProc("GlobalFree") + + modversion = windows.NewLazySystemDLL("version.dll") + procGetFileVersionInfoSize = modversion.NewProc("GetFileVersionInfoSizeW") + procGetFileVersionInfo = modversion.NewProc("GetFileVersionInfoW") + procVerQueryValue = modversion.NewProc("VerQueryValueW") +) + +func getFileVersionInfo(path string) ([]byte, error) { + lptstrFilename, err := syscall.UTF16PtrFromString(path) + if err != nil { + return nil, err + } + + size, _, err := procGetFileVersionInfoSize.Call( + uintptr(unsafe.Pointer(lptstrFilename)), + 0, + ) + + err = maskErrorSuccess(err) + if size == 0 && err == nil { + err = fmt.Errorf("GetFileVersionInfoSize failed") + } + + if err != nil { + return nil, err + } + + data := make([]byte, size) + ret, _, err := procGetFileVersionInfo.Call( + uintptr(unsafe.Pointer(lptstrFilename)), + 0, + uintptr(size), + uintptr(unsafe.Pointer(&data[0])), + ) + + err = maskErrorSuccess(err) + if ret == 0 && err == nil { + err = fmt.Errorf("GetFileVersionInfo failed") + } + + if err != nil { + return nil, err + } + return data, nil +} + +func verQueryValueString(block []byte, subBlock string) (string, error) { + // Allocate memory from native side to make sure the block doesn't get moved + // because we get a pointer into that memory block from the native verQueryValue + // call back. + pBlock := globalAlloc(0, uint32(len(block))) + defer globalFree(unsafe.Pointer(pBlock)) + + // Copy the memory region into native side memory + copy(unsafe.Slice((*byte)(pBlock), len(block)), block) + + lpSubBlock, err := syscall.UTF16PtrFromString(subBlock) + if err != nil { + return "", err + } + + var lplpBuffer unsafe.Pointer + var puLen uint + ret, _, err := procVerQueryValue.Call( + uintptr(pBlock), + uintptr(unsafe.Pointer(lpSubBlock)), + uintptr(unsafe.Pointer(&lplpBuffer)), + uintptr(unsafe.Pointer(&puLen)), + ) + + err = maskErrorSuccess(err) + if ret == 0 && err == nil { + err = fmt.Errorf("VerQueryValue failed") + } + + if err != nil { + return "", err + } + + if puLen <= 1 { + return "", nil + } + puLen -= 1 // Remove Null-Terminator + + wchar := unsafe.Slice((*uint16)(lplpBuffer), puLen) + return string(utf16.Decode(wchar)), nil +} + +func globalAlloc(uFlags uint, dwBytes uint32) unsafe.Pointer { + ret, _, _ := procGlobalAlloc.Call( + uintptr(uFlags), + uintptr(dwBytes)) + + if ret == 0 { + panic("globalAlloc failed") + } + + return unsafe.Pointer(ret) +} + +func globalFree(data unsafe.Pointer) { + ret, _, _ := procGlobalFree.Call(uintptr(data)) + if ret != 0 { + panic("globalFree failed") + } +} + +func maskErrorSuccess(err error) error { + if err == windows.ERROR_SUCCESS { + return nil + } + return err +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/webview2loader/versions.go b/v2/internal/frontend/desktop/windows/go-webview2/webview2loader/versions.go new file mode 100644 index 000000000..910bd5c5b --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/webview2loader/versions.go @@ -0,0 +1,69 @@ +package webview2loader + +import ( + "fmt" + "os" + "path/filepath" + "runtime" +) + +// GetAvailableCoreWebView2BrowserVersionString get the browser version info including channel name. +func GetAvailableCoreWebView2BrowserVersionString(browserExecutableFolder string) (string, error) { + if browserExecutableFolder != "" { + clientPath, err := findEmbeddedClientDll(browserExecutableFolder) + if err != nil { + return "", err + } + + return findEmbeddedBrowserVersion(clientPath) + } + + return "", fmt.Errorf("not implemented yet for empty browserExecutableFolder ") +} + +func findEmbeddedBrowserVersion(filename string) (string, error) { + block, err := getFileVersionInfo(filename) + if err != nil { + return "", err + } + + info, err := verQueryValueString(block, "\\StringFileInfo\\040904B0\\ProductVersion") + if err != nil { + return "", err + } + + return info, nil +} + +func findEmbeddedClientDll(embeddedEdgeSubFolder string) (outClientPath string, err error) { + if !filepath.IsAbs(embeddedEdgeSubFolder) { + exe, err := os.Executable() + if err != nil { + return "", err + } + + embeddedEdgeSubFolder = filepath.Join(filepath.Dir(exe), embeddedEdgeSubFolder) + } + + return findClientDllInFolder(embeddedEdgeSubFolder) +} + +func findClientDllInFolder(folder string) (string, error) { + arch := "" + switch runtime.GOARCH { + case "arm64": + arch = "arm64" + case "amd64": + arch = "x64" + case "386": + arch = "x86" + default: + return "", fmt.Errorf("Unsupported architecture") + } + + dllPath := filepath.Join(folder, "EBWebView", arch, "EmbeddedBrowserWebView.dll") + if _, err := os.Stat(dllPath); err != nil { + return "", err + } + return dllPath, nil +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/module.go b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/module.go index 41f798bbc..86bfe0275 100644 --- a/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/module.go +++ b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/module.go @@ -1,11 +1,15 @@ package webviewloader import ( + "errors" "fmt" + "os" "sync" "unsafe" "github.com/jchv/go-winloader" + "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2/webview2loader" + "golang.org/x/sys/windows" ) @@ -28,9 +32,10 @@ const ( ) // CompareBrowserVersions will compare the 2 given versions and return: -// Less than zero: v1 < v2 -// zero: v1 == v2 -// Greater than zero: v1 > v2 +// +// Less than zero: v1 < v2 +// zero: v1 == v2 +// Greater than zero: v1 > v2 func CompareBrowserVersions(v1 string, v2 string) (int, error) { _v1, err := windows.UTF16PtrFromString(v1) if err != nil { @@ -62,6 +67,19 @@ func CompareBrowserVersions(v1 string, v2 string) (int, error) { // If path is empty, it will try to find installed webview2 is the system. // If there is no version installed, a blank string is returned. func GetWebviewVersion(path string) (string, error) { + if path != "" { + // The default implementation fails if CGO and a fixed browser path is used. It's caused by the go-winloader + // which loads the native DLL from memory. + // Use the new GoWebView2Loader in this case, in the future we will make GoWebView2Loader + // feature-complete and remove the use of the native DLL and go-winloader. + version, err := webview2loader.GetAvailableCoreWebView2BrowserVersionString(path) + if errors.Is(err, os.ErrNotExist) { + // Webview2 is not found + return "", nil + } + return version, nil + } + err := loadFromMemory() if err != nil { return "", err