从CEF3官方Demo源码分析并实现第一个Qt版的SimpleCef

从CEF3官方Demo源码分析并实现第一个Qt版的SimpleCef

首先需要知道几个重要的概念

  • 进程(Processes)

CEF3是多进程架构的。Browser被定义为主进程,负责窗口管理,界面绘制和网络交互。Blink的渲染和Js的执行被放在一个独立的Render 进程中;除此之外,Render进程还负责Js Binding和对Dom节点的访问。 默认的进程模型中,会为每个标签页创建一个新的Render进程。其他进程按需创建,例如管理插件的进程以及处理合成加速的进程等都是按需创建。

默认情况下,主应用程序会被多次启动运行各自独立的进程。这是通过传递不同的命令行参数给CefExecuteProcess函数做到的。如果主应用程序很大,加载时间比较长,或者不能在非浏览器进程里使用,则宿主程序可使用独立的可执行文件去运行这些进程。这可以通过配置CefSettings.browser_subprocess_path变量做到。

  • 线程(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函数组支持简易的线程间异步消息传递。

  • 引用计数(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();

多进程架构

  • 背景

在某种程度上,web浏览器当前状态就像一个与过去的多任务操作系统合作的单独的用户。正如在一个这样的操作系统中的错误程序会让整个系统挂掉,所以一个错误的web页面也可以让一个现代浏览器挂掉。仅仅需要一个浏览器或插件的bug,就能让整个浏览器和所有正在运行的标签页停止运行。

现代操作系统更加鲁棒,因为他们把应用程序分成了彼此隔离的独立线程。一个程序中的crash通常不会影响其他程序或整个操作系统,每个用户对用户数据的访问也是有限制的。

我们为浏览器的标签页使用独立的进程,以此保护整个应用程序免受渲染引擎中的bug和故障的伤害。我们也会限制每个渲染引擎进程的相互访问,以及他们与系统其他部分的访问。某些程度上,这为web浏览提供了内存保护,为操作系统提供了访问控制。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zuf2XwVI-1626783337517)(Qt%E5%B5%8C%E5%85%A5%E6%B5%8F%E8%A7%88%E5%99%A8.assets/arch.png)]

  • CEF结构 (网页嵌入应用程序代码构成)

每个CEF3应用程序都是相同的结构

  • 提供入口函数,用于初始化CEF、运行子进程执行逻辑或者CEF消息循环。

  • 提供CefApp实现,用于处理进程相关的回调。

  • 提供CefClient实现,用于处理Browser实例相关的回调。

  • 执行CefBrowserHost::CreateBrowser()创建一个Browser实例,使用CefLifeSpanHandler管理Browser对象生命周期。

  • CEF进程和窗口之间的结构关系

    CEF3使用多个进程运行。处理窗口创建、绘制和网络访问的主要进程称为浏览器进程。这通常与宿主应用程序的进程相同,大多数应用程序的逻辑将在浏览器进程中运行。使用Blink引擎渲染HTML和JavaScript执行在单独的渲染进程中发生。一些应用程序逻辑(如JavaScript绑定和DOM访问)也将在渲染进程中运行。默认进程模型将为每个唯一源地址(scheme+domain)运行一个新的渲染进程。其他进程将根据需要生成,例如处理Flash等插件的插件进程和处理加速合成的GPU进程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PpXhnSQU-1626783337520)(file://D:\Framework\Qt+Cef\doc\WBCefView%E8%AE%BE%E8%AE%A1%E6%80%9D%E8%B7%AF.assets\v2-b5b4346429dad6c78a01dc16d774b499_720w.jpg?lastModify=1626780285)]

  • Renderer进程的实现结构

renderer程序继承CefApp和CefRenderProcessHandler类,在main函数中初始化。通过CefSettings.browser_subprocess_path配置render可执行程序路径。browser进程就会去启动这个进程去渲染网页。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zCgCfXS2-1626783337521)(file://D:\Framework\Qt+Cef\doc\WBCefView%E8%AE%BE%E8%AE%A1%E6%80%9D%E8%B7%AF.assets\v2-87cb5b9eb0ee8b6a7438394d80ef09e5_r.jpg?lastModify=1626780298)]

  • browser进程的实现结构

browserapp要继承CefApp和CefBrowserProcessHandler类。实现browserapp的定义。同时要新建clienthandler类实现图中的回调函数接口类,用来处理拦截响应请求、管理生命周期、下载、显示加载、右键菜单等。在mian函数中初始化、启动消息循环。调用CefBrowserHost的静态方法创建browser窗口对象,在render进程的Frame中加载渲染内容。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cmAyHUjr-1626783337524)(file://D:\Framework\Qt+Cef\doc\WBCefView%E8%AE%BE%E8%AE%A1%E6%80%9D%E8%B7%AF.assets\v2-06ae6746434179a9cf12e53174bf44ea_720w.jpg?lastModify=1626780310)]

入口函数

#include "widget.h"
#include "simple_app.h"
#include "simple_handler.h"

#include <QApplication>

void QCefInitSettings(CefSettings & settings)
{
    //设置缓存地址
//    std::string cache_path = "WebullView.cache";
//    CefString(&settings.cache_path) = CefString(cache_path);

    //开启多线程消息循环后 浏览器进程的UI线程是另外的线程 就不要CefRunMessageLoop()来阻塞浏览器的UI线程
    settings.multi_threaded_message_loop = true;
    //日志设置
    settings.log_severity = LOGSEVERITY_DISABLE;

    //单一进程模型仅调试使用,
    //线程模型建议使用默认的,默认的进程模型(多进程)中,会为每个标签页创建一个新的Render进程。
    //settings.single_process = true;

    //不启用沙盒
    settings.no_sandbox = true;

}

int QCefInit(int& argc, char** argv)
{
    HINSTANCE hInstance = static_cast<HINSTANCE>(GetModuleHandle(nullptr));

    CefMainArgs mainArgs(hInstance);
    CefRefPtr<SimpleApp> app(new SimpleApp); //CefApp实现,用于处理进程相关的回调。

    CefSettings settings;
    QCefInitSettings(settings);

    //单一执行体
    int exit_code = CefExecuteProcess(mainArgs, app.get(), nullptr);
    if (exit_code >= 0) {
        return exit_code;
    }



    CefInitialize(mainArgs, settings, app, nullptr);

    return -1;
}

void CefQuit()
{
    CefShutdown();
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    int result = QCefInit(argc, argv);
    if (result >= 0) {
        return result;
    }

    Widget w;
    w.show();

    a.exec();
    CefQuit();

    return 0;
}

CefApp实现:

  • OnBeforeCommandLineProcessing 提供了以编程方式设置命令行参数的机会,更多细节,请参考[Command Line Arguments](https://github.com/fanfeilong/cefutil/blob/master/doc/CEF General Usage-zh-cn.md#command-line-arguments)一节。
  • OnRegisterCustomSchemes 提供了注册自定义schemes的机会,更多细节,请参考[Request Handling](https://github.com/fanfeilong/cefutil/blob/master/doc/CEF General Usage-zh-cn.md#request-handling)一节。
  • GetBrowserProcessHandler 返回定制Browser进程的Handler,该Handler包括了诸如OnContextInitialized的回调。
  • GetRenderProcessHandler 返回定制Render进程的Handler,该Handler包含了JavaScript相关的一些回调以及消息处理的回调。 更多细节,请参考[JavascriptIntegration](https://github.com/fanfeilong/cefutil/blob/master/doc/CEF General Usage-zh-cn.md#javascript-integration)和[Inter-Process Communication](https://github.com/fanfeilong/cefutil/blob/master/doc/CEF General Usage-zh-cn.md#inter-process-communication)两节。
    simple_app.h
#pragma once

#include "cef_app.h"

//CefApp接口提供了不同进程的可定制回调函数。

class SimpleApp
    : public CefApp
    , public CefBrowserProcessHandler
    , public CefRenderProcessHandler
{
public:
    SimpleApp();
    virtual ~SimpleApp() OVERRIDE;

public:
    //提供修改命令行参数的机会
    virtual void OnBeforeCommandLineProcessing(const CefString& process_type,
                                               CefRefPtr<CefCommandLine> command_line) OVERRIDE;
    //浏览器进程Handler
    virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler() OVERRIDE {return this;};
    //渲染进程Handler
    virtual CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler() OVERRIDE {return this;};

    // CefBrowserProcessHandler methods:
    virtual void OnContextInitialized() OVERRIDE;


    virtual void OnContextCreated(CefRefPtr<CefBrowser> browser,
                                  CefRefPtr<CefFrame> frame,
                                  CefRefPtr<CefV8Context> context) OVERRIDE;

protected:
    IMPLEMENT_REFCOUNTING(SimpleApp);
};

simple_app.cpp

// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.

#include "simple_app.h"

#include <string>
#include <QDebug>

#include "include/cef_browser.h"
#include "include/cef_command_line.h"
#include "include/views/cef_browser_view.h"
#include "include/views/cef_window.h"
#include "include/wrapper/cef_helpers.h"
#include "simple_handler.h"

SimpleApp::SimpleApp()
{
    qInfo()<<__FUNCTION__;
}

SimpleApp::~SimpleApp()
{
    qInfo()<<__FUNCTION__;
}

void SimpleApp::OnBeforeCommandLineProcessing(const CefString &process_type, CefRefPtr<CefCommandLine> command_line)
{
    qInfo()<<__FUNCTION__<<"process_type: "<<process_type.ToString().c_str();
    qInfo()<<__FUNCTION__<<"command_line: "<<command_line;
}

void SimpleApp::OnContextInitialized()
{
    qInfo()<<__FUNCTION__;
}

void SimpleApp::OnContextCreated(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context)
{
    qInfo()<<__FUNCTION__;
    // The var type can accept all object or variable
    CefRefPtr<CefV8Value> window = context->GetGlobal();
}

CefCilent实现:

  • OnBeforeCommandLineProcessing 提供了以编程方式设置命令行参数的机会,更多细节,请参考[Command Line Arguments](https://github.com/fanfeilong/cefutil/blob/master/doc/CEF General Usage-zh-cn.md#command-line-arguments)一节。
  • OnRegisterCustomSchemes 提供了注册自定义schemes的机会,更多细节,请参考[Request Handling](https://github.com/fanfeilong/cefutil/blob/master/doc/CEF General Usage-zh-cn.md#request-handling)一节。
  • GetBrowserProcessHandler 返回定制Browser进程的Handler,该Handler包括了诸如OnContextInitialized的回调。
  • GetRenderProcessHandler 返回定制Render进程的Handler,该Handler包含了JavaScript相关的一些回调以及消息处理的回调。 更多细节,请参考[JavascriptIntegration](https://github.com/fanfeilong/cefutil/blob/master/doc/CEF General Usage-zh-cn.md#javascript-integration)和[Inter-Process Communication](https://github.com/fanfeilong/cefutil/blob/master/doc/CEF General Usage-zh-cn.md#inter-process-communication)两节。
    simple_handler.h
// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.

#ifndef CEF_TESTS_CEFSIMPLE_SIMPLE_HANDLER_H_
#define CEF_TESTS_CEFSIMPLE_SIMPLE_HANDLER_H_

#include "cef_client.h"

#include <list>

class SimpleHandler : public CefClient
                    , public CefDisplayHandler
                    , public CefLifeSpanHandler
                    , public CefLoadHandler
{
 public:
  explicit SimpleHandler(bool use_views);
  ~SimpleHandler();

  // Provide access to the single global instance of this object.
  static SimpleHandler* GetInstance();

  // CefClient methods:
  virtual CefRefPtr<CefDisplayHandler> GetDisplayHandler() OVERRIDE {
    return this;
  }
  virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() OVERRIDE {
    return this;
  }
  virtual CefRefPtr<CefLoadHandler> GetLoadHandler() OVERRIDE { return this; }

  // CefDisplayHandler methods:
  virtual void OnTitleChange(CefRefPtr<CefBrowser> browser,
                             const CefString& title) OVERRIDE;

  // CefLifeSpanHandler methods:
  virtual void OnAfterCreated(CefRefPtr<CefBrowser> browser) OVERRIDE;
  virtual bool DoClose(CefRefPtr<CefBrowser> browser) OVERRIDE;
  virtual void OnBeforeClose(CefRefPtr<CefBrowser> browser) OVERRIDE;

  // CefLoadHandler methods:
  virtual void OnLoadError(CefRefPtr<CefBrowser> browser,
                           CefRefPtr<CefFrame> frame,
                           ErrorCode errorCode,
                           const CefString& errorText,
                           const CefString& failedUrl) OVERRIDE;

  // Request that all existing browser windows close.
  void CloseAllBrowsers(bool force_close);

  bool IsClosing() const { return is_closing_; }

  std::list<CefRefPtr<CefBrowser> > getCefBrowerList();

 private:

  // True if the application is using the Views framework.
  const bool use_views_;

  // List of existing browser windows. Only accessed on the CEF UI thread.
  typedef std::list<CefRefPtr<CefBrowser>> BrowserList;
  BrowserList browser_list_;

  bool is_closing_;

  // Include the default reference counting implementation.
  IMPLEMENT_REFCOUNTING(SimpleHandler);
};

#endif  // CEF_TESTS_CEFSIMPLE_SIMPLE_HANDLER_H_

simple_handler.h

// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.

#include "simple_handler.h"

#include <sstream>
#include <string>
#include <QDebug>

#include "include/base/cef_bind.h"
#include "include/cef_app.h"
#include "include/views/cef_browser_view.h"
#include "include/views/cef_window.h"
#include "include/wrapper/cef_closure_task.h"
#include "include/wrapper/cef_helpers.h"

namespace {

SimpleHandler* g_instance = NULL;

}  // namespace

SimpleHandler::SimpleHandler(bool use_views)
    : use_views_(use_views), is_closing_(false) {
    DCHECK(!g_instance);
    g_instance = this;
    qInfo()<<__FUNCTION__;
}

SimpleHandler::~SimpleHandler() {
    g_instance = NULL;
    qInfo()<<__FUNCTION__;
}

// static
SimpleHandler* SimpleHandler::GetInstance() {
  return g_instance;
}

void SimpleHandler::OnTitleChange(CefRefPtr<CefBrowser> browser,
                                  const CefString& title) {
    CEF_REQUIRE_UI_THREAD();
    qInfo()<<__FUNCTION__;
    if (use_views_) {
        // Set the title of the window using the Views framework.
        CefRefPtr<CefBrowserView> browser_view =
                CefBrowserView::GetForBrowser(browser);
        if (browser_view) {
            CefRefPtr<CefWindow> window = browser_view->GetWindow();
            if (window)
                window->SetTitle(title);
        }
    } else {
        // Set the title of the window using platform APIs.
        // PlatformTitleChange(browser, title);
    }
}

void SimpleHandler::OnAfterCreated(CefRefPtr<CefBrowser> browser) {
    CEF_REQUIRE_UI_THREAD();
    qInfo()<<__FUNCTION__;
    // Add to the list of existing browsers.
    browser_list_.push_back(browser);
}

bool SimpleHandler::DoClose(CefRefPtr<CefBrowser> browser) {
    CEF_REQUIRE_UI_THREAD();
    qInfo()<<__FUNCTION__;
    // Closing the main window requires special handling. See the DoClose()
    // documentation in the CEF header for a detailed destription of this
    // process.
    if (browser_list_.size() == 1) {
        // Set a flag to indicate that the window close should be allowed.
        is_closing_ = true;
    }

    // Allow the close. For windowed browsers this will result in the OS close
    // event being sent.
    return false;
}

void SimpleHandler::OnBeforeClose(CefRefPtr<CefBrowser> browser) {
    CEF_REQUIRE_UI_THREAD();
    qInfo()<<__FUNCTION__;
    // Remove from the list of existing browsers.
    BrowserList::iterator bit = browser_list_.begin();
    for (; bit != browser_list_.end(); ++bit) {
        if ((*bit)->IsSame(browser)) {
            browser_list_.erase(bit);
            break;
        }
    }

    if (browser_list_.empty()) {
        // All browser windows have closed. Quit the application message loop.
        //CefQuitMessageLoop();
    }
}

void SimpleHandler::OnLoadError(CefRefPtr<CefBrowser> browser,
                                CefRefPtr<CefFrame> frame,
                                ErrorCode errorCode,
                                const CefString& errorText,
                                const CefString& failedUrl) {
    CEF_REQUIRE_UI_THREAD();
    qInfo()<<__FUNCTION__;
    // Don't display an error for downloaded files.
    if (errorCode == ERR_ABORTED)
        return;

    // Display a load error message.
    std::stringstream ss;
    ss << "<html><body bgcolor=\"white\">"
        "<h2>Failed to load URL " << std::string(failedUrl) <<
        " with error " << std::string(errorText) << " (" << errorCode <<
        ").</h2></body></html>";

    frame->LoadString(ss.str(), failedUrl);
}

void SimpleHandler::CloseAllBrowsers(bool force_close) {
    qInfo()<<__FUNCTION__;
    if (!CefCurrentlyOn(TID_UI)) {
        // Execute on the UI thread.
        CefPostTask(TID_UI, base::Bind(&SimpleHandler::CloseAllBrowsers, this,
                                       force_close));
        return;
    }

    if (browser_list_.empty())
        return;

    BrowserList::const_iterator it = browser_list_.begin();
    for (; it != browser_list_.end(); ++it)
        (*it)->GetHost()->CloseBrowser(force_close);
}

std::list<CefRefPtr<CefBrowser> > SimpleHandler::getCefBrowerList()
{
    return browser_list_;
}

最后新建一个Qt工程,把cef浏览器嵌入到widget上。实现效果如下:

image-20210720194120491

完整代码下载:

image-20210720194524676
SimpleCef1.7z-C++文档类资源-CSDN下载

cef3.3239&&3.2623.7z支持mp3、mp4、h264的X86X64的库-C++文档类资源-CSDN下载

参考:

使用CEF(三)— 从CEF官方Demo源码入手解析CEF架构与CefApp、CefClient对象 - 知乎 (zhihu.com)

整体架构 - 跨进程通信 - 《Chromium中文文档》 - 书栈网 · BookStack

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZLOZL

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值