Editor optimizations

This commit is contained in:
Burak Kaan Köse
2026-02-08 10:35:24 +01:00
parent ad9b94d407
commit 22c6452227
4 changed files with 63 additions and 56 deletions
@@ -133,6 +133,7 @@ public sealed partial class WebViewEditorControl : Control, IDisposable
private const string PART_WebView = "WebView"; private const string PART_WebView = "WebView";
private WebView2 _chromium = null!; private WebView2 _chromium = null!;
private bool _disposedValue; private bool _disposedValue;
private bool? _lastAppliedDarkTheme;
private readonly TaskCompletionSource<bool> _domLoadedTask = new(); private readonly TaskCompletionSource<bool> _domLoadedTask = new();
public WebViewEditorControl() public WebViewEditorControl()
@@ -153,8 +154,9 @@ public sealed partial class WebViewEditorControl : Control, IDisposable
private async Task InitializeComponent() private async Task InitializeComponent()
{ {
Environment.SetEnvironmentVariable("WEBVIEW2_DEFAULT_BACKGROUND_COLOR", "00FFFFFF"); WebViewExtensions.EnsureWebView2Environment();
Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--enable-features=OverlayScrollbar,msOverlayScrollbarWinStyle,msOverlayScrollbarWinStyleAnimation");
_chromium.CoreWebView2Initialized -= ChromiumInitialized;
_chromium.CoreWebView2Initialized += ChromiumInitialized; _chromium.CoreWebView2Initialized += ChromiumInitialized;
await _chromium.EnsureCoreWebView2Async(); await _chromium.EnsureCoreWebView2Async();
@@ -218,7 +220,7 @@ public sealed partial class WebViewEditorControl : Control, IDisposable
int composerFontSize = _preferencesService.ComposerFontSize; int composerFontSize = _preferencesService.ComposerFontSize;
var readerFont = _preferencesService.ReaderFont; var readerFont = _preferencesService.ReaderFont;
int readerFontSize = _preferencesService.ReaderFontSize; int readerFontSize = _preferencesService.ReaderFontSize;
return await _chromium.ExecuteScriptFunctionAsync("initializeJodit", false, return await _chromium.ExecuteScriptFunctionAsync("initializeJodit",
JsonSerializer.Serialize(fonts, BasicTypesJsonContext.Default.ListString), JsonSerializer.Serialize(fonts, BasicTypesJsonContext.Default.ListString),
JsonSerializer.Serialize(composerFont, BasicTypesJsonContext.Default.String), JsonSerializer.Serialize(composerFont, BasicTypesJsonContext.Default.String),
JsonSerializer.Serialize(composerFontSize, BasicTypesJsonContext.Default.Int32), 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.CoreWebView2.SetVirtualHostNameToFolderMapping("app.editor", editorBundlePath, CoreWebView2HostResourceAccessKind.Allow);
_chromium.Source = new Uri("https://app.editor/editor.html"); _chromium.Source = new Uri("https://app.editor/editor.html");
_chromium.CoreWebView2.DOMContentLoaded -= DomLoaded;
_chromium.CoreWebView2.DOMContentLoaded += DomLoaded; _chromium.CoreWebView2.DOMContentLoaded += DomLoaded;
_chromium.CoreWebView2.WebMessageReceived -= ScriptMessageReceived;
_chromium.CoreWebView2.WebMessageReceived += ScriptMessageReceived; _chromium.CoreWebView2.WebMessageReceived += ScriptMessageReceived;
} }
public async Task UpdateEditorThemeAsync() public async Task UpdateEditorThemeAsync(bool force = false)
{ {
await _domLoadedTask.Task; await _domLoadedTask.Task;
if (IsEditorDarkMode) var isDark = IsEditorDarkMode;
if (!force && _lastAppliedDarkTheme == isDark) return;
_lastAppliedDarkTheme = isDark;
if (isDark)
{ {
_chromium.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Dark; _chromium.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Dark;
await _chromium.ExecuteScriptFunctionSafeAsync("SetDarkEditor"); await _chromium.ExecuteScriptFunctionSafeAsync("SetDarkEditor");
@@ -5,16 +5,34 @@ namespace Wino.Mail.WinUI.Extensions;
public static class WebViewExtensions public static class WebViewExtensions
{ {
private static bool _environmentInitialized;
/// <summary>
/// Sets WebView2 environment variables once per process.
/// Must be called before any WebView2 is initialized.
/// </summary>
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;
}
/// <summary> /// <summary>
/// Executes a script function in the WebView2 control. /// Executes a script function in the WebView2 control.
/// </summary> /// </summary>
/// <param name="isChromiumDisposed">Weird parameter that needed in mailRendering page. TODO: should be reconsidered.</param>
/// <param name="parameters">Parameters should be serialized to json</param> /// <param name="parameters">Parameters should be serialized to json</param>
public static async Task<string> ExecuteScriptFunctionAsync(this Microsoft.UI.Xaml.Controls.WebView2 Chromium, string functionName, bool isChromiumDisposed = false, params string[] parameters) public static async Task<string> 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) + ");"; string script = functionName + "(" + string.Join(", ", parameters) + ");";
return isChromiumDisposed ? string.Empty : await Chromium.ExecuteScriptAsync(script); return await Chromium.ExecuteScriptAsync(script);
} }
public static async Task<string> ExecuteScriptFunctionSafeAsync(this Microsoft.UI.Xaml.Controls.WebView2 Chromium, string functionName, params string[] parameters) public static async Task<string> ExecuteScriptFunctionSafeAsync(this Microsoft.UI.Xaml.Controls.WebView2 Chromium, string functionName, params string[] parameters)
@@ -24,14 +24,12 @@ public sealed partial class EventDetailsPage : EventDetailsPageAbstract,
{ {
private readonly IPreferencesService _preferencesService = App.Current.Services.GetService<IPreferencesService>()!; private readonly IPreferencesService _preferencesService = App.Current.Services.GetService<IPreferencesService>()!;
private TaskCompletionSource<bool> DOMLoadedTask = new TaskCompletionSource<bool>(); private TaskCompletionSource<bool> DOMLoadedTask = new TaskCompletionSource<bool>();
private bool isChromiumDisposed = false;
public EventDetailsPage() public EventDetailsPage()
{ {
InitializeComponent(); InitializeComponent();
Environment.SetEnvironmentVariable("WEBVIEW2_DEFAULT_BACKGROUND_COLOR", "00FFFFFF"); WebViewExtensions.EnsureWebView2Environment();
Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--enable-features=OverlayScrollbar,msOverlayScrollbarWinStyle,msOverlayScrollbarWinStyleAnimation,msWebView2CodeCache");
} }
protected override void OnNavigatedTo(NavigationEventArgs e) protected override void OnNavigatedTo(NavigationEventArgs e)
@@ -91,11 +89,11 @@ public sealed partial class EventDetailsPage : EventDetailsPageAbstract,
if (string.IsNullOrEmpty(description)) if (string.IsNullOrEmpty(description))
{ {
await EventDetailsWebView.ExecuteScriptFunctionAsync("RenderHTML", isChromiumDisposed, JsonSerializer.Serialize(" ", BasicTypesJsonContext.Default.String)); await EventDetailsWebView.ExecuteScriptFunctionAsync("RenderHTML", JsonSerializer.Serialize(" ", BasicTypesJsonContext.Default.String));
} }
else else
{ {
await EventDetailsWebView.ExecuteScriptFunctionAsync("RenderHTML", isChromiumDisposed, await EventDetailsWebView.ExecuteScriptFunctionAsync("RenderHTML",
JsonSerializer.Serialize(description, BasicTypesJsonContext.Default.String), JsonSerializer.Serialize(description, BasicTypesJsonContext.Default.String),
JsonSerializer.Serialize(true, BasicTypesJsonContext.Default.Boolean)); JsonSerializer.Serialize(true, BasicTypesJsonContext.Default.Boolean));
} }
@@ -138,8 +136,6 @@ public sealed partial class EventDetailsPage : EventDetailsPageAbstract,
EventDetailsWebView.CoreWebView2.NewWindowRequested -= WindowRequested; EventDetailsWebView.CoreWebView2.NewWindowRequested -= WindowRequested;
} }
isChromiumDisposed = true;
EventDetailsWebView.Close(); EventDetailsWebView.Close();
} }
@@ -150,34 +146,23 @@ public sealed partial class EventDetailsPage : EventDetailsPageAbstract,
if (ViewModel.IsDarkWebviewRenderer) if (ViewModel.IsDarkWebviewRenderer)
{ {
EventDetailsWebView.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Dark; EventDetailsWebView.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Dark;
await InvokeScriptSafeAsync("SetDarkEditor();"); await EventDetailsWebView.ExecuteScriptSafeAsync("SetDarkEditor();");
} }
else else
{ {
EventDetailsWebView.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Light; EventDetailsWebView.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Light;
await InvokeScriptSafeAsync("SetLightEditor();"); await EventDetailsWebView.ExecuteScriptSafeAsync("SetLightEditor();");
} }
} }
private async Task UpdateReaderFontPropertiesAsync() 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; var fontName = _preferencesService.ReaderFont;
fontName += ", sans-serif"; fontName += ", sans-serif";
await EventDetailsWebView.ExecuteScriptFunctionAsync("ChangeFontFamily", isChromiumDisposed, JsonSerializer.Serialize(fontName, BasicTypesJsonContext.Default.String)); await EventDetailsWebView.ExecuteScriptFunctionAsync("ChangeFontFamily", JsonSerializer.Serialize(fontName, BasicTypesJsonContext.Default.String));
}
private async Task<string> InvokeScriptSafeAsync(string function)
{
try
{
return await EventDetailsWebView.ExecuteScriptAsync(function);
}
catch (Exception) { }
return string.Empty;
} }
void IRecipient<ApplicationThemeChanged>.Receive(ApplicationThemeChanged message) void IRecipient<ApplicationThemeChanged>.Receive(ApplicationThemeChanged message)
@@ -32,18 +32,16 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
private readonly IMailDialogService _dialogService = App.Current.Services.GetService<IMailDialogService>()!; private readonly IMailDialogService _dialogService = App.Current.Services.GetService<IMailDialogService>()!;
private bool isRenderingInProgress = false; private bool isRenderingInProgress = false;
private bool? _lastAppliedDarkTheme;
private TaskCompletionSource<bool> DOMLoadedTask = new TaskCompletionSource<bool>(); private TaskCompletionSource<bool> DOMLoadedTask = new TaskCompletionSource<bool>();
private bool isChromiumDisposed = false;
public WebView2 GetWebView() => Chromium; public WebView2 GetWebView() => Chromium;
public MailRenderingPage() public MailRenderingPage()
{ {
InitializeComponent(); InitializeComponent();
Environment.SetEnvironmentVariable("WEBVIEW2_DEFAULT_BACKGROUND_COLOR", "00FFFFFF"); WebViewExtensions.EnsureWebView2Environment();
Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--enable-features=OverlayScrollbar,msOverlayScrollbarWinStyle,msOverlayScrollbarWinStyleAnimation,msWebView2CodeCache");
ViewModel.DirectPrintFuncAsync = DirectPrintAsync; ViewModel.DirectPrintFuncAsync = DirectPrintAsync;
@@ -81,17 +79,6 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
await UpdateEditorThemeAsync(); await UpdateEditorThemeAsync();
} }
private async Task<string> InvokeScriptSafeAsync(string function)
{
try
{
return await Chromium.ExecuteScriptAsync(function);
}
catch (Exception) { }
return string.Empty;
}
private async Task RenderInternalAsync(string htmlBody) private async Task RenderInternalAsync(string htmlBody)
{ {
isRenderingInProgress = true; isRenderingInProgress = true;
@@ -103,12 +90,12 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
if (string.IsNullOrEmpty(htmlBody)) if (string.IsNullOrEmpty(htmlBody))
{ {
await Chromium.ExecuteScriptFunctionAsync("RenderHTML", isChromiumDisposed, JsonSerializer.Serialize(" ", BasicTypesJsonContext.Default.String)); await Chromium.ExecuteScriptFunctionAsync("RenderHTML", JsonSerializer.Serialize(" ", BasicTypesJsonContext.Default.String));
} }
else else
{ {
var shouldLinkifyText = ViewModel.CurrentRenderModel?.MailRenderingOptions?.RenderPlaintextLinks ?? true; var shouldLinkifyText = ViewModel.CurrentRenderModel?.MailRenderingOptions?.RenderPlaintextLinks ?? true;
await Chromium.ExecuteScriptFunctionAsync("RenderHTML", isChromiumDisposed, await Chromium.ExecuteScriptFunctionAsync("RenderHTML",
JsonSerializer.Serialize(htmlBody, BasicTypesJsonContext.Default.String), JsonSerializer.Serialize(htmlBody, BasicTypesJsonContext.Default.String),
JsonSerializer.Serialize(shouldLinkifyText, BasicTypesJsonContext.Default.Boolean)); JsonSerializer.Serialize(shouldLinkifyText, BasicTypesJsonContext.Default.Boolean));
} }
@@ -131,14 +118,17 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
async void IRecipient<HtmlRenderingRequested>.Receive(HtmlRenderingRequested message) async void IRecipient<HtmlRenderingRequested>.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)) if (message == null || string.IsNullOrEmpty(message.HtmlBody))
{ {
await RenderInternalAsync(string.Empty); await RenderInternalAsync(string.Empty);
return; return;
} }
await Chromium.EnsureCoreWebView2Async();
await RenderInternalAsync(message.HtmlBody); await RenderInternalAsync(message.HtmlBody);
} }
@@ -168,8 +158,6 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
Chromium.CoreWebView2.NewWindowRequested -= WindowRequested; Chromium.CoreWebView2.NewWindowRequested -= WindowRequested;
} }
isChromiumDisposed = true;
Chromium.Close(); Chromium.Close();
GC.Collect(); GC.Collect();
} }
@@ -261,23 +249,29 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
{ {
await DOMLoadedTask.Task; await DOMLoadedTask.Task;
if (ViewModel.IsDarkWebviewRenderer) var isDark = ViewModel.IsDarkWebviewRenderer;
if (_lastAppliedDarkTheme == isDark) return;
_lastAppliedDarkTheme = isDark;
if (isDark)
{ {
Chromium.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Dark; Chromium.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Dark;
await InvokeScriptSafeAsync("SetDarkEditor();"); await Chromium.ExecuteScriptSafeAsync("SetDarkEditor();");
} }
else else
{ {
Chromium.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Light; Chromium.CoreWebView2.Profile.PreferredColorScheme = CoreWebView2PreferredColorScheme.Light;
await InvokeScriptSafeAsync("SetLightEditor();"); await Chromium.ExecuteScriptSafeAsync("SetLightEditor();");
} }
} }
private async Task UpdateReaderFontPropertiesAsync() 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. // Prepare font family name with fallback to sans-serif by default.
var fontName = _preferencesService.ReaderFont; 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. // If font family name is not supported by the browser, fallback to sans-serif.
fontName += ", 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<ApplicationThemeChanged>.Receive(ApplicationThemeChanged message) void IRecipient<ApplicationThemeChanged>.Receive(ApplicationThemeChanged message)