首页 > 代码库 > CEF3研究(三)

CEF3研究(三)

一、Off-Screen Rendering 脱屏绘制

CEF的脱屏渲染并不创建源生的浏览器窗口,而是CEF提供主应用程序在无效区域和像素buffer里渲染,然后主应用程序通过鼠标、键盘和焦点事件通知CEF。
脱屏渲染现在不支持图层混合加速图层混合加速。脱屏渲染要窗口浏览器一样接受相同的通知,包括生命周期通知等,为了使用脱屏渲染:

  1.  实现CefRenderHandler接口,所有方法是必需的,除非另有指示。
  2. 调用CefWindowInfo::SetAsOffScreen()函数和将CefWindowInfo结构传递给CefBrowserHost::CreateBrowser()之前调用CefWindowInfo::SetTransparentPainting()方法。哪里没有父窗口传递给SetAsOffScreen,那么像Centext菜单功能就不可使用。
  3. CefRenderHandler::GetViewRect()函数获取想要获取视图的矩阵。
  4. CefRenderHandler::OnPaint()被调用,以提供一个无效区域和更新的像素buffer。CefClient应用程序使用OpenGL绘制缓冲。
  5. 调用CefBrowserHost::WasResized()重置浏览器大小。这将导致调用GetViewRect()来检索新尺寸随后调用OnPaint()。
  6. CefBrowserHost::SendXXX()函数通知浏览进程的鼠标、键盘和焦点事件
  7. CefBrowserHost::CloseBrowser()销毁浏览器

二、投递任务

在单进程的不同线程之间可以通过 CefPostTask系列函数投递任务。在目标线程中,收到的任务会以异步方式在消息循环中执行。例如:
在UI线程中执行某个类方法:CefPostTask(TID_UINewCefRunnableMethod(object&MyObject::MyMethodparam1param2));
在UI线程中执行某个方法:CefPostTask(TID_IO, NewCefRunnableFunction(MyFunction, param1, param2));
如果主机应用程序需要获取运行循环的引用,可以使用CefTaskRunner类。 
例如:获取线程的任务运行者:CefRefPtr<CefTaskRunnertask_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(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);
    从浏览进程发送的消息会到达渲染进程的 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))) << 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绑定

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) =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) {}
    };
  • 自定义实现

    基于CEF的应用程序提供异步JaveScript绑定的自定义实现。简单的实现步骤如下:

    1.  在渲染进程中通过回调函数绑定JavaScript

      // In JavaScript register the callback function.
      app.setMessageCallback(‘binding_test‘, function(name, args) {
        document.getElementById(‘result‘).value = "Response: "+args[0];
      });
    2. 在渲染进程中维持回调函数中引用
      
      // 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])));
      }
    3. 渲染过程向浏览器进程发送异步消息IPC过程要求执行工作。

    4. 浏览器进程接受IPC消息并执行工作。
    5. 浏览器完成工作后向渲染进程发送异步IPC消息返回结果
    6. 渲染进程接受到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();
        }
      }
    7. 在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-Typeheader value is "application/x-www-form-urlencoded".
// Set Content-Typevia 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_(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();

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处理、扩展协议、证书错误处理等等

CEF3研究(三)