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();
-----------------------------------------------------------------