[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
22 changed files with 454 additions and 559 deletions

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);
});
}