[FileExplorer Add-ons] If SVG file is bigger than 2MB save it as html page and preview the page (#18186)

* If SVG file is bigger than 2MB save it as html page and navigate WebView2 to it

Reason: WebView2.NavigateToString() has 2MB string limitation

* Cleanup and add limitation link to comment

* Lower the limit after testing it
This commit is contained in:
Stefan Markovic
2022-05-15 22:35:15 +02:00
committed by GitHub
parent 3443c73d0e
commit 3548e6820e
2 changed files with 93 additions and 19 deletions

View File

@@ -16,7 +16,6 @@ using Microsoft.PowerToys.PreviewHandler.Svg.Utilities;
using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry;
using Microsoft.Web.WebView2.Core; using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.WinForms; using Microsoft.Web.WebView2.WinForms;
using PreviewHandlerCommon;
namespace Microsoft.PowerToys.PreviewHandler.Svg namespace Microsoft.PowerToys.PreviewHandler.Svg
{ {
@@ -38,7 +37,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg
/// <summary> /// <summary>
/// Name of the virtual host /// Name of the virtual host
/// </summary> /// </summary>
public const string VirtualHostName = "PowerToysLocalSvg"; private const string VirtualHostName = "PowerToysLocalSvg";
/// <summary> /// <summary>
/// Gets the path of the current assembly. /// Gets the path of the current assembly.
@@ -46,7 +45,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg
/// <remarks> /// <remarks>
/// Source: https://stackoverflow.com/a/283917/14774889 /// Source: https://stackoverflow.com/a/283917/14774889
/// </remarks> /// </remarks>
public static string AssemblyDirectory private static string AssemblyDirectory
{ {
get get
{ {
@@ -67,12 +66,20 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg
/// </summary> /// </summary>
private bool _infoBarAdded; private bool _infoBarAdded;
/// <summary>
/// Represent WebView2 user data folder path.
/// </summary>
private string _webView2UserDataFolder = System.Environment.GetEnvironmentVariable("USERPROFILE") +
"\\AppData\\LocalLow\\Microsoft\\PowerToys\\SvgPreview-Temp";
/// <summary> /// <summary>
/// Start the preview on the Control. /// Start the preview on the Control.
/// </summary> /// </summary>
/// <param name="dataSource">Stream reference to access source file.</param> /// <param name="dataSource">Stream reference to access source file.</param>
public override void DoPreview<T>(T dataSource) public override void DoPreview<T>(T dataSource)
{ {
CleanupWebView2UserDataFolder();
string svgData = null; string svgData = null;
bool blocked = false; bool blocked = false;
@@ -88,9 +95,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg
blocked = SvgPreviewHandlerHelper.CheckBlockedElements(svgData); blocked = SvgPreviewHandlerHelper.CheckBlockedElements(svgData);
} }
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex) catch (Exception ex)
#pragma warning restore CA1031 // Do not catch general exception types
{ {
PreviewError(ex, dataSource); PreviewError(ex, dataSource);
return; return;
@@ -100,9 +105,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg
{ {
svgData = SvgPreviewHandlerHelper.AddStyleSVG(svgData); svgData = SvgPreviewHandlerHelper.AddStyleSVG(svgData);
} }
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex) catch (Exception ex)
#pragma warning restore CA1031 // Do not catch general exception types
{ {
PowerToysTelemetry.Log.WriteEvent(new SvgFilePreviewError { Message = ex.Message }); PowerToysTelemetry.Log.WriteEvent(new SvgFilePreviewError { Message = ex.Message });
} }
@@ -125,9 +128,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg
base.DoPreview(dataSource); base.DoPreview(dataSource);
PowerToysTelemetry.Log.WriteEvent(new SvgFilePreviewed()); PowerToysTelemetry.Log.WriteEvent(new SvgFilePreviewed());
} }
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex) catch (Exception ex)
#pragma warning restore CA1031 // Do not catch general exception types
{ {
PreviewError(ex, dataSource); PreviewError(ex, dataSource);
} }
@@ -169,8 +170,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg
ConfiguredTaskAwaitable<CoreWebView2Environment>.ConfiguredTaskAwaiter ConfiguredTaskAwaitable<CoreWebView2Environment>.ConfiguredTaskAwaiter
webView2EnvironmentAwaiter = CoreWebView2Environment webView2EnvironmentAwaiter = CoreWebView2Environment
.CreateAsync(userDataFolder: System.Environment.GetEnvironmentVariable("USERPROFILE") + .CreateAsync(userDataFolder: _webView2UserDataFolder)
"\\AppData\\LocalLow\\Microsoft\\PowerToys\\SvgPreview-Temp")
.ConfigureAwait(true).GetAwaiter(); .ConfigureAwait(true).GetAwaiter();
webView2EnvironmentAwaiter.OnCompleted(() => webView2EnvironmentAwaiter.OnCompleted(() =>
{ {
@@ -183,10 +183,24 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg
await _browser.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync("window.addEventListener('contextmenu', window => {window.preventDefault();});"); await _browser.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync("window.addEventListener('contextmenu', window => {window.preventDefault();});");
_browser.CoreWebView2.SetVirtualHostNameToFolderMapping(VirtualHostName, AssemblyDirectory, CoreWebView2HostResourceAccessKind.Allow); _browser.CoreWebView2.SetVirtualHostNameToFolderMapping(VirtualHostName, AssemblyDirectory, CoreWebView2HostResourceAccessKind.Allow);
_browser.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = false; _browser.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = false;
// WebView2.NavigateToString() limitation
// See https://docs.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2.navigatetostring?view=webview2-dotnet-1.0.864.35#remarks
// While testing the limit, it turned out it is ~1.5MB, so to be on a safe side we go for 1.5m bytes
if (svgData.Length > 1_500_000)
{
string filename = _webView2UserDataFolder + "\\" + Guid.NewGuid().ToString() + ".html";
File.WriteAllText(filename, svgData);
_browser.Source = new Uri(filename);
}
else
{
_browser.NavigateToString(svgData); _browser.NavigateToString(svgData);
}
Controls.Add(_browser); Controls.Add(_browser);
} }
catch (NullReferenceException) catch (Exception)
{ {
} }
}); });
@@ -224,5 +238,25 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg
AddTextBoxControl(Properties.Resource.SvgNotPreviewedError); AddTextBoxControl(Properties.Resource.SvgNotPreviewedError);
base.DoPreview(dataSource); base.DoPreview(dataSource);
} }
/// <summary>
/// Cleanup the previously created tmp html files from svg files bigger than 2MB.
/// </summary>
private void CleanupWebView2UserDataFolder()
{
try
{
// Cleanup temp dir
var dir = new DirectoryInfo(_webView2UserDataFolder);
foreach (var file in dir.EnumerateFiles("*.html"))
{
file.Delete();
}
}
catch (Exception)
{
}
}
} }
} }

View File

@@ -49,7 +49,7 @@ namespace Microsoft.PowerToys.ThumbnailHandler.Svg
/// <summary> /// <summary>
/// Name of the virtual host /// Name of the virtual host
/// </summary> /// </summary>
public const string VirtualHostName = "PowerToysLocalSvgThumbnail"; private const string VirtualHostName = "PowerToysLocalSvgThumbnail";
/// <summary> /// <summary>
/// Gets the path of the current assembly. /// Gets the path of the current assembly.
@@ -57,7 +57,7 @@ namespace Microsoft.PowerToys.ThumbnailHandler.Svg
/// <remarks> /// <remarks>
/// Source: https://stackoverflow.com/a/283917/14774889 /// Source: https://stackoverflow.com/a/283917/14774889
/// </remarks> /// </remarks>
public static string AssemblyDirectory private static string AssemblyDirectory
{ {
get get
{ {
@@ -68,6 +68,12 @@ namespace Microsoft.PowerToys.ThumbnailHandler.Svg
} }
} }
/// <summary>
/// Represent WebView2 user data folder path.
/// </summary>
private string _webView2UserDataFolder = System.Environment.GetEnvironmentVariable("USERPROFILE") +
"\\AppData\\LocalLow\\Microsoft\\PowerToys\\SvgThumbnailPreview-Temp";
/// <summary> /// <summary>
/// Render SVG using WebView2 control, capture the WebView2 /// Render SVG using WebView2 control, capture the WebView2
/// preview and create Bitmap out of it. /// preview and create Bitmap out of it.
@@ -76,6 +82,8 @@ namespace Microsoft.PowerToys.ThumbnailHandler.Svg
/// <param name="cx">The maximum thumbnail size, in pixels.</param> /// <param name="cx">The maximum thumbnail size, in pixels.</param>
public Bitmap GetThumbnail(string content, uint cx) public Bitmap GetThumbnail(string content, uint cx)
{ {
CleanupWebView2UserDataFolder();
if (cx == 0 || cx > MaxThumbnailSize || string.IsNullOrEmpty(content) || !content.Contains("svg")) if (cx == 0 || cx > MaxThumbnailSize || string.IsNullOrEmpty(content) || !content.Contains("svg"))
{ {
return null; return null;
@@ -117,8 +125,7 @@ namespace Microsoft.PowerToys.ThumbnailHandler.Svg
ConfiguredTaskAwaitable<CoreWebView2Environment>.ConfiguredTaskAwaiter ConfiguredTaskAwaitable<CoreWebView2Environment>.ConfiguredTaskAwaiter
webView2EnvironmentAwaiter = CoreWebView2Environment webView2EnvironmentAwaiter = CoreWebView2Environment
.CreateAsync(userDataFolder: System.Environment.GetEnvironmentVariable("USERPROFILE") + .CreateAsync(userDataFolder: _webView2UserDataFolder)
"\\AppData\\LocalLow\\Microsoft\\PowerToys\\SvgThumbnailPreview-Temp")
.ConfigureAwait(true).GetAwaiter(); .ConfigureAwait(true).GetAwaiter();
webView2EnvironmentAwaiter.OnCompleted(async () => webView2EnvironmentAwaiter.OnCompleted(async () =>
{ {
@@ -129,9 +136,22 @@ namespace Microsoft.PowerToys.ThumbnailHandler.Svg
await _browser.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync("window.addEventListener('contextmenu', window => {window.preventDefault();});"); await _browser.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync("window.addEventListener('contextmenu', window => {window.preventDefault();});");
_browser.CoreWebView2.SetVirtualHostNameToFolderMapping(VirtualHostName, AssemblyDirectory, CoreWebView2HostResourceAccessKind.Allow); _browser.CoreWebView2.SetVirtualHostNameToFolderMapping(VirtualHostName, AssemblyDirectory, CoreWebView2HostResourceAccessKind.Allow);
_browser.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = false; _browser.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = false;
// WebView2.NavigateToString() limitation
// See https://docs.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2.navigatetostring?view=webview2-dotnet-1.0.864.35#remarks
// While testing the limit, it turned out it is ~1.5MB, so to be on a safe side we go for 1.5m bytes
if (wrappedContent.Length > 1_500_000)
{
string filename = _webView2UserDataFolder + "\\" + Guid.NewGuid().ToString() + ".html";
File.WriteAllText(filename, wrappedContent);
_browser.Source = new Uri(filename);
}
else
{
_browser.NavigateToString(wrappedContent); _browser.NavigateToString(wrappedContent);
} }
catch (NullReferenceException) }
catch (Exception)
{ {
} }
}); });
@@ -249,5 +269,25 @@ namespace Microsoft.PowerToys.ThumbnailHandler.Svg
{ {
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
/// <summary>
/// Cleanup the previously created tmp html files from svg files bigger than 2MB.
/// </summary>
private void CleanupWebView2UserDataFolder()
{
try
{
// Cleanup temp dir
var dir = new DirectoryInfo(_webView2UserDataFolder);
foreach (var file in dir.EnumerateFiles("*.html"))
{
file.Delete();
}
}
catch (Exception)
{
}
}
} }
} }