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// Warning: This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback is welcome!
4
5/*************************************************************************
6 * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers. *
7 * All rights reserved. *
8 * *
9 * For the licensing terms see $ROOTSYS/LICENSE. *
10 * For the list of contributors see $ROOTSYS/README/CREDITS. *
11 *************************************************************************/
12
14
15#include <ROOT/RLogger.hxx>
16
17#include "RConfigure.h"
18#include "TSystem.h"
19#include "TRandom3.h"
20#include "TString.h"
21#include "TObjArray.h"
22#include "THttpServer.h"
23#include "TEnv.h"
24#include "TError.h"
25#include "TROOT.h"
26#include "TBase64.h"
27#include "TBufferJSON.h"
29
30#include <fstream>
31#include <iostream>
32#include <filesystem>
33#include <memory>
34#include <regex>
35
36#ifdef _MSC_VER
37#include <process.h>
38#else
39#include <unistd.h>
40#include <stdlib.h>
41#include <signal.h>
42#include <spawn.h>
43#ifdef R__MACOSX
44#include <sys/wait.h>
45#include <crt_externs.h>
46#elif defined(__FreeBSD__)
47#include <sys/wait.h>
48#include <dlfcn.h>
49#else
50#include <wait.h>
51#endif
52#endif
53
54using namespace ROOT;
55using namespace std::string_literals;
56
57/** \class ROOT::RWebDisplayHandle
58\ingroup webdisplay
59
60Handle of created web-based display
61Depending from type of web display, holds handle of started browser process or other display-specific information
62to correctly stop and cleanup display.
63*/
64
65
66//////////////////////////////////////////////////////////////////////////////////////////////////
67/// Static holder of registered creators of web displays
68
69std::map<std::string, std::unique_ptr<RWebDisplayHandle::Creator>> &RWebDisplayHandle::GetMap()
70{
71 static std::map<std::string, std::unique_ptr<RWebDisplayHandle::Creator>> sMap;
72 return sMap;
73}
74
75//////////////////////////////////////////////////////////////////////////////////////////////////
76/// Search for specific browser creator
77/// If not found, try to add one
78/// \param name - creator name like ChromeCreator
79/// \param libname - shared library name where creator could be provided
80
81std::unique_ptr<RWebDisplayHandle::Creator> &RWebDisplayHandle::FindCreator(const std::string &name, const std::string &libname)
82{
83 auto &m = GetMap();
84 auto search = m.find(name);
85 if (search == m.end()) {
86
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()) {
96 gSystem->Load(libname.c_str());
97 }
98
99 search = m.find(name); // try again
100 }
101
102 if (search != m.end())
103 return search->second;
104
105 static std::unique_ptr<RWebDisplayHandle::Creator> dummy;
106 return dummy;
107}
108
109namespace ROOT {
110
111//////////////////////////////////////////////////////////////////////////////////////////////////
112/// Specialized handle to hold information about running browser process
113/// Used to correctly cleanup all processes and temporary directories
114
116
117#ifdef _MSC_VER
118 typedef int browser_process_id;
119#else
120 typedef pid_t browser_process_id;
121#endif
122 std::string fTmpDir; ///< temporary directory to delete at the end
123 std::string fTmpFile; ///< temporary file to remove
124 bool fHasPid{false};
126
127public:
128 RWebBrowserHandle(const std::string &url, const std::string &tmpdir, const std::string &tmpfile,
129 const std::string &dump)
131 {
132 SetContent(dump);
133 }
134
135 RWebBrowserHandle(const std::string &url, const std::string &tmpdir, const std::string &tmpfile,
138 {
139 }
140
142 {
143#ifdef _MSC_VER
144 if (fHasPid)
145 gSystem->Exec(("taskkill /F /PID " + std::to_string(fPid) + " >NUL 2>NUL").c_str());
146 std::string rmdir = "rmdir /S /Q ";
147#else
148 if (fHasPid)
149 kill(fPid, SIGKILL);
150 std::string rmdir = "rm -rf ";
151#endif
152 if (!fTmpDir.empty())
153 gSystem->Exec((rmdir + fTmpDir).c_str());
155 }
156
157 void RemoveStartupFiles() override
158 {
159#ifdef _MSC_VER
160 std::string rmfile = "del /F ";
161#else
162 std::string rmfile = "rm -f ";
163#endif
164 if (!fTmpFile.empty()) {
165 gSystem->Exec((rmfile + fTmpFile).c_str());
166 fTmpFile.clear();
167 }
168 }
169};
170
171} // namespace ROOT
172
173//////////////////////////////////////////////////////////////////////////////////////////////////
174/// Class to handle starting of web-browsers like Chrome or Firefox
175
177{
178 if (custom) return;
179
180 if (!exec.empty()) {
181 if (exec.find("$url") == std::string::npos) {
182 fProg = exec;
183#ifdef _MSC_VER
184 fExec = exec + " $url";
185#else
186 fExec = exec + " $url &";
187#endif
188 } else {
189 fExec = exec;
190 auto pos = exec.find(" ");
191 if (pos != std::string::npos)
192 fProg = exec.substr(0, pos);
193 }
194 } else if (gSystem->InheritsFrom("TMacOSXSystem")) {
195 fExec = "open \'$url\'";
196 } else if (gSystem->InheritsFrom("TWinNTSystem")) {
197 fExec = "start $url";
198 } else {
199 fExec = "xdg-open \'$url\' &";
200 }
201}
202
203//////////////////////////////////////////////////////////////////////////////////////////////////
204/// Check if browser executable exists and can be used
205
207{
208 if (nexttry.empty() || !fProg.empty())
209 return;
210
212#ifdef R__MACOSX
213 fProg = std::regex_replace(nexttry, std::regex("%20"), " ");
214#else
215 fProg = nexttry;
216#endif
217 return;
218 }
219
220 if (!check_std_paths)
221 return;
222
223#ifdef _MSC_VER
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)");
229
230 if (!ProgramFiles.empty())
231 TestProg(ProgramFiles + nexttry, false);
232 if (!ProgramFilesx86.empty())
233 TestProg(ProgramFilesx86 + nexttry, false);
234#endif
235}
236
237//////////////////////////////////////////////////////////////////////////////////////////////////
238/// Create temporary file for web display
239/// Normally gSystem->TempFileName() method used to create file in default temporary directory
240/// For snap chromium use of default temp directory is not always possible therefore one switches to home directory
241/// But one checks if default temp directory modified and already points to /home folder
242
244{
245 std::string dirname;
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))
250 use_home_dir = 0;
251 else if (!tmp_dir || (strncmp(tmp_dir, "/tmp", 4) == 0))
252 use_home_dir = 2;
253 }
254
255 if (use_home_dir > 1)
257 }
258 return gSystem->TempFileName(name, use_home_dir > 1 ? dirname.c_str() : nullptr, suffix);
259}
260
261static void DummyTimeOutHandler(int /* Sig */) {}
262
263
264//////////////////////////////////////////////////////////////////////////////////////////////////
265/// Display given URL in web browser
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 rmdir = MakeProfile(exec, args.IsBatchMode() || args.IsHeadless());
300
301 std::string tmpfile;
302
303 // these are secret parameters, hide them in temp file
304 if (((url.find("token=") != std::string::npos) || (url.find("key=") != std::string::npos)) && !args.IsBatchMode() && !args.IsHeadless()) {
305 TString filebase = "root_start_";
306
307 auto f = TemporaryFile(filebase, IsSnapChromium() ? 1 : 0, ".html");
308
309 bool ferr = false;
310
311 if (!f) {
312 ferr = true;
313 } else {
314 std::string content = std::regex_replace(
315 "<!DOCTYPE html>\n"
316 "<html lang=\"en\">\n"
317 "<head>\n"
318 " <meta charset=\"utf-8\">\n"
319 " <meta http-equiv=\"refresh\" content=\"0;url=$url\"/>\n"
320 " <title>Opening ROOT widget</title>\n"
321 "</head>\n"
322 "<body>\n"
323 "<p>\n"
324 " This page should redirect you to a ROOT widget. If it doesn't,\n"
325 " <a href=\"$url\">click here to go to ROOT</a>.\n"
326 "</p>\n"
327 "</body>\n"
328 "</html>\n", std::regex("\\$url"), url);
329
330 if (fwrite(content.c_str(), 1, content.length(), f) != content.length())
331 ferr = true;
332
333 if (fclose(f) != 0)
334 ferr = true;
335
336 tmpfile = filebase.Data();
337
338 url = "file://"s + tmpfile;
339 }
340
341 if (ferr) {
342 if (!tmpfile.empty())
343 gSystem->Unlink(tmpfile.c_str());
344 R__LOG_ERROR(WebGUILog()) << "Fail to create temporary HTML file to startup widget";
345 return nullptr;
346 }
347 }
348
349 exec = std::regex_replace(exec, std::regex("\\$rootetcdir"), TROOT::GetEtcDir().Data());
350 exec = std::regex_replace(exec, std::regex("\\$url"), url);
351 exec = std::regex_replace(exec, std::regex("\\$width"), swidth);
352 exec = std::regex_replace(exec, std::regex("\\$height"), sheight);
353 exec = std::regex_replace(exec, std::regex("\\$posx"), sposx);
354 exec = std::regex_replace(exec, std::regex("\\$posy"), sposy);
355
356 if (exec.compare(0,5,"fork:") == 0) {
357 if (fProg.empty()) {
358 if (!tmpfile.empty())
359 gSystem->Unlink(tmpfile.c_str());
360 R__LOG_ERROR(WebGUILog()) << "Fork instruction without executable";
361 return nullptr;
362 }
363
364 exec.erase(0, 5);
365
366 // in case of redirection process will wait until output is produced
367 std::string redirect = args.GetRedirectOutput();
368
369#ifndef _MSC_VER
370
371 std::unique_ptr<TObjArray> fargs(TString(exec.c_str()).Tokenize(" "));
372 if (!fargs || (fargs->GetLast()<=0)) {
373 if (!tmpfile.empty())
374 gSystem->Unlink(tmpfile.c_str());
375 R__LOG_ERROR(WebGUILog()) << "Fork instruction is empty";
376 return nullptr;
377 }
378
379 std::vector<char *> argv;
380 argv.push_back((char *) fProg.c_str());
381 for (Int_t n = 0; n <= fargs->GetLast(); ++n)
382 argv.push_back((char *)fargs->At(n)->GetName());
383 argv.push_back(nullptr);
384
385 R__LOG_DEBUG(0, WebGUILog()) << "Show web window in browser with posix_spawn:\n" << fProg << " " << exec;
386
389 if (redirect.empty())
391 else
394
395#ifdef R__MACOSX
396 char **envp = *_NSGetEnviron();
397#elif defined (__FreeBSD__)
398 //this is needed because the FreeBSD linker does not like to resolve these special symbols
399 //in shared libs with -Wl,--no-undefined
400 char** envp = (char**)dlsym(RTLD_DEFAULT, "environ");
401#else
402 char **envp = environ;
403#endif
404
405 pid_t pid;
406 int status = posix_spawn(&pid, argv[0], &action, nullptr, argv.data(), envp);
407
409
410 if (status != 0) {
411 if (!tmpfile.empty())
412 gSystem->Unlink(tmpfile.c_str());
413 R__LOG_ERROR(WebGUILog()) << "Fail to launch " << argv[0];
414 return nullptr;
415 }
416
417 if (!redirect.empty()) {
418 Int_t batch_timeout = gEnv->GetValue("WebGui.BatchTimeout", 30);
419 struct sigaction Act, Old;
420 int elapsed_time = 0;
421
422 if (batch_timeout) {
423 memset(&Act, 0, sizeof(Act));
424 Act.sa_handler = DummyTimeOutHandler;
425 sigemptyset(&Act.sa_mask);
430 }
431
432 int job_done = 0;
433 std::string dump_content;
434
435 while (!job_done) {
436
437 // wait until output is produced
438 int wait_status = 0;
439
441
442 // try read dump anyway
444
445 if (dump_content.find("<div>###batch###job###done###</div>") != std::string::npos)
446 job_done = 1;
447
448 if (wait_res == -1) {
449 // failure when finish process
451 if ((errno == EINTR) && (alarm_timeout > 0) && !job_done) {
452 if (alarm_timeout > 2) alarm_timeout = 2;
455 } else {
456 // end of timeout - do not try to wait any longer
457 job_done = 1;
458 }
459 } else if (!WIFEXITED(wait_status) && !WIFSIGNALED(wait_status)) {
460 // abnormal end of browser process
461 job_done = 1;
462 } else {
463 // this is normal finish, no need for process kill
464 job_done = 2;
465 }
466 }
467
468 if (job_done != 2) {
469 // kill browser process when no normal end was detected
470 kill(pid, SIGKILL);
471 }
472
473 if (batch_timeout) {
474 alarm(0); // disable alarm
475 sigaction(SIGALRM, &Old, nullptr);
476 }
477
478 if (gEnv->GetValue("WebGui.PreserveBatchFiles", -1) > 0)
479 ::Info("RWebDisplayHandle::Display", "Preserve dump file %s", redirect.c_str());
480 else
481 gSystem->Unlink(redirect.c_str());
482
483 return std::make_unique<RWebBrowserHandle>(url, rmdir, tmpfile, dump_content);
484 }
485
486 // add processid and rm dir
487
488 return std::make_unique<RWebBrowserHandle>(url, rmdir, tmpfile, pid);
489
490#else
491
492 if (fProg.empty()) {
493 if (!tmpfile.empty())
494 gSystem->Unlink(tmpfile.c_str());
495 R__LOG_ERROR(WebGUILog()) << "No Web browser found";
496 return nullptr;
497 }
498
499 // use UnixPathName to simplify handling of backslashes
500 exec = "wmic process call create '"s + gSystem->UnixPathName(fProg.c_str()) + " " + exec + "' | find \"ProcessId\" "s;
501 std::string process_id = gSystem->GetFromPipe(exec.c_str()).Data();
502 std::stringstream ss(process_id);
503 std::string tmp;
504 char c;
505 int pid = 0;
506 ss >> tmp >> c >> pid;
507
508 if (pid <= 0) {
509 if (!tmpfile.empty())
510 gSystem->Unlink(tmpfile.c_str());
511 R__LOG_ERROR(WebGUILog()) << "Fail to launch " << fProg;
512 return nullptr;
513 }
514
515 // add processid and rm dir
516 return std::make_unique<RWebBrowserHandle>(url, rmdir, tmpfile, pid);
517#endif
518 }
519
520#ifdef _MSC_VER
521
522 if (exec.rfind("&") == exec.length() - 1) {
523
524 // if last symbol is &, use _spawn to detach execution
525 exec.resize(exec.length() - 1);
526
527 std::vector<char *> argv;
528 std::string firstarg = fProg;
529 auto slashpos = firstarg.find_last_of("/\\");
530 if (slashpos != std::string::npos)
531 firstarg.erase(0, slashpos + 1);
532 argv.push_back((char *)firstarg.c_str());
533
534 std::unique_ptr<TObjArray> fargs(TString(exec.c_str()).Tokenize(" "));
535 for (Int_t n = 1; n <= fargs->GetLast(); ++n)
536 argv.push_back((char *)fargs->At(n)->GetName());
537 argv.push_back(nullptr);
538
539 R__LOG_DEBUG(0, WebGUILog()) << "Showing web window in " << fProg << " with:\n" << exec;
540
541 _spawnv(_P_NOWAIT, gSystem->UnixPathName(fProg.c_str()), argv.data());
542
543 return std::make_unique<RWebBrowserHandle>(url, rmdir, tmpfile, ""s);
544 }
545
546 std::string prog = "\""s + gSystem->UnixPathName(fProg.c_str()) + "\""s;
547
548#else
549
550#ifdef R__MACOSX
551 std::string prog = std::regex_replace(fProg, std::regex(" "), "\\ ");
552#else
553 std::string prog = fProg;
554#endif
555
556#endif
557
558 exec = std::regex_replace(exec, std::regex("\\$prog"), prog);
559
560 std::string redirect = args.GetRedirectOutput(), dump_content;
561
562 if (!redirect.empty()) {
563 if (exec.find("$dumpfile") != std::string::npos) {
564 exec = std::regex_replace(exec, std::regex("\\$dumpfile"), redirect);
565 } else {
566 auto p = exec.length();
567 if (exec.rfind("&") == p-1) --p;
568 exec.insert(p, " >"s + redirect + " "s);
569 }
570 }
571
572 R__LOG_DEBUG(0, WebGUILog()) << "Showing web window in browser with:\n" << exec;
573
574 gSystem->Exec(exec.c_str());
575
576 // read content of redirected output
577 if (!redirect.empty()) {
579
580 if (gEnv->GetValue("WebGui.PreserveBatchFiles", -1) > 0)
581 ::Info("RWebDisplayHandle::Display", "Preserve dump file %s", redirect.c_str());
582 else
583 gSystem->Unlink(redirect.c_str());
584 }
585
586 return std::make_unique<RWebBrowserHandle>(url, rmdir, tmpfile, dump_content);
587}
588
589//////////////////////////////////////////////////////////////////////////////////////////////////
590/// Constructor
591
593{
594 fExec = gEnv->GetValue("WebGui.SafariInteractive", "open -a Safari $url");
595}
596
597//////////////////////////////////////////////////////////////////////////////////////////////////
598/// Returns true if it can be used
599
601{
602#ifdef R__MACOSX
603 return true;
604#else
605 return false;
606#endif
607}
608
609//////////////////////////////////////////////////////////////////////////////////////////////////
610/// Constructor
611
613{
614 fEdge = _edge;
615
616 fEnvPrefix = fEdge ? "WebGui.Edge" : "WebGui.Chrome";
617
618 TestProg(gEnv->GetValue(fEnvPrefix.c_str(), ""));
619
620 if (!fProg.empty() && !fEdge)
621 fChromeVersion = gEnv->GetValue("WebGui.ChromeVersion", -1);
622
623#ifdef _MSC_VER
624 if (fEdge)
625 TestProg("\\Microsoft\\Edge\\Application\\msedge.exe", true);
626 else
627 TestProg("\\Google\\Chrome\\Application\\chrome.exe", true);
628#endif
629#ifdef R__MACOSX
630 TestProg("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome");
631#endif
632#ifdef R__LINUX
633 TestProg("/snap/bin/chromium"); // test snap before to detect it properly
634 TestProg("/usr/bin/chromium");
635 TestProg("/usr/bin/chromium-browser");
636 TestProg("/usr/bin/chrome-browser");
637 TestProg("/usr/bin/google-chrome-stable");
638 TestProg("/usr/bin/google-chrome");
639#endif
640
641// --no-sandbox is required to run chrome with super-user, but only in headless mode
642// --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
643
644#ifdef _MSC_VER
645 // here --headless=old was used to let normally end of Edge process when --dump-dom is used
646 // while on Windows chrome and edge version not tested, just suppose that newest chrome is used
647 fBatchExec = gEnv->GetValue((fEnvPrefix + "Batch").c_str(), "$prog --headless --no-sandbox $geometry --dump-dom $url");
648 // in interactive headless mode fork used to let stop browser via process id
649 fHeadlessExec = gEnv->GetValue((fEnvPrefix + "Headless").c_str(), "fork:--headless --no-sandbox --disable-gpu $geometry \"$url\"");
650 fExec = gEnv->GetValue((fEnvPrefix + "Interactive").c_str(), "$prog $geometry --new-window --app=$url &"); // & in windows mean usage of spawn
651#else
652#ifdef R__MACOSX
653 bool use_normal = true; // mac does not like new flag
654#else
655 bool use_normal = (fChromeVersion < 119) || (fChromeVersion > 131);
656#endif
657 if (use_normal) {
658 // old or newest browser with standard headless mode
659 fBatchExec = gEnv->GetValue((fEnvPrefix + "Batch").c_str(), "fork:--headless --no-sandbox --disable-extensions --disable-audio-output $geometry --dump-dom $url");
660 fHeadlessExec = gEnv->GetValue((fEnvPrefix + "Headless").c_str(), "fork:--headless --no-sandbox --disable-extensions --disable-audio-output $geometry $url");
661 } else {
662 // newer version with headless=new mode
663 fBatchExec = gEnv->GetValue((fEnvPrefix + "Batch").c_str(), "fork:--headless=new --no-sandbox --disable-extensions --disable-audio-output $geometry --dump-dom $url");
664 fHeadlessExec = gEnv->GetValue((fEnvPrefix + "Headless").c_str(), "fork:--headless=new --no-sandbox --disable-extensions --disable-audio-output $geometry $url");
665 }
666 fExec = gEnv->GetValue((fEnvPrefix + "Interactive").c_str(), "$prog $geometry --new-window --app=\'$url\' >/dev/null 2>/dev/null &");
667#endif
668}
669
670
671//////////////////////////////////////////////////////////////////////////////////////////////////
672/// Replace $geometry placeholder with geometry settings
673/// Also RWebDisplayArgs::GetExtraArgs() are appended
674
676{
677 std::string geometry;
678 if ((args.GetWidth() > 0) && (args.GetHeight() > 0))
679 GetWidth())
680 + (args.IsHeadless() ? "x"s : ","s)
681 + std::to_string(args.GetHeight());
682
683 if (((args.GetX() >= 0) || (args.GetY() >= 0)) && !args.IsHeadless()) {
684 if (!geometry.append(" ");
685 GetX() : 0) + ","s +
686 std::to_string(args.GetY() >= 0 ? args.GetY() : 0));
687 }
688
689 if (!args.GetExtraArgs().empty()) {
690 if (!geometry.append(" ");
691 GetExtraArgs());
692 }
693
694 exec = std::regex_replace(exec, std::regex("\\$geometry"), geometry);
695}
696
697
698//////////////////////////////////////////////////////////////////////////////////////////////////
699/// Handle profile argument
700
701std::string RWebDisplayHandle::ChromeCreator::MakeProfile(std::string &exec, bool)
702{
703 std::string rmdir, profile_arg;
704
705 if (exec.find("$profile") == std::string::npos)
706 return rmdir;
707
708 const char *chrome_profile = gEnv->GetValue((fEnvPrefix + "Profile").c_str(), "");
711 } else {
713 rnd.SetSeed(0);
715#ifdef _MSC_VER
716 char slash = '\\';
717#else
718 char slash = '/';
719#endif
720 if (!profile_arg.empty() && (profile_arg[profile_arg.length()-1] != slash))
722 profile_arg += "root_chrome_profile_"s + std::to_string(rnd.Integer(0x100000));
723
724 rmdir = profile_arg;
725 }
726
727 exec = std::regex_replace(exec, std::regex("\\$profile"), profile_arg);
728
729 return rmdir;
730}
731
732
733//////////////////////////////////////////////////////////////////////////////////////////////////
734/// Constructor
735
737{
738 TestProg(gEnv->GetValue("WebGui.Firefox", ""));
739
740#ifdef _MSC_VER
741 TestProg("\\Mozilla Firefox\\firefox.exe", true);
742#endif
743#ifdef R__MACOSX
744 TestProg("/Applications/Firefox.app/Contents/MacOS/firefox");
745#endif
746#ifdef R__LINUX
747 TestProg("/usr/bin/firefox");
748 TestProg("/usr/bin/firefox-bin");
749#endif
750
751#ifdef _MSC_VER
752 // there is a problem when specifying the window size with wmic on windows:
753 // It gives: Invalid format. Hint: <paramlist> = <param> [, <paramlist>].
754 fBatchExec = gEnv->GetValue("WebGui.FirefoxBatch", "$prog -headless -no-remote $profile $url");
755 fHeadlessExec = gEnv->GetValue("WebGui.FirefoxHeadless", "fork:-headless -no-remote $profile \"$url\"");
756 fExec = gEnv->GetValue("WebGui.FirefoxInteractive", "$prog -no-remote $profile $geometry $url &");
757#else
758 fBatchExec = gEnv->GetValue("WebGui.FirefoxBatch", "fork:--headless -no-remote -new-instance $profile $url");
759 fHeadlessExec = gEnv->GetValue("WebGui.FirefoxHeadless", "fork:--headless -no-remote $profile --private-window $url");
760 fExec = gEnv->GetValue("WebGui.FirefoxInteractive", "$rootetcdir/runfirefox.sh __nodump__ $cleanup_profile $prog -no-remote $profile $geometry -url \'$url\' &");
761#endif
762}
763
764//////////////////////////////////////////////////////////////////////////////////////////////////
765/// Process window geometry for Firefox
766
768{
769 std::string geometry;
770 if ((args.GetWidth() > 0) && (args.GetHeight() > 0) && !args.IsHeadless())
771 GetHeight());
772
773 exec = std::regex_replace(exec, std::regex("\\$geometry"), geometry);
774}
775
776//////////////////////////////////////////////////////////////////////////////////////////////////
777/// Create Firefox profile to run independent browser window
778
780{
781 std::string rmdir, profile_arg;
782
783 if (exec.find("$profile") == std::string::npos)
784 return rmdir;
785
786 const char *ff_profile = gEnv->GetValue("WebGui.FirefoxProfile", "");
787 const char *ff_profilepath = gEnv->GetValue("WebGui.FirefoxProfilePath", "");
788 Int_t ff_randomprofile = RWebWindowWSHandler::GetBoolEnv("WebGui.FirefoxRandomProfile", 1);
789 if (ff_profile && *ff_profile) {
790 profile_arg = "-P "s + ff_profile;
791 } else if (ff_profilepath && *ff_profilepath) {
792 profile_arg = "-profile "s + ff_profilepath;
793 } else if (ff_randomprofile > 0) {
795 rnd.SetSeed(0);
796 std::string profile_dir = gSystem->TempDirectory();
797
798#ifdef _MSC_VER
799 char slash = '\\';
800#else
801 char slash = '/';
802#endif
803 if (!profile_dir.empty() && (profile_dir[profile_dir.length()-1] != slash))
805 profile_dir += "root_ff_profile_"s + std::to_string(rnd.Integer(0x100000));
806
807 profile_arg = "-profile "s + profile_dir;
808
809 if (gSystem->mkdir(profile_dir.c_str()) == 0) {
810 rmdir = profile_dir;
811
812 std::ofstream user_js(profile_dir + "/user.js", std::ios::trunc);
813 // workaround for current Firefox, without such settings it fail to close window and terminate it from batch
814 // also disable question about upload of data
815 user_js << "user_pref(\"datareporting.policy.dataSubmissionPolicyAcceptedVersion\", 2);" << std::endl;
816 user_js << "user_pref(\"datareporting.policy.dataSubmissionPolicyNotifiedTime\", \"1635760572813\");" << std::endl;
817
818 // try to ensure that window closes with last tab
819 user_js << "user_pref(\"browser.tabs.closeWindowWithLastTab\", true);" << std::endl;
820 user_js << "user_pref(\"dom.allow_scripts_to_close_windows\", true);" << std::endl;
821 user_js << "user_pref(\"browser.sessionstore.resume_from_crash\", false);" << std::endl;
822
823 if (batch_mode) {
824 // allow to dump messages to std output
825 user_js << "user_pref(\"browser.dom.window.dump.enabled\", true);" << std::endl;
826 } else {
827 // to suppress annoying privacy tab
828 user_js << "user_pref(\"datareporting.policy.firstRunURL\", \"\");" << std::endl;
829 // to use custom userChrome.css files
830 user_js << "user_pref(\"toolkit.legacyUserProfileCustomizations.stylesheets\", true);" << std::endl;
831 // do not put tabs in title
832 user_js << "user_pref(\"browser.tabs.inTitlebar\", 0);" << std::endl;
833
834#ifdef R__LINUX
835 // fix WebGL creation problem on some Linux platforms
836 user_js << "user_pref(\"webgl.out-of-process\", false);" << std::endl;
837#endif
838
839 std::ofstream times_json(profile_dir + "/times.json", std::ios::trunc);
840 times_json << "{" << std::endl;
841 times_json << " \"created\": 1699968480952," << std::endl;
842 times_json << " \"firstUse\": null" << std::endl;
843 times_json << "}" << std::endl;
844 if (gSystem->mkdir((profile_dir + "/chrome").c_str()) == 0) {
845 std::ofstream style(profile_dir + "/chrome/userChrome.css", std::ios::trunc);
846 // do not show tabs
847 style << "#TabsToolbar { visibility: collapse; }" << std::endl;
848 // do not show URL
849 style << "#nav-bar, #urlbar-container, #searchbar { visibility: collapse !important; }" << std::endl;
850 }
851 }
852
853 } else {
854 R__LOG_ERROR(WebGUILog()) << "Cannot create Firefox profile directory " << profile_dir;
855 }
856 }
857
858 exec = std::regex_replace(exec, std::regex("\\$profile"), profile_arg);
859
860 if (exec.find("$cleanup_profile") != std::string::npos) {
861 if (rmdir.empty()) rmdir = "__dummy__";
862 exec = std::regex_replace(exec, std::regex("\\$cleanup_profile"), rmdir);
863 rmdir.clear(); // no need to delete directory - it will be removed by script
864 }
865
866 return rmdir;
867}
868
869///////////////////////////////////////////////////////////////////////////////////////////////////
870/// Check if http server required for display
871/// \param args - defines where and how to display web window
872
874{
877 return false;
878
879 if (!args.IsHeadless() && (args.GetBrowserKind() == RWebDisplayArgs::kOn)) {
880
881#ifdef WITH_QT6WEB
882 auto &qt6 = FindCreator("qt6", "libROOTQt6WebDisplay");
883 if (qt6 && qt6->IsActive())
884 return false;
885#endif
886#ifdef WITH_CEFWEB
887 auto &cef = FindCreator("cef", "libROOTCefDisplay");
888 if (cef && cef->IsActive())
889 return false;
890#endif
891 }
892
893 return true;
894}
895
896
897///////////////////////////////////////////////////////////////////////////////////////////////////
898/// Create web display
899/// \param args - defines where and how to display web window
900/// Returns RWebDisplayHandle, which holds information of running browser application
901/// Can be used fully independent from RWebWindow classes just to show any web page
902
903std::unique_ptr<RWebDisplayHandle> RWebDisplayHandle::Display(const RWebDisplayArgs &args)
904{
905 std::unique_ptr<RWebDisplayHandle> handle;
906
908 return handle;
909
910 auto try_creator = [&](std::unique_ptr<Creator> &creator) {
911 if (!creator || !creator->IsActive())
912 return false;
913 handle = creator->Display(args);
914 return handle ? true : false;
915 };
916
918 (!args.IsHeadless() && (args.GetBrowserKind() == RWebDisplayArgs::kOn)),
919 has_qt6web = false, has_cefweb = false;
920
921#ifdef WITH_QT6WEB
922 has_qt6web = true;
923#endif
924
925#ifdef WITH_CEFWEB
926 has_cefweb = true;
927#endif
928
930 if (try_creator(FindCreator("qt6", "libROOTQt6WebDisplay")))
931 return handle;
932 }
933
935 if (try_creator(FindCreator("cef", "libROOTCefDisplay")))
936 return handle;
937 }
938
939 if (args.IsLocalDisplay()) {
940 R__LOG_ERROR(WebGUILog()) << "Neither Qt5/6 nor CEF libraries were found to provide local display";
941 return handle;
942 }
943
944 bool handleAsNative =
946
948 if (try_creator(FindCreator("chrome", "ChromeCreator")))
949 return handle;
950 }
951
953 if (try_creator(FindCreator("firefox", "FirefoxCreator")))
954 return handle;
955 }
956
957#ifdef _MSC_VER
958 // Edge browser cannot be run headless without registry change, therefore do not try it by default
959 if ((handleAsNative && !args.IsHeadless() && !args.IsBatchMode()) || (args.GetBrowserKind() == RWebDisplayArgs::kEdge)) {
960 if (try_creator(FindCreator("edge", "ChromeCreator")))
961 return handle;
962 }
963#endif
964
967 // R__LOG_ERROR(WebGUILog()) << "Neither Chrome nor Firefox browser cannot be started to provide display";
968 return handle;
969 }
970
972 if (try_creator(FindCreator("safari", "SafariCreator")))
973 return handle;
974 }
975
977 std::unique_ptr<Creator> creator = std::make_unique<BrowserCreator>(false, args.GetCustomExec());
978 try_creator(creator);
979 } else {
980 try_creator(FindCreator("browser", "BrowserCreator"));
981 }
982
983 return handle;
984}
985
986///////////////////////////////////////////////////////////////////////////////////////////////////
987/// Display provided url in configured web browser
988/// \param url - specified URL address like https://root.cern
989/// Browser can specified when starting `root --web=firefox`
990/// Returns true when browser started
991/// It is convenience method, equivalent to:
992/// ~~~
993/// RWebDisplayArgs args;
994/// args.SetUrl(url);
995/// args.SetStandalone(false);
996/// auto handle = RWebDisplayHandle::Display(args);
997/// ~~~
998
999bool RWebDisplayHandle::DisplayUrl(const std::string &url)
1000{
1001 RWebDisplayArgs args;
1002 args.SetUrl(url);
1003 args.SetStandalone(false);
1004
1005 auto handle = Display(args);
1006
1007 return !!handle;
1008}
1009
1010///////////////////////////////////////////////////////////////////////////////////////////////////
1011/// Checks if configured browser can be used for image production
1012
1014{
1018 bool detected = false;
1019
1020 auto &h1 = FindCreator("chrome", "ChromeCreator");
1021 if (h1 && h1->IsActive()) {
1023 detected = true;
1024 }
1025
1026 if (!detected) {
1027 auto &h2 = FindCreator("firefox", "FirefoxCreator");
1028 if (h2 && h2->IsActive()) {
1030 detected = true;
1031 }
1032 }
1033
1034 return detected;
1035 }
1036
1038 auto &h1 = FindCreator("chrome", "ChromeCreator");
1039 return h1 && h1->IsActive();
1040 }
1041
1043 auto &h2 = FindCreator("firefox", "FirefoxCreator");
1044 return h2 && h2->IsActive();
1045 }
1046
1047#ifdef _MSC_VER
1048 if (args.GetBrowserKind() == RWebDisplayArgs::kEdge) {
1049 auto &h3 = FindCreator("edge", "ChromeCreator");
1050 return h3 && h3->IsActive();
1051 }
1052#endif
1053
1054 return true;
1055}
1056
1057///////////////////////////////////////////////////////////////////////////////////////////////////
1058/// Returns true if image production for specified browser kind is supported
1059/// If browser not specified - use currently configured browser or try to test existing web browsers
1060
1062{
1064
1065 return CheckIfCanProduceImages(args);
1066}
1067
1068///////////////////////////////////////////////////////////////////////////////////////////////////
1069/// Detect image format
1070/// There is special handling of ".screenshot.pdf" and ".screenshot.png" extensions
1071/// Creation of such files relies on headless browser functionality and fully supported only by Chrome browser
1072
1073std::string RWebDisplayHandle::GetImageFormat(const std::string &fname)
1074{
1075 std::string _fname = fname;
1076 std::transform(_fname.begin(), _fname.end(), _fname.begin(), ::tolower);
1077 auto EndsWith = [&_fname](const std::string &suffix) {
1078 return (_fname.length() > suffix.length()) ? (0 == _fname.compare(_fname.length() - suffix.length(), suffix.length(), suffix)) : false;
1079 };
1080
1081 if (EndsWith(".screenshot.pdf"))
1082 return "s.pdf"s;
1083 if (EndsWith(".pdf"))
1084 return "pdf"s;
1085 if (EndsWith(".json"))
1086 return "json"s;
1087 if (EndsWith(".svg"))
1088 return "svg"s;
1089 if (EndsWith(".screenshot.png"))
1090 return "s.png"s;
1091 if (EndsWith(".png"))
1092 return "png"s;
1093 if (EndsWith(".jpg") || EndsWith(".jpeg"))
1094 return "jpeg"s;
1095 if (EndsWith(".webp"))
1096 return "webp"s;
1097
1098 return ""s;
1099}
1100
1101
1102///////////////////////////////////////////////////////////////////////////////////////////////////
1103/// Produce image file using JSON data as source
1104/// Invokes JSROOT drawing functionality in headless browser - Google Chrome or Mozilla Firefox
1105
1106bool RWebDisplayHandle::ProduceImage(const std::string &fname, const std::string &json, int width, int height, const char *batch_file)
1107{
1108 return ProduceImages(fname, {json}, {width}, {height}, batch_file);
1109}
1110
1111
1112///////////////////////////////////////////////////////////////////////////////////////////////////
1113/// Produce vector of file names for specified file pattern
1114/// Depending from supported file forma
1115
1116std::vector<std::string> RWebDisplayHandle::ProduceImagesNames(const std::string &fname, unsigned nfiles)
1117{
1118 auto fmt = GetImageFormat(fname);
1119
1120 std::vector<std::string> fnames;
1121
1122 if ((fmt == "s.pdf") || (fmt == "s.png")) {
1123 fnames.emplace_back(fname);
1124 } else {
1125 std::string farg = fname;
1126
1127 bool has_quialifier = farg.find("%") != std::string::npos;
1128
1129 if (!has_quialifier && (nfiles > 1) && (fmt != "pdf")) {
1130 farg.insert(farg.rfind("."), "%d");
1131 has_quialifier = true;
1132 }
1133
1134 for (unsigned n = 0; n < nfiles; n++) {
1135 if(has_quialifier) {
1136 auto expand_name = TString::Format(farg.c_str(), (int) n);
1137 fnames.emplace_back(expand_name.Data());
1138 } else if (n > 0)
1139 fnames.emplace_back(""); // empty name is multiPdf
1140 else
1141 fnames.emplace_back(fname);
1142 }
1143 }
1144
1145 return fnames;
1146}
1147
1148
1149///////////////////////////////////////////////////////////////////////////////////////////////////
1150/// Produce image file(s) using JSON data as source
1151/// Invokes JSROOT drawing functionality in headless browser - Google Chrome or Mozilla Firefox
1152
1153bool 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)
1154{
1156}
1157
1158///////////////////////////////////////////////////////////////////////////////////////////////////
1159/// Produce image file(s) using JSON data as source
1160/// Invokes JSROOT drawing functionality in headless browser - Google Chrome or Mozilla Firefox
1161
1162bool 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)
1163{
1164 if (fnames.empty() || jsons.empty())
1165 return false;
1166
1167 std::vector<std::string> fmts;
1168 for (auto& fname : fnames)
1169 fmts.emplace_back(GetImageFormat(fname));
1170
1171 bool is_any_image = false;
1172
1173 for (unsigned n = 0; (n < fmts.size()) && (n < jsons.size()); n++) {
1174 if (fmts[n] == "json") {
1175 std::ofstream ofs(fnames[n]);
1176 ofs << jsons[n];
1177 fmts[n].clear();
1178 } else if (!fmts[n].empty())
1179 is_any_image = true;
1180 }
1181
1182 if (!is_any_image)
1183 return true;
1184
1185 std::string fdebug;
1186 if (fnames.size() == 1)
1187 fdebug = fnames[0];
1188 else
1190
1191 const char *jsrootsys = gSystem->Getenv("JSROOTSYS");
1193 if (!jsrootsys) {
1194 jsrootsysdflt = TROOT::GetDataDir() + "/js";
1196 R__LOG_ERROR(WebGUILog()) << "Fail to locate JSROOT " << jsrootsysdflt;
1197 return false;
1198 }
1199 jsrootsys = jsrootsysdflt.Data();
1200 }
1201
1202 RWebDisplayArgs args; // set default browser kind, only Chrome/Firefox/Edge or CEF/Qt5/Qt6 can be used here
1203 if (!CheckIfCanProduceImages(args)) {
1204 R__LOG_ERROR(WebGUILog()) << "Fail to detect supported browsers for image production";
1205 return false;
1206 }
1207
1211
1212 std::vector<std::string> draw_kinds;
1213 bool use_browser_draw = false, can_optimize_json = false;
1214 int use_home_dir = 0;
1216
1217 // Some Chrome installation do not allow run html code from files, created in /tmp directory
1218 // When during session such failures happened, force usage of home directory from the beginning
1219 static int chrome_tmp_workaround = 0;
1220
1221 if (isChrome) {
1223 auto &h1 = FindCreator("chrome", "ChromeCreator");
1224 if (h1 && h1->IsActive() && h1->IsSnapChromium() && (use_home_dir == 0))
1225 use_home_dir = 1;
1226 }
1227
1228 if (fmts[0] == "s.png") {
1229 if (!isChromeBased && !isFirefox) {
1230 R__LOG_ERROR(WebGUILog()) << "Direct png image creation supported only by Chrome and Firefox browsers";
1231 return false;
1232 }
1233 use_browser_draw = true;
1234 jsonkind = "1111"; // special mark in canv_batch.htm
1235 } else if (fmts[0] == "s.pdf") {
1236 if (!isChromeBased) {
1237 R__LOG_ERROR(WebGUILog()) << "Direct creation of PDF files supported only by Chrome-based browser";
1238 return false;
1239 }
1240 use_browser_draw = true;
1241 jsonkind = "2222"; // special mark in canv_batch.htm
1242 } else {
1243 draw_kinds = fmts;
1245 can_optimize_json = true;
1246 }
1247
1248 if (!batch_file || !*batch_file)
1249 batch_file = "/js/files/canv_batch.htm";
1250
1253 R__LOG_ERROR(WebGUILog()) << "Fail to find " << origin;
1254 return false;
1255 }
1256
1258 if (filecont.empty()) {
1259 R__LOG_ERROR(WebGUILog()) << "Fail to read content of " << origin;
1260 return false;
1261 }
1262
1263 int max_width = 0, max_height = 0, page_margin = 10;
1264 for (auto &w : widths)
1265 if (w > max_width)
1266 max_width = w;
1267 for (auto &h : heights)
1268 if (h > max_height)
1269 max_height = h;
1270
1273
1274 std::string mains, prev;
1275 for (auto &json : jsons) {
1276 mains.append(mains.empty() ? "[" : ", ");
1277 if (can_optimize_json && (json == prev)) {
1278 mains.append("'same'");
1279 } else {
1280 mains.append(json);
1281 prev = json;
1282 }
1283 }
1284 mains.append("]");
1285
1286 if (strstr(jsrootsys, "http://") || strstr(jsrootsys, "https://") || strstr(jsrootsys, "file://"))
1287 filecont = std::regex_replace(filecont, std::regex("\\$jsrootsys"), jsrootsys);
1288 else {
1289 static std::string jsroot_include = "<script id=\"jsroot\" src=\"$jsrootsys/build/jsroot.js\"></script>";
1290 auto p = filecont.find(jsroot_include);
1291 if (p != std::string::npos) {
1292 auto jsroot_build = THttpServer::ReadFileContent(std::string(jsrootsys) + "/build/jsroot.js");
1293 if (!jsroot_build.empty()) {
1294 // insert actual jsroot file location
1295 jsroot_build = std::regex_replace(jsroot_build, std::regex("'\\$jsrootsys'"), std::string("'file://") + jsrootsys + "/'");
1296 filecont.erase(p, jsroot_include.length());
1297 filecont.insert(p, "<script id=\"jsroot\">" + jsroot_build + "</script>");
1298 }
1299 }
1300
1301 filecont = std::regex_replace(filecont, std::regex("\\$jsrootsys"), "file://"s + jsrootsys);
1302 }
1303
1304 filecont = std::regex_replace(filecont, std::regex("\\$page_margin"), std::to_string(page_margin) + "px");
1305 filecont = std::regex_replace(filecont, std::regex("\\$page_width"), std::to_string(max_width + 2*page_margin) + "px");
1306 filecont = std::regex_replace(filecont, std::regex("\\$page_height"), std::to_string(max_height + 2*page_margin) + "px");
1307
1308 filecont = std::regex_replace(filecont, std::regex("\\$draw_kind"), jsonkind.Data());
1309 filecont = std::regex_replace(filecont, std::regex("\\$draw_widths"), jsonw.Data());
1310 filecont = std::regex_replace(filecont, std::regex("\\$draw_heights"), jsonh.Data());
1311 filecont = std::regex_replace(filecont, std::regex("\\$draw_objects"), mains);
1312
1314
1316 dump_name = "canvasdump";
1318 if (!df) {
1319 R__LOG_ERROR(WebGUILog()) << "Fail to create temporary file for dump-dom";
1320 return false;
1321 }
1322 fputs("placeholder", df);
1323 fclose(df);
1324 }
1325
1326try_again:
1327
1329 args.SetUrl(""s);
1331
1332 html_name.Clear();
1333
1334 R__LOG_DEBUG(0, WebGUILog()) << "Using file content_len " << filecont.length() << " to produce batch images ";
1335
1336 } else {
1337 html_name = "canvasbody";
1339 if (!hf) {
1340 R__LOG_ERROR(WebGUILog()) << "Fail to create temporary file for batch job";
1341 return false;
1342 }
1343 fputs(filecont.c_str(), hf);
1344 fclose(hf);
1345
1346 args.SetUrl("file://"s + gSystem->UnixPathName(html_name.Data()));
1347 args.SetPageContent(""s);
1348
1349 R__LOG_DEBUG(0, WebGUILog()) << "Using " << html_name << " content_len " << filecont.length() << " to produce batch images " << fdebug;
1350 }
1351
1353
1354 args.SetStandalone(true);
1355 args.SetHeadless(true);
1356 args.SetBatchMode(true);
1357 args.SetSize(widths[0], heights[0]);
1358
1359 if (use_browser_draw) {
1360
1361 tgtfilename = fnames[0].c_str();
1364
1366
1367 if (fmts[0] == "s.pdf")
1368 args.SetExtraArgs("--print-to-pdf-no-header --print-to-pdf="s + gSystem->UnixPathName(tgtfilename.Data()));
1369 else if (isFirefox) {
1370 args.SetExtraArgs("--screenshot"); // firefox does not let specify output image file
1371 wait_file_name = "screenshot.png";
1372 } else
1373 args.SetExtraArgs("--screenshot="s + gSystem->UnixPathName(tgtfilename.Data()));
1374
1375 // remove target image file - we use it as detection when chrome is ready
1376 gSystem->Unlink(tgtfilename.Data());
1377
1378 } else if (isFirefox) {
1379 // firefox will use window.dump to output produced result
1380 args.SetRedirectOutput(dump_name.Data());
1381 gSystem->Unlink(dump_name.Data());
1382 } else if (isChromeBased) {
1383 // chrome should have --dump-dom args configures
1384 args.SetRedirectOutput(dump_name.Data());
1385 gSystem->Unlink(dump_name.Data());
1386 }
1387
1388 auto handle = RWebDisplayHandle::Display(args);
1389
1390 if (!handle) {
1391 R__LOG_DEBUG(0, WebGUILog()) << "Cannot start " << args.GetBrowserName() << " to produce image " << fdebug;
1392 return false;
1393 }
1394
1395 // delete temporary HTML file
1396 if (html_name.Length() > 0) {
1397 if (gEnv->GetValue("WebGui.PreserveBatchFiles", -1) > 0)
1398 ::Info("ProduceImages", "Preserve batch file %s", html_name.Data());
1399 else
1400 gSystem->Unlink(html_name.Data());
1401 }
1402
1403 if (!wait_file_name.IsNull() && gSystem->AccessPathName(wait_file_name.Data())) {
1404 R__LOG_ERROR(WebGUILog()) << "Fail to produce image " << fdebug;
1405 return false;
1406 }
1407
1408 if (use_browser_draw) {
1409 if (fmts[0] == "s.pdf")
1410 ::Info("ProduceImages", "PDF file %s with %d pages has been created", fnames[0].c_str(), (int) jsons.size());
1411 else {
1412 if (isFirefox)
1413 gSystem->Rename("screenshot.png", fnames[0].c_str());
1414 ::Info("ProduceImages", "PNG file %s with %d pages has been created", fnames[0].c_str(), (int) jsons.size());
1415 }
1416 } else {
1417 auto dumpcont = handle->GetContent();
1418
1419 if ((dumpcont.length() > 20) && (dumpcont.length() < 60) && (use_home_dir < 2) && isChrome) {
1420 // chrome creates dummy html file with mostly no content
1421 // problem running chrome from /tmp directory, lets try work from home directory
1422 R__LOG_INFO(WebGUILog()) << "Use home directory for running chrome in batch, set TMPDIR for preferable temp directory";
1424 goto try_again;
1425 }
1426
1427 if (dumpcont.length() < 100) {
1428 R__LOG_ERROR(WebGUILog()) << "Fail to dump HTML code into " << (dump_name.IsNull() ? "CEF" : dump_name.Data());
1429 return false;
1430 }
1431
1432 std::string::size_type p = 0;
1433
1434 for (unsigned n = 0; n < fmts.size(); n++) {
1435 if (fmts[n].empty())
1436 continue;
1437 if (fmts[n] == "svg") {
1438 auto p1 = dumpcont.find("<div><svg", p);
1439 auto p2 = dumpcont.find("</svg></div>", p1 + 8);
1440 p = p2 + 12;
1441 std::ofstream ofs(fnames[n]);
1442 if ((p1 != std::string::npos) && (p2 != std::string::npos) && (p1 < p2)) {
1443 if (p2 - p1 > 10) {
1444 ofs << dumpcont.substr(p1 + 5, p2 - p1 + 1);
1445 ::Info("ProduceImages", "Image file %s size %d bytes has been created", fnames[n].c_str(), (int) (p2 - p1 + 1));
1446 } else {
1447 ::Error("ProduceImages", "Failure producing %s", fnames[n].c_str());
1448 }
1449 }
1450 } else {
1451 auto p0 = dumpcont.find("<img src=\"", p);
1452 auto p1 = dumpcont.find(";base64,", p0 + 8);
1453 auto p2 = dumpcont.find("\">", p1 + 8);
1454 p = p2 + 2;
1455
1456 if ((p0 != std::string::npos) && (p1 != std::string::npos) && (p2 != std::string::npos) && (p1 < p2)) {
1457 auto base64 = dumpcont.substr(p1+8, p2-p1-8);
1458 if ((base64 == "failure") || (base64.length() < 10)) {
1459 ::Error("ProduceImages", "Failure producing %s", fnames[n].c_str());
1460 } else {
1461 auto binary = TBase64::Decode(base64.c_str());
1462 std::ofstream ofs(fnames[n], std::ios::binary);
1463 ofs.write(binary.Data(), binary.Length());
1464 ::Info("ProduceImages", "Image file %s size %d bytes has been created", fnames[n].c_str(), (int) binary.Length());
1465 }
1466 } else {
1467 ::Error("ProduceImages", "Failure producing %s", fnames[n].c_str());
1468 return false;
1469 }
1470 }
1471 }
1472 }
1473
1474 R__LOG_DEBUG(0, WebGUILog()) << "Create " << (fnames.size() > 1 ? "files " : "file ") << fdebug;
1475
1476 return true;
1477}
1478
nlohmann::json json
#define R__LOG_ERROR(...)
Definition RLogger.hxx:357
#define R__LOG_DEBUG(DEBUGLEVEL,...)
Definition RLogger.hxx:360
#define R__LOG_INFO(...)
Definition RLogger.hxx:359
#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:170
void Error(const char *location, const char *msgfmt,...)
Use this function in case an error occurred.
Definition TError.cxx:185
winID h TVirtualViewer3D TVirtualGLPainter p
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:110
@ kExecutePermission
Definition TSystem.h:53
R__EXTERN TSystem * gSystem
Definition TSystem.h:572
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 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
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:131
static TString ToJSON(const T *obj, Int_t compact=0, const char *member_name=nullptr)
Definition TBufferJSON.h:75
@ 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:491
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:543
static const TString & GetEtcDir()
Get the sysconfig directory in the installation. Static utility function.
Definition TROOT.cxx:3082
static const TString & GetDataDir()
Get the data directory in the installation. Static utility function.
Definition TROOT.cxx:3092
Random number generator class based on M.
Definition TRandom3.h:27
Basic string class.
Definition TString.h:139
TObjArray * Tokenize(const TString &delim) const
This function is used to isolate sequential tokens in a TString.
Definition TString.cxx:2264
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:2378
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:1511
virtual Bool_t ExpandPathName(TString &path)
Expand a pathname getting rid of special shell characters like ~.
Definition TSystem.cxx:1286
virtual const char * Getenv(const char *env)
Get environment variable.
Definition TSystem.cxx:1677
virtual int mkdir(const char *name, Bool_t recursive=kFALSE)
Make a file system directory.
Definition TSystem.cxx:918
virtual Int_t Exec(const char *shellcmd)
Execute a command.
Definition TSystem.cxx:653
virtual int Load(const char *module, const char *entry="", Bool_t system=kFALSE)
Load a shared library.
Definition TSystem.cxx:1869
virtual const char * PrependPathName(const char *dir, TString &name)
Concatenate a directory and a file name.
Definition TSystem.cxx:1093
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:1308
virtual std::string GetHomeDirectory(const char *userName=nullptr) const
Return the user's home directory.
Definition TSystem.cxx:907
virtual const char * UnixPathName(const char *unixpathname)
Convert from a local pathname to a Unix pathname.
Definition TSystem.cxx:1075
virtual int Rename(const char *from, const char *to)
Rename a file.
Definition TSystem.cxx:1362
virtual TString GetFromPipe(const char *command, Int_t *ret=nullptr, Bool_t redirectStderr=kFALSE)
Execute command and return output in TString.
Definition TSystem.cxx:686
virtual Bool_t IsAbsoluteFileName(const char *dir)
Return true if dir is an absolute pathname.
Definition TSystem.cxx:963
virtual const char * WorkingDirectory()
Return working directory.
Definition TSystem.cxx:883
virtual int Unlink(const char *name)
Unlink, i.e.
Definition TSystem.cxx:1393
virtual const char * TempDirectory() const
Return a user configured or systemwide directory to create temporary files in.
Definition TSystem.cxx:1494
std::ostream & Info()
Definition hadd.cxx:171
const Int_t n
Definition legend1.C:16
TH1F * h1
Definition legend1.C:5
tbb::task_arena is an alias of tbb::interface7::task_arena, which doesn't allow to forward declare tb...
ROOT::RLogChannel & WebGUILog()
Log channel for WebGUI diagnostics.
TCanvas * slash()
Definition slash.C:1
TMarker m
Definition textangle.C:8