CefSharp禁止弹出新窗体,在同一窗口打开链接,或者在新Tab页打开链接,并且支持带type="POST" target="_blank"的链接

 说明:在同一窗口打开链接,只要稍加改造就可以实现,这里实现的是在新Tab页打开链接,并且支持带type="POST" target="_blank"的链接

 

github和bitbucket上相关问题:

1、WPF empty POST data when using custom popup    https://github.com/cefsharp/CefSharp/issues/1267

2、CefLifeSpanHandler, customized OnBeforePopup problem    https://bitbucket.org/chromiumembedded/cef/issues/1949/

 

解决(CefSharp版本75.1.143.0):

一、实现IRequestHandler接口

using CefSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography.X509Certificates;

namespace CefSharpDemo
{
    public class RequestHandler : IRequestHandler
    {
        private ExtChromiumBrowser _browser;

        public RequestHandler(ExtChromiumBrowser browser)
        {
            _browser = browser;
        }

        public bool GetAuthCredentials(IWebBrowser chromiumWebBrowser, IBrowser browser, string originUrl, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback)
        {
            return false;
        }

        public IResourceRequestHandler GetResourceRequestHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling)
        {
            if (request.Method.ToUpper() == "POST" && request.PostData != null)
            {
                if (request.PostData.Elements.Count > 0)
                {
                    _browser.PostData = new byte[request.PostData.Elements[0].Bytes.Length];
                    request.PostData.Elements[0].Bytes.CopyTo(_browser.PostData, 0);
                }
            }
            return null;
        }

        public bool OnBeforeBrowse(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect)
        {
            return false;
        }

        public bool OnCertificateError(IWebBrowser chromiumWebBrowser, IBrowser browser, CefErrorCode errorCode, string requestUrl, ISslInfo sslInfo, IRequestCallback callback)
        {
            return false;
        }

        public bool OnOpenUrlFromTab(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture)
        {
            return false;
        }

        public void OnPluginCrashed(IWebBrowser chromiumWebBrowser, IBrowser browser, string pluginPath)
        {

        }

        public bool OnQuotaRequest(IWebBrowser chromiumWebBrowser, IBrowser browser, string originUrl, long newSize, IRequestCallback callback)
        {
            return false;
        }

        public void OnRenderProcessTerminated(IWebBrowser chromiumWebBrowser, IBrowser browser, CefTerminationStatus status)
        {

        }

        public void OnRenderViewReady(IWebBrowser chromiumWebBrowser, IBrowser browser)
        {

        }

        public bool OnSelectClientCertificate(IWebBrowser chromiumWebBrowser, IBrowser browser, bool isProxy, string host, int port, X509Certificate2Collection certificates, ISelectClientCertificateCallback callback)
        {
            return false;
        }
    }
}
View Code

二、实现ILifeSpanHandler接口

using CefSharp;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Interop;
using Utils;

namespace CefSharpDemo
{
    public class CefLifeSpanHandler : CefSharp.ILifeSpanHandler
    {
        private static LimitedTaskScheduler _scheduler = new LimitedTaskScheduler(2);

        public CefLifeSpanHandler()
        {

        }

        public bool DoClose(IWebBrowser browserControl, CefSharp.IBrowser browser)
        {
            if (browser.IsDisposed || browser.IsPopup)
            {
                return false;
            }

            return true;
        }

        public void OnAfterCreated(IWebBrowser browserControl, IBrowser browser)
        {

        }

        public void OnBeforeClose(IWebBrowser browserControl, IBrowser browser)
        {
        }

        public bool OnBeforePopup(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser)
        {
            var chromiumWebBrowser = (ExtChromiumBrowser)browserControl;

            chromiumWebBrowser.Dispatcher.Invoke(new Action(() =>
            {
                BrowserPopupWin win = new BrowserPopupWin();
                win.ShowInTaskbar = false;
                win.Height = 0;
                win.Width = 0;
                win.Show();

                IntPtr handle = new WindowInteropHelper(win).Handle;
                windowInfo.SetAsChild(handle);

                _scheduler.Run(() =>
                {
                    WaitUtil.Wait(() => chromiumWebBrowser.PostData);

                    IRequest request = null;
                    if (chromiumWebBrowser.PostData != null)
                    {
                        request = frame.CreateRequest();
                        request.Url = targetUrl;
                        request.Method = "POST";

                        request.InitializePostData();
                        var element = request.PostData.CreatePostDataElement();
                        element.Bytes = chromiumWebBrowser.PostData;
                        request.PostData.AddElement(element);
                        chromiumWebBrowser.PostData = null;
                    }

                    chromiumWebBrowser.Dispatcher.Invoke(new Action(() =>
                    {
                        NewWindowEventArgs e = new NewWindowEventArgs(targetUrl, request);
                        chromiumWebBrowser.OnNewWindow(e);
                    }));

                    chromiumWebBrowser.Dispatcher.Invoke(new Action(() =>
                    {
                        win.Close();
                    }));
                });
            }));

            newBrowser = null;
            return false;
        }

    }
}
View Code

三、扩展ChromiumWebBrowser

using CefSharp.Wpf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CefSharpDemo
{
    public class ExtChromiumBrowser : ChromiumWebBrowser
    {
        public byte[] PostData { get; set; }

        public ExtChromiumBrowser()
            : base()
        {
            this.LifeSpanHandler = new CefLifeSpanHandler();
            this.DownloadHandler = new DownloadHandler(this);
            this.MenuHandler = new MenuHandler();
            this.KeyboardHandler = new KeyboardHandler();
            this.RequestHandler = new RequestHandler(this);
        }

        public event EventHandler<NewWindowEventArgs> StartNewWindow;

        public void OnNewWindow(NewWindowEventArgs e)
        {
            if (StartNewWindow != null)
            {
                StartNewWindow(this, e);
            }
        }

        public void ClearHandlers()
        {
            //如果不清理Handler,会导致子进程CefSharp.BrowserSubprocess.exe无法释放
            this.LifeSpanHandler = null;
            this.DownloadHandler = null;
            this.MenuHandler = null;
            this.KeyboardHandler = null;
        }
    }
}
View Code

四、封装ExtChromiumBrowser(BrowserCtrl控件)

using CefSharp;
using CefSharp.Wpf;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Utils;

namespace CefSharpDemo
{
    /// <summary>
    /// 浏览器用户控件
    /// </summary>
    public partial class BrowserCtrl : UserControl, IDisposable
    {
        #region 外部方法
        /*
        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, string windowTitle);
        [DllImport("user32.dll", SetLastError = true)]
        public static extern int MoveWindow(IntPtr hWnd, int x, int y, int nWidth, int nHeight, bool BRePaint);
        [DllImport("user32.dll", SetLastError = true)]
        public static extern int CloseWindow(IntPtr hWnd);
        [DllImport("User32.dll", EntryPoint = "GetWindowText")]
        private static extern int GetWindowText(IntPtr hwnd, StringBuilder lpString, int nMaxCount);
        */
        #endregion

        #region 变量属性事件
        private static bool _isCefInited = false;

        private static object _lockObject = new object();

        private JSObject _jsObject;

        private bool _firstLoad = true;

        /// <summary>
        /// 在此事件中设置URL(此事件已在线程中执行,此事件已对错误情况进行处理)
        /// </summary>
        public event EventHandler SetUrlEvent;

        /// <summary>
        /// URL
        /// </summary>
        public string Url { get; set; }

        public IRequest Request { get; set; }

        /// <summary>
        /// 浏览器FrameLoadEnd事件
        /// </summary>
        public event EventHandler FrameLoadEnd;

        private ExtChromiumBrowser _browser;

        public ExtChromiumBrowser Browser
        {
            get
            {
                WaitUtil.Wait(() => this._browser != null && this._browser.IsInitialized && _isCefInited);

                return this._browser;
            }
        }

        private static LimitedTaskScheduler _scheduler = new LimitedTaskScheduler(2);
        #endregion

        #region 构造函数
        public BrowserCtrl()
        {
            InitializeComponent();
            if (DesignerProperties.GetIsInDesignMode(this)) return;

            this.Loaded += BrowserCtrl_Loaded;

            lock (_lockObject)
            {
                if (!_isCefInited)
                {
                    _isCefInited = true;
                    InitCef();//初始化CefSharp
                }
            }

            _browser = new ExtChromiumBrowser();
            BindBrowser(_browser);
            grid.Children.Add(_browser);
        }
        #endregion

        #region BrowserCtrl_Loaded
        private void BrowserCtrl_Loaded(object sender, RoutedEventArgs e)
        {

        }
        #endregion

        #region SetMapCtrl
        /// <summary>
        /// 设置Map控件接口,用于C#和JS互操作
        /// </summary>
        public void SetMapCtrl(IMapCtrl mapCtrl)
        {
            _jsObject.MapCtrl = mapCtrl;
        }
        #endregion

        #region Dispose 释放资源
        /// <summary>
        /// 释放资源
        /// </summary>
        public void Dispose()
        {
            //如果有弹出窗口则先释放它
            //foreach (UIElement item in grid.Children)
            //{
            //    if (item is BrowserContainer)
            //    {
            //        (item as BrowserContainer).ClearResource();
            //    }
            //}

            _browser.ClearHandlers();
            if (_browser != null && !_browser.IsDisposed)
            {
                _browser.Dispose();
            }
        }
        #endregion

        #region Load
        public void Load(string url)
        {
            if (!string.IsNullOrWhiteSpace(url))
            {
                loadingWait.Visibility = Visibility.Visible;
                Url = url;
                _scheduler.Run(() =>
                {
                    #region Wait
                    WaitUtil.Wait(() =>
                    {
                        if (this._browser == null) return false;
                        if (!this._browser.IsInitialized) return false;
                        if (!_isCefInited) return false;
                        bool isBrowserInitialized = false;
                        this.Dispatcher.Invoke(() =>
                        {
                            isBrowserInitialized = this._browser.IsBrowserInitialized;
                        });
                        if (!isBrowserInitialized) return false;
                        return true;
                    });
                    #endregion

                    _browser.Load(Url);
                });
            }
        }
        #endregion

        #region LoadUrl
        private void LoadUrl()
        {
            if (_firstLoad)
            {
                _firstLoad = false;

                _scheduler.Run(() =>
                {
                    #region Wait
                    WaitUtil.Wait(() =>
                    {
                        if (this._browser == null) return false;
                        if (!this._browser.IsInitialized) return false;
                        if (!_isCefInited) return false;
                        bool isBrowserInitialized = false;
                        this.Dispatcher.Invoke(() =>
                        {
                            isBrowserInitialized = this._browser.IsBrowserInitialized;
                        });
                        if (!isBrowserInitialized) return false;
                        return true;
                    });
                    #endregion

                    if (Url == null && SetUrlEvent != null)
                    {
                        try
                        {
                            SetUrlEvent(this, null);
                        }
                        catch (Exception ex)
                        {
                            LogUtil.Error(ex, "BrowserCtrl LoadUrl error 获取URL失败");
                        }
                    }
                    else
                    {
                        this.Dispatcher.Invoke(new Action(() =>
                        {
                            loadingWait.Visibility = Visibility.Collapsed;
                        }));
                    }

                    if (Url != null)
                    {
                        try
                        {
                            if (Request == null)
                            {
                                _browser.Load(Url);
                            }
                            else
                            {
                                _browser.Load(Url);
                                _browser.GetMainFrame().LoadRequest(Request);
                                Request = null;
                            }
                        }
                        catch (Exception ex)
                        {
                            LogUtil.Error(ex, "BrowserCtrl LoadUrl error Load URL失败");
                        }
                    }
                    else
                    {
                        this.Dispatcher.Invoke(new Action(() =>
                        {
                            loadingWait.Visibility = Visibility.Collapsed;
                        }));
                    }
                });
            }
        }
        #endregion

        #region BindBrowser
        private void BindBrowser(ExtChromiumBrowser browser)
        {
            _jsObject = new JSObject();
            browser.RegisterJsObject("jsObj", _jsObject, new CefSharp.BindingOptions { CamelCaseJavascriptNames = false });

            browser.IsBrowserInitializedChanged += (ss, ee) =>
            {
                LoadUrl();
            };
            browser.FrameLoadStart += (ss, ee) =>
            {
                this.Dispatcher.BeginInvoke(new Action(() =>
                {
                    (ss as ExtChromiumBrowser).Focus();
                }));
            };
            browser.FrameLoadEnd += (ss, ee) =>
            {
                this.Dispatcher.BeginInvoke(new Action(() =>
                {
                    loadingWait.Visibility = Visibility.Collapsed;
                }));
                if (FrameLoadEnd != null)
                {
                    FrameLoadEnd(null, null);
                }
            };
            browser.KeyDown += (ss, ee) =>
            {
                if (ee.Key == Key.F5)
                {
                    try
                    {
                        browser.Reload();
                    }
                    catch (Exception ex)
                    {
                        LogUtil.Error(ex, "ExtChromiumBrowser Reload error");
                    }
                }
            };
            browser.PreviewTextInput += (o, e) =>
            {
                foreach (var character in e.Text)
                {
                    // 把每个字符向浏览器组件发送一遍
                    browser.GetBrowser().GetHost().SendKeyEvent((int)WM.CHAR, (int)character, 0);
                }

                // 不让cef自己处理
                e.Handled = true;
            };
            browser.LoadError += (s, e) =>
            {
                this.Dispatcher.BeginInvoke(new Action(() =>
                {
                    loadingWait.Visibility = Visibility.Collapsed;
                }));
            };
        }
        #endregion

        #region RegisterJsObject
        public void RegisterJsObject(string name, object objectToBind, BindingOptions options = null)
        {
            try
            {
                if (_browser != null)
                {
                    _browser.RegisterJsObject(name, objectToBind, options);
                }
            }
            catch (Exception ex)
            {
                LogUtil.Error(ex, "BrowserCtrl RegisterJsObject 错误");
            }
        }
        #endregion

        #region 初始化CefSharp
        public static void InitCef()
        {
            string cefsharpFolder = "CefSharp";

            var settings = new CefSettings();
            //The location where cache data will be stored on disk. If empty an in-memory cache will be used for some features and a temporary disk cache for others.
            //HTML5 databases such as localStorage will only persist across sessions if a cache path is specified. 
            settings.CachePath = cefsharpFolder + "/cache"; //设置cache目录

            settings.MultiThreadedMessageLoop = true;
            CefSharpSettings.FocusedNodeChangedEnabled = true;
            CefSharpSettings.LegacyJavascriptBindingEnabled = true;
            CefSharpSettings.ShutdownOnExit = true;
            CefSharpSettings.SubprocessExitIfParentProcessClosed = true;

            string logDir = AppDomain.CurrentDomain.BaseDirectory + cefsharpFolder + "/log/";
            if (!Directory.Exists(logDir))
            {
                Directory.CreateDirectory(logDir);
            }

            settings.BrowserSubprocessPath = AppDomain.CurrentDomain.BaseDirectory + cefsharpFolder + "/CefSharp.BrowserSubprocess.exe";
            settings.LogFile = logDir + DateTime.Now.ToString("yyyyMMdd") + ".log";
            settings.LocalesDirPath = AppDomain.CurrentDomain.BaseDirectory + cefsharpFolder + "/locales";
            settings.CefCommandLineArgs.Add("disable-gpu", "1");
            settings.CefCommandLineArgs.Add("enable-media-stream", "1");

            if (!Cef.Initialize(settings, performDependencyCheck: true, browserProcessHandler: new BrowserProcessHandler()))
            {
                throw new Exception("Unable to Initialize Cef");
            }
        }
        #endregion

    }
}
View Code

五、MainWindow测试代码

using CefSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Utils;

namespace CefSharpDemo
{
    /// <summary>
    /// CefSharp Demo 窗体
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            tabControl.AddTabItemEvent += tabControl_AddTabItemEvent;
            Application.Current.MainWindow = this;
        }

        private void tabControl_AddTabItemEvent(object sender, EventArgs e)
        {
            //CreateTabItem("https://www.cnblogs.com/");
            CreateTabItem("file:///D:/_程序/CefSharpDemo/post.html");
        }

        /// <summary>
        /// 新增Tab页
        /// </summary>
        private void CreateTabItem(string url = null, IRequest request = null)
        {
            TabItem tabItem = new TabItem();
            tabItem.Header = "新标签页";
            BrowserDemoCtrl ctrl = new BrowserDemoCtrl();
            ctrl.browserCtrl.Browser.StartNewWindow += (s, e) =>
            {
                CreateTabItem(e.TargetUrl, e.Request);
            };
            ctrl.browserCtrl.SetUrlEvent += (s, e) =>
            {
                ctrl.browserCtrl.Url = url;
                ctrl.browserCtrl.Request = request;
            };
            tabItem.Content = ctrl;
            tabControl.Items.Add(tabItem);
            tabControl.SelectedItem = tabItem;
            ScrollViewer scrollViewer = tabControl.Template.FindName("scrollViewer", tabControl) as ScrollViewer;
            scrollViewer.ScrollToRightEnd();
        }

        private void Window_Closed(object sender, EventArgs e)
        {
            tabControl.CloseAllTabItem(); //关闭窗体清理资源

            //程序退出时删除cache
            CefSharp.Cef.Shutdown();
            string cachePath = AppDomain.CurrentDomain.BaseDirectory + "CefSharp\\cache";
            if (Directory.Exists(cachePath))
            {
                foreach (string path in Directory.GetDirectories(cachePath))
                {
                    Directory.Delete(path, true);
                }
                foreach (string file in Directory.GetFiles(cachePath))
                {
                    if (!file.ToLower().Contains("cookies"))
                    {
                        File.Delete(file);
                    }
                }
            }
        }
    }
}
View Code

 六、测试html代码post.html

<!DOCTYPE html>
<html>
<head>
    <title>CefSharpDemo</title>

    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <style type="text/css">
    </style>

    <script type="text/javascript">
    </script>
</head>
<body>
    <!--enctype="multipart/form-data"-->
    <form method="post" action="http://localhost:1209/netcms/" target="_blank">
        <span>name:</span><input type="text" name="name" value="测试名称" />
        <span>code:</span><input type="text" name="code" value="测试编码" />

        <button type="submit">Post提交</button>
    </form>
</body>
</html>
View Code

七、测试后台代码

public ActionResult index()
{
    string name = Request.Params["name"];
    string code = Request.Params["code"];

    ViewBag.name = name;
    ViewBag.code = code;

    return View();
}
View Code

八、测试前台cshtml代码

@using Models;
@{
    Layout = "~/Views/Shared/_SiteLayout.cshtml";
}

<div style="font-size:50px; height:1200px;">
    <span>name:</span><span>@ViewBag.name</span><br /><span>code:</span><span>@ViewBag.code</span>
</div>
View Code

 

九:关键代码段:

1、RequestHandler类中获取并保存PostData

public IResourceRequestHandler GetResourceRequestHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling)
{
    if (request.Method.ToUpper() == "POST" && request.PostData != null)
    {
        if (request.PostData.Elements.Count > 0)
        {
            _browser.PostData = new byte[request.PostData.Elements[0].Bytes.Length];
            request.PostData.Elements[0].Bytes.CopyTo(_browser.PostData, 0);
        }
    }
    return null;
}
View Code

 2、CefLifeSpanHandler类中创建IRequest

public bool OnBeforePopup(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser)
{
    var chromiumWebBrowser = (ExtChromiumBrowser)browserControl;

    chromiumWebBrowser.Dispatcher.Invoke(new Action(() =>
    {
        BrowserPopupWin win = new BrowserPopupWin();
        win.ShowInTaskbar = false;
        win.Height = 0;
        win.Width = 0;
        win.Show();

        IntPtr handle = new WindowInteropHelper(win).Handle;
        windowInfo.SetAsChild(handle);

        _scheduler.Run(() =>
        {
            WaitUtil.Wait(() => chromiumWebBrowser.PostData);

            IRequest request = null;
            if (chromiumWebBrowser.PostData != null)
            {
                request = frame.CreateRequest();
                request.Url = targetUrl;
                request.Method = "POST";

                request.InitializePostData();
                var element = request.PostData.CreatePostDataElement();
                element.Bytes = chromiumWebBrowser.PostData;
                request.PostData.AddElement(element);
                chromiumWebBrowser.PostData = null;
            }

            chromiumWebBrowser.Dispatcher.Invoke(new Action(() =>
            {
                NewWindowEventArgs e = new NewWindowEventArgs(targetUrl, request);
                chromiumWebBrowser.OnNewWindow(e);
            }));

            chromiumWebBrowser.Dispatcher.Invoke(new Action(() =>
            {
                win.Close();
            }));
        });
    }));

    newBrowser = null;
    return false;
}
View Code

说明:OnBeforePopup方法要return false,用BrowserPopupWin和windowInfo.SetAsChild方法弹出一个不可见的窗体,这样才能拿到PostData

3、在BrowserCtrl控件中用LoadRequest方法打开新的URL,并把post数据带过去

if (Request == null)
{
    _browser.Load(Url);
}
else
{
    _browser.Load(Url);
    _browser.GetMainFrame().LoadRequest(Request);
    Request = null;
}
View Code

 

十、效果图:

效果图

 

完整代码下载:https://files-cdn.cnblogs.com/files/s0611163/CefSharpDemo.zip

源码说明:为了减少源码压缩包的大小,代码中没有依赖的CefSharp文件,请自己下载(使用x86版本),用于测试的网页后台代码也没有,请自己制作测试后台

 

posted @ 2019-12-13 12:13  0611163  阅读(4283)  评论(2编辑  收藏  举报