// Copyright © 2021 The CefSharp Authors. All rights reserved.
|
//
|
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
|
|
using System;
|
using System.ComponentModel;
|
using System.Drawing;
|
using System.Threading;
|
using System.Threading.Tasks;
|
using System.Windows.Forms;
|
|
namespace CefSharp.WinForms.Host
|
{
|
/// <summary>
|
/// Chromium Browser Host Control, used for hosting Popups in WinForms
|
/// </summary>
|
/// <seealso cref="Control" />
|
[Docking(DockingBehavior.AutoDock), ToolboxBitmap(typeof(ChromiumWebBrowser)),
|
Designer(typeof(ChromiumWebBrowserDesigner))]
|
public class ChromiumHostControl : ChromiumHostControlBase, IWinFormsChromiumWebBrowser
|
{
|
/// <summary>
|
/// Get access to the core <see cref="IBrowser"/> instance.
|
/// Maybe null if the underlying CEF Browser has not yet been
|
/// created or if this control has been disposed. Check
|
/// <see cref="IBrowser.IsDisposed"/> before accessing.
|
/// </summary>
|
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue(null)]
|
public IBrowser BrowserCore { get; internal set; }
|
|
/// <summary>
|
/// Event handler that will get called when the resource load for a navigation fails or is canceled.
|
/// It's important to note this event is fired on a CEF UI thread, which by default is not the same as your application UI
|
/// thread. It is unwise to block on this thread for any length of time as your browser will become unresponsive and/or hang..
|
/// To access UI elements you'll need to Invoke/Dispatch onto the UI Thread.
|
/// </summary>
|
public event EventHandler<LoadErrorEventArgs> LoadError;
|
/// <summary>
|
/// Event handler that will get called when the browser begins loading a frame. Multiple frames may be loading at the same
|
/// time. Sub-frames may start or continue loading after the main frame load has ended. This method may not be called for a
|
/// particular frame if the load request for that frame fails. For notification of overall browser load status use
|
/// OnLoadingStateChange instead.
|
/// It's important to note this event is fired on a CEF UI thread, which by default is not the same as your application UI
|
/// thread. It is unwise to block on this thread for any length of time as your browser will become unresponsive and/or hang..
|
/// To access UI elements you'll need to Invoke/Dispatch onto the UI Thread.
|
/// </summary>
|
/// <remarks>Whilst this may seem like a logical place to execute js, it's called before the DOM has been loaded, implement
|
/// <see cref="IRenderProcessMessageHandler.OnContextCreated" /> as it's called when the underlying V8Context is created
|
/// </remarks>
|
public event EventHandler<FrameLoadStartEventArgs> FrameLoadStart;
|
/// <summary>
|
/// Event handler that will get called when the browser is done loading a frame. Multiple frames may be loading at the same
|
/// time. Sub-frames may start or continue loading after the main frame load has ended. This method will always be called
|
/// for all frames irrespective of whether the request completes successfully.
|
/// It's important to note this event is fired on a CEF UI thread, which by default is not the same as your application UI
|
/// thread. It is unwise to block on this thread for any length of time as your browser will become unresponsive and/or hang..
|
/// To access UI elements you'll need to Invoke/Dispatch onto the UI Thread.
|
/// </summary>
|
public event EventHandler<FrameLoadEndEventArgs> FrameLoadEnd;
|
/// <summary>
|
/// Event handler that will get called when the Loading state has changed.
|
/// This event will be fired twice. Once when loading is initiated either programmatically or
|
/// by user action, and once when loading is terminated due to completion, cancellation of failure.
|
/// It's important to note this event is fired on a CEF UI thread, which by default is not the same as your application UI
|
/// thread. It is unwise to block on this thread for any length of time as your browser will become unresponsive and/or hang..
|
/// To access UI elements you'll need to Invoke/Dispatch onto the UI Thread.
|
/// </summary>
|
public event EventHandler<LoadingStateChangedEventArgs> LoadingStateChanged;
|
/// <summary>
|
/// Event handler for receiving Javascript console messages being sent from web pages.
|
/// It's important to note this event is fired on a CEF UI thread, which by default is not the same as your application UI
|
/// thread. It is unwise to block on this thread for any length of time as your browser will become unresponsive and/or hang..
|
/// To access UI elements you'll need to Invoke/Dispatch onto the UI Thread.
|
/// (The exception to this is when you're running with settings.MultiThreadedMessageLoop = false, then they'll be the same thread).
|
/// </summary>
|
public event EventHandler<ConsoleMessageEventArgs> ConsoleMessage;
|
/// <summary>
|
/// Event handler for changes to the status message.
|
/// It's important to note this event is fired on a CEF UI thread, which by default is not the same as your application UI
|
/// thread. It is unwise to block on this thread for any length of time as your browser will become unresponsive and/or hang.
|
/// To access UI elements you'll need to Invoke/Dispatch onto the UI Thread.
|
/// (The exception to this is when you're running with settings.MultiThreadedMessageLoop = false, then they'll be the same thread).
|
/// </summary>
|
public event EventHandler<StatusMessageEventArgs> StatusMessage;
|
|
/// <summary>
|
/// Occurs when the browser address changed.
|
/// It's important to note this event is fired on a CEF UI thread, which by default is not the same as your application UI
|
/// thread. It is unwise to block on this thread for any length of time as your browser will become unresponsive and/or hang..
|
/// To access UI elements you'll need to Invoke/Dispatch onto the UI Thread.
|
/// </summary>
|
public event EventHandler<AddressChangedEventArgs> AddressChanged;
|
/// <summary>
|
/// Occurs when the browser title changed.
|
/// It's important to note this event is fired on a CEF UI thread, which by default is not the same as your application UI
|
/// thread. It is unwise to block on this thread for any length of time as your browser will become unresponsive and/or hang..
|
/// To access UI elements you'll need to Invoke/Dispatch onto the UI Thread.
|
/// </summary>
|
public event EventHandler<TitleChangedEventArgs> TitleChanged;
|
|
/// <summary>
|
/// A flag that indicates whether the control is currently loading one or more web pages (true) or not (false).
|
/// </summary>
|
/// <value><c>true</c> if this instance is loading; otherwise, <c>false</c>.</value>
|
/// <remarks>In the WPF control, this property is implemented as a Dependency Property and fully supports data
|
/// binding.</remarks>
|
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue(false)]
|
public bool IsLoading { get; private set; }
|
/// <summary>
|
/// The address (URL) which the browser control is currently displaying.
|
/// Will automatically be updated as the user navigates to another page (e.g. by clicking on a link).
|
/// </summary>
|
/// <value>The address.</value>
|
/// <remarks>In the WPF control, this property is implemented as a Dependency Property and fully supports data
|
/// binding.</remarks>
|
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue(null)]
|
public string Address { get; private set; }
|
/// <summary>
|
/// A flag that indicates whether the state of the control currently supports the GoForward action (true) or not (false).
|
/// </summary>
|
/// <value><c>true</c> if this instance can go forward; otherwise, <c>false</c>.</value>
|
/// <remarks>In the WPF control, this property is implemented as a Dependency Property and fully supports data
|
/// binding.</remarks>
|
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue(false)]
|
public bool CanGoForward { get; private set; }
|
/// <summary>
|
/// A flag that indicates whether the state of the control current supports the GoBack action (true) or not (false).
|
/// </summary>
|
/// <value><c>true</c> if this instance can go back; otherwise, <c>false</c>.</value>
|
/// <remarks>In the WPF control, this property is implemented as a Dependency Property and fully supports data
|
/// binding.</remarks>
|
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue(false)]
|
public bool CanGoBack { get; private set; }
|
/// <summary>
|
/// A flag that indicates whether the WebBrowser is initialized (true) or not (false).
|
/// </summary>
|
/// <value><c>true</c> if this instance is browser initialized; otherwise, <c>false</c>.</value>
|
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue(false)]
|
public bool IsBrowserInitialized
|
{
|
get { return BrowserCore != null; }
|
}
|
|
/// <summary>
|
/// Handles the <see cref="E:FrameLoadStart" /> event.
|
/// </summary>
|
/// <param name="args">The <see cref="FrameLoadStartEventArgs"/> instance containing the event data.</param>
|
internal void OnFrameLoadStart(FrameLoadStartEventArgs args)
|
{
|
FrameLoadStart?.Invoke(this, args);
|
}
|
|
/// <summary>
|
/// Handles the <see cref="E:FrameLoadEnd" /> event.
|
/// </summary>
|
/// <param name="args">The <see cref="FrameLoadEndEventArgs"/> instance containing the event data.</param>
|
internal void OnFrameLoadEnd(FrameLoadEndEventArgs args)
|
{
|
FrameLoadEnd?.Invoke(this, args);
|
}
|
|
/// <summary>
|
/// Handles the <see cref="E:ConsoleMessage" /> event.
|
/// </summary>
|
/// <param name="args">The <see cref="ConsoleMessageEventArgs"/> instance containing the event data.</param>
|
internal void OnConsoleMessage(ConsoleMessageEventArgs args)
|
{
|
ConsoleMessage?.Invoke(this, args);
|
}
|
|
/// <summary>
|
/// Handles the <see cref="E:StatusMessage" /> event.
|
/// </summary>
|
/// <param name="args">The <see cref="StatusMessageEventArgs"/> instance containing the event data.</param>
|
internal void OnStatusMessage(StatusMessageEventArgs args)
|
{
|
StatusMessage?.Invoke(this, args);
|
}
|
|
/// <summary>
|
/// Handles the <see cref="E:LoadError" /> event.
|
/// </summary>
|
/// <param name="args">The <see cref="LoadErrorEventArgs"/> instance containing the event data.</param>
|
internal void OnLoadError(LoadErrorEventArgs args)
|
{
|
LoadError?.Invoke(this, args);
|
}
|
|
/// <summary>
|
/// Sets the loading state change.
|
/// </summary>
|
/// <param name="args">The <see cref="LoadingStateChangedEventArgs"/> instance containing the event data.</param>
|
internal void OnLoadingStateChange(LoadingStateChangedEventArgs args)
|
{
|
CanGoBack = args.CanGoBack;
|
CanGoForward = args.CanGoForward;
|
IsLoading = args.IsLoading;
|
|
LoadingStateChanged?.Invoke(this, args);
|
}
|
|
/// <summary>
|
/// Sets the title.
|
/// </summary>
|
/// <param name="args">The <see cref="TitleChangedEventArgs"/> instance containing the event data.</param>
|
internal void OnTitleChanged(TitleChangedEventArgs args)
|
{
|
TitleChanged?.Invoke(this, args);
|
}
|
|
/// <summary>
|
/// Sets the address.
|
/// </summary>
|
/// <param name="args">The <see cref="AddressChangedEventArgs"/> instance containing the event data.</param>
|
internal void OnAddressChanged(AddressChangedEventArgs args)
|
{
|
Address = args.Address;
|
|
AddressChanged?.Invoke(this, args);
|
}
|
|
/// <summary>
|
/// Loads the specified <paramref name="url"/> in the Main Frame.
|
/// </summary>
|
/// <param name="url">The URL to be loaded.</param>
|
public void LoadUrl(string url)
|
{
|
if (IsDisposed)
|
{
|
return;
|
}
|
|
var browser = BrowserCore;
|
|
if (browser == null || browser.IsDisposed)
|
{
|
return;
|
}
|
|
using (var frame = browser.MainFrame)
|
{
|
frame.LoadUrl(url);
|
}
|
}
|
|
/// <inheritdoc/>
|
public Task<LoadUrlAsyncResponse> LoadUrlAsync(string url)
|
{
|
//LoadUrlAsync is actually a static method so that CefSharp.Wpf.HwndHost can reuse the code
|
return CefSharp.WebBrowserExtensions.LoadUrlAsync(this, url);
|
}
|
|
/// <inheritdoc/>
|
public Task<WaitForNavigationAsyncResponse> WaitForNavigationAsync(TimeSpan? timeout = null, CancellationToken cancellationToken = default)
|
{
|
//WaitForNavigationAsync is actually a static method so that CefSharp.Wpf.HwndHost can reuse the code
|
return CefSharp.WebBrowserExtensions.WaitForNavigationAsync(this, timeout, cancellationToken);
|
}
|
|
/// <summary>
|
/// Returns the main (top-level) frame for the browser window.
|
/// </summary>
|
/// <returns> the main frame</returns>
|
public IFrame GetMainFrame()
|
{
|
var browser = BrowserCore;
|
|
if(browser == null)
|
{
|
throw new Exception(CefSharp.WebBrowserExtensions.BrowserNullExceptionString);
|
}
|
|
return browser.MainFrame;
|
}
|
|
/// <inheritdoc/>
|
protected override void Dispose(bool disposing)
|
{
|
if (disposing)
|
{
|
var browserCore = BrowserCore;
|
|
AddressChanged = null;
|
ConsoleMessage = null;
|
FrameLoadEnd = null;
|
FrameLoadStart = null;
|
LoadError = null;
|
LoadingStateChanged = null;
|
StatusMessage = null;
|
TitleChanged = null;
|
BrowserCore = null;
|
|
if (browserCore?.IsDisposed == false)
|
{
|
//Close the underlying CEF Browser
|
browserCore?.GetHost()?.CloseBrowser(true);
|
}
|
}
|
|
base.Dispose(disposing);
|
}
|
|
/// <summary>
|
/// Gets the <see cref="ChromiumHostControl"/> associated with
|
/// a specific <see cref="IBrowser"/> instance.
|
/// </summary>
|
/// <param name="browser">browser</param>
|
/// <returns>returns the assocaited <see cref="ChromiumHostControl"/> or null if Disposed or no host found.</returns>
|
public static ChromiumHostControl FromBrowser(IBrowser browser)
|
{
|
return FromBrowser<ChromiumHostControl>(browser);
|
}
|
}
|
}
|