Files
Libation/Source/LibationUiBase/WebView2LoginErrorMessage.cs
T
William Tanksley Jr f61bbe5b68 Fix Linux WebView login hang and User-Agent failure (#2106) (#2108)
- Set NativeWebDialog.UserAgent in AdapterCreated instead of passing a full
  User-Agent header to ApplicationNameForUserAgent (which caused WebKit2GTK
  webkit_settings_set_user_agent assertion failures on Linux).
- Set gtkArgs.ForceX11GdkBackend = true when requesting the GTK WebView
  environment.
- Recognize GTK initialization failures (InvalidOperationException 'Unable to
  initialize GTK', libgtk-3/libgdk-3 DllNotFoundException, and exceptions
  originating from GtkNativeWebViewDialog/AvaloniaGtk) in
  WebView2LoginErrorMessage so async void NativeWebDialog.Show() failures
  fault BrowserLoginAsync instead of leaving tcs.Task waiting forever,
  allowing StartAsyncInternal to fall back to LoginExternalDialog.
2026-10-26 09:46:32 -06:00

134 lines
4.8 KiB
C#

using System;
using System.Runtime.InteropServices;
namespace LibationUiBase;
/// <summary>
/// User-facing copy and exception matching when embedded sign-in browser fails (often mistaken for a "library scan" bug).
/// Shared by WinForms and Avalonia; stack markers include WebView2 and Avalonia's NativeWebDialog.
/// </summary>
public static class WebView2LoginErrorMessage
{
public const string Caption = "Sign-in browser could not start";
public static string ExplainerBody
{
get
{
const string localIssue =
"This is a local sign-in or system setup issue — not a failure of the library scan itself.\r\n\r\n";
const string thingsToTry = "Things to try:\r\n";
const string settingsBullet =
"• In Libation Settings, try turning off the option to use the embedded browser and use external browser sign-in instead.\r\n";
const string securityBullet =
"• Check that security software is not blocking Libation or the embedded browser.\r\n";
const string permissionsBullet =
"• Ensure your account can write to local app data folders (permissions).\r\n";
const string adminBullet =
"• If you run as Administrator, try running Libation as a normal user (or the reverse).\r\n\r\n";
const string footer = "After sign-in works, use Import again to scan your library.";
if (OperatingSystem.IsLinux())
{
return "Libation could not start the in-app sign-in browser. On Linux this uses WebKit2GTK (native WebKit). "
+ localIssue
+ thingsToTry
+ "• Install WebKit2GTK for your distro (e.g. on Arch: webkit2gtk or webkit2gtk-4.1).\r\n"
+ settingsBullet
+ securityBullet
+ permissionsBullet
+ adminBullet
+ footer;
}
return "Libation could not start the in-app sign-in browser. On Windows this uses Microsoft WebView2. "
+ localIssue
+ thingsToTry
+ "• On Windows: install or repair the Microsoft Edge WebView2 Runtime from https://developer.microsoft.com/microsoft-edge/webview2/\r\n"
+ settingsBullet
+ securityBullet
+ permissionsBullet
+ adminBullet
+ footer;
}
}
public static bool IsWebView2SignInInfrastructureFailure(Exception ex)
{
for (var e = ex; e is not null; e = e.InnerException)
{
// Gtk / WebView2: missing native WebKit or WebView2 runtime often surfaces as DllNotFoundException (sometimes without a useful stack).
if (e is DllNotFoundException dll && IsEmbeddedWebViewNativeDllFailure(dll))
return true;
// AvaloniaGtk.RunTask throws InvalidOperationException("Unable to initialize GTK") when GTK cannot start
// (for example on Wayland without X11 backend or when no GTK display is available).
if (e is InvalidOperationException && e.Message.Contains("Unable to initialize GTK", StringComparison.OrdinalIgnoreCase))
return true;
if (!StackMentionsEmbeddedSignInBrowser(e))
continue;
if (e is UnauthorizedAccessException or InvalidOperationException or EntryPointNotFoundException or TypeInitializationException)
return true;
if (e is COMException com
&& (com.HResult == unchecked((int)0x8000FFFF) || com.HResult == unchecked((int)0x80070005)))
return true;
}
return false;
}
/// <summary>
/// Gtk WebView (e.g. Avalonia NativeWebDialog on Linux) requires WebKit2GTK; minimal installs omit <c>libwebkit2gtk</c>.
/// </summary>
private static bool IsEmbeddedWebViewNativeDllFailure(DllNotFoundException e)
{
if (e.Message.Contains("libwebkit2gtk", StringComparison.OrdinalIgnoreCase)
|| e.Message.Contains("webkit2gtk", StringComparison.OrdinalIgnoreCase)
|| e.Message.Contains("libgtk-3", StringComparison.OrdinalIgnoreCase)
|| e.Message.Contains("libgdk-3", StringComparison.OrdinalIgnoreCase))
return true;
return StackMentionsEmbeddedSignInBrowser(e);
}
public static bool TryFindInTree(Exception ex, out Exception? match)
{
Exception? found = null;
walk(ex);
match = found;
return found is not null;
void walk(Exception? e)
{
if (e is null || found is not null)
return;
if (IsWebView2SignInInfrastructureFailure(e))
found = e;
if (found is not null)
return;
if (e is AggregateException agg)
{
foreach (var inner in agg.InnerExceptions)
walk(inner);
}
walk(e.InnerException);
}
}
private static bool StackMentionsEmbeddedSignInBrowser(Exception e)
{
var stack = e.StackTrace;
return stack is not null
&& (stack.Contains("WebView2", StringComparison.Ordinal)
|| stack.Contains("CoreWebView2", StringComparison.Ordinal)
|| stack.Contains("NativeWebDialog", StringComparison.Ordinal)
|| stack.Contains("GtkNativeWebViewDialog", StringComparison.Ordinal)
|| stack.Contains("GtkWebView", StringComparison.Ordinal)
|| stack.Contains("AvaloniaGtk", StringComparison.Ordinal)
|| stack.Contains("WebViewControlAvalonia", StringComparison.Ordinal));
}
}