diff --git a/Wino.Mail.WinUI/Controls/WebViewEditorControl.cs b/Wino.Mail.WinUI/Controls/WebViewEditorControl.cs index 0d8701f9..561c4c19 100644 --- a/Wino.Mail.WinUI/Controls/WebViewEditorControl.cs +++ b/Wino.Mail.WinUI/Controls/WebViewEditorControl.cs @@ -133,6 +133,7 @@ public sealed partial class WebViewEditorControl : Control, IDisposable private const string PART_WebView = "WebView"; private WebView2 _chromium = null!; private bool _disposedValue; + private bool? _lastAppliedDarkTheme; private readonly TaskCompletionSource _domLoadedTask = new(); public WebViewEditorControl() @@ -153,8 +154,9 @@ public sealed partial class WebViewEditorControl : Control, IDisposable private async Task InitializeComponent() { - Environment.SetEnvironmentVariable("WEBVIEW2_DEFAULT_BACKGROUND_COLOR", "00FFFFFF"); - Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--enable-features=OverlayScrollbar,msOverlayScrollbarWinStyle,msOverlayScrollbarWinStyleAnimation"); + WebViewExtensions.EnsureWebView2Environment(); + + _chromium.CoreWebView2Initialized -= ChromiumInitialized; _chromium.CoreWebView2Initialized += ChromiumInitialized; await _chromium.EnsureCoreWebView2Async(); @@ -218,7 +220,7 @@ public sealed partial class WebViewEditorControl : Control, IDisposable int composerFontSize = _preferencesService.ComposerFontSize; var readerFont = _preferencesService.ReaderFont; int readerFontSize = _preferencesService.ReaderFontSize; - return await _chromium.ExecuteScriptFunctionAsync("initializeJodit", false, + return await _chromium.ExecuteScriptFunctionAsync("initializeJodit", JsonSerializer.Serialize(fonts, BasicTypesJsonContext.Default.ListString), JsonSerializer.Serialize(composerFont, BasicTypesJsonContext.Default.String), JsonSerializer.Serialize(composerFontSize, BasicTypesJsonContext.Default.Int32), @@ -233,16 +235,24 @@ public sealed partial class WebViewEditorControl : Control, IDisposable _chromium.CoreWebView2.SetVirtualHostNameToFolderMapping("app.editor", editorBundlePath, CoreWebView2HostResourceAccessKind.Allow); _chromium.Source = new Uri("https://app.editor/editor.html"); + _chromium.CoreWebView2.DOMContentLoaded -= DomLoaded; _chromium.CoreWebView2.DOMContentLoaded += DomLoaded; + _chromium.CoreWebView2.WebMessageReceived -= ScriptMessageReceived; _chromium.CoreWebView2.WebMessageReceived += ScriptMessageReceived; } - public async Task UpdateEditorThemeAsync() + public async Task UpdateEditorThemeAsync(bool force = false) { await _domLoadedTask.Task; - if (IsEditorDarkMode) + var isDark = IsEditorDarkMode; + + if (!force && _lastAppliedDarkTheme == isDark) return; + + _lastAppliedDarkTheme = isDark; + + if (isDark) { _chromium.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Dark; await _chromium.ExecuteScriptFunctionSafeAsync("SetDarkEditor"); diff --git a/Wino.Mail.WinUI/Extensions/WebViewExtensions.cs b/Wino.Mail.WinUI/Extensions/WebViewExtensions.cs index f2e36541..0682cbf6 100644 --- a/Wino.Mail.WinUI/Extensions/WebViewExtensions.cs +++ b/Wino.Mail.WinUI/Extensions/WebViewExtensions.cs @@ -5,16 +5,34 @@ namespace Wino.Mail.WinUI.Extensions; public static class WebViewExtensions { + private static bool _environmentInitialized; + + /// + /// Sets WebView2 environment variables once per process. + /// Must be called before any WebView2 is initialized. + /// + public static void EnsureWebView2Environment() + { + if (_environmentInitialized) return; + + Environment.SetEnvironmentVariable("WEBVIEW2_DEFAULT_BACKGROUND_COLOR", "00FFFFFF"); + Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", + "--enable-features=OverlayScrollbar,msOverlayScrollbarWinStyle,msOverlayScrollbarWinStyleAnimation,msWebView2CodeCache"); + + _environmentInitialized = true; + } + /// /// Executes a script function in the WebView2 control. /// - /// Weird parameter that needed in mailRendering page. TODO: should be reconsidered. /// Parameters should be serialized to json - public static async Task ExecuteScriptFunctionAsync(this Microsoft.UI.Xaml.Controls.WebView2 Chromium, string functionName, bool isChromiumDisposed = false, params string[] parameters) + public static async Task ExecuteScriptFunctionAsync(this Microsoft.UI.Xaml.Controls.WebView2 Chromium, string functionName, params string[] parameters) { + if (Chromium?.CoreWebView2 == null) return string.Empty; + string script = functionName + "(" + string.Join(", ", parameters) + ");"; - return isChromiumDisposed ? string.Empty : await Chromium.ExecuteScriptAsync(script); + return await Chromium.ExecuteScriptAsync(script); } public static async Task ExecuteScriptFunctionSafeAsync(this Microsoft.UI.Xaml.Controls.WebView2 Chromium, string functionName, params string[] parameters) diff --git a/Wino.Mail.WinUI/Views/Calendar/EventDetailsPage.xaml.cs b/Wino.Mail.WinUI/Views/Calendar/EventDetailsPage.xaml.cs index 5a6d064b..ee221563 100644 --- a/Wino.Mail.WinUI/Views/Calendar/EventDetailsPage.xaml.cs +++ b/Wino.Mail.WinUI/Views/Calendar/EventDetailsPage.xaml.cs @@ -24,14 +24,12 @@ public sealed partial class EventDetailsPage : EventDetailsPageAbstract, { private readonly IPreferencesService _preferencesService = App.Current.Services.GetService()!; private TaskCompletionSource DOMLoadedTask = new TaskCompletionSource(); - private bool isChromiumDisposed = false; public EventDetailsPage() { InitializeComponent(); - Environment.SetEnvironmentVariable("WEBVIEW2_DEFAULT_BACKGROUND_COLOR", "00FFFFFF"); - Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--enable-features=OverlayScrollbar,msOverlayScrollbarWinStyle,msOverlayScrollbarWinStyleAnimation,msWebView2CodeCache"); + WebViewExtensions.EnsureWebView2Environment(); } protected override void OnNavigatedTo(NavigationEventArgs e) @@ -91,11 +89,11 @@ public sealed partial class EventDetailsPage : EventDetailsPageAbstract, if (string.IsNullOrEmpty(description)) { - await EventDetailsWebView.ExecuteScriptFunctionAsync("RenderHTML", isChromiumDisposed, JsonSerializer.Serialize(" ", BasicTypesJsonContext.Default.String)); + await EventDetailsWebView.ExecuteScriptFunctionAsync("RenderHTML", JsonSerializer.Serialize(" ", BasicTypesJsonContext.Default.String)); } else { - await EventDetailsWebView.ExecuteScriptFunctionAsync("RenderHTML", isChromiumDisposed, + await EventDetailsWebView.ExecuteScriptFunctionAsync("RenderHTML", JsonSerializer.Serialize(description, BasicTypesJsonContext.Default.String), JsonSerializer.Serialize(true, BasicTypesJsonContext.Default.Boolean)); } @@ -138,8 +136,6 @@ public sealed partial class EventDetailsPage : EventDetailsPageAbstract, EventDetailsWebView.CoreWebView2.NewWindowRequested -= WindowRequested; } - isChromiumDisposed = true; - EventDetailsWebView.Close(); } @@ -150,34 +146,23 @@ public sealed partial class EventDetailsPage : EventDetailsPageAbstract, if (ViewModel.IsDarkWebviewRenderer) { EventDetailsWebView.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Dark; - await InvokeScriptSafeAsync("SetDarkEditor();"); + await EventDetailsWebView.ExecuteScriptSafeAsync("SetDarkEditor();"); } else { EventDetailsWebView.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Light; - await InvokeScriptSafeAsync("SetLightEditor();"); + await EventDetailsWebView.ExecuteScriptSafeAsync("SetLightEditor();"); } } private async Task UpdateReaderFontPropertiesAsync() { - await EventDetailsWebView.ExecuteScriptFunctionAsync("ChangeFontSize", isChromiumDisposed, JsonSerializer.Serialize(_preferencesService.ReaderFontSize, BasicTypesJsonContext.Default.Int32)); + await EventDetailsWebView.ExecuteScriptFunctionAsync("ChangeFontSize", JsonSerializer.Serialize(_preferencesService.ReaderFontSize, BasicTypesJsonContext.Default.Int32)); var fontName = _preferencesService.ReaderFont; fontName += ", sans-serif"; - await EventDetailsWebView.ExecuteScriptFunctionAsync("ChangeFontFamily", isChromiumDisposed, JsonSerializer.Serialize(fontName, BasicTypesJsonContext.Default.String)); - } - - private async Task InvokeScriptSafeAsync(string function) - { - try - { - return await EventDetailsWebView.ExecuteScriptAsync(function); - } - catch (Exception) { } - - return string.Empty; + await EventDetailsWebView.ExecuteScriptFunctionAsync("ChangeFontFamily", JsonSerializer.Serialize(fontName, BasicTypesJsonContext.Default.String)); } void IRecipient.Receive(ApplicationThemeChanged message) diff --git a/Wino.Mail.WinUI/Views/Mail/MailRenderingPage.xaml.cs b/Wino.Mail.WinUI/Views/Mail/MailRenderingPage.xaml.cs index 62b6b7b9..361b0c9b 100644 --- a/Wino.Mail.WinUI/Views/Mail/MailRenderingPage.xaml.cs +++ b/Wino.Mail.WinUI/Views/Mail/MailRenderingPage.xaml.cs @@ -32,18 +32,16 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract, private readonly IMailDialogService _dialogService = App.Current.Services.GetService()!; private bool isRenderingInProgress = false; + private bool? _lastAppliedDarkTheme; private TaskCompletionSource DOMLoadedTask = new TaskCompletionSource(); - private bool isChromiumDisposed = false; - public WebView2 GetWebView() => Chromium; public MailRenderingPage() { InitializeComponent(); - Environment.SetEnvironmentVariable("WEBVIEW2_DEFAULT_BACKGROUND_COLOR", "00FFFFFF"); - Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--enable-features=OverlayScrollbar,msOverlayScrollbarWinStyle,msOverlayScrollbarWinStyleAnimation,msWebView2CodeCache"); + WebViewExtensions.EnsureWebView2Environment(); ViewModel.DirectPrintFuncAsync = DirectPrintAsync; @@ -81,17 +79,6 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract, await UpdateEditorThemeAsync(); } - private async Task InvokeScriptSafeAsync(string function) - { - try - { - return await Chromium.ExecuteScriptAsync(function); - } - catch (Exception) { } - - return string.Empty; - } - private async Task RenderInternalAsync(string htmlBody) { isRenderingInProgress = true; @@ -103,12 +90,12 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract, if (string.IsNullOrEmpty(htmlBody)) { - await Chromium.ExecuteScriptFunctionAsync("RenderHTML", isChromiumDisposed, JsonSerializer.Serialize(" ", BasicTypesJsonContext.Default.String)); + await Chromium.ExecuteScriptFunctionAsync("RenderHTML", JsonSerializer.Serialize(" ", BasicTypesJsonContext.Default.String)); } else { var shouldLinkifyText = ViewModel.CurrentRenderModel?.MailRenderingOptions?.RenderPlaintextLinks ?? true; - await Chromium.ExecuteScriptFunctionAsync("RenderHTML", isChromiumDisposed, + await Chromium.ExecuteScriptFunctionAsync("RenderHTML", JsonSerializer.Serialize(htmlBody, BasicTypesJsonContext.Default.String), JsonSerializer.Serialize(shouldLinkifyText, BasicTypesJsonContext.Default.Boolean)); } @@ -131,14 +118,17 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract, async void IRecipient.Receive(HtmlRenderingRequested message) { + // Ensure WebView2 is fully initialized before first render. + // OnNavigatedTo starts initialization fire-and-forget; this await + // guarantees the core is ready before we invoke any script. + await Chromium.EnsureCoreWebView2Async(); + if (message == null || string.IsNullOrEmpty(message.HtmlBody)) { await RenderInternalAsync(string.Empty); return; } - await Chromium.EnsureCoreWebView2Async(); - await RenderInternalAsync(message.HtmlBody); } @@ -168,8 +158,6 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract, Chromium.CoreWebView2.NewWindowRequested -= WindowRequested; } - isChromiumDisposed = true; - Chromium.Close(); GC.Collect(); } @@ -261,23 +249,29 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract, { await DOMLoadedTask.Task; - if (ViewModel.IsDarkWebviewRenderer) + var isDark = ViewModel.IsDarkWebviewRenderer; + + if (_lastAppliedDarkTheme == isDark) return; + + _lastAppliedDarkTheme = isDark; + + if (isDark) { Chromium.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Dark; - await InvokeScriptSafeAsync("SetDarkEditor();"); + await Chromium.ExecuteScriptSafeAsync("SetDarkEditor();"); } else { Chromium.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Light; - await InvokeScriptSafeAsync("SetLightEditor();"); + await Chromium.ExecuteScriptSafeAsync("SetLightEditor();"); } } private async Task UpdateReaderFontPropertiesAsync() { - await Chromium.ExecuteScriptFunctionAsync("ChangeFontSize", isChromiumDisposed, JsonSerializer.Serialize(_preferencesService.ReaderFontSize, BasicTypesJsonContext.Default.Int32)); + await Chromium.ExecuteScriptFunctionAsync("ChangeFontSize", JsonSerializer.Serialize(_preferencesService.ReaderFontSize, BasicTypesJsonContext.Default.Int32)); // Prepare font family name with fallback to sans-serif by default. var fontName = _preferencesService.ReaderFont; @@ -285,7 +279,7 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract, // If font family name is not supported by the browser, fallback to sans-serif. fontName += ", sans-serif"; - await Chromium.ExecuteScriptFunctionAsync("ChangeFontFamily", isChromiumDisposed, JsonSerializer.Serialize(fontName, BasicTypesJsonContext.Default.String)); + await Chromium.ExecuteScriptFunctionAsync("ChangeFontFamily", JsonSerializer.Serialize(fontName, BasicTypesJsonContext.Default.String)); } void IRecipient.Receive(ApplicationThemeChanged message)