天天看點

CEF-概述和常用功能介紹(GeneralUsage翻譯)

1.介紹

https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage.md

翻譯來自:cefutil

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

2.開始

2.1.1.使用二進制包

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二進制包建立簡單應用程式的細節。

2.1.2.從源碼編譯(Building from Source Code)

CEF可以從源碼編譯,使用者可以使用本地編譯系統或者像TeamCity這樣的自動化編譯系統編譯。首先你需要使用svn或者git下載下傳Chromium和CEF的源碼。由于Chromium源碼很大,隻建議在記憶體大于4GB的現代機器上編譯。編譯Chromium和CEF的細節請參考BranchesAndBuilding頁面。

2.1.3.示例應用程式(Sample Application)

cefclient是一個完整的CEF用戶端應用程式示例,并且它的源碼包含在CEF每個二進制釋出包中。使用CEF建立一個新的應用程式,最簡單的方法是先從cefclient應用程式開始,删除你不需要的部分。本文檔中許多示例都是來源于cefclient應用程式。

2.1.4.重要概念(Important Concepts)

在開發基于CEF3的應用程式前,有一些重要的基礎概念應該被了解。

2.1.4.1.C++ 封裝(C++ Wrapper)

libcef 動态連結庫導出 C API 使得使用者不用關心CEF運作庫和基礎代碼。libcef_dll_wrapper 工程把 C API 封裝成 C++ API同時包含在用戶端應用程式工程中,與cefclient一樣,源代碼作為CEF二進制釋出包的一部分共同釋出。C/C++ API的轉換層代碼是由轉換工具自動生成。UsingTheCAPI 頁面描述了如何使用C API。

2.1.4.2.程序(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。

2.1.4.3.線程(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));
           

2.1.4.4.引用計數(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();
           

2.1.4.5.字元串(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);
           

2.1.4.6.指令行參數(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檔案的注釋。

2.1.5.應用程式布局(Application Layout)

應用資源布局依賴于平台,有很大的不同。比如,在Mac OS X上,你的資源布局必須遵循特定的app bundles結構;Window與Linux則更靈活,允許你定制CEF庫檔案與資源檔案所在的位置。為了擷取到特定可以正常工作的示例,你可以從工程的下載下傳頁面下載下傳到一個client壓縮包。每個平台對應的README.txt檔案詳細說明了哪些檔案是可選的,哪些檔案是必須的。

2.1.5.1.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檔案,但是改為從檔案系統加載資源也很容易。

2.1.6.應用程式結構(Application Structure)

每個CEF3應用程式都是相同的結構

  • 提供入口函數,用于初始化CEF、運作子程序執行邏輯或者CEF消息循環。
  • 提供CefApp實作,用于處理程序相關的回調。
  • 提供CefClient實作,用于處理Browser執行個體相關的回調。
  • 執行CefBrowserHost::CreateBrowser()建立一個Browser執行個體,使用CefLifeSpanHandler管理Browser對象生命周期。

2.1.6.1.入口函數(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);
           

2.1.7.單一執行體(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;
}
           

2.1.8.分離子程序執行體(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());
}
           

2.1.9.內建消息循環(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

    測試上述消息模型。

2.1.10.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”配置。

2.1.11.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的頭檔案注釋。

2.1.12.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);
};
           

2.1.13.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);
};
           

2.1.14.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;
}
           

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例子裡對不同平台的處理。

2.1.15.離屏渲染(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,可以測試離屏渲染的效果。

2.1.16.投遞任務(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);
           

2.1.17.程序間通信(Inter-Process Communication (IPC))

由于CEF3運作在多程序環境下,是以需要提供一個程序間通信機制。CefBrowser和CefFrame對象在Borwser和Render程序裡都有代理對象。CefBrowser和CefFrame對象都有一個唯一ID值綁定,便于在兩個程序間定位比對的代理對象。

2.1.18.處理啟動消息(Process Startup Messages)

為了給所有的Render程序提供一樣的啟動資訊,請在Browser程序實作CefBrowserProcessHander::OnRenderProcessThreadCreated()方法。在這裡傳入的資訊會在Render程序的CefRenderProcessHandler::OnRenderThreadCreated()方法裡接受。

2.1.19.處理運作時消息(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);
           

2.1.20.異步JavaScript綁定(Asynchronous JavaScript Bindings)

JavaScript被內建在Render程序,但是需要頻繁和Browser程序互動。 JavaScript API應該被設計成可使用閉包異步執行。

2.1.20.1.通用消息轉發(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

2.1.20.2.自定義實作(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;
    }
  }
}
           

2.1.21.同步請求(Synchronous Requests)

某些特殊場景下,也許會需要在Browser程序和Render程序做程序間同步通信。這應該被盡可能避免,因為這會對Render程序的性能造成負面影響。然而如果你一定要做程序間同步通信,可以考慮使用XMLHttpRequest,XMLHttpRequest在等待Browser程序的網絡響應的時候會等待。Browser程序可以通過自定義scheme Handler或者網絡互動處理XMLHttpRequest。更多細節,請參考Network Layer一節。

2.1.22.網絡層(Network Layer)

預設情況下,CEF3的網絡請求會被宿主程式手工處理。然而CEF3也暴露了一系列網絡相關的函數用以處理網絡請求。

網絡相關的回調函數可在不同線程被調用,是以要注意相關文檔的說明,并對自己的資料進行線程安全保護。

2.1.23.自定義請求(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);
           

2.1.24.浏覽器無關請求(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);
           

2.1.25.請求響應(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);
}
           

2.1.26.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);
           

2.1.27.請求攔截(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;
}
           

2.1.28.其他回調(Other Callbacks)

CefRequestHander接口還提供了其他回調函數以定制其他網絡相關事件。包括授權、cookie處理、外部協定處理、證書錯誤等。

2.1.29.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;
}
           
cef