Microsoft.Toolkit.Win32/Microsoft.Toolkit.Forms.UI..../WebView.cs

578 строки
22 KiB
C#

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.Toolkit.Win32.UI.Controls;
using Microsoft.Toolkit.Win32.UI.Controls.Interop.Win32;
using Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT;
using WebViewControlDeferredPermissionRequest = Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT.WebViewControlDeferredPermissionRequest;
using WebViewControlMoveFocusReason = Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT.WebViewControlMoveFocusReason;
using WebViewControlProcess = Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT.WebViewControlProcess;
using WebViewControlSettings = Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT.WebViewControlSettings;
namespace Microsoft.Toolkit.Forms.UI.Controls
{
/// <summary>
/// This class is an implementation of <see cref="IWebView" /> for Windows Forms. This class cannot be inherited.
/// </summary>
/// <seealso cref="Control" />
/// <seealso cref="ISupportInitialize" />
[Designer(typeof(WebViewDesigner))]
[DefaultProperty(Constants.ComponentDefaultProperty)]
[DefaultEvent(Constants.ComponentDefaultEvent)]
[Docking(DockingBehavior.AutoDock)]
[Description("Embeds a view into your application that renders web content using the Microsoft Edge rendering engine")]
[SecurityCritical]
[PermissionSet(SecurityAction.InheritanceDemand, Name = Constants.SecurityPermissionSetName)]
[Obsolete("The WebView control will be replaced by WebView2 in the future. Read more at https://aka.ms/webview2")]
public sealed partial class WebView : Control, IWebView, ISupportInitialize
{
private string _delayedEnterpriseId = WebViewDefaults.EnterpriseId;
private bool _delayedIsIndexDbEnabled = WebViewDefaults.IsIndexedDBEnabled;
private bool _delayedIsJavaScriptEnabled = WebViewDefaults.IsJavaScriptEnabled;
private bool _delayedIsScriptNotifyAllowed = WebViewDefaults.IsScriptNotifyEnabled;
private bool _delayedPrivateNetworkEnabled = WebViewDefaults.IsPrivateNetworkEnabled;
private Uri _delayedSource;
private WebViewControlHost _webViewControl;
private bool _webViewControlClosed;
/// <summary>
/// Initializes a new instance of the <see cref="WebView" /> class.
/// </summary>
public WebView()
{
Paint += OnWebViewPaint;
Layout += OnWebViewLayout;
}
public WebView(WebViewControlProcess process)
: this()
{
Process = process;
}
internal WebViewControlHost Host => _webViewControl;
/// <inheritdoc />
protected override void DestroyHandle()
{
// In RS4 if a component is not completely cleaned up it could cause a hang, which was fixed in RS5
// For compatability with RS4, call Close to remove the HWNDs to avoid a possible message storm and UI lock up
Close();
base.DestroyHandle();
}
/// <inheritdoc />
protected override void OnHandleDestroyed(EventArgs e)
{
// In RS4 if a component is not completely cleaned up it could cause a hang, which was fixed in RS5
// For compatability with RS4, call Close to remove the HWNDs to avoid a possible message storm and UI lock up
Close();
base.OnHandleDestroyed(e);
}
/// <summary>
/// Gets a value indicating whether <see cref="WebView"/> is supported in this environment.
/// </summary>
/// <value><see langword="true" /> if this instance is supported; otherwise, <see langword="false" />.</value>
public static bool IsSupported => WebViewControlHost.IsSupported;
/// <inheritdoc />
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool ContainsFullScreenElement
{
get
{
Verify.IsFalse(IsDisposed);
Verify.IsNotNull(_webViewControl);
return _webViewControl?.ContainsFullScreenElement ?? false;
}
}
/// <summary>
/// Gets a value indicating whether the <see cref="WebView"/> is currently in design mode.
/// </summary>
/// <value><see langword="true"/> if the <see cref="WebView"/> is currently in design mode; otherwise, <see langword="false"/>.</value>
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public new bool DesignMode => IsInDesignMode();
/// <summary>
/// Gets the document title.
/// </summary>
/// <value>The document title.</value>
/// <inheritdoc />
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public string DocumentTitle
{
get
{
Verify.IsFalse(IsDisposed);
Verify.IsNotNull(_webViewControl);
return _webViewControl?.DocumentTitle;
}
}
/// <inheritdoc />
[StringResourceCategory(Constants.CategoryBehavior)]
[DefaultValue(WebViewDefaults.EnterpriseId)]
public string EnterpriseId
{
get
{
Verify.IsFalse(IsDisposed);
Verify.Implies(Initializing, !Initialized);
#if DEBUG
if (!DesignMode)
{
Verify.Implies(Initialized, WebViewControlInitialized);
}
#endif
return WebViewControlInitialized
? _webViewControl.Process.EnterpriseId
: _delayedEnterpriseId;
}
set
{
Verify.IsFalse(IsDisposed);
_delayedEnterpriseId = value;
if (!DesignMode)
{
EnsureInitialized();
if (WebViewControlInitialized
&& !string.Equals(_delayedEnterpriseId, _webViewControl.Process.EnterpriseId, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(DesignerUI.E_CANNOT_CHANGE_AFTER_INIT);
}
}
}
}
/// <summary>
/// Gets a value indicating whether this <see cref="WebView" /> is focused.
/// </summary>
/// <value><see langword="true" /> if focused; otherwise, <see langword="false" />.</value>
/// <inheritdoc />
/// <remarks>Returns <see langword="true" /> if this or any of its child windows has focus.</remarks>
public override bool Focused
{
get
{
if (base.Focused)
{
return true;
}
var hwndFocus = UnsafeNativeMethods.GetFocus();
var ret = hwndFocus != IntPtr.Zero
&& NativeMethods.IsChild(new HandleRef(this, Handle), new HandleRef(null, hwndFocus));
return ret;
}
}
/// <summary>
/// Gets or sets a value indicating whether this instance is indexed database enabled.
/// </summary>
/// <value><see langword="true" /> if this instance is indexed database enabled; otherwise, <see langword="false" />.</value>
/// <inheritdoc />
[StringResourceCategory(Constants.CategoryBehavior)]
[DefaultValue(WebViewDefaults.IsIndexedDBEnabled)]
public bool IsIndexedDBEnabled
{
get
{
Verify.IsFalse(IsDisposed);
Verify.Implies(Initializing, !Initialized);
#if DEBUG
if (!DesignMode)
{
Verify.Implies(Initialized, WebViewControlInitialized);
}
#endif
return WebViewControlInitialized
? _webViewControl.Settings.IsIndexedDBEnabled
: _delayedIsIndexDbEnabled;
}
set
{
Verify.IsFalse(IsDisposed);
_delayedIsIndexDbEnabled = value;
if (!DesignMode)
{
EnsureInitialized();
if (WebViewControlInitialized)
{
_webViewControl.Settings.IsIndexedDBEnabled = value;
}
}
}
}
/// <summary>
/// Gets or sets a value indicating whether the use of JavaScript is allowed.
/// </summary>
/// <value><c>true</c> if the use of JavaScript is allowed; otherwise, <c>false</c>.</value>
/// <inheritdoc />
[StringResourceCategory(Constants.CategoryBehavior)]
[DefaultValue(WebViewDefaults.IsJavaScriptEnabled)]
public bool IsJavaScriptEnabled
{
get
{
Verify.IsFalse(IsDisposed);
Verify.Implies(Initializing, !Initialized);
#if DEBUG
if (!DesignMode)
{
Verify.Implies(Initialized, WebViewControlInitialized);
}
#endif
return WebViewControlInitialized
? _webViewControl.Settings.IsJavaScriptEnabled
: _delayedIsJavaScriptEnabled;
}
set
{
Verify.IsFalse(IsDisposed);
_delayedIsJavaScriptEnabled = value;
if (!DesignMode)
{
EnsureInitialized();
if (WebViewControlInitialized)
{
_webViewControl.Settings.IsJavaScriptEnabled = value;
}
}
}
}
/// <summary>
/// Gets or sets a value indicating whether <see cref="Microsoft.Toolkit.Forms.UI.Controls.WebView.ScriptNotify" /> is allowed;
/// </summary>
/// <value><c>true</c> if <see cref="Microsoft.Toolkit.Forms.UI.Controls.WebView.ScriptNotify" /> is allowed; otherwise, <c>false</c>.</value>
/// <inheritdoc />
[StringResourceCategory(Constants.CategoryBehavior)]
[DefaultValue(WebViewDefaults.IsScriptNotifyEnabled)]
public bool IsScriptNotifyAllowed
{
get
{
Verify.IsFalse(IsDisposed);
Verify.Implies(Initializing, !Initialized);
#if DEBUG
if (!DesignMode)
{
Verify.Implies(Initialized, WebViewControlInitialized);
}
#endif
return WebViewControlInitialized
? _webViewControl.Settings.IsScriptNotifyAllowed
: _delayedIsScriptNotifyAllowed;
}
set
{
Verify.IsFalse(IsDisposed);
_delayedIsScriptNotifyAllowed = value;
if (!DesignMode)
{
EnsureInitialized();
if (WebViewControlInitialized)
{
_webViewControl.Settings.IsScriptNotifyAllowed = value;
}
}
}
}
/// <inheritdoc />
/// <summary>
/// Gets or sets a value indicating whether this instance is private network client server capability enabled.
/// </summary>
/// <value><see langword="true" /> if this instance is private network client server capability enabled; otherwise, <see langword="false" />.</value>
/// <exception cref="System.InvalidOperationException">Value cannot be set once the control is initialized.</exception>
[StringResourceCategory(Constants.CategoryBehavior)]
[DefaultValue(WebViewDefaults.IsPrivateNetworkEnabled)]
public bool IsPrivateNetworkClientServerCapabilityEnabled
{
get
{
Verify.IsFalse(IsDisposed);
Verify.Implies(Initializing, !Initialized);
#if DEBUG
if (!DesignMode)
{
Verify.Implies(Initialized, WebViewControlInitialized);
}
#endif
return WebViewControlInitialized
? _webViewControl.Process.IsPrivateNetworkClientServerCapabilityEnabled
: _delayedPrivateNetworkEnabled;
}
set
{
Verify.IsFalse(IsDisposed);
_delayedPrivateNetworkEnabled = value;
if (!DesignMode)
{
EnsureInitialized();
if (WebViewControlInitialized
&& _webViewControl.Process.IsPrivateNetworkClientServerCapabilityEnabled != _delayedPrivateNetworkEnabled)
{
throw new InvalidOperationException(DesignerUI.E_CANNOT_CHANGE_AFTER_INIT);
}
}
}
}
/// <summary>
/// Gets a <see cref="WebViewControlProcess" /> object that the control is hosted in.
/// </summary>
/// <value>The <see cref="WebViewControlProcess" /> object that the control is hosted in.</value>
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public WebViewControlProcess Process { get; private set; }
/// <summary>
/// Gets a <see cref="WebViewControlSettings" /> object that contains properties to enable or disable <see cref="WebView" /> features.
/// </summary>
/// <value>A <see cref="WebViewControlSettings" /> object that contains properties to enable or disable <see cref="WebView" /> features.</value>
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public WebViewControlSettings Settings
{
get
{
Verify.IsFalse(IsDisposed);
Verify.Implies(Initializing, !Initialized);
#if DEBUG
if (!DesignMode)
{
Verify.Implies(Initialized, WebViewControlInitialized);
}
#endif
return _webViewControl?.Settings;
}
}
/// <summary>
/// Gets or sets the Uniform Resource Identifier (URI) source of the HTML content to display in the <see cref="WebView" />.
/// </summary>
/// <value>The Uniform Resource Identifier (URI) source of the HTML content to display in the <see cref="WebView" />.</value>
[Bindable(true)]
[StringResourceCategory(Constants.CategoryBehavior)]
[StringResourceDescription(Constants.DescriptionSource)]
[TypeConverter(typeof(WebBrowserUriTypeConverter))]
[DefaultValue((string)null)]
public Uri Source
{
get
{
Verify.IsFalse(IsDisposed);
Verify.Implies(Initializing, !Initialized);
#if DEBUG
if (!DesignMode)
{
Verify.Implies(Initialized, WebViewControlInitialized);
}
#endif
return WebViewControlInitialized
? _webViewControl.Source
: _delayedSource;
}
set
{
Verify.IsFalse(IsDisposed);
_delayedSource = value;
if (!DesignMode)
{
EnsureInitialized();
if (WebViewControlInitialized)
{
if (Initializing && value != null)
{
// During initialization if there is no Source set a navigation to "about:blank" will occur
_webViewControl.Source = value;
}
else if (Initialized)
{
// After the control is initialized send all values, regardless of if they are null
_webViewControl.Source = value;
}
}
}
}
}
/// <summary>
/// Gets the version of EDGEHTML.DLL used by the control.
/// </summary>
/// <value>The version of EDGEHTML.DLL used by the control.</value>
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Version Version => _webViewControl?.Version;
/// <inheritdoc />
[Obsolete("This item has been depreciated and will be removed in a future version. Use AddInitializeScript(string script) instead.", false)]
public void AddPreLoadedScript(string script) => AddInitializeScript(script);
/// <inheritdoc />
public void AddInitializeScript(string script)
{
Verify.IsFalse(IsDisposed);
Verify.Implies(Initializing, !Initialized);
#if DEBUG
if (!DesignMode)
{
Verify.Implies(Initialized, WebViewControlInitialized);
}
#endif
_webViewControl?.AddInitializeScript(script);
}
/// <summary>
/// Closes this control.
/// </summary>
public void Close()
{
var webViewControlAlreadyClosed = _webViewControlClosed;
_webViewControlClosed = true;
// Unsubscribe all events:
UnsubscribeEvents();
if (!webViewControlAlreadyClosed)
{
_webViewControl?.Close();
_webViewControl?.Dispose();
}
_webViewControl = null;
Process = null;
}
/// <summary>
/// Gets the deferred permission request with the specified Id.
/// </summary>
/// <param name="id">The Id of the deferred permission request.</param>
/// <returns>A <see cref="WebViewControlDeferredPermissionRequest" /> object of the specified <paramref name="id" />.</returns>
public WebViewControlDeferredPermissionRequest GetDeferredPermissionRequestById(uint id) => _webViewControl?.GetDeferredPermissionRequestById(id);
/// <inheritdoc />
public string InvokeScript(string scriptName) => InvokeScript(scriptName, null);
/// <inheritdoc />
public string InvokeScript(string scriptName, params string[] arguments) => InvokeScript(scriptName, (IEnumerable<string>)arguments);
/// <inheritdoc />
public string InvokeScript(string scriptName, IEnumerable<string> arguments)
{
// WebViewControlHost ends up calling InvokeScriptAsync anyway
// The problem we have is that InvokeScript could be called from a UI thread and waiting for an async result that could lead to deadlock
return InvokeScriptAsync(scriptName, arguments).WaitWithNestedMessageLoop();
}
/// <inheritdoc />
public Task<string> InvokeScriptAsync(string scriptName) => _webViewControl?.InvokeScriptAsync(scriptName);
/// <inheritdoc />
public Task<string> InvokeScriptAsync(string scriptName, params string[] arguments) =>
_webViewControl?.InvokeScriptAsync(scriptName, arguments);
/// <inheritdoc />
public Task<string> InvokeScriptAsync(string scriptName, IEnumerable<string> arguments)
=> _webViewControl?.InvokeScriptAsync(scriptName, arguments);
/// <inheritdoc />
public void MoveFocus(WebViewControlMoveFocusReason reason) => _webViewControl?.MoveFocus(reason);
/// <summary>
/// Releases the unmanaged resources used by the <see cref="System.Windows.Forms.Control" /> and its child controls and optionally releases the managed resources.
/// </summary>
/// <param name="disposing"><see langword="true" /> to release both managed and unmanaged resources; <see langword="false" /> to release only unmanaged resources.</param>
protected override void Dispose(bool disposing)
{
try
{
if (disposing)
{
Close();
_webViewControl?.Dispose();
_webViewControl = null;
Process = null;
}
}
finally
{
base.Dispose(disposing);
}
}
private bool IsInDesignMode()
{
var wpfDesignMode = LicenseManager.UsageMode == LicenseUsageMode.Designtime;
var formsDesignMode = System.Diagnostics.Process.GetCurrentProcess().ProcessName == "devenv";
return wpfDesignMode || formsDesignMode;
}
// Ensures the WebViewControl's size stays in sync
private void OnWebViewLayout(object sender, LayoutEventArgs e)
{
// This event is raised once at startup with the AffectedControl and AffectedProperty properties
// on the LayoutEventArgs as null.
if (e.AffectedControl != null && e.AffectedProperty != null)
{
// Ensure that the affected property is the Bounds property to the control
if (e.AffectedProperty == nameof(Bounds))
{
// In a typical control the DisplayRectangle is the interior canvas of the control
// and in a scrolling control the DisplayRectangle would be larger than the ClientRectangle.
// However, that is abstracted from us in WebView so we need to synchronize the ClientRectangle
// and permit WebView to handle scrolling based on the new viewport
UpdateBounds(ClientRectangle);
}
}
}
private void OnWebViewPaint(object sender, PaintEventArgs e)
{
if (!DesignMode)
{
return;
}
using (var g = e.Graphics)
{
using (var hb = new HatchBrush(HatchStyle.ZigZag, Color.Black, BackColor))
{
g.FillRectangle(hb, ClientRectangle);
}
}
}
private void UpdateBounds(Rectangle bounds)
{
_webViewControl?.UpdateBounds(bounds);
}
}
}