天天看點

(轉)CEF3研究

一、Off-Screen Rendering 脫屏繪制

CEF的脫屏渲染并不建立源生的浏覽器視窗,而是CEF提供主應用程式在無效區域和像素buffer裡渲染,然後主應用程式通過滑鼠、鍵盤和焦點事件通知CEF。

脫屏渲染現在不支援圖層混合加速圖層混合加速。脫屏渲染要視窗浏覽器一樣接受相同的通知,包括生命周期通知等,為了使用脫屏渲染:

實作CefRenderHandler接口,所有方法是必需的,除非另有訓示。

調用CefWindowInfo::SetAsOffScreen()函數和将CefWindowInfo結構傳遞給CefBrowserHost::CreateBrowser()之前調用CefWindowInfo::SetTransparentPainting()方法。哪裡沒有父視窗傳遞給SetAsOffScreen,那麼像Centext菜單功能就不可使用。

CefRenderHandler::GetViewRect()函數擷取想要擷取視圖的矩陣。

CefRenderHandler::OnPaint()被調用,以提供一個無效區域和更新的像素buffer。CefClient應用程式使用OpenGL繪制緩沖。

調用CefBrowserHost::WasResized()重置浏覽器大小。這将導緻調用GetViewRect()來檢索新尺寸随後調用OnPaint()。

CefBrowserHost::SendXXX()函數通知浏覽程序的滑鼠、鍵盤和焦點事件

CefBrowserHost::CloseBrowser()銷毀浏覽器

二、投遞任務

在單程序的不同線程之間可以通過 CefPostTask系列函數投遞任務。在目标線程中,收到的任務會以異步方式在消息循環中執行。例如:

在UI線程中執行某個類方法:CefPostTask(TID_UI, NewCefRunnableMethod(object, &MyObject::MyMethod, param1, param2));

在UI線程中執行某個方法:CefPostTask(TID_IO, NewCefRunnableFunction(MyFunction, param1, param2));

如果主機應用程式需要擷取運作循環的引用,可以使用CefTaskRunner類。

例如:擷取線程的任務運作者:CefRefPtr task_runner = CefTaskRunner::GetForThread(TID_UI);

三、程序間通信

由于CEF3以多程序方式運作,需要在不同程序之間提供通信方式。CefBrowsert和CefFrame對象分别浏覽程序和渲染程序,每個CefBrowser和CefFrame對象還有一個與之關聯的惟一的ID值,将比對兩邊邊界過程

程序啟動消息 為所有渲染程序提供相同的資訊在啟動浏覽器的過程中實作CefBrowserProcessHandler::OnRenderProcessThreadCreated(),在渲染程序中傳遞啟動消息用CefRenderProcessHandler::OnRenderThreadCreated()

程序運作消息 在程序生命周期的任何時刻使用程序消息通過CefProcessMessage類傳遞消息。這些消息與一個特定的浏覽器執行個體相關聯并通過CefBrowser::SendProcessMessage()函數發送。程序消息可通過CefProcessMessage::GetArgumentList()函數傳遞狀态資訊。

如:

CefRefPtr<CefProcessMessage> msg= CefProcessMessage::Create(“my_message”);

// Retrieve the argument list object.
CefRefPtr<CefListValue> args = msg>GetArgumentList();

// Populate the argument values.
args->SetString(, “my string”);
args->SetInt(, );

// 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);
           

從浏覽程序發送的消息會到達渲染程序的 CefRenderProcessHandler::OnProcessMessageReceived()函數

從渲染程序發送的消息會到達浏覽程序的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::GetIdentifier()函數擷取視窗的唯一ID,在接受程序中擷取唯一ID的視窗對象使用CefBrowser::GetFrame()函數。如:

// 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))) << ))
#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(, LOW_INT(frame_id));
args->SetInt(, HIGH_INT(frame_id));

// Receiving the frame ID.
const int64 frame_id = MAKE_INT64(args->GetInt(), args->GetInt());
CefRefPtr<CefFrame> frame = browser->GetFrame(frame_id);
           

四、異步JavaScript綁定

JavaScript通信是在渲染程序中實作,在需要頻繁的和浏覽程序通信。JaveScript接口使用關閉和提示本身應該以異步方式設計。

通用的消息路由

CEF在渲染程序中運作JaveScript和浏覽程序中運作C++之間提供了一個通用的異步路由。應用程式從标準的C++回調((OnBeforeBrowse, OnProcessMessageRecieved, OnContextCreated, etc) 函數中通過路由傳參的方式互動。渲染這邊的路由支援通用的Javascript回調的注冊和執行。然而浏覽這邊路由能過一個或多少應用程式提供的處理者執行個體來支援應用程式特定邏輯。

JavaScript綁定方式:

// 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++處理者:

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) =;

  ///
  // 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) =;
};

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) {}
};
           

自定義實作

基于CEF的應用程式提供異步JaveScript綁定的自定義實作。簡單的實作步驟如下:

在渲染程序中通過回調函數綁定JavaScript

// In JavaScript register the callback function.
app.setMessageCallback('binding_test', function(name, args) {
  document.getElementById('result').value = "Response: "+args[];
});
           

在渲染程序中維持回調函數中引用

// 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() ==  && arguments[]->IsString() &&
    arguments[]->IsFunction()) {
  std::string message_name = arguments[]->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[])));
}
           

渲染過程向浏覽器程序發送異步消息IPC過程要求執行工作。

浏覽器程序接受IPC消息并執行工作。

浏覽器完成工作後向渲染程序發送異步IPC消息傳回結果

渲染程序接受到IPC消息并執行回調函數處理結果

// Execute the registered JavaScript callback if any.
if (!callback_map_.empty()) {
  const CefString& message_name = message->GetName();
  CallbackMap::const_iterator it = callback_map_.find(
      std::make_pair(message_name.ToString(),
                     browser->GetIdentifier()));
  if (it != callback_map_.end()) {
    // Keep a local reference to the objects. The callback may remove itself
    // from the callback map.
    CefRefPtr<CefV8Context> context = it->second.first;
    CefRefPtr<CefV8Value> callback = it->second.second;

    // Enter the context.
    context->Enter();

    CefV8ValueList arguments;

    // First argument is the message name.
    arguments.push_back(CefV8Value::CreateString(message_name));

    // Second argument is the list of message arguments.
    CefRefPtr<CefListValue> list = message->GetArgumentList();
    CefRefPtr<CefV8Value> args = CefV8Value::CreateArray(list->GetSize());
    SetList(list, args);  // Helper function to convert CefListValue to CefV8Value.
    arguments.push_back(args);

    // Execute the callback.
    CefRefPtr<CefV8Value> retval = callback->ExecuteFunction(NULL, arguments);
    if (retval.get()) {
      if (retval->IsBool())
        handled = retval->GetBoolValue();
    }

    // Exit the context.
    context->Exit();
  }
}
在CefRenderProcessHandler::OnContextReleased()函數中釋放任何V8所關聯的Context
           

五、同步請求

在浏覽程序和渲染程序中很少使用同步通信。無論什麼時候應該盡可能避免使用,因為會在渲染程序中影響性能。如裡實在需要同步通信可考慮使用XMLHttpRequests。

六、網絡層

CEF3預設網絡請求處理的方式對主機應用程式是透明的,應用程式靠近CEF3網絡層的關系會更多的暴露與網絡相關的功能。在不同的線程上會引用不同的網絡調用。是以一定要注意文檔,妥善保護您的資料成員。

七、自定義請求

在浏覽程序視窗中通過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);
           

八、獨立于浏覽器的請求

應用程式可以通過CefURLRequest類發送不與某個浏覽器相關聯的網絡請求。實作CefURLRequestClient接口以處理響應結果。

CefURLRequest可在浏覽程序和渲染程序中使用。

class MyRequestClient : public CefURLRequestClient {
 public:
  MyRequestClient()
    : upload_total_(),
      download_total_() {}

  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();
           

CefURLRequest制定的請求還可以通過CefRequest::SetFlags()函數指定自定義行為:

UR_FLAG_SKIP_CACHE 當處理請求時,如果設定了緩存就跳過。

UR_FLAG_ALLOW_CACHED_CREDENTIALS 如果設定cookie可以發送請求和儲存從響應。此必須被設定

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 如果設定5 xx重定向錯誤将被傳遞到觀察者,而不是自動重試。這個目前僅适用于來自浏覽器的請求過程。

如:request->SetFlags(UR_FLAG_SKIP_CACHE | UR_FLAG_NO_DOWNLOAD_DATA);

九、請求處理

在應用程式中CEF3支援兩種方法來處理網絡請求。計劃處理程式方法允許注冊的處理程式請求針對一個特定的起源(方案+域),請求攔截方法允許任意請求的處理在應用程式自由裁量權。使用HTTP方案而不是定制的方案,以避免一系列潛在問題。

如果選擇使用自定義方案,你必須向CEF注冊,如果想自定義方案有HTTP相同的行為,那麼應該想标準方案一樣注冊。如果打算執行跨域請求其他方案或通過XMLHttpRequst發送POST請求到自定義方案中處理,那麼應用使用HTTP方案來代替自定義方案以避免潛在的問題。哪裡希望自定義屬性通過cefApp::OnRegisterCustomSchemes()回調函數注冊并在所有程序中實作。

void MyApp::OnRegisterCustomSchemes(CefRefPtr<CefSchemeRegistrar> registrar)

{ // Register "client" as a standard scheme.

registrar->AddCustomScheme("client", true, false, false);

}
           

十、Scheme Handler

Scheme Handler通過CefRegisterSchemeHandlerFactory()函數注冊,CefBrowserProcessHandler::OnContextInitialized()是此函數的最好調用的地方。

CefRegisterSchemeHandlerFactory("client", “myapp”, new MySchemeHandlerFactory());
           

Handler是用在内置方案和自定義方案。當使用内置方案時選擇一個域名唯一辨別應用程式。實作CefSchemeHandlerFactory和CefResourceHandler類處理請求和提供響應資料。如果使用自定義方案不要忘記實作CefApp::OnRegisterCustomSchemes函數。

十一、請求攔截

CefRequestHandler::GetResourceHandler()函數支援任意請求的攔截。使用與CefResourceHandler同樣的類處理方法。如果使用自定義方案不要忘記使用 CefApp::OnRegisterCustomSchemes函數。

十二、Other Callbacks

CefRequestHandler接口處理各種各樣的與網絡相關的事件,包括認證、cookie處理、擴充協定、證書錯誤處理等等

cef