我得到幫助的文檔

http://www.cnblogs.com/liulun/archive/2013/03/18/2874276.html
https://github.com/fanfeilong/cefutil/blob/master/doc/CEF%20General%20Usage-zh-cn.md#browser-life-span
http://www.cnblogs.com/himax/p/cef-select-option-wrong-place.html

這是一個翻譯文檔,持續更新中

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開發中涉及到的一般概念。

開始

使用二進制包

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可以不用它自己提供的消息循環,而與已經存在的程序中消息環境集成在一起,有兩種方式可以做到:

  1. 週期性執行CefDoMessageLoopWork()函數,替代調用CefRunMessageLoop()。CefDoMessageLoopWork()的每一次調用,都將執行一次CEF消息循環的單次迭代。需要注意的是,此方法調用次數太少時,CEF消息循環會餓死,將極大的影響Browser的性能,調用次數太頻繁又將影響CPU使用率。

  2. 設置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相關的一些回調以及消息處理的回調。
    更多細節,請參考JavascriptIntegrationInter-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綁定。典型的實現如下:

  1. Render進程的JavaScript傳遞一個回調函數。
// In JavaScript register the callback function.
app.setMessageCallback('binding_test', function(name, args) {
  document.getElementById('result').value = "Response: "+args[0];
});
  1. 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])));
}
  1. Render進程發送異步進程間通信到Browser進程。

  2. Browser進程接收到進程間消息,並處理。

  3. Browser進程處理完畢後,發送一個異步進程間消息給Render進程,返回結果。

  4. 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();
  }
}
  1. 在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=....

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章