CEF General Usage(CEF3預覽)
介紹
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)
- 應用程序結構(Application Structure)
- 單一執行體(Single Executable)
- 分離子進程執行體(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)
- 網絡層(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工程使用下面的定義來確保方法在期望的線程中被執行。
#define REQUIRE_UI_THREAD() ASSERT(CefCurrentlyOn(TID_UI));
#define REQUIRE_IO_THREAD() ASSERT(CefCurrentlyOn(TID_IO));
#define REQUIRE_FILE_THREAD() ASSERT(CefCurrentlyOn(TID_FILE));
引用計數(Reference Counting)
所有的框架類從CefBase繼承,實例指針由CefRefPtr管理,CefRefPtr通過調用AddRef()和Release()方法自動管理引用計數。框架類的實現方式如下:
class MyClass : public CefBase {
public:
// Various class methods here...
private:
// Various class members here...
IMPLEMENT_REFCOUNTING(MyClass); // Provides atomic refcounting implementation.
};
// References a MyClass instance
CefRefPtr<MyClass> my_class = new MyClass();
字符串(Strings)
CEF為字符串定義了自己的數據結構。主要是出於以下原因:
- libcef包和宿主程序可能使用不同的運行時,對堆管理的方式也不同。所有的對象,包括字符串,需要確保和申請堆內存使用相同的運行時環境。
- libcef包可以編譯為支持不同的字符串類型(UTF8,UTF16以及WIDE)。默認采用的是UTF16,默認字符集可以通過更改cef_string.h文件中的定義,然后重新編譯來修改。當使用寬字節集的時候,切記字符的長度由當前使用的平台決定。
UTF16字符串結構體示例如下:
typedef struct _cef_string_utf16_t {
char16* str; // Pointer to the string
size_t length; // String length
void (*dtor)(char16* str); // Destructor for freeing the string on the correct heap
} cef_string_utf16_t;
通過typedef來設置常用的字符編碼。
typedef char16 cef_char_t;
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的相互轉換:
std::string str = “Some UTF8 string”;
// Equivalent ways of assigning |str| to |cef_str|. Conversion from UTF8 will occur if necessary.
CefString cef_str(str);
cef_str = str;
cef_str.FromString(str);
// Equivalent ways of assigning |cef_str| to |str|. Conversion to UTF8 will occur if necessary.
str = cef_str;
str = cef_str.ToString();
和std::wstring的相互轉換:
std::wstring str = “Some wide string”;
// Equivalent ways of assigning |str| to |cef_str|. Conversion from wide will occur if necessary.
CefString cef_str(str);
cef_str = str;
cef_str.FromWString(str);
// Equivalent ways of assigning |cef_str| to |str|. Conversion to wide will occur if necessary.
str = cef_str;
str = cef_str.ToWString();
如果是ASCII編碼,使用FromASCII進行賦值:
const char* cstr = “Some ASCII string”;
CefString cef_str;
cef_str.FromASCII(cstr);
一些結構體(比如CefSettings)含有cef_string_t類型的成員,CefString支持直接賦值給這些成員。
CefSettings settings;
const char* path = “/path/to/log.txt”;
// Equivalent assignments.
CefString(&settings.log_file).FromASCII(path);
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庫文件、相關資源與可執行文件放置在同級目錄,文件夾結構大致如下:
Application/
cefclient.exe <= cefclient application executable
libcef.dll <= main CEF library
icudt.dll <= ICU unicode support library
ffmpegsumo.dll <= HTML5 audio/video support library
libEGL.dll, libGLESv2.dll, … <= accelerated compositing support libraries
cef.pak, devtools_resources.pak <= non-localized resources and strings
locales/
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
”指定。
Application/
cefclient <= cefclient application executable
libcef.so <= main CEF library
ffmpegsumo.so <-- HTML5 audio/video support library
cef.pak, devtools_resources.pak <= non-localized resources and strings
locales/
en-US.pak, … <= locale-specific resources and strings
files/
binding.html, … <= cefclient application resources
使用結構體CefSettings可以定制CEF庫文件、資源文件(查看README.txt文件或者本文中CefSettings部分獲取更詳細的信息)。
Mac X平台(Mac OS X)
在Mac X平台上,app bundles委托給了Chromium實現,因此不是很靈活。文件夾結構大致如下:
cefclient.app/
Contents/
Frameworks/
Chromium Embedded Framework.framework/
Libraries/
ffmpegsumo.so <= HTML5 audio/video support library
libcef.dylib <= main CEF library
Resources/
cef.pak, devtools_resources.pak <= non-localized resources and strings
*.png, *.tiff <= Blink image and cursor resources
en.lproj/, … <= locale-specific resources and strings
libplugin_carbon_interpose.dylib <= plugin support library
cefclient Helper.app/
Contents/
Info.plist
MacOS/
cefclient Helper <= helper executable
Pkginfo
cefclient Helper EH.app/
Contents/
Info.plist
MacOS/
cefclient Helper EH <= helper executable
Pkginfo
cefclient Helper NP.app/
Contents/
Info.plist
MacOS/
cefclient Helper NP <= helper executable
Pkginfo
Info.plist
MacOS/
cefclient <= cefclient application executable
Pkginfo
Resources/
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平台則不行。
int main(int argc, char* argv[]) {
// Structure for passing command-line arguments.
// The definition of this structure is platform-specific.
CefMainArgs main_args(argc, argv);
// Optional implementation of the CefApp interface.
CefRefPtr<MyApp> app(new MyApp);
// Execute the sub-process logic, if any. This will either return immediately for the browser
// process or block until the sub-process should exit.
int exit_code = CefExecuteProcess(main_args, app.get());
if (exit_code >= 0) {
// The sub-process terminated, exit now.
return exit_code;
}
// Populate this structure to customize CEF behavior.
CefSettings settings;
// Initialize CEF in the main process.
CefInitialize(main_args, settings, app.get());
// Run the CEF message loop. This will block until CefQuitMessageLoop() is called.
CefRunMessageLoop();
// Shut down CEF.
CefShutdown();
return 0;
}
分離子進程執行體(Separate Sub-Process Executable)
當使用獨立的子進程執行體時,你需要2個分開的可執行工程和2個分開的入口函數。
主程序的入口函數:
// Program entry-point function.
// 程序入口函數
int main(int argc, char* argv[]) {
// Structure for passing command-line arguments.
// The definition of this structure is platform-specific.
// 傳遞命令行參數的結構體。
// 這個結構體的定義與平台相關。
CefMainArgs main_args(argc, argv);
// Optional implementation of the CefApp interface.
// 可選擇性地實現CefApp接口
CefRefPtr<MyApp> app(new MyApp);
// Populate this structure to customize CEF behavior.
// 填充這個結構體,用於定制CEF的行為。
CefSettings settings;
// Specify the path for the sub-process executable.
// 指定子進程的執行路徑
CefString(&settings.browser_subprocess_path).FromASCII(“/path/to/subprocess”);
// Initialize CEF in the main process.
// 在主進程中初始化CEF
CefInitialize(main_args, settings, app.get());
// Run the CEF message loop. This will block until CefQuitMessageLoop() is called.
// 執行消息循環,此時會堵塞,直到CefQuitMessageLoop()函數被調用。
CefRunMessageLoop();
// Shut down CEF.
// 關閉CEF
CefShutdown();
return 0;
}
子進程程序的入口函數:
// Program entry-point function.
// 程序入口函數
int main(int argc, char* argv[]) {
// Structure for passing command-line arguments.
// The definition of this structure is platform-specific.
// 傳遞命令行參數的結構體。
// 這個結構體的定義與平台相關。
CefMainArgs main_args(argc, argv);
// Optional implementation of the CefApp interface.
// 可選擇性地實現CefApp接口
CefRefPtr<MyApp> app(new MyApp);
// Execute the sub-process logic. This will block until the sub-process should exit.
// 執行子進程邏輯,此時會堵塞直到子進程退出。
return CefExecuteProcess(main_args, app.get());
}
集成消息循環(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內容:
// Implementation of the CefStringVisitor interface.
class Visitor : public CefStringVisitor {
public:
Visitor() {}
// Called asynchronously when the HTML contents are available.
virtual void Visit(const CefString& string) OVERRIDE {
// Do something with |string|...
}
IMPLEMENT_REFCOUNTING(Visitor);
};
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子類的例子:
// MyApp implements CefApp and the process-specific interfaces.
class MyApp : public CefApp,
public CefBrowserProcessHandler,
public CefRenderProcessHandler {
public:
MyApp() {}
// CefApp methods. Important to return |this| for the handler callbacks.
virtual void OnBeforeCommandLineProcessing(
const CefString& process_type,
CefRefPtr<CefCommandLine> command_line) {
// Programmatically configure command-line arguments...
}
virtual void OnRegisterCustomSchemes(
CefRefPtr<CefSchemeRegistrar> registrar) OVERRIDE {
// Register custom schemes...
}
virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler()
OVERRIDE { return this; }
virtual CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler()
OVERRIDE { return this; }
// CefBrowserProcessHandler methods.
virtual void OnContextInitialized() OVERRIDE {
// The browser process UI thread has been initialized...
}
virtual void OnRenderProcessThreadCreated(CefRefPtr<CefListValue> extra_info)
OVERRIDE {
// Send startup information to a new render process...
}
// CefRenderProcessHandler methods.
virtual void OnRenderThreadCreated(CefRefPtr<CefListValue> extra_info)
OVERRIDE {
// The render process main thread has been initialized...
// Receive startup information in the new render process...
}
virtual void OnWebKitInitialized(CefRefPtr<ClientApp> app) OVERRIDE {
// WebKit has been initialized, register V8 extensions...
}
virtual void OnBrowserCreated(CefRefPtr<CefBrowser> browser) OVERRIDE {
// Browser created in this render process...
}
virtual void OnBrowserDestroyed(CefRefPtr<CefBrowser> browser) OVERRIDE {
// Browser destroyed in this render process...
}
virtual bool OnBeforeNavigation(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
NavigationType navigation_type,
bool is_redirect) OVERRIDE {
// Allow or block different types of navigation...
}
virtual void OnContextCreated(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefV8Context> context) OVERRIDE {
// JavaScript context created, add V8 bindings here...
}
virtual void OnContextReleased(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefV8Context> context) OVERRIDE {
// JavaScript context released, release V8 references here...
}
virtual bool OnProcessMessageReceived(
CefRefPtr<CefBrowser> browser,
CefProcessId source_process,
CefRefPtr<CefProcessMessage> message) OVERRIDE {
// Handle IPC messages from the browser process...
}
IMPLEMENT_REFCOUNTING(MyApp);
};
CefClient
CefClient提供訪問Browser實例的回調接口。一個CefClient實現可以在任意數量的Browser進程中共享。以下為幾個重要的回調:
- 比如處理Browser的生命周期,右鍵菜單,對話框,通知顯示, 拖曳事件,焦點事件,鍵盤事件等等。如果沒有對某個特定的處理接口進行實現會造成什么影響,請查看cef_client.h文件中相關說明。
- OnProcessMessageReceived在Browser收到Render進程的消息時被調用。更多細節,請參考Inter-Process Communication一節。
CefClient子類的例子:
// MyHandler implements CefClient and a number of other interfaces.
class MyHandler : public CefClient,
public CefContextMenuHandler,
public CefDisplayHandler,
public CefDownloadHandler,
public CefDragHandler,
public CefGeolocationHandler,
public CefKeyboardHandler,
public CefLifeSpanHandler,
public CefLoadHandler,
public CefRequestHandler {
public:
MyHandler();
// CefClient methods. Important to return |this| for the handler callbacks.
virtual CefRefPtr<CefContextMenuHandler> GetContextMenuHandler() OVERRIDE {
return this;
}
virtual CefRefPtr<CefDisplayHandler> GetDisplayHandler() OVERRIDE {
return this;
}
virtual CefRefPtr<CefDownloadHandler> GetDownloadHandler() OVERRIDE {
return this;
}
virtual CefRefPtr<CefDragHandler> GetDragHandler() OVERRIDE {
return this;
}
virtual CefRefPtr<CefGeolocationHandler> GetGeolocationHandler() OVERRIDE {
return this;
}
virtual CefRefPtr<CefKeyboardHandler> GetKeyboardHandler() OVERRIDE {
return this;
}
virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() OVERRIDE {
return this;
}
virtual CefRefPtr<CefLoadHandler> GetLoadHandler() OVERRIDE {
return this;
}
virtual CefRefPtr<CefRequestHandler> GetRequestHandler() OVERRIDE {
return this;
}
virtual bool OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
CefProcessId source_process,
CefRefPtr<CefProcessMessage> message)
OVERRIDE {
// Handle IPC messages from the render process...
}
// CefContextMenuHandler methods
virtual void OnBeforeContextMenu(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefContextMenuParams> params,
CefRefPtr<CefMenuModel> model) OVERRIDE {
// Customize the context menu...
}
virtual bool OnContextMenuCommand(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefContextMenuParams> params,
int command_id,
EventFlags event_flags) OVERRIDE {
// Handle a context menu command...
}
// CefDisplayHandler methods
virtual void OnLoadingStateChange(CefRefPtr<CefBrowser> browser,
bool isLoading,
bool canGoBack,
bool canGoForward) OVERRIDE {
// Update UI for browser state...
}
virtual void OnAddressChange(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
const CefString& url) OVERRIDE {
// Update the URL in the address bar...
}
virtual void OnTitleChange(CefRefPtr<CefBrowser> browser,
const CefString& title) OVERRIDE {
// Update the browser window title...
}
virtual bool OnConsoleMessage(CefRefPtr<CefBrowser> browser,
const CefString& message,
const CefString& source,
int line) OVERRIDE {
// Log a console message...
}
// CefDownloadHandler methods
virtual void OnBeforeDownload(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefDownloadItem> download_item,
const CefString& suggested_name,
CefRefPtr<CefBeforeDownloadCallback> callback) OVERRIDE {
// Specify a file path or cancel the download...
}
virtual void OnDownloadUpdated(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefDownloadItem> download_item,
CefRefPtr<CefDownloadItemCallback> callback) OVERRIDE {
// Update the download status...
}
// CefDragHandler methods
virtual bool OnDragEnter(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefDragData> dragData,
DragOperationsMask mask) OVERRIDE {
// Allow or deny drag events...
}
// CefGeolocationHandler methods
virtual void OnRequestGeolocationPermission(
CefRefPtr<CefBrowser> browser,
const CefString& requesting_url,
int request_id,
CefRefPtr<CefGeolocationCallback> callback) OVERRIDE {
// Allow or deny geolocation API access...
}
// CefKeyboardHandler methods
virtual bool OnPreKeyEvent(CefRefPtr<CefBrowser> browser,
const CefKeyEvent& event,
CefEventHandle os_event,
bool* is_keyboard_shortcut) OVERRIDE {
// Perform custom handling of key events...
}
// CefLifeSpanHandler methods
virtual bool OnBeforePopup(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
const CefString& target_url,
const CefString& target_frame_name,
const CefPopupFeatures& popupFeatures,
CefWindowInfo& windowInfo,
CefRefPtr<CefClient>& client,
CefBrowserSettings& settings,
bool* no_javascript_access) OVERRIDE {
// Allow or block popup windows, customize popup window creation...
}
virtual void OnAfterCreated(CefRefPtr<CefBrowser> browser) OVERRIDE {
// Browser window created successfully...
}
virtual bool DoClose(CefRefPtr<CefBrowser> browser) OVERRIDE {
// Allow or block browser window close...
}
virtual void OnBeforeClose(CefRefPtr<CefBrowser> browser) OVERRIDE {
// Browser window is closed, perform cleanup...
}
// CefLoadHandler methods
virtual void OnLoadStart(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame) OVERRIDE {
// A frame has started loading content...
}
virtual void OnLoadEnd(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int httpStatusCode) OVERRIDE {
// A frame has finished loading content...
}
virtual void OnLoadError(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
ErrorCode errorCode,
const CefString& errorText,
const CefString& failedUrl) OVERRIDE {
// A frame has failed to load content...
}
virtual void OnRenderProcessTerminated(CefRefPtr<CefBrowser> browser,
TerminationStatus status) OVERRIDE {
// A render process has crashed...
}
// CefRequestHandler methods
virtual CefRefPtr<CefResourceHandler> GetResourceHandler(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request) OVERRIDE {
// Optionally intercept resource requests...
}
virtual bool OnQuotaRequest(CefRefPtr<CefBrowser> browser,
const CefString& origin_url,
int64 new_size,
CefRefPtr<CefQuotaCallback> callback) OVERRIDE {
// Allow or block quota requests...
}
virtual void OnProtocolExecution(CefRefPtr<CefBrowser> browser,
const CefString& url,
bool& allow_os_execution) OVERRIDE {
// Handle execution of external protocols...
}
IMPLEMENT_REFCOUNTING(MyHandler);
};
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.
// 定義的結構體與平台相關
CefWindowInfo info;
// On Windows for example...
info.SetAsChild(parent_hwnd, client_rect);
// Customize this structure to control browser behavior.
CefBrowserSettings settings;
// CefClient implementation.
CefRefPtr<MyClient> client(new MyClient);
// Create the browser asynchronously. Initially loads the Google URL.
CefBrowserHost::CreateBrowser(info, client.get(), “http://www.google.com”, settings);
The CefLifeSpanHandler class provides the callbacks necessary for managing browser life span. Below is an extract of the relevant methods and members.
CefLifeSpanHandler 類提供管理 Browser生命周期必需的回調。以下為相關方法和成員。
class MyClient : public CefClient,
public CefLifeSpanHandler,
... {
// CefClient methods.
virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() OVERRIDE {
return this;
}
// CefLifeSpanHandler methods.
void OnAfterCreated(CefRefPtr<CefBrowser> browser) OVERRIDE;
bool DoClose(CefRefPtr<CefBrowser> browser) OVERRIDE;
void OnBeforeClose(CefRefPtr<CefBrowser> browser) OVERRIDE;
// Member accessors.
CefRefPtr<CefBrowser> GetBrower() { return m_Browser; }
bool IsClosing() { return m_bIsClosing; }
private:
CefRefPtr<CefBrowser> m_Browser;
int m_BrowserId;
int m_BrowserCount;
bool m_bIsClosing;
IMPLEMENT_REFCOUNTING(MyHandler);
IMPLEMENT_LOCKING(MyHandler);
};
當Browser對象創建后OnAfterCreated() 方法立即執行。宿主程序可以用這個方法來保持對Browser對象的引用。
void MyClient::OnAfterCreated(CefRefPtr<CefBrowser> browser) {
// Must be executed on the UI thread.
REQUIRE_UI_THREAD();
// Protect data members from access on multiple threads.
AutoLock lock_scope(this);
if (!m_Browser.get()) {
// Keep a reference to the main browser.
m_Browser = browser;
m_BrowserId = browser->GetIdentifier();
}
// Keep track of how many browsers currently exist.
m_BrowserCount++;
}
執行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消息:
case WM_CLOSE:
if (g_handler.get() && !g_handler->IsClosing()) {
CefRefPtr<CefBrowser> browser = g_handler->GetBrowser();
if (browser.get()) {
// 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);
// Cancel the close.
return 0;
}
}
// Allow the close.
break;
case WM_DESTROY:
// Quitting CEF is handled in MyHandler::OnBeforeClose().
return 0;
}
Linux平台下,處理delete_event
信號:
gboolean delete_event(GtkWidget* widget, GdkEvent* event,
GtkWindow* window) {
if (g_handler.get() && !g_handler->IsClosing()) {
CefRefPtr<CefBrowser> browser = g_handler->GetBrowser();
if (browser.get()) {
// 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);
// Cancel the close.
return TRUE;
}
}
// Allow the close.
return FALSE;
}
MacOS X平台下,處理windowShouldClose選擇器:
// Called when the window is about to close. Perform the self-destruction
// sequence by getting rid of the window. By returning YES, we allow the window
// to be removed from the screen.
- (BOOL)windowShouldClose:(id)window {
if (g_handler.get() && !g_handler->IsClosing()) {
CefRefPtr<CefBrowser> browser = g_handler->GetBrowser();
if (browser.get()) {
// 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);
// Cancel the close.
return NO;
}
}
// Try to make the window go away.
[window autorelease];
// Clean ourselves up after clearing the stack of anything that might have the
// window on it.
[self performSelectorOnMainThread:@selector(cleanup:)
withObject:window
waitUntilDone:NO];
// Allow the close.
return YES;
}
DoClose方法設置m_blsClosing 標志位為true,並返回false以再次發送操作系統的關閉事件。
bool MyClient::DoClose(CefRefPtr<CefBrowser> browser) {
// Must be executed on the UI thread.
REQUIRE_UI_THREAD();
// Protect data members from access on multiple threads.
AutoLock lock_scope(this);
// Closing the main window requires special handling. See the DoClose()
// documentation in the CEF header for a detailed description of this
// process.
if (m_BrowserId == browser->GetIdentifier()) {
// Notify the browser that the parent window is about to close.
browser->GetHost()->ParentWindowWillClose();
// Set a flag to indicate that the window close should be allowed.
m_bIsClosing = true;
}
// Allow the close. For windowed browsers this will result in the OS close
// event being sent.
return false;
}
當操作系統捕捉到第二次關閉事件,它才會允許父窗口真正關閉。該動作會先觸發OnBeforeClose()回調,請在該回調里釋放所有對瀏覽器對象的引用。
void MyHandler::OnBeforeClose(CefRefPtr<CefBrowser> browser) {
// Must be executed on the UI thread.
REQUIRE_UI_THREAD();
// Protect data members from access on multiple threads.
AutoLock lock_scope(this);
if (m_BrowserId == browser->GetIdentifier()) {
// Free the browser pointer so that the browser can be destroyed.
m_Browser = NULL;
}
if (--m_BrowserCount == 0) {
// All browser windows have closed. Quit the application message loop.
CefQuitMessageLoop();
}
}
完整的流程,請參考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代碼如下:
class Callback : public CefBase {
public:
///
// Notify the associated JavaScript onSuccess callback that the query has
// completed successfully with the specified |response|.
///
virtual void Success(const CefString& response) =0;
///
// Notify the associated JavaScript onFailure callback that the query has
// failed with the specified |error_code| and |error_message|.
///
virtual void Failure(int error_code, const CefString& error_message) =0;
};
class Handler {
public:
///
// Executed when a new query is received. |query_id| uniquely identifies the
// query for the life span of the router. Return true to handle the query
// or false to propagate the query to other registered handlers, if any. If
// no handlers return true from this method then the query will be
// automatically canceled with an error code of -1 delivered to the
// JavaScript onFailure callback. If this method returns true then a
// Callback method must be executed either in this method or asynchronously
// to complete the query.
///
virtual bool OnQuery(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int64 query_id,
const CefString& request,
bool persistent,
CefRefPtr<Callback> callback) {
return false;
}
///
// Executed when a query has been canceled either explicitly using the
// JavaScript cancel function or implicitly due to browser destruction,
// navigation or renderer process termination. It will only be called for
// the single handler that returned true from OnQuery for the same
// |query_id|. No references to the associated Callback object should be
// kept after this method is called, nor should any Callback methods be
// executed.
///
virtual void OnQueryCanceled(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int64 query_id) {}
};
完整的用法請參考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的例子。
// Implementation of the factory for creating client request handlers.
class MySchemeHandlerFactory : public CefSchemeHandlerFactory {
public:
virtual CefRefPtr<CefResourceHandler> Create(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
const CefString& scheme_name,
CefRefPtr<CefRequest> request)
OVERRIDE {
// Return a new resource handler instance to handle the request.
return new MyResourceHandler();
}
IMPLEMENT_REFCOUNTING(MySchemeHandlerFactory);
};
// Implementation of the resource handler for client requests.
class MyResourceHandler : public CefResourceHandler {
public:
MyResourceHandler() {}
virtual bool ProcessRequest(CefRefPtr<CefRequest> request,
CefRefPtr<CefCallback> callback)
OVERRIDE {
// Evaluate |request| to determine proper handling...
// Execute |callback| once header information is available.
// Return true to handle the request.
return true;
}
virtual void GetResponseHeaders(CefRefPtr<CefResponse> response,
int64& response_length,
CefString& redirectUrl) OVERRIDE {
// Populate the response headers.
response->SetMimeType("text/html");
response->SetStatus(200);
// Specify the resulting response length.
response_length = ...;
}
virtual void Cancel() OVERRIDE {
// Cancel the response...
}
virtual bool ReadResponse(void* data_out,
int bytes_to_read,
int& bytes_read,
CefRefPtr<CefCallback> callback)
OVERRIDE {
// Read up to |bytes_to_read| data into |data_out| and set |bytes_read|.
// If data isn't immediately available set bytes_read=0 and execute
// |callback| asynchronously.
// Return true to continue the request or false to complete the request.
return …;
}
private:
IMPLEMENT_REFCOUNTING(MyResourceHandler);
};
如果響應數據類型是已知的,則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=...
.