diff --git a/v2/internal/frontend/desktop/windows/frontend.go b/v2/internal/frontend/desktop/windows/frontend.go index b83541afbc6..16d9b6e8103 100644 --- a/v2/internal/frontend/desktop/windows/frontend.go +++ b/v2/internal/frontend/desktop/windows/frontend.go @@ -352,8 +352,9 @@ func (f *Frontend) Quit() { func (f *Frontend) setupChromium() { chromium := edge.NewChromium() f.chromium = chromium - if opts := f.frontendOptions.Windows; opts != nil && opts.WebviewUserDataPath != "" { + if opts := f.frontendOptions.Windows; opts != nil { chromium.DataPath = opts.WebviewUserDataPath + chromium.BrowserPath = opts.WebviewBrowserPath } chromium.MessageCallback = f.processMessage chromium.WebResourceRequestedCallback = f.processRequest 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 ad046a5c5ae..78514091705 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 @@ -4,6 +4,7 @@ package edge import ( + "errors" "log" "os" "path/filepath" @@ -30,8 +31,9 @@ type Chromium struct { environment *ICoreWebView2Environment // Settings - Debug bool - DataPath string + Debug bool + DataPath string + BrowserPath string // permissions permissions map[CoreWebView2PermissionKind]CoreWebView2PermissionState @@ -84,7 +86,27 @@ func (e *Chromium) Embed(hwnd uintptr) bool { dataPath = filepath.Join(os.Getenv("AppData"), currentExeName) } - res, err := createCoreWebView2EnvironmentWithOptions(nil, windows.StringToUTF16Ptr(dataPath), 0, e.envCompleted) + var browserPathPtr *uint16 = nil + if e.BrowserPath != "" { + if _, err := os.Stat(e.BrowserPath); !errors.Is(err, os.ErrNotExist) { + browserPathPtr, err = windows.UTF16PtrFromString(e.BrowserPath) + if err != nil { + log.Printf("Error calling UTF16PtrFromString for %s: %v", e.BrowserPath, err) + return false + } + } else { + log.Printf("Browser path %s does not exist", e.BrowserPath) + return false + } + } + + dataPathPtr, err := windows.UTF16PtrFromString(dataPath) + if err != nil { + log.Printf("Error calling UTF16PtrFromString for %s: %v", dataPath, err) + return false + } + + res, err := createCoreWebView2EnvironmentWithOptions(browserPathPtr, dataPathPtr, 0, e.envCompleted) if err != nil { log.Printf("Error calling Webview2Loader: %v", err) return false 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 b25181795b3..e8ae8fab506 100644 --- a/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/module.go +++ b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/module.go @@ -54,17 +54,26 @@ func CompareBrowserVersions(v1 string, v2 string) (int, error) { return int(result), nil } -// GetInstalledVersion returns the installed version of the webview2 runtime. +// GetWebviewVersion returns version of the webview2 runtime. +// 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 GetInstalledVersion() (string, error) { +func GetWebviewVersion(path string) (string, error) { err := loadFromMemory() if err != nil { return "", err } + var browserPath *uint16 = nil + if path != "" { + browserPath, err = windows.UTF16PtrFromString(path) + if err != nil { + return "", fmt.Errorf("error calling UTF16PtrFromString for %s: %v", path, err) + } + } + var result *uint16 res, _, err := memGetAvailableCoreWebView2BrowserVersionString.Call( - uint64(uintptr(unsafe.Pointer(nil))), + uint64(uintptr(unsafe.Pointer(browserPath))), uint64(uintptr(unsafe.Pointer(&result)))) if res != 0 { diff --git a/v2/internal/system/system_windows.go b/v2/internal/system/system_windows.go index 4bb6d670732..3d1b8dc79cb 100644 --- a/v2/internal/system/system_windows.go +++ b/v2/internal/system/system_windows.go @@ -28,8 +28,7 @@ func (i *Info) discover() error { } func checkWebView2() *packagemanager.Dependancy { - - version, _ := webviewloader.GetInstalledVersion() + version, _ := webviewloader.GetWebviewVersion("") installed := version != "" return &packagemanager.Dependancy{ diff --git a/v2/internal/wv2installer/wv2installer.go b/v2/internal/wv2installer/wv2installer.go index 41e2beaaf97..1549cf1ba11 100644 --- a/v2/internal/wv2installer/wv2installer.go +++ b/v2/internal/wv2installer/wv2installer.go @@ -3,6 +3,7 @@ package wv2installer import ( + "fmt" "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2/webviewloader" "github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/options/windows" @@ -24,7 +25,14 @@ func Process(appoptions *options.App) (string, error) { } installStatus := needsInstalling - installedVersion, err := webviewloader.GetInstalledVersion() + + // Override version check for manually specified webview path if present + var webviewPath = "" + if opts := appoptions.Windows; opts != nil && opts.WebviewBrowserPath != "" { + webviewPath = opts.WebviewBrowserPath + } + + installedVersion, err := webviewloader.GetWebviewVersion(webviewPath) if err != nil { return "", err } @@ -42,5 +50,10 @@ func Process(appoptions *options.App) (string, error) { } } + // Force error strategy if webview is manually specified + if webviewPath != "" { + return installedVersion, fmt.Errorf(messages.InvalidFixedWebview2) + } + return installedVersion, doInstallationStrategy(installStatus, messages) } diff --git a/v2/pkg/options/windows/windows.go b/v2/pkg/options/windows/windows.go index 146d23d3aec..5c18d1737f8 100644 --- a/v2/pkg/options/windows/windows.go +++ b/v2/pkg/options/windows/windows.go @@ -12,6 +12,7 @@ type Messages struct { DownloadPage string PressOKToInstall string ContactAdmin string + InvalidFixedWebview2 string } const ( @@ -71,6 +72,9 @@ type Options struct { // If the path is not valid, a messagebox will be displayed with the error and the app will exit with error code. WebviewUserDataPath string + // Path to the directory with WebView2 executables. If empty WebView2 installed in the system will be used. + WebviewBrowserPath string + // Dark/Light or System Default Theme Theme Theme @@ -104,5 +108,6 @@ func DefaultMessages() *Messages { DownloadPage: "This application requires the WebView2 runtime. Press OK to open the download page. Minimum version required: ", 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.", } } diff --git a/website/docs/guides/windows.mdx b/website/docs/guides/windows.mdx index e0ec17d5f6d..520f0bd8611 100644 --- a/website/docs/guides/windows.mdx +++ b/website/docs/guides/windows.mdx @@ -35,3 +35,20 @@ up to the user. ### Error If no suitable runtime is found, an error is given to the user and no further action taken. + +## Fixed version runtime + +Another way of dealing with webview2 dependency is shipping it yourself. +You can download [fixed version runtime](https://developer.microsoft.com/ru-ru/microsoft-edge/webview2/#download-section) and bundle or download it with your application. + +Also, you should specify path to fixed version of webview2 runtime in the `windows.Options` structure when launching wails. + +```go + wails.Run(&options.App{ + Windows: &windows.Options{ + WebviewBrowserPath: "", + }, + }) +``` + +Note: When `WebviewBrowserPath` is specified, `error` strategy will be forced in case of minimal required version mismatch or invalid path to a runtime. \ No newline at end of file diff --git a/website/docs/reference/options.mdx b/website/docs/reference/options.mdx index 96b4d0f173e..c557ef612e4 100644 --- a/website/docs/reference/options.mdx +++ b/website/docs/reference/options.mdx @@ -48,6 +48,7 @@ func main() { DisableWindowIcon: false, DisableFramelessWindowDecorations: false, WebviewUserDataPath: "", + WebviewBrowserPath: "", Theme: windows.SystemDefault, CustomTheme: &windows.ThemeSettings{ DarkModeTitleBar: windows.RGB(20, 20, 20), @@ -430,6 +431,19 @@ Type: string This defines the path where the WebView2 stores the user data. If empty `%APPDATA%\[BinaryName.exe]` will be used. +### WebviewBrowserPath + +Name: WebviewBrowserPath + +Type: string + +This defines the path to a directory with WebView2 executable files and libraries. If empty, webview2 installed in the system will be used. + +Important information about distribution of fixed version runtime: +- [How to get and extract runtime](https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#details-about-the-fixed-version-runtime-distribution-mode) +- [Known issues for fixed version](https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#known-issues-for-fixed-version) +- [The path of fixed version of the WebView2 Runtime should not contain \Edge\Application\.](https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.1245.22#createcorewebview2environmentwithoptions) + ### Theme Name: Theme