| 1 | //go:build windows && !native_webview2loader |
| 2 | |
| 3 | package webviewloader |
| 4 | |
| 5 | import ( |
| 6 | "unicode/utf16" |
| 7 | "unsafe" |
| 8 | |
| 9 | "github.com/wailsapp/go-webview2/pkg/combridge" |
| 10 | "golang.org/x/sys/windows" |
| 11 | ) |
| 12 | |
| 13 | // WithBrowserExecutableFolder to specify whether WebView2 controls use a fixed or installed version |
| 14 | // of the WebView2 Runtime that exists on a user machine. |
| 15 | // |
| 16 | // To use a fixed version of the WebView2 Runtime, |
| 17 | // pass the folder path that contains the fixed version of the WebView2 Runtime. |
| 18 | // BrowserExecutableFolder supports both relative (to the application's executable) and absolute files paths. |
| 19 | // To create WebView2 controls that use the installed version of the WebView2 Runtime that exists on user |
| 20 | // machines, pass a empty string to WithBrowserExecutableFolder. In this scenario, the API tries to find a |
| 21 | // compatible version of the WebView2 Runtime that is installed on the user machine (first at the machine level, |
| 22 | // and then per user) using the selected channel preference. The path of fixed version of the WebView2 Runtime |
| 23 | // should not contain \Edge\Application\. When such a path is used, the API fails with HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED). |
| 24 | func WithBrowserExecutableFolder(folder string) option { |
| 25 | return func(wvep *environmentOptions) { |
| 26 | wvep.browserExecutableFolder = folder |
| 27 | } |
| 28 | } |
| 29 | |
| 30 | // WithUserDataFolder specifies to user data folder location for WebView2 |
| 31 | // |
| 32 | // You may specify the userDataFolder to change the default user data folder location for WebView2. |
| 33 | // The path is either an absolute file path or a relative file path that is interpreted as relative |
| 34 | // to the compiled code for the current process. |
| 35 | // Dhe default user data ({Executable File Name}.WebView2) folder is created in the same directory |
| 36 | // next to the compiled code for the app. WebView2 creation fails if the compiled code is running |
| 37 | // in a directory in which the process does not have permission to create a new directory. |
| 38 | // The app is responsible to clean up the associated user data folder when it is done. |
| 39 | func WithUserDataFolder(folder string) option { |
| 40 | return func(wvep *environmentOptions) { |
| 41 | wvep.userDataFolder = folder |
| 42 | } |
| 43 | } |
| 44 | |
| 45 | // WithAdditionalBrowserArguments changes the behavior of the WebView. |
| 46 | // |
| 47 | // The arguments are passed to the |
| 48 | // browser process as part of the command. For more information about |
| 49 | // using command-line switches with Chromium browser processes, navigate to |
| 50 | // [Run Chromium with Flags][ChromiumDevelopersHowTosRunWithFlags]. |
| 51 | // The value appended to a switch is appended to the browser process, for |
| 52 | // example, in `--edge-webview-switches=xxx` the value is `xxx`. If you |
| 53 | // specify a switch that is important to WebView functionality, it is |
| 54 | // ignored, for example, `--user-data-dir`. Specific features are disabled |
| 55 | // internally and blocked from being enabled. If a switch is specified |
| 56 | // multiple times, only the last instance is used. |
| 57 | // |
| 58 | // \> [!NOTE]\n\> A merge of the different values of the same switch is not attempted, |
| 59 | // except for disabled and enabled features. The features specified by |
| 60 | // `--enable-features` and `--disable-features` are merged with simple |
| 61 | // logic.\n\> * The features is the union of the specified features |
| 62 | // and built-in features. If a feature is disabled, it is removed from the |
| 63 | // enabled features list. |
| 64 | // |
| 65 | // If you specify command-line switches and use the |
| 66 | // `additionalBrowserArguments` parameter, the `--edge-webview-switches` |
| 67 | // value takes precedence and is processed last. If a switch fails to |
| 68 | // parse, the switch is ignored. The default state for the operation is |
| 69 | // to run the browser process with no extra flags. |
| 70 | // |
| 71 | // [ChromiumDevelopersHowTosRunWithFlags]: https://www.chromium.org/developers/how-tos/run-chromium-with-flags "Run Chromium with flags | The Chromium Projects" |
| 72 | func WithAdditionalBrowserArguments(args string) option { |
| 73 | return func(wvep *environmentOptions) { |
| 74 | wvep.additionalBrowserArguments = args |
| 75 | } |
| 76 | } |
| 77 | |
| 78 | // WithLanguage sets the default display language for WebView. |
| 79 | // |
| 80 | // It applies to browser UI such as |
| 81 | // context menu and dialogs. It also applies to the `accept-languages` HTTP |
| 82 | // header that WebView sends to websites. It is in the format of |
| 83 | // |
| 84 | // `language[-country]` where `language` is the 2-letter code from |
| 85 | // [ISO 639][ISO639LanguageCodesHtml] |
| 86 | // and `country` is the |
| 87 | // 2-letter code from |
| 88 | // [ISO 3166][ISOStandard72482Html]. |
| 89 | // |
| 90 | // [ISO639LanguageCodesHtml]: https://www.iso.org/iso-639-language-codes.html "ISO 639 | ISO" |
| 91 | // [ISOStandard72482Html]: https://www.iso.org/standard/72482.html "ISO 3166-1:2020 | ISO" |
| 92 | func WithLanguage(lang string) option { |
| 93 | return func(wvep *environmentOptions) { |
| 94 | wvep.language = lang |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | // WithTargetCompatibleBrowserVersion secifies the version of the WebView2 Runtime binaries required to be |
| 99 | // compatible with your app. |
| 100 | // |
| 101 | // This defaults to the WebView2 Runtime version |
| 102 | // that corresponds with the version of the SDK the app is using. The |
| 103 | // format of this value is the same as the format of the |
| 104 | // `BrowserVersionString` property and other `BrowserVersion` values. Only |
| 105 | // the version part of the `BrowserVersion` value is respected. The channel |
| 106 | // suffix, if it exists, is ignored. The version of the WebView2 Runtime |
| 107 | // binaries actually used may be different from the specified |
| 108 | // `TargetCompatibleBrowserVersion`. The binaries are only guaranteed to be |
| 109 | // compatible. Verify the actual version on the `BrowserVersionString` |
| 110 | // property on the `ICoreWebView2Environment`. |
| 111 | func WithTargetCompatibleBrowserVersion(version string) option { |
| 112 | return func(wvep *environmentOptions) { |
| 113 | wvep.targetCompatibleBrowserVersion = version |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | // WithAllowSingleSignOnUsingOSPrimaryAccount is used to enable |
| 118 | // single sign on with Azure Active Directory (AAD) and personal Microsoft |
| 119 | // Account (MSA) resources inside WebView. All AAD accounts, connected to |
| 120 | // Windows and shared for all apps, are supported. For MSA, SSO is only enabled |
| 121 | // for the account associated for Windows account login, if any. |
| 122 | // Default is disabled. Universal Windows Platform apps must also declare |
| 123 | // `enterpriseCloudSSO` |
| 124 | // [Restricted capabilities][WindowsUwpPackagingAppCapabilityDeclarationsRestrictedCapabilities] |
| 125 | // for the single sign on (SSO) to work. |
| 126 | // |
| 127 | // [WindowsUwpPackagingAppCapabilityDeclarationsRestrictedCapabilities]: /windows/uwp/packaging/app-capability-declarations\#restricted-capabilities "Restricted capabilities - App capability declarations | Microsoft Docs" |
| 128 | func WithAllowSingleSignOnUsingOSPrimaryAccount(allow bool) option { |
| 129 | return func(wvep *environmentOptions) { |
| 130 | wvep.allowSingleSignOnUsingOSPrimaryAccount = allow |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | // WithExclusiveUserDataFolderAccess specifies that the WebView environment |
| 135 | // obtains exclusive access to the user data folder. |
| 136 | // |
| 137 | // If the user data folder is already being used by another WebView environment with a |
| 138 | // different value for `ExclusiveUserDataFolderAccess` property, the creation of a WebView2Controller |
| 139 | // using the environment object will fail with `HRESULT_FROM_WIN32(ERROR_INVALID_STATE)`. |
| 140 | // When set as TRUE, no other WebView can be created from other processes using WebView2Environment |
| 141 | // objects with the same UserDataFolder. This prevents other processes from creating WebViews |
| 142 | // which share the same browser process instance, since sharing is performed among |
| 143 | // WebViews that have the same UserDataFolder. When another process tries to create a |
| 144 | // WebView2Controller from an WebView2Environment object created with the same user data folder, |
| 145 | // it will fail with `HRESULT_FROM_WIN32(ERROR_INVALID_STATE)`. |
| 146 | func WithExclusiveUserDataFolderAccess(exclusive bool) option { |
| 147 | return func(wvep *environmentOptions) { |
| 148 | wvep.exclusiveUserDataFolderAccess = exclusive |
| 149 | } |
| 150 | } |
| 151 | |
| 152 | type option func(*environmentOptions) |
| 153 | |
| 154 | var _ iCoreWebView2EnvironmentOptions = &environmentOptions{} |
| 155 | var _ iCoreWebView2EnvironmentOptions2 = &environmentOptions{} |
| 156 | |
| 157 | type environmentOptions struct { |
| 158 | browserExecutableFolder string |
| 159 | userDataFolder string |
| 160 | preferCanary bool |
| 161 | |
| 162 | additionalBrowserArguments string |
| 163 | language string |
| 164 | targetCompatibleBrowserVersion string |
| 165 | allowSingleSignOnUsingOSPrimaryAccount bool |
| 166 | exclusiveUserDataFolderAccess bool |
| 167 | } |
| 168 | |
| 169 | func (o *environmentOptions) AdditionalBrowserArguments() string { |
| 170 | return o.additionalBrowserArguments |
| 171 | } |
| 172 | |
| 173 | func (o *environmentOptions) Language() string { |
| 174 | return o.language |
| 175 | } |
| 176 | |
| 177 | func (o *environmentOptions) TargetCompatibleBrowserVersion() string { |
| 178 | v := o.targetCompatibleBrowserVersion |
| 179 | if v == "" { |
| 180 | v = kMinimumCompatibleVersion |
| 181 | } |
| 182 | return v |
| 183 | } |
| 184 | |
| 185 | func (o *environmentOptions) AllowSingleSignOnUsingOSPrimaryAccount() bool { |
| 186 | return o.allowSingleSignOnUsingOSPrimaryAccount |
| 187 | } |
| 188 | |
| 189 | func (o *environmentOptions) ExclusiveUserDataFolderAccess() bool { |
| 190 | return o.exclusiveUserDataFolderAccess |
| 191 | } |
| 192 | |
| 193 | type iCoreWebView2EnvironmentOptions interface { |
| 194 | combridge.IUnknown |
| 195 | |
| 196 | AdditionalBrowserArguments() string |
| 197 | Language() string |
| 198 | TargetCompatibleBrowserVersion() string |
| 199 | AllowSingleSignOnUsingOSPrimaryAccount() bool |
| 200 | } |
| 201 | |
| 202 | type iCoreWebView2EnvironmentOptions2 interface { |
| 203 | combridge.IUnknown |
| 204 | |
| 205 | ExclusiveUserDataFolderAccess() bool |
| 206 | } |
| 207 | |
| 208 | func init() { |
| 209 | combridge.RegisterVTable[combridge.IUnknown, iCoreWebView2EnvironmentOptions]( |
| 210 | "{2fde08a8-1e9a-4766-8c05-95a9ceb9d1c5}", |
| 211 | _iCoreWebView2EnvironmentOptionsAdditionalBrowserArguments, |
| 212 | _iCoreWebView2EnvironmentOptionsNOP, |
| 213 | _iCoreWebView2EnvironmentOptionsLanguage, |
| 214 | _iCoreWebView2EnvironmentOptionsNOP, |
| 215 | _iCoreWebView2EnvironmentTargetCompatibleBrowserVersion, |
| 216 | _iCoreWebView2EnvironmentOptionsNOP, |
| 217 | _iCoreWebView2EnvironmentOptionsAllowSingleSignOnUsingOSPrimaryAccount, |
| 218 | _iCoreWebView2EnvironmentOptionsNOP, |
| 219 | ) |
| 220 | |
| 221 | combridge.RegisterVTable[combridge.IUnknown, iCoreWebView2EnvironmentOptions2]( |
| 222 | "{ff85c98a-1ba7-4a6b-90c8-2b752c89e9e2}", |
| 223 | _iCoreWebView2EnvironmentOptions2ExclusiveUserDataFolderAccess, |
| 224 | _iCoreWebView2EnvironmentOptionsNOP, |
| 225 | ) |
| 226 | } |
| 227 | func _iCoreWebView2EnvironmentOptionsNOP(this uintptr) uintptr { |
| 228 | return uintptr(windows.S_FALSE) |
| 229 | } |
| 230 | |
| 231 | func _iCoreWebView2EnvironmentOptionsAdditionalBrowserArguments(this uintptr, value **uint16) uintptr { |
| 232 | v := combridge.Resolve[iCoreWebView2EnvironmentOptions](this).AdditionalBrowserArguments() |
| 233 | *value = stringToOleString(v) |
| 234 | return uintptr(windows.S_OK) |
| 235 | } |
| 236 | |
| 237 | func _iCoreWebView2EnvironmentOptionsLanguage(this uintptr, value **uint16) uintptr { |
| 238 | args := combridge.Resolve[iCoreWebView2EnvironmentOptions](this).Language() |
| 239 | *value = stringToOleString(args) |
| 240 | return uintptr(windows.S_OK) |
| 241 | } |
| 242 | |
| 243 | func _iCoreWebView2EnvironmentTargetCompatibleBrowserVersion(this uintptr, value **uint16) uintptr { |
| 244 | args := combridge.Resolve[iCoreWebView2EnvironmentOptions](this).TargetCompatibleBrowserVersion() |
| 245 | *value = stringToOleString(args) |
| 246 | return uintptr(windows.S_OK) |
| 247 | } |
| 248 | |
| 249 | func _iCoreWebView2EnvironmentOptionsAllowSingleSignOnUsingOSPrimaryAccount(this uintptr, value *int32) uintptr { |
| 250 | v := combridge.Resolve[iCoreWebView2EnvironmentOptions](this).AllowSingleSignOnUsingOSPrimaryAccount() |
| 251 | *value = boolToInt(v) |
| 252 | return uintptr(windows.S_OK) |
| 253 | } |
| 254 | |
| 255 | func _iCoreWebView2EnvironmentOptions2ExclusiveUserDataFolderAccess(this uintptr, value *int32) uintptr { |
| 256 | v := combridge.Resolve[iCoreWebView2EnvironmentOptions2](this).ExclusiveUserDataFolderAccess() |
| 257 | *value = boolToInt(v) |
| 258 | return uintptr(windows.S_OK) |
| 259 | } |
| 260 | |
| 261 | func stringToOleString(v string) *uint16 { |
| 262 | wstr := utf16.Encode([]rune(v + "\x00")) |
| 263 | lwstr := len(wstr) |
| 264 | ptr := (*uint16)(coTaskMemAlloc(2 * lwstr)) |
| 265 | |
| 266 | copy(unsafe.Slice(ptr, lwstr), wstr) |
| 267 | |
| 268 | return ptr |
| 269 | } |
| 270 | |
| 271 | func boolToInt(v bool) int32 { |
| 272 | if v { |
| 273 | return 1 |
| 274 | } |
| 275 | return 0 |
| 276 | } |
| 277 |