From 0328efb4a678c997b6ca54db9dfbd7683a0bddc9 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Wed, 10 Jun 2026 14:53:19 +0200 Subject: [PATCH 1/6] [cef] enable batch image production with view framework Newest CEF builds include support of views framework, therefore one can create images and pdf in headless mode. Add several .rootrc parameter to be able configure CEF headless timeouts and CEF log settings --- gui/cefdisplay/CMakeLists.txt | 2 +- gui/cefdisplay/inc/gui_handler.h | 2 +- gui/cefdisplay/inc/simple_app.h | 5 +- gui/cefdisplay/src/RCefWebDisplayHandle.cxx | 77 ++++++++++++++------- gui/cefdisplay/src/gui_handler.cxx | 7 +- gui/cefdisplay/src/gui_handler_linux.cxx | 11 +-- gui/cefdisplay/src/gui_handler_mac.mm | 4 +- gui/cefdisplay/src/gui_handler_win.cc | 7 +- gui/cefdisplay/src/simple_app.cxx | 50 +++++++------ 9 files changed, 94 insertions(+), 71 deletions(-) diff --git a/gui/cefdisplay/CMakeLists.txt b/gui/cefdisplay/CMakeLists.txt index 32f2ad2c131e4..90a178b2ccb48 100644 --- a/gui/cefdisplay/CMakeLists.txt +++ b/gui/cefdisplay/CMakeLists.txt @@ -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) diff --git a/gui/cefdisplay/inc/gui_handler.h b/gui/cefdisplay/inc/gui_handler.h index ed8ed4270c24b..c230c7e8c5390 100644 --- a/gui/cefdisplay/inc/gui_handler.h +++ b/gui/cefdisplay/inc/gui_handler.h @@ -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); diff --git a/gui/cefdisplay/inc/simple_app.h b/gui/cefdisplay/inc/simple_app.h index c7f47697e6206..d85721ba8f168 100644 --- a/gui/cefdisplay/inc/simple_app.h +++ b/gui/cefdisplay/inc/simple_app.h @@ -35,7 +35,8 @@ class SimpleApp : public CefApp, /*, public CefRenderProcessHandler */ public CefBrowserProcessHandler { protected: - bool fUseViewes{false}; /// fGuiHandler; /// 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()); @@ -96,12 +98,13 @@ std::unique_ptr RCefWebDisplayHandle::CefCreator::Displ 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")) @@ -109,25 +112,54 @@ std::unique_ptr RCefWebDisplayHandle::CefCreator::Displ 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); + #ifdef OS_WIN CefMainArgs main_args(GetModuleHandle(nullptr)); #else TApplication *root_app = gROOT->GetApplication(); - int cef_argc = 1; - const char *arg2 = nullptr, *arg3 = nullptr; + std::vector cef_argv = { root_app->Argv(0) }; + 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("--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) @@ -146,8 +178,6 @@ std::unique_ptr 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); @@ -177,8 +207,8 @@ std::unique_ptr 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; @@ -186,8 +216,6 @@ std::unique_ptr RCefWebDisplayHandle::CefCreator::Displ 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; @@ -196,10 +224,11 @@ std::unique_ptr 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()); @@ -207,7 +236,7 @@ std::unique_ptr RCefWebDisplayHandle::CefCreator::Displ 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); diff --git a/gui/cefdisplay/src/gui_handler.cxx b/gui/cefdisplay/src/gui_handler.cxx index 6edfee46bb54f..c1ffc7afd32ae 100644 --- a/gui/cefdisplay/src/gui_handler.cxx +++ b/gui/cefdisplay/src/gui_handler.cxx @@ -175,21 +175,22 @@ bool GuiHandler::OnConsoleMessage(CefRefPtr 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()); + R__LOG_WARNING(CefWebDisplayLog()) << TString::Format("CEF: %s:%d: %s", src.c_str(), line, message.ToString().c_str()); break; case LOGSEVERITY_ERROR: if (fConsole > -2) - R__LOG_ERROR(CefWebDisplayLog()) << Form("CEF: %s:%d: %s", src.c_str(), line, message.ToString().c_str()); + R__LOG_ERROR(CefWebDisplayLog()) << TString::Format("CEF: %s:%d: %s", src.c_str(), line, message.ToString().c_str()); break; default: if (fConsole > 0) - R__LOG_DEBUG(0, CefWebDisplayLog()) << Form("CEF: %s:%d: %s", src.c_str(), line, message.ToString().c_str()); + R__LOG_DEBUG(0, CefWebDisplayLog()) << TString::Format("CEF: %s:%d: %s", src.c_str(), line, message.ToString().c_str()); break; } return true; } + cef_return_value_t GuiHandler::OnBeforeResourceLoad( CefRefPtr browser, CefRefPtr frame, diff --git a/gui/cefdisplay/src/gui_handler_linux.cxx b/gui/cefdisplay/src/gui_handler_linux.cxx index 6996f024c6eac..87a719421acc3 100644 --- a/gui/cefdisplay/src/gui_handler_linux.cxx +++ b/gui/cefdisplay/src/gui_handler_linux.cxx @@ -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 browser, const CefString &title) @@ -116,9 +110,8 @@ bool GuiHandler::PlatformResize(CefRefPtr browser, int width, int he #else -bool GuiHandler::PlatformInit() +void GuiHandler::PlatformInit() { - return true; // use view framework } void GuiHandler::PlatformTitleChange(CefRefPtr, const CefString &) diff --git a/gui/cefdisplay/src/gui_handler_mac.mm b/gui/cefdisplay/src/gui_handler_mac.mm index 579b79945dfe6..ff0b736021f4c 100644 --- a/gui/cefdisplay/src/gui_handler_mac.mm +++ b/gui/cefdisplay/src/gui_handler_mac.mm @@ -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 browser, const CefString &title) diff --git a/gui/cefdisplay/src/gui_handler_win.cc b/gui/cefdisplay/src/gui_handler_win.cc index a6eade413d23c..54b86cb23ce23 100644 --- a/gui/cefdisplay/src/gui_handler_win.cc +++ b/gui/cefdisplay/src/gui_handler_win.cc @@ -22,13 +22,8 @@ #include "include/cef_browser.h" #include "include/cef_config.h" -bool GuiHandler::PlatformInit() +void GuiHandler::PlatformInit() { -#ifdef CEF_X11 - return false; // compiled without ozone support -#else - return true; // compiled with ozone support -#endif } void GuiHandler::PlatformTitleChange(CefRefPtr browser, const CefString &title) diff --git a/gui/cefdisplay/src/simple_app.cxx b/gui/cefdisplay/src/simple_app.cxx index 9dc77b44a0167..1d30f5a8020e1 100644 --- a/gui/cefdisplay/src/simple_app.cxx +++ b/gui/cefdisplay/src/simple_app.cxx @@ -109,10 +109,10 @@ class SimpleBrowserViewDelegate : public CefBrowserViewDelegate { } // namespace -SimpleApp::SimpleApp(bool use_viewes, +SimpleApp::SimpleApp(bool use_viewes, bool supress_log, THttpServer *serv, const std::string &url, const std::string &cont, int width, int height, bool headless) - : CefApp(), CefBrowserProcessHandler(), fUseViewes(use_viewes), fFirstServer(serv), fFirstUrl(url), fFirstContent(cont), fFirstHeadless(headless) + : CefApp(), CefBrowserProcessHandler(), fUseViewes(use_viewes), fSupressLog(supress_log), fFirstServer(serv), fFirstUrl(url), fFirstContent(cont), fFirstHeadless(headless) { fFirstRect.Set(0, 0, width, height); @@ -122,10 +122,10 @@ SimpleApp::SimpleApp(bool use_viewes, // platform framework. The Views framework is currently only supported on // Windows and Linux. #else - if (fUseViewes) { - R__LOG_ERROR(CefWebDisplayLog()) << "view framework does not supported by CEF on the platform, switching off"; - fUseViewes = false; - } +// if (fUseViewes) { +// R__LOG_ERROR(CefWebDisplayLog()) << "view framework does not supported by CEF on the platform, switching off"; +// fUseViewes = false; +// } #endif } @@ -145,6 +145,11 @@ void SimpleApp::OnRegisterCustomSchemes(CefRawPtr registrar) void SimpleApp::OnBeforeCommandLineProcessing(const CefString &process_type, CefRefPtr command_line) { + if (fSupressLog) { + command_line->AppendSwitchWithValue("v", "-1"); + command_line->AppendSwitch("disable-logging"); + command_line->AppendSwitchWithValue("enable-logging", "none"); + } // command_line->AppendSwitch("allow-file-access-from-files"); // command_line->AppendSwitch("disable-web-security"); // if (fBatch) { @@ -156,6 +161,11 @@ void SimpleApp::OnBeforeCommandLineProcessing(const CefString &process_type, Cef void SimpleApp::OnBeforeChildProcessLaunch(CefRefPtr command_line) { + if (fSupressLog) { + command_line->AppendSwitchWithValue("v", "-1"); + command_line->AppendSwitch("disable-logging"); + command_line->AppendSwitchWithValue("enable-logging", "none"); + } // command_line->AppendSwitch("allow-file-access-from-files"); // command_line->AppendSwitch("disable-web-security"); // if (fLastBatch) { @@ -181,24 +191,24 @@ void SimpleApp::StartWindow(THttpServer *serv, const std::string &addr, const st { CEF_REQUIRE_UI_THREAD(); + bool is_batch = addr.empty() && !cont.empty(); + if (!fGuiHandler) fGuiHandler = new GuiHandler(fUseViewes); std::string url; - //bool is_batch = false; - - if(addr.empty() && !cont.empty()) { + if(is_batch) url = fGuiHandler->AddBatchPage(cont); - // is_batch = true; - } else if (serv) { + else if (serv) url = fGuiHandler->MakePageUrl(serv, addr); - } else { + else url = addr; - } // Specify CEF browser settings here. CefBrowserSettings browser_settings; + if (is_batch) + browser_settings.windowless_frame_rate = 30; // browser_settings.plugins = STATE_DISABLED; // browser_settings.file_access_from_file_urls = STATE_ENABLED; // browser_settings.universal_access_from_file_urls = STATE_ENABLED; @@ -230,17 +240,13 @@ void SimpleApp::StartWindow(THttpServer *serv, const std::string &addr, const st // On Windows we need to specify certain flags that will be passed to // CreateWindowEx(). window_info.SetAsPopup(0, "cefsimple"); - //if (is_batch) - // window_info.SetAsWindowless(GetDesktopWindow()); - #elif defined(OS_LINUX) - if (!rect.IsEmpty()) window_info.SetAsChild(0, rect); - //if (is_batch) - // window_info.SetAsWindowless(kNullWindowHandle); + if (is_batch) + window_info.SetAsWindowless(GetDesktopWindow()); #else if (!rect.IsEmpty()) - window_info.SetAsChild(0, rect.x, rect.y, rect.width, rect.height ); - //if (is_batch) - // window_info.SetAsWindowless(kNullWindowHandle); + window_info.SetAsChild(0, rect); + if (is_batch) + window_info.SetAsWindowless(kNullWindowHandle); #endif // Create the first browser window. From 8f132d4c100aacfe98c89628eac7ed54f186b53f Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Thu, 11 Jun 2026 07:58:42 +0200 Subject: [PATCH 2/6] [cef] add documentation for new rootrc parameters --- gui/webdisplay/src/RWebWindowsManager.cxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gui/webdisplay/src/RWebWindowsManager.cxx b/gui/webdisplay/src/RWebWindowsManager.cxx index f6e628b9635df..7ad3b5bc4e531 100644 --- a/gui/webdisplay/src/RWebWindowsManager.cxx +++ b/gui/webdisplay/src/RWebWindowsManager.cxx @@ -788,6 +788,8 @@ std::string RWebWindowsManager::GetUrl(RWebWindow &win, bool remote, std::string /// WebGui.ReconnectTmout: time to reconnect for already existing connection, if negative - no reconnecting possible (default 15 s) /// WebGui.CefTimer: periodic time to run CEF event loop (default 10 ms) /// WebGui.CefUseViews: "yes" - enable / "no" - disable usage of CEF views frameworks (default is platform/version dependent) +/// WebGui.CefLogSeveriry: "disable", "fatal", "error", "warning", "info", "verbose" (default is "fatal") +/// WebGui.CefHeadlessTimeout: timeout to wait produce result of headless output (default is 30 s) /// WebGui.OperationTmout: time required to perform WebWindow operation like execute command or update drawings /// WebGui.RecordData: if specified enables data recording for each web window; "yes" or "no" (default) /// WebGui.JsonComp: compression factor for JSON conversion, if not specified - each widget uses own default values From 4a89400528de786f61666d8b4ca6ade27456d428 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Thu, 11 Jun 2026 13:36:13 +0200 Subject: [PATCH 3/6] [cef] use cout/cerr for logging Otherwise it is very difficult to enable output from JS console --- gui/cefdisplay/src/gui_handler.cxx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gui/cefdisplay/src/gui_handler.cxx b/gui/cefdisplay/src/gui_handler.cxx index c1ffc7afd32ae..3c172d73409ce 100644 --- a/gui/cefdisplay/src/gui_handler.cxx +++ b/gui/cefdisplay/src/gui_handler.cxx @@ -21,6 +21,7 @@ #include #include +#include #include #include "include/base/cef_bind.h" @@ -175,15 +176,15 @@ bool GuiHandler::OnConsoleMessage(CefRefPtr browser, switch (level) { case LOGSEVERITY_WARNING: if (fConsole > -1) - R__LOG_WARNING(CefWebDisplayLog()) << TString::Format("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()) << TString::Format("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()) << TString::Format("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; } From 37820b7b9350c2a0fc08c41410d8e6d7e7797475 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Thu, 11 Jun 2026 08:02:17 +0200 Subject: [PATCH 4/6] [cef] let use gpu emultation in offscreen rendering Plain GPU is not available in headless, but one can enable flags for emultation of WebGL --- gui/cefdisplay/inc/simple_app.h | 3 ++- gui/cefdisplay/src/RCefWebDisplayHandle.cxx | 7 +++++-- gui/cefdisplay/src/simple_app.cxx | 22 ++------------------- 3 files changed, 9 insertions(+), 23 deletions(-) diff --git a/gui/cefdisplay/inc/simple_app.h b/gui/cefdisplay/inc/simple_app.h index d85721ba8f168..f9540bed25d3a 100644 --- a/gui/cefdisplay/inc/simple_app.h +++ b/gui/cefdisplay/inc/simple_app.h @@ -43,6 +43,7 @@ class SimpleApp : public CefApp, CefRect fFirstRect; /// fGuiHandler; /// GetBrowserProcessHandler() override { return this; } diff --git a/gui/cefdisplay/src/RCefWebDisplayHandle.cxx b/gui/cefdisplay/src/RCefWebDisplayHandle.cxx index d6c107b968b8c..00fe179f10429 100644 --- a/gui/cefdisplay/src/RCefWebDisplayHandle.cxx +++ b/gui/cefdisplay/src/RCefWebDisplayHandle.cxx @@ -90,7 +90,7 @@ std::unique_ptr RCefWebDisplayHandle::CefCreator::Displ 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); @@ -146,6 +146,9 @@ std::unique_ptr RCefWebDisplayHandle::CefCreator::Displ 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"); + cef_argv.emplace_back("--use-gl=swiftshader"); + cef_argv.emplace_back("--enable-unsafe-swiftshader"); cef_argv.emplace_back("--off-screen-rendering-enabled"); if (use_views) cef_argv.emplace_back("--ozone-platform=headless"); @@ -230,7 +233,7 @@ std::unique_ptr RCefWebDisplayHandle::CefCreator::Displ 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); diff --git a/gui/cefdisplay/src/simple_app.cxx b/gui/cefdisplay/src/simple_app.cxx index 1d30f5a8020e1..ccdefa5379467 100644 --- a/gui/cefdisplay/src/simple_app.cxx +++ b/gui/cefdisplay/src/simple_app.cxx @@ -131,9 +131,10 @@ SimpleApp::SimpleApp(bool use_viewes, bool supress_log, } -void SimpleApp::SetNextHandle(RCefWebDisplayHandle *handle) +void SimpleApp::SetNextHandle(RCefWebDisplayHandle *handle, bool headless) { fNextHandle = handle; + fNextHeadless = headless; } @@ -150,29 +151,10 @@ void SimpleApp::OnBeforeCommandLineProcessing(const CefString &process_type, Cef command_line->AppendSwitch("disable-logging"); command_line->AppendSwitchWithValue("enable-logging", "none"); } -// command_line->AppendSwitch("allow-file-access-from-files"); -// command_line->AppendSwitch("disable-web-security"); -// if (fBatch) { -// command_line->AppendSwitch("disable-gpu"); -// command_line->AppendSwitch("disable-gpu-compositing"); -// command_line->AppendSwitch("disable-gpu-sandbox"); -// } } void SimpleApp::OnBeforeChildProcessLaunch(CefRefPtr command_line) { - if (fSupressLog) { - command_line->AppendSwitchWithValue("v", "-1"); - command_line->AppendSwitch("disable-logging"); - command_line->AppendSwitchWithValue("enable-logging", "none"); - } -// command_line->AppendSwitch("allow-file-access-from-files"); -// command_line->AppendSwitch("disable-web-security"); -// if (fLastBatch) { -// command_line->AppendSwitch("disable-webgl"); -// command_line->AppendSwitch("disable-gpu"); -// command_line->AppendSwitch("disable-gpu-compositing"); -// } } void SimpleApp::OnContextInitialized() From 2d7badcb13a44f59cf3b51ce01bbcea0a75fb1e6 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Fri, 12 Jun 2026 11:22:35 +0200 Subject: [PATCH 5/6] [cef] adjust windows code While view framework used - hide window before it appears --- gui/cefdisplay/src/RCefWebDisplayHandle.cxx | 12 ++++++--- gui/cefdisplay/src/simple_app.cxx | 29 ++++++++++++--------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/gui/cefdisplay/src/RCefWebDisplayHandle.cxx b/gui/cefdisplay/src/RCefWebDisplayHandle.cxx index 00fe179f10429..4386043f4777d 100644 --- a/gui/cefdisplay/src/RCefWebDisplayHandle.cxx +++ b/gui/cefdisplay/src/RCefWebDisplayHandle.cxx @@ -134,21 +134,26 @@ std::unique_ptr RCefWebDisplayHandle::CefCreator::Displ bool supress_log = (settings.log_severity == LOGSEVERITY_DISABLE) || (settings.log_severity == LOGSEVERITY_FATAL); -#ifdef OS_WIN - CefMainArgs main_args(GetModuleHandle(nullptr)); -#else TApplication *root_app = gROOT->GetApplication(); std::vector cef_argv = { root_app->Argv(0) }; +#ifdef OS_WIN + + CefMainArgs main_args(args.IsHeadless() ? (HINSTANCE) 0 : GetModuleHandle(nullptr)); + +#else + if (args.IsHeadless()) { 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"); @@ -163,6 +168,7 @@ std::unique_ptr RCefWebDisplayHandle::CefCreator::Displ 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) diff --git a/gui/cefdisplay/src/simple_app.cxx b/gui/cefdisplay/src/simple_app.cxx index ccdefa5379467..58971c817d37a 100644 --- a/gui/cefdisplay/src/simple_app.cxx +++ b/gui/cefdisplay/src/simple_app.cxx @@ -44,22 +44,26 @@ namespace { // implementation for the CefWindow that hosts the Views-based browser. class SimpleWindowDelegate : public CefWindowDelegate { CefRefPtr fBrowserView; - int fWidth{800}; ///< preferred window width - int fHeight{600}; ///< preferred window height + int fWidth = 800; ///< preferred window width + int fHeight = 600; ///< preferred window height + bool fBatch = false; public: - explicit SimpleWindowDelegate(CefRefPtr browser_view, int width = 800, int height = 600) - : fBrowserView(browser_view), fWidth(width), fHeight(height) + explicit SimpleWindowDelegate(CefRefPtr browser_view, int width = 800, int height = 600, bool batch = false) + : fBrowserView(browser_view), fWidth(width), fHeight(height), fBatch(batch) { } void OnWindowCreated(CefRefPtr window) override { - // Add the browser view and show the window. window->AddChildView(fBrowserView); - window->Show(); - // Give keyboard focus to the browser view. - fBrowserView->RequestFocus(); + if (fBatch) { + window->Hide(); + } else { + window->Show(); + // Give keyboard focus to the browser view. + fBrowserView->RequestFocus(); + } } void OnWindowDestroyed(CefRefPtr window) override { fBrowserView = nullptr; } @@ -202,7 +206,7 @@ void SimpleApp::StartWindow(THttpServer *serv, const std::string &addr, const st CefBrowserView::CreateBrowserView(fGuiHandler, url, browser_settings, nullptr, nullptr, new SimpleBrowserViewDelegate()); // Create the Window. It will show itself after creation. - CefWindow::CreateTopLevelWindow(new SimpleWindowDelegate(browser_view, rect.width, rect.height)); + CefWindow::CreateTopLevelWindow(new SimpleWindowDelegate(browser_view, rect.width, rect.height, is_batch)); if (fNextHandle) { fNextHandle->SetBrowser(browser_view->GetBrowser()); @@ -217,13 +221,14 @@ void SimpleApp::StartWindow(THttpServer *serv, const std::string &addr, const st // one should implement CefRenderHandler #if defined(OS_WIN) - RECT wnd_rect = {rect.x, rect.y, rect.x + rect.width, rect.y + rect.height}; - if (!rect.IsEmpty()) window_info.SetAsChild(0, wnd_rect); + if (!rect.IsEmpty()) + window_info.SetAsChild(0, rect); // On Windows we need to specify certain flags that will be passed to // CreateWindowEx(). window_info.SetAsPopup(0, "cefsimple"); if (is_batch) - window_info.SetAsWindowless(GetDesktopWindow()); + // window_info.SetAsWindowless(GetDesktopWindow()); + window_info.SetAsWindowless(kNullWindowHandle); #else if (!rect.IsEmpty()) window_info.SetAsChild(0, rect); From 8181fc9af65546d854367b677499d05716025ba0 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Fri, 12 Jun 2026 16:55:39 +0200 Subject: [PATCH 6/6] [cef] update documentation --- documentation/doxygen/Doxyfile | 1 - gui/cefdisplay/Readme.md | 55 ++++++++-------------------------- 2 files changed, 12 insertions(+), 44 deletions(-) diff --git a/documentation/doxygen/Doxyfile b/documentation/doxygen/Doxyfile index 50515a212a3d5..a46142f3c676f 100644 --- a/documentation/doxygen/Doxyfile +++ b/documentation/doxygen/Doxyfile @@ -1123,7 +1123,6 @@ EXCLUDE_PATTERNS = */G__* \ *.xml \ *.dtd \ */graf3d/eve7/glu/* \ - */gui/cefdisplay/* \ */gui/qt6webdisplay/* \ */tutorials/visualisation/webgui/qtweb/* \ */math/mathcore/src/CDT* diff --git a/gui/cefdisplay/Readme.md b/gui/cefdisplay/Readme.md index 474842f28a23d..ab8228353292a 100644 --- a/gui/cefdisplay/Readme.md +++ b/gui/cefdisplay/Readme.md @@ -4,8 +4,7 @@ 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: @@ -13,8 +12,8 @@ See details about [Chromium Embedded Framework](https://bitbucket.org/chromiumem ~~~ $ 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 ~~~ @@ -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 .. @@ -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: @@ -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` @@ -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. -