[FileExplorerPreview] Move everything from WebBrowser to WebView2 (#17588)

* Move MarkdownPreviewHandler from WebBrowser to WebView2

* Disable context menu
Open links in default browser

* Update expect.txt

* Move SvgPreviewHandler from WebBrowser to WebView2

* Migrate SvgThumbnailProvider from WebBrowser to WebView2

* Migrate CustomControlTest to WebView2
Remove WebBrowser related stuff

* Update tests

* Revert GetThumbnail return value
Disable javascript dialogs in WebView2 for Svg thumbnail and preview

* expect.txt

* Increase timeout for Markdown tests

* Add sleeps

* Add zero check
This commit is contained in:
Stefan Markovic 2022-04-14 17:27:22 +02:00 committed by GitHub
parent cbd362cef1
commit 88517bfdf7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 454 additions and 559 deletions

View File

@ -65,7 +65,7 @@ APPICON
appid
appium
APPLASTZONE
applets
Applets
Applicationcan
applicationframehost
applog
@ -303,7 +303,7 @@ constexpr
contentdialog
contentfiles
CONTEXTHELP
CONTEXTMENU
contextmenu
CONTEXTMENUHANDLER
CONTROLL
CONTROLPARENT

View File

@ -26,7 +26,6 @@ using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly", Scope = "member", Target = "Microsoft.Templates.Core.Locations.TemplatesSynchronization.#SyncStatusChanged", Justification = "Using an Action<object, SyncStatusEventArgs> does not allow the required notation")]
// Non general suppressions
[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "The WebBrowser is loading source code to be shown to the user. No localization required.", MessageId = "System.Windows.Controls.WebBrowser.NavigateToString(System.String)", Scope = "member", Target = "Microsoft.Templates.UI.Controls.CodeViewer.#UpdateCodeView(System.Func`2<System.String,System.String>,System.String,System.String,System.Boolean)")]
[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "This is part of the markdown processing", MessageId = "System.Windows.Documents.Run.#ctor(System.String)", Scope = "member", Target = "Microsoft.Templates.UI.Controls.Markdown.#ImageInlineEvaluator(System.Text.RegularExpressions.Match)")]
[assembly: SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "We need to have the names of these keys in lowercase to be able to compare with the keys becoming form the template json. ContainsKey does not allow StringComparer specification to IgnoreCase", Scope = "member", Target = "Microsoft.Templates.Core.ITemplateInfoExtensions.#GetQueryableProperties(Microsoft.TemplateEngine.Abstractions.ITemplateInfo)")]
[assembly: SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "We need to have the names of these keys in lowercase to be able to compare with the keys becoming form the template json. ContainsKey does not allow StringComparer specification to IgnoreCase", Scope = "member", Target = "Microsoft.Templates.Core.Composition.CompositionQuery.#Match(System.Collections.Generic.IEnumerable`1<Microsoft.Templates.Core.Composition.QueryNode>,Microsoft.Templates.Core.Composition.QueryablePropertyDictionary)")]

View File

@ -26,7 +26,6 @@ using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly", Scope = "member", Target = "Microsoft.Templates.Core.Locations.TemplatesSynchronization.#SyncStatusChanged", Justification = "Using an Action<object, SyncStatusEventArgs> does not allow the required notation")]
// Non general suppressions
[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "The WebBrowser is loading source code to be shown to the user. No localization required.", MessageId = "System.Windows.Controls.WebBrowser.NavigateToString(System.String)", Scope = "member", Target = "Microsoft.Templates.UI.Controls.CodeViewer.#UpdateCodeView(System.Func`2<System.String,System.String>,System.String,System.String,System.Boolean)")]
[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "This is part of the markdown processing", MessageId = "System.Windows.Documents.Run.#ctor(System.String)", Scope = "member", Target = "Microsoft.Templates.UI.Controls.Markdown.#ImageInlineEvaluator(System.Text.RegularExpressions.Match)")]
[assembly: SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "We need to have the names of these keys in lowercase to be able to compare with the keys becoming form the template json. ContainsKey does not allow StringComparer specification to IgnoreCase", Scope = "member", Target = "Microsoft.Templates.Core.ITemplateInfoExtensions.#GetQueryableProperties(Microsoft.TemplateEngine.Abstractions.ITemplateInfo)")]
[assembly: SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "We need to have the names of these keys in lowercase to be able to compare with the keys becoming form the template json. ContainsKey does not allow StringComparer specification to IgnoreCase", Scope = "member", Target = "Microsoft.Templates.Core.Composition.CompositionQuery.#Match(System.Collections.Generic.IEnumerable`1<Microsoft.Templates.Core.Composition.QueryNode>,Microsoft.Templates.Core.Composition.QueryablePropertyDictionary)")]

View File

@ -15,7 +15,7 @@
<PropertyGroup>
<ProjectGuid>{6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}</ProjectGuid>
<RootNamespace>Microsoft.PowerToys.PreviewHandler.Markdown</RootNamespace>
<TargetFramework>net6.0-windows</TargetFramework>
<TargetFramework>net6.0-windows10.0.18362.0</TargetFramework>
<EnableComHosting>true</EnableComHosting>
<IntermediateOutputPath>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(AssemblyName)\</IntermediateOutputPath>
<AssemblyName>PowerToys.MarkdownPreviewHandler</AssemblyName>
@ -45,6 +45,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.1150.38" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
</PackageReference>

View File

@ -5,6 +5,8 @@
using System;
using System.Drawing;
using System.IO.Abstractions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using Common;
@ -12,7 +14,10 @@ using Markdig;
using Microsoft.PowerToys.PreviewHandler.Markdown.Properties;
using Microsoft.PowerToys.PreviewHandler.Markdown.Telemetry.Events;
using Microsoft.PowerToys.Telemetry;
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.WinForms;
using PreviewHandlerCommon;
using Windows.System;
namespace Microsoft.PowerToys.PreviewHandler.Markdown
{
@ -53,13 +58,40 @@ namespace Microsoft.PowerToys.PreviewHandler.Markdown
/// <summary>
/// Extended Browser Control to display markdown html.
/// </summary>
private WebBrowserExt _browser;
private WebView2 _browser;
/// <summary>
/// WebView2 Environment
/// </summary>
private CoreWebView2Environment _webView2Environment;
/// <summary>
/// Name of the virtual host
/// </summary>
public const string VirtualHostName = "PowerToysLocalMarkdown";
/// <summary>
/// True if external image is blocked, false otherwise.
/// </summary>
private bool _infoBarDisplayed;
/// <summary>
/// Gets the path of the current assembly.
/// </summary>
/// <remarks>
/// Source: https://stackoverflow.com/a/283917/14774889
/// </remarks>
public static string AssemblyDirectory
{
get
{
string codeBase = Assembly.GetExecutingAssembly().Location;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
return Path.GetDirectoryName(path);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="MarkdownPreviewHandlerControl"/> class.
/// </summary>
@ -103,17 +135,38 @@ namespace Microsoft.PowerToys.PreviewHandler.Markdown
string parsedMarkdown = Markdig.Markdown.ToHtml(fileText, pipeline);
string markdownHTML = $"{htmlHeader}{parsedMarkdown}{htmlFooter}";
_browser = new WebView2()
{
Dock = DockStyle.Fill,
};
_browser.NavigationStarting += async (object sender, CoreWebView2NavigationStartingEventArgs args) =>
{
if (args.Uri != null && args.IsUserInitiated)
{
args.Cancel = true;
await Launcher.LaunchUriAsync(new Uri(args.Uri));
}
};
InvokeOnControlThread(() =>
{
_browser = new WebBrowserExt
ConfiguredTaskAwaitable<CoreWebView2Environment>.ConfiguredTaskAwaiter
webView2EnvironmentAwaiter = CoreWebView2Environment
.CreateAsync(userDataFolder: System.Environment.GetEnvironmentVariable("USERPROFILE") +
"\\AppData\\LocalLow\\Microsoft\\PowerToys\\MarkdownPreview-Temp")
.ConfigureAwait(true).GetAwaiter();
webView2EnvironmentAwaiter.OnCompleted(() =>
{
DocumentText = markdownHTML,
Dock = DockStyle.Fill,
IsWebBrowserContextMenuEnabled = false,
ScriptErrorsSuppressed = true,
ScrollBarsEnabled = true,
AllowNavigation = false,
};
InvokeOnControlThread(async () =>
{
try
{
_webView2Environment = webView2EnvironmentAwaiter.GetResult();
await _browser.EnsureCoreWebView2Async(_webView2Environment).ConfigureAwait(true);
await _browser.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync("window.addEventListener('contextmenu', window => {window.preventDefault();});");
_browser.CoreWebView2.SetVirtualHostNameToFolderMapping(VirtualHostName, AssemblyDirectory, CoreWebView2HostResourceAccessKind.Allow);
_browser.NavigateToString(markdownHTML);
Controls.Add(_browser);
if (_infoBarDisplayed)
@ -122,6 +175,12 @@ namespace Microsoft.PowerToys.PreviewHandler.Markdown
Resize += FormResized;
Controls.Add(_infoBar);
}
}
catch (NullReferenceException)
{
}
});
});
});
PowerToysTelemetry.Log.WriteEvent(new MarkdownFilePreviewed());

View File

@ -31,7 +31,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.1108.44" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.1150.38" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
</PackageReference>

View File

@ -5,6 +5,8 @@
using System;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices.ComTypes;
using System.Windows.Forms;
using Common;
@ -12,6 +14,8 @@ using Common.Utilities;
using Microsoft.PowerToys.PreviewHandler.Svg.Telemetry.Events;
using Microsoft.PowerToys.PreviewHandler.Svg.Utilities;
using Microsoft.PowerToys.Telemetry;
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.WinForms;
using PreviewHandlerCommon;
namespace Microsoft.PowerToys.PreviewHandler.Svg
@ -22,9 +26,36 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg
public class SvgPreviewControl : FormHandlerControl
{
/// <summary>
/// Extended Browser Control to display Svg.
/// WebView2 Control to display Svg.
/// </summary>
private WebBrowserExt _browser;
private WebView2 _browser;
/// <summary>
/// WebView2 Environment
/// </summary>
private CoreWebView2Environment _webView2Environment;
/// <summary>
/// Name of the virtual host
/// </summary>
public const string VirtualHostName = "PowerToysLocalSvg";
/// <summary>
/// Gets the path of the current assembly.
/// </summary>
/// <remarks>
/// Source: https://stackoverflow.com/a/283917/14774889
/// </remarks>
public static string AssemblyDirectory
{
get
{
string codeBase = Assembly.GetExecutingAssembly().Location;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
return Path.GetDirectoryName(path);
}
}
/// <summary>
/// Text box to display the information about blocked elements from Svg.
@ -73,7 +104,6 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg
catch (Exception ex)
#pragma warning restore CA1031 // Do not catch general exception types
{
_browser.ScrollBarsEnabled = true;
PowerToysTelemetry.Log.WriteEvent(new SvgFilePreviewError { Message = ex.Message });
}
@ -90,7 +120,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg
AddTextBoxControl(Properties.Resource.BlockedElementInfoText);
}
AddBrowserControl(svgData);
AddWebViewControl(svgData);
Resize += FormResized;
base.DoPreview(dataSource);
PowerToysTelemetry.Log.WriteEvent(new SvgFilePreviewed());
@ -129,20 +159,39 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg
}
/// <summary>
/// Adds a Web Browser Control to Control Collection.
/// Adds a WebView2 Control to Control Collection.
/// </summary>
/// <param name="svgData">Svg to display on Browser Control.</param>
private void AddBrowserControl(string svgData)
private void AddWebViewControl(string svgData)
{
_browser = new WebBrowserExt();
_browser.DocumentText = svgData;
_browser = new WebView2();
_browser.Dock = DockStyle.Fill;
_browser.IsWebBrowserContextMenuEnabled = false;
_browser.ScriptErrorsSuppressed = true;
_browser.ScrollBarsEnabled = false;
_browser.AllowNavigation = false;
ConfiguredTaskAwaitable<CoreWebView2Environment>.ConfiguredTaskAwaiter
webView2EnvironmentAwaiter = CoreWebView2Environment
.CreateAsync(userDataFolder: System.Environment.GetEnvironmentVariable("USERPROFILE") +
"\\AppData\\LocalLow\\Microsoft\\PowerToys\\SvgPreview-Temp")
.ConfigureAwait(true).GetAwaiter();
webView2EnvironmentAwaiter.OnCompleted(() =>
{
InvokeOnControlThread(async () =>
{
try
{
_webView2Environment = webView2EnvironmentAwaiter.GetResult();
await _browser.EnsureCoreWebView2Async(_webView2Environment).ConfigureAwait(true);
await _browser.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync("window.addEventListener('contextmenu', window => {window.preventDefault();});");
_browser.CoreWebView2.SetVirtualHostNameToFolderMapping(VirtualHostName, AssemblyDirectory, CoreWebView2HostResourceAccessKind.Allow);
_browser.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = false;
_browser.NavigateToString(svgData);
Controls.Add(_browser);
}
catch (NullReferenceException)
{
}
});
});
}
/// <summary>
/// Adds a Text Box in Controls for showing information about blocked elements.

View File

@ -49,6 +49,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.1150.38" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
</PackageReference>

View File

@ -117,7 +117,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg.Utilities
string centering = "position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);";
// Because WebBrowser class is based on IE version that do not support max-width and max-height extra CSS is needed for it to work.
// max-width and max-height not supported. Extra CSS is needed for it to work.
string scaling = $"max-width: {width} ; max-height: {height} ;";
scaling += $" _height:expression(this.scrollHeight > {heightR} ? \" {height}\" : \"auto\"); _width:expression(this.scrollWidth > {widthR} ? \"{width}\" : \"auto\");";

View File

@ -6,13 +6,15 @@ using System.Drawing;
using System.Drawing.Drawing2D;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Windows.Forms;
using Common.ComInterlop;
using Common.Utilities;
using PreviewHandlerCommon;
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.WinForms;
namespace Microsoft.PowerToys.ThumbnailHandler.Svg
{
@ -22,7 +24,7 @@ namespace Microsoft.PowerToys.ThumbnailHandler.Svg
[Guid("36B27788-A8BB-4698-A756-DF9F11F64F84")]
[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
public class SvgThumbnailProvider : IInitializeWithStream, IThumbnailProvider
public class SvgThumbnailProvider : IInitializeWithStream, IThumbnailProvider, IDisposable
{
/// <summary>
/// Gets the stream object to access file.
@ -35,118 +37,72 @@ namespace Microsoft.PowerToys.ThumbnailHandler.Svg
private const uint MaxThumbnailSize = 10000;
/// <summary>
/// Captures an image representation of browser contents.
/// WebView2 Control to display Svg.
/// </summary>
/// <param name="browser">The WebBrowser instance rendering the SVG.</param>
/// <param name="rectangle">The client rectangle to capture from.</param>
/// <param name="backgroundColor">The default background color to apply.</param>
/// <returns>A Bitmap representing the browser contents.</returns>
public static Bitmap GetBrowserContentImage(WebBrowser browser, Rectangle rectangle, Color backgroundColor)
{
Bitmap image = new Bitmap(rectangle.Width, rectangle.Height);
using (Graphics graphics = Graphics.FromImage(image))
{
IntPtr deviceContextHandle = IntPtr.Zero;
RECT rect = new RECT
{
Left = rectangle.Left,
Top = rectangle.Top,
Right = rectangle.Right,
Bottom = rectangle.Bottom,
};
private WebView2 _browser;
graphics.Clear(backgroundColor);
/// <summary>
/// WebView2 Environment
/// </summary>
private CoreWebView2Environment _webView2Environment;
try
{
deviceContextHandle = graphics.GetHdc();
/// <summary>
/// Name of the virtual host
/// </summary>
public const string VirtualHostName = "PowerToysLocalSvgThumbnail";
IViewObject viewObject = browser?.ActiveXInstance as IViewObject;
viewObject.Draw(1, -1, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, deviceContextHandle, ref rect, IntPtr.Zero, IntPtr.Zero, 0);
}
finally
/// <summary>
/// Gets the path of the current assembly.
/// </summary>
/// <remarks>
/// Source: https://stackoverflow.com/a/283917/14774889
/// </remarks>
public static string AssemblyDirectory
{
if (deviceContextHandle != IntPtr.Zero)
get
{
graphics.ReleaseHdc(deviceContextHandle);
string codeBase = Assembly.GetExecutingAssembly().Location;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
return Path.GetDirectoryName(path);
}
}
}
return image;
}
/// <summary>
/// Wrap the SVG markup in HTML with a meta tag to ensure the
/// WebBrowser control is in Edge mode to enable SVG rendering.
/// We also set the padding and margin for the body to zero as
/// there is a default margin of 8.
/// Render SVG using WebView2 control, capture the WebView2
/// preview and create Bitmap out of it.
/// </summary>
/// <param name="content">The content to render.</param>
/// <param name="cx">The maximum thumbnail size, in pixels.</param>
/// <returns>A thumbnail of the rendered content.</returns>
public static Bitmap GetThumbnail(string content, uint cx)
public Bitmap GetThumbnail(string content, uint cx)
{
if (cx > MaxThumbnailSize)
if (cx == 0 || cx > MaxThumbnailSize || string.IsNullOrEmpty(content) || !content.Contains("svg"))
{
return null;
}
Bitmap thumbnail = null;
// Wrap the SVG content in HTML in IE Edge mode so we can ensure
// we render properly.
bool thumbnailDone = false;
string wrappedContent = WrapSVGInHTML(content);
using (WebBrowserExt browser = new WebBrowserExt())
{
browser.Dock = DockStyle.Fill;
browser.IsWebBrowserContextMenuEnabled = false;
browser.ScriptErrorsSuppressed = true;
browser.ScrollBarsEnabled = false;
browser.AllowNavigation = false;
browser.Width = (int)cx;
browser.Height = (int)cx;
browser.DocumentText = wrappedContent;
// Wait for the browser to render the content.
while (browser.IsBusy || browser.ReadyState != WebBrowserReadyState.Complete)
_browser = new WebView2();
_browser.Dock = DockStyle.Fill;
_browser.Visible = true;
_browser.Width = (int)cx;
_browser.Height = (int)cx;
_browser.NavigationCompleted += async (object sender, CoreWebView2NavigationCompletedEventArgs args) =>
{
Application.DoEvents();
var a = await _browser.ExecuteScriptAsync($"document.getElementsByTagName('svg')[0].viewBox;");
if (a != null)
{
await _browser.ExecuteScriptAsync($"document.getElementsByTagName('svg')[0].style = 'width:100%;height:100%';");
}
// Check size of the rendered SVG.
var svg = browser.Document.GetElementsByTagName("svg").Cast<HtmlElement>().FirstOrDefault();
if (svg != null)
{
var viewBox = svg.GetAttribute("viewbox");
if (viewBox != null)
{
// Update the svg style to override any width or height explicit settings
// Setting to 100% width and height will allow to scale to our intended size
// Otherwise, we would end up with a scaled up blurry image.
svg.Style = "width:100%;height:100%";
MemoryStream ms = new MemoryStream();
await _browser.CoreWebView2.CapturePreviewAsync(CoreWebView2CapturePreviewImageFormat.Png, ms);
thumbnail = new Bitmap(ms);
// Wait for the browser to render the content.
while (browser.IsBusy || browser.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
}
// Update the size of the browser control to fit the SVG
// in the visible viewport.
browser.Width = svg.OffsetRectangle.Width;
browser.Height = svg.OffsetRectangle.Height;
// Wait for the browser to render the content.
while (browser.IsBusy || browser.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
// Capture the image of the SVG from the browser.
thumbnail = GetBrowserContentImage(browser, svg.OffsetRectangle, Color.White);
if (thumbnail.Width != cx && thumbnail.Height != cx)
if (thumbnail.Width != cx && thumbnail.Height != cx && thumbnail.Width != 0 && thumbnail.Height != 0)
{
// We are not the appropriate size for caller. Resize now while
// respecting the aspect ratio.
@ -155,15 +111,42 @@ namespace Microsoft.PowerToys.ThumbnailHandler.Svg
int scaleHeight = (int)(thumbnail.Height * scale);
thumbnail = ResizeImage(thumbnail, scaleWidth, scaleHeight);
}
thumbnailDone = true;
};
ConfiguredTaskAwaitable<CoreWebView2Environment>.ConfiguredTaskAwaiter
webView2EnvironmentAwaiter = CoreWebView2Environment
.CreateAsync(userDataFolder: System.Environment.GetEnvironmentVariable("USERPROFILE") +
"\\AppData\\LocalLow\\Microsoft\\PowerToys\\SvgThumbnailPreview-Temp")
.ConfigureAwait(true).GetAwaiter();
webView2EnvironmentAwaiter.OnCompleted(async () =>
{
try
{
_webView2Environment = webView2EnvironmentAwaiter.GetResult();
await _browser.EnsureCoreWebView2Async(_webView2Environment).ConfigureAwait(true);
await _browser.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync("window.addEventListener('contextmenu', window => {window.preventDefault();});");
_browser.CoreWebView2.SetVirtualHostNameToFolderMapping(VirtualHostName, AssemblyDirectory, CoreWebView2HostResourceAccessKind.Allow);
_browser.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = false;
_browser.NavigateToString(wrappedContent);
}
catch (NullReferenceException)
{
}
});
while (thumbnailDone == false)
{
Application.DoEvents();
}
return thumbnail;
}
/// <summary>
/// Wrap the SVG markup in HTML with a meta tag to ensure the
/// WebBrowser control is in Edge mode to enable SVG rendering.
/// Wrap the SVG markup in HTML with a meta tag to render it
/// using WebView2 control.
/// We also set the padding and margin for the body to zero as
/// there is a default margin of 8.
/// </summary>
@ -261,5 +244,10 @@ namespace Microsoft.PowerToys.ThumbnailHandler.Svg
}
}
}
public void Dispose()
{
GC.SuppressFinalize(this);
}
}
}

View File

@ -25,6 +25,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.1150.38" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
</PackageReference>

View File

@ -2,18 +2,23 @@
// 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.Drawing;
using System.Threading;
using System.Windows.Forms;
using Microsoft.PowerToys.PreviewHandler.Markdown;
using Microsoft.PowerToys.STATestExtension;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PreviewHandlerCommon;
using Microsoft.Web.WebView2.WinForms;
namespace MarkdownPreviewHandlerUnitTests
{
[STATestClass]
public class MarkdownPreviewHandlerTest
{
private static readonly int TenSecondsInMilliseconds = 10000;
private static readonly int SleepTimeInMilliseconds = 200;
[TestMethod]
public void MarkdownPreviewHandlerControlAddsBrowserToFormWhenDoPreviewIsCalled()
{
@ -23,9 +28,17 @@ namespace MarkdownPreviewHandlerUnitTests
// Act
markdownPreviewHandlerControl.DoPreview<string>("HelperFiles/MarkdownWithExternalImage.txt");
int beforeTick = Environment.TickCount;
while (markdownPreviewHandlerControl.Controls.Count == 0 && Environment.TickCount < beforeTick + TenSecondsInMilliseconds)
{
Application.DoEvents();
Thread.Sleep(SleepTimeInMilliseconds);
}
// Assert
Assert.AreEqual(2, markdownPreviewHandlerControl.Controls.Count);
Assert.IsInstanceOfType(markdownPreviewHandlerControl.Controls[0], typeof(WebBrowserExt));
Assert.IsInstanceOfType(markdownPreviewHandlerControl.Controls[0], typeof(WebView2));
}
}
@ -38,6 +51,14 @@ namespace MarkdownPreviewHandlerUnitTests
// Act
markdownPreviewHandlerControl.DoPreview<string>("HelperFiles/MarkdownWithExternalImage.txt");
int beforeTick = Environment.TickCount;
while (markdownPreviewHandlerControl.Controls.Count == 0 && Environment.TickCount < beforeTick + TenSecondsInMilliseconds)
{
Application.DoEvents();
Thread.Sleep(SleepTimeInMilliseconds);
}
// Assert
Assert.AreEqual(2, markdownPreviewHandlerControl.Controls.Count);
Assert.IsInstanceOfType(markdownPreviewHandlerControl.Controls[1], typeof(RichTextBox));
@ -53,6 +74,14 @@ namespace MarkdownPreviewHandlerUnitTests
// Act
markdownPreviewHandlerControl.DoPreview<string>("HelperFiles/MarkdownWithHTMLImageTag.txt");
int beforeTick = Environment.TickCount;
while (markdownPreviewHandlerControl.Controls.Count < 2 && Environment.TickCount < beforeTick + TenSecondsInMilliseconds)
{
Application.DoEvents();
Thread.Sleep(SleepTimeInMilliseconds);
}
// Assert
Assert.AreEqual(2, markdownPreviewHandlerControl.Controls.Count);
Assert.IsInstanceOfType(markdownPreviewHandlerControl.Controls[1], typeof(RichTextBox));
@ -68,9 +97,17 @@ namespace MarkdownPreviewHandlerUnitTests
// Act
markdownPreviewHandlerControl.DoPreview<string>("HelperFiles/MarkdownWithScript.txt");
int beforeTick = Environment.TickCount;
while (markdownPreviewHandlerControl.Controls.Count == 0 && Environment.TickCount < beforeTick + TenSecondsInMilliseconds)
{
Application.DoEvents();
Thread.Sleep(SleepTimeInMilliseconds);
}
// Assert
Assert.AreEqual(1, markdownPreviewHandlerControl.Controls.Count);
Assert.IsInstanceOfType(markdownPreviewHandlerControl.Controls[0], typeof(WebBrowserExt));
Assert.IsInstanceOfType(markdownPreviewHandlerControl.Controls[0], typeof(WebView2));
}
}
@ -83,14 +120,17 @@ namespace MarkdownPreviewHandlerUnitTests
// Act
markdownPreviewHandlerControl.DoPreview<string>("HelperFiles/MarkdownWithExternalImage.txt");
int beforeTick = Environment.TickCount;
while (markdownPreviewHandlerControl.Controls.Count < 2 && Environment.TickCount < beforeTick + TenSecondsInMilliseconds)
{
Application.DoEvents();
Thread.Sleep(SleepTimeInMilliseconds);
}
// Assert
Assert.IsInstanceOfType(markdownPreviewHandlerControl.Controls[0], typeof(WebBrowserExt));
Assert.IsNotNull(((WebBrowser)markdownPreviewHandlerControl.Controls[0]).DocumentText);
Assert.AreEqual(DockStyle.Fill, ((WebBrowser)markdownPreviewHandlerControl.Controls[0]).Dock);
Assert.AreEqual(false, ((WebBrowser)markdownPreviewHandlerControl.Controls[0]).IsWebBrowserContextMenuEnabled);
Assert.AreEqual(true, ((WebBrowser)markdownPreviewHandlerControl.Controls[0]).ScriptErrorsSuppressed);
Assert.AreEqual(true, ((WebBrowser)markdownPreviewHandlerControl.Controls[0]).ScrollBarsEnabled);
Assert.AreEqual(false, ((WebBrowser)markdownPreviewHandlerControl.Controls[0]).AllowNavigation);
Assert.IsInstanceOfType(markdownPreviewHandlerControl.Controls[0], typeof(WebView2));
Assert.AreEqual(DockStyle.Fill, ((WebView2)markdownPreviewHandlerControl.Controls[0]).Dock);
}
}
@ -103,6 +143,14 @@ namespace MarkdownPreviewHandlerUnitTests
// Act
markdownPreviewHandlerControl.DoPreview<string>("HelperFiles/MarkdownWithExternalImage.txt");
int beforeTick = Environment.TickCount;
while (markdownPreviewHandlerControl.Controls.Count == 0 && Environment.TickCount < beforeTick + TenSecondsInMilliseconds)
{
Application.DoEvents();
Thread.Sleep(SleepTimeInMilliseconds);
}
// Assert
Assert.IsInstanceOfType(markdownPreviewHandlerControl.Controls[1], typeof(RichTextBox));
Assert.IsNotNull(((RichTextBox)markdownPreviewHandlerControl.Controls[1]).Text);

View File

@ -11,7 +11,7 @@
<ProjectGuid>{A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}</ProjectGuid>
<RootNamespace>PreviewPaneUnitTests</RootNamespace>
<AssemblyName>PreviewPaneUnitTests</AssemblyName>
<TargetFramework>net6.0-windows</TargetFramework>
<TargetFramework>net6.0-windows10.0.18362.0</TargetFramework>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>

View File

@ -25,9 +25,6 @@
<PackageReference Include="MSTest.TestAdapter" Version="2.2.3" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.3" />
</ItemGroup>
<ItemGroup>
<Compile Update="WebBrowserExtUnitTests.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\..\codeAnalysis\GlobalSuppressions.cs" Link="GlobalSuppressions.cs" />
<Compile Include="..\STATestClassAttribute.cs" Link="STATestClassAttribute.cs" />

View File

@ -1,60 +0,0 @@
// 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.Reflection;
using Common;
using Microsoft.PowerToys.STATestExtension;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PreviewHandlerCommon;
namespace PreviewHandlerCommonUnitTests
{
[STATestClass]
public class WebBrowserExtUnitTests : WebBrowserExt
{
private const string DISPIDAMBIENTDLCONTROL = "[DISPID=-5512]";
[TestMethod]
public void InvokeMemberShouldSetValidFlagsWhenCalledWithValidDispId()
{
// Arrange
var extendedSite = CreateWebBrowserSiteBase() as WebBrowserSiteExt;
// Act
var actualFlags = (int)extendedSite.InvokeMember(DISPIDAMBIENTDLCONTROL, BindingFlags.InvokeMethod, null, null, null, null, null, null);
// Assert
Assert.IsTrue((actualFlags & (int)WebBrowserDownloadControlFlags.PRAGMA_NO_CACHE) >= 0);
Assert.IsTrue((actualFlags & (int)WebBrowserDownloadControlFlags.FORCEOFFLINE) >= 0);
Assert.IsTrue((actualFlags & (int)WebBrowserDownloadControlFlags.NO_CLIENTPULL) >= 0);
Assert.IsTrue((actualFlags & (int)WebBrowserDownloadControlFlags.NO_SCRIPTS) >= 0);
Assert.IsTrue((actualFlags & (int)WebBrowserDownloadControlFlags.NO_JAVA) >= 0);
Assert.IsTrue((actualFlags & (int)WebBrowserDownloadControlFlags.NO_FRAMEDOWNLOAD) >= 0);
Assert.IsTrue((actualFlags & (int)WebBrowserDownloadControlFlags.NOFRAMES) >= 0);
Assert.IsTrue((actualFlags & (int)WebBrowserDownloadControlFlags.NO_DLACTIVEXCTLS) >= 0);
Assert.IsTrue((actualFlags & (int)WebBrowserDownloadControlFlags.NO_RUNACTIVEXCTLS) >= 0);
Assert.IsTrue((actualFlags & (int)WebBrowserDownloadControlFlags.NO_BEHAVIORS) >= 0);
Assert.IsTrue((actualFlags & (int)WebBrowserDownloadControlFlags.SILENT) >= 0);
}
[TestMethod]
public void InvokeMemberShouldOnlySetValidFlagsWhenCalledWithValidDispId()
{
// Arrange
var extendedSite = CreateWebBrowserSiteBase() as WebBrowserSiteExt;
// Act
var actualFlags = (int)extendedSite.InvokeMember(DISPIDAMBIENTDLCONTROL, BindingFlags.InvokeMethod, null, null, null, null, null, null);
// Assert
Assert.IsTrue((actualFlags & (int)WebBrowserDownloadControlFlags.VIDEOS) == 0);
Assert.IsTrue((actualFlags & (int)WebBrowserDownloadControlFlags.BGSOUNDS) == 0);
Assert.IsTrue((actualFlags & (int)WebBrowserDownloadControlFlags.DOWNLOADONLY) == 0);
Assert.IsTrue((actualFlags & (int)WebBrowserDownloadControlFlags.RESYNCHRONIZE) == 0);
Assert.IsTrue((actualFlags & (int)WebBrowserDownloadControlFlags.NO_METACHARSET) == 0);
Assert.IsTrue((actualFlags & (int)WebBrowserDownloadControlFlags.URL_ENCODING_DISABLE_UTF8) == 0);
Assert.IsTrue((actualFlags & (uint)WebBrowserDownloadControlFlags.URL_ENCODING_ENABLE_UTF8) == 0);
}
}
}

View File

@ -7,12 +7,13 @@ using System.Drawing;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using Microsoft.PowerToys.PreviewHandler.Svg;
using Microsoft.PowerToys.STATestExtension;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Web.WebView2.WinForms;
using Moq;
using PreviewHandlerCommon;
namespace SvgPreviewHandlerUnitTests
{
@ -20,6 +21,9 @@ namespace SvgPreviewHandlerUnitTests
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "new Exception() is fine in test projects.")]
public class SvgPreviewControlTests
{
private static readonly int ThreeSecondsInMilliseconds = 3000;
private static readonly int SleepTimeInMilliseconds = 200;
[TestMethod]
public void SvgPreviewControlShouldAddExtendedBrowserControlWhenDoPreviewCalled()
{
@ -29,14 +33,22 @@ namespace SvgPreviewHandlerUnitTests
// Act
svgPreviewControl.DoPreview(GetMockStream("<svg></svg>"));
int beforeTick = Environment.TickCount;
while (svgPreviewControl.Controls.Count == 0 && Environment.TickCount < beforeTick + ThreeSecondsInMilliseconds)
{
Application.DoEvents();
Thread.Sleep(SleepTimeInMilliseconds);
}
// Assert
Assert.AreEqual(1, svgPreviewControl.Controls.Count);
Assert.IsInstanceOfType(svgPreviewControl.Controls[0], typeof(WebBrowserExt));
Assert.IsInstanceOfType(svgPreviewControl.Controls[0], typeof(WebView2));
}
}
[TestMethod]
public void SvgPreviewControlShouldSetDocumentStreamWhenDoPreviewCalled()
public void SvgPreviewControlShouldFillDockForWebView2WhenDoPreviewCalled()
{
// Arrange
using (var svgPreviewControl = new SvgPreviewControl())
@ -44,80 +56,16 @@ namespace SvgPreviewHandlerUnitTests
// Act
svgPreviewControl.DoPreview(GetMockStream("<svg></svg>"));
// Assert
Assert.IsNotNull(((WebBrowser)svgPreviewControl.Controls[0]).DocumentStream);
}
}
int beforeTick = Environment.TickCount;
[TestMethod]
public void SvgPreviewControlShouldDisableWebBrowserContextMenuWhenDoPreviewCalled()
while (svgPreviewControl.Controls.Count == 0 && Environment.TickCount < beforeTick + ThreeSecondsInMilliseconds)
{
// Arrange
using (var svgPreviewControl = new SvgPreviewControl())
{
// Act
svgPreviewControl.DoPreview(GetMockStream("<svg></svg>"));
Application.DoEvents();
Thread.Sleep(SleepTimeInMilliseconds);
}
// Assert
Assert.AreEqual(false, ((WebBrowser)svgPreviewControl.Controls[0]).IsWebBrowserContextMenuEnabled);
}
}
[TestMethod]
public void SvgPreviewControlShouldFillDockForWebBrowserWhenDoPreviewCalled()
{
// Arrange
using (var svgPreviewControl = new SvgPreviewControl())
{
// Act
svgPreviewControl.DoPreview(GetMockStream("<svg></svg>"));
// Assert
Assert.AreEqual(DockStyle.Fill, ((WebBrowser)svgPreviewControl.Controls[0]).Dock);
}
}
[TestMethod]
public void SvgPreviewControlShouldSetScriptErrorsSuppressedPropertyWhenDoPreviewCalled()
{
// Arrange
using (var svgPreviewControl = new SvgPreviewControl())
{
// Act
svgPreviewControl.DoPreview(GetMockStream("<svg></svg>"));
// Assert
Assert.AreEqual(true, ((WebBrowser)svgPreviewControl.Controls[0]).ScriptErrorsSuppressed);
}
}
// ToDo: fix unit test
[Ignore]
[TestMethod]
public void SvgPreviewControlShouldSetScrollBarsEnabledPropertyWhenDoPreviewCalled()
{
// Arrange
using (var svgPreviewControl = new SvgPreviewControl())
{
// Act
svgPreviewControl.DoPreview(GetMockStream("<svg></svg>"));
// Assert
Assert.AreEqual(true, ((WebBrowser)svgPreviewControl.Controls[0]).ScrollBarsEnabled);
}
}
[TestMethod]
public void SvgPreviewControlShouldDisableAllowNavigationWhenDoPreviewCalled()
{
// Arrange
using (var svgPreviewControl = new SvgPreviewControl())
{
// Act
svgPreviewControl.DoPreview(GetMockStream("<svg></svg>"));
// Assert
Assert.AreEqual(false, ((WebBrowser)svgPreviewControl.Controls[0]).AllowNavigation);
Assert.AreEqual(DockStyle.Fill, ((WebView2)svgPreviewControl.Controls[0]).Dock);
}
}
@ -134,6 +82,15 @@ namespace SvgPreviewHandlerUnitTests
// Act
svgPreviewControl.DoPreview(mockStream.Object);
int beforeTick = Environment.TickCount;
while (svgPreviewControl.Controls.Count == 0 && Environment.TickCount < beforeTick + ThreeSecondsInMilliseconds)
{
Application.DoEvents();
Thread.Sleep(SleepTimeInMilliseconds);
}
var textBox = svgPreviewControl.Controls[0] as RichTextBox;
// Assert
@ -159,6 +116,15 @@ namespace SvgPreviewHandlerUnitTests
.Setup(x => x.Read(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<IntPtr>()))
.Throws(new Exception());
svgPreviewControl.DoPreview(mockStream.Object);
int beforeTick = Environment.TickCount;
while (svgPreviewControl.Controls.Count == 0 && Environment.TickCount < beforeTick + ThreeSecondsInMilliseconds)
{
Application.DoEvents();
Thread.Sleep(SleepTimeInMilliseconds);
}
var textBox = svgPreviewControl.Controls[0] as RichTextBox;
var incrementParentControlWidth = 5;
var initialParentWidth = svgPreviewControl.Width;
@ -188,9 +154,17 @@ namespace SvgPreviewHandlerUnitTests
// Act
svgPreviewControl.DoPreview(GetMockStream(svgBuilder.ToString()));
int beforeTick = Environment.TickCount;
while (svgPreviewControl.Controls.Count < 2 && Environment.TickCount < beforeTick + ThreeSecondsInMilliseconds)
{
Application.DoEvents();
Thread.Sleep(SleepTimeInMilliseconds);
}
// Assert
Assert.IsInstanceOfType(svgPreviewControl.Controls[0], typeof(RichTextBox));
Assert.IsInstanceOfType(svgPreviewControl.Controls[1], typeof(WebBrowserExt));
Assert.IsInstanceOfType(svgPreviewControl.Controls[1], typeof(WebView2));
Assert.AreEqual(2, svgPreviewControl.Controls.Count);
}
}
@ -210,8 +184,16 @@ namespace SvgPreviewHandlerUnitTests
// Act
svgPreviewControl.DoPreview(GetMockStream(svgBuilder.ToString()));
int beforeTick = Environment.TickCount;
while (svgPreviewControl.Controls.Count == 0 && Environment.TickCount < beforeTick + ThreeSecondsInMilliseconds)
{
Application.DoEvents();
Thread.Sleep(SleepTimeInMilliseconds);
}
// Assert
Assert.IsInstanceOfType(svgPreviewControl.Controls[0], typeof(WebBrowserExt));
Assert.IsInstanceOfType(svgPreviewControl.Controls[0], typeof(WebView2));
Assert.AreEqual(1, svgPreviewControl.Controls.Count);
}
}
@ -227,6 +209,15 @@ namespace SvgPreviewHandlerUnitTests
svgBuilder.AppendLine("\t<script>alert(\"hello\")</script>");
svgBuilder.AppendLine("</svg>");
svgPreviewControl.DoPreview(GetMockStream(svgBuilder.ToString()));
int beforeTick = Environment.TickCount;
while (svgPreviewControl.Controls.Count == 0 && Environment.TickCount < beforeTick + ThreeSecondsInMilliseconds)
{
Application.DoEvents();
Thread.Sleep(SleepTimeInMilliseconds);
}
var textBox = svgPreviewControl.Controls[0] as RichTextBox;
var incrementParentControlWidth = 5;
var initialParentWidth = svgPreviewControl.Width;

View File

@ -7,6 +7,7 @@ using System.Drawing;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using System.Windows.Forms;
using Common.ComInterlop;
using Microsoft.PowerToys.STATestExtension;
using Microsoft.PowerToys.ThumbnailHandler.Svg;
@ -27,8 +28,12 @@ namespace SvgThumbnailProviderUnitTests
svgBuilder.AppendLine("\t</circle>");
svgBuilder.AppendLine("</svg>");
Bitmap thumbnail = SvgThumbnailProvider.GetThumbnail(svgBuilder.ToString(), 256);
Assert.IsTrue(thumbnail != null);
SvgThumbnailProvider svgThumbnailProvider = new SvgThumbnailProvider();
Bitmap thumbnail = svgThumbnailProvider.GetThumbnail(svgBuilder.ToString(), 256);
Assert.IsNotNull(thumbnail);
Assert.IsTrue(thumbnail.Width > 0);
Assert.IsTrue(thumbnail.Height > 0);
}
[TestMethod]
@ -41,7 +46,8 @@ namespace SvgThumbnailProviderUnitTests
svgBuilder.AppendLine("\t</circle>");
svgBuilder.AppendLine("</svg>");
Bitmap thumbnail = SvgThumbnailProvider.GetThumbnail(svgBuilder.ToString(), 256);
SvgThumbnailProvider svgThumbnailProvider = new SvgThumbnailProvider();
Bitmap thumbnail = svgThumbnailProvider.GetThumbnail(svgBuilder.ToString(), 256);
Assert.IsTrue(thumbnail != null);
}
@ -51,21 +57,24 @@ namespace SvgThumbnailProviderUnitTests
var svgBuilder = new StringBuilder();
svgBuilder.AppendLine("<p>foo</p>");
Bitmap thumbnail = SvgThumbnailProvider.GetThumbnail(svgBuilder.ToString(), 256);
SvgThumbnailProvider svgThumbnailProvider = new SvgThumbnailProvider();
Bitmap thumbnail = svgThumbnailProvider.GetThumbnail(svgBuilder.ToString(), 256);
Assert.IsTrue(thumbnail == null);
}
[TestMethod]
public void CheckNoSvgEmptyStringShouldReturnNullBitmap()
{
Bitmap thumbnail = SvgThumbnailProvider.GetThumbnail(string.Empty, 256);
SvgThumbnailProvider svgThumbnailProvider = new SvgThumbnailProvider();
Bitmap thumbnail = svgThumbnailProvider.GetThumbnail(string.Empty, 256);
Assert.IsTrue(thumbnail == null);
}
[TestMethod]
public void CheckNoSvgNullStringShouldReturnNullBitmap()
{
Bitmap thumbnail = SvgThumbnailProvider.GetThumbnail(null, 256);
SvgThumbnailProvider svgThumbnailProvider = new SvgThumbnailProvider();
Bitmap thumbnail = svgThumbnailProvider.GetThumbnail(null, 256);
Assert.IsTrue(thumbnail == null);
}
@ -73,7 +82,8 @@ namespace SvgThumbnailProviderUnitTests
public void CheckZeroSizedThumbnailShouldReturnNullBitmap()
{
string content = "<svg></svg>";
Bitmap thumbnail = SvgThumbnailProvider.GetThumbnail(content, 0);
SvgThumbnailProvider svgThumbnailProvider = new SvgThumbnailProvider();
Bitmap thumbnail = svgThumbnailProvider.GetThumbnail(content, 0);
Assert.IsTrue(thumbnail == null);
}
@ -94,7 +104,8 @@ namespace SvgThumbnailProviderUnitTests
svgBuilder.AppendLine("</body>");
svgBuilder.AppendLine("</html>");
Bitmap thumbnail = SvgThumbnailProvider.GetThumbnail(svgBuilder.ToString(), 256);
SvgThumbnailProvider svgThumbnailProvider = new SvgThumbnailProvider();
Bitmap thumbnail = svgThumbnailProvider.GetThumbnail(svgBuilder.ToString(), 256);
Assert.IsTrue(thumbnail != null);
}

View File

@ -23,7 +23,6 @@
<ItemGroup>
<Compile Include="..\..\..\codeAnalysis\GlobalSuppressions.cs" Link="GlobalSuppressions.cs" />
<Compile Update="controls\WebBrowserExt.cs" />
<Compile Update="examplehandler\CustomControlTest.cs" />
<Compile Update="controls\FormHandlerControl.cs" />
</ItemGroup>
@ -39,6 +38,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.1150.38" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
</PackageReference>

View File

@ -120,7 +120,7 @@ namespace Common
this.Controls.Clear();
});
// Call garbage collection at the time of unloading of Preview. This is to mitigate issue with WebBrowser Control not able to dispose properly.
// Call garbage collection at the time of unloading of Preview.
// Which is preventing prevhost.exe to exit at the time of closing File explorer.
// Preview Handlers run in a separate process from PowerToys. This will not affect the performance of other modules.
// Mitigate the following Github issue: https://github.com/microsoft/PowerToys/issues/1468

View File

@ -1,112 +0,0 @@
// 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;
namespace Common
{
/// <summary>
/// Flags to control download and execution in Web Browser Control.
/// Values of flags are defined in mshtmdid.h in distributed Windows Sdk.
/// </summary>
[Flags]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Interop, keeping stuff in sync")]
public enum WebBrowserDownloadControlFlags : int
{
/// <summary>
/// Images will be downloaded from the server if this flag is set.
/// </summary>
DLIMAGES = 0x00000010,
/// <summary>
/// Videos will be downloaded from the server if this flag is set.
/// </summary>
VIDEOS = 0x00000020,
/// <summary>
/// Background sounds will be downloaded from the server if this flag is set.
/// </summary>
BGSOUNDS = 0x00000040,
/// <summary>
/// Scripts will not be executed.
/// </summary>
NO_SCRIPTS = 0x00000080,
/// <summary>
/// Java applets will not be executed.
/// </summary>
NO_JAVA = 0x00000100,
/// <summary>
/// ActiveX controls will not be executed.
/// </summary>
NO_RUNACTIVEXCTLS = 0x00000200,
/// <summary>
/// ActiveX controls will not be downloaded.
/// </summary>
NO_DLACTIVEXCTLS = 0x00000400,
/// <summary>
/// The page will only be downloaded, not displayed.
/// </summary>
DOWNLOADONLY = 0x00000800,
/// <summary>
/// WebBrowser Control will download and parse a frameSet, but not the individual frame objects within the frameSet.
/// </summary>
NO_FRAMEDOWNLOAD = 0x00001000,
/// <summary>
/// The server will be asked for update status. Cached files will be used if the server indicates that the cached information is up-to-date.
/// </summary>
RESYNCHRONIZE = 0x00002000,
/// <summary>
/// Files will be re-downloaded from the server regardless of the update status of the files.
/// </summary>
PRAGMA_NO_CACHE = 0x00004000,
/// <summary>
/// Behaviors are not downloaded and are disabled in the document.
/// </summary>
NO_BEHAVIORS = 0x00008000,
/// <summary>
/// Character sets specified in meta elements are suppressed.
/// </summary>
NO_METACHARSET = 0x00010000,
/// <summary>
/// The browsing component will disable UTF-8 encoding.
/// </summary>
URL_ENCODING_DISABLE_UTF8 = 0x00020000,
/// <summary>
/// The browsing component will enable UTF-8 encoding.
/// </summary>
URL_ENCODING_ENABLE_UTF8 = 0x00040000,
/// <summary>
/// No Documentation Available.
/// </summary>
NOFRAMES = 0x00080000,
/// <summary>
/// WebBrowser Control always operates in offline mode.
/// </summary>
FORCEOFFLINE = 0x10000000,
/// <summary>
/// No client pull operations will be performed.
/// </summary>
NO_CLIENTPULL = 0x20000000,
/// <summary>
/// No user interface will be displayed during downloads.
/// </summary>
SILENT = 0x40000000,
}
}

View File

@ -1,142 +0,0 @@
// 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.Globalization;
using System.Reflection;
using System.Windows.Forms;
using Common;
namespace PreviewHandlerCommon
{
/// <summary>
/// Customized the WebBrowser to get control over what it downloads, displays and executes.
/// </summary>
public class WebBrowserExt : WebBrowser
{
/// <inheritdoc/>
protected override WebBrowserSiteBase CreateWebBrowserSiteBase()
{
// Returns instance of WebBrowserSiteExt.
return new WebBrowserSiteExt(this);
}
/// <summary>
/// Extend the WebBrowserSite with IDispatch implementation to handle the DISPID_AMBIENT_DLCONTROL.
/// More details: https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa770041(v=vs.85)?redirectedfrom=MSDN#controlling-download-and-execution.
/// </summary>
protected class WebBrowserSiteExt : WebBrowserSite, IReflect
{
// Dispid of DISPID_AMBIENT_DLCONTROL is defined in MsHtmdid.h header file in distributed Windows Sdk component.
private const string DISPIDAMBIENTDLCONTROL = "[DISPID=-5512]";
private WebBrowserExt browserExtControl;
/// <summary>
/// Initializes a new instance of the <see cref="WebBrowserSiteExt"/> class.
/// </summary>
/// <param name="browserControl">Browser Control Instance pass to the site.</param>
public WebBrowserSiteExt(WebBrowserExt browserControl)
: base(browserControl)
{
this.browserExtControl = browserControl;
}
/// <inheritdoc/>
public Type UnderlyingSystemType
{
get { return this.GetType(); }
}
/// <inheritdoc/>
public object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters)
{
object result;
if (name != null && name.Equals(DISPIDAMBIENTDLCONTROL, StringComparison.Ordinal))
{
// Using InvariantCulture since this is used for web browser configurations
result = Convert.ToInt32(
WebBrowserDownloadControlFlags.DLIMAGES |
WebBrowserDownloadControlFlags.PRAGMA_NO_CACHE |
WebBrowserDownloadControlFlags.FORCEOFFLINE |
WebBrowserDownloadControlFlags.NO_CLIENTPULL |
WebBrowserDownloadControlFlags.NO_SCRIPTS |
WebBrowserDownloadControlFlags.NO_JAVA |
WebBrowserDownloadControlFlags.NO_FRAMEDOWNLOAD |
WebBrowserDownloadControlFlags.NOFRAMES |
WebBrowserDownloadControlFlags.NO_DLACTIVEXCTLS |
WebBrowserDownloadControlFlags.NO_RUNACTIVEXCTLS |
WebBrowserDownloadControlFlags.NO_BEHAVIORS |
WebBrowserDownloadControlFlags.SILENT, CultureInfo.InvariantCulture);
}
else
{
result = GetType().InvokeMember(name, invokeAttr, binder, target, args, modifiers, culture, namedParameters);
}
return result;
}
/// <inheritdoc/>
public FieldInfo[] GetFields(BindingFlags bindingAttr)
{
return this.GetType().GetFields(bindingAttr);
}
/// <inheritdoc/>
public MethodInfo[] GetMethods(BindingFlags bindingAttr)
{
return this.GetType().GetMethods(bindingAttr);
}
/// <inheritdoc/>
public PropertyInfo[] GetProperties(BindingFlags bindingAttr)
{
return this.GetType().GetProperties(bindingAttr);
}
/// <inheritdoc/>
public FieldInfo GetField(string name, BindingFlags bindingAttr)
{
return this.GetType().GetField(name, bindingAttr);
}
/// <inheritdoc/>
public MemberInfo[] GetMember(string name, BindingFlags bindingAttr)
{
return this.GetType().GetMember(name, bindingAttr);
}
/// <inheritdoc/>
public MemberInfo[] GetMembers(BindingFlags bindingAttr)
{
return this.GetType().GetMembers(bindingAttr);
}
/// <inheritdoc/>
public MethodInfo GetMethod(string name, BindingFlags bindingAttr)
{
return this.GetType().GetMethod(name, bindingAttr);
}
/// <inheritdoc/>
public MethodInfo GetMethod(string name, BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers)
{
return this.GetType().GetMethod(name, bindingAttr, binder, types, modifiers);
}
/// <inheritdoc/>
public PropertyInfo GetProperty(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers)
{
return this.GetType().GetProperty(name, bindingAttr, binder, returnType, types, modifiers);
}
/// <inheritdoc/>
public PropertyInfo GetProperty(string name, BindingFlags bindingAttr)
{
return this.GetType().GetProperty(name, bindingAttr);
}
}
}
}

View File

@ -3,7 +3,12 @@
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Windows.Forms;
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.WinForms;
namespace Common
{
@ -12,6 +17,38 @@ namespace Common
/// </summary>
public class CustomControlTest : FormHandlerControl
{
/// <summary>
/// WebView2 Control to display Svg.
/// </summary>
private WebView2 _browser;
/// <summary>
/// WebView2 Environment
/// </summary>
private CoreWebView2Environment _webView2Environment;
/// <summary>
/// Name of the virtual host
/// </summary>
public const string VirtualHostName = "PowerToysLocalCustomControlTest";
/// <summary>
/// Gets the path of the current assembly.
/// </summary>
/// <remarks>
/// Source: https://stackoverflow.com/a/283917/14774889
/// </remarks>
public static string AssemblyDirectory
{
get
{
string codeBase = Assembly.GetExecutingAssembly().Location;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
return Path.GetDirectoryName(path);
}
}
/// <summary>
/// Start the preview on the Control.
/// </summary>
@ -21,13 +58,41 @@ namespace Common
this.InvokeOnControlThread(() =>
{
var filePath = dataSource as string;
WebBrowser browser = new WebBrowser();
browser.DocumentText = "Test";
browser.Navigate(new Uri(filePath));
browser.Dock = DockStyle.Fill;
browser.IsWebBrowserContextMenuEnabled = false;
this.Controls.Add(browser);
_browser = new WebView2();
_browser.Dock = DockStyle.Fill;
_browser.Visible = true;
_browser.NavigationCompleted += (object sender, CoreWebView2NavigationCompletedEventArgs args) =>
{
// Put here logic needed after WebView2 control is done navigating to url/page
};
ConfiguredTaskAwaitable<CoreWebView2Environment>.ConfiguredTaskAwaiter
webView2EnvironmentAwaiter = CoreWebView2Environment
.CreateAsync(userDataFolder: System.Environment.GetEnvironmentVariable("USERPROFILE") +
"\\AppData\\LocalLow\\Microsoft\\PowerToys\\CustomControlTest-Temp")
.ConfigureAwait(true).GetAwaiter();
webView2EnvironmentAwaiter.OnCompleted(async () =>
{
try
{
_webView2Environment = webView2EnvironmentAwaiter.GetResult();
await _browser.EnsureCoreWebView2Async(_webView2Environment).ConfigureAwait(true);
await _browser.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync("window.addEventListener('contextmenu', window => {window.preventDefault();});");
_browser.CoreWebView2.SetVirtualHostNameToFolderMapping(VirtualHostName, AssemblyDirectory, CoreWebView2HostResourceAccessKind.Allow);
// Navigate to page represented as a string
_browser.NavigateToString("Test");
// Or navigate to Uri
_browser.Source = new Uri(filePath);
}
catch (NullReferenceException)
{
}
});
this.Controls.Add(_browser);
base.DoPreview(dataSource);
});
}