Logo ROOT  
Reference Guide
 
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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 "TRandom.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"
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 <stdlib.h>
40#include <signal.h>
41#include <spawn.h>
42#endif
43
44using namespace ROOT;
45using namespace std::string_literals;
46
47/** \class ROOT::RWebDisplayHandle
48\ingroup webdisplay
49
50Handle of created web-based display
51Depending from type of web display, holds handle of started browser process or other display-specific information
52to correctly stop and cleanup display.
53*/
54
55
56//////////////////////////////////////////////////////////////////////////////////////////////////
57/// Static holder of registered creators of web displays
58
59std::map<std::string, std::unique_ptr<RWebDisplayHandle::Creator>> &RWebDisplayHandle::GetMap()
60{
61 static std::map<std::string, std::unique_ptr<RWebDisplayHandle::Creator>> sMap;
62 return sMap;
63}
64
65//////////////////////////////////////////////////////////////////////////////////////////////////
66/// Search for specific browser creator
67/// If not found, try to add one
68/// \param name - creator name like ChromeCreator
69/// \param libname - shared library name where creator could be provided
70
71std::unique_ptr<RWebDisplayHandle::Creator> &RWebDisplayHandle::FindCreator(const std::string &name, const std::string &libname)
72{
73 auto &m = GetMap();
74 auto search = m.find(name);
75 if (search == m.end()) {
76
77 if (libname == "ChromeCreator") {
78 m.emplace(name, std::make_unique<ChromeCreator>(name == "edge"));
79 } else if (libname == "FirefoxCreator") {
80 m.emplace(name, std::make_unique<FirefoxCreator>());
81 } else if (libname == "SafariCreator") {
82 m.emplace(name, std::make_unique<SafariCreator>());
83 } else if (libname == "BrowserCreator") {
84 m.emplace(name, std::make_unique<BrowserCreator>(false));
85 } else if (!libname.empty()) {
86 gSystem->Load(libname.c_str());
87 }
88
89 search = m.find(name); // try again
90 }
91
92 if (search != m.end())
93 return search->second;
94
95 static std::unique_ptr<RWebDisplayHandle::Creator> dummy;
96 return dummy;
97}
98
99namespace ROOT {
100
101//////////////////////////////////////////////////////////////////////////////////////////////////
102/// Specialized handle to hold information about running browser process
103/// Used to correctly cleanup all processes and temporary directories
104
106
107#ifdef _MSC_VER
108 typedef int browser_process_id;
109#else
110 typedef pid_t browser_process_id;
111#endif
112 std::string fTmpDir; ///< temporary directory to delete at the end
113 std::string fTmpFile; ///< temporary file to remove
114 bool fHasPid{false};
116
117public:
118 RWebBrowserHandle(const std::string &url, const std::string &tmpdir, const std::string &tmpfile, const std::string &dump) :
120 {
121 SetContent(dump);
122 }
123
124 RWebBrowserHandle(const std::string &url, const std::string &tmpdir, const std::string &tmpfile, browser_process_id pid)
126 {
127 }
128
130 {
131#ifdef _MSC_VER
132 if (fHasPid)
133 gSystem->Exec(("taskkill /F /PID "s + std::to_string(fPid) + " >NUL 2>NUL").c_str());
134 std::string rmdir = "rmdir /S /Q ", rmfile = "del /F ";
135#else
136 if (fHasPid)
137 kill(fPid, SIGKILL);
138 std::string rmdir = "rm -rf ", rmfile = "rm -f ";
139#endif
140 if (!fTmpDir.empty())
141 gSystem->Exec((rmdir + fTmpDir).c_str());
142 if (!fTmpFile.empty())
143 gSystem->Exec((rmfile + fTmpFile).c_str());
144 }
145
146};
147
148} // namespace ROOT
149
150//////////////////////////////////////////////////////////////////////////////////////////////////
151/// Class to handle starting of web-browsers like Chrome or Firefox
152
154{
155 if (custom) return;
156
157 if (!exec.empty()) {
158 if (exec.find("$url") == std::string::npos) {
159 fProg = exec;
160#ifdef _MSC_VER
161 fExec = exec + " $url";
162#else
163 fExec = exec + " $url &";
164#endif
165 } else {
166 fExec = exec;
167 auto pos = exec.find(" ");
168 if (pos != std::string::npos)
169 fProg = exec.substr(0, pos);
170 }
171 } else if (gSystem->InheritsFrom("TMacOSXSystem")) {
172 fExec = "open \'$url\'";
173 } else if (gSystem->InheritsFrom("TWinNTSystem")) {
174 fExec = "start $url";
175 } else {
176 fExec = "xdg-open \'$url\' &";
177 }
178}
179
180//////////////////////////////////////////////////////////////////////////////////////////////////
181/// Check if browser executable exists and can be used
182
184{
185 if (nexttry.empty() || !fProg.empty())
186 return;
187
189#ifdef R__MACOSX
190 fProg = std::regex_replace(nexttry, std::regex("%20"), " ");
191#else
192 fProg = nexttry;
193#endif
194 return;
195 }
196
197 if (!check_std_paths)
198 return;
199
200#ifdef _MSC_VER
201 std::string ProgramFiles = gSystem->Getenv("ProgramFiles");
202 auto pos = ProgramFiles.find(" (x86)");
203 if (pos != std::string::npos)
204 ProgramFiles.erase(pos, 6);
205 std::string ProgramFilesx86 = gSystem->Getenv("ProgramFiles(x86)");
206
207 if (!ProgramFiles.empty())
208 TestProg(ProgramFiles + nexttry, false);
209 if (!ProgramFilesx86.empty())
210 TestProg(ProgramFilesx86 + nexttry, false);
211#endif
212}
213
214//////////////////////////////////////////////////////////////////////////////////////////////////
215/// Display given URL in web browser
216
217std::unique_ptr<RWebDisplayHandle>
219{
220 std::string url = args.GetFullUrl();
221 if (url.empty())
222 return nullptr;
223
225 std::cout << "New web window: " << url << std::endl;
226 return std::make_unique<RWebBrowserHandle>(url, "", "", "");
227 }
228
229 std::string exec;
230 if (args.IsBatchMode())
231 exec = fBatchExec;
232 else if (args.IsHeadless())
233 exec = fHeadlessExec;
234 else if (args.IsStandalone())
235 exec = fExec;
236 else
237 exec = "$prog $url &";
238
239 if (exec.empty())
240 return nullptr;
241
242 std::string swidth = std::to_string(args.GetWidth() > 0 ? args.GetWidth() : 800),
243 sheight = std::to_string(args.GetHeight() > 0 ? args.GetHeight() : 600),
244 sposx = std::to_string(args.GetX() >= 0 ? args.GetX() : 0),
245 sposy = std::to_string(args.GetY() >= 0 ? args.GetY() : 0);
246
247 ProcessGeometry(exec, args);
248
249 std::string rmdir = MakeProfile(exec, args.IsBatchMode() || args.IsHeadless());
250
251 std::string tmpfile;
252
253 // these are secret parameters, hide them in temp file
254 if (((url.find("token=") != std::string::npos) || (url.find("key=") != std::string::npos)) && !args.IsBatchMode() && !args.IsHeadless()) {
255 TString filebase = "root_start_";
256
257 auto f = gSystem->TempFileName(filebase, nullptr, ".html");
258
259 bool ferr = false;
260
261 if (!f) {
262 ferr = true;
263 } else {
264 std::string content = std::regex_replace(
265 "<!DOCTYPE html>\n"
266 "<html lang=\"en\">\n"
267 "<head>\n"
268 " <meta charset=\"utf-8\">\n"
269 " <meta http-equiv=\"refresh\" content=\"0;url=$url\"/>\n"
270 " <title>Opening ROOT widget</title>\n"
271 "</head>\n"
272 "<body>\n"
273 "<p>\n"
274 " This page should redirect you to a ROOT widget. If it doesn't,\n"
275 " <a href=\"$url\">click here to go to ROOT</a>.\n"
276 "</p>\n"
277 "</body>\n"
278 "</html>\n", std::regex("\$url"), url);
279
280 if (fwrite(content.c_str(), 1, content.length(), f) != content.length())
281 ferr = true;
282
283 if (fclose(f) != 0)
284 ferr = true;
285
286 tmpfile = filebase.Data();
287
288 url = "file://"s + tmpfile;
289 }
290
291 if (ferr) {
292 if (!tmpfile.empty())
293 gSystem->Unlink(tmpfile.c_str());
294 R__LOG_ERROR(WebGUILog()) << "Fail to create temporary HTML file to startup widget";
295 return nullptr;
296 }
297 }
298
299 exec = std::regex_replace(exec, std::regex("\$rootetcdir"), TROOT::GetEtcDir().Data());
300 exec = std::regex_replace(exec, std::regex("\$url"), url);
301 exec = std::regex_replace(exec, std::regex("\$width"), swidth);
302 exec = std::regex_replace(exec, std::regex("\$height"), sheight);
303 exec = std::regex_replace(exec, std::regex("\$posx"), sposx);
304 exec = std::regex_replace(exec, std::regex("\$posy"), sposy);
305
306 if (exec.compare(0,5,"fork:") == 0) {
307 if (fProg.empty()) {
308 if (!tmpfile.empty())
309 gSystem->Unlink(tmpfile.c_str());
310 R__LOG_ERROR(WebGUILog()) << "Fork instruction without executable";
311 return nullptr;
312 }
313
314 exec.erase(0, 5);
315
316#ifndef _MSC_VER
317
318 std::unique_ptr<TObjArray> fargs(TString(exec.c_str()).Tokenize(" "));
319 if (!fargs || (fargs->GetLast()<=0)) {
320 if (!tmpfile.empty())
321 gSystem->Unlink(tmpfile.c_str());
322 R__LOG_ERROR(WebGUILog()) << "Fork instruction is empty";
323 return nullptr;
324 }
325
326 std::vector<char *> argv;
327 argv.push_back((char *) fProg.c_str());
328 for (Int_t n = 0; n <= fargs->GetLast(); ++n)
329 argv.push_back((char *)fargs->At(n)->GetName());
330 argv.push_back(nullptr);
331
332 R__LOG_DEBUG(0, WebGUILog()) << "Show web window in browser with posix_spawn:\n" << fProg << " " << exec;
333
338
339 pid_t pid;
340 int status = posix_spawn(&pid, argv[0], &action, nullptr, argv.data(), nullptr);
341
343
344 if (status != 0) {
345 if (!tmpfile.empty())
346 gSystem->Unlink(tmpfile.c_str());
347 R__LOG_ERROR(WebGUILog()) << "Fail to launch " << argv[0];
348 return nullptr;
349 }
350
351 // add processid and rm dir
352
353 return std::make_unique<RWebBrowserHandle>(url, rmdir, tmpfile, pid);
354
355#else
356
357 if (fProg.empty()) {
358 if (!tmpfile.empty())
359 gSystem->Unlink(tmpfile.c_str());
360 R__LOG_ERROR(WebGUILog()) << "No Web browser found";
361 return nullptr;
362 }
363
364 // use UnixPathName to simplify handling of backslashes
365 exec = "wmic process call create '"s + gSystem->UnixPathName(fProg.c_str()) + " " + exec + "' | find \"ProcessId\" "s;
366 std::string process_id = gSystem->GetFromPipe(exec.c_str()).Data();
367 std::stringstream ss(process_id);
368 std::string tmp;
369 char c;
370 int pid = 0;
371 ss >> tmp >> c >> pid;
372
373 if (pid <= 0) {
374 if (!tmpfile.empty())
375 gSystem->Unlink(tmpfile.c_str());
376 R__LOG_ERROR(WebGUILog()) << "Fail to launch " << fProg;
377 return nullptr;
378 }
379
380 // add processid and rm dir
381 return std::make_unique<RWebBrowserHandle>(url, rmdir, tmpfile, pid);
382#endif
383 }
384
385#ifdef _MSC_VER
386
387 if (exec.rfind("&") == exec.length() - 1) {
388
389 // if last symbol is &, use _spawn to detach execution
390 exec.resize(exec.length() - 1);
391
392 std::vector<char *> argv;
393 std::string firstarg = fProg;
394 auto slashpos = firstarg.find_last_of("/\");
395 if (slashpos != std::string::npos)
396 firstarg.erase(0, slashpos + 1);
397 argv.push_back((char *)firstarg.c_str());
398
399 std::unique_ptr<TObjArray> fargs(TString(exec.c_str()).Tokenize(" "));
400 for (Int_t n = 1; n <= fargs->GetLast(); ++n)
401 argv.push_back((char *)fargs->At(n)->GetName());
402 argv.push_back(nullptr);
403
404 R__LOG_DEBUG(0, WebGUILog()) << "Showing web window in " << fProg << " with:\n" << exec;
405
406 _spawnv(_P_NOWAIT, gSystem->UnixPathName(fProg.c_str()), argv.data());
407
408 return std::make_unique<RWebBrowserHandle>(url, rmdir, tmpfile, ""s);
409 }
410
411 std::string prog = "\""s + gSystem->UnixPathName(fProg.c_str()) + "\""s;
412
413#else
414
415#ifdef R__MACOSX
416 std::string prog = std::regex_replace(fProg, std::regex(" "), "\ ");
417#else
418 std::string prog = fProg;
419#endif
420
421#endif
422
423 exec = std::regex_replace(exec, std::regex("\$prog"), prog);
424
425 std::string redirect = args.GetRedirectOutput(), dump_content;
426
427 if (!redirect.empty()) {
428 if (exec.find("$dumpfile") != std::string::npos) {
429 exec = std::regex_replace(exec, std::regex("\$dumpfile"), redirect);
430 } else {
431 auto p = exec.length();
432 if (exec.rfind("&") == p-1) --p;
433 exec.insert(p, " >"s + redirect + " "s);
434 }
435 }
436
437 R__LOG_DEBUG(0, WebGUILog()) << "Showing web window in browser with:\n" << exec;
438
439 gSystem->Exec(exec.c_str());
440
441 // read content of redirected output
442 if (!redirect.empty()) {
444
445 if (gEnv->GetValue("WebGui.PreserveBatchFiles", -1) > 0)
446 ::Info("RWebDisplayHandle::Display", "Preserve dump file %s", redirect.c_str());
447 else
448 gSystem->Unlink(redirect.c_str());
449 }
450
451 return std::make_unique<RWebBrowserHandle>(url, rmdir, tmpfile, dump_content);
452}
453
454//////////////////////////////////////////////////////////////////////////////////////////////////
455/// Constructor
456
458{
459 fExec = gEnv->GetValue("WebGui.SafariInteractive", "open -a Safari $url");
460}
461
462//////////////////////////////////////////////////////////////////////////////////////////////////
463/// Returns true if it can be used
464
466{
467#ifdef R__MACOSX
468 return true;
469#else
470 return false;
471#endif
472}
473
474//////////////////////////////////////////////////////////////////////////////////////////////////
475/// Constructor
476
478{
479 fEdge = _edge;
480
481 fEnvPrefix = fEdge ? "WebGui.Edge" : "WebGui.Chrome";
482
483 TestProg(gEnv->GetValue(fEnvPrefix.c_str(), ""));
484
485 if (!fProg.empty() && !fEdge)
486 fChromeVersion = gEnv->GetValue("WebGui.ChromeVersion", -1);
487
488#ifdef _MSC_VER
489 if (fEdge)
490 TestProg("\Microsoft\Edge\Application\msedge.exe", true);
491 else
492 TestProg("\Google\Chrome\Application\chrome.exe", true);
493#endif
494#ifdef R__MACOSX
495 TestProg("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome");
496#endif
497#ifdef R__LINUX
498 TestProg("/usr/bin/chromium");
499 TestProg("/usr/bin/chromium-browser");
500 TestProg("/usr/bin/chrome-browser");
501 TestProg("/usr/bin/google-chrome-stable");
502 TestProg("/usr/bin/google-chrome");
503#endif
504
505// --no-sandbox is required to run chrome with super-user, but only in headless mode
506// --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
507
508#ifdef _MSC_VER
509 // here --headless=old was used to let normally end of Edge process when --dump-dom is used
510 // while on Windows chrome and edge version not tested, just suppose that newest chrome is used
511 fBatchExec = gEnv->GetValue((fEnvPrefix + "Batch").c_str(), "$prog --headless --no-sandbox $geometry --dump-dom $url");
512 // in interactive headless mode fork used to let stop browser via process id
513 fHeadlessExec = gEnv->GetValue((fEnvPrefix + "Headless").c_str(), "fork:--headless --no-sandbox --disable-gpu $geometry \"$url\"");
514 fExec = gEnv->GetValue((fEnvPrefix + "Interactive").c_str(), "$prog $geometry --new-window --app=$url &"); // & in windows mean usage of spawn
515#else
516#ifdef R__MACOSX
517 bool use_normal = true; // mac does not like new flag
518#else
519 bool use_normal = (fChromeVersion < 119) || (fChromeVersion > 131);
520#endif
521 if (use_normal) {
522 // old ow newest browser with standard headless mode
523 fBatchExec = gEnv->GetValue((fEnvPrefix + "Batch").c_str(), "$prog --headless --no-sandbox --disable-extensions --disable-audio-output $geometry --dump-dom $url 2>/dev/null");
524 fHeadlessExec = gEnv->GetValue((fEnvPrefix + "Headless").c_str(), "fork:--headless --no-sandbox --disable-extensions --disable-audio-output $geometry $url");
525 } else {
526 // newer version with headless=new mode
527 fBatchExec = gEnv->GetValue((fEnvPrefix + "Batch").c_str(), "$prog --headless=new --no-sandbox --disable-extensions --disable-audio-output $geometry --dump-dom $url 2>/dev/null");
528 fHeadlessExec = gEnv->GetValue((fEnvPrefix + "Headless").c_str(), "fork:--headless=new --no-sandbox --disable-extensions --disable-audio-output $geometry $url");
529 }
530 fExec = gEnv->GetValue((fEnvPrefix + "Interactive").c_str(), "$prog $geometry --new-window --app=\'$url\' >/dev/null 2>/dev/null &");
531#endif
532}
533
534
535//////////////////////////////////////////////////////////////////////////////////////////////////
536/// Replace $geometry placeholder with geometry settings
537/// Also RWebDisplayArgs::GetExtraArgs() are appended
538
540{
541 std::string geometry;
542 if ((args.GetWidth() > 0) && (args.GetHeight() > 0))
543 geometry = "--window-size="s + std::to_string(args.GetWidth())
544 + (args.IsHeadless() ? "x"s : ","s)
545 + std::to_string(args.GetHeight());
546
547 if (((args.GetX() >= 0) || (args.GetY() >= 0)) && !args.IsHeadless()) {
548 if (!geometry.empty()) geometry.append(" ");
549 geometry.append("--window-position="s + std::to_string(args.GetX() >= 0 ? args.GetX() : 0) + ","s +
550 std::to_string(args.GetY() >= 0 ? args.GetY() : 0));
551 }
552
553 if (!args.GetExtraArgs().empty()) {
554 if (!geometry.empty()) geometry.append(" ");
555 geometry.append(args.GetExtraArgs());
556 }
557
558 exec = std::regex_replace(exec, std::regex("\$geometry"), geometry);
559}
560
561
562//////////////////////////////////////////////////////////////////////////////////////////////////
563/// Handle profile argument
564
565std::string RWebDisplayHandle::ChromeCreator::MakeProfile(std::string &exec, bool)
566{
567 std::string rmdir, profile_arg;
568
569 if (exec.find("$profile") == std::string::npos)
570 return rmdir;
571
572 const char *chrome_profile = gEnv->GetValue((fEnvPrefix + "Profile").c_str(), "");
575 } else {
576 gRandom->SetSeed(0);
578#ifdef _MSC_VER
579 char slash = '\';
580#else
581 char slash = '/';
582#endif
583 if (!profile_arg.empty() && (profile_arg[profile_arg.length()-1] != slash))
585 profile_arg += "root_chrome_profile_"s + std::to_string(gRandom->Integer(0x100000));
586
587 rmdir = profile_arg;
588 }
589
590 exec = std::regex_replace(exec, std::regex("\$profile"), profile_arg);
591
592 return rmdir;
593}
594
595
596//////////////////////////////////////////////////////////////////////////////////////////////////
597/// Constructor
598
600{
601 TestProg(gEnv->GetValue("WebGui.Firefox", ""));
602
603#ifdef _MSC_VER
604 TestProg("\Mozilla Firefox\firefox.exe", true);
605#endif
606#ifdef R__MACOSX
607 TestProg("/Applications/Firefox.app/Contents/MacOS/firefox");
608#endif
609#ifdef R__LINUX
610 TestProg("/usr/bin/firefox");
611 TestProg("/usr/bin/firefox-bin");
612#endif
613
614#ifdef _MSC_VER
615 // there is a problem when specifying the window size with wmic on windows:
616 // It gives: Invalid format. Hint: <paramlist> = <param> [, <paramlist>].
617 fBatchExec = gEnv->GetValue("WebGui.FirefoxBatch", "$prog -headless -no-remote $profile $url");
618 fHeadlessExec = gEnv->GetValue("WebGui.FirefoxHeadless", "fork:-headless -no-remote $profile \"$url\"");
619 fExec = gEnv->GetValue("WebGui.FirefoxInteractive", "$prog -no-remote $profile $geometry $url &");
620#else
621 fBatchExec = gEnv->GetValue("WebGui.FirefoxBatch", "$rootetcdir/runfirefox.sh $dumpfile $cleanup_profile $prog --headless -no-remote -new-instance $profile $url 2>/dev/null");
622 fHeadlessExec = gEnv->GetValue("WebGui.FirefoxHeadless", "fork:--headless -no-remote $profile --private-window $url");
623 fExec = gEnv->GetValue("WebGui.FirefoxInteractive", "$rootetcdir/runfirefox.sh __nodump__ $cleanup_profile $prog -no-remote $profile $geometry -url \'$url\' &");
624#endif
625}
626
627//////////////////////////////////////////////////////////////////////////////////////////////////
628/// Process window geometry for Firefox
629
631{
632 std::string geometry;
633 if ((args.GetWidth() > 0) && (args.GetHeight() > 0) && !args.IsHeadless())
634 geometry = "-width="s + std::to_string(args.GetWidth()) + " -height=" + std::to_string(args.GetHeight());
635
636 exec = std::regex_replace(exec, std::regex("\$geometry"), geometry);
637}
638
639//////////////////////////////////////////////////////////////////////////////////////////////////
640/// Create Firefox profile to run independent browser window
641
643{
644 std::string rmdir, profile_arg;
645
646 if (exec.find("$profile") == std::string::npos)
647 return rmdir;
648
649 const char *ff_profile = gEnv->GetValue("WebGui.FirefoxProfile", "");
650 const char *ff_profilepath = gEnv->GetValue("WebGui.FirefoxProfilePath", "");
651 Int_t ff_randomprofile = gEnv->GetValue("WebGui.FirefoxRandomProfile", (Int_t) 1);
652 if (ff_profile && *ff_profile) {
653 profile_arg = "-P "s + ff_profile;
654 } else if (ff_profilepath && *ff_profilepath) {
655 profile_arg = "-profile "s + ff_profilepath;
656 } else if (ff_randomprofile > 0) {
657
658 gRandom->SetSeed(0);
659 std::string profile_dir = gSystem->TempDirectory();
660
661#ifdef _MSC_VER
662 char slash = '\';
663#else
664 char slash = '/';
665#endif
666 if (!profile_dir.empty() && (profile_dir[profile_dir.length()-1] != slash))
668 profile_dir += "root_ff_profile_"s + std::to_string(gRandom->Integer(0x100000));
669
670 profile_arg = "-profile "s + profile_dir;
671
672 if (gSystem->mkdir(profile_dir.c_str()) == 0) {
673 rmdir = profile_dir;
674
675 std::ofstream user_js(profile_dir + "/user.js", std::ios::trunc);
676 // workaround for current Firefox, without such settings it fail to close window and terminate it from batch
677 // also disable question about upload of data
678 user_js << "user_pref(\"datareporting.policy.dataSubmissionPolicyAcceptedVersion\", 2);" << std::endl;
679 user_js << "user_pref(\"datareporting.policy.dataSubmissionPolicyNotifiedTime\", \"1635760572813\");" << std::endl;
680
681 // try to ensure that window closes with last tab
682 user_js << "user_pref(\"browser.tabs.closeWindowWithLastTab\", true);" << std::endl;
683 user_js << "user_pref(\"dom.allow_scripts_to_close_windows\", true);" << std::endl;
684 user_js << "user_pref(\"browser.sessionstore.resume_from_crash\", false);" << std::endl;
685
686 if (batch_mode) {
687 // allow to dump messages to std output
688 user_js << "user_pref(\"browser.dom.window.dump.enabled\", true);" << std::endl;
689 } else {
690 // to suppress annoying privacy tab
691 user_js << "user_pref(\"datareporting.policy.firstRunURL\", \"\");" << std::endl;
692 // to use custom userChrome.css files
693 user_js << "user_pref(\"toolkit.legacyUserProfileCustomizations.stylesheets\", true);" << std::endl;
694 // do not put tabs in title
695 user_js << "user_pref(\"browser.tabs.inTitlebar\", 0);" << std::endl;
696
697 std::ofstream times_json(profile_dir + "/times.json", std::ios::trunc);
698 times_json << "{" << std::endl;
699 times_json << " \"created\": 1699968480952," << std::endl;
700 times_json << " \"firstUse\": null" << std::endl;
701 times_json << "}" << std::endl;
702 if (gSystem->mkdir((profile_dir + "/chrome").c_str()) == 0) {
703 std::ofstream style(profile_dir + "/chrome/userChrome.css", std::ios::trunc);
704 // do not show tabs
705 style << "#TabsToolbar { visibility: collapse; }" << std::endl;
706 // do not show URL
707 style << "#nav-bar, #urlbar-container, #searchbar { visibility: collapse !important; }" << std::endl;
708 }
709 }
710
711 } else {
712 R__LOG_ERROR(WebGUILog()) << "Cannot create Firefox profile directory " << profile_dir;
713 }
714 }
715
716 exec = std::regex_replace(exec, std::regex("\$profile"), profile_arg);
717
718 if (exec.find("$cleanup_profile") != std::string::npos) {
719 if (rmdir.empty()) rmdir = "__dummy__";
720 exec = std::regex_replace(exec, std::regex("\$cleanup_profile"), rmdir);
721 rmdir.clear(); // no need to delete directory - it will be removed by script
722 }
723
724 return rmdir;
725}
726
727///////////////////////////////////////////////////////////////////////////////////////////////////
728/// Check if http server required for display
729/// \param args - defines where and how to display web window
730
732{
736 return false;
737
738 if (!args.IsHeadless() && (args.GetBrowserKind() == RWebDisplayArgs::kOn)) {
739
740#ifdef WITH_QT6WEB
741 auto &qt6 = FindCreator("qt6", "libROOTQt6WebDisplay");
742 if (qt6 && qt6->IsActive())
743 return false;
744#endif
745#ifdef WITH_QT5WEB
746 auto &qt5 = FindCreator("qt5", "libROOTQt5WebDisplay");
747 if (qt5 && qt5->IsActive())
748 return false;
749#endif
750#ifdef WITH_CEFWEB
751 auto &cef = FindCreator("cef", "libROOTCefDisplay");
752 if (cef && cef->IsActive())
753 return false;
754#endif
755 }
756
757 return true;
758}
759
760
761///////////////////////////////////////////////////////////////////////////////////////////////////
762/// Create web display
763/// \param args - defines where and how to display web window
764/// Returns RWebDisplayHandle, which holds information of running browser application
765/// Can be used fully independent from RWebWindow classes just to show any web page
766
767std::unique_ptr<RWebDisplayHandle> RWebDisplayHandle::Display(const RWebDisplayArgs &args)
768{
769 std::unique_ptr<RWebDisplayHandle> handle;
770
772 return handle;
773
774 auto try_creator = [&](std::unique_ptr<Creator> &creator) {
775 if (!creator || !creator->IsActive())
776 return false;
777 handle = creator->Display(args);
778 return handle ? true : false;
779 };
780
782 (!args.IsHeadless() && (args.GetBrowserKind() == RWebDisplayArgs::kOn)),
783 has_qt5web = false, has_qt6web = false, has_cefweb = false;
784
785#ifdef WITH_QT5WEB
786 has_qt5web = true;
787#endif
788
789#ifdef WITH_QT6WEB
790 has_qt6web = true;
791#endif
792
793#ifdef WITH_CEFWEB
794 has_cefweb = true;
795#endif
796
798 if (try_creator(FindCreator("qt6", "libROOTQt6WebDisplay")))
799 return handle;
800 }
801
802 // qt5 uses older chromium therefore do not invoke by default
804 if (try_creator(FindCreator("qt5", "libROOTQt5WebDisplay")))
805 return handle;
806 }
807
809 if (try_creator(FindCreator("cef", "libROOTCefDisplay")))
810 return handle;
811 }
812
813 if (args.IsLocalDisplay()) {
814 R__LOG_ERROR(WebGUILog()) << "Neither Qt5/6 nor CEF libraries were found to provide local display";
815 return handle;
816 }
817
818 bool handleAsNative =
820
822 if (try_creator(FindCreator("chrome", "ChromeCreator")))
823 return handle;
824 }
825
827 if (try_creator(FindCreator("firefox", "FirefoxCreator")))
828 return handle;
829 }
830
831#ifdef _MSC_VER
832 // Edge browser cannot be run headless without registry change, therefore do not try it by default
833 if ((handleAsNative && !args.IsHeadless() && !args.IsBatchMode()) || (args.GetBrowserKind() == RWebDisplayArgs::kEdge)) {
834 if (try_creator(FindCreator("edge", "ChromeCreator")))
835 return handle;
836 }
837#endif
838
841 // R__LOG_ERROR(WebGUILog()) << "Neither Chrome nor Firefox browser cannot be started to provide display";
842 return handle;
843 }
844
846 if (try_creator(FindCreator("safari", "SafariCreator")))
847 return handle;
848 }
849
851 std::unique_ptr<Creator> creator = std::make_unique<BrowserCreator>(false, args.GetCustomExec());
852 try_creator(creator);
853 } else {
854 try_creator(FindCreator("browser", "BrowserCreator"));
855 }
856
857 return handle;
858}
859
860///////////////////////////////////////////////////////////////////////////////////////////////////
861/// Display provided url in configured web browser
862/// \param url - specified URL address like https://root.cern
863/// Browser can specified when starting `root --web=firefox`
864/// Returns true when browser started
865/// It is convenience method, equivalent to:
866/// ~~~
867/// RWebDisplayArgs args;
868/// args.SetUrl(url);
869/// args.SetStandalone(false);
870/// auto handle = RWebDisplayHandle::Display(args);
871/// ~~~
872
873bool RWebDisplayHandle::DisplayUrl(const std::string &url)
874{
875 RWebDisplayArgs args;
876 args.SetUrl(url);
877 args.SetStandalone(false);
878
879 auto handle = Display(args);
880
881 return !!handle;
882}
883
884///////////////////////////////////////////////////////////////////////////////////////////////////
885/// Checks if configured browser can be used for image production
886
888{
892 bool detected = false;
893
894 auto &h1 = FindCreator("chrome", "ChromeCreator");
895 if (h1 && h1->IsActive()) {
897 detected = true;
898 }
899
900 if (!detected) {
901 auto &h2 = FindCreator("firefox", "FirefoxCreator");
902 if (h2 && h2->IsActive()) {
904 detected = true;
905 }
906 }
907
908 return detected;
909 }
910
912 auto &h1 = FindCreator("chrome", "ChromeCreator");
913 return h1 && h1->IsActive();
914 }
915
917 auto &h2 = FindCreator("firefox", "FirefoxCreator");
918 return h2 && h2->IsActive();
919 }
920
921#ifdef _MSC_VER
923 auto &h3 = FindCreator("edge", "ChromeCreator");
924 return h3 && h3->IsActive();
925 }
926#endif
927
928 return true;
929}
930
931///////////////////////////////////////////////////////////////////////////////////////////////////
932/// Returns true if image production for specified browser kind is supported
933/// If browser not specified - use currently configured browser or try to test existing web browsers
934
936{
938
939 return CheckIfCanProduceImages(args);
940}
941
942///////////////////////////////////////////////////////////////////////////////////////////////////
943/// Detect image format
944/// There is special handling of ".screenshot.pdf" and ".screenshot.png" extensions
945/// Creation of such files relies on headless browser functionality and fully supported only by Chrome browser
946
947std::string RWebDisplayHandle::GetImageFormat(const std::string &fname)
948{
949 std::string _fname = fname;
950 std::transform(_fname.begin(), _fname.end(), _fname.begin(), ::tolower);
951 auto EndsWith = [&_fname](const std::string &suffix) {
952 return (_fname.length() > suffix.length()) ? (0 == _fname.compare(_fname.length() - suffix.length(), suffix.length(), suffix)) : false;
953 };
954
955 if (EndsWith(".screenshot.pdf"))
956 return "s.pdf"s;
957 if (EndsWith(".pdf"))
958 return "pdf"s;
959 if (EndsWith(".json"))
960 return "json"s;
961 if (EndsWith(".svg"))
962 return "svg"s;
963 if (EndsWith(".screenshot.png"))
964 return "s.png"s;
965 if (EndsWith(".png"))
966 return "png"s;
967 if (EndsWith(".jpg") || EndsWith(".jpeg"))
968 return "jpeg"s;
969 if (EndsWith(".webp"))
970 return "webp"s;
971
972 return ""s;
973}
974
975
976///////////////////////////////////////////////////////////////////////////////////////////////////
977/// Produce image file using JSON data as source
978/// Invokes JSROOT drawing functionality in headless browser - Google Chrome or Mozilla Firefox
979
980bool RWebDisplayHandle::ProduceImage(const std::string &fname, const std::string &json, int width, int height, const char *batch_file)
981{
982 return ProduceImages(fname, {json}, {width}, {height}, batch_file);
983}
984
985
986///////////////////////////////////////////////////////////////////////////////////////////////////
987/// Produce vector of file names for specified file pattern
988/// Depending from supported file forma
989
990std::vector<std::string> RWebDisplayHandle::ProduceImagesNames(const std::string &fname, unsigned nfiles)
991{
992 auto fmt = GetImageFormat(fname);
993
994 std::vector<std::string> fnames;
995
996 if ((fmt == "s.pdf") || (fmt == "s.png")) {
997 fnames.emplace_back(fname);
998 } else {
999 std::string farg = fname;
1000
1001 bool has_quialifier = farg.find("%") != std::string::npos;
1002
1003 if (!has_quialifier && (nfiles > 1) && (fmt != "pdf")) {
1004 farg.insert(farg.rfind("."), "%d");
1005 has_quialifier = true;
1006 }
1007
1008 for (unsigned n = 0; n < nfiles; n++) {
1009 if(has_quialifier) {
1010 auto expand_name = TString::Format(farg.c_str(), (int) n);
1011 fnames.emplace_back(expand_name.Data());
1012 } else if (n > 0)
1013 fnames.emplace_back(""); // empty name is multiPdf
1014 else
1015 fnames.emplace_back(fname);
1016 }
1017 }
1018
1019 return fnames;
1020}
1021
1022
1023///////////////////////////////////////////////////////////////////////////////////////////////////
1024/// Produce image file(s) using JSON data as source
1025/// Invokes JSROOT drawing functionality in headless browser - Google Chrome or Mozilla Firefox
1026
1027bool 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)
1028{
1030}
1031
1032///////////////////////////////////////////////////////////////////////////////////////////////////
1033/// Produce image file(s) using JSON data as source
1034/// Invokes JSROOT drawing functionality in headless browser - Google Chrome or Mozilla Firefox
1035
1036bool 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)
1037{
1038 if (fnames.empty() || jsons.empty())
1039 return false;
1040
1041 std::vector<std::string> fmts;
1042 for (auto& fname : fnames)
1043 fmts.emplace_back(GetImageFormat(fname));
1044
1045 bool is_any_image = false;
1046
1047 for (unsigned n = 0; (n < fmts.size()) && (n < jsons.size()); n++) {
1048 if (fmts[n] == "json") {
1049 std::ofstream ofs(fnames[n]);
1050 ofs << jsons[n];
1051 fmts[n].clear();
1052 } else if (!fmts[n].empty())
1053 is_any_image = true;
1054 }
1055
1056 if (!is_any_image)
1057 return true;
1058
1059 std::string fdebug;
1060 if (fnames.size() == 1)
1061 fdebug = fnames[0];
1062 else
1064
1065 const char *jsrootsys = gSystem->Getenv("JSROOTSYS");
1067 if (!jsrootsys) {
1068 jsrootsysdflt = TROOT::GetDataDir() + "/js";
1070 R__LOG_ERROR(WebGUILog()) << "Fail to locate JSROOT " << jsrootsysdflt;
1071 return false;
1072 }
1073 jsrootsys = jsrootsysdflt.Data();
1074 }
1075
1076 RWebDisplayArgs args; // set default browser kind, only Chrome/Firefox/Edge or CEF/Qt5/Qt6 can be used here
1077 if (!CheckIfCanProduceImages(args)) {
1078 R__LOG_ERROR(WebGUILog()) << "Fail to detect supported browsers for image production";
1079 return false;
1080 }
1081
1084
1085 std::vector<std::string> draw_kinds;
1086 bool use_browser_draw = false;
1088
1089 if (fmts[0] == "s.png") {
1090 if (!isChromeBased && !isFirefox) {
1091 R__LOG_ERROR(WebGUILog()) << "Direct png image creation supported only by Chrome and Firefox browsers";
1092 return false;
1093 }
1094 use_browser_draw = true;
1095 jsonkind = "1111"; // special mark in canv_batch.htm
1096 } else if (fmts[0] == "s.pdf") {
1097 if (!isChromeBased) {
1098 R__LOG_ERROR(WebGUILog()) << "Direct creation of PDF files supported only by Chrome-based browser";
1099 return false;
1100 }
1101 use_browser_draw = true;
1102 jsonkind = "2222"; // special mark in canv_batch.htm
1103 } else {
1104 draw_kinds = fmts;
1106 }
1107
1108 if (!batch_file || !*batch_file)
1109 batch_file = "/js/files/canv_batch.htm";
1110
1113 R__LOG_ERROR(WebGUILog()) << "Fail to find " << origin;
1114 return false;
1115 }
1116
1118 if (filecont.empty()) {
1119 R__LOG_ERROR(WebGUILog()) << "Fail to read content of " << origin;
1120 return false;
1121 }
1122
1123 int max_width = 0, max_height = 0, page_margin = 10;
1124 for (auto &w : widths)
1125 if (w > max_width)
1126 max_width = w;
1127 for (auto &h : heights)
1128 if (h > max_height)
1129 max_height = h;
1130
1133
1134 std::string mains;
1135 for (auto &json : jsons) {
1136 mains.append(mains.empty() ? "[" : ", ");
1137 mains.append(json);
1138 }
1139 mains.append("]");
1140
1141 if (strstr(jsrootsys, "http://") || strstr(jsrootsys, "https://") || strstr(jsrootsys, "file://"))
1142 filecont = std::regex_replace(filecont, std::regex("\$jsrootsys"), jsrootsys);
1143 else {
1144 static std::string jsroot_include = "<script id=\"jsroot\" src=\"$jsrootsys/build/jsroot.js\"></script>";
1145 auto p = filecont.find(jsroot_include);
1146 if (p != std::string::npos) {
1147 auto jsroot_build = THttpServer::ReadFileContent(std::string(jsrootsys) + "/build/jsroot.js");
1148 if (!jsroot_build.empty()) {
1149 // insert actual jsroot file location
1150 jsroot_build = std::regex_replace(jsroot_build, std::regex("'\$jsrootsys'"), std::string("'file://") + jsrootsys + "/'");
1151 filecont.erase(p, jsroot_include.length());
1152 filecont.insert(p, "<script id=\"jsroot\">" + jsroot_build + "</script>");
1153 }
1154 }
1155
1156 filecont = std::regex_replace(filecont, std::regex("\$jsrootsys"), "file://"s + jsrootsys);
1157 }
1158
1159 filecont = std::regex_replace(filecont, std::regex("\$page_margin"), std::to_string(page_margin) + "px");
1160 filecont = std::regex_replace(filecont, std::regex("\$page_width"), std::to_string(max_width + 2*page_margin) + "px");
1161 filecont = std::regex_replace(filecont, std::regex("\$page_height"), std::to_string(max_height + 2*page_margin) + "px");
1162
1163 filecont = std::regex_replace(filecont, std::regex("\$draw_kind"), jsonkind.Data());
1164 filecont = std::regex_replace(filecont, std::regex("\$draw_widths"), jsonw.Data());
1165 filecont = std::regex_replace(filecont, std::regex("\$draw_heights"), jsonh.Data());
1166 filecont = std::regex_replace(filecont, std::regex("\$draw_objects"), mains);
1167
1170 dump_name = "canvasdump";
1172 if (!df) {
1173 R__LOG_ERROR(WebGUILog()) << "Fail to create temporary file for dump-dom";
1174 return false;
1175 }
1176 fputs("placeholder", df);
1177 fclose(df);
1178 }
1179
1180 // When true, place HTML file into home directory
1181 // Some Chrome installation do not allow run html code from files, created in /tmp directory
1182 static bool chrome_tmp_workaround = false;
1183
1185
1186try_again:
1187
1189 args.SetUrl(""s);
1191
1192 tmp_name.Clear();
1193 html_name.Clear();
1194
1195 R__LOG_DEBUG(0, WebGUILog()) << "Using file content_len " << filecont.length() << " to produce batch images ";
1196
1197 } else {
1198 tmp_name = "canvasbody";
1200 if (!hf) {
1201 R__LOG_ERROR(WebGUILog()) << "Fail to create temporary file for batch job";
1202 return false;
1203 }
1204 fputs(filecont.c_str(), hf);
1205 fclose(hf);
1206
1207 html_name = tmp_name + ".html";
1208
1210 std::string homedir = gSystem->GetHomeDirectory();
1211 auto pos = html_name.Last('/');
1212 if (pos == kNPOS)
1213 html_name = TString::Format("/random%d.html", gRandom->Integer(1000000));
1214 else
1215 html_name.Remove(0, pos);
1216 html_name = homedir + html_name.Data();
1217 gSystem->Unlink(html_name.Data());
1218 gSystem->Unlink(tmp_name.Data());
1219
1220 std::ofstream ofs(html_name.Data(), std::ofstream::out);
1221 ofs << filecont;
1222 } else {
1223 if (gSystem->Rename(tmp_name.Data(), html_name.Data()) != 0) {
1224 R__LOG_ERROR(WebGUILog()) << "Fail to rename temp file " << tmp_name << " into " << html_name;
1225 gSystem->Unlink(tmp_name.Data());
1226 return false;
1227 }
1228 }
1229
1230 args.SetUrl("file://"s + gSystem->UnixPathName(html_name.Data()));
1231 args.SetPageContent(""s);
1232
1233 R__LOG_DEBUG(0, WebGUILog()) << "Using " << html_name << " content_len " << filecont.length() << " to produce batch images " << fdebug;
1234 }
1235
1237
1238 args.SetStandalone(true);
1239 args.SetHeadless(true);
1240 args.SetBatchMode(true);
1241 args.SetSize(widths[0], heights[0]);
1242
1243 if (use_browser_draw) {
1244
1245 tgtfilename = fnames[0].c_str();
1248
1250
1251 if (fmts[0] == "s.pdf")
1252 args.SetExtraArgs("--print-to-pdf-no-header --print-to-pdf="s + gSystem->UnixPathName(tgtfilename.Data()));
1253 else if (isFirefox) {
1254 args.SetExtraArgs("--screenshot"); // firefox does not let specify output image file
1255 wait_file_name = "screenshot.png";
1256 } else
1257 args.SetExtraArgs("--screenshot="s + gSystem->UnixPathName(tgtfilename.Data()));
1258
1259 // remove target image file - we use it as detection when chrome is ready
1260 gSystem->Unlink(tgtfilename.Data());
1261
1262 } else if (isFirefox) {
1263 // firefox will use window.dump to output produced result
1264 args.SetRedirectOutput(dump_name.Data());
1265 gSystem->Unlink(dump_name.Data());
1266 } else if (isChromeBased) {
1267 // chrome should have --dump-dom args configures
1268 args.SetRedirectOutput(dump_name.Data());
1269 gSystem->Unlink(dump_name.Data());
1270 }
1271
1272 auto handle = RWebDisplayHandle::Display(args);
1273
1274 if (!handle) {
1275 R__LOG_DEBUG(0, WebGUILog()) << "Cannot start " << args.GetBrowserName() << " to produce image " << fdebug;
1276 return false;
1277 }
1278
1279 // delete temporary HTML file
1280 if (html_name.Length() > 0) {
1281 if (gEnv->GetValue("WebGui.PreserveBatchFiles", -1) > 0)
1282 ::Info("ProduceImages", "Preserve batch file %s", html_name.Data());
1283 else
1284 gSystem->Unlink(html_name.Data());
1285 }
1286
1287 if (!wait_file_name.IsNull() && gSystem->AccessPathName(wait_file_name.Data())) {
1288 R__LOG_ERROR(WebGUILog()) << "Fail to produce image " << fdebug;
1289 return false;
1290 }
1291
1292 if (use_browser_draw) {
1293 if (fmts[0] == "s.pdf")
1294 ::Info("ProduceImages", "PDF file %s with %d pages has been created", fnames[0].c_str(), (int) jsons.size());
1295 else {
1296 if (isFirefox)
1297 gSystem->Rename("screenshot.png", fnames[0].c_str());
1298 ::Info("ProduceImages", "PNG file %s with %d pages has been created", fnames[0].c_str(), (int) jsons.size());
1299 }
1300 } else {
1301 auto dumpcont = handle->GetContent();
1302
1303 if ((dumpcont.length() > 20) && (dumpcont.length() < 60) && !chrome_tmp_workaround && isChromeBased) {
1304 // chrome creates dummy html file with mostly no content
1305 // problem running chrome from /tmp directory, lets try work from home directory
1306
1307 printf("Handle chrome workaround\n");
1308 chrome_tmp_workaround = true;
1309 goto try_again;
1310 }
1311
1312 if (dumpcont.length() < 100) {
1313 R__LOG_ERROR(WebGUILog()) << "Fail to dump HTML code into " << (dump_name.IsNull() ? "CEF" : dump_name.Data());
1314 return false;
1315 }
1316
1317 std::string::size_type p = 0;
1318
1319 for (unsigned n = 0; n < fmts.size(); n++) {
1320 if (fmts[n].empty())
1321 continue;
1322 if (fmts[n] == "svg") {
1323 auto p1 = dumpcont.find("<svg", p);
1324 auto p2 = dumpcont.find("</svg></div>", p1 + 4);
1325 p = p2 + 6;
1326 std::ofstream ofs(fnames[n]);
1327 if ((p1 != std::string::npos) && (p2 != std::string::npos) && (p1 < p2)) {
1328 ofs << dumpcont.substr(p1, p2-p1+6);
1329 ::Info("ProduceImages", "SVG file %s size %d bytes has been created", fnames[n].c_str(), (int) (p2-p1+6));
1330 } else {
1331 R__LOG_ERROR(WebGUILog()) << "Fail to extract SVG from HTML dump " << dump_name;
1332 ofs << "Failure!!!\n" << dumpcont;
1333 return false;
1334 }
1335 } else {
1336 auto p1 = dumpcont.find(";base64,", p);
1337 auto p2 = dumpcont.find("></div>", p1 + 4);
1338 p = p2 + 5;
1339
1340 if ((p1 != std::string::npos) && (p2 != std::string::npos) && (p1 < p2)) {
1341 auto base64 = dumpcont.substr(p1+8, p2-p1-9);
1342 auto binary = TBase64::Decode(base64.c_str());
1343
1344 std::ofstream ofs(fnames[n], std::ios::binary);
1345 ofs.write(binary.Data(), binary.Length());
1346
1347 ::Info("ProduceImages", "Image file %s size %d bytes has been created", fnames[n].c_str(), (int) binary.Length());
1348 } else {
1349 R__LOG_ERROR(WebGUILog()) << "Fail to extract image from dump HTML code " << dump_name;
1350
1351 return false;
1352 }
1353 }
1354 }
1355 }
1356
1357 R__LOG_DEBUG(0, WebGUILog()) << "Create " << (fnames.size() > 1 ? "files " : "file ") << fdebug;
1358
1359 return true;
1360}
1361
nlohmann::json json
#define R__LOG_ERROR(...)
Definition RLogger.hxx:362
#define R__LOG_DEBUG(DEBUGLEVEL,...)
Definition RLogger.hxx:365
#define f(i)
Definition RSha256.hxx:104
#define c(i)
Definition RSha256.hxx:101
#define h(i)
Definition RSha256.hxx:106
constexpr Ssiz_t kNPOS
Definition RtypesCore.h:117
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 Info(const char *location, const char *msgfmt,...)
Use this function for informational messages.
Definition TError.cxx:218
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
R__EXTERN TRandom * gRandom
Definition TRandom.h:62
@ 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
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.
@ kQt5
Qt5 QWebEngine libraries - Chromium code packed in qt5.
@ 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
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 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:530
static const TString & GetEtcDir()
Get the sysconfig directory in the installation. Static utility function.
Definition TROOT.cxx:3055
static const TString & GetDataDir()
Get the data directory in the installation. Static utility function.
Definition TROOT.cxx:3065
virtual void SetSeed(ULong_t seed=0)
Set the random generator seed.
Definition TRandom.cxx:615
virtual UInt_t Integer(UInt_t imax)
Returns a random integer uniformly distributed on the interval [ 0, imax-1 ].
Definition TRandom.cxx:361
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:1499
virtual Bool_t ExpandPathName(TString &path)
Expand a pathname getting rid of special shell characters like ~.
Definition TSystem.cxx:1274
virtual const char * Getenv(const char *env)
Get environment variable.
Definition TSystem.cxx:1665
virtual int mkdir(const char *name, Bool_t recursive=kFALSE)
Make a file system directory.
Definition TSystem.cxx:906
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:1857
virtual const char * PrependPathName(const char *dir, TString &name)
Concatenate a directory and a file name.
Definition TSystem.cxx:1081
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:1296
virtual std::string GetHomeDirectory(const char *userName=nullptr) const
Return the user's home directory.
Definition TSystem.cxx:895
virtual const char * UnixPathName(const char *unixpathname)
Convert from a local pathname to a Unix pathname.
Definition TSystem.cxx:1063
virtual int Rename(const char *from, const char *to)
Rename a file.
Definition TSystem.cxx:1350
virtual TString GetFromPipe(const char *command)
Execute command and return output in TString.
Definition TSystem.cxx:680
virtual Bool_t IsAbsoluteFileName(const char *dir)
Return true if dir is an absolute pathname.
Definition TSystem.cxx:951
virtual const char * WorkingDirectory()
Return working directory.
Definition TSystem.cxx:871
virtual int Unlink(const char *name)
Unlink, i.e.
Definition TSystem.cxx:1381
virtual const char * TempDirectory() const
Return a user configured or systemwide directory to create temporary files in.
Definition TSystem.cxx:1482
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::Experimental::RLogChannel & WebGUILog()
Log channel for WebGUI diagnostics.
TCanvas * slash()
Definition slash.C:1
TMarker m
Definition textangle.C:8