Qt浏览器开发:关于CEF开发知识点以及QCef开发原理与使用 - Go语言中文社区

Qt浏览器开发:关于CEF开发知识点以及QCef开发原理与使用


开发环境:
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

内附小编浏览器效果
在这里插入图片描述

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_36651243/article/details/115350668
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2021-06-14 04:01:06
  • 阅读 ( 2049 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢