開發環境:
VS2015+Qt5.9
關于CEF
CEF全稱是Chromium Embedded Framework,它是Chromium的Content API的封裝庫,基于Google Chromium 的開源項目,而Google Chromium項目主要是為Google Chrome應用開發的,而CEF的目标則是為第三方應用提供可嵌入浏覽器支援
主要組成分為
Chromium:基礎,網絡堆棧,線程,消息機制,log,程序控制,生成Web browser。
WebKit:提供DOM解析,布局,事件處理,渲染,HTML5JS的API。
V8:JS引擎。
Skia:2D圖形庫。
Angle:3D圖形轉換,和DirectX有關。
目前CEF分為CEF1,CEF2,CEF3,其中前者使用的是單程序架構,後兩者是多程序架構。
在開發使用中一般都是基于CEF3開發。
CEF3是基于Chomuim Content API多程序構架的下一代CEF,擁有下列優勢:
1.改進的性能和穩定性(JavaScript和插件在一個獨立的程序内執行)。
2.支援Retina顯示器。
3.支援WebGL和3D CSS的GPU加速。
4.類似WebRTC和語音輸入這樣的前衛特性。
5.通過DevTools遠端調試協定以及ChromeDriver2提供更好的自動化UI測試。
6.更快獲得目前以及未來的Web特性和标準的能力。
CEF使用(QCef封裝)
單一執行體(Single Executable)
以windows平台為例子
在使用CEF3的時候,需要在主程序中啟動CEF
CefEnableHighDPISupport();
//入口函數(Entry-Point Function)
HINSTANCE hInstance = static_cast<HINSTANCE>(GetModuleHandle(nullptr));
CefMainArgs main_args(hInstance);
void* sandbox_info = nullptr; //沙盒資訊為空
CefRefPtr<MyRenderProcessHandler> app(new MyRenderProcessHandler);//處理程序相關的回調。
int exit_code = CefExecuteProcess(main_args, app.get(), sandbox_info);
if (exit_code >= 0) {
// 子流程已經完成,傳回
return exit_code;
}
CefSettings settings;
settings.no_sandbox = true; //關閉沙盒模式
settings.multi_threaded_message_loop = true; //多線程消息循環
settings.log_severity = LOGSEVERITY_DISABLE;//日志
//settings.windowless_rendering_enabled = true; //開啟離屏模式
CefInitialize(main_args, settings, app_r, sandbox_info);
//啟動CEF消息循環,運作後會阻塞到CefQuitMessageLoop()被喚醒
CefRunMessageLoop();
//關閉CEF
CefShutdown();
CEF3常用類和接口
一:CefClient:回調管理類
CefClient提供通路browser-instance-specific的回調接口,單執行個體CefClient可以共數任意數量的浏覽器程序。
比如處理Browser的生命周期,右鍵菜單,對話框,通知顯示, 拖曳事件,焦點事件,
鍵盤事件等等。如果沒有對某個特定的處理接口進行實作會造成什麼影響,請檢視
cef_client.h檔案中相關說明。
舉例代碼說明(目前小編用到的開發代碼)
class CefBrowserHandler: public QObject,
public CefClient,
public CefDisplayHandler,
public CefLifeSpanHandler,
public CefFocusHandler,
public CefLoadHandler//,
//public CefRenderHandler //離屏渲染(關于離屏渲染,後續介紹)
{
Q_OBJECT
public:
CefBrowserHandler();
public:
// CefClient methods:
virtual CefRefPtr<CefDisplayHandler> GetDisplayHandler() OVERRIDE { return this;}
virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() OVERRIDE { return this;}
virtual CefRefPtr<CefLoadHandler> GetLoadHandler() OVERRIDE { return this; }
virtual CefRefPtr<CefFocusHandler> GetFocusHandler() OVERRIDE { return this; }
//virtual CefRefPtr<CefRenderHandler> GetRenderHandler() OVERRIDE { return this; }
// CefLifeSpanHandler methods:
virtual void OnAfterCreated(CefRefPtr<CefBrowser> browser) OVERRIDE;
virtual bool DoClose(CefRefPtr<CefBrowser> browser) OVERRIDE;
virtual void OnBeforeClose(CefRefPtr<CefBrowser> browser) OVERRIDE;
virtual bool OnBeforePopup(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
const CefString& target_url,
const CefString& target_frame_name,
WindowOpenDisposition target_disposition,
bool user_gesture,
const CefPopupFeatures& popupFeatures,
CefWindowInfo& windowInfo,
CefRefPtr<CefClient>& client,
CefBrowserSettings& settings,
bool* no_javascript_access)OVERRIDE;
//用來發送一些網頁處理的信号
// CefLoadHandler methods:
virtual void OnLoadError(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
ErrorCode errorCode,
const CefString& errorText,
const CefString& failedUrl) OVERRIDE;
virtual void OnLoadingStateChange(CefRefPtr<CefBrowser> browser,bool isLoading,bool canGoBack,bool canGoForward) OVERRIDE;
virtual void OnLoadStart(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, TransitionType transition_type) OVERRIDE;
virtual void OnLoadEnd(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, int httpStatusCode) OVERRIDE;
// CefFocusHandler methods:
virtual void OnGotFocus(CefRefPtr<CefBrowser> browser) OVERRIDE;
virtual bool OnProcessMessageReceived(CefRefPtr<CefBrowser> browser, CefProcessId source_process, CefRefPtr<CefProcessMessage> message);
// Member accessors.
void CloseAllBrowsers(bool forceClose);
CefRefPtr<CefBrowser> GetBrower() { return m_Browser; }
bool IsClosing() { return m_bIsClosing; }
// CefRenderHandler methods:
/*virtual bool GetViewRect(CefRefPtr<CefBrowser> browser, CefRect& rect) OVERRIDE;
virtual void OnPaint(CefRefPtr<CefBrowser> browser,
PaintElementType type,
const RectList& dirtyRects,
const void* buffer,
int width,
int height) OVERRIDE;
virtual bool GetScreenPoint(CefRefPtr<CefBrowser> browser,
int viewX,
int viewY,
int& screenX,
int& screenY) OVERRIDE;
virtual void OnCursorChange(CefRefPtr<CefBrowser> browser,
CefCursorHandle cursor,
CursorType type,
const CefCursorInfo& custom_cursor_info) OVERRIDE;*/
signals:
void loadStarted();
void loadFinished(bool loadSuccess);
void browserCreated();
void urlChanged(const QString& url);
void titleChanged(const QString& title);
void loadingStateChanged(bool isLoading, bool canGoBack, bool canGoForward);
void webViewGotFocus();
void recvRenderMsg(const QString& msg);
private:
CefRefPtr<CefBrowser> m_Browser;
int m_BrowserId;
int m_BrowserCount;
bool m_bIsClosing;
typedef std::list<CefRefPtr<CefBrowser>> BrowserList;
BrowserList _browserList;
IMPLEMENT_REFCOUNTING(CefBrowserHandler);
IMPLEMENT_LOCKING(CefBrowserHandler);
};
1.Browser的生命周期從執行 CefBrowserHost::CreateBrowser() 或者CefBrowserHost::CreateBrowserSync()開始
建立代碼,以windows平台為例子
//自定義此結構體來管理浏覽器行為
CefBrowserSettings cSettings;
cSettings.file_access_from_file_urls = STATE_ENABLED; //url檔案能否通路其他url檔案
cSettings.universal_access_from_file_urls = STATE_ENABLED; //url檔案能否通路所有url
cSettings.javascript_access_clipboard = STATE_ENABLED; //js是否可以通路剪貼闆
cSettings.plugins = STATE_DISABLED; //是否加載插件
//cSettings.windowless_frame_rate = 60; //設定幀率,預設值是30
//根據視窗id來确定浏覽器渲染位置
CefWindowInfo info;
CefWindowHandle hWnd = (CefWindowHandle)this->winId();
RECT rect;
rect.left = 0;
rect.top = 0;
rect.right = this->width()*qApp->devicePixelRatio();
rect.bottom = this->height()*qApp->devicePixelRatio();
info.SetAsChild(hWnd, rect);
QString url = "www.baidu.com";
//初始化CefClient子類,其中CefLifeSpanHandler類提供了管理Browser生命周期所必需的回調
CefRefPtr<CefBrowserHandler> browserHandler = GetBrowserHandler(browserHandlerIndex);
//建立浏覽器
CefBrowserHost::CreateBrowser(info, browserHandler.get(), CefString(url.toStdWString()), _settings, NULL);
2.當Browser對象建立後OnAfterCreated() 方法立即執行,宿主程式可以用這個方法來保持對Browser對象的引用。
void CefBrowserHandler::OnAfterCreated(CefRefPtr<CefBrowser> browser)
{
CEF_REQUIRE_UI_THREAD();
//保護成員不受多線程上的通路
AutoLock lock_scope(this);
if (!_browser.get()) {
// 建立浏覽器成功後彈出浏覽器主視窗
_browser = browser;
_browserId = browser->GetIdentifier();
//發送信号,處理某些業務邏輯
emit browserCreated();
}
else if (browser->IsPopup()) {
// 如果浏覽器視窗已經彈出,加入list,友善後續管理(保證隻能在CEF UI線程通路)
_browserList.push_back(browser);
}
//記錄浏覽器個數
++_browserCount;
}
3.Browser的生命周期從執行CefBrowserHost::CloseBrowser()銷毀Browser對象結束
Browser對象的關閉事件來源于他的父視窗的關閉方法(比如,在父視窗上點選X控鈕。)。父視窗需要調用 CloseBrowser(false) 并且等待作業系統的第二個關閉事件來決定是否允許關閉。
void CefBrowserHandler::CloseAllBrowsers(bool forceClose)
{
qInfo() << "CloseAllBrowsers::" << __FUNCTION__;
if (!CefCurrentlyOn(TID_UI)) {
// 在UI線程中執行
CefPostTask(TID_UI, base::Bind(&CefBrowserHandler::CloseAllBrowsers, this, forceClose));
return;
}
if (_browser == NULL)
return;
if (!_browserList.empty()) {
//關閉浏覽器清單存在的浏覽器
BrowserList::const_iterator it = _browserList.begin();
for (; it != _browserList.end(); ++it)
(*it)->GetHost()->CloseBrowser(forceClose);
return;
}
if (_browser.get()) {
// 請求關閉浏覽器
_browser->GetHost()->CloseBrowser(forceClose);
}
}
DoClose方法設定m_blsClosing 标志位為true,并傳回false以再次發送作業系統的關閉事件。
bool CefBrowserHandler::DoClose(CefRefPtr<CefBrowser> browser)
{
qInfo() << "DoClose::" << __FUNCTION__;
CEF_REQUIRE_UI_THREAD();
//保護成員不受多線程上的通路
AutoLock lock_scope(this);
if (browser->GetIdentifier() == _browserId) {
_isClosing = true;
}
//再次發送作業系統的關閉事件
return false;
}
當作業系統捕捉到第二次關閉事件,它才會允許父視窗真正關閉。該動作會先觸發OnBeforeClose()回調,請在該回調裡釋放所有對浏覽器對象的引用
void CefBrowserHandler::OnBeforeClose(CefRefPtr<CefBrowser> browser)
{
qInfo() << "OnBeforeClose::" << __FUNCTION__;
CEF_REQUIRE_UI_THREAD();
//保護成員不受多線程上的通路
AutoLock lock_scope(this);
if (_browserId == browser->GetIdentifier()) {
_browser = nullptr;
}
else if (browser->IsPopup()) {
//清空浏覽器清單并關閉銷毀浏覽器對象
BrowserList::iterator bit = _browserList.begin();
for (; bit != _browserList.end(); ++bit) {
if ((*bit)->IsSame(browser)) {
_browserList.erase(bit);
break;
}
}
}
if (--_browserCount == 0) {
// 當所有浏覽器視窗已關閉退出時應用程式消息循環.
CefQuitMessageLoop();
}
}
二:CefApp
CefApp接口提供了不同程序的可定制回調函數,包含與程序,指令行參數,代理,資源管理相關的回調類,一些功能是由所有程序共享的,有些必須實作浏覽器的過程中,必須在渲染過程中執行,可以讓開發者定制屬于自己的邏輯。
其中重要的回調函數如下:
1.OnBeforeCommandLineProcessing 提供了以程式設計方式設定指令行參數的機會。
2.OnRegisterCustomSchemes 提供了注冊自定義schemes的機會。
3.CefBrowserProcessHandler 傳回定制Browser程序的Handler,該Handler包括了OnContextInitialized這樣的回調。
4.CefRenderProcessHandler 傳回定制Render程序的Handler,該Handler包含了JavaScript相關的一些回調以及消息處理的回調。
舉例代碼說明(小編所用到的回調類)
//CefBrowserProcessHandler
class CefAppHandler : public CefApp,
public CefBrowserProcessHandler
{
public:
CefAppHandler();
public:
// CefApp methods:
virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler() OVERRIDE {
return this;
}
private:
IMPLEMENT_REFCOUNTING(CefAppHandler);
};
//CefRenderProcessHandler
class MyRenderProcessHandler :public CefApp,
public CefRenderProcessHandler
{
public:
MyRenderProcessHandler();
CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler() OVERRIDE
{
return this;
}
void OnContextCreated(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefV8Context> context);
private:
IMPLEMENT_REFCOUNTING(MyRenderProcessHandler);
};
詳細定制邏輯功能不詳說了,因功能而異實作對應邏輯。
三:CefBrowser和CefFrame
CefBrowser管理renderer程序中執行浏覽相關的類,包括網頁前進後退,曆史導航,加載字元串和請求,發送編輯指令,提取text/html内容,來源檢索,加載請求等。
CefFrame用于加載特定url,在該運作環境下執行JavaScript代碼等。
CefBrowser和CefFrame對象被用來發送指令給浏覽器以及在回調函數裡擷取狀态資訊。每個CefBrowser對象包含一個主CefFrame對象,主CefFrame對象代表頁面的頂層frame。
同時每個CefBrowser對象可以包含零個或多個的CefFrame對象,分别代表不同的子Frame,例如,一個浏覽器加載了兩個iframe,則該CefBrowser對象擁有三個CefFrame對象(頂層frame和兩個iframe)。
常見的網頁操作舉例代碼如下:
//在浏覽器的主frame加載一個特定url
browser->GetMainFrame()->LoadURL("www.baidu.com");
//浏覽器頁面回退
browser->GoBack();
//浏覽器頁面向前
browser->GoForward();
//浏覽器頁面重新整理
browser->Reload();
//浏覽器頁面停止
browser->StopLoad();
//浏覽器視窗的原生句柄
CefWindowHandle window_handle = browser->GetHost()->GetWindowHandle();
//從主frame裡擷取HTML内容
class Visitor : public CefStringVisitor
{
public:
Visitor() {}
virtual void Visit(const CefString& string) OVERRIDE {
// xxx(); 某些邏輯操作
}
IMPLEMENT_REFCOUNTING(Visitor);
};
browser->GetMainFrame()->GetSource(new Visitor());
四:V8引擎
v8 引擎用于高效解析和執行 JavaScript。
v8 引擎設計了高效的垃圾回收機制,以保證快速的對象記憶體配置設定、短暫的垃圾回收暫停、無記憶體碎片。
CEF3提供支援V8Extension的接口,但是這有兩個限制
第一,v8 extension僅在Renderer程序使用。
第二,僅在沙箱模型關閉時使用。
V8引擎的功能比較多,目前小編隻用到了CefV8Handler類與JavaScript互動功能
CefV8Handler 是一個純接口類,隻有一個方法,你可以繼承它,并提供相應的實作
class MyV8Handler :public CefV8Handler
{
public:
MyV8Handler();
virtual bool Execute(const CefString& name,
CefRefPtr<CefV8Value> object,
const CefV8ValueList& arguments,
CefRefPtr<CefV8Value>& retval,
CefString& exception) OVERRIDE;
void SendMsgToBrowser(CefString str);
public:
IMPLEMENT_REFCOUNTING(MyV8Handler);
};
//與js進行通信互動,還需要用到CefV8Context類和CEF 類型:CefV8Value
CefRefPtr<CefV8Context> context
CefRefPtr<CefV8Value> object = context->GetGlobal();
// 建立MyV8Handler對象
CefRefPtr<MyV8Handler> handler = new MyV8Handler();
CefRefPtr<CefV8Value> func = CefV8Value::CreateFunction("ReceivedMsgFromJS", handler);
object->SetValue("ReceivedMsgFromJS", func, V8_PROPERTY_ATTRIBUTE_NONE);
//發送消息
void QCefWebView::SendMsgToPage(const QString &msg)
{
CefRefPtr<CefFrame> frame =GetBrowser(browserHandlerIndex)->GetMainFrame();
QString datastr;
datastr="SigSendMessageToJS('";
datastr+=msg;
datastr+="')";
CefString code;
code.FromString(datastr.toStdString());
frame->ExecuteJavaScript(code, frame->GetURL(), 0);
}
bool MyV8Handler::Execute(const CefString &name, CefRefPtr<CefV8Value> object, const CefV8ValueList &arguments, CefRefPtr<CefV8Value> &retval, CefString &exception)
{
if (name == "ReceivedMsgFromJS") {
if (arguments.size() == 1) {
CefString strFromWeb = arguments.at(0)->GetStringValue();
std::string stdstr=strFromWeb.ToString();
QString str=QString::fromLocal8Bit(strFromWeb.ToString().c_str());
SendMsgToBrowser(strFromWeb);
retval = CefV8Value::CreateString(strFromWeb);
}
else {
retval = CefV8Value::CreateInt(0);
}
return true;
}
return false;
}
void MyV8Handler::SendMsgToBrowser(CefString str)
{
CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create("vvMsg");
CefRefPtr<CefListValue> args = msg->GetArgumentList();
args->SetSize(1);
args->SetString(0, str);
CefV8Context::GetCurrentContext()->GetBrowser()->SendProcessMessage(PID_BROWSER, msg);
}
//接收消息
//OnProcessMessageReceived在Render程序收到程序間消息時被調用
bool CefBrowserHandler::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser, CefProcessId source_process, CefRefPtr<CefProcessMessage> message)
{
const std::string& messageName = message->GetName();
if (messageName == "vvMsg") //messageName要和MyV8Handler類SendMsgToBrowser函數中定義的一樣
{
CefRefPtr<CefListValue> args = message->GetArgumentList();
CefString string0 = args->GetString(0);
// QString res=QString::fromLocal8Bit(string0.ToString().c_str()); 中文會亂碼
QString res = QString::fromStdWString(string0.c_str());
emit recvRenderMsg(res);
return true;
}
return false;
}
五:其他類
常見的都在以上代碼中已定義,其他的可以查詢文檔詳解,不例舉了。
CEF官方文檔
目前還在深入研究學習CEF,文章有諸多漏洞,望見諒,望指正,感謝~
CEF官網位址
CEF官方論壇
參考文檔:https://github.com/fanfeilong/cefutil/blob/master/doc/CEF%20General%20Usage-zh-cn.md#off-screen-rendering
内附小編浏覽器效果