In “Using CEF (2) — Writing a Simple CEF Sample based on VS2019” above, we showed how to write a CEF sample and provided code listings that mentioned some of the classes defined by CEF, such as CefApp, CefClient, and so on. What exactly do they do and how do they relate to CEF’s process architecture? This article will introduce each of them.
The process architecture of CEF
CEF3 runs using multiple processes. The main process which handles window creation, painting and network access is called the “browser” process. This is generally the same process as the host application and the majority of the application logic will run in the browser process. Blink rendering and JavaScript execution occur in a separate “render” process. Some application logic, such as JavaScript bindings and DOM access, will also run in the render process. The default process model will spawn a new render process for each unique origin (scheme + domain). Other processes will be spawned as needed, such as “plugin” processes to handle plugins like Flash and “gpu” processes to handle accelerated compositing.
CEF3 runs with multiple processes. The main process that handles window creation, drawing, and network access is called the browser process. This is usually the same process as the host application; most of the application logic will run in the browser process. Rendering HTML and JavaScript using the Blink engine takes place in a separate rendering process. Some application logic, such as JavaScript bindings and DOM access, will also run in the rendering process. The default process model will run a new rendering process for each unique source address (Scheme +domain). Other processes are generated as needed, such as plug-in processes that process plug-ins such as Flash and GPU processes that process accelerated synthesis. Based on the above documents, let’s sort them out:
Browser Process:
- Window creation, drawing
- Network access
- .
Renderer Process:
-
Render HTML through the Blink engine
-
JavaScript execution (V8 engine)
-
.
It is important to note that the browser process that draws the window does not mean that the HTML content is drawn, but the shell of the form that hosts the web content, and the render process is not used to create the form. Next, I will start with the official CefSimple Demo source code and introduce the Cef concept step by step.
Originally, I wanted to use the simple-CEF written in the previous article for source code parsing, but in order to make this article relatively independent, I still decided to use the official Demo: CEFSimple for source code parsing. Although not very different from the content of the Simple-CEF project. It is important to note that the following source code in the parsing, will be appropriate to delete and change, the reader had better compare the source to read better. PS: the source code shows…… Indicates that the sample code has been deleted.
cefsimple_win.cc
/ /...
// Entry point function for all processes.
int APIENTRY wWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow) {
/ /...
// CEF applications have multiple sub-processes (render, plugin, GPU, etc)
// that share the same executable. This function checks the command-line and,
// if this is a sub-process, executes the appropriate logic.
int exit_code = CefExecuteProcess(main_args, nullptr, sandbox_info);
if (exit_code >= 0) {
// The sub-process has completed so return here.
return exit_code;
}
/ /...
// SimpleApp implements application-level callbacks for the browser process.
// It will create the first browser instance in OnContextInitialized() after
// CEF has initialized.
CefRefPtr<SimpleApp> app(new SimpleApp);
// Initialize CEF.
CefInitialize(main_args, settings, app.get(), sandbox_info);
// Run the CEF message loop. This will block until CefQuitMessageLoop() is
// called.
CefRunMessageLoop(a);// Shut down CEF.
CefShutdown(a);return 0;
}
Copy the code
The first important point is:
// CEF applications have multiple sub-processes (render, plugin, GPU, etc)
// that share the same executable. This function checks the command-line and,
// if this is a sub-process, executes the appropriate logic.
int exit_code = CefExecuteProcess(main_args, nullptr, sandbox_info);
if (exit_code >= 0) {
// The sub-process has completed so return here.
return exit_code;
}
Copy the code
This code looks a little strange, but the English translation is as follows:
CEF applications create multiple sub-processes (render, plugin, GPU processing, etc.) but share a single executable. The following function checks the command line and, if it is confirmed to be a child process, executes the associated logic.
Then, we look at the function: CefExecuteProcess:
///
// This function should be called from the application entry point function to
// execute a secondary process. It can be used to run secondary processes from
// the browser client executable (default behavior) or from a separate
// executable specified by the CefSettings.browser_subprocess_path value. If
// called for the browser process (identified by no "type" command-line value)
// it will return immediately with a value of -1. If called for a recognized
// secondary process it will block until the process should exit and then return
// the process exit code. The |application| parameter may be empty. The
// |windows_sandbox_info| parameter is only used on Windows and may be NULL (see
// cef_sandbox_win.h for details).
///
/*--cef(api_hash_check,optional_param=application, optional_param=windows_sandbox_info)--*/
int CefExecuteProcess(const CefMainArgs& args,
CefRefPtr<CefApp> application,
void* windows_sandbox_info);
Copy the code
Translation:
This function should be called at the entry function of the application to execute a child process. It can be used to start a child process by executing an executable that can be either the current browser client executable (the default behavior) or an executable on the path specified by setting cefsettings.browser_subprocess_PATH. If called for a browser process (with no “type” argument on the launch command line), this function immediately returns -1. If identified as a child process when called, the function blocks until the child exits and returns the return code for the child’s exit. The application argument can be null. The windows_sandbox_info parameter can only be used on Windows or set to NULL (see cef_sandbox_win.h for details)
It is not difficult to infer from this passage that when CEF starts in a multi-process architecture, it starts its own executable multiple times. At startup, certain identifiers are passed in as command-line arguments, determined internally by CefExecuteProcess. If it is the main process, the function immediately returns -1 and the program continues, so that all subsequent code runs in the main process. If it is a child process (rendering process, etc.), then the function blocks until the child process is finished, returns a value greater than or equal to 0, and exits directly from main.
This is the end of the CefExecuteProcess analysis, you can read the official documentation for details, let’s continue the subsequent code analysis:
// SimpleApp implements application-level callbacks for the browser process.
// It will create the first browser instance in OnContextInitialized() after
// CEF has initialized.
CefRefPtr<SimpleApp> app(new SimpleApp);
Copy the code
The notes are translated as follows
SimpleApp implements application-level callbacks to browser processes. After the instance CEF is initialized (initialized), the first Browser instance is created in OnContextInitialized
Looking at the declaration for SimpleApp, we see that the class inherits from CefApp:
class SimpleApp : public CefApp, public CefBrowserProcessHandler { public: SimpleApp(); . }Copy the code
And that brings us to the first important class: CefApp.
CefApp
In the official CefApp document, there is a one-sentence introduction:
The CefApp interface provides access to process-specific callbacks.
The CefApp interface provides callback access to the specified process.
When I first saw CefApp, I thought of the multi-process architecture of CEF mentioned above, combined with the CefClient mentioned later, I thought that the so-called CefApp refers to the browser process, and CefClient corresponds to other processes (one App corresponds to multiple clients, how natural understanding). However, such a wrong understanding, let me in the reading of the code when a great detour.
First, let’s look at the CefApp header declaration:
class CefApp : public virtual CefBaseRefCounted {
public:
virtual void OnBeforeCommandLineProcessing(
const CefString& process_type,
CefRefPtr<CefCommandLine> command_line) {}
virtual void OnRegisterCustomSchemes( CefRawPtr
registrar)
{}
virtual CefRefPtr<CefResourceBundleHandler> GetResourceBundleHandler(a) {
return nullptr;
}
virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler(a) {
return nullptr;
}
virtual CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler(a) {
return nullptr; }};Copy the code
Let’s start with two of the methods that are the focus of this article: GetBrowserProcessHandler and GetRenderProcessHandler. Their documentation is commented as follows:
///
// Return the handler for functionality specific to the browser process. This
// method is called on multiple threads in the browser process.
// Return the handler for the browser process specific function. This method is called on multiple threads in the browser process.
///
virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler(a)
///
// Return the handler for functionality specific to the render process. This
// method is called on the render process main thread.
// Return the handler for the rendering process specific function. This method is called on the main thread in the render process.
///
virtual CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler(a)
Copy the code
Readers of these comments may wonder: why does the comment say one moment in the browser process and the next in the rendering process? Would an instance of this class be used in more than one process? Yes and no. An instance of the class does the process and rendering process used in the browser, but we also know that resources are not Shared between two processes, including class instance, so the process of running in the browser process, will be used to CefApp an instantiation objects, while in the process of the operation of the rendering process, and will be used to CefApp another instantiation objects, They are all instances of the CefApp subclass, but they must not be the same instance object.
We can understand: a CefApp corresponds to a Process, and a Process can be a Browser Process (Browser Process), can be a Renderer Process (Renderer Process). Therefore, CefApp provides GetBrowserProcessHandler and GetRendererProcessHandler are in the process of related to the corresponding handler.
The implementation of these two methods is up to us, that is, we can programmatically return the handler, but these two methods are not called by our client code, but by CEF at some point in the running process. So, although wrote two GetXXXProcessHandler here, but in the browser process and rendering process would only call GetBrowserProcessHandler and GetRendererProcessHandler respectively.
According to the program is running point of view, when the browser process, CEF framework will be called when a CefApp: : GetBrowserProcessHandler BrowserProcessHandler instance defined by the us, This instance calls some of the methods it provides when appropriate (which methods are described later); When the rendering process, CEF framework will be called when a CefApp: : GetRendererProcessHandler get we define RendererProcessHandler instance, Then, when appropriate, call some of the methods in The RenererProcessHandler (which methods are described later).
In the CefSimple sample code, only one SimpleApp inherits from CefApp. This class also inherits from CefBrowserHandler, indicating that it is also a CefBrowserHandler. The GetBrowserProcessHandler implementation then returns itself. So how does CEF associate our CefApp instance with CEF running?
// SimpleApp implements application-level callbacks for the browser process.
// It will create the first browser instance in OnContextInitialized() after
// CEF has initialized.
CefRefPtr<SimpleApp> app(new SimpleApp);
// Initialize CEF.
CefInitialize(main_args, settings, app.get(), sandbox_info);
Copy the code
Notice that the app.get() parameter in CefInitialize relates our CefApp to the running of CEF. So, some readers may wonder, in the sample code, what you see is that the SimpleApp class we created inherits from CefApp and returns itself with GetBrowserProcessHandler to indicate that it is a callback instance of the browser process, but not the code that represents the rendering process? Indeed, CefSimple, as helloWorld level code, does not reflect this. In the cEFClient example code (the higher-order CEF example, which is also more complex), you’ll see:
Above is the browser process CefApp subclass ClientAppBrowser (” Client “here is the” Client “of the CefClient sample code, not to be confused with the cefClient class below).
You’ll also find a CefApp subclass, ClientAppRenderer:
You can even find a CefApp subclass called ClientAppOther:
So where are they used?
At this point, I believe most readers will be able to understand what I mean by CefApp as an abstraction of a process. The general process is to determine the type of the process by parsing –type= XXX (browser processes don’t have this command argument) from the command line using a utility function GetProcessType, then instantiating the corresponding CefApp subclass, and finally running the process through CefExecuteProcess.
Now that we’ve introduced the basic concepts of CefApp, we can move on to the analysis of SimpleApp.
We know that SimpleApp is a subclass of CefApp, and that instances of this class will only be used in the browser process because it implements the CefBrowserProcessHandler interface and has the following code:
// CefApp methods:
virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler(a)
OVERRIDE {
return this;
}
Copy the code
So in CEF, what callbacks can we customize as CefBrowserProcessHandler? Here’s the header declaration, and I’ve also written a summary comment:
class CefBrowserProcessHandler : public virtual CefBaseRefCounted {
public:
// Cookie processing is customized
virtual void GetCookieableSchemes(std::vector<CefString>& schemes,
bool& include_defaults) {}
// After the CEF context is initialized, it is called in the browser process UI thread.
virtual void OnContextInitialized(a) {}
// Customizable command line arguments for handling child process startup
virtual void OnBeforeChildProcessLaunch( CefRefPtr
command_line)
{}
// Print processing
virtual CefRefPtr<CefPrintHandler> GetPrintHandler(a) { return nullptr; }
// When customizing the message loop, the frequency of the message loop
virtual void OnScheduleMessagePumpWork(int64 delay_ms) {}
// Get the default CefClient
virtual CefRefPtr<CefClient> GetDefaultClient(a) { return nullptr; }};Copy the code
By reading the Handler’s header files, and each function call, we continue to read the SimpleApp has: : OnContextInitialized this function:
void SimpleApp::OnContextInitialized(a) {
CEF_REQUIRE_UI_THREAD(a); CefRefPtr<CefCommandLine> command_line = CefCommandLine::GetGlobalCommandLine(a);const bool enable_chrome_runtime =
command_line->HasSwitch("enable-chrome-runtime"); // Whether to enable the Chrome runtime
#if defined(OS_WIN) || defined(OS_LINUX)
// Create the browser using the Views framework if "--use-views" is specified
// via the command-line. Otherwise, create the browser using the native
// platform framework. The Views framework is currently only supported on
// Windows and Linux.
// If "--use-views" is specified on the command line, use CEF's own views framework.
// Otherwise use the OS native API. View Framework currently supports only Windows and Linux.
const bool use_views = command_line->HasSwitch("use-views");
#else
const bool use_views = false;
#endif
// SimpleHandler implements browser-level callbacks.
CefRefPtr<SimpleHandler> handler(new SimpleHandler(use_views));
// Specify CEF browser settings here.
CefBrowserSettings browser_settings;
std::string url;
// Check if a "--url=" value was provided via the command-line. If so, use
// that instead of the default URL.
url = command_line->GetSwitchValue("url");
if (url.empty())
url = "http://www.google.com";
if(use_views && ! enable_chrome_runtime) {// Create the BrowserView.
CefRefPtr<CefBrowserView> browser_view = CefBrowserView::CreateBrowserView(
handler, url, browser_settings, nullptr.nullptr.new SimpleBrowserViewDelegate());
// Create the Window. It will show itself after creation.
CefWindow::CreateTopLevelWindow(new SimpleWindowDelegate(browser_view));
} else {
// Information used when creating the native window.
CefWindowInfo window_info;
#if defined(OS_WIN)
// On Windows we need to specify certain flags that will be passed to
// CreateWindowEx().
window_info.SetAsPopup(NULL."cefsimple");
#endif
// Create the first browser window.
CefBrowserHost::CreateBrowser(window_info, handler, url, browser_settings,
nullptr.nullptr); }}Copy the code
For this code, I sorted out the following process, convenient for readers to read:
In this process, the three most critical parts are highlighted in red:
- SimpleHandler (CefClient subclass);
- Use CEF’s form view framework to create CefBrowserView and CefWindow.
- Build forms using the operating system native API.
In this process, subclass instances of CefClient are created, and then CefClient and forms are combined using the CEF API.
To use CEF’s own view framework, there are the following steps:
- First is called CefBrowserView: : CreateBrowserView CefBrowserView instance, the process will take CefClient instances and View objects through the API bindings.
- Call CefWindow: : CreateTopLevelWindow, incoming CefBrowserView instance to create the form.
To create a browser form using the OS native API, the main steps are as follows:
- Use CefWindowInfo to set the window handle
- Call CefBrowserHost: : CreateBrowser will correspond to the window handle of forms and CefClient bind
Of course, the creation of both forms involves the CEF forms module, which we won’t go into here, but both processes are dependent on an important class: CefClient. What is it? Next, we will introduce CefClient and do some source analysis on the SimpleHandler class (a subclass of CefClient).
CefClient
In the official documentation, the concept of CefClien is described:
The CefClient interface provides access to browser-instance-specific callbacks. A single CefClient instance can be shared among any number of browsers. Important callbacks include:
The CefClient interface provides access to browser instance-specific callbacks. A CefClient instance can be shared between any number of browsers. Important callbacks include:
- Handlers for things like browser life span, context menus, dialogs, display notifications, drag events, focus events, keyboard events and more. The majority of handlers are optional. See the documentation in cef_client.h for the side effects, if any, of not implementing a specific handler.
- All handlers, such as browser lifecycle, context menus, dialog boxes, display notifications, drag events, focus events, keyboard events, etc. Most handlers are optional. Refer to the documentation in cef_client.h for side effects, if any, of not implementing a particular handler.
- OnProcessMessageReceived which is called when an IPC message is received from the render process. See the “Inter-Process Communication” section for more information.
- OnProcessMessageReceived is called when an IPC message is received from the rendering process. For more information, see the Interprocess Communication section.
First we need to explain what a specific browser instance is. In fact, it refers to a browser instance generated by the following process:
CefRefPtr<CefBrowserView> browser_view = CefBrowserView::CreateBrowserView(
handler, url, browser_settings, nullptr.nullptr.new SimpleBrowserViewDelegate());
/ / or
CefBrowserHost::CreateBrowser(window_info, handler, url, browser_settings,
nullptr.nullptr);
Copy the code
The browser instance created in these two ways is a conceptual instance, not a browser window that you can see, but a host for the browser instance. Events that occur in the browser, such as lifecycle changes, dialogs, etc., are only called back by the various types of handlers returned by the CefClient and the methods provided by these Handler interface instances.
The following is the CefClient statement:
class CefClient : public virtual CefBaseRefCounted {
public:
virtual CefRefPtr<CefAudioHandler> GetAudioHandler(a) { return nullptr; }
virtual CefRefPtr<CefContextMenuHandler> GetContextMenuHandler(a) {
return nullptr;
}
virtual CefRefPtr<CefDialogHandler> GetDialogHandler(a) { return nullptr; }
virtual CefRefPtr<CefDisplayHandler> GetDisplayHandler(a) { return nullptr; }
virtual CefRefPtr<CefDownloadHandler> GetDownloadHandler(a) { return nullptr; }
virtual CefRefPtr<CefDragHandler> GetDragHandler(a) { return nullptr; }
/ /... There are a lot of handlers
}
Copy the code
The CefClient provides a number of GetXXXHandler methods. These methods will be called by CEF at the appropriate time to get the corresponding Handler and then call the methods in the returned Handler. For example, the Title of the HTML page, when there is a change is called CefClient: : CefDisplayHandler () to get a CefDisplayHandler instance, Then call the CefDisplayHandler: : OnTitleChange, and the process is not what we call, but the CEF framework. Only the concrete implementation is written by our client code.
So now think about it, why does the CefClient exist? In my opinion, the main reasons are as follows:
The various callback events in CefClient essentially take place in the render process. Because every time a browser instance (not a browser process) is created, a corresponding rendering process is created (or, due to configuration, it may share one, so let’s consider multiple one-to-one by default). After the various V8 events, download events, display events and other events occurred in the rendering process are triggered, they will be sent to the browser process through inter-process communication, and then find the related CefClient in the browser process, and then find the corresponding Handler from the CefClient, and call back the corresponding method of the Handler.
That is, events that occur in the render process are abstracted from the browser process CefClient, rather than directly from the browser process processor, because a browser process may create multiple render processes, allowing CefClient to act as the middle layer to avoid coupling.
Of course, the documentation also points out that CefClient instances and browser instances can not be one-to-one, and that multiple browser instances can share a CefClient, so we can also summarize one point about Cefclients: do not write stateful Cefclients unless it is necessary.
So far, we start with the source code of Demo, and have an overall understanding of CefApp and CefClient. Readers can read the official document to have a deeper understanding: the official document.