第一次在博客園寫文章,想跟大家分享一下一些關於Chromium的.net封裝版本
從學校畢業后到現在也有一年半了,主要做.net方面的,winform和asp.net,MVC。期間維護過一個項目,用Winform的WebBrowser展現web網站項目,給用戶更好的用戶體驗。后來聽說了html5,很想試試,但由於WebBrowser是采用IE內核,么法子。找了一堆和瀏覽器相關的資料,當中也學到了不少。比如說Chrome Frame 並不能和WebBrowser相結合,FireFox的Gecko的binary感覺好冗余,也沒有繼續下去。后來看到了采用V8引擎的Chromium,執行javascript的速度快,而且binary也很精簡,代碼看着清爽,蠻喜歡的。於是采用了。
經過資料和第三方代碼的整合之后,搭了這個項目

1. CefGlue項目是對libcef.dll的封裝(libcef.dll是Chromium的核心,可以從開源項目Chromium的SVN下來,編譯下來,我編譯的version是1045)
2. Cef.Demo項目是引用CefGlue項目,主要調用CefGlue對libcef.dll封裝的API,並在此基礎上封裝了一個WebBrowser基類
3. Cef.Demo.Common是幫助類庫
4. Cef.Demo.WinForms(Winform主程序)
下面是Cef.Demo項目

其中類DemoCefApp是Cef3應用的核心,他主管着兩類進程,一類進程是Browser進程,這類進程有着一系列的事件可供重寫,
其中有V8引擎上下文初始化完成事件,代碼如下
//V8引擎初始化完成,可重寫
protected virtual void OnContextInitialized()
{
}
還有當Render進程(下面會講到Render進程)創建時的事件,代碼如下
//當一個新的渲染進程創建時 可重寫
protected virtual void OnRenderProcessThreadCreated(CefListValue extraInfo)
{
}
另一類進程就是Render進程,同樣他也有很多事件可供重寫,當你打開chrome瀏覽網頁,你會發現任務管理面有時會有很多chrome的進程,多出的那些進程就是Render進程。每當你打開一個新的tab或者頁面,其所承載的網頁會創建一個新的進程,即Render進程。當你關閉一個tab或者頁面時,這個進程就會被kill掉,資源也會被再次回收。
其中有此進程創建完成的事件
//Render進程創建完成時 可重寫
protected virtual void OnRenderThreadCreated(CefListValue extraInfo)
{
}
還有兩個該進程生命周期的兩個事件
//在瀏覽器創建完成時 可重寫
protected virtual void OnBrowserCreated(CefBrowser browser)
{
}
//在瀏覽器銷毀之前調用 可重寫
protected virtual void OnBrowserDestroyed(CefBrowser browser)
{
}
有在瀏覽器跳轉之前的事件
//可重寫
protected virtual bool OnBeforeNavigation(CefBrowser browser, CefFrame frame, CefRequest request, CefNavigationType navigationType, bool isRedirect) { //默認返回假 允許跳轉,否則返回真來阻止跳轉 return false; }
有頁面的javascript上下文創建時的事件
//javascript上下文創建時 可重寫
protected virtual void OnContextCreated(CefBrowser browser, CefFrame frame, CefV8Context context) { }
以上舉例了這些事件,其實這些事件也可以形象的理解為博客園中的自定義自己的主頁一樣,編寫一段html代碼讓其更豐富,更美觀,而這就是我們想要的。我可以不用知道博客園是怎樣將我輸入的代碼改變我的主頁面的,但是我只要分別寫好頁頭的html代碼和頁腳的,交給博客園就可以了。同樣對於libcef.dll也可以將它看成是“博客園”。
上面的代碼中出現了CefBrowser和CEfFrame這兩個比較常用的類,以下是代碼
public sealed unsafe partial class CefBrowser
{
public CefBrowserHost GetHost()
{
return CefBrowserHost.FromNative(
cef_browser_t.get_host(_self)
);
}
public bool CanGoBack
{
get { return cef_browser_t.can_go_back(_self) != 0; }
}
public void GoBack()
{
cef_browser_t.go_back(_self);
}
public bool CanGoForward
{
get { return cef_browser_t.can_go_forward(_self) != 0; }
}
public void GoForward()
{
cef_browser_t.go_forward(_self);
}
public bool IsLoading
{
get { return cef_browser_t.is_loading(_self) != 0; }
}
public void Reload()
{
cef_browser_t.reload(_self);
}
public void ReloadIgnoreCache()
{
cef_browser_t.reload_ignore_cache(_self);
}
public void StopLoad()
{
cef_browser_t.stop_load(_self);
}
public int Identifier
{
get { return cef_browser_t.get_identifier(_self); }
}
public bool IsSame(CefBrowser that)
{
if (that == null) return false;
return cef_browser_t.is_same(_self, that.ToNative()) != 0;
}
public bool IsPopup
{
get { return cef_browser_t.is_popup(_self) != 0; }
}
public bool HasDocument
{
get { return cef_browser_t.has_document(_self) != 0; }
}
public CefFrame GetMainFrame()
{
return CefFrame.FromNative(
cef_browser_t.get_main_frame(_self)
);
}
public CefFrame GetFocusedFrame()
{
return CefFrame.FromNative(
cef_browser_t.get_focused_frame(_self)
);
}
public CefFrame GetFrame(long identifier)
{
return CefFrame.FromNativeOrNull(
cef_browser_t.get_frame_byident(_self, identifier)
);
}
public CefFrame GetFrame(string name)
{
fixed (char* name_str = name)
{
var n_name = new cef_string_t(name_str, name.Length);
return CefFrame.FromNativeOrNull(
cef_browser_t.get_frame(_self, &n_name)
);
}
}
public int FrameCount
{
get { return (int)cef_browser_t.get_frame_count(_self); }
}
public long[] GetFrameIdentifiers()
{
var frameCount = FrameCount;
var identifiers = new long[frameCount];
UIntPtr n_count = (UIntPtr)frameCount;
fixed (long* identifiers_ptr = identifiers)
{
cef_browser_t.get_frame_identifiers(_self, &n_count, identifiers_ptr);
if ((int)n_count != frameCount) throw new InvalidOperationException(); // FIXME: ...
}
return identifiers;
}
public string[] GetFrameNames()
{
var list = libcef.string_list_alloc();
cef_browser_t.get_frame_names(_self, list);
var result = cef_string_list.ToArray(list);
libcef.string_list_free(list);
return result;
}
public bool SendProcessMessage(CefProcessId target, CefProcessMessage message)
{
if (message == null) throw new ArgumentNullException("message");
return cef_browser_t.send_process_message(_self, target, message.ToNative()) != 0;
}
}
public sealed unsafe partial class CefFrame
{
public bool IsValid
{
get { return cef_frame_t.is_valid(_self) != 0; }
}
public void Undo()
{
cef_frame_t.undo(_self);
}
public void Redo()
{
cef_frame_t.redo(_self);
}
public void Cut()
{
cef_frame_t.cut(_self);
}
public void Copy()
{
cef_frame_t.copy(_self);
}
public void Paste()
{
cef_frame_t.paste(_self);
}
public void Delete()
{
cef_frame_t.del(_self);
}
public void SelectAll()
{
cef_frame_t.select_all(_self);
}
public void ViewSource()
{
cef_frame_t.view_source(_self);
}
public void GetSource(CefStringVisitor visitor)
{
if (visitor == null) throw new ArgumentNullException("visitor");
cef_frame_t.get_source(_self, visitor.ToNative());
}
public void GetText(CefStringVisitor visitor)
{
if (visitor == null) throw new ArgumentNullException("visitor");
cef_frame_t.get_text(_self, visitor.ToNative());
}
public void LoadRequest(CefRequest request)
{
if (request == null) throw new ArgumentNullException("request");
cef_frame_t.load_request(_self, request.ToNative());
}
public void LoadUrl(string url)
{
fixed (char* url_str = url)
{
var n_url = new cef_string_t(url_str, url != null ? url.Length : 0);
cef_frame_t.load_url(_self, &n_url);
}
}
public void LoadString(string content, string url)
{
fixed (char* content_str = content)
fixed (char* url_str = url)
{
var n_content = new cef_string_t(content_str, content != null ? content.Length : 0);
var n_url = new cef_string_t(url_str, url != null ? url.Length : 0);
cef_frame_t.load_string(_self, &n_content, &n_url);
}
}
public void ExecuteJavaScript(string code, string url, int line)
{
fixed (char* code_str = code)
fixed (char* url_str = url)
{
var n_code = new cef_string_t(code_str, code != null ? code.Length : 0);
var n_url = new cef_string_t(url_str, url != null ? url.Length : 0);
cef_frame_t.execute_java_script(_self, &n_code, &n_url, line);
}
}
public bool IsMain
{
get { return cef_frame_t.is_main(_self) != 0; }
}
public bool IsFocused
{
get { return cef_frame_t.is_focused(_self) != 0; }
}
public string Name
{
get
{
var n_result = cef_frame_t.get_name(_self);
return cef_string_userfree.ToString(n_result);
}
}
public long Identifier
{
get { return cef_frame_t.get_identifier(_self); }
}
public CefFrame Parent
{
get
{
return CefFrame.FromNativeOrNull(
cef_frame_t.get_parent(_self)
);
}
}
public string Url
{
get
{
var n_result = cef_frame_t.get_url(_self);
return cef_string_userfree.ToString(n_result);
}
}
public CefBrowser Browser
{
get
{
return CefBrowser.FromNative(
cef_frame_t.get_browser(_self)
);
}
}
public CefV8Context V8Context
{
get
{
return CefV8Context.FromNative(
cef_frame_t.get_v8context(_self)
);
}
}
public void VisitDom(CefDomVisitor visitor)
{
if (visitor == null) throw new ArgumentNullException("visitor");
cef_frame_t.visit_dom(_self, visitor.ToNative());
}
}
CefFrame可以認為是一個iframe,有global對象,有腳本的上下文。一個頁面至少會有一個CefFrame,在這里頁面是和CefBrowser等同的。即他們的關系是CefBrowser至少會有一個CefFrame,一個CefFrame必須在CefBrowser下生存。
介紹了上面這些后 還有一個必須得說一下,那就是WebClient,如果說DemoCefApp是主人,擁有一切,那么WebClient就是管家了。
WebClient會對兩個進程之間的通信消息進行接收,代碼如下
//可重寫
protected virtual bool OnProcessMessageReceived(CefBrowser browser, CefProcessId sourceProcess, CefProcessMessage message) { return false; }
其中的CefProcessId如下
public enum CefProcessId
{
Browser,
Renderer,
}
而發送消息已經在上面的CefBrowser中有這么一個方法 public bool SendProcessMessage(CefProcessId target, CefProcessMessage message) CefBrowser是會在兩類進程的主線程上都會有這么一個實例的引用。當你在某些事件調用時,你就可以通過這兩個方法輕松的在不同進程之間發送和接收消息了。
WebClient還包含了很多細節的可重寫方法。
比如說下面這個方法
protected virtual CefRequestHandler GetRequestHandler()
{
return null;
}
通過自定義CefRequestHandler之后我們就可以返回自定義CefRequestHandler的對象,每當我們打算請求的時候,可以對這個request請求進行處理,處理完成之后再將該請求經行發送,甚至可以取消這次的發送。
下面再看看自定義的WebBrowser。由於之前已經將CefBrowser定義為sealed類型,於是我不再繼承他了,我只將他作為一個屬性以便於使用。同樣我也需要一個管家WebClient經行進程間的通信還有對請求Request、加載Load等等的支持,才能讓我這個WebBrowser提供更需要的功能。下面是代碼
public sealed class WebBrowser
{
private readonly object _owner;
private readonly CefBrowserSettings _settings;
private string _startUrl;
private CefClient _client;
private CefBrowser _browser;
private IntPtr _windowHandle;
private bool _created;
public WebBrowser(object owner, CefBrowserSettings settings, string startUrl)
{
_owner = owner;
_settings = settings;
_startUrl = startUrl;
}
public string StartUrl
{
get { return _startUrl; }
set { _startUrl = value; }
}
public CefBrowser CefBrowser
{
get { return _browser; }
}
public void Create(CefWindowInfo windowInfo)
{
if (_client == null)
{
_client = new WebClient(this);
}
CefBrowserHost.CreateBrowser(windowInfo, _client, _settings, StartUrl);
}
public event EventHandler Created;
internal void OnCreated(CefBrowser browser)
{
if (_created) throw new InvalidOperationException("Browser already created.");
_created = true;
_browser = browser;
var handler = Created;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
internal void Close()
{
if (_browser != null)
{
_browser.Dispose();
_browser = null;
}
}
public event EventHandler<TitleChangedEventArgs> TitleChanged;
internal void OnTitleChanged(string title)
{
var handler = TitleChanged;
if (handler != null)
{
handler(this, new TitleChangedEventArgs(title));
}
}
public event EventHandler<AddressChangedEventArgs> AddressChanged;
internal void OnAddressChanged(string address)
{
var handler = AddressChanged;
if (handler != null)
{
handler(this, new AddressChangedEventArgs(address));
}
}
public event EventHandler<TargetUrlChangedEventArgs> TargetUrlChanged;
internal void OnTargetUrlChanged(string targetUrl)
{
var handler = TargetUrlChanged;
if (handler != null)
{
handler(this, new TargetUrlChangedEventArgs(targetUrl));
}
}
public event EventHandler<LoadingStateChangedEventArgs> LoadingStateChanged;
internal void OnLoadingStateChanged(bool isLoading, bool canGoBack, bool canGoForward)
{
var handler = LoadingStateChanged;
if (handler != null)
{
handler(this, new LoadingStateChangedEventArgs(isLoading, canGoBack, canGoForward));
}
}
public event EventHandler<BeforeResourceLoadedEventArgs> BeforeResourceLoaded;
internal void OnBeforeResourceLoaded(CefRequest request, long frameIdentifier)
{
var handler = BeforeResourceLoaded;
if (handler != null)
{
handler(this, new BeforeResourceLoadedEventArgs(request, frameIdentifier));
}
}
}
當中使用了很多自定義的事件,比如說剛剛自定義的CefRequestHandler在處理特定的請求時給自定義的WebBrowser一個信號,於是使用了事件的方式。
今天太晚了。。。打算下次在接着分享
可以先看一下幾張截圖。
下面是winform的項目引用以上的項目,采用了第三方的Razor視圖引擎,能夠滿足離線應用和在線應用的需求了

看一下運行效果吧


登陸成功后(異步登陸是ajax的方式並對ajax請求經行捕獲,使用了jquery。。驚奇的發現該請求的Method是options,不是post和get,怪不得一開始沒捕獲到。小插曲啦,嘻嘻)

