Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RWebDisplayHandle.cxx
Go to the documentation of this file.
1// Author: Sergey Linev <s.linev@gsi.de>
2// Date: 2018-10-17
3
4/*************************************************************************
5 * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers. *
6 * All rights reserved. *
7 * *
8 * For the licensing terms see $ROOTSYS/LICENSE. *
9 * For the list of contributors see $ROOTSYS/README/CREDITS. *
10 *************************************************************************/
11
13
14#include <ROOT/RLogger.hxx>
15
16#include "RConfigure.h"
17#include "TSystem.h"
18#include "TRandom3.h"
19#include "TString.h"
20#include "TObjArray.h"
21#include "THttpServer.h"
22#include "TEnv.h"
23#include "TError.h"
24#include "TROOT.h"
25#include "TBase64.h"
26#include "TBufferJSON.h"
28
29#include <fstream>
30#include <iostream>
31#include <filesystem>
32#include <memory>
33#include <regex>
34
35#ifdef _MSC_VER
36#include <process.h>
37#else
38#include <unistd.h>
39#include <cstdlib>
40#include <csignal>
41#include <spawn.h>
42#ifdef R__MACOSX
43#include <sys/wait.h>
44#include <crt_externs.h>
45#elif defined(__FreeBSD__)
46#include <sys/wait.h>
47#include <dlfcn.h>
48#else
49#include <wait.h>
50#endif
51#endif
52
53using namespace ROOT;
54using namespace std::string_literals;
55
56/** \class ROOT::RWebDisplayHandle
57\ingroup webdisplay
58
59Handle of created web-based display
60Depending from type of web display, holds handle of started browser process or other display-specific information
61to correctly stop and cleanup display.
62*/
63
64
65//////////////////////////////////////////////////////////////////////////////////////////////////
66/// Static holder of registered creators of web displays
67
68std::map<std::string, std::unique_ptr<RWebDisplayHandle::Creator>> &RWebDisplayHandle::GetMap()
69{
70 static std::map<std::string, std::unique_ptr<RWebDisplayHandle::Creator>> sMap;
71 return sMap;
72}
73
74//////////////////////////////////////////////////////////////////////////////////////////////////
75/// Search for specific browser creator
76/// If not found, try to add one
77/// \param name - creator name like ChromeCreator
78/// \param libname - shared library name where creator could be provided
79
80std::unique_ptr<RWebDisplayHandle::Creator> &RWebDisplayHandle::FindCreator(const std::string &name, const std::string &libname)
81{
82 auto &m = GetMap();
83 auto search = m.find(name);
84 if (search == m.end()) {
85
86 if (libname == "ChromeCreator") {
87 m.emplace(name, std::make_unique<ChromeCreator>(name == "edge"));
88 } else if (libname == "FirefoxCreator") {
89 m.emplace(name, std::make_unique<FirefoxCreator>());
90 } else if (libname == "SafariCreator") {
91 m.emplace(name, std::make_unique<SafariCreator>());
92 } else if (libname == "BrowserCreator") {
93 m.emplace(name, std::make_unique<BrowserCreator>(false));
94 } else if (!libname.empty()) {
95 gSystem->Load(libname.c_str());
96 }
97
98 search = m.find(name); // try again
99 }
100
101 if (search != m.end())
102 return search->second;
103
104 static std::unique_ptr<RWebDisplayHandle::Creator> dummy;
105 return dummy;
106}
107
108namespace ROOT {
109
110//////////////////////////////////////////////////////////////////////////////////////////////////
111/// Specialized handle to hold information about running browser process
112/// Used to correctly cleanup all processes and temporary directories
113
115
116#ifdef _MSC_VER
117 typedef int browser_process_id;
118#else
119 typedef pid_t browser_process_id;
120#endif
121 std::string fTmpDir; ///< temporary directory to delete at the end
122 std::string fTmpFile; ///< temporary file to remove
123 bool fHasPid{false};
125
126public:
127 RWebBrowserHandle(const std::string &url, const std::string &tmpdir, const std::string &tmpfile,
128 const std::string &dump)
130 {
131 SetContent(dump);
132 }
133
134 RWebBrowserHandle(const std::string &url, const std::string &tmpdir, const std::string &tmpfile,
137 {
138 }
139
141 {
142#ifdef _MSC_VER
143 if (fHasPid)
144 gSystem->Exec(("taskkill /F /PID " + std::to_string(fPid) + " >NUL 2>NUL").c_str());
145 std::string rmdir = "rmdir /S /Q ";
146#else
147 if (fHasPid)
148 kill(fPid, SIGKILL);
149 std::string rmdir = "rm -rf ";
150#endif
151 if (!fTmpDir.empty())
152 gSystem->Exec((rmdir + fTmpDir).c_str());
154 }
155
156 void RemoveStartupFiles() override
157 {
158#ifdef _MSC_VER
159 std::string rmfile = "del /F ";
160#else
161 std::string rmfile = "rm -f ";
162#endif
163 if (!fTmpFile.empty()) {
164 gSystem->Exec((rmfile + fTmpFile).c_str());
165 fTmpFile.clear();
166 }
167 }
168};
169
170} // namespace ROOT
171
172//////////////////////////////////////////////////////////////////////////////////////////////////
173/// Class to handle starting of web-browsers like Chrome or Firefox
174
176{
177 if (custom) return;
178
179 if (!exec.empty()) {
180 if (exec.find("$url") == std::string::npos) {
181 fProg = exec;
182#ifdef _MSC_VER
183 fExec = exec + " $url";
184#else
185 fExec = exec + " $url &";
186#endif
187 } else {
188 fExec = exec;
189 auto pos = exec.find(" ");
190 if (pos != std::string::npos)
191 fProg = exec.substr(0, pos);
192 }
193 } else if (gSystem->InheritsFrom("TMacOSXSystem")) {
194 fExec = "open \'$url\'";
195 } else if (gSystem->InheritsFrom("TWinNTSystem")) {
196 fExec = "start $url";
197 } else {
198 fExec = "xdg-open \'$url\' &";
199 }
200}
201
202//////////////////////////////////////////////////////////////////////////////////////////////////
203/// Check if browser executable exists and can be used
204
206{
207 if (nexttry.empty() || !fProg.empty())
208 return;
209
211#ifdef R__MACOSX
212 fProg = std::regex_replace(nexttry, std::regex("%20"), " ");
213#else
214 fProg = nexttry;
215#endif
216 return;
217 }
218
219 if (!check_std_paths)
220 return;
221
222#ifdef _MSC_VER
223 std::string ProgramFiles = gSystem->Getenv("ProgramFiles");
224 auto pos = ProgramFiles.find(" (x86)");
225 if (pos != std::string::npos)
226 ProgramFiles.erase(pos, 6);
227 std::string ProgramFilesx86 = gSystem->Getenv("ProgramFiles(x86)");
228
229 if (!ProgramFiles.empty())
230 TestProg(ProgramFiles + nexttry, false);
231 if (!ProgramFilesx86.empty())
232 TestProg(ProgramFilesx86 + nexttry, false);
233#endif
234}
235
236//////////////////////////////////////////////////////////////////////////////////////////////////
237/// Create temporary file for web display
238/// Normally gSystem->TempFileName() method used to create file in default temporary directory
239/// For snap chromium use of default temp directory is not always possible therefore one switches to home directory
240/// But one checks if default temp directory modified and already points to /home folder
241
243{
244 std::string dirname;
245 if (use_home_dir > 0) {
246 if (use_home_dir == 1) {
247 const char *tmp_dir = gSystem->TempDirectory();
248 if (tmp_dir && (strncmp(tmp_dir, "/home", 5) == 0))
249 use_home_dir = 0;
250 else if (!tmp_dir || (strncmp(tmp_dir, "/tmp", 4) == 0))
251 use_home_dir = 2;
252 }
253
254 if (use_home_dir > 1)
256 }
257 return gSystem->TempFileName(name, use_home_dir > 1 ? dirname.c_str() : nullptr, suffix);
258}
259
260static void DummyTimeOutHandler(int /* Sig */) {}
261
262
263//////////////////////////////////////////////////////////////////////////////////////////////////
264/// Display given URL in web browser
265/// \note See more details related to webdisplay on RWebWindowsManager::ShowWindow
266
267std::unique_ptr<RWebDisplayHandle>
269{
270 std::string url = args.GetFullUrl();
271 if (url.empty())
272 return nullptr;
273
275 std::cout << "New web window: " << url << std::endl;
276 return std::make_unique<RWebBrowserHandle>(url, "", "", "");
277 }
278
279 std::string exec;
280 if (args.IsBatchMode())
281 exec = fBatchExec;
282 else if (args.IsHeadless())
283 exec = fHeadlessExec;
284 else if (args.IsStandalone())
285 exec = fExec;
286 else
287 exec = "$prog $url &";
288
289 if (exec.empty())
290 return nullptr;
291
292 std::string swidth = std::to_string(args.GetWidth() > 0 ? args.GetWidth() : 800),
293 sheight = std::to_string(args.GetHeight() > 0 ? args.GetHeight() : 600),
294 sposx = std::to_string(args.GetX() >= 0 ? args.GetX() : 0),
295 sposy = std::to_string(args.GetY() >= 0 ? args.GetY() : 0);
296
297 ProcessGeometry(exec, args);
298
299 std::string extra = args.GetExtraArgs();
300 if (!extra.empty()) {
301 auto p = exec.find("$url");
302 if (p != std::string::npos)
303 exec.insert(p, extra + " ");
304 }
305
306 std::string rmdir = MakeProfile(exec, args.IsBatchMode() || args.IsHeadless());
307
308 std::string tmpfile;
309
310 // these are secret parameters, hide them in temp file
311 if (((url.find("token=") != std::string::npos) || (url.find("key=") != std::string::npos)) && !args.IsBatchMode() && !args.IsHeadless()) {
312 TString filebase = "root_start_";
313
314 auto f = TemporaryFile(filebase, IsSnapBrowser() ? 1 : 0, ".html");
315
316 bool ferr = false;
317
318 if (!f) {
319 ferr = true;
320 } else {
321 std::string content = std::regex_replace(
322 "<!DOCTYPE html>\n"
323 "<html lang=\"en\">\n"
324 "<head>\n"
325 " <meta charset=\"utf-8\">\n"
326 " <meta http-equiv=\"refresh\" content=\"0;url=$url\"/>\n"
327 " <title>Opening ROOT widget</title>\n"
328 "</head>\n"
329 "<body>\n"
330 "<p>\n"
331 " This page should redirect you to a ROOT widget. If it doesn't,\n"
332 " <a href=\"$url\">click here to go to ROOT</a>.\n"
333 "</p>\n"
334 "</body>\n"
335 "</html>\n", std::regex("\\$url"), url);
336
337 if (fwrite(content.c_str(), 1, content.length(), f) != content.length())
338 ferr = true;
339
340 if (fclose(f) != 0)
341 ferr = true;
342
343 tmpfile = filebase.Data();
344
345 url = "file://"s + tmpfile;
346 }
347
348 if (ferr) {
349 if (!tmpfile.empty())
350 gSystem->Unlink(tmpfile.c_str());
351 R__LOG_ERROR(WebGUILog()) << "Fail to create temporary HTML file to startup widget";
352 return nullptr;
353 }
354 }
355
356 exec = std::regex_replace(exec, std::regex("\\$rootetcdir"), TROOT::GetEtcDir().Data());
357 exec = std::regex_replace(exec, std::regex("\\$url"), url);
358 exec = std::regex_replace(exec, std::regex("\\$width"), swidth);
359 exec = std::regex_replace(exec, std::regex("\\$height"), sheight);
360 exec = std::regex_replace(exec, std::regex("\\$posx"), sposx);
361 exec = std::regex_replace(exec, std::regex("\\$posy"), sposy);
362
363 if (exec.compare(0,5,"fork:") == 0) {
364 if (fProg.empty()) {
365 if (!tmpfile.empty())
366 gSystem->Unlink(tmpfile.c_str());
367 R__LOG_ERROR(WebGUILog()) << "Fork instruction without executable";
368 return nullptr;
369 }
370
371 exec.erase(0, 5);
372
373 // in case of redirection process will wait until output is produced
374 std::string redirect = args.GetRedirectOutput();
375
376#ifndef _MSC_VER
377
378 std::unique_ptr<TObjArray> fargs(TString(exec.c_str()).Tokenize(" "));
379 if (!fargs || (fargs->GetLast()<=0)) {
380 if (!tmpfile.empty())
381 gSystem->Unlink(tmpfile.c_str());
382 R__LOG_ERROR(WebGUILog()) << "Fork instruction is empty";
383 return nullptr;
384 }
385
386 std::vector<char *> argv;
387 argv.push_back((char *) fProg.c_str());
388 for (Int_t n = 0; n <= fargs->GetLast(); ++n)
389 argv.push_back((char *)fargs->At(n)->GetName());
390 argv.push_back(nullptr);
391
392 R__LOG_DEBUG(0, WebGUILog()) << "Show web window in browser with posix_spawn:\n" << fProg << " " << exec;
393
396 if (redirect.empty())
398 else
401
402#ifdef R__MACOSX
403 char **envp = *_NSGetEnviron();
404#elif defined (__FreeBSD__)
405 //this is needed because the FreeBSD linker does not like to resolve these special symbols
406 //in shared libs with -Wl,--no-undefined
407 char** envp = (char**)dlsym(RTLD_DEFAULT, "environ");
408#else
409 char **envp = environ;
410#endif
411
412 pid_t pid;
413 int status = posix_spawn(&pid, argv[0], &action, nullptr, argv.data(), envp);
414
416
417 if (status != 0) {
418 if (!tmpfile.empty())
419 gSystem->Unlink(tmpfile.c_str());
420 R__LOG_ERROR(WebGUILog()) << "Fail to launch " << argv[0];
421 return nullptr;
422 }
423
424 if (!redirect.empty()) {
425 Int_t batch_timeout = gEnv->GetValue("WebGui.BatchTimeout", 30);
426 struct sigaction Act, Old;
427 int elapsed_time = 0;
428
429 if (batch_timeout) {
430 memset(&Act, 0, sizeof(Act));
431 Act.sa_handler = DummyTimeOutHandler;
432 sigemptyset(&Act.sa_mask);
437 }
438
439 int job_done = 0;
440 std::string dump_content;
441
442 while (!job_done) {
443
444 // wait until output is produced
445 int wait_status = 0;
446
448
449 // try read dump anyway
451
452 if (dump_content.find("<div>###batch###job###done###</div>") != std::string::npos)
453 job_done = 1;
454
455 if (wait_res == -1) {
456 // failure when finish process
458 if ((errno == EINTR) && (alarm_timeout > 0) && !job_done) {
459 if (alarm_timeout > 2) alarm_timeout = 2;
462 } else {
463 // end of timeout - do not try to wait any longer
464 job_done = 1;
465 }
466 } else if (!WIFEXITED(wait_status) && !WIFSIGNALED(wait_status)) {
467 // abnormal end of browser process
468 job_done = 1;
469 } else {
470 // this is normal finish, no need for process kill
471 job_done = 2;
472 }
473 }
474
475 if (job_done != 2) {
476 // kill browser process when no normal end was detected
477 kill(pid, SIGKILL);
478 }
479
480 if (batch_timeout) {
481 alarm(0); // disable alarm
482 sigaction(SIGALRM, &Old, nullptr);
483 }
484
485 if (gEnv->GetValue("WebGui.PreserveBatchFiles", -1) > 0)
486 ::Info("RWebDisplayHandle::Display", "Preserve dump file %s", redirect.c_str());
487 else
488 gSystem->Unlink(redirect.c_str());
489
490 return std::make_unique<RWebBrowserHandle>(url, rmdir, tmpfile, dump_content);
491 }
492
493 // add processid and rm dir
494
495 return std::make_unique<RWebBrowserHandle>(url, rmdir, tmpfile, pid);
496
497#else
498
499 if (fProg.empty()) {
500 if (!tmpfile.empty())
501 gSystem->Unlink(tmpfile.c_str());
502 R__LOG_ERROR(WebGUILog()) << "No Web browser found";
503 return nullptr;
504 }
505
506 // use UnixPathName to simplify handling of backslashes
507 exec = "wmic process call create '"s + gSystem->UnixPathName(fProg.c_str()) + " " + exec + "' | find \"ProcessId\" "s;
508 std::string process_id = gSystem->GetFromPipe(exec.c_str()).Data();
509 std::stringstream ss(process_id);
510 std::string tmp;
511 char c;
512 int pid = 0;
513 ss >> tmp >> c >> pid;
514
515 if (pid <= 0) {
516 if (!tmpfile.empty())
517 gSystem->Unlink(tmpfile.c_str());
518 R__LOG_ERROR(WebGUILog()) << "Fail to launch " << fProg;
519 return nullptr;
520 }
521
522 // add processid and rm dir
523 return std::make_unique<RWebBrowserHandle>(url, rmdir, tmpfile, pid);
524#endif
525 }
526
527#ifdef _MSC_VER
528
529 if (exec.rfind("&") == exec.length() - 1) {
530
531 // if last symbol is &, use _spawn to detach execution
532 exec.resize(exec.length() - 1);
533
534 std::vector<char *> argv;
535 std::string firstarg = fProg;
536 auto slashpos = firstarg.find_last_of("/\\");
537 if (slashpos != std::string::npos)
538 firstarg.erase(0, slashpos + 1);
539 argv.push_back((char *)firstarg.c_str());
540
541 std::unique_ptr<TObjArray> fargs(TString(exec.c_str()).Tokenize(" "));
542 for (Int_t n = 1; n <= fargs->GetLast(); ++n)
543 argv.push_back((char *)fargs->At(n)->GetName());
544 argv.push_back(nullptr);
545
546 R__LOG_DEBUG(0, WebGUILog()) << "Showing web window in " << fProg << " with:\n" << exec;
547
548 _spawnv(_P_NOWAIT, gSystem->UnixPathName(fProg.c_str()), argv.data());
549
550 return std::make_unique<RWebBrowserHandle>(url, rmdir, tmpfile, ""s);
551 }
552
553 std::string prog = "\""s + gSystem->UnixPathName(fProg.c_str()) + "\""s;
554
555#else
556
557#ifdef R__MACOSX
558 std::string prog = std::regex_replace(fProg, std::regex(" "), "\\ ");
559#else
560 std::string prog = fProg;
561#endif
562
563#endif
564
565 exec = std::regex_replace(exec, std::regex("\\$prog"), prog);
566
567 std::string redirect = args.GetRedirectOutput(), dump_content;
568
569 if (!redirect.empty()) {
570 if (exec.find("$dumpfile") != std::string::npos) {
571 exec = std::regex_replace(exec, std::regex("\\$dumpfile"), redirect);
572 } else {
573 auto p = exec.length();
574 if (exec.rfind("&") == p-1) --p;
575 exec.insert(p, " >"s + redirect + " "s);
576 }
577 }
578
579 R__LOG_DEBUG(0, WebGUILog()) << "Showing web window in browser with:\n" << exec;
580
581 gSystem->Exec(exec.c_str());
582
583 // read content of redirected output
584 if (!redirect.empty()) {
586
587 if (gEnv->GetValue("WebGui.PreserveBatchFiles", -1) > 0)
588 ::Info("RWebDisplayHandle::Display", "Preserve dump file %s", redirect.c_str());
589 else
590 gSystem->Unlink(redirect.c_str());
591 }
592
593 return std::make_unique<RWebBrowserHandle>(url, rmdir, tmpfile, dump_content);
594}
595
596//////////////////////////////////////////////////////////////////////////////////////////////////
597/// Constructor
598
600{
601 fExec = gEnv->GetValue("WebGui.SafariInteractive", "open -a Safari $url");
602}
603
604//////////////////////////////////////////////////////////////////////////////////////////////////
605/// Returns true if it can be used
606
608{
609#ifdef R__MACOSX
610 return true;
611#else
612 return false;
613#endif
614}
615
616//////////////////////////////////////////////////////////////////////////////////////////////////
617/// Constructor
618
620{
621 fEdge = _edge;
622
623 fEnvPrefix = fEdge ? "WebGui.Edge" : "WebGui.Chrome";
624
625 TestProg(gEnv->GetValue(fEnvPrefix.c_str(), ""));
626
627 if (!fProg.empty() && !fEdge)
628 fChromeVersion = gEnv->GetValue("WebGui.ChromeVersion", -1);
629
630#ifdef _MSC_VER
631 if (fEdge)
632 TestProg("\\Microsoft\\Edge\\Application\\msedge.exe", true);
633 else
634 TestProg("\\Google\\Chrome\\Application\\chrome.exe", true);
635#endif
636#ifdef R__MACOSX
637 TestProg("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome");
638#endif
639#ifdef R__LINUX
640 TestProg("/snap/bin/chromium"); // test snap before to detect it properly
641 TestProg("/usr/bin/chromium");
642 TestProg("/usr/bin/chromium-browser");
643 TestProg("/usr/bin/chrome-browser");
644 TestProg("/usr/bin/google-chrome-stable");
645 TestProg("/usr/bin/google-chrome");
646#endif
647
648// --no-sandbox is required to run chrome with super-user, but only in headless mode
649// --headless=new was used when both old and new were available, but old was removed from chrome 132, see https://developer.chrome.com/blog/removing-headless-old-from-chrome
650
651#ifdef _MSC_VER
652 // here --headless=old was used to let normally end of Edge process when --dump-dom is used
653 // while on Windows chrome and edge version not tested, just suppose that newest chrome is used
654 fBatchExec = gEnv->GetValue((fEnvPrefix + "Batch").c_str(), "$prog --headless --no-sandbox $geometry --dump-dom $url");
655 // in interactive headless mode fork used to let stop browser via process id
656 fHeadlessExec = gEnv->GetValue((fEnvPrefix + "Headless").c_str(), "fork:--headless --no-sandbox --disable-gpu $geometry \"$url\"");
657 fExec = gEnv->GetValue((fEnvPrefix + "Interactive").c_str(), "$prog $geometry --new-window --app=$url &"); // & in windows mean usage of spawn
658#else
659#ifdef R__MACOSX
660 bool use_normal = true; // mac does not like new flag
661#else
662 bool use_normal = (fChromeVersion < 119) || (fChromeVersion > 131);
663#endif
664 if (use_normal) {
666 // in starting from version 151 one have to allow use of unsafe swiftshader
667 if (fChromeVersion > 150)
668 extra_arg = "--enable-unsafe-swiftshader";
669 // in docker disable shared memory usage because of limited resources
670 if (!gSystem->AccessPathName("/.dockerenv", kFileExists))
671 extra_arg.Append(" --disable-dev-shm-usage");
672 // old or newest browser with standard headless mode
673 fBatchExec = gEnv->GetValue((fEnvPrefix + "Batch").c_str(), TString::Format("fork:--headless --no-sandbox --disable-extensions --disable-audio-output %s $geometry --dump-dom $url", extra_arg.Data()).Data());
674 fHeadlessExec = gEnv->GetValue((fEnvPrefix + "Headless").c_str(), TString::Format("fork:--headless --no-sandbox --disable-extensions --disable-audio-output %s $geometry $url", extra_arg.Data()).Data());
675 } else {
676 // newer version with headless=new mode
677 fBatchExec = gEnv->GetValue((fEnvPrefix + "Batch").c_str(), "fork:--headless=new --no-sandbox --disable-extensions --disable-audio-output $geometry --dump-dom $url");
678 fHeadlessExec = gEnv->GetValue((fEnvPrefix + "Headless").c_str(), "fork:--headless=new --no-sandbox --disable-extensions --disable-audio-output $geometry $url");
679 }
680 fExec = gEnv->GetValue((fEnvPrefix + "Interactive").c_str(), "$prog $geometry --new-window --app=\'$url\' >/dev/null 2>/dev/null &");
681#endif
682}
683
684
685//////////////////////////////////////////////////////////////////////////////////////////////////
686/// Replace $geometry placeholder with geometry settings
687/// Also RWebDisplayArgs::GetExtraArgs() are appended
688
690{
691 std::string geometry;
692 if ((args.GetWidth() > 0) && (args.GetHeight() > 0))
693 geometry = "--window-size="s + std::to_string(args.GetWidth())
694 + (args.IsHeadless() ? "x"s : ","s)
695 + std::to_string(args.GetHeight());
696
697 if (((args.GetX() >= 0) || (args.GetY() >= 0)) && !args.IsHeadless()) {
698 if (!geometry.empty()) geometry.append(" ");
699 geometry.append("--window-position="s + std::to_string(args.GetX() >= 0 ? args.GetX() : 0) + ","s +
700 std::to_string(args.GetY() >= 0 ? args.GetY() : 0));
701 }
702
703 exec = std::regex_replace(exec, std::regex("\\$geometry"), geometry);
704}
705
706
707//////////////////////////////////////////////////////////////////////////////////////////////////
708/// Handle profile argument
709
710std::string RWebDisplayHandle::ChromeCreator::MakeProfile(std::string &exec, bool)
711{
712 std::string rmdir, profile_arg;
713
714 if (exec.find("$profile") == std::string::npos)
715 return rmdir;
716
717 const char *chrome_profile = gEnv->GetValue((fEnvPrefix + "Profile").c_str(), "");
720 } else {
722 rnd.SetSeed(0);
724 if ((profile_arg.compare(0, 4, "/tmp") == 0) && IsSnapBrowser())
726
727#ifdef _MSC_VER
728 char slash = '\\';
729#else
730 char slash = '/';
731#endif
732 if (!profile_arg.empty() && (profile_arg[profile_arg.length()-1] != slash))
734 profile_arg += "root_chrome_profile_"s + std::to_string(rnd.Integer(0x100000));
735
736 rmdir = profile_arg;
737 }
738
739 exec = std::regex_replace(exec, std::regex("\\$profile"), profile_arg);
740
741 return rmdir;
742}
743
744
745//////////////////////////////////////////////////////////////////////////////////////////////////
746/// Constructor
747
749{
750 TestProg(gEnv->GetValue("WebGui.Firefox", ""));
751
752#ifdef _MSC_VER
753 TestProg("\\Mozilla Firefox\\firefox.exe", true);
754#endif
755#ifdef R__MACOSX
756 TestProg("/Applications/Firefox.app/Contents/MacOS/firefox");
757#endif
758#ifdef R__LINUX
759 TestProg("/snap/bin/firefox");
760 TestProg("/usr/bin/firefox");
761 TestProg("/usr/bin/firefox-bin");
762#endif
763
764#ifdef _MSC_VER
765 // there is a problem when specifying the window size with wmic on windows:
766 // It gives: Invalid format. Hint: <paramlist> = <param> [, <paramlist>].
767 fBatchExec = gEnv->GetValue("WebGui.FirefoxBatch", "$prog -headless -no-remote $profile $url");
768 fHeadlessExec = gEnv->GetValue("WebGui.FirefoxHeadless", "fork:-headless -no-remote $profile \"$url\"");
769 fExec = gEnv->GetValue("WebGui.FirefoxInteractive", "$prog -no-remote $profile $geometry $url &");
770#else
771 fBatchExec = gEnv->GetValue("WebGui.FirefoxBatch", "fork:--headless -no-remote -new-instance $profile $url");
772 fHeadlessExec = gEnv->GetValue("WebGui.FirefoxHeadless", "fork:--headless -no-remote $profile --private-window $url");
773 fExec = gEnv->GetValue("WebGui.FirefoxInteractive", "$rootetcdir/runfirefox.sh __nodump__ $cleanup_profile $prog -no-remote $profile $geometry -url \'$url\' &");
774#endif
775}
776
777//////////////////////////////////////////////////////////////////////////////////////////////////
778/// Process window geometry for Firefox
779
781{
782 std::string geometry;
783 if ((args.GetWidth() > 0) && (args.GetHeight() > 0) && !args.IsHeadless())
784 geometry = "-width="s + std::to_string(args.GetWidth()) + " -height=" + std::to_string(args.GetHeight());
785
786 exec = std::regex_replace(exec, std::regex("\\$geometry"), geometry);
787}
788
789//////////////////////////////////////////////////////////////////////////////////////////////////
790/// Create Firefox profile to run independent browser window
791
793{
794 std::string rmdir, profile_arg;
795
796 if (exec.find("$profile") == std::string::npos)
797 return rmdir;
798
799 const char *ff_profile = gEnv->GetValue("WebGui.FirefoxProfile", "");
800 const char *ff_profilepath = gEnv->GetValue("WebGui.FirefoxProfilePath", "");
801 Int_t ff_randomprofile = RWebWindowWSHandler::GetBoolEnv("WebGui.FirefoxRandomProfile", 1);
802 if (ff_profile && *ff_profile) {
803 profile_arg = "-P "s + ff_profile;
804 } else if (ff_profilepath && *ff_profilepath) {
805 profile_arg = "-profile "s + ff_profilepath;
806 } else if (ff_randomprofile > 0) {
808 rnd.SetSeed(0);
809 std::string profile_dir = gSystem->TempDirectory();
810 if ((profile_dir.compare(0, 4, "/tmp") == 0) && IsSnapBrowser())
812
813#ifdef _MSC_VER
814 char slash = '\\';
815#else
816 char slash = '/';
817#endif
818 if (!profile_dir.empty() && (profile_dir[profile_dir.length()-1] != slash))
820 profile_dir += "root_ff_profile_"s + std::to_string(rnd.Integer(0x100000));
821
822 profile_arg = "-profile "s + profile_dir;
823
824 if (gSystem->mkdir(profile_dir.c_str()) == 0) {
825 rmdir = profile_dir;
826
827 std::ofstream user_js(profile_dir + "/user.js", std::ios::trunc);
828 // workaround for current Firefox, without such settings it fail to close window and terminate it from batch
829 // also disable question about upload of data
830 user_js << "user_pref(\"datareporting.policy.dataSubmissionPolicyBypassNotification\", true);" << std::endl;
831 user_js << "user_pref(\"datareporting.policy.dataSubmissionPolicyAcceptedVersion\", 2);" << std::endl;
832 user_js << "user_pref(\"datareporting.policy.dataSubmissionPolicyNotifiedTime\", \"1635760572813\");" << std::endl;
833
834 // try to avoid any kind of dialogs on the start
835 user_js << "user_pref(\"app.update.auto\", false);" << std::endl;
836 user_js << "user_pref(\"browser.shell.checkDefaultBrowser\", false);" << std::endl;
837 user_js << "user_pref(\"browser.aboutwelcome.enabled\", false);" << std::endl;
838 user_js << "user_pref(\"browser.tabs.disableBackgroundLinkLoading\", true);" << std::endl;
839
840 // try to ensure that window closes with last tab
841 user_js << "user_pref(\"browser.tabs.closeWindowWithLastTab\", true);" << std::endl;
842 user_js << "user_pref(\"dom.allow_scripts_to_close_windows\", true);" << std::endl;
843 user_js << "user_pref(\"browser.sessionstore.resume_from_crash\", false);" << std::endl;
844
845 if (batch_mode) {
846 // allow to dump messages to std output
847 user_js << "user_pref(\"browser.dom.window.dump.enabled\", true);" << std::endl;
848 } else {
849 // to suppress annoying privacy tab
850 user_js << "user_pref(\"datareporting.policy.firstRunURL\", \"\");" << std::endl;
851 // to use custom userChrome.css files
852 user_js << "user_pref(\"toolkit.legacyUserProfileCustomizations.stylesheets\", true);" << std::endl;
853 // do not put tabs in title
854 user_js << "user_pref(\"browser.tabs.inTitlebar\", 0);" << std::endl;
855
856#ifdef R__LINUX
857 // fix WebGL creation problem on some Linux platforms
858 user_js << "user_pref(\"webgl.out-of-process\", false);" << std::endl;
859#endif
860
861 std::ofstream times_json(profile_dir + "/times.json", std::ios::trunc);
862 times_json << "{" << std::endl;
863 times_json << " \"created\": 1699968480952," << std::endl;
864 times_json << " \"firstUse\": null" << std::endl;
865 times_json << "}" << std::endl;
866 if (gSystem->mkdir((profile_dir + "/chrome").c_str()) == 0) {
867 std::ofstream style(profile_dir + "/chrome/userChrome.css", std::ios::trunc);
868 // do not show tabs
869 style << "#TabsToolbar { visibility: collapse; }" << std::endl;
870 // do not show URL
871 style << "#nav-bar, #urlbar-container, #searchbar { visibility: collapse !important; }" << std::endl;
872 }
873 }
874
875 } else {
876 R__LOG_ERROR(WebGUILog()) << "Cannot create Firefox profile directory " << profile_dir;
877 }
878 }
879
880 exec = std::regex_replace(exec, std::regex("\\$profile"), profile_arg);
881
882 if (exec.find("$cleanup_profile") != std::string::npos) {
883 if (rmdir.empty()) rmdir = "__dummy__";
884 exec = std::regex_replace(exec, std::regex("\\$cleanup_profile"), rmdir);
885 rmdir.clear(); // no need to delete directory - it will be removed by script
886 }
887
888 return rmdir;
889}
890
891///////////////////////////////////////////////////////////////////////////////////////////////////
892/// Check if http server required for display
893/// \param args - defines where and how to display web window
894
896{
899 return false;
900
901 if (!args.IsHeadless() && (args.GetBrowserKind() == RWebDisplayArgs::kOn)) {
902
903#ifdef WITH_QT6WEB
904 auto &qt6 = FindCreator("qt6", "libROOTQt6WebDisplay");
905 if (qt6 && qt6->IsActive())
906 return false;
907#endif
908#ifdef WITH_CEFWEB
909 auto &cef = FindCreator("cef", "libROOTCefDisplay");
910 if (cef && cef->IsActive())
911 return false;
912#endif
913 }
914
915 return true;
916}
917
918
919///////////////////////////////////////////////////////////////////////////////////////////////////
920/// Create web display
921/// \param args - defines where and how to display web window
922/// Returns RWebDisplayHandle, which holds information of running browser application
923/// Can be used fully independent from RWebWindow classes just to show any web page
924
925std::unique_ptr<RWebDisplayHandle> RWebDisplayHandle::Display(const RWebDisplayArgs &args)
926{
927 std::unique_ptr<RWebDisplayHandle> handle;
928
930 return handle;
931
932 auto try_creator = [&](std::unique_ptr<Creator> &creator) {
933 if (!creator || !creator->IsActive())
934 return false;
935 handle = creator->Display(args);
936 return handle ? true : false;
937 };
938
940 (!args.IsHeadless() && (args.GetBrowserKind() == RWebDisplayArgs::kOn)),
941 has_qt6web = false, has_cefweb = false;
942
943#ifdef WITH_QT6WEB
944 has_qt6web = true;
945#endif
946
947#ifdef WITH_CEFWEB
948 has_cefweb = true;
949#endif
950
952 if (try_creator(FindCreator("qt6", "libROOTQt6WebDisplay")))
953 return handle;
954 }
955
957 if (try_creator(FindCreator("cef", "libROOTCefDisplay")))
958 return handle;
959 }
960
961 if (args.IsLocalDisplay()) {
962 R__LOG_ERROR(WebGUILog()) << "Neither Qt5/6 nor CEF libraries were found to provide local display";
963 return handle;
964 }
965
966 bool handleAsNative =
968
970 if (try_creator(FindCreator("chrome", "ChromeCreator")))
971 return handle;
972 }
973
975 if (try_creator(FindCreator("firefox", "FirefoxCreator")))
976 return handle;
977 }
978
979#ifdef _MSC_VER
980 // Edge browser cannot be run headless without registry change, therefore do not try it by default
981 if ((handleAsNative && !args.IsHeadless() && !args.IsBatchMode()) || (args.GetBrowserKind() == RWebDisplayArgs::kEdge)) {
982 if (try_creator(FindCreator("edge", "ChromeCreator")))
983 return handle;
984 }
985#endif
986
989 // R__LOG_ERROR(WebGUILog()) << "Neither Chrome nor Firefox browser cannot be started to provide display";
990 return handle;
991 }
992
994 if (try_creator(FindCreator("safari", "SafariCreator")))
995 return handle;
996 }
997
999 std::unique_ptr<Creator> creator = std::make_unique<BrowserCreator>(false, args.GetCustomExec());
1000 try_creator(creator);
1001 } else {
1002 try_creator(FindCreator("browser", "BrowserCreator"));
1003 }
1004
1005 return handle;
1006}
1007
1008///////////////////////////////////////////////////////////////////////////////////////////////////
1009/// Display provided url in configured web browser
1010/// \param url - specified URL address like https://root.cern
1011/// Browser can specified when starting `root --web=firefox`
1012/// Returns true when browser started
1013/// It is convenience method, equivalent to:
1014/// ~~~
1015/// RWebDisplayArgs args;
1016/// args.SetUrl(url);
1017/// args.SetStandalone(false);
1018/// auto handle = RWebDisplayHandle::Display(args);
1019/// ~~~
1020
1021bool RWebDisplayHandle::DisplayUrl(const std::string &url)
1022{
1023 RWebDisplayArgs args;
1024 args.SetUrl(url);
1025 args.SetStandalone(false);
1026
1027 auto handle = Display(args);
1028
1029 return !!handle;
1030}
1031
1032///////////////////////////////////////////////////////////////////////////////////////////////////
1033/// Checks if configured browser can be used for image production
1034
1036{
1040 bool detected = false;
1041
1042 auto &h1 = FindCreator("chrome", "ChromeCreator");
1043 if (h1 && h1->IsActive()) {
1045 detected = true;
1046 }
1047
1048 if (!detected) {
1049 auto &h2 = FindCreator("firefox", "FirefoxCreator");
1050 if (h2 && h2->IsActive()) {
1052 detected = true;
1053 }
1054 }
1055
1056 return detected;
1057 }
1058
1060 auto &h1 = FindCreator("chrome", "ChromeCreator");
1061 return h1 && h1->IsActive();
1062 }
1063
1065 auto &h2 = FindCreator("firefox", "FirefoxCreator");
1066 return h2 && h2->IsActive();
1067 }
1068
1069#ifdef _MSC_VER
1070 if (args.GetBrowserKind() == RWebDisplayArgs::kEdge) {
1071 auto &h3 = FindCreator("edge", "ChromeCreator");
1072 return h3 && h3->IsActive();
1073 }
1074#endif
1075
1076 return true;
1077}
1078
1079///////////////////////////////////////////////////////////////////////////////////////////////////
1080/// Returns true if image production for specified browser kind is supported
1081/// If browser not specified - use currently configured browser or try to test existing web browsers
1082
1084{
1086
1087 return CheckIfCanProduceImages(args);
1088}
1089
1090///////////////////////////////////////////////////////////////////////////////////////////////////
1091/// Detect image format
1092/// There is special handling of ".screenshot.pdf" and ".screenshot.png" extensions
1093/// Creation of such files relies on headless browser functionality and fully supported only by Chrome browser
1094
1095std::string RWebDisplayHandle::GetImageFormat(const std::string &fname)
1096{
1097 std::string _fname = fname;
1098 std::transform(_fname.begin(), _fname.end(), _fname.begin(), ::tolower);
1099 auto EndsWith = [&_fname](const std::string &suffix) {
1100 return (_fname.length() > suffix.length()) ? (0 == _fname.compare(_fname.length() - suffix.length(), suffix.length(), suffix)) : false;
1101 };
1102
1103 if (EndsWith(".screenshot.pdf"))
1104 return "s.pdf"s;
1105 if (EndsWith(".pdf"))
1106 return "pdf"s;
1107 if (EndsWith(".json"))
1108 return "json"s;
1109 if (EndsWith(".svg"))
1110 return "svg"s;
1111 if (EndsWith(".screenshot.png"))
1112 return "s.png"s;
1113 if (EndsWith(".png"))
1114 return "png"s;
1115 if (EndsWith(".html") || EndsWith(".htm"))
1116 return "html"s;
1117 if (EndsWith(".jpg") || EndsWith(".jpeg"))
1118 return "jpeg"s;
1119 if (EndsWith(".webp"))
1120 return "webp"s;
1121
1122 return ""s;
1123}
1124
1125
1126///////////////////////////////////////////////////////////////////////////////////////////////////
1127/// Produce image file using JSON data as source
1128/// Invokes JSROOT drawing functionality in headless browser - Google Chrome or Mozilla Firefox
1129
1130bool RWebDisplayHandle::ProduceImage(const std::string &fname, const std::string &json, int width, int height, const char *batch_file)
1131{
1132 return ProduceImages(fname, {json}, {width}, {height}, batch_file);
1133}
1134
1135
1136///////////////////////////////////////////////////////////////////////////////////////////////////
1137/// Produce vector of file names for specified file pattern
1138/// Depending from supported file formats
1139
1140std::vector<std::string> RWebDisplayHandle::ProduceImagesNames(const std::string &fname, unsigned nfiles)
1141{
1142 auto fmt = GetImageFormat(fname);
1143
1144 std::vector<std::string> fnames;
1145
1146 if ((fmt == "s.pdf") || (fmt == "s.png")) {
1147 fnames.emplace_back(fname);
1148 } else {
1149 std::string farg = fname;
1150
1151 bool has_quialifier = farg.find("%") != std::string::npos;
1152
1153 if (!has_quialifier && (nfiles > 1) && (fmt != "pdf") && (fmt != "html")) {
1154 farg.insert(farg.rfind("."), "%d");
1155 has_quialifier = true;
1156 }
1157
1158 for (unsigned n = 0; n < nfiles; n++) {
1159 if(has_quialifier) {
1160 auto expand_name = TString::Format(farg.c_str(), (int) n);
1161 fnames.emplace_back(expand_name.Data());
1162 } else if (n > 0)
1163 fnames.emplace_back(""); // empty name is multiPdf or multiHtml
1164 else
1165 fnames.emplace_back(fname);
1166 }
1167 }
1168
1169 return fnames;
1170}
1171
1172
1173///////////////////////////////////////////////////////////////////////////////////////////////////
1174/// Produce image file(s) using JSON data as source
1175/// Invokes JSROOT drawing functionality in headless browser - Google Chrome or Mozilla Firefox
1176
1177bool 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)
1178{
1180}
1181
1182///////////////////////////////////////////////////////////////////////////////////////////////////
1183/// Produce image file(s) using JSON data as source
1184/// Invokes JSROOT drawing functionality in headless browser - Google Chrome or Mozilla Firefox
1185
1186bool 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)
1187{
1188 if (fnames.empty() || jsons.empty())
1189 return false;
1190
1191 std::vector<std::string> fmts;
1192 unsigned num_non_empty_fmts = 0, num_non_empty_files = 0;
1193 for (auto& fname : fnames) {
1194 if (!fname.empty())
1196 std::string fmt = GetImageFormat(fname);
1197 if (!fmt.empty())
1199 fmts.emplace_back(fmt);
1200 }
1201
1202 bool is_any_image = false;
1203
1204 const char *jsrootsys = gSystem->Getenv("JSROOTSYS");
1205
1206 for (unsigned n = 0; (n < fmts.size()) && (n < jsons.size()); n++) {
1207 if (fmts[n] == "json") {
1208 std::ofstream ofs(fnames[n]);
1209 ofs << jsons[n];
1210 fmts[n].clear();
1211 ::Info("ProduceImages", "JSON file %s size %d bytes has been created", fnames[n].c_str(), (int) jsons[n].length());
1212
1213 } else if (fmts[n] == "html") {
1214 bool is_multi_html = (num_non_empty_fmts == 1) && (num_non_empty_files == 1) && (jsons.size() > 1) && (n == 0);
1215
1216 std::string filejsrootsys;
1217 if (jsrootsys)
1219 if (filejsrootsys.empty() || ((filejsrootsys.find("http://") != 0) && (filejsrootsys.find("https://") != 0)))
1220 filejsrootsys = "https://root.cern/js/latest";
1221
1222 std::ofstream ofs(fnames[n]);
1223
1224 ofs << "<!DOCTYPE html>\n"
1225 "<html lang=\"en\">\n"
1226 "<head>\n"
1227 " <meta charset=\"utf-8\">\n"
1228 " <title>Dsiplay of ROOT " << (is_multi_html ? "objects" : "object") << "</title>\n"
1229 " <link rel=\"shortcut icon\" href=\"" << filejsrootsys << "/img/RootIcon.ico\"/>\n"
1230 " <script type=\"importmap\">\n"
1231 " { \"imports\": { \"jsroot\": \"" << filejsrootsys << "/modules/main.mjs\" } }\n"
1232 " </script>\n"
1233 " <style>\n";
1234 if (is_multi_html) {
1235 ofs << " .root-container {\n"
1236 " display: flex;\n"
1237 " flex-direction: column;\n"
1238 " align-items: center;\n"
1239 " gap: 20px;\n"
1240 " width: 100%;\n"
1241 " }\n";
1242 } else {
1243 ofs << " body {\n"
1244 " margin: 0;\n"
1245 " padding: 0;\n"
1246 " display: flex;\n"
1247 " justify-content: center;\n"
1248 " align-items: center;\n"
1249 " min-height: 100vh;\n"
1250 " background-color: #f0f0f0;\n"
1251 " }\n";
1252 }
1253 ofs << " .root-drawing {\n"
1254 " background-color: white;\n"
1255 " box-shadow: 0 4px 10px rgba(0,0,0,0.1);\n"
1256 " }\n"
1257 " </style>\n"
1258 "</head>\n"
1259 "<body>\n";
1260 if (is_multi_html) {
1261 ofs << " <div class=\"root-container\">\n";
1262 for (unsigned k = 0; k < jsons.size(); ++k)
1263 ofs << " <div id=\"drawing" << k << "\" class=\"root-drawing\""
1264 " style=\"width: " << widths[k] << "px;"
1265 " height: " << heights[k] << "px;\"></div>\n";
1266 ofs << " </div>\n";
1267 } else {
1268 ofs << " <div id=\"drawing\" class=\"root-drawing\""
1269 " style=\"width: " << widths[n] << "px;"
1270 " min-height: " << heights[n] << "px;\"></div>\n";
1271 }
1272 ofs << " <script type=\"module\">\n"
1273 " import { parse, draw } from \"jsroot\";\n";
1274 if (is_multi_html) {
1275 for (unsigned k = 0; k < jsons.size(); ++k) {
1276 ofs << " const obj" << k << " = parse(" << jsons[k] << ");\n"
1277 " draw(\"drawing" << k << "\", obj" << k << ");\n";
1278 }
1279 } else {
1280 ofs << " const obj = parse(" << jsons[n] << ");\n"
1281 " draw(\"drawing\", obj);\n";
1282 }
1283 ofs << " </script>\n"
1284 "</body>\n"
1285 "</html>\n";
1286
1287 ::Info("ProduceImages", "HTML file %s size %d bytes has been created", fnames[n].c_str(), (int) ofs.tellp());
1288
1289 fmts[n].clear();
1290 if (is_multi_html)
1291 break;
1292 } else if (!fmts[n].empty())
1293 is_any_image = true;
1294 }
1295
1296 if (!is_any_image)
1297 return true;
1298
1299 std::string fdebug;
1300 if (fnames.size() == 1)
1301 fdebug = fnames[0];
1302 else
1304
1306 if (!jsrootsys) {
1307 jsrootsysdflt = TROOT::GetDataDir() + "/js";
1309 R__LOG_ERROR(WebGUILog()) << "Fail to locate JSROOT " << jsrootsysdflt;
1310 return false;
1311 }
1312 jsrootsys = jsrootsysdflt.Data();
1313 }
1314
1315 RWebDisplayArgs args; // set default browser kind, only Chrome/Firefox/Edge or CEF/Qt6 can be used here
1316 if (!CheckIfCanProduceImages(args)) {
1317 R__LOG_ERROR(WebGUILog()) << "Fail to detect supported browsers for image production";
1318 return false;
1319 }
1320
1324
1325 std::vector<std::string> draw_kinds;
1326 bool use_browser_draw = false, can_optimize_json = false;
1327 int use_home_dir = 0;
1329
1330 // Some Chrome installation do not allow run html code from files, created in /tmp directory
1331 // When during session such failures happened, force usage of home directory from the beginning
1332 static int chrome_tmp_workaround = 0;
1333
1334 if (isChrome) {
1336 auto &h1 = FindCreator("chrome", "ChromeCreator");
1337 if (h1 && h1->IsActive() && h1->IsSnapBrowser() && (use_home_dir == 0))
1338 use_home_dir = 1;
1339 }
1340
1341 if (fmts[0] == "s.png") {
1342 if (!isChromeBased && !isFirefox) {
1343 R__LOG_ERROR(WebGUILog()) << "Direct png image creation supported only by Chrome and Firefox browsers";
1344 return false;
1345 }
1346 use_browser_draw = true;
1347 jsonkind = "1111"; // special mark in canv_batch.htm
1348 } else if (fmts[0] == "s.pdf") {
1349 if (!isChromeBased) {
1350 R__LOG_ERROR(WebGUILog()) << "Direct creation of PDF files supported only by Chrome-based browser";
1351 return false;
1352 }
1353 use_browser_draw = true;
1354 jsonkind = "2222"; // special mark in canv_batch.htm
1355 } else {
1356 draw_kinds = fmts;
1358 can_optimize_json = true;
1359 }
1360
1361 if (!batch_file || !*batch_file)
1362 batch_file = "/js/files/canv_batch.htm";
1363
1366 R__LOG_ERROR(WebGUILog()) << "Fail to find " << origin;
1367 return false;
1368 }
1369
1371 if (filecont.empty()) {
1372 R__LOG_ERROR(WebGUILog()) << "Fail to read content of " << origin;
1373 return false;
1374 }
1375
1376 int max_width = 0, max_height = 0, page_margin = 10;
1377 for (auto &w : widths)
1378 if (w > max_width)
1379 max_width = w;
1380 for (auto &h : heights)
1381 if (h > max_height)
1382 max_height = h;
1383
1386
1387 std::string mains, prev;
1388 for (auto &json : jsons) {
1389 mains.append(mains.empty() ? "[" : ", ");
1390 if (can_optimize_json && (json == prev)) {
1391 mains.append("'same'");
1392 } else {
1393 mains.append(json);
1394 prev = json;
1395 }
1396 }
1397 mains.append("]");
1398
1399 if (strstr(jsrootsys, "http://") || strstr(jsrootsys, "https://") || strstr(jsrootsys, "file://"))
1400 filecont = std::regex_replace(filecont, std::regex("\\$jsrootsys"), jsrootsys);
1401 else {
1402 static std::string jsroot_include = "<script id=\"jsroot\" src=\"$jsrootsys/build/jsroot.js\"></script>";
1403 auto p = filecont.find(jsroot_include);
1404 if (p != std::string::npos) {
1405 auto jsroot_build = THttpServer::ReadFileContent(std::string(jsrootsys) + "/build/jsroot.js");
1406 if (!jsroot_build.empty()) {
1407 // insert actual jsroot file location
1408 jsroot_build = std::regex_replace(jsroot_build, std::regex("'\\$jsrootsys'"), std::string("'file://") + jsrootsys + "/'");
1409 filecont.erase(p, jsroot_include.length());
1410 filecont.insert(p, "<script id=\"jsroot\">" + jsroot_build + "</script>");
1411 }
1412 }
1413
1414 filecont = std::regex_replace(filecont, std::regex("\\$jsrootsys"), "file://"s + jsrootsys);
1415 }
1416
1417 filecont = std::regex_replace(filecont, std::regex("\\$page_margin"), std::to_string(page_margin) + "px");
1418 filecont = std::regex_replace(filecont, std::regex("\\$page_width"), std::to_string(max_width + 2*page_margin) + "px");
1419 filecont = std::regex_replace(filecont, std::regex("\\$page_height"), std::to_string(max_height + 2*page_margin) + "px");
1420
1421 filecont = std::regex_replace(filecont, std::regex("\\$draw_kind"), jsonkind.Data());
1422 filecont = std::regex_replace(filecont, std::regex("\\$draw_widths"), jsonw.Data());
1423 filecont = std::regex_replace(filecont, std::regex("\\$draw_heights"), jsonh.Data());
1424 filecont = std::regex_replace(filecont, std::regex("\\$draw_objects"), mains);
1425
1427
1429 dump_name = "canvasdump";
1431 if (!df) {
1432 R__LOG_ERROR(WebGUILog()) << "Fail to create temporary file for dump-dom";
1433 return false;
1434 }
1435 fputs("placeholder", df);
1436 fclose(df);
1437 }
1438
1439try_again:
1440
1442 args.SetUrl(""s);
1444
1445 html_name.Clear();
1446
1447 R__LOG_DEBUG(0, WebGUILog()) << "Using file content_len " << filecont.length() << " to produce batch images ";
1448
1449 } else {
1450 html_name = "canvasbody";
1452 if (!hf) {
1453 R__LOG_ERROR(WebGUILog()) << "Fail to create temporary file for batch job";
1454 return false;
1455 }
1456 fputs(filecont.c_str(), hf);
1457 fclose(hf);
1458
1459 args.SetUrl("file://"s + gSystem->UnixPathName(html_name.Data()));
1460 args.SetPageContent(""s);
1461
1462 R__LOG_DEBUG(0, WebGUILog()) << "Using " << html_name << " content_len " << filecont.length() << " to produce batch images " << fdebug;
1463 }
1464
1466
1467 args.SetStandalone(true);
1468 args.SetHeadless(true);
1469 args.SetBatchMode(true);
1470 args.SetSize(widths[0], heights[0]);
1471
1472 if (use_browser_draw) {
1473
1474 tgtfilename = fnames[0].c_str();
1477
1479
1480 if (fmts[0] == "s.pdf")
1481 args.SetExtraArgs("--print-to-pdf-no-header --print-to-pdf="s + gSystem->UnixPathName(tgtfilename.Data()));
1482 else if (isFirefox) {
1483 args.SetExtraArgs("--screenshot"); // firefox does not let specify output image file
1484 wait_file_name = "screenshot.png";
1485 } else
1486 args.SetExtraArgs("--screenshot="s + gSystem->UnixPathName(tgtfilename.Data()));
1487
1488 // remove target image file - we use it as detection when chrome is ready
1489 gSystem->Unlink(tgtfilename.Data());
1490
1491 } else if (isFirefox) {
1492 // firefox will use window.dump to output produced result
1493 args.SetRedirectOutput(dump_name.Data());
1494 gSystem->Unlink(dump_name.Data());
1495 } else if (isChromeBased) {
1496 // chrome should have --dump-dom args configures
1497 args.SetRedirectOutput(dump_name.Data());
1498 gSystem->Unlink(dump_name.Data());
1499 }
1500
1501 auto handle = RWebDisplayHandle::Display(args);
1502
1503 // ensure file is created by browser draw
1504 if (use_browser_draw && handle) {
1505 Int_t batch_timeout = gEnv->GetValue("WebGui.BatchTimeout", 30) * 10;
1506 while (gSystem->AccessPathName(wait_file_name.Data()) && (--batch_timeout > 0)) {
1508 gSystem->Sleep(100);
1509 }
1510 }
1511
1512 // delete temporary HTML file
1513 if (html_name.Length() > 0) {
1514 if (gEnv->GetValue("WebGui.PreserveBatchFiles", -1) > 0)
1515 ::Info("ProduceImages", "Preserve batch file %s", html_name.Data());
1516 else
1517 gSystem->Unlink(html_name.Data());
1518 }
1519
1520 if (!handle) {
1521 R__LOG_DEBUG(0, WebGUILog()) << "Cannot start " << args.GetBrowserName() << " to produce image " << fdebug;
1522 return false;
1523 }
1524
1525 if (use_browser_draw) {
1526
1527 if (gSystem->AccessPathName(wait_file_name.Data())) {
1528 R__LOG_ERROR(WebGUILog()) << "Fail to produce image " << fdebug;
1529 return false;
1530 }
1531
1532 if (fmts[0] == "s.pdf")
1533 ::Info("ProduceImages", "PDF file %s with %d pages has been created", fnames[0].c_str(), (int) jsons.size());
1534 else {
1535 if (isFirefox)
1536 gSystem->Rename("screenshot.png", fnames[0].c_str());
1537 ::Info("ProduceImages", "PNG file %s with %d pages has been created", fnames[0].c_str(), (int) jsons.size());
1538 }
1539 } else {
1540 auto dumpcont = handle->GetContent();
1541
1542 if ((dumpcont.length() > 20) && (dumpcont.length() < 60) && (use_home_dir < 2) && isChrome) {
1543 // chrome creates dummy html file with mostly no content
1544 // problem running chrome from /tmp directory, lets try work from home directory
1545 R__LOG_INFO(WebGUILog()) << "Use home directory for running chrome in batch, set TMPDIR for preferable temp directory";
1547 goto try_again;
1548 }
1549
1550 if (dumpcont.length() < 100) {
1551 R__LOG_ERROR(WebGUILog()) << "Fail to dump HTML code into " << (dump_name.IsNull() ? "CEF" : dump_name.Data());
1552 return false;
1553 }
1554
1555 std::string::size_type p = 0;
1556
1557 for (unsigned n = 0; n < fmts.size(); n++) {
1558 if (fmts[n].empty())
1559 continue;
1560 if (fmts[n] == "svg") {
1561 auto p1 = dumpcont.find("<div><svg", p);
1562 auto p2 = dumpcont.find("</svg></div>", p1 + 8);
1563 p = p2 + 12;
1564 std::ofstream ofs(fnames[n]);
1565 if ((p1 != std::string::npos) && (p2 != std::string::npos) && (p1 < p2)) {
1566 if (p2 - p1 > 10) {
1567 ofs << dumpcont.substr(p1 + 5, p2 - p1 + 1);
1568 ::Info("ProduceImages", "Image file %s size %d bytes has been created", fnames[n].c_str(), (int) (p2 - p1 + 1));
1569 } else {
1570 ::Error("ProduceImages", "Failure producing %s", fnames[n].c_str());
1571 }
1572 }
1573 } else {
1574 auto p0 = dumpcont.find("<img src=\"", p);
1575 auto p1 = dumpcont.find(";base64,", p0 + 8);
1576 auto p2 = dumpcont.find("\">", p1 + 8);
1577 p = p2 + 2;
1578
1579 if ((p0 != std::string::npos) && (p1 != std::string::npos) && (p2 != std::string::npos) && (p1 < p2)) {
1580 auto base64 = dumpcont.substr(p1+8, p2-p1-8);
1581 if ((base64 == "failure") || (base64.length() < 10)) {
1582 ::Error("ProduceImages", "Failure producing %s", fnames[n].c_str());
1583 } else {
1584 auto binary = TBase64::Decode(base64.c_str());
1585 std::ofstream ofs(fnames[n], std::ios::binary);
1586 ofs.write(binary.Data(), binary.Length());
1587 ::Info("ProduceImages", "Image file %s size %d bytes has been created", fnames[n].c_str(), (int) binary.Length());
1588 }
1589 } else {
1590 ::Error("ProduceImages", "Failure producing %s", fnames[n].c_str());
1591 return false;
1592 }
1593 }
1594 }
1595 }
1596
1597 R__LOG_DEBUG(0, WebGUILog()) << "Create " << (fnames.size() > 1 ? "files " : "file ") << fdebug;
1598
1599 return true;
1600}
1601
nlohmann::json json
#define R__LOG_ERROR(...)
Definition RLogger.hxx:356
#define R__LOG_DEBUG(DEBUGLEVEL,...)
Definition RLogger.hxx:359
#define R__LOG_INFO(...)
Definition RLogger.hxx:358
#define f(i)
Definition RSha256.hxx:104
#define c(i)
Definition RSha256.hxx:101
#define h(i)
Definition RSha256.hxx:106
static void DummyTimeOutHandler(int)
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
R__EXTERN TEnv * gEnv
Definition TEnv.h:126
void Info(const char *location, const char *msgfmt,...)
Use this function for informational messages.
Definition TError.cxx:241
void Error(const char *location, const char *msgfmt,...)
Use this function in case an error occurred.
Definition TError.cxx:208
winID h TVirtualViewer3D TVirtualGLPainter p
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h length
Option_t Option_t width
Option_t Option_t style
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t height
char name[80]
Definition TGX11.cxx:148
@ kFileExists
Definition TSystem.h:52
@ kExecutePermission
Definition TSystem.h:53
R__EXTERN TSystem * gSystem
Definition TSystem.h:582
const_iterator begin() const
const_iterator end() const
Specialized handle to hold information about running browser process Used to correctly cleanup all pr...
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
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.
@ kSafari
Safari browser.
@ 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 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.
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.
ChromeCreator(bool is_edge=false)
Constructor.
void ProcessGeometry(std::string &, const RWebDisplayArgs &) override
Replace $geometry placeholder with geometry settings Also RWebDisplayArgs::GetExtraArgs() are appende...
std::string MakeProfile(std::string &exec, bool) override
Handle profile argument.
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 IsActive() const override
Returns true if it can be used.
Handle of created web-based display Depending from type of web display, holds handle of started brows...
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 formats.
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
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.
Definition TBase64.cxx:130
static TString ToJSON(const T *obj, Int_t compact=0, const char *member_name=nullptr)
Definition TBufferJSON.h:77
@ kNoSpaces
no new lines plus remove all spaces around "," and ":" symbols
Definition TBufferJSON.h:39
virtual Int_t GetValue(const char *name, Int_t dflt) const
Returns the integer value for a resource.
Definition TEnv.cxx:511
static char * ReadFileContent(const char *filename, Int_t &len)
Reads content of file from the disk.
virtual Bool_t InheritsFrom(const char *classname) const
Returns kTRUE if object inherits from class "classname".
Definition TObject.cxx:548
static const TString & GetEtcDir()
Get the sysconfig directory in the installation. Static utility function.
Definition TROOT.cxx:3396
static const TString & GetDataDir()
Get the data directory in the installation. Static utility function.
Definition TROOT.cxx:3406
Random number generator class based on M.
Definition TRandom3.h:27
Basic string class.
Definition TString.h:138
TObjArray * Tokenize(const TString &delim) const
This function is used to isolate sequential tokens in a TString.
Definition TString.cxx:2344
static TString Format(const char *fmt,...)
Static method which formats a string using a printf style format descriptor and return a TString.
Definition TString.cxx:2459
virtual FILE * TempFileName(TString &base, const char *dir=nullptr, const char *suffix=nullptr)
Create a secure temporary file by appending a unique 6 letter string to base.
Definition TSystem.cxx:1514
virtual Bool_t ExpandPathName(TString &path)
Expand a pathname getting rid of special shell characters like ~.
Definition TSystem.cxx:1289
virtual const char * Getenv(const char *env)
Get environment variable.
Definition TSystem.cxx:1680
virtual int mkdir(const char *name, Bool_t recursive=kFALSE)
Make a file system directory.
Definition TSystem.cxx:920
virtual Int_t Exec(const char *shellcmd)
Execute a command.
Definition TSystem.cxx:655
virtual int Load(const char *module, const char *entry="", Bool_t system=kFALSE)
Load a shared library.
Definition TSystem.cxx:1872
virtual const char * PrependPathName(const char *dir, TString &name)
Concatenate a directory and a file name.
Definition TSystem.cxx:1096
virtual Bool_t AccessPathName(const char *path, EAccessMode mode=kFileExists)
Returns FALSE if one can access a file using the specified access mode.
Definition TSystem.cxx:1311
virtual std::string GetHomeDirectory(const char *userName=nullptr) const
Return the user's home directory.
Definition TSystem.cxx:909
virtual const char * UnixPathName(const char *unixpathname)
Convert from a local pathname to a Unix pathname.
Definition TSystem.cxx:1077
virtual int Rename(const char *from, const char *to)
Rename a file.
Definition TSystem.cxx:1365
virtual TString GetFromPipe(const char *command, Int_t *ret=nullptr, Bool_t redirectStderr=kFALSE)
Execute command and return output in TString.
Definition TSystem.cxx:688
virtual Bool_t IsAbsoluteFileName(const char *dir)
Return true if dir is an absolute pathname.
Definition TSystem.cxx:965
virtual void Sleep(UInt_t milliSec)
Sleep milliSec milli seconds.
Definition TSystem.cxx:439
virtual const char * WorkingDirectory()
Return working directory.
Definition TSystem.cxx:885
virtual Bool_t ProcessEvents()
Process pending events (GUI, timers, sockets).
Definition TSystem.cxx:418
virtual int Unlink(const char *name)
Unlink, i.e.
Definition TSystem.cxx:1396
virtual const char * TempDirectory() const
Return a user configured or systemwide directory to create temporary files in.
Definition TSystem.cxx:1497
const Int_t n
Definition legend1.C:16
TH1F * h1
Definition legend1.C:5
bool EndsWith(std::string_view string, std::string_view suffix)
ROOT::RLogChannel & WebGUILog()
Log channel for WebGUI diagnostics.
TCanvas * slash()
Definition slash.C:1
TMarker m
Definition textangle.C:8