Chromium Embedded Framework 3 - Bare Bones
Update (2014-08-23): I have a few updated notes I’ve included at the bottom of this article.
“The Chromium Embedded Framework (CEF) is an open source project founded by Marshall Greenblatt in 2008 to develop a Web browser control based on the Google Chromium project.” (stolen from the project’s web site) Over the last few weeks I’ve spent a number of hours reading up on how to integrate CEF into an application. The project’s forum provides a nice support area where people can ask for help and the source distribution comes with an example application called “cefclient” that uses CEF to show how to build an application with it. The problem that I had, though, is that while “cefclient” is a nice resource if you’re looking for an advanced implementation on how to integrate CEF as a whole, but if you’re looking to start out with a complete bare-bones implementation that just barely works it’s overkill. You’re not quite sure where to start and the example in the General Usage page of the wiki is currently either out of date or referencing a different version of CEF than I’m working with. Once I was able to get the most basic implementation running, I decided it would be good to document how to do it as the resources I was able to find were either out of date or unavailable.
In my case, I wanted to use Chromium Embedded Framework 3 (CEF3). From what I’ve read, this is the newest way to use CEF to embed Chromium and it’s using the official Chromium Content API. It’s a multi-process implementation (unlike the single-process that CEF1 uses) and sounds like it will be the better supported version going forward. There are some trade-offs when using this version, though, and you can see those by browsing the CEF project site. If you want more detail on what any of the classes or functions I’m referencing do, the project has a good CEF3 API reference. To be honest, at this point I don’t have a complete understanding of how CEF works internally yet, so there are some cases where it’s “just do this because it works”.
The CefClient
Your application’s implementation of the CefClient class is a hook into a number of the lower level functions you need for the browser to interact with the display and for you to interact with the browser. It enables you to provide your own implementations for many things, such as a way to handle context menus, a way to handle Javascript dialogs, geolocation and others. Each “subsystem” (as I guess you’d call them) has its own handler that you can implement (such as CefGeolocationHandler for geolocation). Take a look at the ClientHandler class in the cefclient code that comes with the CEF source to see all your different options, as well as how to implement them. You don’t need to provide an implementation of them by default, though. As long as you have a class that inherits from CefClient that implements CEF’s handy reference counting (CefRefPtr) you’re good to go. There’s a default implementation built-in for many of the systems and the ones that don’t have one aren’t required for a minimal implementation.
Instead of explaining the bare bones implementation of the CefClient line by line, here’s the full implementation since it’s so basic:
1// include/bareboneshandler.h
2
3#ifndef BAREBONESHANDLER_H_
4#define BAREBONESHANDLER_H_
5
6#include "include/cef_client.h"
7
8class BareBonesHandler : public CefClient {
9
10public:
11 BareBonesHandler() { }
12 ~BareBonesHandler() { }
13
14 // Note that any of the IMPLEMENT_WHATEVER
15 // macros that come with CEF can (and do) set
16 // access modifiers, so you'll want them after
17 // everything else in your class or you may be
18 // in for a surprise when the access of a member
19 // isn't what you expect it to be!
20 IMPLEMENT_REFCOUNTING(BareBonesHandler);
21};
22
23#endif
You’ll see the standard header stuff in there along with the full implementation of the class. I decided not to namespace it because it would be pointless for this example. There’s an empty constructor and deconstructor, and the only thing actually implemented on the class is the macro provided by CEF to implement the reference counting. Make note of my comment if you plan on using any of those macros!
For a more advanced version of this, take a look at ClientHandler in the cefclient source.
The Code To Actually Run It!
The next (and only other) part to a bare bones implementation of CEF is the main() method. In my current implementation I’m running only on Linux, so I’m using GTK+ as my toolkit. As a warning, though, I’ve never used GTK+ before so the code I have may not be the most elegant. The reason I’m using GTK+ instead of something I’m more familiar with like Qt is that CEF comes by default with an implementation for GTK+ 2. Based on some comments I’ve read during my research it should be possible to use another toolkit, but you’d have to write your own implementation of it.
First, we need to include the headers that are needed:
1#include <gtk/gtk.h>
2
3#include "include/cef_app.h"
4
5#include "bareboneshandler.h"
gtk.h and bareboneshandler.h are probably obvious, but the cef_app.h one may not be. CEF has a CefApp class that you can inherit from to customize the global functionality of your implementation but in a bare bones implementation you don’t need it. The reason cef_app.h is included is because it also defines a couple other functions and classes that we need to run, such as CefExecuteProcess(), CefInitialize() and CefShutdown(). These functions set up CEF and I’ll go over those shortly.
Next is a global reference to the handler class we created:
1CefRefPtr<BareBonesHandler> g_handler;
After that we begin the actual main function and create an instance of the CefMainArgs class that allows CEF command line switches to be passed on to CEF:
1int main(int argc, char* argv[]) {
2 CefMainArgs main_args(argc, argv);
Once in the main function, it’s time to either spawn off some additional processes (to support the multi-process model that CEF3 uses) or to just block until it’s time to exit. The actual functionality of this method is defined in the API docs I mention above:
1 int exitCode = CefExecuteProcess(main_args, NULL);
2 if (exitCode >= 0) {
3 return exitCode;
4 }
Now that the additional process is taken care of, it’s time to intialize CEF itself. Using the CefInitialize() function we pass in the main_args, an instance of the CefSettings (where you would specify any customized settings if you wanted) as well as an optional CefApp instance:
1 CefSettings settings;
2 CefInitialize(main_args, settings, NULL);
Next it’s on to initializing GTK+ and setting up the widgets that will be displayed in the application:
1 GtkWidget* window;
2 GtkWidget* vbox;
3 GtkWidget* hbox;
4
5 gtk_init(&argc, &argv);
6
7 window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
8 gtk_window_set_title(GTK_WINDOW(window), "CEF3 Bare Bones");
9 // Set the window to 400x400
10 gtk_window_set_default_size(GTK_WINDOW(window), 400, 400);
11
12 vbox = gtk_vbox_new(FALSE, 0);
13 gtk_container_add(GTK_CONTAINER(window), vbox);
14
15 hbox = gtk_hbox_new(FALSE, 0);
16 gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
17
18 g_signal_connect(window, "destroy", G_CALLBACK(destroy), NULL);
The “destroy” signal is connected to a callback method called destroy that I’ve implemented outside the main method to tell CEF to exit out of the message loop and shut down:
1void destroy(void) {
2 // Tells CEF to quit its message loop so the application can exit.
3 CefQuitMessageLoop();
4}
The next code block is rather long because I wanted to capture the entire context of what’s happening:
1 CefBrowserSettings browserSettings;
2 CefWindowInfo info;
3
4 g_handler = new BareBonesHandler();
5
6 info.SetAsChild(hbox);
7 CefBrowserHost::CreateBrowserSync(info, g_handler.get(),
8 "http://code.google.com", browserSettings);
9 CefBrowserHost::CreateBrowserSync(info, g_handler.get(),
10 "http://www.github.com", browserSettings);
11
12 info.SetAsChild(vbox);
13 CefBrowserHost::CreateBrowserSync(info, g_handler.get(),
14 "http://www.google.com", browserSettings);
15
16 gtk_widget_show_all(window);
In this block, we first define an instance of the CefBrowserSettings. This (I believe) is where you would provide any browser frame specific settings. After that, we define an instance of CefWindowInfo. This is a class that has a per-platform implementation to hook CEF into whatever widget toolkit you’re using. On Windows it takes MFC window handles, OS X it takes Cocoa and on Linux it takes GTK+.
Before creating any actual browser windows, though, we want to create an instance of our BareBonesHandler that implements CefClient to pass it to each browser. Once that’s done, it’s time to actually create the browsers!
Using the CefWindowInfo.SetAsChild() method, we specify the widget that the browser will be created into. For the first two frames we just want them to show up in the hbox GTK+ container, so that’s what’s passed in to SetAsChild().
Next is where the magic happens. We make a call to CefBrowserHost’s static CreateBrowserSync() method to create the browser window. We pass in the CefWindowInfo that has the child to create the browser in, a reference to the BareBonesHandler (using CefRefPtr’s get() method to increment the counter), a default URL to load and the settings for the browser to use.
After doing that twice, we set the vertical box as the child to create into using SetAsChild() again and then call CreateBrowserSync() for a third time.
Last of all, we call the GTK+ function to show all the widgets in the window.
At this point there are only two things left to do before your browser is actually up and running! Those are to call the CefRunMessageLoop() (you don’t call your normal widget toolkit’s loop) and then call CefShutdown(). CefRunMessageLoop() will block until CefQuitMessageLoop() is called (in the destroy method), then it will tell CEF to shut everything down with CefShutdown():
1 CefRunMessageLoop();
2
3 CefShutdown();
4
5 return 0;
6}
That’s it! You should now have all the code you need to use a bare bones version of CEF! To help pull it all together, here’s the full file of what we just created:
1// barebones.cc
2
3#include <gtk/gtk.h>
4
5#include "include/cef_app.h"
6
7#include "bareboneshandler.h"
8
9CefRefPtr<BareBonesHandler> g_handler;
10
11void destroy(void) {
12 // Tells CEF to quit its message loop so the application can exit.
13 CefQuitMessageLoop();
14}
15
16int main(int argc, char* argv[]) {
17 CefMainArgs main_args(argc, argv);
18
19 int exitCode = CefExecuteProcess(main_args, NULL);
20 if (exitCode >= 0) {
21 return exitCode;
22 }
23
24 CefSettings settings;
25 CefInitialize(main_args, settings, NULL);
26
27 GtkWidget* window;
28 GtkWidget* vbox;
29 GtkWidget* hbox;
30
31 gtk_init(&argc, &argv);
32
33 window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
34 gtk_window_set_title(GTK_WINDOW(window), "CEF3 Bare Bones");
35 // Set the window to 400x400
36 gtk_window_set_default_size(GTK_WINDOW(window), 400, 400);
37
38 vbox = gtk_vbox_new(FALSE, 0);
39 gtk_container_add(GTK_CONTAINER(window), vbox);
40
41 hbox = gtk_hbox_new(FALSE, 0);
42 gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
43
44 g_signal_connect(window, "destroy", G_CALLBACK(destroy), NULL);
45
46 CefBrowserSettings browserSettings;
47 CefWindowInfo info;
48
49 g_handler = new BareBonesHandler();
50
51 info.SetAsChild(hbox);
52 CefBrowserHost::CreateBrowserSync(info, g_handler.get(),
53 "http://code.google.com", browserSettings);
54 CefBrowserHost::CreateBrowserSync(info, g_handler.get(),
55 "http://www.github.com", browserSettings);
56
57 info.SetAsChild(vbox);
58 CefBrowserHost::CreateBrowserSync(info, g_handler.get(),
59 "http://www.google.com", browserSettings);
60
61 gtk_widget_show_all(window);
62
63 CefRunMessageLoop();
64
65 CefShutdown();
66
67 return 0;
68}
Building and Running
I would be remiss if I didn’t explain how to build the code, though. There’s one step that wasn’t completely intuitive to me that took me hours upon hours to get working. Hopefully this post will save you a bunch of time!
Here’s the command to build it with GCC:
1g++ -Iinclude -Icef3 -o build/barebones `pkg-config --cflags gtk+-2.0` barebones.cc -Lcef3/bin/linux/x86_64/cef -lcef -lcef_dll_wrapper -Wl,-rpath,. `pkg-config --libs gtk+-2.0`
Update (2014-08-23): Added -Wl,-rpath,.
to the command based on feedback from
this thread.
You’ll see the standard include directories (I have an include directory and a cef3/include directory to keep things separated), an output of the binary into build/barebones, the GTK+ 2 cflags from pkg-config, and the barebones.cc file we created. After that is a flag to tell GCC where to find the libraries to link against and lastly the libraries.
Something to note here is that there are two libraries you need to link against (this is where I spent all the time). The cef library is the obvious one, it’s the shared library with everything in it. The cef_dll_wrapper, however, is not. cef_dll_wrapper is a static library that contains the C++ wrapper around the CEF C functions. Without it, you’ll run into many linking errors. It’s also good to note that CEF has pretty strict requirements about how the structure of the libraries and includes are set up in your project. There are some additional libraries and files (libffmpegsumo.so and locales/) for running that you’ll need as well. I’d highly recommend you take a look at my github repository I mention below to see how I have everything laid out. It could save you some time.
Once you have it building on your system, it’s time to run it! Since my project is a dashboard, I’ve been running it straight on X without any kind of window environment (via SSH) with this command:
1DISPLAY=:0 LD_LIBRARY_PATH=. xinit ./barebones --no-sandbox
The DISPLAY variable tells X to run it on the primary screen and the LD_LIBRARY_PATH tells it to look in the current directory for any linked libraries. The –no-sandbox option tells Chromium not to run with the sandbox. Take a look at this article for more info on that. It’s highly recommended you don’t run it that way in production because it’s a security risk.
When you get it all running, you should be rewarded with:
A Reference Repository
I’ve created a reference project containing all the code I showed above as well as a build of CEF (on the latest version of Arch Linux, 32- and 64-bit) and the include files. I was hoping it could be a starting point for people new to CEF3 to save a lot of the time and headaches I went through trying to figure everything out. You can find the repository at: https://github.com/aphistic/cef3barebones. At the time this was posted it currently only includes a GTK+ example, but I hope to add more platforms in the future as I work on my CEF-based projects.
A Few Annoyances
I have run into a few annoyances (which could also be called gotchas) with CEF, though. One of them is that the CEF include files hard-code the “include” directory in their include directives (such as #include “include/cef_app.h”). This forces me to lay out the project in a certain way. I would much prefer the include files to be in a cef/ directory, so you could do “cef/cef_app.h” instead since this would allow me to separate out the CEF includes from my project includes. The way it’s set up now I have to have a completely separate includes directory to do this.
One other annoyance (aside from the fact there’s a libcef_dll_wrapper.a at all) is that cef_dll_wrapper has paths to a number of other objects hard-coded in the compiled library and these paths are (I think) rather weird. I don’t know if this is a side effect of when I built them, just how GCC does it, or how CEF has it set up. Anyway, the static library has “../libcef_dll_wrapper/” hard-coded as the path. It would be nicer if it was in “./libcef_dll_wrapper” so I could have the .so and .a files at one level and the required files another level below those.
They’re rather small annoyances, though, and pretty easy to work around.
In Closing
All in all I’m quite happy and impressed with CEF now that I have it up and running. I’m glad there’s a way to embed webkit with V8 and all the other nice things Chromium provides into my project without having to write a lot of boilerplate or implementation code to do it. As I mentioned before, I hope this post saves you a lot of time and that your CEF projects turn out amazing!
Update (2014-08-23): Marshall Greenblatt responded to a post I made about this article on the official CEF forums with some notes that I have wrong in my article. I intended to get this article and the corresponding code updated from his feedback but I didn’t have time at that point and it was never finished. Since I see this article is getting traffic at times and I’d still like it to be a resource for anyone needing help, I’ve included his notes below:
- Windows uses standard WinAPI HWND handles. There are no MFC dependencies.
- You don’t need to call get() when passing a CefRefPtr<> unless the type stored in the CefRefPtr<> changes (for example from MyClient to CefClient).
- You can put the CEF header files in whatever directory you like (for example /path/to/cef/include) and use the compiler include path directive to point to the correct location (for example -I/path/to/cef).
- The libcef_dll_wrapper.a static library should contain all of the object code (.o files). If it doesn’t then there may be a bug in how we’re creating the static library.
I still intend to update the article but since it’s been so long since I’ve worked with CEF and this code I need to take some time to get acclimated with it again.