Cef框架的使用:内嵌Chromium内核浏览器

Chromium Embedded Framework (CEF)是个基于Google Chromium项目的开源Web browser控件,支持Windows, Linux, Mac平台。除了提供C/C++接口外,也有其他语言的移植版。
因为基于Chromium,所以CEF支持Webkit & Chrome中实现的HTML5的特性,并且在性能上面,也比较接近Chrome。

1.从Nuget下载CEF框架

从nuget下载是最简单的:

 

 我的示例程序是WPF程序,所以下载的框架是CefSharp.Wpf,你也可以下载Cef的其他版本如CefSharp.WinForms

2.创建一个Wpf窗口:

<Window x:Class="ChromeWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"   
        xmlns:cefSharpWPF="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf"
        mc:Ignorable="d"
        Title="ChromeWindow" Height="450" Width="800" Closing="Window_Closing">
    <DockPanel Name="dockpanel"  LastChildFill="True">
        <cefSharpWPF:ChromiumWebBrowser Name="mybrower" Address="https://www.cnblogs.com/tuyile006"></cefSharpWPF:ChromiumWebBrowser>
    </DockPanel>
</Window>

 

打开看看效果。这是超简单的使用方式,我就不展示了。

3.让cef  chromium支持flash视频播放。

很多网页中有flash,内嵌的chromium浏览器是不支持flash的,必须设置一下。

需要下载一个插件,放到项目中,并随项目发布到输出目录。

 

 

 主要看构造函数中的代码:

using CefSharp;
using CefSharp.Wpf;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Windows;
namespace xiaoy.Control
{
    /// <summary>
    /// 谷歌浏览器帮助类(静态类)  
    /// </summary>
public class ChromeCefHelper
    {
        private static List<ChromeWindow> webList= new List<ChromeWindow>();
        private static object lockObj = new object();

        public static string ErrhtmlTemplate = string.Empty;
static ChromeCefHelper()
        {
            //初始化浏览器设置
            if (!Cef.IsInitialized)
            {
                //打开静态地址
                string strMenu = AppDomain.CurrentDomain.BaseDirectory;
                //pepflashplayerDLL 地址
                string flashPath = strMenu + @"\flashPlugin\pepflashplayer64_32_0_0_414.dll";
                CefSettings set = new CefSettings();
                set.CachePath = strMenu + "\\cache";
                set.PersistSessionCookies = true;
                
                set.LogSeverity = LogSeverity.Disable;
                //安全证书
                set.CefCommandLineArgs.Add("--ignore-urlfetcher-cert-requests", "1");
                set.CefCommandLineArgs.Add("--ignore-certificate-errors", "1");
               //开启ppapi-flash
                set.CefCommandLineArgs["enable-system-flash"] = "1";
                set.CefCommandLineArgs.Add("ppapi-flash-version", "32.0.0.414");                
                //插入地址
                set.CefCommandLineArgs.Add("ppapi-flash-path", flashPath);

                //访问本地资源
                set.RegisterScheme(new CefCustomScheme
                {
                    SchemeName = CefSharpSchemeHandlerFactory.SchemeName,
                    SchemeHandlerFactory = new CefSharpSchemeHandlerFactory()
                });
               //启用配置
                //bool bint= CefSharp.Cef.Initialize(set);
                Cef.Initialize(set, performDependencyCheck: false, browserProcessHandler: null);

                //错误网页模板
                string htmlpath = AppDomain.CurrentDomain.BaseDirectory + "html\\error.html";
                ErrhtmlTemplate = File.ReadAllText(htmlpath);

            }
        }

/// <summary>
        /// 自动设置token到cookie中
        /// </summary>
        public static void SetCookies()
        {
           
            if (!string.IsNullOrEmpty(CommCach.Token))
            {
                lock(lockObj)
                {
                    try
                    {
                        //获取portalurl中的域名地址
                        int starti = CommCach.PortalUrl.IndexOf("//") + 2;
                        int endi = CommCach.PortalUrl.IndexOf("/", starti);
                        string domain = CommCach.PortalUrl.Substring(starti, endi - starti);

                        var cookieManager = CefSharp.Cef.GetGlobalCookieManager();
                        cookieManager.SetCookieAsync("http://" + domain, new CefSharp.Cookie()
                        {
                            Domain = domain,
                            Name = "Admin-Token",
                            Value = HttpUtility.UrlEncode(CommCach.Token),
                            Expires = DateTime.MinValue
                        });
                    }
                    catch(Exception ex)
                    {
                        LogHelper.Error("写Cookie失败",ex);
                    }
                }
                
            }
     }

      /// <summary>
        /// 第一次启动  
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public static ChromeWindow Open(string title,string url)
        {
            SetCookies();
            ChromeWindow cw = new ChromeWindow();
            cw.Title = title;
            cw.Open(url);
            cw.Show();
            //cw.WindowStyle = WindowStyle.None;
            webList.Add(cw); //放在缓存中  以便退出关闭浏览器
            return cw;
        }

        /// <summary>
        /// 在原chrome中改url
        /// </summary>
        /// <param name="webDriver"></param>
        /// <param name="url"></param>
        public static void Open(ChromeWindow webDriver, string title, string url)
        {
            if (webDriver != null)
            {
                SetCookies();
                webDriver.Title = title;
                webDriver.Open(url);
            }
        }

        /// <summary>
        /// 关闭所有窗口
        /// </summary>
        public static void CloseAll()
        {
            foreach (ChromeWindow w in webList)
            {
                if (w != null)
                    w.Close();               
            }
            webList.Clear();
        }
   }
}
ChromeWindow.xaml是要弹的浏览器窗体。在ChromiumWebBrowser对象初始化完之后需要支持直接打开flash播放,不能让用户点播放按钮才播放,这是用户使用习惯。实现如下:
前端窗体可以只放个DockPanel控件,后台代码动态添加浏览器控件
<Window x:Class="xiaoy.ChromeWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"   
        
        mc:Ignorable="d"
        Title="ChromeWindow" Height="450" Width="800" Closing="Window_Closing">
    <DockPanel Name="dockpanel"  LastChildFill="True">
        <!--<cefSharpWPF:ChromiumWebBrowser Name="mybrower" Address="https://www.cnblogs.com/tuyile006"></cefSharpWPF:ChromiumWebBrowser>-->
    </DockPanel>
</Window>
 然后在Browser_IsBrowserInitializedChanged中自动播放flash
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using CefSharp;
using CefSharp.Wpf;
namespace xiaoy.Control
{
    /// <summary>
    /// ChromeWindow.xaml 的交互逻辑
    /// </summary>
    public partial class ChromeWindow : Window
    {
        ChromiumWebBrowser browser = null;
        bool bInited = false;//是否初始化完成
        public ChromeWindow()
        {
            InitializeComponent();
            //初始化浏览器
            InitBrowser();

            //Open(url);
        }

       /// <summary>
        /// 初始化浏览器,让其支持flash
        /// </summary>
        /// <param name="url"></param>
        public void InitBrowser()
        {
            browser = new ChromiumWebBrowser();
           // browser.RequestHandler=new CustomRequestHandler();
            browser.KeyboardHandler = new CustomKeyBoardHander();

            BrowserSettings bset = new BrowserSettings();
            bset.Plugins = CefState.Enabled;
            bset.ApplicationCache = CefState.Enabled;
            //关于跨域限制
            //bset.WebSecurity = CefState.Disabled;
            browser.BrowserSettings = bset;
            browser.IsBrowserInitializedChanged += Browser_IsBrowserInitializedChanged;
            //打开网页
            //browser.Load(strMenu + htmlDidr);
            //绑定JS
            //browser.RegisterJsObject("callbackObj", new CallbackObjectForJs());
            

            // browser.FrameLoadEnd += Browser_FrameLoadEnd;
            this.dockpanel.Children.Add(browser);
        }


       private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e)
        {
            var visitor = new CookieVisitor(all_cookies => {
                var sb = new StringBuilder();
                foreach (var nameValue in all_cookies)
                    sb.AppendLine(nameValue.Item1 + " = " + nameValue.Item2);

                //MessageBox.Show("读取到Cookie值:" + sb.ToString());
                //LogHelper.Info("读取到Cookie值:" + sb.ToString());

            });
            Cef.GetGlobalCookieManager().VisitAllCookies(visitor);
        }

private void Browser_IsBrowserInitializedChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            try
            {
                if ((bool)e.NewValue == true)
                {
                    bInited = true;
                }

                if (browser.IsBrowserInitialized)
                {
                    //自动播放flash
                    Cef.UIThreadTaskFactory.StartNew(() =>
                    {
                        string error = "";
                        var requestContext = browser.GetBrowser().GetHost().RequestContext;
                        requestContext.SetPreference("profile.default_content_setting_values.plugins", 1, out error);
                    });

                }
            }
            catch(Exception ex)
            {
                LogHelper.Error(ex);
            }
            
        }

       /// <summary>
        /// 打开指定网址
        /// </summary>
        /// <param name="url">网址</param>
        public void Open(string url)
        {
            //打开网页
            if (browser != null && browser.IsBrowserInitialized)
                browser.Load(url);
            else
                browser.Address = url;
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (browser != null)
            {
                browser.Dispose();
                browser = null;
            }
        }
    }
}

4.用cef给Chromium浏览器写Cookie

有时候需要实现免登录打开网页,即你之前已经登录过网站,打开该网站内其他网页不需要再登录。这就需要根据该网页的验证方式进行相应适配了,比如写cookie,以下就是写cookie的实现:
 /// <summary>
        /// 自动设置token到cookie中
        /// </summary>
        public static void SetCookies()
        {
            if (!string.IsNullOrEmpty(CommCach.Token))
            {
                lock(lockObj)
                {
                   try
                    {
                        //获取portalurl中的域名地址
                        int starti = CommCach.PortalUrl.IndexOf("//") + 2;
                        int endi = CommCach.PortalUrl.IndexOf("/", starti);
                        string domain = CommCach.PortalUrl.Substring(starti, endi - starti);

                        var cookieManager = CefSharp.Cef.GetGlobalCookieManager();
                        cookieManager.SetCookieAsync("http://" + domain, new CefSharp.Cookie()
                        {
                            Domain = domain,
                            Name = "Admin-Token",
                            Value = HttpUtility.UrlEncode(CommCach.Token),
                            Expires = DateTime.MinValue
                        });
                    }
                    catch(Exception ex)
                    {
                        LogHelper.Error("写Cookie失败",ex);
                    }
                }
       }
}

以上是我测试的代码,请根据你自己的实际要求,修改Cookie名称和域名等。  注意SetCookieAsync的url要是http://开头。domain不要http

5.用Cef在http请求头中加字段

这个也是一个常见需求。以下代码实现在http头中增加access-token字段。

增加一个ResourceRequestHandler的自定义类:

using CefSharp;
using CefSharp.Handler;
namespace xiaoy.Control
{
    public class CustomResourceRequestHandler :  ResourceRequestHandler
    {
      protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, 
           IRequest request, IRequestCallback callback)
        {
            var headers = request.Headers;

            //自动增加access-token
            if (!string.IsNullOrEmpty(CommCach.Token))
            {
                bool bExist = false;

                foreach (string k in headers.Keys)
                {
                    if (string.Compare(k, "access-token", true) == 0)
                    {
                        bExist = true;
                        break;
                    }
                }
               
                if (bExist)
                    headers["access-token"] = CommCach.Token;
                else
                    headers.Add("access-token", CommCach.Token);
            }
           request.Headers = headers;

            return CefReturnValue.Continue;
        }
    }
 public class CustomRequestHandler : RequestHandler
    {
        protected override IResourceRequestHandler GetResourceRequestHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, 
IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling)
        {
            return new CustomResourceRequestHandler();
        }
    }
}

然后在浏览器对象创建后,增加handler

 browser = new ChromiumWebBrowser();
 browser.RequestHandler=new CustomRequestHandler();

 

6.用Cef打开网页访问本地图片等资源

如果要打开本地网页,cef支持loadhtml方法,但是浏览器权限无法打开本地资源,必须做特殊处理才可以。

官网的方案如下:https://github.com/cefsharp/CefSharp/wiki/General-Usage#initialize-and-shutdown
这是一个歪果仁在37.0老版本上实现的:https://thechriskent.com/tag/cefcustomscheme/
首先实现一下ISchemeHandlerFactory接口,自定义资源解析:
using CefSharp;
using System;
using System.IO;
using System.Reflection;
namespace xiaoy.Control
{
    public class CefSharpSchemeHandlerFactory : ISchemeHandlerFactory
    {
         public const string SchemeName = "custom";

         public IResourceHandler Create(IBrowser browser, IFrame frame, string schemeName, IRequest request)
         {
           var fileName = request.Url.Replace(SchemeName+"://", "");

            Assembly ass = Assembly.GetExecutingAssembly();
            String resourcePath = ass.GetName().Name + "." + fileName.Replace("/", ".");
            Stream resource= ass.GetManifestResourceStream(resourcePath); 

            if (resource!=null)
            {
                var fileExtension = Path.GetExtension(fileName);
                return ResourceHandler.FromStream(resource,  mimeType:Cef.GetMimeType(fileExtension));
            }

            return null;
        }

    }
}

然后在初始化cef的时候(上文ChromeCefHelper中)添加

                //访问本地资源
                set.RegisterScheme(new CefCustomScheme
                {
                    SchemeName = CefSharpSchemeHandlerFactory.SchemeName,
                    SchemeHandlerFactory = new CefSharpSchemeHandlerFactory()
                });
                

                //启用配置
                //bool bint= CefSharp.Cef.Initialize(set);
                Cef.Initialize(set, performDependencyCheck: false, browserProcessHandler: null);

因为你定义的schemename是“custom”,所以在你本地要打开的html中,资源地址要用“custom://”开头。

<div class="content-container">
    <div class="head-line">
        <img src="custom://html/error.png" alt="" width="120"/>
    </div>
    <div class="subheader">
        {errorTitle}</div>
    <div class="hr"></div>
    <div class="context">
        <p>
            {errorContent}
        </p>
    </div>
</div>

然后可以直接调用 browser.LoadHtml方法显示本地网页了。本地网页模板建议先读取到内存中。

 /// <summary>
        /// 显示错误信息
        /// </summary>
        /// <param name="errTitle"></param>
        /// <param name="errContent"></param>
        public void ShowError(string errTitle,string errContent)
        {
Task.Factory.StartNew(() => {
                //网页中可替换的变量: {imagePath}   {errorTitle}   {errorContent}
                //string imgpath = AppDomain.CurrentDomain.BaseDirectory + "html\\error.png";
                //imgpath= imgpath.Replace("\\", "/");
                string imgpath = CefSharpSchemeHandlerFactory.SchemeName+"://html/error.png";
                //
                string html = ChromeCefHelper.ErrhtmlTemplate.Replace("{imagePath}", imgpath).Replace("{errorTitle}", errTitle).Replace("{errorContent}", errContent);
                //string html = string.Format(ChromeCefHelper.ErrhtmlTemplate, errTitle, errContent);
                while (bInited == false)
                    Thread.Sleep(50);
                browser.LoadHtml(html, CefSharpSchemeHandlerFactory.SchemeName+"://error.html");
            });
        }

 7.Cef中让浏览器支持F5/F12等快捷键

内嵌的chromium浏览器是默认不支持快捷键的,需要添加keyboardhandler支持。

using CefSharp;
using System;
using System.Windows.Forms;
namespace xiaoy.Control
{
   public class CustomKeyBoardHander : IKeyboardHandler
    {

     public bool OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey)
        {
            if (type == KeyType.KeyUp && Enum.IsDefined(typeof(System.Windows.Forms.Keys), windowsKeyCode))
            {
                var key = (Keys)windowsKeyCode;
                switch (key)
                {
                    case Keys.F12:
                        browser.ShowDevTools();
                        break;
                    case Keys.F5:

                        if (modifiers == CefEventFlags.ControlDown)
                        {
                            //MessageBox.Show("ctrl+f5");
                            browser.Reload(true); //强制忽略缓存

                        }
                        else
                        {
                            //MessageBox.Show("f5");
                            browser.Reload();
                        }
                        break;
                }
            }
            return false;
        }

       public bool OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode,
    int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut)
        {
            return false;
        }
    }
}

然后在浏览器对象创建后增加handler

browser = new ChromiumWebBrowser();
// browser.RequestHandler=new CustomRequestHandler();
browser.KeyboardHandler = new CustomKeyBoardHander();

 

posted @ 2020-10-21 16:02  小y  阅读(1191)  评论(0编辑  收藏