// Copyright © 2020 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.Numerics; using System.Threading; using System.Threading.Tasks; using CefSharp.Internals; #if OFFSCREEN namespace CefSharp.OffScreen #elif WPF namespace CefSharp.Wpf #elif WINFORMS namespace CefSharp.WinForms #endif { //ChromiumWebBrowser Partial class implementation shared between the //WPF, Winforms and Offscreen public partial class ChromiumWebBrowser { public const string BrowserNotInitializedExceptionErrorMessage = "The ChromiumWebBrowser instance creates the underlying Chromium Embedded Framework (CEF) browser instance in an async fashion. " + "The undelying CefBrowser instance is not yet initialized. Use the IsBrowserInitializedChanged event and check " + "the IsBrowserInitialized property to determine when the browser has been initialized."; private const string CefInitializeFailedErrorMessage = "Cef.Initialize() failed.Check the log file see https://github.com/cefsharp/CefSharp/wiki/Trouble-Shooting#log-file for details."; private const string CefIsInitializedFalseErrorMessage = "Cef.IsInitialized was false!.Check the log file for errors!. See https://github.com/cefsharp/CefSharp/wiki/Trouble-Shooting#log-file for details."; /// /// Used as workaround for issue https://github.com/cefsharp/CefSharp/issues/3021 /// private int canExecuteJavascriptInMainFrameChildProcessId; /// /// The browser initialized - boolean represented as 0 (false) and 1(true) as we use Interlocker to increment/reset /// private int browserInitialized; /// /// The value for disposal, if it's 1 (one) then this instance is either disposed /// or in the process of getting disposed /// private int disposeSignaled; /// /// The browser /// private IBrowser browser; /// /// Initial browser load task complection source /// private TaskCompletionSource initialLoadTaskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); /// /// Initial browser load action /// private Action initialLoadAction; /// /// Get access to the core instance. /// Maybe null if the underlying CEF Browser has not yet been /// created or if this control has been disposed. Check /// before accessing. /// [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue(null)] public IBrowser BrowserCore { get; internal set; } /// /// A flag that indicates if you can execute javascript in the main frame. /// Flag is set to true in IRenderProcessMessageHandler.OnContextCreated. /// and false in IRenderProcessMessageHandler.OnContextReleased /// [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue(false)] public bool CanExecuteJavascriptInMainFrame { get; private set; } /// /// Implement and assign to handle dialog events. /// /// The dialog handler. [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue(null)] public IDialogHandler DialogHandler { get; set; } /// /// Implement and assign to handle events related to JavaScript Dialogs. /// /// The js dialog handler. [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue(null)] public IJsDialogHandler JsDialogHandler { get; set; } /// /// Implement and assign to handle events related to key press. /// /// The keyboard handler. [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue(null)] public IKeyboardHandler KeyboardHandler { get; set; } /// /// Implement and assign to handle events related to browser requests. /// /// The request handler. [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue(null)] public IRequestHandler RequestHandler { get; set; } /// /// Implement and assign to handle events related to downloading files. /// /// The download handler. [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue(null)] public IDownloadHandler DownloadHandler { get; set; } /// /// Implement and assign to handle events related to browser load status. /// /// The load handler. [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue(null)] public ILoadHandler LoadHandler { get; set; } /// /// Implement and assign to handle events related to popups. /// /// The life span handler. [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue(null)] public ILifeSpanHandler LifeSpanHandler { get; set; } /// /// Implement and assign to handle events related to browser display state. /// /// The display handler. [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue(null)] public IDisplayHandler DisplayHandler { get; set; } /// /// Implement and assign to handle events related to the browser context menu /// /// The menu handler. [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue(null)] public IContextMenuHandler MenuHandler { get; set; } /// /// Implement and assign to handle messages from the render process. /// /// The render process message handler. [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue(null)] public IRenderProcessMessageHandler RenderProcessMessageHandler { get; set; } /// /// Implement to handle events related to find results. /// /// The find handler. [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue(null)] public IFindHandler FindHandler { get; set; } /// /// Implement to handle audio events. /// [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue(null)] public IAudioHandler AudioHandler { get; set; } /// /// Implement to handle frame events. /// [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue(null)] public IFrameHandler FrameHandler { get; set; } /// /// Implement to handle events related to permission requests. /// [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue(null)] public IPermissionHandler PermissionHandler { get; set; } /// /// The for this ChromiumWebBrowser. /// /// The focus handler. /// If you need customized focus handling behavior for WinForms, the suggested /// best practice would be to inherit from DefaultFocusHandler and try to avoid /// needing to override the logic in OnGotFocus. The implementation in /// DefaultFocusHandler relies on very detailed behavior of how WinForms and /// Windows interact during window activation. [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue(null)] public IFocusHandler FocusHandler { get; set; } /// /// Implement and assign to handle events related to dragging. /// /// The drag handler. [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue(null)] public IDragHandler DragHandler { get; set; } /// /// Implement and control the loading of resources /// /// The resource handler factory. [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue(null)] public IResourceRequestHandlerFactory ResourceRequestHandlerFactory { get; set; } /// /// 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. /// public event EventHandler LoadError; /// /// 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. /// /// Whilst this may seem like a logical place to execute js, it's called before the DOM has been loaded, implement /// as it's called when the underlying V8Context is created /// public event EventHandler FrameLoadStart; /// /// 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. /// public event EventHandler FrameLoadEnd; /// /// 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. /// public event EventHandler LoadingStateChanged; /// /// 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). /// public event EventHandler ConsoleMessage; /// /// 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). /// public event EventHandler StatusMessage; /// /// Event handler that will get called when the message that originates from CefSharp.PostMessage /// public event EventHandler JavascriptMessageReceived; /// /// A flag that indicates whether the WebBrowser is initialized (true) or not (false). /// /// true if this instance is browser initialized; otherwise, false. bool IChromiumWebBrowserBase.IsBrowserInitialized { get { return InternalIsBrowserInitialized(); } } void IWebBrowserInternal.SetCanExecuteJavascriptOnMainFrame(string frameId, bool canExecute) { //When loading pages of a different origin the frameId changes //For the first loading of a new origin the messages from the render process //Arrive in a different order than expected, the OnContextCreated message //arrives before the OnContextReleased, then the message for OnContextReleased //incorrectly overrides the value //https://github.com/cefsharp/CefSharp/issues/3021 var chromiumChildProcessId = GetChromiumChildProcessId(frameId); if (chromiumChildProcessId > canExecuteJavascriptInMainFrameChildProcessId && !canExecute) { return; } canExecuteJavascriptInMainFrameChildProcessId = chromiumChildProcessId; CanExecuteJavascriptInMainFrame = canExecute; } void IWebBrowserInternal.SetJavascriptMessageReceived(JavascriptMessageReceivedEventArgs args) { //Run the event on the ThreadPool (rather than the CEF Thread we are currently on). Task.Run(() => JavascriptMessageReceived?.Invoke(this, args)); } /// /// Handles the event. /// /// The instance containing the event data. void IWebBrowserInternal.OnFrameLoadStart(FrameLoadStartEventArgs args) { FrameLoadStart?.Invoke(this, args); } /// /// Handles the event. /// /// The instance containing the event data. void IWebBrowserInternal.OnFrameLoadEnd(FrameLoadEndEventArgs args) { FrameLoadEnd?.Invoke(this, args); } /// /// Handles the event. /// /// The instance containing the event data. void IWebBrowserInternal.OnConsoleMessage(ConsoleMessageEventArgs args) { ConsoleMessage?.Invoke(this, args); } /// /// Handles the event. /// /// The instance containing the event data. void IWebBrowserInternal.OnStatusMessage(StatusMessageEventArgs args) { StatusMessage?.Invoke(this, args); } /// /// Handles the event. /// /// The instance containing the event data. void IWebBrowserInternal.OnLoadError(LoadErrorEventArgs args) { LoadError?.Invoke(this, args); initialLoadAction?.Invoke(null, args.ErrorCode); } /// /// Gets or sets a value indicating whether this instance has parent. /// /// true if this instance has parent; otherwise, false. bool IWebBrowserInternal.HasParent { get; set; } /// /// Used by CefSharp.Puppeteer to associate a single DevToolsContext with a ChromiumWebBrowser instance. /// IDisposable IWebBrowserInternal.DevToolsContext { get; set; } /// /// Gets the browser adapter. /// /// The browser adapter. IBrowserAdapter IWebBrowserInternal.BrowserAdapter { get { return managedCefBrowserAdapter; } } void IWebBrowserInternal.OnAfterBrowserCreated(IBrowser browser) { if (IsDisposed || browser.IsDisposed) { return; } this.browser = browser; BrowserCore = browser; initialLoadAction = InitialLoad; Interlocked.Exchange(ref browserInitialized, 1); OnAfterBrowserCreated(browser); } /// /// Sets the loading state change. /// /// The instance containing the event data. void IWebBrowserInternal.SetLoadingStateChange(LoadingStateChangedEventArgs args) { SetLoadingStateChange(args); LoadingStateChanged?.Invoke(this, args); initialLoadAction?.Invoke(args.IsLoading, null); } /// public void LoadUrl(string url) { Load(url); } /// public Task LoadUrlAsync(string url) { //LoadUrlAsync is actually a static method so that CefSharp.Wpf.HwndHost can reuse the code return CefSharp.WebBrowserExtensions.LoadUrlAsync(this, url); } /// public Task 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); } /// public Task WaitForInitialLoadAsync() { return initialLoadTaskCompletionSource.Task; } /// public bool TryGetBrowserCoreById(int browserId, out IBrowser browser) { var browserAdapter = managedCefBrowserAdapter; if (IsDisposed || browserAdapter == null || browserAdapter.IsDisposed) { browser = null; return false; } browser = browserAdapter.GetBrowser(browserId); return browser != null; } /// public async Task GetContentSizeAsync() { ThrowExceptionIfDisposed(); ThrowExceptionIfBrowserNotInitialized(); using (var devToolsClient = browser.GetDevToolsClient()) { //Get the content size var layoutMetricsResponse = await devToolsClient.Page.GetLayoutMetricsAsync().ConfigureAwait(continueOnCapturedContext: false); var rect = layoutMetricsResponse.CssContentSize; return new Structs.DomRect(rect.X, rect.Y, rect.Width, rect.Height); } } private void InitialLoad(bool? isLoading, CefErrorCode? errorCode) { if (IsDisposed) { initialLoadAction = null; initialLoadTaskCompletionSource.TrySetCanceled(); return; } if (isLoading.HasValue) { if (isLoading.Value) { return; } initialLoadAction = null; var host = browser?.GetHost(); var navEntry = host?.GetVisibleNavigationEntry(); int statusCode = navEntry?.HttpStatusCode ?? -1; //By default 0 is some sort of error, we map that to -1 //so that it's clearer that something failed. if (statusCode == 0) { statusCode = -1; } initialLoadTaskCompletionSource.TrySetResult(new LoadUrlAsyncResponse(CefErrorCode.None, statusCode)); } else if (errorCode.HasValue) { //Actions that trigger a download will raise an aborted error. //Generally speaking Aborted is safe to ignore if (errorCode == CefErrorCode.Aborted) { return; } initialLoadAction = null; initialLoadTaskCompletionSource.TrySetResult(new LoadUrlAsyncResponse(errorCode.Value, -1)); } } partial void OnAfterBrowserCreated(IBrowser browser); partial void SetLoadingStateChange(LoadingStateChangedEventArgs args); /// /// Sets the handler references to null. /// Where required also calls Dispose(). /// private void FreeHandlersExceptLifeSpanAndFocus() { AudioHandler?.Dispose(); AudioHandler = null; DialogHandler = null; FindHandler = null; RequestHandler = null; DisplayHandler = null; LoadHandler = null; KeyboardHandler = null; JsDialogHandler = null; DragHandler = null; DownloadHandler = null; MenuHandler = null; ResourceRequestHandlerFactory = null; RenderProcessMessageHandler = null; this.FreeDevToolsContext(); } private static void InitializeCefInternal() { if (Cef.IsInitialized == null) { if (!Cef.Initialize(new CefSettings())) { throw new InvalidOperationException(CefInitializeFailedErrorMessage); } } if (Cef.IsInitialized == false) { throw new InvalidOperationException(CefIsInitializedFalseErrorMessage); } } /// /// Check is browser is initialized /// /// true if browser is initialized private bool InternalIsBrowserInitialized() { // Use CompareExchange to read the current value - if disposeCount is 1, we set it to 1, effectively a no-op // Volatile.Read would likely use a memory barrier which I believe is unnecessary in this scenario return Interlocked.CompareExchange(ref browserInitialized, 0, 0) == 1; } /// /// Throw exception if browser not initialized. /// /// Thrown when an exception error condition occurs. private void ThrowExceptionIfBrowserNotInitialized() { if (!InternalIsBrowserInitialized()) { throw new Exception(BrowserNotInitializedExceptionErrorMessage); } } /// /// Throw exception if disposed. /// /// Thrown when a supplied object has been disposed. private void ThrowExceptionIfDisposed() { if (IsDisposed) { throw new ObjectDisposedException("ChromiumWebBrowser"); } } private int GetChromiumChildProcessId(string frameIdentifier) { try { var parts = frameIdentifier.Split('-'); if (int.TryParse(parts[0], out var childProcessId)) return childProcessId; } catch { } return -1; } } }