From b29bffafd907434ce3f9adc41693f3ec6fb62d6f Mon Sep 17 00:00:00 2001 From: Henglie Date: Fri, 6 Nov 2026 15:15:11 +0800 Subject: [PATCH 1/4] fix(webview2): share one UDF configuration across preview panels All WebView2 panels use the same UserDataFolder, but FontViewer, CHM and PlantUML optionally set AdditionalBrowserArguments (ForceDark). WebView2 requires identical environment options for a shared UDF: the first environment wins and later panels fail with 0x8007139F. That failure was ignored, so the panels stayed blank until the process restarted. - Centralize CreationProperties in WebpagePanel so no panel adds browser arguments. - Drop the ForceDark arguments; apply CoreWebView2Profile.PreferredColorScheme after initialization instead. - Log initialization failures and WebView2 process failures. Verified with a WebView2 runtime harness: a ForceDark environment followed by a plain environment reproduces 0x8007139F on the same UDF, two plain environments succeed, and PreferredColorScheme=Dark is honored. Affected plugins build clean. The full GUI preview flow was not run. --- .../ChmWebpagePanel.cs | 22 ------------- .../WebfontPanel.cs | 8 ----- .../WebpagePanel.cs | 31 ++++++++++++++----- .../Webview/PlantUml/PuImagePanel.cs | 25 --------------- .../Webview/Svg/SvgImagePanel.cs | 6 +--- 5 files changed, 25 insertions(+), 67 deletions(-) diff --git a/QuickLook.Plugin/QuickLook.Plugin.ChmViewer/ChmWebpagePanel.cs b/QuickLook.Plugin/QuickLook.Plugin.ChmViewer/ChmWebpagePanel.cs index 9bff638c1..37b37812b 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.ChmViewer/ChmWebpagePanel.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.ChmViewer/ChmWebpagePanel.cs @@ -15,7 +15,6 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using QuickLook.Common.Helpers; using QuickLook.Plugin.HtmlViewer; using System; using System.Collections.Generic; @@ -42,27 +41,6 @@ static ChmWebpagePanel() public ChmWebpagePanel() { _homePage = ReadResource("/chm2html.html") ?? []; - - if (OSThemeHelper.AppsUseDarkTheme()) - { - // Invoke using reflection: WebView2.CreationProperties.AdditionalBrowserArguments - // This approach allows the library to avoid direct dependency on WebView2 - if (typeof(WebpagePanel).GetField("_webView", BindingFlags.NonPublic | BindingFlags.Instance) is FieldInfo fieldInfo) - { - object webView2 = fieldInfo.GetValue(this); - - if (webView2?.GetType().GetProperty("CreationProperties", BindingFlags.Public | BindingFlags.Instance) is PropertyInfo creationPropertiesProperty) - { - object creationProperties = creationPropertiesProperty.GetValue(webView2); - - if (creationProperties?.GetType().GetProperty("AdditionalBrowserArguments", BindingFlags.Public | BindingFlags.Instance) is PropertyInfo additionalBrowserArgumentsProperty) - { - string additionalBrowserArguments = (additionalBrowserArgumentsProperty.GetValue(creationProperties) as string) ?? string.Empty; - additionalBrowserArgumentsProperty.SetValue(creationProperties, additionalBrowserArguments + " --enable-features=WebContentsForceDark"); - } - } - } - } } private static void InitializeResources() diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/WebfontPanel.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/WebfontPanel.cs index c0a63bf0c..5ff3b9297 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/WebfontPanel.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/WebfontPanel.cs @@ -43,14 +43,6 @@ static WebfontPanel() InitializeResources(); } - public WebfontPanel() - { - if (OSThemeHelper.AppsUseDarkTheme()) - { - _webView.CreationProperties.AdditionalBrowserArguments = "--enable-features=WebContentsForceDark"; - } - } - protected static void InitializeResources() { if (_resources.Any()) return; diff --git a/QuickLook.Plugin/QuickLook.Plugin.HtmlViewer/WebpagePanel.cs b/QuickLook.Plugin/QuickLook.Plugin.HtmlViewer/WebpagePanel.cs index b0004f107..fe9dae7df 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.HtmlViewer/WebpagePanel.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.HtmlViewer/WebpagePanel.cs @@ -54,10 +54,7 @@ protected virtual void InitializeComponent() { _webView = new WebView2 { - CreationProperties = new CoreWebView2CreationProperties - { - UserDataFolder = Path.Combine(SettingHelper.LocalDataPath, @"WebView2_Data\"), - }, + CreationProperties = CreateCreationProperties(), // Prevent white flash in dark mode DefaultBackgroundColor = OSThemeHelper.AppsUseDarkTheme() ? Color.FromArgb(255, 32, 32, 32) : Color.White, @@ -68,6 +65,14 @@ protected virtual void InitializeComponent() Content = _webView; } + protected static CoreWebView2CreationProperties CreateCreationProperties() + { + return new CoreWebView2CreationProperties + { + UserDataFolder = Path.Combine(SettingHelper.LocalDataPath, @"WebView2_Data\"), + }; + } + public void NavigateToFile(string path) { try @@ -173,11 +178,23 @@ protected virtual void WebView_NavigationCompleted(object sender, CoreWebView2Na protected virtual void WebView_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e) { - if (e.IsSuccess) + if (!e.IsSuccess) { - _webView.CoreWebView2.AddWebResourceRequestedFilter("*", CoreWebView2WebResourceContext.All); - _webView.CoreWebView2.WebResourceRequested += WebView_WebResourceRequested; + ProcessHelper.WriteLog($"WebView2 initialization failed: {e.InitializationException}"); + return; } + + _webView.CoreWebView2.Profile.PreferredColorScheme = OSThemeHelper.AppsUseDarkTheme() + ? CoreWebView2PreferredColorScheme.Dark + : CoreWebView2PreferredColorScheme.Light; + _webView.CoreWebView2.ProcessFailed += WebView_ProcessFailed; + _webView.CoreWebView2.AddWebResourceRequestedFilter("*", CoreWebView2WebResourceContext.All); + _webView.CoreWebView2.WebResourceRequested += WebView_WebResourceRequested; + } + + protected virtual void WebView_ProcessFailed(object sender, CoreWebView2ProcessFailedEventArgs e) + { + ProcessHelper.WriteLog($"WebView2 process failed: {e.ProcessFailedKind}; reason: {e.Reason}; exit code: {e.ExitCode}"); } protected virtual void WebView_WebResourceRequested(object sender, CoreWebView2WebResourceRequestedEventArgs args) diff --git a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/PlantUml/PuImagePanel.cs b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/PlantUml/PuImagePanel.cs index 975f0caa8..726e94a82 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/PlantUml/PuImagePanel.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/PlantUml/PuImagePanel.cs @@ -17,7 +17,6 @@ using Microsoft.Web.WebView2.Core; using QuickLook.Common.Helpers; -using QuickLook.Plugin.HtmlViewer; using QuickLook.Plugin.ImageViewer.Webview.Svg; using System; using System.IO; @@ -32,30 +31,6 @@ public class PuImagePanel : SvgImagePanel { private string _puContent; - public PuImagePanel() - { - if (OSThemeHelper.AppsUseDarkTheme()) - { - // Invoke using reflection: WebView2.CreationProperties.AdditionalBrowserArguments - // This approach allows the library to avoid direct dependency on WebView2 - if (typeof(WebpagePanel).GetField("_webView", BindingFlags.NonPublic | BindingFlags.Instance) is FieldInfo fieldInfo) - { - object webView2 = fieldInfo.GetValue(this); - - if (webView2?.GetType().GetProperty("CreationProperties", BindingFlags.Public | BindingFlags.Instance) is PropertyInfo creationPropertiesProperty) - { - object creationProperties = creationPropertiesProperty.GetValue(webView2); - - if (creationProperties?.GetType().GetProperty("AdditionalBrowserArguments", BindingFlags.Public | BindingFlags.Instance) is PropertyInfo additionalBrowserArgumentsProperty) - { - string additionalBrowserArguments = (additionalBrowserArgumentsProperty.GetValue(creationProperties) as string) ?? string.Empty; - additionalBrowserArgumentsProperty.SetValue(creationProperties, additionalBrowserArguments + "--enable-features=WebContentsForceDark"); - } - } - } - } - } - public override void Preview(string path) { bool consented = SettingHelper.Get("PlantUMLOnlineConsented", false, "QuickLook.Plugin.ImageViewer"); diff --git a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/Svg/SvgImagePanel.cs b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/Svg/SvgImagePanel.cs index e829611e1..d3f25ad46 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/Svg/SvgImagePanel.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/Svg/SvgImagePanel.cs @@ -17,7 +17,6 @@ using Microsoft.Web.WebView2.Core; using Microsoft.Web.WebView2.Wpf; -using QuickLook.Common.Helpers; using QuickLook.Plugin.HtmlViewer; using System; using System.Collections.Generic; @@ -62,10 +61,7 @@ protected override void InitializeComponent() { _webView = new WebView2() { - CreationProperties = new CoreWebView2CreationProperties - { - UserDataFolder = Path.Combine(SettingHelper.LocalDataPath, @"WebView2_Data\"), - }, + CreationProperties = CreateCreationProperties(), DefaultBackgroundColor = Color.Transparent, }; _webView.CoreWebView2InitializationCompleted += WebView_CoreWebView2InitializationCompleted; From 0b4d0eb18e2dcd8a03520539d53dcbac1f41d60b Mon Sep 17 00:00:00 2001 From: Henglie Date: Fri, 6 Nov 2026 23:45:11 +0800 Subject: [PATCH 2/4] fix(webview2): fall back when CoreWebView2 initialization fails Remove the half-initialized control from the visual tree, dispose it, and show the WebView2 install fallback so a failed controller creation cannot crash the process when previewing SVG or HTML. --- .../WebpagePanel.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/QuickLook.Plugin/QuickLook.Plugin.HtmlViewer/WebpagePanel.cs b/QuickLook.Plugin/QuickLook.Plugin.HtmlViewer/WebpagePanel.cs index fe9dae7df..0d01ee382 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.HtmlViewer/WebpagePanel.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.HtmlViewer/WebpagePanel.cs @@ -173,7 +173,8 @@ protected virtual void Webview_NavigationStarting(object sender, CoreWebView2Nav protected virtual void WebView_NavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e) { - _webView.DefaultBackgroundColor = Color.White; // Reset to white after page load to match expected default behavior + if (_webView != null) + _webView.DefaultBackgroundColor = Color.White; // Reset to white after page load to match expected default behavior } protected virtual void WebView_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e) @@ -181,6 +182,22 @@ protected virtual void WebView_CoreWebView2InitializationCompleted(object sender if (!e.IsSuccess) { ProcessHelper.WriteLog($"WebView2 initialization failed: {e.InitializationException}"); + + // Remove the half-initialized control from the visual tree before + // its window procedure can receive messages with a null COM + // interface, then dispose it and show the fallback action. + Content = null; + _webView.CoreWebView2InitializationCompleted -= WebView_CoreWebView2InitializationCompleted; + try + { + _webView.Dispose(); + } + catch (Exception ex) + { + Debug.WriteLine($"Failed to dispose WebView2: {ex.Message}"); + } + _webView = null; + Content = CreateDownloadButton(); return; } From d50e46795db8f97556f88a9d165eea8c2ae5d64d Mon Sep 17 00:00:00 2001 From: Henglie Date: Thu, 12 Nov 2026 23:33:49 +0800 Subject: [PATCH 3/4] Keep PlantUML consent message readable in dark theme Removing WebContentsForceDark leaves the consent-denied page on the panel's transparent background, so the #555 text was drawn over the dark window background. Give the page an explicit white background. --- .../Webview/PlantUml/PuImagePanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/PlantUml/PuImagePanel.cs b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/PlantUml/PuImagePanel.cs index 726e94a82..6b5f93813 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/PlantUml/PuImagePanel.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/PlantUml/PuImagePanel.cs @@ -52,7 +52,7 @@ public override void Preview(string path) string deniedMsg = TranslationHelper.Get("PU_OnlineDeniedMessage", translationFile, failsafe: "Network access was denied. PlantUML preview requires an internet connection."); NavigateToHtml( - $"" + + $"" + $"

{HttpUtility.HtmlEncode(deniedMsg)}

"); return; } From b0cbb6952a154582227eae15716b851a28d38167 Mon Sep 17 00:00:00 2001 From: Henglie Date: Fri, 13 Nov 2026 20:46:09 +0800 Subject: [PATCH 4/4] fix(imageviewer): fall back to ImageMagick when SVG WebView2 init fails WebpagePanel now removes and disposes a half-initialized WebView2 control, but an SVG preview only left the WebView2 install button behind. Reuse the existing ImageMagick ImagePanel path for .svg when controller creation fails, while keeping the shared WebView2 creation properties and failure cleanup. Fixes #2057. --- .../WebpagePanel.cs | 2 +- .../Webview/Svg/SvgImagePanel.cs | 48 ++++++++++++++++++- .../Webview/WebHandler.cs | 2 +- 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/QuickLook.Plugin/QuickLook.Plugin.HtmlViewer/WebpagePanel.cs b/QuickLook.Plugin/QuickLook.Plugin.HtmlViewer/WebpagePanel.cs index 0d01ee382..ac8009049 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.HtmlViewer/WebpagePanel.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.HtmlViewer/WebpagePanel.cs @@ -273,7 +273,7 @@ protected virtual void WebView_WebResourceRequested(object sender, CoreWebView2W } } - public void Dispose() + public virtual void Dispose() { _webView?.Dispose(); _webView = null; diff --git a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/Svg/SvgImagePanel.cs b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/Svg/SvgImagePanel.cs index d3f25ad46..239ed53f4 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/Svg/SvgImagePanel.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/Svg/SvgImagePanel.cs @@ -1,4 +1,4 @@ -// Copyright © 2017-2026 QL-Win Contributors +// Copyright © 2017-2026 QL-Win Contributors // // This file is part of QuickLook program. // @@ -17,7 +17,10 @@ using Microsoft.Web.WebView2.Core; using Microsoft.Web.WebView2.Wpf; +using QuickLook.Common.Helpers; +using QuickLook.Common.Plugin; using QuickLook.Plugin.HtmlViewer; +using QuickLook.Plugin.ImageViewer.Helpers; using System; using System.Collections.Generic; using System.Diagnostics; @@ -36,6 +39,10 @@ public class SvgImagePanel : WebpagePanel, IWebImagePanel protected const string _resourcePrefix = "QuickLook.Plugin.ImageViewer.Resources."; protected internal static readonly Dictionary _resources = []; protected byte[] _homePage; + private string _path; + private ImagePanel _fallbackPanel; + + public ContextObject ContextObject { get; set; } public object ObjectForScripting { @@ -91,6 +98,7 @@ protected static void InitializeResources() public virtual void Preview(string path) { + _path = path; FallbackPath = Path.GetDirectoryName(path); ObjectForScripting ??= new ScriptHandler(path); @@ -99,6 +107,44 @@ public virtual void Preview(string path) NavigateToUri(new Uri("file://quicklook/")); } + protected override void WebView_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e) + { + base.WebView_CoreWebView2InitializationCompleted(sender, e); + + if (!e.IsSuccess) + ShowImageMagickFallback(); + } + + private void ShowImageMagickFallback() + { + if (_fallbackPanel != null || string.IsNullOrEmpty(_path) || ContextObject == null) + return; + + try + { + var panel = new ImagePanel(ContextObject, new MetaProvider(_path)) + { + ImageUriSource = ImageHelper.FilePathToFileUrl(_path) + }; + + _fallbackPanel = panel; + Content = panel; + ContextObject.IsBusy = false; + ProcessHelper.WriteLog($"Fell back to ImageMagick for the SVG preview: {_path}"); + } + catch (Exception ex) + { + ProcessHelper.WriteLog($"Failed to render the SVG with ImageMagick: {ex}"); + } + } + + public override void Dispose() + { + _fallbackPanel?.Dispose(); + _fallbackPanel = null; + base.Dispose(); + } + protected override void WebView_WebResourceRequested(object sender, CoreWebView2WebResourceRequestedEventArgs args) { Debug.WriteLine($"[{args.Request.Method}] {args.Request.Uri}"); diff --git a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/WebHandler.cs b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/WebHandler.cs index 39edb5bbc..b07438504 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/WebHandler.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Webview/WebHandler.cs @@ -122,7 +122,7 @@ public static bool TryView(string path, ContextObject context, IWebMetaProvider ipWeb = ext switch { - ".svg" => new SvgImagePanel(), + ".svg" => new SvgImagePanel { ContextObject = context }, ".svga" => new SvgaImagePanel(metaWeb), ".lottie" or ".json" => new LottieImagePanel(), ".tgs" => new TgsImagePanel(),