Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion documentation/doxygen/Doxyfile
Original file line number Diff line number Diff line change
Expand Up @@ -1123,7 +1123,6 @@ EXCLUDE_PATTERNS = */G__* \
*.xml \
*.dtd \
*/graf3d/eve7/glu/* \
*/gui/cefdisplay/* \
*/gui/qt6webdisplay/* \
*/tutorials/visualisation/webgui/qtweb/* \
*/math/mathcore/src/CDT*
Expand Down
2 changes: 1 addition & 1 deletion gui/cefdisplay/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ set(CEF_MAIN src/cef_main.cxx)

ROOT_LINKER_LIBRARY(${libname} ${CEF_sources} ${CEF_platform}
LIBRARIES ${CMAKE_DL_LIBS} ${CEF_LIBRARY} ${CEF_DLL_WRAPPER} ${CEF_LIB_DEPENDENCY}
DEPENDENCIES RHTTP ROOTWebDisplay)
DEPENDENCIES RHTTP ASImage ROOTWebDisplay)

target_compile_definitions(${libname} PRIVATE NDEBUG)

Expand Down
55 changes: 12 additions & 43 deletions gui/cefdisplay/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@

See details about [Chromium Embedded Framework](https://bitbucket.org/chromiumembedded/cef)

1. Current code tested with CEF3 branch 6998, Chromium 134 (April 2025)
Some older CEF versions (like 107 or 124) may also be supported.
1. Current code tested with CEF3 branch 778, Chromium 148 (June 2026)

2. Download binary code from [https://cef-builds.spotifycdn.com/index.html](https://cef-builds.spotifycdn.com/index.html)
and unpack it in directory without spaces and special symbols:

~~~
$ mkdir /d/cef
$ cd /d/cef/
$ wget https://cef-builds.spotifycdn.com/cef_binary_134.3.9%2Bg5dc6f2f%2Bchromium-134.0.6998.178_linux64_minimal.tar.bz2
$ tar xjf cef_binary_134.3.9+g5dc6f2f+chromium-134.0.6998.178_linux64_minimal.tar.bz2
$ wget https://cef-builds.spotifycdn.com/cef_binary_148.0.10%2Bg7ee53f5%2Bchromium-148.0.7778.218_linux64.tar.bz2
$ tar xjf cef_binary_148.0.10+g7ee53f5+chromium-148.0.7778.218_linux64.tar.bz2
~~~


Expand All @@ -24,7 +23,7 @@ See details about [Chromium Embedded Framework](https://bitbucket.org/chromiumem
4. Compile CEF to produce `libcef_dll_wrapper`:

~~~
$ cd cef_binary_134.3.9+g5dc6f2f+chromium-134.0.6998.178_linux64_minimal
$ cd xjf cef_binary_148.0.10+g7ee53f5+chromium-148.0.7778.218_linux64
$ mkdir build
$ cd build
$ cmake ..
Expand All @@ -34,13 +33,10 @@ See details about [Chromium Embedded Framework](https://bitbucket.org/chromiumem
5. Set CEF_ROOT variable to unpacked directory:

~~~
$ export CEF_ROOT=/d/cef/cef_binary_134.3.9+g5dc6f2f+chromium-134.0.6998.178_linux64_minimal
$ export CEF_ROOT=/d/cef/xjf cef_binary_148.0.10+g7ee53f5+chromium-148.0.7778.218_linux64
~~~

6. When configure ROOT compilation with `cmake -Dwebgui=ON -Dcefweb=ON ...`, CEF_ROOT shell variable should be set appropriately.
During compilation library `$ROOTSYS/lib/libROOTCefDisplay.so` and executable `$ROOTSYS/bin/cef_main`
should be created. Also check that several files like `icudtl.dat`, `v8_context_snapshot_blob.bin`, `snapshot_blob.bin`
copied into ROOT library directory
6. When configure ROOT compilation with `cmake -Dwebgui=ON -Dcefweb=ON ...`, CEF_ROOT shell variable should be set appropriately. During compilation library `$ROOTSYS/lib/libROOTCefDisplay.so` and executable `$ROOTSYS/bin/cef_main` should be created. Also check that several files like `icudtl.dat`, `v8_context_snapshot_blob.bin`, `snapshot_blob.bin` copied into ROOT library directory

7. Run ROOT with `--web=cef` argument to use CEF web display like:

Expand All @@ -51,7 +47,7 @@ See details about [Chromium Embedded Framework](https://bitbucket.org/chromiumem

## Compile libcef_dll_wrapper on Windows

1. Download binary win32 build like https://cef-builds.spotifycdn.com/cef_binary_95.7.12%2Bg99c4ac0%2Bchromium-95.0.4638.54_windows32.tar.bz2
1. Download binary win32 build like [win64](https://cef-builds.spotifycdn.com/cef_binary_148.0.10%2Bg7ee53f5%2Bchromium-148.0.7778.218_windows64.tar.bz2)

2. Extract in directory without spaces like `C:\Soft\cef`

Expand All @@ -62,45 +58,18 @@ See details about [Chromium Embedded Framework](https://bitbucket.org/chromiumem
$ cd C:\Soft\cef
$ mkdir build
$ cd build
$ cmake -G"Visual Studio 16 2019" -A Win32 -Thost=x64 ..
$ cmake ..
$ cmake --build . --config Release --target libcef_dll_wrapper
~~~

5. Before compiling ROOT, `set CEF_ROOT=C:\Soft\cef` variable


## Using plain CEF in ROOT batch mode on Linux
## Using plain CEF in ROOT batch mode on Linux and Windows

Default CEF builds, provided by [https://cef-builds.spotifycdn.com/index.html](https://cef-builds.spotifycdn.com/index.html), do
not include support of Ozone framework, which the only support headless mode in CEF. To run ROOT in headless (or batch) made with such CEF distribution,
one can use `Xvfb` server. Most simple way is to use `xvfb-run` utility like:
Default CEF builds, provided by [https://cef-builds.spotifycdn.com/index.html](https://cef-builds.spotifycdn.com/index.html), now (June 2026) **INCLUDES!** support of Ozone framework, which allows to run CEF in headless mode on Linux and Windows. So one can run different ROOT macros and tests in batch mode like

~~~
$ xvfb-run --server-args='-screen 0, 1024x768x16' root.exe --web=cef $ROOTSYS/tutorials/experimental/rcanvas/rline.cxx -q
$ cd $ROOTSYS/test
$ ./stressGraphics -b --web=cef
~~~

Or run `Xvfb` before starting ROOT:

~~~
$ Xvfb :99 &
$ export DISPLAY=:99
$ root.exe --web=cef $ROOTSYS/tutorials/experimental/rcanvas/rline.cxx -q
~~~


## Compile CEF with ozone support

Since March 2019 one can compile [CEF without X11](https://bitbucket.org/chromiumembedded/cef/issues/2296/), but such builds not provided.
Therefore to be able to use real headless mode in CEF, one should compile it from sources.
On [CEF build tutorial](https://bitbucket.org/chromiumembedded/cef/wiki/AutomatedBuildSetup.md) one can find complete compilation documentation.
Several Ubuntu distributions are supported by CEF, all others may require extra work. Once all depndencies are installed, CEF with ozone support can be compiled with following commands:

~~~
$ export GN_DEFINES="is_official_build=true use_sysroot=true use_allocator=none symbol_level=1 is_cfi=false use_thin_lto=false use_ozone=true"
$ python automate-git.py --download-dir=/home/user/cef --branch=4638 --minimal-distrib --client-distrib --force-clean --x64-build --build-target=cefsimple
~~~

With little luck one get prepared tarballs in `/home/user/cef/chromium/src/cef/binary_distrib`.
Just install it in the same way as described before in this document.
ROOT will automatically detect that CEF build with `ozone` support and will use it for both interactive and headless modes.

2 changes: 1 addition & 1 deletion gui/cefdisplay/inc/gui_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ class GuiHandler : public CefClient,

std::string MakePageUrl(THttpServer *serv, const std::string &addr);

static bool PlatformInit();
static void PlatformInit();

static std::string GetDataURI(const std::string& data, const std::string& mime_type);

Expand Down
8 changes: 5 additions & 3 deletions gui/cefdisplay/inc/simple_app.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,24 @@ class SimpleApp : public CefApp,
/*, public CefRenderProcessHandler */
public CefBrowserProcessHandler {
protected:
bool fUseViewes{false}; ///<! is views framework used
bool fUseViewes = false; ///<! is views framework used
bool fSupressLog = false; ///<! supress log output when possible
THttpServer *fFirstServer; ///<! first server
std::string fFirstUrl; ///<! first URL to open
std::string fFirstContent; ///<! first page content open
CefRect fFirstRect; ///<! original width
bool fFirstHeadless{false}; ///<! is first window is headless
RCefWebDisplayHandle *fNextHandle{nullptr}; ///< next handle where browser will be created
bool fNextHeadless = false; ///< if next handle display is headless

CefRefPtr<GuiHandler> fGuiHandler; ///<! normal handler

public:
SimpleApp(bool use_viewes,
SimpleApp(bool use_viewes, bool supress_log,
THttpServer *serv = nullptr, const std::string &url = "", const std::string &cont = "",
int width = 0, int height = 0, bool headless = false);

void SetNextHandle(RCefWebDisplayHandle *handle);
void SetNextHandle(RCefWebDisplayHandle *handle, bool headless);

// CefApp methods:
CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler() override { return this; }
Expand Down
94 changes: 66 additions & 28 deletions gui/cefdisplay/src/RCefWebDisplayHandle.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -87,47 +87,88 @@ std::unique_ptr<ROOT::RWebDisplayHandle> RCefWebDisplayHandle::CefCreator::Displ
if (!args.IsStandalone())
handle->fCloseBrowser = false;

Int_t wait_tmout = args.IsHeadless() ? gEnv->GetValue("WebGui.CefHeadlessTimeout", 30) : -1;

if (fCefApp) {
fCefApp->SetNextHandle(handle.get());
fCefApp->SetNextHandle(handle.get(), args.IsHeadless());

CefRect rect((args.GetX() > 0) ? args.GetX() : 0, (args.GetY() > 0) ? args.GetY() : 0,
(args.GetWidth() > 0) ? args.GetWidth() : 800, (args.GetHeight() > 0) ? args.GetHeight() : 600);

fCefApp->StartWindow(args.GetHttpServer(), args.GetFullUrl(), args.GetPageContent(), rect);

if (args.IsHeadless())
handle->WaitForContent(30, args.GetExtraArgs()); // 30 seconds
handle->WaitForContent(wait_tmout, args.GetExtraArgs());

return handle;
}

bool use_views = GuiHandler::PlatformInit();
GuiHandler::PlatformInit();
bool use_views = true;

TString env_use_views = gEnv->GetValue("WebGui.CefUseViews", "");
if ((env_use_views == "yes") || (env_use_views == "1"))
use_views = true;
else if ((env_use_views == "no") || (env_use_views == "0"))
use_views = false;

// Specify CEF global settings here.
CefSettings settings;

TString ceflog = gEnv->GetValue("WebGui.CefLogSeveriry", "fatal");
if (ceflog == "fatal")
settings.log_severity = LOGSEVERITY_FATAL;
else if (ceflog == "verbose")
settings.log_severity = LOGSEVERITY_VERBOSE;
else if (ceflog == "info")
settings.log_severity = LOGSEVERITY_INFO;
else if (ceflog == "warning")
settings.log_severity = LOGSEVERITY_WARNING;
else if (ceflog == "error")
settings.log_severity = LOGSEVERITY_ERROR;
else if (ceflog == "disable")
settings.log_severity = LOGSEVERITY_DISABLE;
else
settings.log_severity = LOGSEVERITY_FATAL;

bool supress_log = (settings.log_severity == LOGSEVERITY_DISABLE) ||
(settings.log_severity == LOGSEVERITY_FATAL);

TApplication *root_app = gROOT->GetApplication();

std::vector<const char *> cef_argv = { root_app->Argv(0) };

#ifdef OS_WIN
CefMainArgs main_args(GetModuleHandle(nullptr));

CefMainArgs main_args(args.IsHeadless() ? (HINSTANCE) 0 : GetModuleHandle(nullptr));

#else
TApplication *root_app = gROOT->GetApplication();

int cef_argc = 1;
const char *arg2 = nullptr, *arg3 = nullptr;
if (args.IsHeadless()) {
// arg2 = "--allow-file-access-from-files";
arg2 = "--disable-web-security";
cef_argc++;
if (use_views) {
arg3 = "--ozone-platform=headless";
cef_argc++;
}
cef_argv.emplace_back("--user-data-dir=.");
cef_argv.emplace_back("--allow-file-access-from-files");
cef_argv.emplace_back("--disable-web-security");
cef_argv.emplace_back("--disable-gpu");
cef_argv.emplace_back("--ignore-gpu-blocklist");
#ifdef OS_LINUX
cef_argv.emplace_back("--use-gl=swiftshader");
cef_argv.emplace_back("--enable-unsafe-swiftshader");
#endif
cef_argv.emplace_back("--off-screen-rendering-enabled");
if (use_views)
cef_argv.emplace_back("--ozone-platform=headless");
}

if (supress_log) {
cef_argv.emplace_back("--disable-logging");
cef_argv.emplace_back("--enable-logging=none");
cef_argv.emplace_back("--v=-1");
}
char *cef_argv[] = {root_app->Argv(0), (char *) arg2, (char *) arg3, nullptr};

CefMainArgs main_args(cef_argc, cef_argv);
cef_argv.emplace_back(nullptr);

CefMainArgs main_args(cef_argv.size() - 1, (char **) cef_argv.data());

#endif

// CEF applications have multiple sub-processes (render, plugin, GPU, etc)
Expand All @@ -146,8 +187,6 @@ std::unique_ptr<ROOT::RWebDisplayHandle> RCefWebDisplayHandle::CefCreator::Displ
// XSetErrorHandler(XErrorHandlerImpl);
// XSetIOErrorHandler(XIOErrorHandlerImpl);

// Specify CEF global settings here.
CefSettings settings;

TString cef_main = TROOT::GetBinDir() + "/cef_main";
cef_string_ascii_to_utf16(cef_main.Data(), cef_main.Length(), &settings.browser_subprocess_path);
Expand Down Expand Up @@ -177,17 +216,15 @@ std::unique_ptr<ROOT::RWebDisplayHandle> RCefWebDisplayHandle::CefCreator::Displ
settings.no_sandbox = true;
// if (gROOT->IsWebDisplayBatch()) settings.single_process = true;

// if (batch_mode)
// settings.windowless_rendering_enabled = true;
if (args.IsHeadless())
settings.windowless_rendering_enabled = true;

// settings.external_message_pump = true;
// settings.multi_threaded_message_loop = false;

std::string plog = "cef.log";
cef_string_ascii_to_utf16(plog.c_str(), plog.length(), &settings.log_file);

settings.log_severity = LOGSEVERITY_ERROR; // LOGSEVERITY_VERBOSE, LOGSEVERITY_INFO, LOGSEVERITY_WARNING,
// LOGSEVERITY_ERROR, LOGSEVERITY_DISABLE
// settings.uncaught_exception_stack_size = 100;
// settings.ignore_certificate_errors = true;

Expand All @@ -196,18 +233,19 @@ std::unique_ptr<ROOT::RWebDisplayHandle> RCefWebDisplayHandle::CefCreator::Displ
// SimpleApp implements application-level callbacks for the browser process.
// It will create the first browser instance in OnContextInitialized() after
// CEF has initialized.
fCefApp = new SimpleApp(use_views, args.GetHttpServer(), args.GetFullUrl(), args.GetPageContent(),
args.GetWidth() > 0 ? args.GetWidth() : 800,
args.GetHeight() > 0 ? args.GetHeight() : 600,
args.IsHeadless());
fCefApp = new SimpleApp(use_views, supress_log,
args.GetHttpServer(), args.GetFullUrl(), args.GetPageContent(),
args.GetWidth() > 0 ? args.GetWidth() : 800,
args.GetHeight() > 0 ? args.GetHeight() : 600,
args.IsHeadless());

fCefApp->SetNextHandle(handle.get());
fCefApp->SetNextHandle(handle.get(), args.IsHeadless());

// Initialize CEF for the browser process.
CefInitialize(main_args, settings, fCefApp.get(), nullptr);

if (args.IsHeadless()) {
handle->WaitForContent(30, args.GetExtraArgs()); // 30 seconds
handle->WaitForContent(wait_tmout, args.GetExtraArgs());
} else {
// Create timer to let run CEF message loop together with ROOT event loop
Int_t interval = gEnv->GetValue("WebGui.CefTimer", 10);
Expand Down
8 changes: 5 additions & 3 deletions gui/cefdisplay/src/gui_handler.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include <sstream>
#include <fstream>
#include <iostream>
#include <string>

#include "include/base/cef_bind.h"
Expand Down Expand Up @@ -175,21 +176,22 @@ bool GuiHandler::OnConsoleMessage(CefRefPtr<CefBrowser> browser,
switch (level) {
case LOGSEVERITY_WARNING:
if (fConsole > -1)
R__LOG_WARNING(CefWebDisplayLog()) << Form("CEF: %s:%d: %s", src.c_str(), line, message.ToString().c_str());
std::cout << TString::Format("CEF: %s:%d: %s", src.c_str(), line, message.ToString().c_str()) << std::endl;
break;
case LOGSEVERITY_ERROR:
if (fConsole > -2)
R__LOG_ERROR(CefWebDisplayLog()) << Form("CEF: %s:%d: %s", src.c_str(), line, message.ToString().c_str());
std::cerr << TString::Format("CEF: %s:%d: %s", src.c_str(), line, message.ToString().c_str()) << std::endl;
break;
default:
if (fConsole > 0)
R__LOG_DEBUG(0, CefWebDisplayLog()) << Form("CEF: %s:%d: %s", src.c_str(), line, message.ToString().c_str());
std::cout << TString::Format("CEF: %s:%d: %s", src.c_str(), line, message.ToString().c_str()) << std::endl;
break;
}

return true;
}


cef_return_value_t GuiHandler::OnBeforeResourceLoad(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
Expand Down
11 changes: 2 additions & 9 deletions gui/cefdisplay/src/gui_handler_linux.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,10 @@ int x11_errhandler( Display *dpy, XErrorEvent *err )
return 0;
}

bool GuiHandler::PlatformInit()
void GuiHandler::PlatformInit()
{
// install custom X11 error handler to avoid application exit in case of X11 failure
XSetErrorHandler( x11_errhandler );

#if CEF_VERSION_MAJOR > 130
return true; // use CEF view framework
#else
return false; // do not use CEF view framework
#endif
}

void GuiHandler::PlatformTitleChange(CefRefPtr<CefBrowser> browser, const CefString &title)
Expand Down Expand Up @@ -116,9 +110,8 @@ bool GuiHandler::PlatformResize(CefRefPtr<CefBrowser> browser, int width, int he

#else

bool GuiHandler::PlatformInit()
void GuiHandler::PlatformInit()
{
return true; // use view framework
}

void GuiHandler::PlatformTitleChange(CefRefPtr<CefBrowser>, const CefString &)
Expand Down
4 changes: 1 addition & 3 deletions gui/cefdisplay/src/gui_handler_mac.mm
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@

#include "include/cef_browser.h"


bool GuiHandler::PlatformInit()
void GuiHandler::PlatformInit()
{
return false; // MAC not yet support ozone and headless mode
}

void GuiHandler::PlatformTitleChange(CefRefPtr<CefBrowser> browser, const CefString &title)
Expand Down
Loading
Loading