介紹
CEF全稱Chromium Embedded Framework,是一個基於Google Chromium 的開源項目。Google Chromium項目主要是為Google Chrome應用開發的,而CEF的目標則是為第三方應用提供可嵌入瀏覽器支持。CEF隔離底層Chromium和Blink的復雜代碼,並提供一套產品級穩定的API,發布跟蹤具體Chromium版本的分支,以及二進制包。CEF的大部分特性都提供了豐富的默認實現,讓使用者做盡量少的定制即可滿足需求。在本文發布的時候,世界上已經有很多公司和機構采用CEF,CEF的安裝量超過了100萬。[CEF wikipedia]頁面上有使用CEF的公司和機構的不完全的列表。CEF的典型應用場景包括:
- 嵌入一個兼容HTML5的瀏覽器控件到一個已經存在的本地應用。
- 創建一個輕量化的殼瀏覽器,用以托管主要用Web技術開發的應用。
- 有些應用有獨立的繪制框架,使用CEF對Web內容做離線渲染。
- 使用CEF做自動化Web測試。
CEF3是基於Chomuim Content API多進程構架的下一代CEF,擁有下列優勢:
- 改進的性能和穩定性(JavaScript和插件在一個獨立的進程內執行)。
- 支持Retina顯示器。
- 支持WebGL和3D CSS的GPU加速。
- 類似WebRTC和語音輸入這樣的前衛特性。
- 通過DevTools遠程調試協議以及ChromeDriver2提供更好的自動化UI測試。
- 更快獲得當前以及未來的Web特性和標准的能力。
本文檔介紹CEF3開發中涉及到的一般概念。
開始
- 使用二進制包
- 從源碼編譯(Building from Source Code)
- 示例應用程序(Sample Application)
- 重要概念(Important Concepts)
- C++ 封裝(C++ Wrapper)應用程序布局(Application Layout)
- 進程(Processes)
- 線程(Threads)
- 引用計數(Reference Counting)
- 字符串(Strings)
- 命令行參數(Command Line Arguments)
- 應用程序結構(Application Structure)單一執行體(Single Executable)
- Windows操作系統(Windows)
- Linux操作系統(Linux)
- Mac X平台(Mac OS X)
- 分離子進程執行體(Separate Sub-Process Executable)
- 集成消息循環(Message Loop Integration)
- CefSettings
- CefBrowser和CefFrame
- CefApp
- CefClient
- Browser生命周期(Browser Life Span)
- 離屏渲染(Off-Screen Rendering)
- 投遞任務(Posting Tasks)
- 進程間通信(Inter-Process Communication (IPC))
- 處理啟動消息(Process Startup Messages)
- 處理運行時消息(Process Runtime Messages)
- 異步JavaScript綁定(Asynchronous JavaScript Bindings)同步請求(Synchronous Requests)
- 通用消息轉發(Generic Message Router)
- 自定義實現(Custom Implementation)
- 網絡層(Network Layer)
- 自定義請求(Custom Requests)
- 瀏覽器無關請求(Browser-Independent Requests)
- 請求響應(Request Handling)
- Scheme響應(Scheme Handler)
- 請求攔截(Request Interception)
- 其他回調(Other Callbacks)
- Proxy Resolution
使用二進制包
CEF3的二進制包可以在這個頁面下載。其中包含了在特定平台(Windows,Mac OS X 以及 Linux)編譯特定版本CEF3所需的全部文件。不同平台擁有共同的結構:
- cefclient
- Debug
- include
- libcef_dll
- Release
- Resources
- tools
每個二進制包包含一個README.txt文件和一個LICENSE.txt文件,README.txt用以描述平台相關的細節,而LICENSE.txt包含CEF的BSD版權說明。如果你發布了基於CEF的應用,則應該在應用程序的某個地方包含該版權聲明。例如,你可以在"關於”和“授權"頁面列出該版權聲明,或者單獨一個文檔包含該版權聲明。“關於”和“授權”信息也可以分別在CEF瀏覽器的"about:license"和"about:credits"頁面查看。
基於CEF二進制包的應用程序可以使用每個平台上的經典編譯工具。包括Windows平台上的Visual Studio,Mac OSX平台上的Xcode,以及Linux平台上的gcc/make編譯工具鏈。CEF項目的下載頁面包含了這些平台上編譯特定版本CEF所需的編譯工具的版本信息。在Linux上編譯CEF時需要特別注意依賴工具鏈。
Tutorial Wiki頁面有更多關於如何使用CEF3二進制包創建簡單應用程序的細節。
從源碼編譯(Building from Source Code)
CEF可以從源碼編譯,用戶可以使用本地編譯系統或者像TeamCity這樣的自動化編譯系統編譯。首先你需要使用svn或者git下載Chromium和CEF的源碼。由於Chromium源碼很大,只建議在內存大於4GB的現代機器上編譯。編譯Chromium和CEF的細節請參考BranchesAndBuilding頁面。
示例應用程序(Sample Application)
cefclient是一個完整的CEF客戶端應用程序示例,並且它的源碼包含在CEF每個二進制發布包中。使用CEF創建一個新的應用程序,最簡單的方法是先從cefclient應用程序開始,刪除你不需要的部分。本文檔中許多示例都是來源於cefclient應用程序。
重要概念(Important Concepts)
在開發基於CEF3的應用程序前,有一些重要的基礎概念應該被理解。
C++ 封裝(C++ Wrapper)
libcef 動態鏈接庫導出 C API 使得使用者不用關心CEF運行庫和基礎代碼。libcef_dll_wrapper 工程把 C API 封裝成 C++ API同時包含在客戶端應用程序工程中,與cefclient一樣,源代碼作為CEF二進制發布包的一部分共同發布。C/C++ API的轉換層代碼是由轉換工具自動生成。UsingTheCAPI 頁面描述了如何使用C API。
進程(Processes)
CEF3是多進程架構的。Browser被定義為主進程,負責窗口管理,界面繪制和網絡交互。Blink的渲染和Js的執行被放在一個獨立的Render 進程中;除此之外,Render進程還負責Js Binding和對Dom節點的訪問。 默認的進程模型中,會為每個標簽頁創建一個新的Render進程。其他進程按需創建,例如管理插件的進程以及處理合成加速的進程等都是按需創建。
默認情況下,主應用程序會被多次啟動運行各自獨立的進程。這是通過傳遞不同的命令行參數給CefExecuteProcess函數做到的。如果主應用程序很大,加載時間比較長,或者不能在非瀏覽器進程里使用,則宿主程序可使用獨立的可執行文件去運行這些進程。這可以通過配置CefSettings.browser_subprocess_path變量做到。更多細節請參考Application Structure一節。
CEF3的進程之間可以通過IPC進行通信。Browser和Render進程可以通過發送異步消息進行雙向通信。甚至在Render進程可以注冊在Browser進程響應的異步JavaScript API。 更多細節,請參考Inter-Process Communication一節。
通過設置命令行的--single-process
,CEF3就可以支持用於調試目的的單進程運行模型。支持的平台為:Windows,Mac OS X 和Linux。
線程(Threads)
在CEF3中,每個進程都會運行多個線程。完整的線程類型表請參照cef_thread_id_t。例如,在Browser進程中包含如下主要的線程:
- TID_UI 線程是瀏覽器的主線程。如果應用程序在調用調用CefInitialize()時,傳遞CefSettings.multi_threaded_message_loop=false,這個線程也是應用程序的主線程。
- TID_IO 線程主要負責處理IPC消息以及網絡通信。
- TID_FILE 線程負責與文件系統交互。
由於CEF采用多線程架構,有必要使用鎖和閉包來保證數據的線程安全語義。IMPLEMENT_LOCKING定義提供了Lock()和Unlock()方法以及AutoLock對象來保證不同代碼塊同步訪問數據。CefPostTask函數組支持簡易的線程間異步消息傳遞。更多信息,請參考Posting Tasks章節。
可以通過CefCurrentlyOn()方法判斷當前所在的線程環境,cefclient工程使用下面的定義來確保方法在期望的線程中被執行。
1 #define REQUIRE_UI_THREAD() ASSERT(CefCurrentlyOn(TID_UI)); 2 #define REQUIRE_IO_THREAD() ASSERT(CefCurrentlyOn(TID_IO)); 3 #define REQUIRE_FILE_THREAD() ASSERT(CefCurrentlyOn(TID_FILE));
引用計數(Reference Counting)
所有的框架類從CefBase繼承,實例指針由CefRefPtr管理,CefRefPtr通過調用AddRef()和Release()方法自動管理引用計數。框架類的實現方式如下:
1 class MyClass : public CefBase { 2 public: 3 // Various class methods here... 4 5 private: 6 // Various class members here... 7 8 IMPLEMENT_REFCOUNTING(MyClass); // Provides atomic refcounting implementation. 9 }; 10 11 // References a MyClass instance 12 CefRefPtr<MyClass> my_class = new MyClass();
字符串(Strings)
CEF為字符串定義了自己的數據結構。主要是出於以下原因:
- libcef包和宿主程序可能使用不同的運行時,對堆管理的方式也不同。所有的對象,包括字符串,需要確保和申請堆內存使用相同的運行時環境。
- libcef包可以編譯為支持不同的字符串類型(UTF8,UTF16以及WIDE)。默認采用的是UTF16,默認字符集可以通過更改cef_string.h文件中的定義,然后重新編譯來修改。當使用寬字節集的時候,切記字符的長度由當前使用的平台決定。
UTF16字符串結構體示例如下:
1 typedef struct _cef_string_utf16_t { 2 char16* str; // Pointer to the string 3 size_t length; // String length 4 void (*dtor)(char16* str); // Destructor for freeing the string on the correct heap 5 } cef_string_utf16_t;
通過typedef來設置常用的字符編碼。
1 typedef char16 cef_char_t; 2 typedef cef_string_utf16_t cef_string_t;
CEF提供了一批C語言的方法來操作字符串(通過#define的方式來適應不同的字符編碼)
- cef_string_set 對制定的字符串變量賦值(支持深拷貝或淺拷貝)。
- cef_string_clear 清空字符串。
- cef_string_cmp 比較兩個字符串。
CEF也提供了字符串不同編碼之間相互轉換的方法。具體函數列表請查閱cef_string.h和cef_string_types.h文件。
在C++中,通常使用CefString類來管理CEF的字符串。CefString支持與std::string(UTF8)、std::wstring(wide)類型的相互轉換。也可以用來包裹一個cef_string_t結構來對其進行賦值。
和std::string的相互轉換:
1 std::string str = “Some UTF8 string”; 2 3 // Equivalent ways of assigning |str| to |cef_str|. Conversion from UTF8 will occur if necessary. 4 CefString cef_str(str); 5 cef_str = str; 6 cef_str.FromString(str); 7 8 // Equivalent ways of assigning |cef_str| to |str|. Conversion to UTF8 will occur if necessary. 9 str = cef_str; 10 str = cef_str.ToString();
和std::wstring的相互轉換:
1 std::wstring str = “Some wide string”; 2 3 // Equivalent ways of assigning |str| to |cef_str|. Conversion from wide will occur if necessary. 4 CefString cef_str(str); 5 cef_str = str; 6 cef_str.FromWString(str); 7 8 // Equivalent ways of assigning |cef_str| to |str|. Conversion to wide will occur if necessary. 9 str = cef_str; 10 str = cef_str.ToWString();
如果是ASCII編碼,使用FromASCII進行賦值:
1 const char* cstr = “Some ASCII string”; 2 CefString cef_str; 3 cef_str.FromASCII(cstr);
一些結構體(比如CefSettings)含有cef_string_t類型的成員,CefString支持直接賦值給這些成員。
1 CefSettings settings; 2 const char* path = “/path/to/log.txt”; 3 4 // Equivalent assignments. 5 CefString(&settings.log_file).FromASCII(path); 6 cef_string_from_ascii(path, strlen(path), &settings.log_file);
命令行參數(Command Line Arguments)
在CEF3和Chromium中許多特性可以使用命令行參數進行配置。這些參數采用--some-argument[=optional-param]
形式,並通過CefExecuteProcess()和CefMainArgs結構(參考下面的應用資源布局章節)傳遞給CEF。在傳遞CefSettings結構給CefInitialize()之前,我們可以設置CefSettings.command_line_args_disabled為true來禁用對命令行參數的處理。如果想指定命令行參數傳入主應用程序,實現CefApp::OnBeforeCommandLineProcessing()方法。更多關於如何查找已支持的命令行選項的信息,請查看client_switches.cpp文件的注釋。
應用程序布局(Application Layout)
應用資源布局依賴於平台,有很大的不同。比如,在Mac OS X上,你的資源布局必須遵循特定的app bundles結構;Window與Linux則更靈活,允許你定制CEF庫文件與資源文件所在的位置。為了獲取到特定可以正常工作的示例,你可以從工程的下載頁面下載到一個client壓縮包。每個平台對應的README.txt文件詳細說明了哪些文件是可選的,哪些文件是必須的。
Windows操作系統(Windows)
在Windows平台上,默認的資源布局將libcef庫文件、相關資源與可執行文件放置在同級目錄,文件夾結構大致如下:
1 Application/ 2 cefclient.exe <= cefclient application executable 3 libcef.dll <= main CEF library 4 icudt.dll <= ICU unicode support library 5 ffmpegsumo.dll <= HTML5 audio/video support library 6 libEGL.dll, libGLESv2.dll, … <= accelerated compositing support libraries 7 cef.pak, devtools_resources.pak <= non-localized resources and strings 8 locales/ 9 en-US.pak, … <= locale-specific resources and strings
使用結構體CefSettings可以定制CEF庫文件、資源文件的位置(查看README.txt文件或者本文中CefSettings部分獲取更詳細的信息)。雖然在Windows平台上,cefclient項目將資源文件以二進制形式編譯進cefclient.rc文件,但是改為從文件系統加載資源也很容易。
Linux操作系統(Linux)
在Linux平台上,默認的資源布局將libcef庫文件、相關資源與可執行文件放置在同級目錄。注意:在你編譯的版本與發行版本應用程序中,libcef.so的位置是有差異的,此文件的位置取決於編譯可執行程序時,編譯器rpath的值。比如,編譯選項為“-Wl,-rpath,.”(“.”意思是當前文件夾),這樣libcef.so與可執行文件處於同級目錄。libcef.so文件的路徑可以通過環境變量中的“LD_LIBRARY_PATH
”指定。
1 Application/ 2 cefclient <= cefclient application executable 3 libcef.so <= main CEF library 4 ffmpegsumo.so <-- HTML5 audio/video support library 5 cef.pak, devtools_resources.pak <= non-localized resources and strings 6 locales/ 7 en-US.pak, … <= locale-specific resources and strings 8 files/ 9 binding.html, … <= cefclient application resources
使用結構體CefSettings可以定制CEF庫文件、資源文件(查看README.txt文件或者本文中CefSettings部分獲取更詳細的信息)。
Mac X平台(Mac OS X)
在Mac X平台上,app bundles委托給了Chromium實現,因此不是很靈活。文件夾結構大致如下:
1 cefclient.app/ 2 Contents/ 3 Frameworks/ 4 Chromium Embedded Framework.framework/ 5 Libraries/ 6 ffmpegsumo.so <= HTML5 audio/video support library 7 libcef.dylib <= main CEF library 8 Resources/ 9 cef.pak, devtools_resources.pak <= non-localized resources and strings 10 *.png, *.tiff <= Blink image and cursor resources 11 en.lproj/, … <= locale-specific resources and strings 12 libplugin_carbon_interpose.dylib <= plugin support library 13 cefclient Helper.app/ 14 Contents/ 15 Info.plist 16 MacOS/ 17 cefclient Helper <= helper executable 18 Pkginfo 19 cefclient Helper EH.app/ 20 Contents/ 21 Info.plist 22 MacOS/ 23 cefclient Helper EH <= helper executable 24 Pkginfo 25 cefclient Helper NP.app/ 26 Contents/ 27 Info.plist 28 MacOS/ 29 cefclient Helper NP <= helper executable 30 Pkginfo 31 Info.plist 32 MacOS/ 33 cefclient <= cefclient application executable 34 Pkginfo 35 Resources/ 36 binding.html, … <= cefclient application resources
列表中的“Chromium Embedded Framework.framework”,這個未受版本管控的框架包含了所有的CEF庫文件、資源文件。使用install_name_tool與@executable_path,將cefclient,cefclient helper等可執行文件,連接到了libcef.dylib上。
應用程序cefclient helper用來執行不同特點、獨立的進程(Renderer,plugin等),這些進程需要獨立的資源布局與Info.plist等文件,它們沒有顯示停靠圖標。用來啟動插件進程的EH Helper清除了MH_NO_HEAP_EXECUTION標志位,這樣就允許一個可執行堆。只能用來啟動NaCL插件進程的NP Helper,清除了MH_PIE標志位,這樣就禁用了ASLR。這些都是tools文件夾下面,用來構建進程腳本的一部分。為了理清腳本的依賴關系,更好的做法是檢查發行版本中的Xcode工程或者原始文件cefclient.gyp。
應用程序結構(Application Structure)
每個CEF3應用程序都是相同的結構
- 提供入口函數,用於初始化CEF、運行子進程執行邏輯或者CEF消息循環。
- 提供CefApp實現,用於處理進程相關的回調。
- 提供CefClient實現,用於處理Browser實例相關的回調。
- 執行CefBrowserHost::CreateBrowser()創建一個Browser實例,使用CefLifeSpanHandler管理Browser對象生命周期。
入口函數(Entry-Point Function)
像本文中進程章節描述的那樣,一個CEF3應用程序會運行多個進程,這些進程能夠使用同一個執行器或者為子進程定制的、單獨的執行器。進程的執行從入口函數開始,示例cefclient_win.cc、cefclient_gtk.cc、cefclient_mac.mm分別對應Windows、Linux和Mac OS-X平台下的實現。
當執行子進程時,CEF將使用命令行參數指定配置信息,這些命令行參數必須通過CefMainArgs結構體傳入到CefExecuteProcess函數。CefMainArgs的定義與平台相關,在Linux、Mac OS X平台下,它接收main函數傳入的argc和argv參數值。
CefMainArgs main_args(argc, argv);
在Windows平台下,它接收wWinMain函數傳入的參數:實例句柄(HINSTANCE),這個實例能夠通過函數GetModuleHandle(NULL)獲取。
CefMainArgs main_args(hInstance);
單一執行體(Single Executable)
當以單一執行體運行時,根據不同的進程類型,入口函數有差異。Windows、Linux平台支持單一執行體架構,Mac OS X平台則不行。
1 int main(int argc, char* argv[]) { 2 // Structure for passing command-line arguments. 3 // The definition of this structure is platform-specific. 4 CefMainArgs main_args(argc, argv); 5 6 // Optional implementation of the CefApp interface. 7 CefRefPtr<MyApp> app(new MyApp); 8 9 // Execute the sub-process logic, if any. This will either return immediately for the browser 10 // process or block until the sub-process should exit. 11 int exit_code = CefExecuteProcess(main_args, app.get()); 12 if (exit_code >= 0) { 13 // The sub-process terminated, exit now. 14 return exit_code; 15 } 16 17 // Populate this structure to customize CEF behavior. 18 CefSettings settings; 19 20 // Initialize CEF in the main process. 21 CefInitialize(main_args, settings, app.get()); 22 23 // Run the CEF message loop. This will block until CefQuitMessageLoop() is called. 24 CefRunMessageLoop(); 25 26 // Shut down CEF. 27 CefShutdown(); 28 29 return 0; 30 }
分離子進程執行體(Separate Sub-Process Executable)
當使用獨立的子進程執行體時,你需要2個分開的可執行工程和2個分開的入口函數。
主程序的入口函數:
1 // Program entry-point function. 2 // 程序入口函數 3 int main(int argc, char* argv[]) { 4 // Structure for passing command-line arguments. 5 // The definition of this structure is platform-specific. 6 // 傳遞命令行參數的結構體。 7 // 這個結構體的定義與平台相關。 8 CefMainArgs main_args(argc, argv); 9 10 // Optional implementation of the CefApp interface. 11 // 可選擇性地實現CefApp接口 12 CefRefPtr<MyApp> app(new MyApp); 13 14 // Populate this structure to customize CEF behavior. 15 // 填充這個結構體,用於定制CEF的行為。 16 CefSettings settings; 17 18 // Specify the path for the sub-process executable. 19 // 指定子進程的執行路徑 20 CefString(&settings.browser_subprocess_path).FromASCII(“/path/to/subprocess”); 21 22 // Initialize CEF in the main process. 23 // 在主進程中初始化CEF 24 CefInitialize(main_args, settings, app.get()); 25 26 // Run the CEF message loop. This will block until CefQuitMessageLoop() is called. 27 // 執行消息循環,此時會堵塞,直到CefQuitMessageLoop()函數被調用。 28 CefRunMessageLoop(); 29 30 // Shut down CEF. 31 // 關閉CEF 32 CefShutdown(); 33 34 return 0; 35 }
子進程程序的入口函數:
1 // Program entry-point function. 2 // 程序入口函數 3 int main(int argc, char* argv[]) { 4 // Structure for passing command-line arguments. 5 // The definition of this structure is platform-specific. 6 // 傳遞命令行參數的結構體。 7 // 這個結構體的定義與平台相關。 8 CefMainArgs main_args(argc, argv); 9 10 // Optional implementation of the CefApp interface. 11 // 可選擇性地實現CefApp接口 12 CefRefPtr<MyApp> app(new MyApp); 13 14 // Execute the sub-process logic. This will block until the sub-process should exit. 15 // 執行子進程邏輯,此時會堵塞直到子進程退出。 16 return CefExecuteProcess(main_args, app.get()); 17 }
集成消息循環(Message Loop Integration)
CEF可以不用它自己提供的消息循環,而與已經存在的程序中消息環境集成在一起,有兩種方式可以做到:
-
周期性執行CefDoMessageLoopWork()函數,替代調用CefRunMessageLoop()。CefDoMessageLoopWork()的每一次調用,都將執行一次CEF消息循環的單次迭代。需要注意的是,此方法調用次數太少時,CEF消息循環會餓死,將極大的影響Browser的性能,調用次數太頻繁又將影響CPU使用率。
-
設置CefSettings.multi_threaded_message_loop=true(Windows平台下有效),這個設置項將導致CEF在單獨的線程上運行Browser的界面,而不是在主線程上,這種場景下CefDoMessageLoopWork()或者CefRunMessageLoop()都不需要調用,CefInitialze()、CefShutdown()仍然在主線程中調用。你需要提供主程序線程通信的機制(查看cefclient_win.cpp中提供的消息窗口實例)。在Windows平台下,你可以通過命令行參數
--multi-threaded-message-loop
測試上述消息模型。
CefSettings
CefSettings結構體允許定義全局的CEF配置,經常用到的配置項如下:
- single_process 設置為true時,Browser和Renderer使用一個進程。此項也可以通過命令行參數“single-process”配置。查看本文中“進程”章節獲取更多的信息。
- browser_subprocess_path 設置用於啟動子進程單獨執行器的路徑。參考本文中單進程執行體章節獲取更多的信息。
- cache_path 設置磁盤上用於存放緩存數據的位置。如果此項為空,某些功能將使用內存緩存,多數功能將使用臨時的磁盤緩存。形如本地存儲的HTML5數據庫只能在設置了緩存路徑才能跨session存儲。
- locale 此設置項將傳遞給Blink。如果此項為空,將使用默認值“en-US”。在Linux平台下此項被忽略,使用環境變量中的值,解析的依次順序為:LANGUAE,LC_ALL,LC_MESSAGES和LANG。此項也可以通過命令行參數“lang”配置。
- log_file 此項設置的文件夾和文件名將用於輸出debug日志。如果此項為空,默認的日志文件名為debug.log,位於應用程序所在的目錄。此項也可以通過命令參數“log-file”配置。
- log_severity 此項設置日志級別。只有此等級、或者比此等級高的日志的才會被記錄。此項可以通過命令行參數“log-severity”配置,可以設置的值為“verbose”,“info”,“warning”,“error”,“error-report”,“disable”。
- resources_dir_path 此項設置資源文件夾的位置。如果此項為空,Windows平台下cef.pak、Linux平台下devtools_resourcs.pak、Mac OS X下的app bundle Resources目錄必須位於組件目錄。此項也可以通過命令行參數“resource-dir-path”配置。
- locales_dir_path 此項設置locale文件夾位置。如果此項為空,locale文件夾必須位於組件目錄,在Mac OS X平台下此項被忽略,pak文件從app bundle Resources目錄。此項也可以通過命令行參數“locales-dir-path”配置。
- remote_debugging_port 此項可以設置1024-65535之間的值,用於在指定端口開啟遠程調試。例如,如果設置的值為8080,遠程調試的URL為http://localhost:8080。CEF或者Chrome瀏覽器能夠調試CEF。此項也可以通過命令行參數“remote-debugging-port”配置。
CefBrowser和CefFrame
CefBrowser和CefFrame對象被用來發送命令給瀏覽器以及在回調函數里獲取狀態信息。每個CefBrowser對象包含一個主CefFrame對象,主CefFrame對象代表頁面的頂層frame;同時每個CefBrowser對象可以包含零個或多個的CefFrame對象,分別代表不同的子Frame。例如,一個瀏覽器加載了兩個iframe,則該CefBrowser對象擁有三個CefFrame對象(頂層frame和兩個iframe)。
下面的代碼在瀏覽器的主frame里加載一個URL:
browser->GetMainFrame()->LoadURL(some_url);
下面的代碼執行瀏覽器的回退操作:
browser->GoBack();
下面的代碼從主frame里獲取HTML內容:
1 // Implementation of the CefStringVisitor interface. 2 class Visitor : public CefStringVisitor { 3 public: 4 Visitor() {} 5 6 // Called asynchronously when the HTML contents are available. 7 virtual void Visit(const CefString& string) OVERRIDE { 8 // Do something with |string|... 9 } 10 11 IMPLEMENT_REFCOUNTING(Visitor); 12 }; 13 14 browser->GetMainFrame()->GetSource(new Visitor());
CefBrowser和CefFrame對象在Browser進程和Render進程都有對等的代理對象。在Browser進程里,Host(宿主)行為控制可以通過CefBrowser::GetHost()方法控制。例如,瀏覽器窗口的原生句柄可以用下面的代碼獲取:
// CefWindowHandle is defined as HWND on Windows, NSView* on Mac OS X // and GtkWidget* on Linux. CefWindowHandle window_handle = browser->GetHost()->GetWindowHandle();
其他方法包括歷史導航,加載字符串和請求,發送編輯命令,提取text/html內容等。請參考支持函數相關的文檔或者CefBrowser的頭文件注釋。
CefApp
CefApp接口提供了不同進程的可定制回調函數。畢竟重要的回調函數如下:
- OnBeforeCommandLineProcessing 提供了以編程方式設置命令行參數的機會,更多細節,請參考Command Line Arguments一節。
- OnRegisterCustomSchemes 提供了注冊自定義schemes的機會,更多細節,請參考Request Handling一節。
- GetBrowserProcessHandler 返回定制Browser進程的Handler,該Handler包括了諸如OnContextInitialized的回調。
- GetRenderProcessHandler 返回定制Render進程的Handler,該Handler包含了JavaScript相關的一些回調以及消息處理的回調。 更多細節,請參考JavascriptIntegration和Inter-Process Communication兩節。
CefApp子類的例子:
1 // MyApp implements CefApp and the process-specific interfaces. 2 class MyApp : public CefApp, 3 public CefBrowserProcessHandler, 4 public CefRenderProcessHandler { 5 public: 6 MyApp() {} 7 8 // CefApp methods. Important to return |this| for the handler callbacks. 9 virtual void OnBeforeCommandLineProcessing( 10 const CefString& process_type, 11 CefRefPtr<CefCommandLine> command_line) { 12 // Programmatically configure command-line arguments... 13 } 14 virtual void OnRegisterCustomSchemes( 15 CefRefPtr<CefSchemeRegistrar> registrar) OVERRIDE { 16 // Register custom schemes... 17 } 18 virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler() 19 OVERRIDE { return this; } 20 virtual CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler() 21 OVERRIDE { return this; } 22 23 // CefBrowserProcessHandler methods. 24 virtual void OnContextInitialized() OVERRIDE { 25 // The browser process UI thread has been initialized... 26 } 27 virtual void OnRenderProcessThreadCreated(CefRefPtr<CefListValue> extra_info) 28 OVERRIDE { 29 // Send startup information to a new render process... 30 } 31 32 // CefRenderProcessHandler methods. 33 virtual void OnRenderThreadCreated(CefRefPtr<CefListValue> extra_info) 34 OVERRIDE { 35 // The render process main thread has been initialized... 36 // Receive startup information in the new render process... 37 } 38 virtual void OnWebKitInitialized(CefRefPtr<ClientApp> app) OVERRIDE { 39 // WebKit has been initialized, register V8 extensions... 40 } 41 virtual void OnBrowserCreated(CefRefPtr<CefBrowser> browser) OVERRIDE { 42 // Browser created in this render process... 43 } 44 virtual void OnBrowserDestroyed(CefRefPtr<CefBrowser> browser) OVERRIDE { 45 // Browser destroyed in this render process... 46 } 47 virtual bool OnBeforeNavigation(CefRefPtr<CefBrowser> browser, 48 CefRefPtr<CefFrame> frame, 49 CefRefPtr<CefRequest> request, 50 NavigationType navigation_type, 51 bool is_redirect) OVERRIDE { 52 // Allow or block different types of navigation... 53 } 54 virtual void OnContextCreated(CefRefPtr<CefBrowser> browser, 55 CefRefPtr<CefFrame> frame, 56 CefRefPtr<CefV8Context> context) OVERRIDE { 57 // JavaScript context created, add V8 bindings here... 58 } 59 virtual void OnContextReleased(CefRefPtr<CefBrowser> browser, 60 CefRefPtr<CefFrame> frame, 61 CefRefPtr<CefV8Context> context) OVERRIDE { 62 // JavaScript context released, release V8 references here... 63 } 64 virtual bool OnProcessMessageReceived( 65 CefRefPtr<CefBrowser> browser, 66 CefProcessId source_process, 67 CefRefPtr<CefProcessMessage> message) OVERRIDE { 68 // Handle IPC messages from the browser process... 69 } 70 71 IMPLEMENT_REFCOUNTING(MyApp); 72 };
CefClient
CefClient提供訪問Browser實例的回調接口。一個CefClient實現可以在任意數量的Browser進程中共享。以下為幾個重要的回調:
- 比如處理Browser的生命周期,右鍵菜單,對話框,通知顯示, 拖曳事件,焦點事件,鍵盤事件等等。如果沒有對某個特定的處理接口進行實現會造成什么影響,請查看cef_client.h文件中相關說明。
- OnProcessMessageReceived在Browser收到Render進程的消息時被調用。更多細節,請參考Inter-Process Communication一節。
CefClient子類的例子:
1 // MyHandler implements CefClient and a number of other interfaces. 2 class MyHandler : public CefClient, 3 public CefContextMenuHandler, 4 public CefDisplayHandler, 5 public CefDownloadHandler, 6 public CefDragHandler, 7 public CefGeolocationHandler, 8 public CefKeyboardHandler, 9 public CefLifeSpanHandler, 10 public CefLoadHandler, 11 public CefRequestHandler { 12 public: 13 MyHandler(); 14 15 // CefClient methods. Important to return |this| for the handler callbacks. 16 virtual CefRefPtr<CefContextMenuHandler> GetContextMenuHandler() OVERRIDE { 17 return this; 18 } 19 virtual CefRefPtr<CefDisplayHandler> GetDisplayHandler() OVERRIDE { 20 return this; 21 } 22 virtual CefRefPtr<CefDownloadHandler> GetDownloadHandler() OVERRIDE { 23 return this; 24 } 25 virtual CefRefPtr<CefDragHandler> GetDragHandler() OVERRIDE { 26 return this; 27 } 28 virtual CefRefPtr<CefGeolocationHandler> GetGeolocationHandler() OVERRIDE { 29 return this; 30 } 31 virtual CefRefPtr<CefKeyboardHandler> GetKeyboardHandler() OVERRIDE { 32 return this; 33 } 34 virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() OVERRIDE { 35 return this; 36 } 37 virtual CefRefPtr<CefLoadHandler> GetLoadHandler() OVERRIDE { 38 return this; 39 } 40 virtual CefRefPtr<CefRequestHandler> GetRequestHandler() OVERRIDE { 41 return this; 42 } 43 virtual bool OnProcessMessageReceived(CefRefPtr<CefBrowser> browser, 44 CefProcessId source_process, 45 CefRefPtr<CefProcessMessage> message) 46 OVERRIDE { 47 // Handle IPC messages from the render process... 48 } 49 50 // CefContextMenuHandler methods 51 virtual void OnBeforeContextMenu(CefRefPtr<CefBrowser> browser, 52 CefRefPtr<CefFrame> frame, 53 CefRefPtr<CefContextMenuParams> params, 54 CefRefPtr<CefMenuModel> model) OVERRIDE { 55 // Customize the context menu... 56 } 57 virtual bool OnContextMenuCommand(CefRefPtr<CefBrowser> browser, 58 CefRefPtr<CefFrame> frame, 59 CefRefPtr<CefContextMenuParams> params, 60 int command_id, 61 EventFlags event_flags) OVERRIDE { 62 // Handle a context menu command... 63 } 64 65 // CefDisplayHandler methods 66 virtual void OnLoadingStateChange(CefRefPtr<CefBrowser> browser, 67 bool isLoading, 68 bool canGoBack, 69 bool canGoForward) OVERRIDE { 70 // Update UI for browser state... 71 } 72 virtual void OnAddressChange(CefRefPtr<CefBrowser> browser, 73 CefRefPtr<CefFrame> frame, 74 const CefString& url) OVERRIDE { 75 // Update the URL in the address bar... 76 } 77 virtual void OnTitleChange(CefRefPtr<CefBrowser> browser, 78 const CefString& title) OVERRIDE { 79 // Update the browser window title... 80 } 81 virtual bool OnConsoleMessage(CefRefPtr<CefBrowser> browser, 82 const CefString& message, 83 const CefString& source, 84 int line) OVERRIDE { 85 // Log a console message... 86 } 87 88 // CefDownloadHandler methods 89 virtual void OnBeforeDownload( 90 CefRefPtr<CefBrowser> browser, 91 CefRefPtr<CefDownloadItem> download_item, 92 const CefString& suggested_name, 93 CefRefPtr<CefBeforeDownloadCallback> callback) OVERRIDE { 94 // Specify a file path or cancel the download... 95 } 96 virtual void OnDownloadUpdated( 97 CefRefPtr<CefBrowser> browser, 98 CefRefPtr<CefDownloadItem> download_item, 99 CefRefPtr<CefDownloadItemCallback> callback) OVERRIDE { 100 // Update the download status... 101 } 102 103 // CefDragHandler methods 104 virtual bool OnDragEnter(CefRefPtr<CefBrowser> browser, 105 CefRefPtr<CefDragData> dragData, 106 DragOperationsMask mask) OVERRIDE { 107 // Allow or deny drag events... 108 } 109 110 // CefGeolocationHandler methods 111 virtual void OnRequestGeolocationPermission( 112 CefRefPtr<CefBrowser> browser, 113 const CefString& requesting_url, 114 int request_id, 115 CefRefPtr<CefGeolocationCallback> callback) OVERRIDE { 116 // Allow or deny geolocation API access... 117 } 118 119 // CefKeyboardHandler methods 120 virtual bool OnPreKeyEvent(CefRefPtr<CefBrowser> browser, 121 const CefKeyEvent& event, 122 CefEventHandle os_event, 123 bool* is_keyboard_shortcut) OVERRIDE { 124 // Perform custom handling of key events... 125 } 126 127 // CefLifeSpanHandler methods 128 virtual bool OnBeforePopup(CefRefPtr<CefBrowser> browser, 129 CefRefPtr<CefFrame> frame, 130 const CefString& target_url, 131 const CefString& target_frame_name, 132 const CefPopupFeatures& popupFeatures, 133 CefWindowInfo& windowInfo, 134 CefRefPtr<CefClient>& client, 135 CefBrowserSettings& settings, 136 bool* no_javascript_access) OVERRIDE { 137 // Allow or block popup windows, customize popup window creation... 138 } 139 virtual void OnAfterCreated(CefRefPtr<CefBrowser> browser) OVERRIDE { 140 // Browser window created successfully... 141 } 142 virtual bool DoClose(CefRefPtr<CefBrowser> browser) OVERRIDE { 143 // Allow or block browser window close... 144 } 145 virtual void OnBeforeClose(CefRefPtr<CefBrowser> browser) OVERRIDE { 146 // Browser window is closed, perform cleanup... 147 } 148 149 // CefLoadHandler methods 150 virtual void OnLoadStart(CefRefPtr<CefBrowser> browser, 151 CefRefPtr<CefFrame> frame) OVERRIDE { 152 // A frame has started loading content... 153 } 154 virtual void OnLoadEnd(CefRefPtr<CefBrowser> browser, 155 CefRefPtr<CefFrame> frame, 156 int httpStatusCode) OVERRIDE { 157 // A frame has finished loading content... 158 } 159 virtual void OnLoadError(CefRefPtr<CefBrowser> browser, 160 CefRefPtr<CefFrame> frame, 161 ErrorCode errorCode, 162 const CefString& errorText, 163 const CefString& failedUrl) OVERRIDE { 164 // A frame has failed to load content... 165 } 166 virtual void OnRenderProcessTerminated(CefRefPtr<CefBrowser> browser, 167 TerminationStatus status) OVERRIDE { 168 // A render process has crashed... 169 } 170 171 // CefRequestHandler methods 172 virtual CefRefPtr<CefResourceHandler> GetResourceHandler( 173 CefRefPtr<CefBrowser> browser, 174 CefRefPtr<CefFrame> frame, 175 CefRefPtr<CefRequest> request) OVERRIDE { 176 // Optionally intercept resource requests... 177 } 178 virtual bool OnQuotaRequest(CefRefPtr<CefBrowser> browser, 179 const CefString& origin_url, 180 int64 new_size, 181 CefRefPtr<CefQuotaCallback> callback) OVERRIDE { 182 // Allow or block quota requests... 183 } 184 virtual void OnProtocolExecution(CefRefPtr<CefBrowser> browser, 185 const CefString& url, 186 bool& allow_os_execution) OVERRIDE { 187 // Handle execution of external protocols... 188 } 189 190 IMPLEMENT_REFCOUNTING(MyHandler); 191 };
Browser生命周期(Browser Life Span)
Browser生命周期從執行 CefBrowserHost::CreateBrowser() 或者 CefBrowserHost::CreateBrowserSync() 開始。可以在CefBrowserProcessHandler::OnContextInitialized() 回調或者特殊平台例如windows的WM_CREATE 中方便的執行業務邏輯。
// Information about the window that will be created including parenting, size, etc. // The definition of this structure is platform-specific.
1 // 定義的結構體與平台相關 2 3 CefWindowInfo info; 4 // On Windows for example... 5 info.SetAsChild(parent_hwnd, client_rect); 6 7 // Customize this structure to control browser behavior. 8 CefBrowserSettings settings; 9 10 // CefClient implementation. 11 CefRefPtr<MyClient> client(new MyClient); 12 13 // Create the browser asynchronously. Initially loads the Google URL. 14 CefBrowserHost::CreateBrowser(info, client.get(), “http://www.google.com”, settings); 15 16 The CefLifeSpanHandler class provides the callbacks necessary for managing browser life span. Below is an extract of the relevant methods and members. 17 18 CefLifeSpanHandler 類提供管理 Browser生命周期必需的回調。以下為相關方法和成員。 19 20 class MyClient : public CefClient, 21 public CefLifeSpanHandler, 22 ... { 23 // CefClient methods. 24 virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() OVERRIDE { 25 return this; 26 } 27 28 // CefLifeSpanHandler methods. 29 void OnAfterCreated(CefRefPtr<CefBrowser> browser) OVERRIDE; 30 bool DoClose(CefRefPtr<CefBrowser> browser) OVERRIDE; 31 void OnBeforeClose(CefRefPtr<CefBrowser> browser) OVERRIDE; 32 33 // Member accessors. 34 CefRefPtr<CefBrowser> GetBrower() { return m_Browser; } 35 bool IsClosing() { return m_bIsClosing; } 36 37 private: 38 CefRefPtr<CefBrowser> m_Browser; 39 int m_BrowserId; 40 int m_BrowserCount; 41 bool m_bIsClosing; 42 43 IMPLEMENT_REFCOUNTING(MyHandler); 44 IMPLEMENT_LOCKING(MyHandler); 45 };
當Browser對象創建后OnAfterCreated() 方法立即執行。宿主程序可以用這個方法來保持對Browser對象的引用。
1 void MyClient::OnAfterCreated(CefRefPtr<CefBrowser> browser) { 2 // Must be executed on the UI thread. 3 REQUIRE_UI_THREAD(); 4 // Protect data members from access on multiple threads. 5 AutoLock lock_scope(this); 6 7 if (!m_Browser.get()) { 8 // Keep a reference to the main browser. 9 m_Browser = browser; 10 m_BrowserId = browser->GetIdentifier(); 11 } 12 13 // Keep track of how many browsers currently exist. 14 m_BrowserCount++; 15 }
執行CefBrowserHost::CloseBrowser()銷毀Browser對象。
// Notify the browser window that we would like to close it. This will result in a call to // MyHandler::DoClose() if the JavaScript 'onbeforeunload' event handler allows it. browser->GetHost()->CloseBrowser(false);
Browser對象的關閉事件來源於他的父窗口的關閉方法(比如,在父窗口上點擊X控鈕。)。父窗口需要調用 CloseBrowser(false) 並且等待操作系統的第二個關閉事件來決定是否允許關閉。如果在JavaScript 'onbeforeunload'事件處理或者 DoClose()回調中取消了關閉操作,則操作系統的第二個關閉事件可能不會發送。注意一下面示例中對IsCloseing()的判斷-它在第一個關閉事件中返回false,在第二個關閉事件中返回true(當 DoCloase 被調用后)。
Windows平台下,在父窗口的WndProc里處理WM_ClOSE消息:
1 case WM_CLOSE: 2 if (g_handler.get() && !g_handler->IsClosing()) { 3 CefRefPtr<CefBrowser> browser = g_handler->GetBrowser(); 4 if (browser.get()) { 5 // Notify the browser window that we would like to close it. This will result in a call to 6 // MyHandler::DoClose() if the JavaScript 'onbeforeunload' event handler allows it. 7 browser->GetHost()->CloseBrowser(false); 8 9 // Cancel the close. 10 return 0; 11 } 12 } 13 14 // Allow the close. 15 break; 16 17 case WM_DESTROY: 18 // Quitting CEF is handled in MyHandler::OnBeforeClose(). 19 return 0; 20 }
Linux平台下,處理delete_event
信號:
1 gboolean delete_event(GtkWidget* widget, GdkEvent* event, 2 GtkWindow* window) { 3 if (g_handler.get() && !g_handler->IsClosing()) { 4 CefRefPtr<CefBrowser> browser = g_handler->GetBrowser(); 5 if (browser.get()) { 6 // Notify the browser window that we would like to close it. This will result in a call to 7 // MyHandler::DoClose() if the JavaScript 'onbeforeunload' event handler allows it. 8 browser->GetHost()->CloseBrowser(false); 9 10 // Cancel the close. 11 return TRUE; 12 } 13 } 14 15 // Allow the close. 16 return FALSE; 17 }
MacOS X平台下,處理windowShouldClose選擇器:
1 // Called when the window is about to close. Perform the self-destruction 2 // sequence by getting rid of the window. By returning YES, we allow the window 3 // to be removed from the screen. 4 - (BOOL)windowShouldClose:(id)window { 5 if (g_handler.get() && !g_handler->IsClosing()) { 6 CefRefPtr<CefBrowser> browser = g_handler->GetBrowser(); 7 if (browser.get()) { 8 // Notify the browser window that we would like to close it. This will result in a call to 9 // MyHandler::DoClose() if the JavaScript 'onbeforeunload' event handler allows it. 10 browser->GetHost()->CloseBrowser(false); 11 12 // Cancel the close. 13 return NO; 14 } 15 } 16 17 // Try to make the window go away. 18 [window autorelease]; 19 20 // Clean ourselves up after clearing the stack of anything that might have the 21 // window on it. 22 [self performSelectorOnMainThread:@selector(cleanup:) 23 withObject:window 24 waitUntilDone:NO]; 25 26 // Allow the close. 27 return YES; 28 }
DoClose方法設置m_blsClosing 標志位為true,並返回false以再次發送操作系統的關閉事件。
1 bool MyClient::DoClose(CefRefPtr<CefBrowser> browser) { 2 // Must be executed on the UI thread. 3 REQUIRE_UI_THREAD(); 4 // Protect data members from access on multiple threads. 5 AutoLock lock_scope(this); 6 7 // Closing the main window requires special handling. See the DoClose() 8 // documentation in the CEF header for a detailed description of this 9 // process. 10 if (m_BrowserId == browser->GetIdentifier()) { 11 // Notify the browser that the parent window is about to close. 12 browser->GetHost()->ParentWindowWillClose(); 13 14 // Set a flag to indicate that the window close should be allowed. 15 m_bIsClosing = true; 16 } 17 18 // Allow the close. For windowed browsers this will result in the OS close 19 // event being sent. 20 return false; 21 }
當操作系統捕捉到第二次關閉事件,它才會允許父窗口真正關閉。該動作會先觸發OnBeforeClose()回調,請在該回調里釋放所有對瀏覽器對象的引用。
1 void MyHandler::OnBeforeClose(CefRefPtr<CefBrowser> browser) { 2 // Must be executed on the UI thread. 3 REQUIRE_UI_THREAD(); 4 // Protect data members from access on multiple threads. 5 AutoLock lock_scope(this); 6 7 if (m_BrowserId == browser->GetIdentifier()) { 8 // Free the browser pointer so that the browser can be destroyed. 9 m_Browser = NULL; 10 } 11 12 if (--m_BrowserCount == 0) { 13 // All browser windows have closed. Quit the application message loop. 14 CefQuitMessageLoop(); 15 } 16 }
完整的流程,請參考cefclient例子里對不同平台的處理。
離屏渲染(Off-Screen Rendering)
在離屏渲染模式下,CEF不會創建原生瀏覽器窗口。CEF為宿主程序提供無效的區域和像素緩存區,而宿主程序負責通知鼠標鍵盤以及焦點事件給CEF。離屏渲染目前不支持混合加速,所以性能上可能無法和非離屏渲染相比。離屏瀏覽器將收到和窗口瀏覽器同樣的事件通知,例如前一節介紹的生命周期事件。下面介紹如何使用離屏渲染:
- 實現CefRenderHandler接口。除非特別說明,所有的方法都需要覆寫。
- 調用CefWindowInfo::SetAsOffScreen(),將CefWindowInfo傳遞給CefBrowserHost::CreateBrowser()之前還可以選擇設置CefWindowInfo::SetTransparentPainting()。如果沒有父窗口被傳遞給SetAsOffScreen,則有些類似上下文菜單這樣的功能將不可用。
- CefRenderHandler::GetViewRect方法將被調用以獲得所需要的可視區域。
- CefRenderHandler::OnPaint() 方法將被調用以提供無效區域(臟區域)以及更新過的像素緩存。cefclient程序里使用OpenGL繪制緩存,但你可以使用任何別的繪制技術。
- 可以調用CefBrowserHost::WasResized()方法改變瀏覽器大小。這將導致對GetViewRect()方法的調用,以獲取新的瀏覽器大小,然后調用OnPaint()重新繪制。
- 調用CefBrowserHost::SendXXX()方法通知瀏覽器的鼠標、鍵盤和焦點事件。
- 調用CefBrowserHost::CloseBrowser()銷毀瀏覽器。
使用命令行參數--off-screen-rendering-enabled
運行cefclient,可以測試離屏渲染的效果。
投遞任務(Posting Tasks)
任務(Task)可以通過CefPostTask在一個進程內的不同的線程之間投遞。CefPostTask有一系列的重載方法,詳細內容請參考cef_task.h頭文件。任務將會在被投遞線程的消息循環里異步執行。例如,為了在UI線程上執行MyObject::MyMethod方法,並傳遞兩個參數,代碼如下:
CefPostTask(TID_UI, NewCefRunnableMethod(object, &MyObject::MyMethod, param1, param2));
為了在IO線程在執行MyFunction方法,同時傳遞兩個參數,代碼如下:
CefPostTask(TID_IO, NewCefRunnableFunction(MyFunction, param1, param2));
參考cef_runnable.h頭文件以了解更多關於NewCefRunnable模板方法的細節。
如果宿主程序需要保留一個運行循環的引用,則可以使用CefTaskRunner類。例如,獲取UI線程的任務運行器(task runner),代碼如下:
CefRefPtr<CefTaskRunner> task_runner = CefTaskRunner::GetForThread(TID_UI);
進程間通信(Inter-Process Communication (IPC))
由於CEF3運行在多進程環境下,所以需要提供一個進程間通信機制。CefBrowser和CefFrame對象在Borwser和Render進程里都有代理對象。CefBrowser和CefFrame對象都有一個唯一ID值綁定,便於在兩個進程間定位匹配的代理對象。
處理啟動消息(Process Startup Messages)
為了給所有的Render進程提供一樣的啟動信息,請在Browser進程實現CefBrowserProcessHander::OnRenderProcessThreadCreated()方法。在這里傳入的信息會在Render進程的CefRenderProcessHandler::OnRenderThreadCreated()方法里接受。
處理運行時消息(Process Runtime Messages)
在進程生命周期內,任何時候你都可以通過CefProcessMessage類傳遞進程間消息。這些信息和特定的CefBrowser實例綁定在一起,用戶可以通過CefBrowser::SendProcessMessage()方法發送。進程間消息可以包含任意的狀態信息,用戶可以通過CefProcessMessage::GetArgumentList()獲取。
// Create the message object. CefRefPtr<CefProcessMessage> msg= CefProcessMessage::Create(“my_message”); // Retrieve the argument list object. CefRefPtr<CefListValue> args = msg>GetArgumentList(); // Populate the argument values. args->SetString(0, “my string”); args->SetInt(0, 10); // Send the process message to the render process. // Use PID_BROWSER instead when sending a message to the browser process. browser->SendProcessMessage(PID_RENDERER, msg);
一個從Browser進程發送到Render進程的消息將會在CefRenderProcessHandler::OnProcessMessageReceived()方法里被接收。一個從Render進程發送到Browser進程的消息將會在CefClient::OnProcessMessageReceived()方法里被接收。
bool MyHandler::OnProcessMessageReceived( CefRefPtr<CefBrowser> browser, CefProcessId source_process, CefRefPtr<CefProcessMessage> message) { // Check the message name. const std::string& message_name = message->GetName(); if (message_name == “my_message”) { // Handle the message here... return true; } return false; }
我們可以調用CefFrame::GerIdentifier()獲取CefFrame的ID,並通過進程間消息發送給另一個進程,然后在接收端通過CefBrowser::GetFrame()找到對應的CefFrame。通過這種方式可以將進程間消息和特定的CefFrame聯系在一起。
// Helper macros for splitting and combining the int64 frame ID value. #define MAKE_INT64(int_low, int_high) \ ((int64) (((int) (int_low)) | ((int64) ((int) (int_high))) << 32)) #define LOW_INT(int64_val) ((int) (int64_val)) #define HIGH_INT(int64_val) ((int) (((int64) (int64_val) >> 32) & 0xFFFFFFFFL)) // Sending the frame ID. const int64 frame_id = frame->GetIdentifier(); args->SetInt(0, LOW_INT(frame_id)); args->SetInt(1, HIGH_INT(frame_id)); // Receiving the frame ID. const int64 frame_id = MAKE_INT64(args->GetInt(0), args->GetInt(1)); CefRefPtr<CefFrame> frame = browser->GetFrame(frame_id);
異步JavaScript綁定(Asynchronous JavaScript Bindings)
JavaScript被集成在Render進程,但是需要頻繁和Browser進程交互。 JavaScript API應該被設計成可使用閉包異步執行。
通用消息轉發(Generic Message Router)
從1574版本開始,CEF提供了在Render進程執行的JavaScript和在Browser進程執行的C++代碼之間同步通信的轉發器。應用程序通過C++回調函數(OnBeforeBrowse, OnProcessMessageRecieved, OnContextCreated等)傳遞數據。Render進程支持通用的JavaScript回調函數注冊機制,Browser進程則支持應用程序注冊特定的Handler進行處理。
下面的代碼示例在JavaScript端擴展window對象,添加cefQuery函數:
// Create and send a new query. var request_id = window.cefQuery({ request: 'my_request', persistent: false, onSuccess: function(response) {}, onFailure: function(error_code, error_message) {} }); // Optionally cancel the query. window.cefQueryCancel(request_id);
對應的C++ Handler代碼如下:
1 class Callback : public CefBase { 2 public: 3 /// 4 // Notify the associated JavaScript onSuccess callback that the query has 5 // completed successfully with the specified |response|. 6 /// 7 virtual void Success(const CefString& response) =0; 8 9 /// 10 // Notify the associated JavaScript onFailure callback that the query has 11 // failed with the specified |error_code| and |error_message|. 12 /// 13 virtual void Failure(int error_code, const CefString& error_message) =0; 14 }; 15 16 class Handler { 17 public: 18 /// 19 // Executed when a new query is received. |query_id| uniquely identifies the 20 // query for the life span of the router. Return true to handle the query 21 // or false to propagate the query to other registered handlers, if any. If 22 // no handlers return true from this method then the query will be 23 // automatically canceled with an error code of -1 delivered to the 24 // JavaScript onFailure callback. If this method returns true then a 25 // Callback method must be executed either in this method or asynchronously 26 // to complete the query. 27 /// 28 virtual bool OnQuery(CefRefPtr<CefBrowser> browser, 29 CefRefPtr<CefFrame> frame, 30 int64 query_id, 31 const CefString& request, 32 bool persistent, 33 CefRefPtr<Callback> callback) { 34 return false; 35 } 36 37 /// 38 // Executed when a query has been canceled either explicitly using the 39 // JavaScript cancel function or implicitly due to browser destruction, 40 // navigation or renderer process termination. It will only be called for 41 // the single handler that returned true from OnQuery for the same 42 // |query_id|. No references to the associated Callback object should be 43 // kept after this method is called, nor should any Callback methods be 44 // executed. 45 /// 46 virtual void OnQueryCanceled(CefRefPtr<CefBrowser> browser, 47 CefRefPtr<CefFrame> frame, 48 int64 query_id) {} 49 };
完整的用法請參考wrapper/cef_message_router.h
自定義實現(Custom Implementation)
一個CEF應用程序也可以提供自己的異步JavaScript綁定。典型的實現如下:
- Render進程的JavaScript傳遞一個回調函數。
// In JavaScript register the callback function. app.setMessageCallback('binding_test', function(name, args) { document.getElementById('result').value = "Response: "+args[0]; });
- Render進程的C++端通過一個map持有JavaScript端注冊的回調函數。
// Map of message callbacks. typedef std::map<std::pair<std::string, int>, std::pair<CefRefPtr<CefV8Context>, CefRefPtr<CefV8Value> > > CallbackMap; CallbackMap callback_map_; // In the CefV8Handler::Execute implementation for “setMessageCallback”. if (arguments.size() == 2 && arguments[0]->IsString() && arguments[1]->IsFunction()) { std::string message_name = arguments[0]->GetStringValue(); CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext(); int browser_id = context->GetBrowser()->GetIdentifier(); callback_map_.insert( std::make_pair(std::make_pair(message_name, browser_id), std::make_pair(context, arguments[1]))); }
-
Render進程發送異步進程間通信到Browser進程。
-
Browser進程接收到進程間消息,並處理。
-
Browser進程處理完畢后,發送一個異步進程間消息給Render進程,返回結果。
-
Render進程接收到進程間消息,則調用最開始保存的JavaScript注冊的回調函數處理之。
// Execute the registered JavaScript callback if any. if (!callback_map_.empty()) { const CefString& message_name = message->GetName(); CallbackMap::const_iterator it = callback_map_.find( std::make_pair(message_name.ToString(), browser->GetIdentifier())); if (it != callback_map_.end()) { // Keep a local reference to the objects. The callback may remove itself // from the callback map. CefRefPtr<CefV8Context> context = it->second.first; CefRefPtr<CefV8Value> callback = it->second.second; // Enter the context. context->Enter(); CefV8ValueList arguments; // First argument is the message name. arguments.push_back(CefV8Value::CreateString(message_name)); // Second argument is the list of message arguments. CefRefPtr<CefListValue> list = message->GetArgumentList(); CefRefPtr<CefV8Value> args = CefV8Value::CreateArray(list->GetSize()); SetList(list, args); // Helper function to convert CefListValue to CefV8Value. arguments.push_back(args); // Execute the callback. CefRefPtr<CefV8Value> retval = callback->ExecuteFunction(NULL, arguments); if (retval.get()) { if (retval->IsBool()) handled = retval->GetBoolValue(); } // Exit the context. context->Exit(); } }
- 在CefRenderProcessHandler::OnContextReleased()里釋放JavaScript注冊的回調函數以及其他V8資源。
void MyHandler::OnContextReleased(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context) { // Remove any JavaScript callbacks registered for the context that has been released. if (!callback_map_.empty()) { CallbackMap::iterator it = callback_map_.begin(); for (; it != callback_map_.end();) { if (it->second.first->IsSame(context)) callback_map_.erase(it++); else ++it; } } }
同步請求(Synchronous Requests)
某些特殊場景下,也許會需要在Browser進程和Render進程做進程間同步通信。這應該被盡可能避免,因為這會對Render進程的性能造成負面影響。然而如果你一定要做進程間同步通信,可以考慮使用XMLHttpRequest,XMLHttpRequest在等待Browser進程的網絡響應的時候會等待。Browser進程可以通過自定義scheme Handler或者網絡交互處理XMLHttpRequest。更多細節,請參考Network Layer一節。
網絡層(Network Layer)
默認情況下,CEF3的網絡請求會被宿主程序手工處理。然而CEF3也暴露了一系列網絡相關的函數用以處理網絡請求。
網絡相關的回調函數可在不同線程被調用,因此要注意相關文檔的說明,並對自己的數據進行線程安全保護。
自定義請求(Custom Requests)
通過CefFrame::LoadURL()方法可簡單加載一個url:
browser->GetMainFrame()->LoadURL(some_url);
如果希望發送更復雜的請求,則可以調用CefFrame::LoadRequest()方法。該方法接受一個CefRequest對象作為唯一的參數。
// Create a CefRequest object. CefRefPtr<CefRequest> request = CefRequest::Create(); // Set the request URL. request->SetURL(some_url); // Set the request method. Supported methods include GET, POST, HEAD, DELETE and PUT. request->SetMethod(“POST”); // Optionally specify custom headers. CefRequest::HeaderMap headerMap; headerMap.insert( std::make_pair("X-My-Header", "My Header Value")); request->SetHeaderMap(headerMap); // Optionally specify upload content. // The default “Content-Type” header value is "application/x-www-form-urlencoded". // Set “Content-Type” via the HeaderMap if a different value is desired. const std::string& upload_data = “arg1=val1&arg2=val2”; CefRefPtr<CefPostData> postData = CefPostData::Create(); CefRefPtr<CefPostDataElement> element = CefPostDataElement::Create(); element->SetToBytes(upload_data.size(), upload_data.c_str()); postData->AddElement(element); request->SetPostData(postData);
瀏覽器無關請求(Browser-Independent Requests)
應用程序可以通過CefURLRequest類發送和瀏覽器無關的網絡請求。並實現CefURLRequestClient接口處理響應。CefURLRequest可以在Browser和Render進程被使用。
class MyRequestClient : public CefURLRequestClient { public: MyRequestClient() : upload_total_(0), download_total_(0) {} virtual void OnRequestComplete(CefRefPtr<CefURLRequest> request) OVERRIDE { CefURLRequest::Status status = request->GetRequestStatus(); CefURLRequest::ErrorCode error_code = request->GetRequestError(); CefRefPtr<CefResponse> response = request->GetResponse(); // Do something with the response... } virtual void OnUploadProgress(CefRefPtr<CefURLRequest> request, uint64 current, uint64 total) OVERRIDE { upload_total_ = total; } virtual void OnDownloadProgress(CefRefPtr<CefURLRequest> request, uint64 current, uint64 total) OVERRIDE { download_total_ = total; } virtual void OnDownloadData(CefRefPtr<CefURLRequest> request, const void* data, size_t data_length) OVERRIDE { download_data_ += std::string(static_cast<const char*>(data), data_length); } private: uint64 upload_total_; uint64 download_total_; std::string download_data_; private: IMPLEMENT_REFCOUNTING(MyRequestClient); };
下面的代碼發送一個請求:
// Set up the CefRequest object. CefRefPtr<CefRequest> request = CefRequest::Create(); // Populate |request| as shown above... // Create the client instance. CefRefPtr<MyRequestClient> client = new MyRequestClient(); // Start the request. MyRequestClient callbacks will be executed asynchronously. CefRefPtr<CefURLRequest> url_request = CefURLRequest::Create(request, client.get()); // To cancel the request: url_request->Cancel();
可以通過CefRequest::SetFlags定制請求的行為,這些標志位包括:
- UR_FLAG_SKIP_CACHE 如果設置了該標志位,則處理請求響應時,緩存將被跳過。
- UR_FLAG_ALLOW_CACHED_CREDENTIALS 如果設置了該標志位,則可能會發送cookie並在響應端被保存。同時UR_FLAG_ALLOW_CACHED_CREDENTIALS標志位也必須被設置。
- UR_FLAG_REPORT_UPLOAD_PROGRESS 如果設置了該標志位,則當請求擁有請求體時,上載進度事件將會被觸發。
- UR_FLAG_REPORT_LOAD_TIMING 如果設置了該標志位,則時間信息會被收集。
- UR_FLAG_REPORT_RAW_HEADERS 如果設置了該標志位,則頭部會被發送,並且接收端會被記錄。
- UR_FLAG_NO_DOWNLOAD_DATA 如果設置了該標志位,則CefURLRequestClient::OnDownloadData方法不會被調用。
- UR_FLAG_NO_RETRY_ON_5XX 如果設置了該標志位,則5xx重定向錯誤會被交給相關Observer去處理,而不是自動重試。這個功能目前只能在Browser進程的請求端使用。
例如,為了跳過緩存並不報告下載數據,代碼如下:
request->SetFlags(UR_FLAG_SKIP_CACHE | UR_FLAG_NO_DOWNLOAD_DATA);
請求響應(Request Handling)
CEF3 支持兩種方式處理網絡請求。一種是實現scheme Handler,這種方式允許為特定的(sheme+domain)請求注冊特定的請求響應。另一種是請求攔截,允許處理任意的網絡請求。
注冊自定義scheme(有別於HTTP
,HTTPS
等)可以讓CEF按希望的方式處理請求。例如,如果你希望特定的shceme被當策划那個HTTP一樣處理,則應該注冊一個standard
的scheme。如果你的自定義shceme可被跨域執行,則應該考慮使用使用HTTP scheme代替自定義scheme以避免潛在問題。如果你希望使用自定義scheme,實現CefApp::OnRegisterCustomSchemes回調。
void MyApp::OnRegisterCustomSchemes(CefRefPtr<CefSchemeRegistrar> registrar) { // Register "client" as a standard scheme. registrar->AddCustomScheme("client", true, false, false); }
Scheme響應(Scheme Handler)
通過CefRegisterSchemeHandlerFactory方法注冊一個scheme響應,最好在CefBrowserProcessHandler::OnContextInitialized()方法里調用。例如,你可以注冊一個"client://myapp/"的請求:
CefRegisterSchemeHandlerFactory("client", “myapp”, new MySchemeHandlerFactory());
scheme Handler類可以被用在內置shcme(HTTP,HTTPS等),也可以被用在自定義scheme上。當使用內置shceme,選擇一個對你的應用程序來說唯一的域名。實現CefSchemeHandlerFactory和CefResoureHandler類去處理請求並返回響應數據。可以參考cefclient/sheme_test.h的例子。
1 // Implementation of the factory for creating client request handlers. 2 class MySchemeHandlerFactory : public CefSchemeHandlerFactory { 3 public: 4 virtual CefRefPtr<CefResourceHandler> Create(CefRefPtr<CefBrowser> browser, 5 CefRefPtr<CefFrame> frame, 6 const CefString& scheme_name, 7 CefRefPtr<CefRequest> request) 8 OVERRIDE { 9 // Return a new resource handler instance to handle the request. 10 return new MyResourceHandler(); 11 } 12 13 IMPLEMENT_REFCOUNTING(MySchemeHandlerFactory); 14 }; 15 16 // Implementation of the resource handler for client requests. 17 class MyResourceHandler : public CefResourceHandler { 18 public: 19 MyResourceHandler() {} 20 21 virtual bool ProcessRequest(CefRefPtr<CefRequest> request, 22 CefRefPtr<CefCallback> callback) 23 OVERRIDE { 24 // Evaluate |request| to determine proper handling... 25 // Execute |callback| once header information is available. 26 // Return true to handle the request. 27 return true; 28 } 29 30 virtual void GetResponseHeaders(CefRefPtr<CefResponse> response, 31 int64& response_length, 32 CefString& redirectUrl) OVERRIDE { 33 // Populate the response headers. 34 response->SetMimeType("text/html"); 35 response->SetStatus(200); 36 37 // Specify the resulting response length. 38 response_length = ...; 39 } 40 41 virtual void Cancel() OVERRIDE { 42 // Cancel the response... 43 } 44 45 virtual bool ReadResponse(void* data_out, 46 int bytes_to_read, 47 int& bytes_read, 48 CefRefPtr<CefCallback> callback) 49 OVERRIDE { 50 // Read up to |bytes_to_read| data into |data_out| and set |bytes_read|. 51 // If data isn't immediately available set bytes_read=0 and execute 52 // |callback| asynchronously. 53 // Return true to continue the request or false to complete the request. 54 return …; 55 } 56 57 private: 58 IMPLEMENT_REFCOUNTING(MyResourceHandler); 59 };
如果響應數據類型是已知的,則CefStreamResourceHandler類提供了CefResourceHandler類的默認實現。
// CefStreamResourceHandler is part of the libcef_dll_wrapper project. #include “include/wrapper/cef_stream_resource_handler.h” const std::string& html_content = “<html><body>Hello!</body></html>”; // Create a stream reader for |html_content|. CefRefPtr<CefStreamReader> stream = CefStreamReader::CreateForData( static_cast<void*>(const_cast<char*>(html_content.c_str())), html_content.size()); // Constructor for HTTP status code 200 and no custom response headers. // There’s also a version of the constructor for custom status code and response headers. return new CefStreamResourceHandler("text/html", stream);
請求攔截(Request Interception)
CefRequestHandler::GetResourceHandler()方法支持攔截任意請求。參考client_handler.cpp。
CefRefPtr<CefResourceHandler> MyHandler::GetResourceHandler( CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefRequest> request) { // Evaluate |request| to determine proper handling... if (...) return new MyResourceHandler(); // Return NULL for default handling of the request. return NULL; }
其他回調(Other Callbacks)
CefRequestHander接口還提供了其他回調函數以定制其他網絡相關事件。包括授權、cookie處理、外部協議處理、證書錯誤等。
Proxy Resolution
CEF3使用類似Google Chrome一樣的方式,通過命令行參數傳遞代理配置。
--proxy-server=host:port Specify the HTTP/SOCKS4/SOCKS5 proxy server to use for requests. An individual proxy server is specified using the format: [<proxy-scheme>://]<proxy-host>[:<proxy-port>] Where <proxy-scheme> is the protocol of the proxy server, and is one of: "http", "socks", "socks4", "socks5". If the <proxy-scheme> is omitted, it defaults to "http". Also note that "socks" is equivalent to "socks5". Examples: --proxy-server="foopy:99" Use the HTTP proxy "foopy:99" to load all URLs. --proxy-server="socks://foobar:1080" Use the SOCKS v5 proxy "foobar:1080" to load all URLs. --proxy-server="sock4://foobar:1080" Use the SOCKS v4 proxy "foobar:1080" to load all URLs. --proxy-server="socks5://foobar:66" Use the SOCKS v5 proxy "foobar:66" to load all URLs. It is also possible to specify a separate proxy server for different URL types, by prefixing the proxy server specifier with a URL specifier: Example: --proxy-server="https=proxy1:80;http=socks4://baz:1080" Load https://* URLs using the HTTP proxy "proxy1:80". And load http://* URLs using the SOCKS v4 proxy "baz:1080". --no-proxy-server Disables the proxy server. --proxy-auto-detect Autodetect proxy configuration. --proxy-pac-url=URL Specify proxy autoconfiguration URL.
如果代理請求授權,CefRequestHandler::GetAuthCredentials()回調會被調用。如果isProxy參數為true,則需要返回用戶名和密碼。
bool MyHandler::GetAuthCredentials( CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, bool isProxy, const CefString& host, int port, const CefString& realm, const CefString& scheme, CefRefPtr<CefAuthCallback> callback) { if (isProxy) { // Provide credentials for the proxy server connection. callback->Continue("myuser", "mypass"); return true; } return false; }
網絡內容加載可能會因為代理而有延遲。為了更好的用戶體驗,可以考慮讓你的應用程序先顯示一個閃屏,等內容加載好了再通過meta refresh顯示真實網頁。可以指定--no-proxy-server
禁用代理並做相關測試。代理延遲也可以通過chrome瀏覽器重現,方式是使用命令行傳參:chrome -url=...
.
下載
CEF下載地址:https://cefbuilds.com/和http://opensource.spotify.com/cefbuilds/index.html
根據自己的操作系統選擇對應的版本下載,我們拿Windows 32位來演示操作
在此說一下,網上說這個版本比較穩定,所以建議采用這個版本
這個網站需要翻牆才能訪問,如果你的電腦上沒有安裝翻牆軟件,不要怕,點這里http://pan.baidu.com/s/1jIltcvc,這個軟件是windows版的,至於其他操作系統可以在網上找方法。
翻牆軟件安裝成功后,網頁應該是這樣的
輸入驗證碼之后,就可以下載。
如果不想安裝翻牆軟件的話,可以用我下載好的:http://pan.baidu.com/s/1bo37YGn
下載解壓得到如下文件:
這個時候你就會發現沒有解決方案,網上很多說在文件夾中找到對應vs版本的解決方案,但這個版本沒有,這個時候,需要下載CMake了,這是什么鬼?
CMake是一個跨平台的安裝(編譯)工具,可以用簡單的語句來描述所有平台的安裝(編譯過程)。他能夠輸出各種各樣的makefile或者project文件,能測試編譯器所支持的C++特性,類似UNIX下的automake。
下載地址: https://cmake.org/
使用CMake生成解決方案:
第一步:
Browse Source選擇剛剛解壓的libcef文件夾
Browse Build選擇要把生成的工程放在哪里,我們和Browse Source選擇一樣的路徑
第二步:
選擇后,點擊Finish按鈕
這樣操作完成后,我們點擊Generate生成解決方案
最后我們看一下文件夾下,解決方案生成了
大功告成!!!
在此參考了博覽群書1989,他的博客:http://blog.csdn.net/wangshubo1989/article/details/50167045