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