翻译:CEF常见用法

本文翻译自:https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage.md

本文档提供了CEF3常见用法的概述。

介绍

Chromium Embedded Framework (CEF) 是基于Google Chromium项目的开源项目。不同于Chromium项目本身专注于Google Chrome浏览器开发,CEF专注于在第三方应用程序中方便嵌入浏览器功能。CEF通过提供产品级的稳定API,跟随Chromium的发布和二进制发行版,来隔离用户使免受底层Chromium和Blink代码复杂性的影响。CEF中的大多数功能都有默认的实现,提供了丰富的功能却不需要用户做很多集成工作。截至本文发布时,全球有超过1亿个已经安装的CEF实例到各种公司和行业的产品中。CEF维基百科页面上有使用CEF的公司和产品的部分列表。一些的CEF使用场景包括:

CEF3是基于多进程Chromium Content API的下一代CEF。 CEF3的多进程架构的优点包括:

本文档介绍了使用CEF3开发应用程序时涉及的一般概念。

开始

使用二进制发行包

CEF3的二进制发行包可以在项目下载页面得到。它们包含了在指定平台(Windows、Mac OS X、Linux)构建指定版本的CEF3所需的所有文件。无论平台如何,它们都具有相同的通用结构:

每个二进制发行包还包含一个README.txt文件,该文件更详细地描述了特定于平台的分发,以及一个包含CEF的BSD许可证的LICENSE.txt文件。在基于CEF分发应用程序时,您应该在应用程序的发行版中的某处包含许可证文本。例如,您可以在应用程序的UI中的“关于”或“授权”页面上,或在与您的应用程序捆绑在一起的文档中列出它。 通过分别加载““about:license”和“about:credits”,CEF3浏览器窗口中也可以获得许可证和授权信息。

可以使用标准平台构建工具构建基于CEF二进制分发的应用程序。 这包括Windows上的Visual Studio,Mac OS X上的Xcode和Linux上的gcc/make。 项目下载页面包含有关特定二进制版本所需的操作系统和构建工具版本的信息。 在Linux上构建时还要特别注意列出的包依赖项。

有关如何使用CEF3二进制分发创建简单应用程序的详细说明,请参阅TutorialWiki页面。

从源代码中构建

CEF可以在本地或者使用类似TeamCity的构建系统从源代码中构建。这需要通过Git下载Chromium和CEF的源代码。Chromium的代码仓库相当巨大,仅在具有6GB以上物理内存的中等功能机器上推荐使用源代码构建Chromium。有关从源代码构建Chromium和CEF的详细说明,请参见BranchesAndBuildingWiki页面。

示例程序

cefclient示例应用程序是CEF集成的完整工作示例,并且以源代码的形式中包含在每个二进制分发包中。 使用CEF创建新应用程序的最简单方法是从cefclient应用程序开始并删除不需要的部分。 本文档中的许多示例都源于cefclient应用程序。

重要的概念

开发基于CEF3的应用程序有一些重要的基本概念,在继续之前应该先了解这些概念。

c++封装(C++ Wrapper)

libcef共享库导出一个C API,用于将用户与CEF运行时和代码库隔离开来。libcef_dll_wrapper工程以源代码形式作为二进制版本的一部分分发,将此导出的C API包装在C ++ API中,然后链接到客户端应用程序。该C/C ++ API转换层的代码由转换器工具自动生成。UsingTheCAPI页面描述了直接使用C API。

进程

CEF3使用多进程运行。主进程处理创建窗口,绘制和网络访问,被称之为浏览器(browser)进程。它通常也是宿主进程,大多数应用程序的逻辑都运行在browser进程。Blink页面渲染和Javascript执行都在一个独立的渲染(render)进程。某些应用程序的逻辑,比如Javascript绑定和DOM访问,也是运行在渲染进程。默认的进程模型将为每个唯一的来源(scheme + domain)创建新的渲染进程。其他进程将按需创建,例如插件(plugin)进程来处理Flash,gpu进程来处理加速合成。

默认情况下,主应用程序可执行文件将多次被创建成不同的进程。这是通过传递给CefExecuteProcess函数的命令行标志来处理的。如果主应用程序可执行文件很大,需要很长时间才能加载,或者不适合非browser进程,则宿主可以为这些其他进程使用单独的可执行文件。这可以通过CefSettings.browser_subprocess_path变量进行配置。有关更多信息,请参阅“应用程序结构”部分。

CEF3创建的这些分开的进程之间通过IPC进行通信。在browser进程中实现的应用程序逻辑和render进程可以通过来回发送异步消息进行通信。在render进程中JavaScriptIntegration暴露一些异步API可以在browser进程中处理。有关详细信息,请参阅“进程间通信”部分。

特定于平台的调试技巧也适用于Windows,Mac OS X和Linux。

线程

CEF3中的每个进程都运行着多个线程。有关完整的线程列表,请参阅cef_thread_id_t枚举。这些是一些常用的线程:

由于CEF的多线程特性,使用消息传递或锁定来保护数据成员不受多线程访问非常重要。CefPostTask系列函数支持线程之间的简单异步消息传递。有关详细信息,请参阅“投递任务”部分。

可以使用CefCurrentlyOn()函数验证当前线程。CEF示例应用程序使用以下定义来验证方法是否在预期的线程上执行。这些定义包含在include/wrapper/cef_helpers.h头文件中。

#define CEF_REQUIRE_UI_THREAD()       DCHECK(CefCurrentlyOn(TID_UI));
#define CEF_REQUIRE_IO_THREAD()       DCHECK(CefCurrentlyOn(TID_IO));
#define CEF_REQUIRE_FILE_THREAD()     DCHECK(CefCurrentlyOn(TID_FILE));
#define CEF_REQUIRE_RENDERER_THREAD() DCHECK(CefCurrentlyOn(TID_RENDERER));

为了支持对代码块的同步访问,CEF通过include/base/cef_lock.h头文件提供base::Lock和base::AutoLock类型。例如:

// Include the necessary header.
#include "include/base/cef_lock.h"

// Class declaration.
class MyClass : public CefBase {
 public:
  MyClass() : value_(0) {}
  // Method that may be called on multiple threads.
  void IncrementValue();
 private:
  // Value that may be accessed on multiple theads.
  int value_;
  // Lock used to protect access to |value_|.
  base::Lock lock_;
  IMPLEMENT_REFCOUNTING(MyClass);
};

// Class implementation.
void MyClass::IncrementValue() {
  // Acquire the lock for the scope of this method.
  base::AutoLock lock_scope(lock_);
  // |value_| can now be modified safely.
  value_++;
}

引用计数

CEF所有框架类都实现CefBase接口,所有实例指针都使用CefRefPtr智能指针实现进行处理,该实现通过调用AddRef()和Release()自动处理引用计数。实现这些类的最简单方法如下:

class MyClass : public CefBase {
 public:
  // Various class methods here...

 private:
  // Various class members here...

  IMPLEMENT_REFCOUNTING(MyClass);  // 提供原子引用计数实现
};

// References a MyClass instance
CefRefPtr<MyClass> my_class = new MyClass();

字符串

CEF定义了自己的数据结构来表示字符串。这是出于几个不同的原因:

对于UTF16,字符串结构如下所示:

typedef struct _cef_string_utf16_t {
  char16* str;  // Pointer to the string
  size_t length;  // String length
  void (*dtor)(char16* str);  // Destructor for freeing the string on the correct heap
} cef_string_utf16_t;

通过typedef定义所选的字符串类型:

typedef char16 cef_char_t;
typedef cef_string_utf16_t cef_string_t;

CEF提供了许多用于操作CEF字符串类型的C API函数(通过#defines映射到特定于类型的函数)。例如:

CEF还提供在所有支持的字符串类型(ASCII,UTF8,UTF16和wide)之间进行转换的功能。参见cef_string.hcef_string_types.h头文件查看完整函数列表。

CefString类简化了C++中CEF字符串的使用。CefString提供与std::string(UTF8)和std::wstring(wide)类型的自动转换。它还可以用于包装现有的cef_string_t结构以进行分配。

与std::string的转换:

std::string str = “Some UTF8 string”;

// Equivalent ways of assigning |str| to |cef_str|. Conversion from UTF8 will occur if necessary.
CefString cef_str(str);
cef_str = str;
cef_str.FromString(str);

// Equivalent ways of assigning |cef_str| to |str|. Conversion to UTF8 will occur if necessary.
str = cef_str;
str = cef_str.ToString();

与std::wtring的转换:

std::wstring str = “Some wide string”;

// Equivalent ways of assigning |str| to |cef_str|. Conversion from wide will occur if necessary.
CefString cef_str(str);
cef_str = str;
cef_str.FromWString(str);

// Equivalent ways of assigning |cef_str| to |str|. Conversion to wide will occur if necessary.
str = cef_str;
str = cef_str.ToWString();

从ascii字符串转换过来:

const char* cstr = “Some ASCII string”;
CefString cef_str;
cef_str.FromASCII(cstr);

像CefSettings这样的结构有cef_string_t成员。CefString可用于简化对这些成员的赋值:

CefSettings settings;
const char* path = “/path/to/log.txt”;

// Equivalent assignments.
CefString(&settings.log_file).FromASCII(path);
cef_string_from_ascii(path, strlen(path), &settings.log_file);

命令行参数

CEF3和Chromium中的许多功能可以使用命令行参数进行配置。这些参数以--some-argument[=optional-param]形式,并通过CefExecuteProcess()和CefMainArgs结构传递给CEF(参见下面的“应用程序结构”部分)。

如何发现CEF/Chromium支持的命令行开关的更多信息,请参阅shared/common/client_switches.cc中的注释。

应用程序文件结构

应用程序文件结构可能因平台而有很大差异。例如,在Mac OS X上,您的应用程序布局必须遵循特定的应用程序包结构。Windows和Linux更灵活,允许您自定义存储CEF库和资源的位置。有关所需布局的完整工作示例,您可以从http://opensource.spotify.com/cefbuilds/index.html下载“示例应用程序”。发布包里某些文件是必需的,有些是可选的。可以在二进制发布包的README.txt文件中找到每个文件的要求和其他信息。

Windows

在Windows上,默认应用程序文件结构将libcef库和相关资源放在应用程序可执行文件同目录。对于2623分支,最终的目录结构如下:

Application/
    cefclient.exe  <= cefclient 应用程序可执行文件
    libcef.dll <= 动态库
    icudtl.dat <= unicode 支持数据文件
    libEGL.dll, libGLESv2.dll, ... <= 加速合成支持库
    cef.pak, devtools_resources.pak, ... <= 非本地化的资源和字符串
    natives_blob.bin, snapshot_blob.bin <= V8 相关文件
    locales/
        en-US.pak, ... <= 本地化的资源和字符串

可以使用CefSettings结构自定义CEF库和资源文件的位置(有关详细信息,请参阅README.txt文件或“CefSettings”部分)。Windows上的cefclient应用程序通过cefclient/resources/win/cefclient.rc中的BINARY资源类型编译资源,但应用程序可以轻松地从本地文件系统加载资源。

Linux

在Linux上,默认应用程序文件结构将libcef库和相关资源放在应用程序可执行文件同目录。但请注意,libcef.so位于客户端发布中的位置与您自己构建的二进制分发中的位置之间存在差异。该位置取决于构建应用程序可执行文件时如何设置链接器rpath值。例如,值-Wl,-rpath,.(.表示当前目录)将允许您将libcef.so放在应用程序可执行文件旁边。也可以使用LD_LIBRARY_PATH环境变量指定libcef.so的路径。对于2623分支,最终的目录结构如下:

Application/
    cefclient  <= cefclient 应用程序可执行文件
    chrome-sandbox <= sandbox支持库
    libcef.so <= CEF库
    icudtl.dat <= unicode支持数据
    cef.pak, devtools_resources.pak, ... <= 非本地化的资源和字符串
    natives_blob.bin, snapshot_blob.bin <= V8 相关文件
    locales/
        en-US.pak, ... <= 本地化的资源和字符串
    files/
        binding.html, ... <= cefclient应用程序资源文件

可以使用CefSettings结构自定义CEF库和资源文件的位置(有关详细信息,请参阅“CefSettings”部分的README.txt文件)。

Mac OS X

Mac OS X上的应用程序(应用程序包)布局是由Chromium实现强制执行的,因此不是很灵活。对于2623分支,最终的目录结构如下:

cefclient.app/
    Contents/
        Frameworks/
            Chromium Embedded Framework.framework/
                Chromium Embedded Framework <= 主应用库
                Resources/
                    cef.pak, devtools_resources.pak, ... <= 非本地化的资源和字符串resources and strings
                    icudtl.dat <= unicode支持数据
                    natives_blob.bin, snapshot_blob.bin <= V8 相关文件
                    en.lproj/, ... <= 本地化的资源和字符串
            cefclient Helper.app/
                Contents/
                    Info.plist
                    MacOS/
                        cefclient Helper <= 辅助可执行程序
                    Pkginfo
        Info.plist
        MacOS/
            cefclient <= cefclient应用可执行程序
        Pkginfo
        Resources/
            binding.html, ... <= cefclient应用程序资源

Chromium Embedded Framework.framework是一个包含所有CEF二进制文件和资源的unversioned framework。可执行文件(cefclient,cefclient Helper等)动态加载CEF框架,如此处所述。

cefclient Helper应用程序用于执行具有不同特征的单独进程(render,plugin等)。它需要一个单独的应用程序包和Info.plist文件,以便除其他外,它不显示停靠图标。

应用程序结构

每个CEF3应用程序都具有相同的通用结构。

入口点函数

如“进程”部分所述,CEF3应用程序将运行多个进程。这些进程都可以使用相同的可执行文件,也可以为子进程指定单独的可执行文件。进程的执行从入口点函数开始。Windows,Linux和Mac OS-X的完整平台特定示例分别在cefclient/cefclient_win.cc,cefclient/cefclient_gtk.cc,cefclient/cefclient_mac.mm中。

启动子进程时,CEF将使用必须通过CefMainArgs结构传递到CefExecuteProcess函数的命令行来指定配置信息。CefMainArgs的定义是特定于平台的。在Linux和Mac OS X上,它接受传递给main()函数的argc和argv值。

CefMainArgs main_args(argc, argv);

在Windows上,它接受传递给wWinMain()函数的实例句柄(HINSTANCE)。实例句柄也可以通过GetModuleHandle(NULL)获取到。

CefMainArgs main_args(hInstance);

单个可执行文件

CEF以单个可执行文件运行时,需要使用入口点功能来区分不同的进程类型。Windows和Linux支持单一可执行结构,但Mac OS X不支持。

// Program entry-point function.
int main(int argc, char* argv[]) {
  // Structure for passing command-line arguments.
  // The definition of this structure is platform-specific.
  CefMainArgs main_args(argc, argv);

  // Optional implementation of the CefApp interface.
  CefRefPtr<MyApp> app(new MyApp);

  // Execute the sub-process logic, if any. This will either return immediately for the browser
  // process or block until the sub-process should exit.
  int exit_code = CefExecuteProcess(main_args, app.get());
  if (exit_code >= 0) {
    // The sub-process terminated, exit now.
    return exit_code;
  }

  // Populate this structure to customize CEF behavior.
  CefSettings settings;

  // Initialize CEF in the main process.
  CefInitialize(main_args, settings, app.get());

  // Run the CEF message loop. This will block until CefQuitMessageLoop() is called.
  CefRunMessageLoop();

  // Shut down CEF.
  CefShutdown();

  return 0;
}

单独的子进程可执行文件

CEF使用单独的子进程可执行文件时,您需要两个单独的可执行项目和两个单独的入口点函数。

主应用程序入口点函数:

// Program entry-point function.
int main(int argc, char* argv[]) {
  // Load the CEF framework library at runtime instead of linking directly
  // as required by the macOS sandbox implementation.
  CefScopedLibraryLoader library_loader;
  if (!library_loader.LoadInMain())
    return 1;

  // Structure for passing command-line arguments.
  // The definition of this structure is platform-specific.
  CefMainArgs main_args(argc, argv);

  // Optional implementation of the CefApp interface.
  CefRefPtr<MyApp> app(new MyApp);

  // Populate this structure to customize CEF behavior.
  CefSettings settings;

  // Specify the path for the sub-process executable.
  CefString(&settings.browser_subprocess_path).FromASCII(“/path/to/subprocess”);

  // Initialize CEF in the main process.
  CefInitialize(main_args, settings, app.get());

  // Run the CEF message loop. This will block until CefQuitMessageLoop() is called.
  CefRunMessageLoop();

  // Shut down CEF.
  CefShutdown();

  return 0;
}

子进程应用程序入口点函数:

// Program entry-point function.
int main(int argc, char* argv[]) {
  // Initialize the macOS sandbox for this helper process.
  CefScopedSandboxContext sandbox_context;
  if (!sandbox_context.Initialize(argc, argv))
    return 1;

  // Load the CEF framework library at runtime instead of linking directly
  // as required by the macOS sandbox implementation.
  CefScopedLibraryLoader library_loader;
  if (!library_loader.LoadInHelper())
    return 1;

  // Structure for passing command-line arguments.
  // The definition of this structure is platform-specific.
  CefMainArgs main_args(argc, argv);

  // Optional implementation of the CefApp interface.
  CefRefPtr<MyApp> app(new MyApp);

  // Execute the sub-process logic. This will block until the sub-process should exit.
  return CefExecuteProcess(main_args, app.get());
}

消息循环集成

CEF还可以与现有的应用程序消息循环集成,而不是运行自己的消息循环。有两种方法可以做到这一点。

  1. 定期调用CefDoMessageLoopWork()而不是调用CefRunMessageLoop()。每次调用CefDoMessageLoopWork()都将执行CEF消息循环的单次迭代。这种方法应该谨慎使用。过于频繁地调用该方法会使CEF消息循环挨饿并对浏览器性能产生负面影响。过于频繁地调用方法会对CPU使用产生负面影响。
  2. 设置CefSettings.multi_threaded_message_loop = true(仅限Windows)。这将导致CEF在主应用程序线程的单独线程上运行浏览器UI线程。使用这种方法,既不需要调用CefDoMessageLoopWork()也不需要调用CefRunMessageLoop()。仍应在主应用程序线程上调用CefInitialize()和CefShutdown()。您需要提供自己的机制来与主应用程序线程进行通信(例如,参见cefclient_win.cpp中的消息窗口用法)。您可以通过运行“--multi-threaded-message-loop”命令行标志在Windows上的cefclient中测试此模式。

CefSettings

CefSettings结构允许配置应用程序范围的CEF设置。一些常用的成员包括:

CefBrowser 和 CefFrame

CefBrowserCefFrame对象都是用来向浏览器发送命令以及在回调函数中获得状态信息的。每个CefBrowser对象都拥有一个代表网页顶级frame的主CefFrame对象和0到多个代表子frame的CefFrame对象。例如,一个browser加载了2个iframe它就有3个CefFrame对象(一个顶级frame和两个iframe)。

要在浏览器主框架中加载URL:

browser->GetMainFrame()->LoadURL(some_url);

要向后导航浏览器:

browser->GoBack();

要获取主框架HTML内容:

// Implementation of the CefStringVisitor interface.
class Visitor : public CefStringVisitor {
 public:
  Visitor() {}

  // Called asynchronously when the HTML contents are available.
  virtual void Visit(const CefString& string) OVERRIDE {
    // Do something with |string|...
  }

  IMPLEMENT_REFCOUNTING(Visitor);
};

browser->GetMainFrame()->GetSource(new Visitor());

CefBrowser和CefFrame对象同时存在于browser和render进程。可以通过CefBrowser::GetHost()方法在browser进程中控制宿主的行为。例如,可以按如下方式获取浏览器窗口的本机句柄:

// CefWindowHandle is defined as HWND on Windows, NSView* on Mac OS X
// and GtkWidget* on Linux.
CefWindowHandle window_handle = browser->GetHost()->GetWindowHandle();

其他方法可用于浏览器历史导航,字符串和网络请求,发送编辑命令,获取文本/html内容等。有关支持的方法的完整列表,请参阅文档。

CefApp

CefApp提供了进程相关的回调。以下是一些重要的回调:

一个CefApp实现的例子可以参见cefsimple/simple_app.hcefsimple/simple_app.cc

CefClient

CefClient提供了browser-instance相关的回调。一个CefClient实例可以被多个browser共享。以下是一些重要的回调:

一个CefApp实现的例子可以参见cefsimple/simple_handler.hcefsimple/simple_handler.cc

浏览器生命周期

浏览器生命周期从调用CefBrowserHost::CreateBrowser()或CefBrowserHost: CreateBrowserSync()开始。执行此逻辑的合适位置包括在CefBrowserProcessHandler::OnContextInitialized()回调或特定于平台的消息处理程序,如Windows上的WM_CREATE。

// Information about the window that will be created including parenting, size, etc.
// The definition of this structure is platform-specific.
CefWindowInfo info;
// On Windows for example...
info.SetAsChild(parent_hwnd, client_rect);

// Customize this structure to control browser behavior.
CefBrowserSettings settings;

// CefClient implementation.
CefRefPtr<MyClient> client(new MyClient);

// Create the browser asynchronously. Initially loads the Google URL.
CefBrowserHost::CreateBrowser(info, client.get(), “http://www.google.com”, settings, NULL);

CefLifeSpanHandler类提供了必要的回调函数来管理浏览器的生命周期。以下是相关方法和成员的摘录。

class MyClient : public CefClient,
                 public CefLifeSpanHandler,
                 ... {
  // CefClient methods.
  virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() OVERRIDE {
    return this;
  }

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

  // Member accessors.
  CefRefPtr<CefBrowser> GetBrower() { return m_Browser; }
  bool IsClosing() { return m_bIsClosing; }

 private:
  CefRefPtr<CefBrowser> m_Browser;
  int m_BrowserId;
  int m_BrowserCount;
  bool m_bIsClosing;

  IMPLEMENT_REFCOUNTING(MyClient);
};

当浏览器对象创建之后会立马调用OnAfterCreated()方法。宿主程序可以使用这个方法来对主浏览器对象保持引用。

void MyClient::OnAfterCreated(CefRefPtr<CefBrowser> browser) {
  // Must be executed on the UI thread.
  REQUIRE_UI_THREAD();

  if (!m_Browser.get())   {
    // Keep a reference to the main browser.
    m_Browser = browser;
    m_BrowserId = browser->GetIdentifier();
  }

  // Keep track of how many browsers currently exist.
  m_BrowserCount++;
}

要销毁浏览器,请调用CefBrowserHost :: CloseBrowser()。

// Notify the browser window that we would like to close it. This will result in a call to 
// MyHandler::DoClose() if the JavaScript 'onbeforeunload' event handler allows it.
browser->GetHost()->CloseBrowser(false);

如果浏览器的是另外一个窗口的父窗口,则关闭事件可能源自该父窗口的OS功能(例如,通过单击父窗口上的X)。父窗口需要调用CloseBrowser(false)并等待第二个OS关闭事件以指示浏览器已允许关闭。如果通过JavaScript“onbeforeunload”事件处理程序或DoClose()回调取消关闭,则不会发送第二个OS关闭事件。请注意以下示例中的IsClosing()检查 - 它将为第一个OS关闭事件返回false,对于第二个事件(在调用DoClose之后)返回true。

在Windows上的父窗口WndProc中处理:

case WM_CLOSE:
  if (g_handler.get() && !g_handler->IsClosing()) {
    CefRefPtr<CefBrowser> browser = g_handler->GetBrowser();
    if (browser.get()) {
      // Notify the browser window that we would like to close it. This will result in a call to 
      // MyHandler::DoClose() if the JavaScript 'onbeforeunload' event handler allows it.
      browser->GetHost()->CloseBrowser(false);

      // Cancel the close.
      return 0;
    }
  }

  // Allow the close.
  break;

case WM_DESTROY:
  // Quitting CEF is handled in MyHandler::OnBeforeClose().
  return 0;
}

处理Linux上的“delete_event”信号:

gboolean delete_event(GtkWidget* widget, GdkEvent* event,
                      GtkWindow* window) {
  if (g_handler.get() && !g_handler->IsClosing()) {
    CefRefPtr<CefBrowser> browser = g_handler->GetBrowser();
    if (browser.get()) {
      // Notify the browser window that we would like to close it. This will result in a call to 
      // MyHandler::DoClose() if the JavaScript 'onbeforeunload' event handler allows it.
      browser->GetHost()->CloseBrowser(false);

      // Cancel the close.
      return TRUE;
    }
  }

  // Allow the close.
  return FALSE;
}

在OS X关闭更复杂。请参阅cefsimple/cefsimple_mac.mm中的注释,以全面了解shutdown在该平台上的工作原理。

DoClose()方法设置m_bIsClosing标志并返回false以发送第二个OS关闭事件。

bool MyClient::DoClose(CefRefPtr<CefBrowser> browser) {
  // Must be executed on the UI thread.
  REQUIRE_UI_THREAD();

  // Closing the main window requires special handling. See the DoClose()
  // documentation in the CEF header for a detailed description of this
  // process.
  if (m_BrowserId == browser->GetIdentifier()) {
    // Set a flag to indicate that the window close should be allowed.
    m_bIsClosing = true;
  }

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

当OS功能接收到第二个OS关闭事件时,它允许父窗口实际关闭。然后,这会调用OnBeforeClose()。确保在OnBeforeClose()回调中释放对浏览器对象的任何引用。

void MyHandler::OnBeforeClose(CefRefPtr<CefBrowser> browser) {
  // Must be executed on the UI thread.
  REQUIRE_UI_THREAD();

  if (m_BrowserId == browser->GetIdentifier()) {
    // Free the browser pointer so that the browser can be destroyed.
    m_Browser = NULL;
  }

  if (--m_BrowserCount == 0) {
    // All browser windows have closed. Quit the application message loop.
    CefQuitMessageLoop();
  }
}

有关每个平台的完整工作示例,请参阅cefclient应用程序。

离屏渲染

使用离屏渲染CEF不会创建本机浏览器窗口。相反,CEF为宿主应用程序提供无效区域和像素缓冲区,宿主应用程序通知CEF鼠标,键盘和焦点事件。屏幕外渲染当前不支持加速合成,因此与窗口浏览器相比,性能可能会受到影响。屏幕外浏览器将收到与窗口浏览器相同的通知,包括上一节中描述的生命周期通知。要使用离屏渲染:

  1. 实现CefRenderHandler接口。除非另有说明,否则所有方法都是必须的。
  2. 在将CefWindowInfo结构传递给CefBrowserHost::CreateBrowser()之前调用CefWindowInfo::SetAsWindowless()。如果没有父窗口传递给SetAsWindowless,则某些功能(如上下文菜单)可能不可用。
  3. 将调用CefRenderHandler::GetViewRect()方法来获取所需的视图矩形。
  4. 将调用CefRenderHandler::OnPaint()方法以提供无效区域和更新的像素缓冲区。cefclient应用程序使用OpenGL绘制缓冲区,但您的应用程序可以使用您喜欢的任何技术。
  5. 要调整浏览器的大小,请调用CefBrowserHost::WasResized()。这将导致调用GetViewRect()以检索新大小,然后调用OnPaint()。
  6. 调用CefBrowserHost::SendXXX()方法以通知浏览器鼠标,键盘和焦点事件。
  7. 调用CefBrowserHost::CloseBrowser()来销毁浏览器。

使用“--off-screen-rendering-enabled”命令行标志运行cefclient作为工作示例。

投递任务

可以使用CefPostTask系列方法在单个进程中的各个线程之间投递任务(有关完整列表,请参阅include/cef_task.h头文件)。该任务将在目标线程的消息循环上异步执行。

CEF提供base::Bind将方法和对应的参数绑定成一个base::Callback任务,然后通过CefPostTask去投递任务。关于base::Bind和base::Callback的完整使用信息,可以查看include/base/cef_callback.h头文件的注释。include/wrapper/cef_closure_task.h头文件提供了一些帮助类用于把一个base::Closure转换成CefTask。例如:

// Include the necessary headers.
#include “include/base/cef_bind.h”
#include “include/wrapper/cef_closure_task.h”

// To execute a bound function:

// Define a function.
void MyFunc(int arg) { /* do something with |arg| on the UI thread */ }

// Post a task that will execute MyFunc on the UI thread and pass an |arg|
// value of 5.
CefPostTask(TID_UI, base::Bind(&MyFunc, 5));

// To execute a bound method:

// Define a class.
class MyClass : public CefBase {
 public:
  MyClass() {}
  void MyMethod(int arg) { /* do something with |arg| on the UI thread */ }
 private:
  IMPLEMENT_REFCOUNTING(MyClass);
};

// Create an instance of MyClass.
CefRefPtr<MyClass> instance = new MyClass();

// Post a task that will execute MyClass::MyMethod on the UI thread and pass
// an |arg| value of 5. |instance| will be kept alive until after the task
// completes.
CefPostTask(TID_UI, base::Bind(&MyClass::MyMethod, instance, 5));

如果宿主应用程序需要保持对run loop的引用,则可以使用CefTaskRunner类。例如,要获取UI线程的task runner:

CefRefPtr<CefTaskRunner> task_runner = CefTaskRunner::GetForThread(TID_UI);

进程间通信

由于CEF3在多个进程中运行,因此有必要提供在这些进程之间进行通信的机制。CefBrowser和CefFrame对象同时存在于browser和render进程中,这有助于促进此过程。每个CefBrowser和CefFrame对象还具有与其关联的唯一ID值,该值将在进程边界的两侧匹配。

进程启动消息

要在启动时为所有渲染进程提供相同的信息,请在浏览器进程中实现CefBrowserProcessHandler::OnRenderProcessThreadCreated()。这将在渲染进程中将信息传递给CefRenderProcessHandler::OnRenderThreadCreated()。

进程运行时消息

在进程生命周期中的任何时间传递信息都可以通过CefProcessMessage类使用进程消息。这些消息与特定的CefBrowser实例相关联,并使用CefBrowser::SendProcessMessage()方法发送。进程消息应包含通过CefProcessMessage::GetArgumentList()所需的任何状态信息。

// Create the message object.
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);

从browser进程发送到render进程的消息将到达CefRenderProcessHandler::OnProcessMessageReceived()。从render过程发送到browser进程的消息将到达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相关联,请frame ID(可通过CefFrame::GetIdentifier()获取)作为参数传递,并通过CefBrowser::GetFrame()方法在接收过程中获取关联的CefFrame。

// 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绑定

JavaScriptIntegration是在render进程里面实现的,但是需要频繁的跟browser进程通信。应该将JavaScript API本身设计为使用closurespromises异步工作。

通用消息路由

CEF提供了一个通用实现,用于在render进程中运行的JavaScript和在browser进程中运行的C++之间路由异步消息。应用程序通过从标准CEF C++回调(OnBeforeBrowse,OnProcessMessageRecieved,OnContextCreated等)传递数据来与路由器交互。render端路由器支持通用JavaScript回调注册和执行,而browser端路由器通过一个或多个应用程序提供的Handler实例支持特定于应用程序的逻辑。有关演示CefMessageRouter用法的独立示例应用程序,请参阅 message_router example。有关完整的使用文档,请参阅include/wrapper/cef_message_router.h

自定义实现

基于CEF的应用程序还可以提供自己的异步JavaScript绑定的自定义实现。简单的实现可以如下工作:

  1. render进程中的JavaScript绑定传递回调函数。
// In JavaScript register the callback function.
app.setMessageCallback('binding_test', function(name, args) {
  document.getElementById('result').value = "Response: "+args[0];
});
  1. render进程保留对回调函数的引用。
// 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])));
}
  1. render进程向browser进程发送异步IPC消息,请求执行该工作。

  2. browser进程接收IPC消息并执行工作。

  3. 完成工作后,browser进程将异步IPC消息发送回render进程并返回结果。

  4. render过程接收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();
  }
}
  1. 释放与CefRenderProcessHandler::OnContextReleased()中的上下文关联的任何V8引用。
void MyHandler::OnContextReleased(CefRefPtr<CefBrowser> browser,
                                  CefRefPtr<CefFrame> frame,
                                  CefRefPtr<CefV8Context> context) {
  // Remove any JavaScript callbacks registered for the context that has been released.
  if (!callback_map_.empty()) {
    CallbackMap::iterator it = callback_map_.begin();
    for (; it != callback_map_.end();) {
      if (it->second.first->IsSame(context))
        callback_map_.erase(it++);
      else
        ++it;
    }
  }
}

同步请求

在极少数情况下,可能需要在browser和render进程之间实现同步通信。应尽可能避免这种情况,因为它会对render过程中的性能产生负面影响。但是,如果必须进行同步通信,请考虑使用synchronous XMLHttpRequests,它将在等待browser进程网络层中的处理时阻止render进程。browser进程可以使用自定义方案处理程序或网络拦截来处理请求。有关详细信息,请参阅“网络层”部分。

网络层

默认情况下,CEF3中的网络请求将以对宿主应用程序透明的方式处理。对于希望与网络层建立更密切关系的应用,CEF3暴露了一系列与网络相关的功能。

网络相关的回调可能发生在不同的线程上,因此请务必注意文档并正确保护您的数据成员。

自定义网络请求

在browser frame中加载URL的最简单方法是通过CefFrame::LoadURL()方法。

browser->GetMainFrame()->LoadURL(some_url);

希望发送包含自定义网络请求head或上传数据的更复杂请求的应用程序可以使用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_(0),
      download_total_(0) {}

  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...
  }

  void OnUploadProgress(CefRefPtr<CefURLRequest> request,
                        int64 current,
                        int64 total) OVERRIDE {
    upload_total_ = total;
  }

  void OnDownloadProgress(CefRefPtr<CefURLRequest> request,
                          int64 current,
                          int64 total) OVERRIDE {
    download_total_ = total;
  }

  void OnDownloadData(CefRefPtr<CefURLRequest> request,
                      const void* data,
                      size_t data_length) OVERRIDE {
    download_data_ += std::string(static_cast<const char*>(data), data_length);
  }

  bool GetAuthCredentials(bool isProxy,
                          const CefString& host,
                          int port,
                          const CefString& realm,
                          const CefString& scheme,
                          CefRefPtr<CefAuthCallback> callback) OVERRIDE {
    return false;  // Not handled.
  }

 private:
  int64 upload_total_;
  int64 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(), nullptr);
// To cancel the request: url_request->Cancel();

使用CefURLRequest进行的请求还可以通过CefRequest::SetFlags()方法指定自定义行为。

例如,要跳过缓存而不报告下载数据:

request->SetFlags(UR_FLAG_SKIP_CACHE | UR_FLAG_NO_DOWNLOAD_DATA);

网络请求处理

CEF3支持两种处理应用程序内部网络请求的方法。scheme handler允许为针对特定来源(scheme + domain)的请求注册处理程序。网络请求拦截方法允许应用程序自行决定处理任意网络请求。

使用HTTP scheme而不是自定义scheme来避免一系列潜在问题。

如果您选择使用自定义scheme(“HTTP”,“HTTPS”以外的任何其他scheme),则必须将其注册到CEF,以使其按预期运行。如果您希望自定义scheme的行为类似于HTTP(支持POST请求并强制执行 HTTP access control (CORS)限制),则应将其注册为“标准”scheme。如果您计划对其他scheme执行跨源请求或通过XMLHttpRequest将POST请求发送到您的scheme handler,那么您应该使用HTTP方案而不是自定义方案来避免潜在问题。如果您希望使用自定义scheme,则通过CefApp::OnRegisterCustomSchemes()回调注册属性,该回调必须在所有进程中实现。

void MyApp::OnRegisterCustomSchemes(CefRefPtr<CefSchemeRegistrar> registrar) {
  // Register "client" as a standard scheme.
  registrar->AddCustomScheme("client", true, ...);
}

通用资源管理器

CEF提供了用于管理来自一个或多个数据源的资源请求的通用实现。此使用者注册不同数据源的处理程序,例如磁盘上的目录,zip存档或自定义实现,并且管理器处理请求。应用程序通过从标准CEF C++回调(OnBeforeResourceLoad,GetResourceHandler)传递数据来与路由器交互。有关演示CefResourceManager用法的独立示例应用程序,请参阅resource_manager example。有关完整的使用文档,请参阅include/wrapper/cef_resource_manager.h

Scheme Handler

Scheme Handler通过CefRegisterSchemeHandlerFactory()函数注册。调用此函数的好地方是CefBrowserProcessHandler::OnContextInitialized()。例如,您可以为client://myapp/注册Scheme Handler:

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

Handler可以与内置Scheme(HTTP,HTTPS等)和自定义Scheme一起使用。使用内置Scheme时,请选择应用程序特有的域名(例如“myapp”或“internal”)。实现CefSchemeHandlerFactoryCefResourceHandler类来处理请求并提供响应数据。如果使用自定义Scheme,请不要忘记实现如上所述的CefApp::OnRegisterCustomSchemes方法。有关演示CefSchemeHandlerFactory用法的独立示例应用程序,请参阅scheme_handler example。有关完整的使用文档,请参阅include/cef_scheme.h

如果响应数据在请求时是已知的,则CefStreamResourceHandler类提供了CefResourceHandler的方便的默认实现。

// CefStreamResourceHandler is part of the libcef_dll_wrapper project.
#include “include/wrapper/cef_stream_resource_handler.h”

const std::string& html_content = “<html><body>Hello!</body></html>”;

// Create a stream reader for |html_content|.
CefRefPtr<CefStreamReader> stream =
    CefStreamReader::CreateForData(
        static_cast<void*>(const_cast<char*>(html_content.c_str())),
        html_content.size());

// Constructor for HTTP status code 200 and no custom response headers.
// There’s also a version of the constructor for custom status code and response headers.
return new CefStreamResourceHandler("text/html", stream);

网络请求拦截

CefRequestHandler::GetResourceHandler()方法支持拦截任意网络请求。它使用相同的CefResourceHandler类作为方案处理程序方法。如果使用自定义scheme ,请不要忘记实现如上所述的CefApp::OnRegisterCustomSchemes方法。

CefRefPtr<CefResourceHandler> MyHandler::GetResourceHandler(
      CefRefPtr<CefBrowser> browser,
      CefRefPtr<CefFrame> frame,
      CefRefPtr<CefRequest> request) {
  // Evaluate |request| to determine proper handling...
  if (...)
    return new MyResourceHandler();

  // Return NULL for default handling of the request.
  return NULL;
}

网络响应过滤

CefRequestHandler::GetResourceResponseFilter()方法支持过滤网络请求的响应数据。有关工作示例,请参阅cefclient/browser/response_filter_test.cc(可通过cefclient示例程序的菜单 Tests menu > Other Tests >Response Filtering )。

其他回调

CefRequestHandler接口为各种与网络相关的事件提供回调,包括身份验证,cookie处理,外部协议处理,证书错误等。

代理解析

使用与Google Chrome相同的命令行标志在CEF3中配置代理设置。

--proxy-server=host:port
      Specify the HTTP/SOCKS4/SOCKS5 proxy server to use for requests. An individual proxy
      server is specified using the format:

        [<proxy-scheme>://]<proxy-host>[:<proxy-port>]

      Where <proxy-scheme> is the protocol of the proxy server, and is one of:

        "http", "socks", "socks4", "socks5".

      If the <proxy-scheme> is omitted, it defaults to "http". Also note that "socks" is equivalent to
      "socks5".

      Examples:

        --proxy-server="foopy:99"
            Use the HTTP proxy "foopy:99" to load all URLs.

        --proxy-server="socks://foobar:1080"
            Use the SOCKS v5 proxy "foobar:1080" to load all URLs.

        --proxy-server="sock4://foobar:1080"
            Use the SOCKS v4 proxy "foobar:1080" to load all URLs.

        --proxy-server="socks5://foobar:66"
            Use the SOCKS v5 proxy "foobar:66" to load all URLs.

      It is also possible to specify a separate proxy server for different URL types, by prefixing
      the proxy server specifier with a URL specifier:

      Example:

        --proxy-server="https=proxy1:80;http=socks4://baz:1080"
            Load https://* URLs using the HTTP proxy "proxy1:80". And load http://*
            URLs using the SOCKS v4 proxy "baz:1080".

--no-proxy-server
      Disables the proxy server.

--proxy-auto-detect
      Autodetect  proxy  configuration.

--proxy-pac-url=URL
      Specify proxy autoconfiguration URL.
If the proxy requires authentication the CefRequestHandler::GetAuthCredentials() callback will be executed with an |isProxy| value of true to retrieve the username and password.

bool MyHandler::GetAuthCredentials(
    CefRefPtr<CefBrowser> browser,
    CefRefPtr<CefFrame> frame,
    bool isProxy,
    const CefString& host,
    int port,
    const CefString& realm,
    const CefString& scheme,
    CefRefPtr<CefAuthCallback> callback) {
  if (isProxy) {
    // Provide credentials for the proxy server connection.
    callback->Continue("myuser", "mypass");
    return true;
  }
  return false;
}

应用程序启动期间的Web内容加载可能会因网络代理解析而延迟(例如,如果在Windows上选中“自动检测代理设置”)。为了获得最佳用户体验,请考虑将应用程序设计为首先显示静态启动页面,然后使用meta refresh重定向到实际网站 - 重定向将被阻止,直到代理解析完成。出于测试目的,可以使用“--no-proxy-server”命令行标志禁用代理解析。通过从命令行运行chrome --url = ...,也可以在Google Chrome中复制代理解析延迟。

参考: