Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 21 additions & 22 deletions src/modules/previewpane/SvgPreviewHandler/SvgPreviewControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.IO;
using System.Net.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
Expand Down Expand Up @@ -106,7 +107,7 @@ public override void DoPreview<T>(T dataSource)
return;
}

CleanupWebView2UserDataFolder();
EnsureWebView2UserDataFolder();

string svgData = null;
bool blocked = false;
Expand Down Expand Up @@ -251,23 +252,27 @@ private void AddWebViewControl(string svgData)
_browser.CoreWebView2.AddWebResourceRequestedFilter("*", CoreWebView2WebResourceContext.All);
_browser.CoreWebView2.WebResourceRequested += CoreWebView2_BlockExternalResources;

string generatedPreview = _previewGenerator.GeneratePreview(svgData);
var cacheKey = SvgPreviewCacheHelper.BuildCacheKey(
"v1",
VirtualHostName,
svgData,
_settings.ColorMode.ToString(),
_settings.ThemeColor.ToArgb().ToString(),
_settings.SolidColor.ToArgb().ToString(),
_settings.CheckeredShade.ToString());

// WebView2.NavigateToString() limitation
// See https://learn.microsoft.com/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 (generatedPreview.Length > 1_500_000)
{
string filename = _webView2UserDataFolder + "\\" + Guid.NewGuid().ToString() + ".html";
File.WriteAllText(filename, generatedPreview);
_localFileURI = new Uri(filename);
_browser.Source = _localFileURI;
}
else
var cacheFolder = Path.Combine(_webView2UserDataFolder, "Cache");
var cacheFilePath = SvgPreviewCacheHelper.GetCacheFilePath(cacheFolder, cacheKey);

if (!File.Exists(cacheFilePath) || new FileInfo(cacheFilePath).Length == 0)
{
_browser.NavigateToString(generatedPreview);
string generatedPreview = _previewGenerator.GeneratePreview(svgData);
File.WriteAllText(cacheFilePath, generatedPreview);
}

_localFileURI = new Uri(cacheFilePath);
_browser.Source = _localFileURI;

Controls.Add(_browser);
}
catch (Exception)
Expand Down Expand Up @@ -318,17 +323,11 @@ private void PreviewError<T>(Exception exception, T dataSource)
/// <summary>
/// Cleanup the previously created tmp html files from svg files bigger than 2MB.
/// </summary>
private void CleanupWebView2UserDataFolder()
private void EnsureWebView2UserDataFolder()
{
try
{
// Cleanup temp dir
var dir = new DirectoryInfo(_webView2UserDataFolder);

foreach (var file in dir.EnumerateFiles("*.html"))
{
file.Delete();
}
Directory.CreateDirectory(_webView2UserDataFolder);
}
catch (Exception)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ private static string AssemblyDirectory
/// <param name="cx">The maximum thumbnail size, in pixels.</param>
public Bitmap GetThumbnailImpl(uint cx)
{
CleanupWebView2UserDataFolder();
EnsureWebView2UserDataFolder();

if (cx == 0 || cx > MaxThumbnailSize)
{
Expand Down Expand Up @@ -177,27 +177,24 @@ public Bitmap GetThumbnailImpl(uint cx)
}
};

// WebView2.NavigateToString() limitation
// See https://learn.microsoft.com/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
SvgContentsReady.Wait();
if (string.IsNullOrEmpty(SvgContents) || !SvgContents.Contains("svg"))
{
thumbnailDone.Set();
return;
}

if (SvgContents.Length > 1_500_000)
{
string filename = _webView2UserDataFolder + "\\" + Guid.NewGuid().ToString() + ".html";
File.WriteAllText(filename, SvgContents);
_localFileURI = new Uri(filename);
_browser.Source = _localFileURI;
}
else
var cacheKey = SvgPreviewCacheHelper.BuildCacheKey("v1", VirtualHostName, SvgContents);
var cacheFolder = Path.Combine(_webView2UserDataFolder, "Cache");
var cacheFilePath = SvgPreviewCacheHelper.GetCacheFilePath(cacheFolder, cacheKey);

if (!File.Exists(cacheFilePath) || new FileInfo(cacheFilePath).Length == 0)
{
_browser.NavigateToString(SvgContents);
File.WriteAllText(cacheFilePath, SvgContents);
}

_localFileURI = new Uri(cacheFilePath);
_browser.Source = _localFileURI;
}
catch (Exception ex)
{
Expand Down Expand Up @@ -344,17 +341,11 @@ public void Dispose()
/// <summary>
/// Cleanup the previously created tmp html files from svg files bigger than 2MB.
/// </summary>
private void CleanupWebView2UserDataFolder()
private void EnsureWebView2UserDataFolder()
{
try
{
// Cleanup temp dir
var dir = new DirectoryInfo(_webView2UserDataFolder);

foreach (var file in dir.EnumerateFiles("*.html"))
{
file.Delete();
}
Directory.CreateDirectory(_webView2UserDataFolder);
}
catch (Exception)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,5 +99,31 @@ public void CheckBlockedElementsShouldReturnFalseIfSvgDataIsNullOrWhiteSpaces(st
// Assert
Assert.IsFalse(foundFilteredElement);
}

[TestMethod]
public void BuildCacheKeyShouldReturnSameValueForSameInputs()
{
// Arrange
var firstKey = SvgPreviewCacheHelper.BuildCacheKey("v1", "svg-preview", "sample data");

// Act
var secondKey = SvgPreviewCacheHelper.BuildCacheKey("v1", "svg-preview", "sample data");

// Assert
Assert.AreEqual(firstKey, secondKey);
}

[TestMethod]
public void BuildCacheKeyShouldReturnDifferentValueForDifferentInputs()
{
// Arrange
var firstKey = SvgPreviewCacheHelper.BuildCacheKey("v1", "svg-preview", "sample data");

// Act
var secondKey = SvgPreviewCacheHelper.BuildCacheKey("v1", "svg-preview", "different data");

// Assert
Assert.AreNotEqual(firstKey, secondKey);
}
}
}
5 changes: 5 additions & 0 deletions src/modules/previewpane/common/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("PowerToys.SvgPreviewHandler")]
[assembly: InternalsVisibleTo("PowerToys.SvgThumbnailProvider")]
[assembly: InternalsVisibleTo("Preview.SvgPreviewHandler.UnitTests")]
33 changes: 33 additions & 0 deletions src/modules/previewpane/common/Utilities/SvgPreviewCacheHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace Common.Utilities
{
internal static class SvgPreviewCacheHelper
{
internal static string BuildCacheKey(params string[] cacheInputs)
{
var cacheKeyBuilder = new StringBuilder();

foreach (var input in cacheInputs)
{
cacheKeyBuilder.Append(input ?? string.Empty);
cacheKeyBuilder.Append('\n');
}

return Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(cacheKeyBuilder.ToString())));
}

internal static string GetCacheFilePath(string cacheRootFolder, string cacheKey)
{
Directory.CreateDirectory(cacheRootFolder);
return Path.Combine(cacheRootFolder, $"{cacheKey}.html");
}
}
}