17#include "RConfigure.h"
45#include <crt_externs.h>
46#elif defined(__FreeBSD__)
55using namespace std::string_literals;
71 static std::map<std::string, std::unique_ptr<RWebDisplayHandle::Creator>> sMap;
84 auto search =
m.find(
name);
85 if (search ==
m.end()) {
87 if (libname ==
"ChromeCreator") {
88 m.emplace(
name, std::make_unique<ChromeCreator>(
name ==
"edge"));
89 }
else if (libname ==
"FirefoxCreator") {
90 m.emplace(
name, std::make_unique<FirefoxCreator>());
91 }
else if (libname ==
"SafariCreator") {
92 m.emplace(
name, std::make_unique<SafariCreator>());
93 }
else if (libname ==
"BrowserCreator") {
94 m.emplace(
name, std::make_unique<BrowserCreator>(
false));
95 }
else if (!libname.empty()) {
99 search =
m.find(
name);
102 if (search !=
m.end())
103 return search->second;
105 static std::unique_ptr<RWebDisplayHandle::Creator> dummy;
128 RWebBrowserHandle(
const std::string &url,
const std::string &tmpdir,
const std::string &tmpfile,
129 const std::string &dump)
135 RWebBrowserHandle(
const std::string &url,
const std::string &tmpdir,
const std::string &tmpfile,
145 gSystem->Exec((
"taskkill /F /PID " + std::to_string(
fPid) +
" >NUL 2>NUL").c_str());
146 std::string rmdir =
"rmdir /S /Q ";
150 std::string rmdir =
"rm -rf ";
160 std::string rmfile =
"del /F ";
162 std::string rmfile =
"rm -f ";
181 if (exec.find(
"$url") == std::string::npos) {
184 fExec = exec +
" $url";
186 fExec = exec +
" $url &";
190 auto pos = exec.find(
" ");
191 if (
pos != std::string::npos)
194 }
else if (
gSystem->InheritsFrom(
"TMacOSXSystem")) {
195 fExec =
"open \'$url\'";
196 }
else if (
gSystem->InheritsFrom(
"TWinNTSystem")) {
197 fExec =
"start $url";
199 fExec =
"xdg-open \'$url\' &";
208 if (nexttry.empty() || !
fProg.empty())
213 fProg = std::regex_replace(nexttry, std::regex(
"%20"),
" ");
220 if (!check_std_paths)
224 std::string ProgramFiles =
gSystem->Getenv(
"ProgramFiles");
225 auto pos = ProgramFiles.find(
" (x86)");
226 if (
pos != std::string::npos)
227 ProgramFiles.erase(
pos, 6);
228 std::string ProgramFilesx86 =
gSystem->Getenv(
"ProgramFiles(x86)");
230 if (!ProgramFiles.empty())
231 TestProg(ProgramFiles + nexttry,
false);
232 if (!ProgramFilesx86.empty())
233 TestProg(ProgramFilesx86 + nexttry,
false);
246 if (use_home_dir > 0) {
247 if (use_home_dir == 1) {
248 const char *tmp_dir =
gSystem->TempDirectory();
249 if (tmp_dir && (strncmp(tmp_dir,
"/home", 5) == 0))
251 else if (!tmp_dir || (strncmp(tmp_dir,
"/tmp", 4) == 0))
255 if (use_home_dir > 1)
256 dirname =
gSystem->GetHomeDirectory();
258 return gSystem->TempFileName(
name, use_home_dir > 1 ? dirname.c_str() :
nullptr, suffix);
268std::unique_ptr<RWebDisplayHandle>
276 std::cout <<
"New web window: " << url << std::endl;
277 return std::make_unique<RWebBrowserHandle>(url,
"",
"",
"");
288 exec =
"$prog $url &";
293 std::string swidth = std::to_string(args.
GetWidth() > 0 ? args.
GetWidth() : 800),
295 sposx = std::to_string(args.
GetX() >= 0 ? args.
GetX() : 0),
296 sposy = std::to_string(args.
GetY() >= 0 ? args.
GetY() : 0);
301 if (!extra.empty()) {
302 auto p = exec.find(
"$url");
303 if (p != std::string::npos)
304 exec.insert(p, extra +
" ");
312 if (((url.find(
"token=") != std::string::npos) || (url.find(
"key=") != std::string::npos)) && !args.
IsBatchMode() && !args.
IsHeadless()) {
313 TString filebase =
"root_start_";
322 std::string content = std::regex_replace(
324 "<html lang=\"en\">\n"
326 " <meta charset=\"utf-8\">\n"
327 " <meta http-equiv=\"refresh\" content=\"0;url=$url\"/>\n"
328 " <title>Opening ROOT widget</title>\n"
332 " This page should redirect you to a ROOT widget. If it doesn't,\n"
333 " <a href=\"$url\">click here to go to ROOT</a>.\n"
336 "</html>\n", std::regex(
"\\$url"), url);
338 if (fwrite(content.c_str(), 1, content.length(),
f) != content.length())
344 tmpfile = filebase.
Data();
346 url =
"file://"s + tmpfile;
350 if (!tmpfile.empty())
351 gSystem->Unlink(tmpfile.c_str());
357 exec = std::regex_replace(exec, std::regex(
"\\$rootetcdir"),
TROOT::GetEtcDir().Data());
358 exec = std::regex_replace(exec, std::regex(
"\\$url"), url);
359 exec = std::regex_replace(exec, std::regex(
"\\$width"), swidth);
360 exec = std::regex_replace(exec, std::regex(
"\\$height"), sheight);
361 exec = std::regex_replace(exec, std::regex(
"\\$posx"), sposx);
362 exec = std::regex_replace(exec, std::regex(
"\\$posy"), sposy);
364 if (exec.compare(0,5,
"fork:") == 0) {
366 if (!tmpfile.empty())
367 gSystem->Unlink(tmpfile.c_str());
380 if (!fargs || (fargs->GetLast()<=0)) {
381 if (!tmpfile.empty())
382 gSystem->Unlink(tmpfile.c_str());
387 std::vector<char *> argv;
388 argv.push_back((
char *)
fProg.c_str());
389 for (
Int_t n = 0;
n <= fargs->GetLast(); ++
n)
390 argv.push_back((
char *)fargs->At(
n)->GetName());
391 argv.push_back(
nullptr);
395 posix_spawn_file_actions_t action;
396 posix_spawn_file_actions_init(&action);
397 if (redirect.empty())
398 posix_spawn_file_actions_addopen(&action, STDOUT_FILENO,
"/dev/null", O_WRONLY|O_APPEND, 0);
400 posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, redirect.c_str(), O_WRONLY|O_CREAT, 0600);
401 posix_spawn_file_actions_addopen(&action, STDERR_FILENO,
"/dev/null", O_WRONLY|O_APPEND, 0);
404 char **envp = *_NSGetEnviron();
405#elif defined (__FreeBSD__)
408 char** envp = (
char**)dlsym(RTLD_DEFAULT,
"environ");
410 char **envp = environ;
414 int status = posix_spawn(&pid, argv[0], &action,
nullptr, argv.data(), envp);
416 posix_spawn_file_actions_destroy(&action);
419 if (!tmpfile.empty())
420 gSystem->Unlink(tmpfile.c_str());
425 if (!redirect.empty()) {
426 Int_t batch_timeout =
gEnv->GetValue(
"WebGui.BatchTimeout", 30);
427 struct sigaction Act, Old;
428 int elapsed_time = 0;
431 memset(&Act, 0,
sizeof(Act));
433 sigemptyset(&Act.sa_mask);
434 sigaction(SIGALRM, &Act, &Old);
435 int alarm_timeout = batch_timeout > 3 ? 3 : batch_timeout;
436 alarm(alarm_timeout);
437 elapsed_time = alarm_timeout;
441 std::string dump_content;
448 auto wait_res = waitpid(pid, &wait_status, WUNTRACED | WCONTINUED);
453 if (dump_content.find(
"<div>###batch###job###done###</div>") != std::string::npos)
456 if (wait_res == -1) {
458 int alarm_timeout = batch_timeout - elapsed_time;
459 if ((errno == EINTR) && (alarm_timeout > 0) && !job_done) {
460 if (alarm_timeout > 2) alarm_timeout = 2;
461 elapsed_time += alarm_timeout;
462 alarm(alarm_timeout);
467 }
else if (!WIFEXITED(wait_status) && !WIFSIGNALED(wait_status)) {
483 sigaction(SIGALRM, &Old,
nullptr);
486 if (
gEnv->GetValue(
"WebGui.PreserveBatchFiles", -1) > 0)
487 ::Info(
"RWebDisplayHandle::Display",
"Preserve dump file %s", redirect.c_str());
489 gSystem->Unlink(redirect.c_str());
491 return std::make_unique<RWebBrowserHandle>(url, rmdir, tmpfile, dump_content);
496 return std::make_unique<RWebBrowserHandle>(url, rmdir, tmpfile, pid);
501 if (!tmpfile.empty())
502 gSystem->Unlink(tmpfile.c_str());
508 exec =
"wmic process call create '"s +
gSystem->UnixPathName(
fProg.c_str()) +
" " + exec +
"' | find \"ProcessId\" "s;
509 std::string process_id =
gSystem->GetFromPipe(exec.c_str()).Data();
510 std::stringstream ss(process_id);
514 ss >> tmp >>
c >> pid;
517 if (!tmpfile.empty())
518 gSystem->Unlink(tmpfile.c_str());
524 return std::make_unique<RWebBrowserHandle>(url, rmdir, tmpfile, pid);
530 if (exec.rfind(
"&") == exec.length() - 1) {
533 exec.resize(exec.length() - 1);
535 std::vector<char *> argv;
536 std::string firstarg =
fProg;
537 auto slashpos = firstarg.find_last_of(
"/\\");
538 if (slashpos != std::string::npos)
539 firstarg.erase(0, slashpos + 1);
540 argv.push_back((
char *)firstarg.c_str());
543 for (
Int_t n = 1;
n <= fargs->GetLast(); ++
n)
544 argv.push_back((
char *)fargs->At(
n)->GetName());
545 argv.push_back(
nullptr);
549 _spawnv(_P_NOWAIT,
gSystem->UnixPathName(
fProg.c_str()), argv.data());
551 return std::make_unique<RWebBrowserHandle>(url, rmdir, tmpfile,
""s);
554 std::string prog =
"\""s +
gSystem->UnixPathName(
fProg.c_str()) +
"\""s;
559 std::string prog = std::regex_replace(
fProg, std::regex(
" "),
"\\ ");
561 std::string prog =
fProg;
566 exec = std::regex_replace(exec, std::regex(
"\\$prog"), prog);
570 if (!redirect.empty()) {
571 if (exec.find(
"$dumpfile") != std::string::npos) {
572 exec = std::regex_replace(exec, std::regex(
"\\$dumpfile"), redirect);
574 auto p = exec.length();
575 if (exec.rfind(
"&") == p-1) --p;
576 exec.insert(p,
" >"s + redirect +
" "s);
585 if (!redirect.empty()) {
588 if (
gEnv->GetValue(
"WebGui.PreserveBatchFiles", -1) > 0)
589 ::Info(
"RWebDisplayHandle::Display",
"Preserve dump file %s", redirect.c_str());
591 gSystem->Unlink(redirect.c_str());
594 return std::make_unique<RWebBrowserHandle>(url, rmdir, tmpfile, dump_content);
602 fExec =
gEnv->GetValue(
"WebGui.SafariInteractive",
"open -a Safari $url");
633 TestProg(
"\\Microsoft\\Edge\\Application\\msedge.exe",
true);
635 TestProg(
"\\Google\\Chrome\\Application\\chrome.exe",
true);
638 TestProg(
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome");
643 TestProg(
"/usr/bin/chromium-browser");
644 TestProg(
"/usr/bin/chrome-browser");
645 TestProg(
"/usr/bin/google-chrome-stable");
655 fBatchExec =
gEnv->GetValue((
fEnvPrefix +
"Batch").c_str(),
"$prog --headless --no-sandbox $geometry --dump-dom $url");
657 fHeadlessExec =
gEnv->GetValue((
fEnvPrefix +
"Headless").c_str(),
"fork:--headless --no-sandbox --disable-gpu $geometry \"$url\"");
658 fExec =
gEnv->GetValue((
fEnvPrefix +
"Interactive").c_str(),
"$prog $geometry --new-window --app=$url &");
661 bool use_normal =
true;
667 fBatchExec =
gEnv->GetValue((
fEnvPrefix +
"Batch").c_str(),
"fork:--headless --no-sandbox --disable-extensions --disable-audio-output $geometry --dump-dom $url");
668 fHeadlessExec =
gEnv->GetValue((
fEnvPrefix +
"Headless").c_str(),
"fork:--headless --no-sandbox --disable-extensions --disable-audio-output $geometry $url");
671 fBatchExec =
gEnv->GetValue((
fEnvPrefix +
"Batch").c_str(),
"fork:--headless=new --no-sandbox --disable-extensions --disable-audio-output $geometry --dump-dom $url");
672 fHeadlessExec =
gEnv->GetValue((
fEnvPrefix +
"Headless").c_str(),
"fork:--headless=new --no-sandbox --disable-extensions --disable-audio-output $geometry $url");
674 fExec =
gEnv->GetValue((
fEnvPrefix +
"Interactive").c_str(),
"$prog $geometry --new-window --app=\'$url\' >/dev/null 2>/dev/null &");
685 std::string geometry;
687 geometry =
"--window-size="s + std::to_string(args.
GetWidth())
694 std::to_string(args.
GetY() >= 0 ? args.
GetY() : 0));
697 exec = std::regex_replace(exec, std::regex(
"\\$geometry"), geometry);
706 std::string rmdir, profile_arg;
708 if (exec.find(
"$profile") == std::string::npos)
711 const char *chrome_profile =
gEnv->GetValue((
fEnvPrefix +
"Profile").c_str(),
"");
712 if (chrome_profile && *chrome_profile) {
713 profile_arg = chrome_profile;
717 profile_arg =
gSystem->TempDirectory();
718 if ((profile_arg.compare(0, 4,
"/tmp") == 0) &&
IsSnapBrowser())
719 profile_arg =
gSystem->GetHomeDirectory();
726 if (!profile_arg.empty() && (profile_arg[profile_arg.length()-1] !=
slash))
727 profile_arg +=
slash;
728 profile_arg +=
"root_chrome_profile_"s + std::to_string(rnd.
Integer(0x100000));
733 exec = std::regex_replace(exec, std::regex(
"\\$profile"), profile_arg);
747 TestProg(
"\\Mozilla Firefox\\firefox.exe",
true);
750 TestProg(
"/Applications/Firefox.app/Contents/MacOS/firefox");
761 fBatchExec =
gEnv->GetValue(
"WebGui.FirefoxBatch",
"$prog -headless -no-remote $profile $url");
762 fHeadlessExec =
gEnv->GetValue(
"WebGui.FirefoxHeadless",
"fork:-headless -no-remote $profile \"$url\"");
763 fExec =
gEnv->GetValue(
"WebGui.FirefoxInteractive",
"$prog -no-remote $profile $geometry $url &");
765 fBatchExec =
gEnv->GetValue(
"WebGui.FirefoxBatch",
"fork:--headless -no-remote -new-instance $profile $url");
766 fHeadlessExec =
gEnv->GetValue(
"WebGui.FirefoxHeadless",
"fork:--headless -no-remote $profile --private-window $url");
767 fExec =
gEnv->GetValue(
"WebGui.FirefoxInteractive",
"$rootetcdir/runfirefox.sh __nodump__ $cleanup_profile $prog -no-remote $profile $geometry -url \'$url\' &");
776 std::string geometry;
778 geometry =
"-width="s + std::to_string(args.
GetWidth()) +
" -height=" + std::to_string(args.
GetHeight());
780 exec = std::regex_replace(exec, std::regex(
"\\$geometry"), geometry);
788 std::string rmdir, profile_arg;
790 if (exec.find(
"$profile") == std::string::npos)
793 const char *ff_profile =
gEnv->GetValue(
"WebGui.FirefoxProfile",
"");
794 const char *ff_profilepath =
gEnv->GetValue(
"WebGui.FirefoxProfilePath",
"");
796 if (ff_profile && *ff_profile) {
797 profile_arg =
"-P "s + ff_profile;
798 }
else if (ff_profilepath && *ff_profilepath) {
799 profile_arg =
"-profile "s + ff_profilepath;
800 }
else if (ff_randomprofile > 0) {
803 std::string profile_dir =
gSystem->TempDirectory();
804 if ((profile_dir.compare(0, 4,
"/tmp") == 0) &&
IsSnapBrowser())
805 profile_dir =
gSystem->GetHomeDirectory();
812 if (!profile_dir.empty() && (profile_dir[profile_dir.length()-1] !=
slash))
813 profile_dir +=
slash;
814 profile_dir +=
"root_ff_profile_"s + std::to_string(rnd.
Integer(0x100000));
816 profile_arg =
"-profile "s + profile_dir;
818 if (
gSystem->mkdir(profile_dir.c_str()) == 0) {
821 std::ofstream user_js(profile_dir +
"/user.js", std::ios::trunc);
824 user_js <<
"user_pref(\"datareporting.policy.dataSubmissionPolicyAcceptedVersion\", 2);" << std::endl;
825 user_js <<
"user_pref(\"datareporting.policy.dataSubmissionPolicyNotifiedTime\", \"1635760572813\");" << std::endl;
828 user_js <<
"user_pref(\"browser.tabs.closeWindowWithLastTab\", true);" << std::endl;
829 user_js <<
"user_pref(\"dom.allow_scripts_to_close_windows\", true);" << std::endl;
830 user_js <<
"user_pref(\"browser.sessionstore.resume_from_crash\", false);" << std::endl;
834 user_js <<
"user_pref(\"browser.dom.window.dump.enabled\", true);" << std::endl;
837 user_js <<
"user_pref(\"datareporting.policy.firstRunURL\", \"\");" << std::endl;
839 user_js <<
"user_pref(\"toolkit.legacyUserProfileCustomizations.stylesheets\", true);" << std::endl;
841 user_js <<
"user_pref(\"browser.tabs.inTitlebar\", 0);" << std::endl;
845 user_js <<
"user_pref(\"webgl.out-of-process\", false);" << std::endl;
848 std::ofstream times_json(profile_dir +
"/times.json", std::ios::trunc);
849 times_json <<
"{" << std::endl;
850 times_json <<
" \"created\": 1699968480952," << std::endl;
851 times_json <<
" \"firstUse\": null" << std::endl;
852 times_json <<
"}" << std::endl;
853 if (
gSystem->mkdir((profile_dir +
"/chrome").c_str()) == 0) {
854 std::ofstream
style(profile_dir +
"/chrome/userChrome.css", std::ios::trunc);
856 style <<
"#TabsToolbar { visibility: collapse; }" << std::endl;
858 style <<
"#nav-bar, #urlbar-container, #searchbar { visibility: collapse !important; }" << std::endl;
867 exec = std::regex_replace(exec, std::regex(
"\\$profile"), profile_arg);
869 if (exec.find(
"$cleanup_profile") != std::string::npos) {
870 if (rmdir.empty()) rmdir =
"__dummy__";
871 exec = std::regex_replace(exec, std::regex(
"\\$cleanup_profile"), rmdir);
891 auto &qt6 =
FindCreator(
"qt6",
"libROOTQt6WebDisplay");
892 if (qt6 && qt6->IsActive())
896 auto &cef =
FindCreator(
"cef",
"libROOTCefDisplay");
897 if (cef && cef->IsActive())
914 std::unique_ptr<RWebDisplayHandle> handle;
919 auto try_creator = [&](std::unique_ptr<Creator> &creator) {
920 if (!creator || !creator->IsActive())
922 handle = creator->Display(args);
923 return handle ? true :
false;
928 has_qt6web =
false, has_cefweb =
false;
939 if (try_creator(
FindCreator(
"qt6",
"libROOTQt6WebDisplay")))
944 if (try_creator(
FindCreator(
"cef",
"libROOTCefDisplay")))
953 bool handleAsNative =
957 if (try_creator(
FindCreator(
"chrome",
"ChromeCreator")))
962 if (try_creator(
FindCreator(
"firefox",
"FirefoxCreator")))
969 if (try_creator(
FindCreator(
"edge",
"ChromeCreator")))
981 if (try_creator(
FindCreator(
"safari",
"SafariCreator")))
986 std::unique_ptr<Creator> creator = std::make_unique<BrowserCreator>(
false, args.
GetCustomExec());
987 try_creator(creator);
989 try_creator(
FindCreator(
"browser",
"BrowserCreator"));
1027 bool detected =
false;
1030 if (
h1 &&
h1->IsActive()) {
1036 auto &h2 =
FindCreator(
"firefox",
"FirefoxCreator");
1037 if (h2 && h2->IsActive()) {
1048 return h1 &&
h1->IsActive();
1052 auto &h2 =
FindCreator(
"firefox",
"FirefoxCreator");
1053 return h2 && h2->IsActive();
1059 return h3 && h3->IsActive();
1084 std::string _fname = fname;
1085 std::transform(_fname.begin(), _fname.end(), _fname.begin(), ::tolower);
1086 auto EndsWith = [&_fname](
const std::string &suffix) {
1087 return (_fname.length() > suffix.length()) ? (0 == _fname.compare(_fname.length() - suffix.length(), suffix.length(), suffix)) :
false;
1117 return ProduceImages(fname, {json}, {width}, {height}, batch_file);
1129 std::vector<std::string> fnames;
1131 if ((fmt ==
"s.pdf") || (fmt ==
"s.png")) {
1132 fnames.emplace_back(fname);
1134 std::string farg = fname;
1136 bool has_quialifier = farg.find(
"%") != std::string::npos;
1138 if (!has_quialifier && (nfiles > 1) && (fmt !=
"pdf")) {
1139 farg.insert(farg.rfind(
"."),
"%d");
1140 has_quialifier =
true;
1143 for (
unsigned n = 0;
n < nfiles;
n++) {
1144 if(has_quialifier) {
1146 fnames.emplace_back(expand_name.Data());
1148 fnames.emplace_back(
"");
1150 fnames.emplace_back(fname);
1162bool RWebDisplayHandle::ProduceImages(
const std::string &fname,
const std::vector<std::string> &jsons,
const std::vector<int> &widths,
const std::vector<int> &heights,
const char *batch_file)
1171bool RWebDisplayHandle::ProduceImages(
const std::vector<std::string> &fnames,
const std::vector<std::string> &jsons,
const std::vector<int> &widths,
const std::vector<int> &heights,
const char *batch_file)
1173 if (fnames.empty() || jsons.empty())
1176 std::vector<std::string> fmts;
1177 for (
auto& fname : fnames)
1180 bool is_any_image =
false;
1182 for (
unsigned n = 0; (
n < fmts.size()) && (
n < jsons.size());
n++) {
1183 if (fmts[
n] ==
"json") {
1184 std::ofstream ofs(fnames[
n]);
1187 }
else if (!fmts[
n].empty())
1188 is_any_image =
true;
1195 if (fnames.size() == 1)
1200 const char *jsrootsys =
gSystem->Getenv(
"JSROOTSYS");
1204 if (
gSystem->ExpandPathName(jsrootsysdflt)) {
1208 jsrootsys = jsrootsysdflt.
Data();
1221 std::vector<std::string> draw_kinds;
1222 bool use_browser_draw =
false, can_optimize_json =
false;
1223 int use_home_dir = 0;
1228 static int chrome_tmp_workaround = 0;
1231 use_home_dir = chrome_tmp_workaround;
1233 if (
h1 &&
h1->IsActive() &&
h1->IsSnapBrowser() && (use_home_dir == 0))
1237 if (fmts[0] ==
"s.png") {
1238 if (!isChromeBased && !isFirefox) {
1239 R__LOG_ERROR(
WebGUILog()) <<
"Direct png image creation supported only by Chrome and Firefox browsers";
1242 use_browser_draw =
true;
1244 }
else if (fmts[0] ==
"s.pdf") {
1245 if (!isChromeBased) {
1249 use_browser_draw =
true;
1254 can_optimize_json =
true;
1257 if (!batch_file || !*batch_file)
1258 batch_file =
"/js/files/canv_batch.htm";
1261 if (
gSystem->ExpandPathName(origin)) {
1267 if (filecont.empty()) {
1272 int max_width = 0, max_height = 0, page_margin = 10;
1273 for (
auto &w : widths)
1276 for (
auto &
h : heights)
1283 std::string mains, prev;
1284 for (
auto &json : jsons) {
1285 mains.append(mains.empty() ?
"[" :
", ");
1286 if (can_optimize_json && (json == prev)) {
1287 mains.append(
"'same'");
1295 if (strstr(jsrootsys,
"http://") || strstr(jsrootsys,
"https://") || strstr(jsrootsys,
"file://"))
1296 filecont = std::regex_replace(filecont, std::regex(
"\\$jsrootsys"), jsrootsys);
1298 static std::string jsroot_include =
"<script id=\"jsroot\" src=\"$jsrootsys/build/jsroot.js\"></script>";
1299 auto p = filecont.find(jsroot_include);
1300 if (p != std::string::npos) {
1302 if (!jsroot_build.empty()) {
1304 jsroot_build = std::regex_replace(jsroot_build, std::regex(
"'\\$jsrootsys'"), std::string(
"'file://") + jsrootsys +
"/'");
1305 filecont.erase(p, jsroot_include.length());
1306 filecont.insert(p,
"<script id=\"jsroot\">" + jsroot_build +
"</script>");
1310 filecont = std::regex_replace(filecont, std::regex(
"\\$jsrootsys"),
"file://"s + jsrootsys);
1313 filecont = std::regex_replace(filecont, std::regex(
"\\$page_margin"), std::to_string(page_margin) +
"px");
1314 filecont = std::regex_replace(filecont, std::regex(
"\\$page_width"), std::to_string(max_width + 2*page_margin) +
"px");
1315 filecont = std::regex_replace(filecont, std::regex(
"\\$page_height"), std::to_string(max_height + 2*page_margin) +
"px");
1317 filecont = std::regex_replace(filecont, std::regex(
"\\$draw_kind"), jsonkind.
Data());
1318 filecont = std::regex_replace(filecont, std::regex(
"\\$draw_widths"), jsonw.Data());
1319 filecont = std::regex_replace(filecont, std::regex(
"\\$draw_heights"), jsonh.Data());
1320 filecont = std::regex_replace(filecont, std::regex(
"\\$draw_objects"), mains);
1324 if (!use_browser_draw && (isChromeBased || isFirefox)) {
1325 dump_name =
"canvasdump";
1331 fputs(
"placeholder", df);
1343 R__LOG_DEBUG(0,
WebGUILog()) <<
"Using file content_len " << filecont.length() <<
" to produce batch images ";
1346 html_name =
"canvasbody";
1352 fputs(filecont.c_str(), hf);
1358 R__LOG_DEBUG(0,
WebGUILog()) <<
"Using " << html_name <<
" content_len " << filecont.length() <<
" to produce batch images " << fdebug;
1361 TString wait_file_name, tgtfilename;
1366 args.
SetSize(widths[0], heights[0]);
1368 if (use_browser_draw) {
1370 tgtfilename = fnames[0].c_str();
1371 if (!
gSystem->IsAbsoluteFileName(tgtfilename.
Data()))
1372 gSystem->PrependPathName(
gSystem->WorkingDirectory(), tgtfilename);
1374 wait_file_name = tgtfilename;
1376 if (fmts[0] ==
"s.pdf")
1378 else if (isFirefox) {
1380 wait_file_name =
"screenshot.png";
1387 }
else if (isFirefox) {
1391 }
else if (isChromeBased) {
1400 if (use_browser_draw && handle) {
1401 Int_t batch_timeout =
gEnv->GetValue(
"WebGui.BatchTimeout", 30) * 10;
1402 while (
gSystem->AccessPathName(wait_file_name.
Data()) && (--batch_timeout > 0)) {
1409 if (html_name.
Length() > 0) {
1410 if (
gEnv->GetValue(
"WebGui.PreserveBatchFiles", -1) > 0)
1411 ::Info(
"ProduceImages",
"Preserve batch file %s", html_name.
Data());
1421 if (use_browser_draw) {
1423 if (
gSystem->AccessPathName(wait_file_name.
Data())) {
1428 if (fmts[0] ==
"s.pdf")
1429 ::Info(
"ProduceImages",
"PDF file %s with %d pages has been created", fnames[0].c_str(), (
int) jsons.size());
1432 gSystem->Rename(
"screenshot.png", fnames[0].c_str());
1433 ::Info(
"ProduceImages",
"PNG file %s with %d pages has been created", fnames[0].c_str(), (
int) jsons.size());
1436 auto dumpcont = handle->GetContent();
1438 if ((dumpcont.length() > 20) && (dumpcont.length() < 60) && (use_home_dir < 2) && isChrome) {
1441 R__LOG_INFO(
WebGUILog()) <<
"Use home directory for running chrome in batch, set TMPDIR for preferable temp directory";
1442 chrome_tmp_workaround = use_home_dir = 2;
1446 if (dumpcont.length() < 100) {
1451 std::string::size_type p = 0;
1453 for (
unsigned n = 0;
n < fmts.size();
n++) {
1454 if (fmts[
n].empty())
1456 if (fmts[
n] ==
"svg") {
1457 auto p1 = dumpcont.find(
"<div><svg", p);
1458 auto p2 = dumpcont.find(
"</svg></div>", p1 + 8);
1460 std::ofstream ofs(fnames[
n]);
1461 if ((p1 != std::string::npos) && (p2 != std::string::npos) && (p1 < p2)) {
1463 ofs << dumpcont.substr(p1 + 5, p2 - p1 + 1);
1464 ::Info(
"ProduceImages",
"Image file %s size %d bytes has been created", fnames[
n].c_str(), (
int) (p2 - p1 + 1));
1466 ::Error(
"ProduceImages",
"Failure producing %s", fnames[
n].c_str());
1470 auto p0 = dumpcont.find(
"<img src=\"", p);
1471 auto p1 = dumpcont.find(
";base64,", p0 + 8);
1472 auto p2 = dumpcont.find(
"\">", p1 + 8);
1475 if ((p0 != std::string::npos) && (p1 != std::string::npos) && (p2 != std::string::npos) && (p1 < p2)) {
1476 auto base64 = dumpcont.substr(p1+8, p2-p1-8);
1477 if ((base64 ==
"failure") || (base64.length() < 10)) {
1478 ::Error(
"ProduceImages",
"Failure producing %s", fnames[
n].c_str());
1481 std::ofstream ofs(fnames[
n], std::ios::binary);
1482 ofs.write(binary.Data(), binary.Length());
1483 ::Info(
"ProduceImages",
"Image file %s size %d bytes has been created", fnames[
n].c_str(), (
int) binary.Length());
1486 ::Error(
"ProduceImages",
"Failure producing %s", fnames[
n].c_str());
#define R__LOG_ERROR(...)
#define R__LOG_DEBUG(DEBUGLEVEL,...)
static void DummyTimeOutHandler(int)
int Int_t
Signed integer 4 bytes (int).
Error("WriteTObject","The current directory (%s) is not associated with a file. The object (%s) has not been written.", GetName(), objname)
void Info(const char *location, const char *msgfmt,...)
Use this function for informational messages.
RWebBrowserHandle(const std::string &url, const std::string &tmpdir, const std::string &tmpfile, browser_process_id pid)
std::string fTmpDir
temporary directory to delete at the end
void RemoveStartupFiles() override
remove file which was used to startup widget - if possible
RWebBrowserHandle(const std::string &url, const std::string &tmpdir, const std::string &tmpfile, const std::string &dump)
std::string fTmpFile
temporary file to remove
~RWebBrowserHandle() override
Holds different arguments for starting browser with RWebDisplayHandle::Display() method.
std::string GetBrowserName() const
Returns configured browser name.
EBrowserKind GetBrowserKind() const
returns configured browser kind, see EBrowserKind for supported values
const std::string & GetRedirectOutput() const
get file name to which web browser output should be redirected
void SetStandalone(bool on=true)
Set standalone mode for running browser, default on When disabled, normal browser window (or just tab...
void SetBatchMode(bool on=true)
set batch mode
RWebDisplayArgs & SetSize(int w, int h)
set preferable web window width and height
RWebDisplayArgs & SetUrl(const std::string &url)
set window url
int GetWidth() const
returns preferable web window width
RWebDisplayArgs & SetPageContent(const std::string &cont)
set window url
int GetY() const
set preferable web window y position
std::string GetFullUrl() const
returns window url with append options
bool IsStandalone() const
Return true if browser should runs in standalone mode.
int GetHeight() const
returns preferable web window height
RWebDisplayArgs & SetBrowserKind(const std::string &kind)
Set browser kind as string argument.
std::string GetCustomExec() const
returns custom executable to start web browser
void SetExtraArgs(const std::string &args)
set extra command line arguments for starting web browser command
bool IsBatchMode() const
returns batch mode
bool IsHeadless() const
returns headless mode
@ kOn
web display enable, first try use embed displays like Qt or CEF, then native browsers and at the end ...
@ kFirefox
Mozilla Firefox browser.
@ kNative
either Chrome or Firefox - both support major functionality
@ kLocal
either CEF or Qt5 - both runs on local display without real http server
@ kServer
indicates that ROOT runs as server and just printouts window URL, browser should be started by the us...
@ kOff
disable web display, do not start any browser
@ kCEF
Chromium Embedded Framework - local display with CEF libs.
@ kQt6
Qt6 QWebEngine libraries - Chromium code packed in qt6.
@ kCustom
custom web browser, execution string should be provided
@ kChrome
Google Chrome browser.
@ kEdge
Microsoft Edge browser (Windows only).
void SetRedirectOutput(const std::string &fname="")
specify file name to which web browser output should be redirected
void SetHeadless(bool on=true)
set headless mode
const std::string & GetExtraArgs() const
get extra command line arguments for starting web browser command
int GetX() const
set preferable web window x position
bool IsLocalDisplay() const
returns true if local display like CEF or Qt5 QWebEngine should be used
std::string fProg
browser executable
std::string fBatchExec
batch execute line
std::string fHeadlessExec
headless execute line
static FILE * TemporaryFile(TString &name, int use_home_dir=0, const char *suffix=nullptr)
Create temporary file for web display Normally gSystem->TempFileName() method used to create file in ...
std::unique_ptr< RWebDisplayHandle > Display(const RWebDisplayArgs &args) override
Display given URL in web browser.
virtual void ProcessGeometry(std::string &, const RWebDisplayArgs &)
std::string fExec
standard execute line
void TestProg(const std::string &nexttry, bool check_std_paths=false)
Check if browser executable exists and can be used.
BrowserCreator(bool custom=true, const std::string &exec="")
Class to handle starting of web-browsers like Chrome or Firefox.
virtual std::string MakeProfile(std::string &, bool)
ChromeCreator(bool is_edge=false)
Constructor.
void ProcessGeometry(std::string &, const RWebDisplayArgs &) override
Replace $geometry placeholder with geometry settings Also RWebDisplayArgs::GetExtraArgs() are appende...
bool IsSnapBrowser() const override
std::string MakeProfile(std::string &exec, bool) override
Handle profile argument.
virtual bool IsSnapBrowser() const
FirefoxCreator()
Constructor.
std::string MakeProfile(std::string &exec, bool batch) override
Create Firefox profile to run independent browser window.
void ProcessGeometry(std::string &, const RWebDisplayArgs &) override
Process window geometry for Firefox.
bool IsSnapBrowser() const override
bool IsActive() const override
Returns true if it can be used.
SafariCreator()
Constructor.
static std::map< std::string, std::unique_ptr< Creator > > & GetMap()
Static holder of registered creators of web displays.
static bool CheckIfCanProduceImages(RWebDisplayArgs &args)
Checks if configured browser can be used for image production.
static bool ProduceImages(const std::string &fname, const std::vector< std::string > &jsons, const std::vector< int > &widths, const std::vector< int > &heights, const char *batch_file=nullptr)
Produce image file(s) using JSON data as source Invokes JSROOT drawing functionality in headless brow...
static std::vector< std::string > ProduceImagesNames(const std::string &fname, unsigned nfiles=1)
Produce vector of file names for specified file pattern Depending from supported file forma.
static std::string GetImageFormat(const std::string &fname)
Detect image format There is special handling of ".screenshot.pdf" and ".screenshot....
void SetContent(const std::string &cont)
set content
RWebDisplayHandle(const std::string &url)
constructor
static bool ProduceImage(const std::string &fname, const std::string &json, int width=800, int height=600, const char *batch_file=nullptr)
Produce image file using JSON data as source Invokes JSROOT drawing functionality in headless browser...
static bool CanProduceImages(const std::string &browser="")
Returns true if image production for specified browser kind is supported If browser not specified - u...
static bool NeedHttpServer(const RWebDisplayArgs &args)
Check if http server required for display.
static bool DisplayUrl(const std::string &url)
Display provided url in configured web browser.
static std::unique_ptr< RWebDisplayHandle > Display(const RWebDisplayArgs &args)
Create web display.
static std::unique_ptr< Creator > & FindCreator(const std::string &name, const std::string &libname="")
Search for specific browser creator If not found, try to add one.
static int GetBoolEnv(const std::string &name, int dfl=-1)
Parse boolean gEnv variable which should be "yes" or "no".
static TString Decode(const char *data)
Decode a base64 string date into a generic TString.
static TString ToJSON(const T *obj, Int_t compact=0, const char *member_name=nullptr)
@ kNoSpaces
no new lines plus remove all spaces around "," and ":" symbols
static char * ReadFileContent(const char *filename, Int_t &len)
Reads content of file from the disk.
static const TString & GetEtcDir()
Get the sysconfig directory in the installation. Static utility function.
static const TString & GetDataDir()
Get the data directory in the installation. Static utility function.
Random number generator class based on M.
void SetSeed(ULong_t seed=0) override
Set the random generator sequence.
virtual UInt_t Integer(UInt_t imax)
Returns a random integer uniformly distributed on the interval [ 0, imax-1 ].
void Clear()
Clear string without changing its capacity.
const char * Data() const
TObjArray * Tokenize(const TString &delim) const
This function is used to isolate sequential tokens in a TString.
static TString Format(const char *fmt,...)
Static method which formats a string using a printf style format descriptor and return a TString.
bool EndsWith(std::string_view string, std::string_view suffix)
ROOT::RLogChannel & WebGUILog()
Log channel for WebGUI diagnostics.