Logo ROOT  
Reference Guide
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 "TROOT.h"
25#include "TBase64.h"
26
27#include <fstream>
28#include <iostream>
29#include <memory>
30#include <regex>
31
32#ifdef _MSC_VER
33#include <process.h>
34#else
35#include <unistd.h>
36#include <stdlib.h>
37#include <signal.h>
38#include <spawn.h>
39#endif
40
41using namespace ROOT::Experimental;
42using namespace std::string_literals;
43
44/** \class ROOT::Experimental::RWebDisplayHandle
45\ingroup webdisplay
46
47Handle of created web-based display
48Depending from type of web display, holds handle of started browser process or other display-specific information
49to correctly stop and cleanup display.
50*/
51
52
53//////////////////////////////////////////////////////////////////////////////////////////////////
54/// Static holder of registered creators of web displays
55
56std::map<std::string, std::unique_ptr<RWebDisplayHandle::Creator>> &RWebDisplayHandle::GetMap()
57{
58 static std::map<std::string, std::unique_ptr<RWebDisplayHandle::Creator>> sMap;
59 return sMap;
60}
61
62//////////////////////////////////////////////////////////////////////////////////////////////////
63/// Search for specific browser creator
64/// If not found, try to add one
65/// \param name - creator name like ChromeCreator
66/// \param libname - shared library name where creator could be provided
67
68std::unique_ptr<RWebDisplayHandle::Creator> &RWebDisplayHandle::FindCreator(const std::string &name, const std::string &libname)
69{
70 auto &m = GetMap();
71 auto search = m.find(name);
72 if (search == m.end()) {
73
74 if (libname == "ChromeCreator") {
75 m.emplace(name, std::make_unique<ChromeCreator>(name == "edge"));
76 } else if (libname == "FirefoxCreator") {
77 m.emplace(name, std::make_unique<FirefoxCreator>());
78 } else if (libname == "BrowserCreator") {
79 m.emplace(name, std::make_unique<BrowserCreator>(false));
80 } else if (!libname.empty()) {
81 gSystem->Load(libname.c_str());
82 }
83
84 search = m.find(name); // try again
85 }
86
87 if (search != m.end())
88 return search->second;
89
90 static std::unique_ptr<RWebDisplayHandle::Creator> dummy;
91 return dummy;
92}
93
94namespace ROOT {
95namespace Experimental {
96
97//////////////////////////////////////////////////////////////////////////////////////////////////
98/// Specialized handle to hold information about running browser process
99/// Used to correctly cleanup all processes and temporary directories
100
102
103#ifdef _MSC_VER
104 typedef int browser_process_id;
105#else
106 typedef pid_t browser_process_id;
107#endif
108 std::string fTmpDir; ///< temporary directory to delete at the end
109 bool fHasPid{false};
111
112public:
113 RWebBrowserHandle(const std::string &url, const std::string &tmpdir, const std::string &dump) : RWebDisplayHandle(url), fTmpDir(tmpdir)
114 {
115 SetContent(dump);
116 }
117
118 RWebBrowserHandle(const std::string &url, const std::string &tmpdir, browser_process_id pid)
119 : RWebDisplayHandle(url), fTmpDir(tmpdir), fHasPid(true), fPid(pid)
120 {
121 }
122
124 {
125#ifdef _MSC_VER
126 if (fHasPid)
127 gSystem->Exec(("taskkill /F /PID "s + std::to_string(fPid) + " >NUL 2>NUL").c_str());
128 std::string rmdir = "rmdir /S /Q ";
129#else
130 if (fHasPid)
131 kill(fPid, SIGKILL);
132 std::string rmdir = "rm -rf ";
133#endif
134 if (!fTmpDir.empty())
135 gSystem->Exec((rmdir + fTmpDir).c_str());
136 }
137
138};
139
140} // namespace Experimental
141} // namespace ROOT
142
143//////////////////////////////////////////////////////////////////////////////////////////////////
144/// Class to handle starting of web-browsers like Chrome or Firefox
145
146RWebDisplayHandle::BrowserCreator::BrowserCreator(bool custom, const std::string &exec)
147{
148 if (custom) return;
149
150 if (!exec.empty()) {
151 if (exec.find("$url") == std::string::npos) {
152 fProg = exec;
153#ifdef _MSC_VER
154 fExec = exec + " $url";
155#else
156 fExec = exec + " $url &";
157#endif
158 } else {
159 fExec = exec;
160 auto pos = exec.find(" ");
161 if (pos != std::string::npos)
162 fProg = exec.substr(0, pos);
163 }
164 } else if (gSystem->InheritsFrom("TMacOSXSystem")) {
165 fExec = "open \'$url\'";
166 } else if (gSystem->InheritsFrom("TWinNTSystem")) {
167 fExec = "start $url";
168 } else {
169 fExec = "xdg-open \'$url\' &";
170 }
171}
172
173//////////////////////////////////////////////////////////////////////////////////////////////////
174/// Check if browser executable exists and can be used
175
176void RWebDisplayHandle::BrowserCreator::TestProg(const std::string &nexttry, bool check_std_paths)
177{
178 if (nexttry.empty() || !fProg.empty())
179 return;
180
181 if (!gSystem->AccessPathName(nexttry.c_str(), kExecutePermission)) {
182#ifdef R__MACOSX
183 fProg = std::regex_replace(nexttry, std::regex("%20"), " ");
184#else
185 fProg = nexttry;
186#endif
187 return;
188 }
189
190 if (!check_std_paths)
191 return;
192
193#ifdef _MSC_VER
194 std::string ProgramFiles = gSystem->Getenv("ProgramFiles");
195 auto pos = ProgramFiles.find(" (x86)");
196 if (pos != std::string::npos)
197 ProgramFiles.erase(pos, 6);
198 std::string ProgramFilesx86 = gSystem->Getenv("ProgramFiles(x86)");
199
200 if (!ProgramFiles.empty())
201 TestProg(ProgramFiles + nexttry, false);
202 if (!ProgramFilesx86.empty())
203 TestProg(ProgramFilesx86 + nexttry, false);
204#endif
205}
206
207//////////////////////////////////////////////////////////////////////////////////////////////////
208/// Display given URL in web browser
209
210std::unique_ptr<RWebDisplayHandle>
212{
213 std::string url = args.GetFullUrl();
214 if (url.empty())
215 return nullptr;
216
218 std::cout << "New web window: " << url << std::endl;
219 return std::make_unique<RWebBrowserHandle>(url, "", "");
220 }
221
222 std::string exec;
223 if (args.IsBatchMode())
224 exec = fBatchExec;
225 else if (args.IsHeadless())
226 exec = fHeadlessExec;
227 else if (args.IsStandalone())
228 exec = fExec;
229 else
230 exec = "$prog $url &";
231
232 if (exec.empty())
233 return nullptr;
234
235 std::string swidth = std::to_string(args.GetWidth() > 0 ? args.GetWidth() : 800),
236 sheight = std::to_string(args.GetHeight() > 0 ? args.GetHeight() : 600),
237 sposx = std::to_string(args.GetX() >= 0 ? args.GetX() : 0),
238 sposy = std::to_string(args.GetY() >= 0 ? args.GetY() : 0);
239
240 ProcessGeometry(exec, args);
241
242 std::string rmdir = MakeProfile(exec, args.IsHeadless());
243
244 exec = std::regex_replace(exec, std::regex("\\$url"), url);
245 exec = std::regex_replace(exec, std::regex("\\$width"), swidth);
246 exec = std::regex_replace(exec, std::regex("\\$height"), sheight);
247 exec = std::regex_replace(exec, std::regex("\\$posx"), sposx);
248 exec = std::regex_replace(exec, std::regex("\\$posy"), sposy);
249
250 if (exec.compare(0,5,"fork:") == 0) {
251 if (fProg.empty()) {
252 R__LOG_ERROR(WebGUILog()) << "Fork instruction without executable";
253 return nullptr;
254 }
255
256 exec.erase(0, 5);
257
258#ifndef _MSC_VER
259
260 std::unique_ptr<TObjArray> fargs(TString(exec.c_str()).Tokenize(" "));
261 if (!fargs || (fargs->GetLast()<=0)) {
262 R__LOG_ERROR(WebGUILog()) << "Fork instruction is empty";
263 return nullptr;
264 }
265
266 std::vector<char *> argv;
267 argv.push_back((char *) fProg.c_str());
268 for (Int_t n = 0; n <= fargs->GetLast(); ++n)
269 argv.push_back((char *)fargs->At(n)->GetName());
270 argv.push_back(nullptr);
271
272 R__LOG_DEBUG(0, WebGUILog()) << "Show web window in browser with posix_spawn:\n" << fProg << " " << exec;
273
274 pid_t pid;
275 int status = posix_spawn(&pid, argv[0], nullptr, nullptr, argv.data(), nullptr);
276 if (status != 0) {
277 R__LOG_ERROR(WebGUILog()) << "Fail to launch " << argv[0];
278 return nullptr;
279 }
280
281 // add processid and rm dir
282
283 return std::make_unique<RWebBrowserHandle>(url, rmdir, pid);
284
285#else
286
287 if (fProg.empty()) {
288 R__LOG_ERROR(WebGUILog()) << "No Web browser found";
289 return nullptr;
290 }
291
292 // use UnixPathName to simplify handling of backslashes
293 exec = "wmic process call create '"s + gSystem->UnixPathName(fProg.c_str()) + exec + "' | find \"ProcessId\" "s;
294 std::string process_id = gSystem->GetFromPipe(exec.c_str()).Data();
295 std::stringstream ss(process_id);
296 std::string tmp;
297 char c;
298 int pid = 0;
299 ss >> tmp >> c >> pid;
300
301 if (pid <= 0) {
302 R__LOG_ERROR(WebGUILog()) << "Fail to launch " << fProg;
303 return nullptr;
304 }
305
306 // add processid and rm dir
307 return std::make_unique<RWebBrowserHandle>(url, rmdir, pid);
308#endif
309 }
310
311#ifdef _MSC_VER
312
313 if (exec.rfind("&") == exec.length() - 1) {
314
315 // if last symbol is &, use _spawn to detach execution
316 exec.resize(exec.length() - 1);
317
318 std::vector<char *> argv;
319 std::string firstarg = fProg;
320 auto slashpos = firstarg.find_last_of("/\\");
321 if (slashpos != std::string::npos)
322 firstarg.erase(0, slashpos + 1);
323 argv.push_back((char *)firstarg.c_str());
324
325 std::unique_ptr<TObjArray> fargs(TString(exec.c_str()).Tokenize(" "));
326 for (Int_t n = 1; n <= fargs->GetLast(); ++n)
327 argv.push_back((char *)fargs->At(n)->GetName());
328 argv.push_back(nullptr);
329
330 R__LOG_DEBUG(0, WebGUILog()) << "Showing web window in " << fProg << " with:\n" << exec;
331
332 _spawnv(_P_NOWAIT, fProg.c_str(), argv.data());
333
334 return std::make_unique<RWebBrowserHandle>(url, rmdir, ""s);
335 }
336
337 std::string prog = "\""s + gSystem->UnixPathName(fProg.c_str()) + "\""s;
338
339#else
340
341#ifdef R__MACOSX
342 std::string prog = std::regex_replace(fProg, std::regex(" "), "\\ ");
343#else
344 std::string prog = fProg;
345#endif
346
347#endif
348
349 exec = std::regex_replace(exec, std::regex("\\$prog"), prog);
350
351 std::string redirect = args.GetRedirectOutput(), dump_content;
352
353 if (!redirect.empty()) {
354 auto p = exec.length();
355 if (exec.rfind("&") == p-1) --p;
356 exec.insert(p, " >"s + redirect + " "s);
357 }
358
359 R__LOG_DEBUG(0, WebGUILog()) << "Showing web window in browser with:\n" << exec;
360
361 gSystem->Exec(exec.c_str());
362
363 // read content of redirected output
364 if (!redirect.empty()) {
365 dump_content = THttpServer::ReadFileContent(redirect.c_str());
366
367 gSystem->Unlink(redirect.c_str());
368 }
369
370 // add rmdir if required
371 return std::make_unique<RWebBrowserHandle>(url, rmdir, dump_content);
372}
373
374//////////////////////////////////////////////////////////////////////////////////////////////////
375/// Constructor
376
378{
379 fEdge = _edge;
380
381 fEnvPrefix = fEdge ? "WebGui.Edge" : "WebGui.Chrome";
382
383 TestProg(gEnv->GetValue(fEnvPrefix.c_str(), ""));
384
385#ifdef _MSC_VER
386 if (fEdge)
387 TestProg("\\Microsoft\\Edge\\Application\\msedge.exe", true);
388 else
389 TestProg("\\Google\\Chrome\\Application\\chrome.exe", true);
390#endif
391#ifdef R__MACOSX
392 TestProg("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome");
393#endif
394#ifdef R__LINUX
395 TestProg("/usr/bin/chromium");
396 TestProg("/usr/bin/chromium-browser");
397 TestProg("/usr/bin/chrome-browser");
398#endif
399
400#ifdef _MSC_VER
401 fBatchExec = gEnv->GetValue((fEnvPrefix + "Batch").c_str(), "$prog --headless $geometry $url");
402 fHeadlessExec = gEnv->GetValue((fEnvPrefix + "Headless").c_str(), "$prog --headless --disable-gpu $geometry $url &");
403 fExec = gEnv->GetValue((fEnvPrefix + "Interactive").c_str(), "$prog $geometry --new-window --app=$url &"); // & in windows mean usage of spawn
404#else
405 fBatchExec = gEnv->GetValue((fEnvPrefix + "Batch").c_str(), "$prog --headless --no-sandbox --no-zygote --disable-extensions --disable-gpu --disable-audio-output $geometry $url");
406 fHeadlessExec = gEnv->GetValue((fEnvPrefix + "Headless").c_str(), "fork: --headless --no-sandbox --no-zygote --disable-extensions --disable-gpu --disable-audio-output $geometry $url");
407 fExec = gEnv->GetValue((fEnvPrefix + "Interactive").c_str(), "$prog $geometry --new-window --app=\'$url\' &");
408#endif
409}
410
411
412//////////////////////////////////////////////////////////////////////////////////////////////////
413/// Replace $geometry placeholder with geometry settings
414/// Also RWebDisplayArgs::GetExtraArgs() are appended
415
417{
418 std::string geometry;
419 if ((args.GetWidth() > 0) && (args.GetHeight() > 0))
420 geometry = "--window-size="s + std::to_string(args.GetWidth())
421 + (args.IsHeadless() ? "x"s : ","s)
422 + std::to_string(args.GetHeight());
423
424 if (((args.GetX() >= 0) || (args.GetY() >= 0)) && !args.IsHeadless()) {
425 if (!geometry.empty()) geometry.append(" ");
426 geometry.append("--window-position="s + std::to_string(args.GetX() >= 0 ? args.GetX() : 0) + ","s +
427 std::to_string(args.GetY() >= 0 ? args.GetY() : 0));
428 }
429
430 if (!args.GetExtraArgs().empty()) {
431 if (!geometry.empty()) geometry.append(" ");
432 geometry.append(args.GetExtraArgs());
433 }
434
435 exec = std::regex_replace(exec, std::regex("\\$geometry"), geometry);
436}
437
438
439//////////////////////////////////////////////////////////////////////////////////////////////////
440/// Handle profile argument
441
442std::string RWebDisplayHandle::ChromeCreator::MakeProfile(std::string &exec, bool)
443{
444 std::string rmdir, profile_arg;
445
446 if (exec.find("$profile") == std::string::npos)
447 return rmdir;
448
449 const char *chrome_profile = gEnv->GetValue((fEnvPrefix + "Profile").c_str(), "");
450 if (chrome_profile && *chrome_profile) {
451 profile_arg = chrome_profile;
452 } else {
453 gRandom->SetSeed(0);
454 std::string rnd_profile = "root_chrome_profile_"s + std::to_string(gRandom->Integer(0x100000));
455 profile_arg = gSystem->TempDirectory();
456
457#ifdef _MSC_VER
458 profile_arg += "\\"s + rnd_profile;
459#else
460 profile_arg += "/"s + rnd_profile;
461#endif
462 rmdir = profile_arg;
463 }
464
465 exec = std::regex_replace(exec, std::regex("\\$profile"), profile_arg);
466
467 return rmdir;
468}
469
470
471//////////////////////////////////////////////////////////////////////////////////////////////////
472/// Constructor
473
475{
476 TestProg(gEnv->GetValue("WebGui.Firefox", ""));
477
478#ifdef _MSC_VER
479 TestProg("\\Mozilla Firefox\\firefox.exe", true);
480#endif
481#ifdef R__MACOSX
482 TestProg("/Applications/Firefox.app/Contents/MacOS/firefox");
483#endif
484#ifdef R__LINUX
485 TestProg("/usr/bin/firefox");
486#endif
487
488#ifdef _MSC_VER
489 // there is a problem when specifying the window size with wmic on windows:
490 // It gives: Invalid format. Hint: <paramlist> = <param> [, <paramlist>].
491 fBatchExec = gEnv->GetValue("WebGui.FirefoxBatch", "$prog -headless -no-remote $profile $url");
492 fHeadlessExec = gEnv->GetValue("WebGui.FirefoxHeadless", "$prog -headless -no-remote $profile $url &");
493 fExec = gEnv->GetValue("WebGui.FirefoxInteractive", "$prog -no-remote $profile $url &");
494#else
495 fBatchExec = gEnv->GetValue("WebGui.FirefoxBatch", "$prog --headless --private-window --no-remote $profile $url");
496 fHeadlessExec = gEnv->GetValue("WebGui.FirefoxHeadless", "fork:--headless --private-window --no-remote $profile $url");
497 fExec = gEnv->GetValue("WebGui.FirefoxInteractive", "$prog --private-window \'$url\' &");
498#endif
499}
500
501//////////////////////////////////////////////////////////////////////////////////////////////////
502/// Create Firefox profile to run independent browser window
503
504std::string RWebDisplayHandle::FirefoxCreator::MakeProfile(std::string &exec, bool batch_mode)
505{
506 std::string rmdir, profile_arg;
507
508 if (exec.find("$profile") == std::string::npos)
509 return rmdir;
510
511 const char *ff_profile = gEnv->GetValue("WebGui.FirefoxProfile", "");
512 const char *ff_profilepath = gEnv->GetValue("WebGui.FirefoxProfilePath", "");
513 Int_t ff_randomprofile = gEnv->GetValue("WebGui.FirefoxRandomProfile", (Int_t) 0);
514 if (ff_profile && *ff_profile) {
515 profile_arg = "-P "s + ff_profile;
516 } else if (ff_profilepath && *ff_profilepath) {
517 profile_arg = "-profile "s + ff_profilepath;
518 } else if ((ff_randomprofile > 0) || (batch_mode && (ff_randomprofile >= 0))) {
519
520 gRandom->SetSeed(0);
521 std::string rnd_profile = "root_ff_profile_"s + std::to_string(gRandom->Integer(0x100000));
522 std::string profile_dir = gSystem->TempDirectory();
523
524#ifdef _MSC_VER
525 profile_dir += "\\"s + rnd_profile;
526#else
527 profile_dir += "/"s + rnd_profile;
528#endif
529
530 profile_arg = "-profile "s + profile_dir;
531
532 if (gSystem->mkdir(profile_dir.c_str()) == 0) {
533 rmdir = profile_dir;
534
535 if (batch_mode) {
536 std::ofstream user_js(profile_dir + "/user.js", std::ios::trunc);
537 user_js << "user_pref(\"browser.dom.window.dump.enabled\", true);" << std::endl;
538 // workaround for current Firefox, without such settings it fail to close window and terminate it from batch
539 user_js << "user_pref(\"datareporting.policy.dataSubmissionPolicyAcceptedVersion\", 2);" << std::endl;
540 user_js << "user_pref(\"datareporting.policy.dataSubmissionPolicyNotifiedTime\", \"1635760572813\");" << std::endl;
541 }
542
543 } else {
544 R__LOG_ERROR(WebGUILog()) << "Cannot create Firefox profile directory " << profile_dir;
545 }
546 }
547
548 exec = std::regex_replace(exec, std::regex("\\$profile"), profile_arg);
549
550 return rmdir;
551}
552
553///////////////////////////////////////////////////////////////////////////////////////////////////
554/// Create web display
555/// \param args - defines where and how to display web window
556/// Returns RWebDisplayHandle, which holds information of running browser application
557/// Can be used fully independent from RWebWindow classes just to show any web page
558
559std::unique_ptr<RWebDisplayHandle> RWebDisplayHandle::Display(const RWebDisplayArgs &args)
560{
561 std::unique_ptr<RWebDisplayHandle> handle;
562
564 return handle;
565
566 auto try_creator = [&](std::unique_ptr<Creator> &creator) {
567 if (!creator || !creator->IsActive())
568 return false;
569 handle = creator->Display(args);
570 return handle ? true : false;
571 };
572
574 if (try_creator(FindCreator("cef", "libROOTCefDisplay")))
575 return handle;
576 }
577
579 if (try_creator(FindCreator("qt5", "libROOTQt5WebDisplay")))
580 return handle;
581 }
582
584 if (try_creator(FindCreator("qt6", "libROOTQt6WebDisplay")))
585 return handle;
586 }
587
588 if (args.IsLocalDisplay()) {
589 R__LOG_ERROR(WebGUILog()) << "Neither Qt5/6 nor CEF libraries were found to provide local display";
590 return handle;
591 }
592
593 bool handleAsNative = (args.GetBrowserKind() == RWebDisplayArgs::kNative) ||
595
596#ifdef _MSC_VER
597 if (handleAsNative || (args.GetBrowserKind() == RWebDisplayArgs::kEdge)) {
598 if (try_creator(FindCreator("edge", "ChromeCreator")))
599 return handle;
600 }
601#endif
602
603 if (handleAsNative || (args.GetBrowserKind() == RWebDisplayArgs::kChrome)) {
604 if (try_creator(FindCreator("chrome", "ChromeCreator")))
605 return handle;
606 }
607
608 if (handleAsNative || (args.GetBrowserKind() == RWebDisplayArgs::kFirefox)) {
609 if (try_creator(FindCreator("firefox", "FirefoxCreator")))
610 return handle;
611 }
612
613 if (handleAsNative || (args.GetBrowserKind() == RWebDisplayArgs::kChrome) || (args.GetBrowserKind() == RWebDisplayArgs::kFirefox) || (args.GetBrowserKind() == RWebDisplayArgs::kEdge)) {
614 // R__LOG_ERROR(WebGUILog()) << "Neither Chrome nor Firefox browser cannot be started to provide display";
615 return handle;
616 }
617
619 std::unique_ptr<Creator> creator = std::make_unique<BrowserCreator>(false, args.GetCustomExec());
620 try_creator(creator);
621 } else {
622 try_creator(FindCreator("browser", "BrowserCreator"));
623 }
624
625 return handle;
626}
627
628///////////////////////////////////////////////////////////////////////////////////////////////////
629/// Display provided url in configured web browser
630/// \param url - specified URL address like https://root.cern
631/// Browser can specified when starting `root --web=firefox`
632/// Returns true when browser started
633/// It is convenience method, equivalent to:
634/// ~~~
635/// RWebDisplayArgs args;
636/// args.SetUrl(url);
637/// args.SetStandalone(false);
638/// auto handle = RWebDisplayHandle::Display(args);
639/// ~~~
640
641bool RWebDisplayHandle::DisplayUrl(const std::string &url)
642{
643 RWebDisplayArgs args;
644 args.SetUrl(url);
645 args.SetStandalone(false);
646
647 auto handle = Display(args);
648
649 return !!handle;
650}
651
652
653///////////////////////////////////////////////////////////////////////////////////////////////////
654/// Produce image file using JSON data as source
655/// Invokes JSROOT drawing functionality in headless browser - Google Chrome or Mozilla Firefox
656
657bool RWebDisplayHandle::ProduceImage(const std::string &fname, const std::string &json, int width, int height)
658{
659 if (json.empty())
660 return false;
661
662 std::string _fname = fname;
663 std::transform(_fname.begin(), _fname.end(), _fname.begin(), ::tolower);
664
665 auto EndsWith = [_fname](const std::string &suffix) {
666 return (_fname.length() > suffix.length()) ? (0 == _fname.compare (_fname.length() - suffix.length(), suffix.length(), suffix)) : false;
667 };
668
669 if (EndsWith(".json")) {
670 std::ofstream ofs(fname);
671 ofs << json;
672 return true;
673 }
674
675 const char *jsrootsys = gSystem->Getenv("JSROOTSYS");
676 TString jsrootsysdflt;
677 if (!jsrootsys) {
678 jsrootsysdflt = TROOT::GetDataDir() + "/js";
679 if (gSystem->ExpandPathName(jsrootsysdflt)) {
680 R__LOG_ERROR(WebGUILog()) << "Fail to locate JSROOT " << jsrootsysdflt;
681 return false;
682 }
683 jsrootsys = jsrootsysdflt.Data();
684 }
685
686 RWebDisplayArgs args; // set default browser kind, only Chrome or Firefox or CEF or Qt5 can be used here
690
691 std::string draw_kind;
692
693 if (EndsWith(".pdf"))
694 draw_kind = "draw"; // not a JSROOT drawing but Chrome capability to create PDF out of HTML page is used
695 else if (EndsWith("shot.png"))
696 draw_kind = (args.GetBrowserKind() == RWebDisplayArgs::kChrome) ? "draw" : "png";
697 else if (EndsWith(".svg"))
698 draw_kind = "svg";
699 else if (EndsWith(".png"))
700 draw_kind = "png";
701 else if (EndsWith(".jpg") || EndsWith(".jpeg"))
702 draw_kind = "jpeg";
703 else if (EndsWith(".webp"))
704 draw_kind = "webp";
705 else
706 return false;
707
708 TString origin = TROOT::GetDataDir() + "/js/files/canv_batch.htm";
709 if (gSystem->ExpandPathName(origin)) {
710 R__LOG_ERROR(WebGUILog()) << "Fail to find " << origin;
711 return false;
712 }
713
714 auto filecont = THttpServer::ReadFileContent(origin.Data());
715 if (filecont.empty()) {
716 R__LOG_ERROR(WebGUILog()) << "Fail to read content of " << origin;
717 return false;
718 }
719
720 filecont = std::regex_replace(filecont, std::regex("\\$draw_width"), std::to_string(width));
721 filecont = std::regex_replace(filecont, std::regex("\\$draw_height"), std::to_string(height));
722
723 if (strstr(jsrootsys,"http://") || strstr(jsrootsys,"https://") || strstr(jsrootsys,"file://"))
724 filecont = std::regex_replace(filecont, std::regex("\\$jsrootsys"), jsrootsys);
725 else
726 filecont = std::regex_replace(filecont, std::regex("\\$jsrootsys"), "file://"s + jsrootsys);
727
728 filecont = std::regex_replace(filecont, std::regex("\\$draw_kind"), draw_kind);
729
730 filecont = std::regex_replace(filecont, std::regex("\\$draw_object"), json);
731
732 TString dump_name;
733 if (draw_kind == "draw") {
735 R__LOG_ERROR(WebGUILog()) << "Creation of PDF files supported only by Chrome browser";
736 return false;
737 }
739 dump_name = "canvasdump";
740 FILE *df = gSystem->TempFileName(dump_name);
741 if (!df) {
742 R__LOG_ERROR(WebGUILog()) << "Fail to create temporary file for dump-dom";
743 return false;
744 }
745 fputs("placeholder", df);
746 fclose(df);
747 }
748
749 // When true, place HTML file into home directory
750 // Some Chrome installation do not allow run html code from files, created in /tmp directory
751 static bool chrome_tmp_workaround = false;
752
753 TString tmp_name, html_name;
754
755try_again:
756
758 args.SetUrl(""s);
759 args.SetPageContent(filecont);
760
761 tmp_name.Clear();
762 html_name.Clear();
763
764 R__LOG_DEBUG(0, WebGUILog()) << "Using file content_len " << filecont.length() << " to produce batch image " << fname;
765
766 } else {
767 tmp_name = "canvasbody";
768 FILE *hf = gSystem->TempFileName(tmp_name);
769 if (!hf) {
770 R__LOG_ERROR(WebGUILog()) << "Fail to create temporary file for batch job";
771 return false;
772 }
773 fputs(filecont.c_str(), hf);
774 fclose(hf);
775
776 html_name = tmp_name + ".html";
777
778 if (chrome_tmp_workaround) {
779 std::string homedir = gSystem->GetHomeDirectory();
780 auto pos = html_name.Last('/');
781 if (pos == kNPOS)
782 html_name = TString::Format("/random%d.html", gRandom->Integer(1000000));
783 else
784 html_name.Remove(0, pos);
785 html_name = homedir + html_name.Data();
786 gSystem->Unlink(html_name.Data());
787 gSystem->Unlink(tmp_name.Data());
788
789 std::ofstream ofs(html_name.Data(), std::ofstream::out);
790 ofs << filecont;
791 } else {
792 if (gSystem->Rename(tmp_name.Data(), html_name.Data()) != 0) {
793 R__LOG_ERROR(WebGUILog()) << "Fail to rename temp file " << tmp_name << " into " << html_name;
794 gSystem->Unlink(tmp_name.Data());
795 return false;
796 }
797 }
798
799 args.SetUrl("file://"s + gSystem->UnixPathName(html_name.Data()));
800 args.SetPageContent(""s);
801
802 R__LOG_DEBUG(0, WebGUILog()) << "Using " << html_name << " content_len " << filecont.length() << " to produce batch image " << fname;
803 }
804
805 TString tgtfilename = fname.c_str();
806 if (!gSystem->IsAbsoluteFileName(tgtfilename.Data()))
808
809 TString wait_file_name;
810
811 args.SetStandalone(true);
812 args.SetHeadless(true);
813 args.SetBatchMode(true);
814 args.SetSize(width, height);
815
816 if (draw_kind == "draw") {
817
818 wait_file_name = tgtfilename;
819
820 if (EndsWith(".pdf"))
821 args.SetExtraArgs("--print-to-pdf="s + gSystem->UnixPathName(tgtfilename.Data()));
822 else
823 args.SetExtraArgs("--screenshot="s + gSystem->UnixPathName(tgtfilename.Data()));
824
825 } else if (args.GetBrowserKind() == RWebDisplayArgs::kFirefox) {
826 // firefox will use window.dump to output produced result
827 args.SetRedirectOutput(dump_name.Data());
828 gSystem->Unlink(dump_name.Data());
829 } else if (args.GetBrowserKind() == RWebDisplayArgs::kChrome) {
830 // require temporary output file
831 args.SetExtraArgs("--dump-dom");
832 args.SetRedirectOutput(dump_name.Data());
833
834 // wait_file_name = dump_name;
835
836 gSystem->Unlink(dump_name.Data());
837 }
838
839 // remove target image file - we use it as detection when chrome is ready
840 gSystem->Unlink(tgtfilename.Data());
841
842 auto handle = RWebDisplayHandle::Display(args);
843
844 if (!handle) {
845 R__LOG_DEBUG(0, WebGUILog()) << "Cannot start " << args.GetBrowserName() << " to produce image " << fname;
846 return false;
847 }
848
849 // delete temporary HTML file
850 if (html_name.Length() > 0)
851 gSystem->Unlink(html_name.Data());
852
853 if (!wait_file_name.IsNull() && gSystem->AccessPathName(wait_file_name.Data())) {
854 R__LOG_ERROR(WebGUILog()) << "Fail to produce image " << fname;
855 return false;
856 }
857
858 if (draw_kind != "draw") {
859
860 auto dumpcont = handle->GetContent();
861
862 if ((dumpcont.length() > 20) && (dumpcont.length() < 60) && !chrome_tmp_workaround && (args.GetBrowserKind() == RWebDisplayArgs::kChrome)) {
863 // chrome creates dummy html file with mostly no content
864 // problem running chrome from /tmp directory, lets try work from home directory
865 chrome_tmp_workaround = true;
866 goto try_again;
867 }
868
869 if (dumpcont.length() < 100) {
870 R__LOG_ERROR(WebGUILog()) << "Fail to dump HTML code into " << (dump_name.IsNull() ? "CEF" : dump_name.Data());
871 return false;
872 }
873
874 if (draw_kind == "svg") {
875 auto p1 = dumpcont.find("<svg");
876 auto p2 = dumpcont.rfind("</svg>");
877
878 std::ofstream ofs(tgtfilename);
879 if ((p1 != std::string::npos) && (p2 != std::string::npos) && (p1 < p2)) {
880 ofs << dumpcont.substr(p1,p2-p1+6);
881 } else {
882 R__LOG_ERROR(WebGUILog()) << "Fail to extract SVG from HTML dump " << dump_name;
883 ofs << "Failure!!!\n" << dumpcont;
884 return false;
885 }
886 } else {
887
888 auto p1 = dumpcont.rfind(";base64,");
889 auto p2 = dumpcont.rfind("></div>");
890
891 if ((p1 != std::string::npos) && (p2 != std::string::npos) && (p1 < p2)) {
892
893 auto base64 = dumpcont.substr(p1+8, p2-p1-9);
894 auto binary = TBase64::Decode(base64.c_str());
895
896 std::ofstream ofs(tgtfilename, std::ios::binary);
897 ofs.write(binary.Data(), binary.Length());
898 } else {
899 R__LOG_ERROR(WebGUILog()) << "Fail to extract image from dump HTML code " << dump_name;
900
901 return false;
902 }
903 }
904 }
905
906 R__LOG_DEBUG(0, WebGUILog()) << "Create file " << fname;
907
908 return true;
909}
910
#define R__LOG_ERROR(...)
Definition: RLogger.hxx:362
#define R__LOG_DEBUG(DEBUGLEVEL,...)
Definition: RLogger.hxx:365
#define c(i)
Definition: RSha256.hxx:101
const Ssiz_t kNPOS
Definition: RtypesCore.h:124
R__EXTERN TEnv * gEnv
Definition: TEnv.h:170
winID h TVirtualViewer3D TVirtualGLPainter p
Option_t Option_t width
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:45
R__EXTERN TSystem * gSystem
Definition: TSystem.h:560
Specialized handle to hold information about running browser process Used to correctly cleanup all pr...
std::string fTmpDir
temporary directory to delete at the end
RWebBrowserHandle(const std::string &url, const std::string &tmpdir, const std::string &dump)
RWebBrowserHandle(const std::string &url, const std::string &tmpdir, browser_process_id pid)
Holds different arguments for starting browser with RWebDisplayHandle::Display() method.
std::string GetBrowserName() const
Returns configured browser name.
bool IsHeadless() const
returns headless mode
RWebDisplayArgs & SetPageContent(const std::string &cont)
set window url
void SetExtraArgs(const std::string &args)
set extra command line arguments for starting web browser command
RWebDisplayArgs & SetUrl(const std::string &url)
set window url
bool IsBatchMode() const
returns batch mode
void SetBatchMode(bool on=true)
set batch mode
int GetHeight() const
returns preferable web window height
void SetHeadless(bool on=true)
set headless mode
EBrowserKind GetBrowserKind() const
returns configured browser kind, see EBrowserKind for supported values
const std::string & GetExtraArgs() const
get extra command line arguments for starting web browser command
void SetStandalone(bool on=true)
Set standalone mode for running browser, default on When disabled, normal browser window (or just tab...
std::string GetFullUrl() const
returns window url with append options
RWebDisplayArgs & SetBrowserKind(const std::string &kind)
Set browser kind as string argument.
std::string GetCustomExec() const
returns custom executable to start web browser
bool IsStandalone() const
Return true if browser should runs in standalone mode.
bool IsLocalDisplay() const
returns true if local display like CEF or Qt5 QWebEngine should be used
void SetRedirectOutput(const std::string &fname="")
specify file name to which web browser output should be redirected
@ kFirefox
Mozilla Firefox browser.
@ kCEF
Chromium Embedded Framework - local display with CEF libs.
@ kQt6
Qt6 QWebEngine libraries - Chromium code packed in qt6.
@ kCustom
custom web browser, execution string should be provided
@ kNative
either Chrome or Firefox - both support major functionality
@ kEdge
Microsoft Edge browser (Windows only)
@ kDefault
default system web browser, can not be used in batch mode
@ 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
@ kQt5
Qt5 QWebEngine libraries - Chromium code packed in qt5.
const std::string & GetRedirectOutput() const
get file name to which web browser output should be redirected
RWebDisplayArgs & SetSize(int w, int h)
set preferable web window width and height
int GetY() const
set preferable web window y position
int GetX() const
set preferable web window x position
int GetWidth() const
returns preferable web window width
std::unique_ptr< RWebDisplayHandle > Display(const RWebDisplayArgs &args) override
Display given URL in web browser.
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.
void ProcessGeometry(std::string &, const RWebDisplayArgs &args) 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.
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 ProduceImage(const std::string &fname, const std::string &json, int width=800, int height=600)
Produce image file using JSON data as source Invokes JSROOT drawing functionality in headless browser...
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
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:526
static const TString & GetDataDir()
Get the data directory in the installation. Static utility function.
Definition: TROOT.cxx:2977
virtual void SetSeed(ULong_t seed=0)
Set the random generator seed.
Definition: TRandom.cxx:608
virtual UInt_t Integer(UInt_t imax)
Returns a random integer uniformly distributed on the interval [ 0, imax-1 ].
Definition: TRandom.cxx:360
Basic string class.
Definition: TString.h:136
Ssiz_t Length() const
Definition: TString.h:410
void Clear()
Clear string without changing its capacity.
Definition: TString.cxx:1210
const char * Data() const
Definition: TString.h:369
Ssiz_t Last(char c) const
Find last occurrence of a character c.
Definition: TString.cxx:925
TObjArray * Tokenize(const TString &delim) const
This function is used to isolate sequential tokens in a TString.
Definition: TString.cxx:2231
Bool_t IsNull() const
Definition: TString.h:407
TString & Remove(Ssiz_t pos)
Definition: TString.h:673
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:2345
virtual Bool_t ExpandPathName(TString &path)
Expand a pathname getting rid of special shell characters like ~.
Definition: TSystem.cxx:1277
virtual const char * Getenv(const char *env)
Get environment variable.
Definition: TSystem.cxx:1666
virtual int mkdir(const char *name, Bool_t recursive=kFALSE)
Make a file system directory.
Definition: TSystem.cxx:909
virtual Int_t Exec(const char *shellcmd)
Execute a command.
Definition: TSystem.cxx:656
virtual int Load(const char *module, const char *entry="", Bool_t system=kFALSE)
Load a shared library.
Definition: TSystem.cxx:1858
virtual const char * PrependPathName(const char *dir, TString &name)
Concatenate a directory and a file name.
Definition: TSystem.cxx:1084
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:1299
virtual FILE * TempFileName(TString &base, const char *dir=nullptr)
Create a secure temporary file by appending a unique 6 letter string to base.
Definition: TSystem.cxx:1500
virtual std::string GetHomeDirectory(const char *userName=nullptr) const
Return the user's home directory.
Definition: TSystem.cxx:898
virtual const char * UnixPathName(const char *unixpathname)
Convert from a local pathname to a Unix pathname.
Definition: TSystem.cxx:1066
virtual int Rename(const char *from, const char *to)
Rename a file.
Definition: TSystem.cxx:1353
virtual TString GetFromPipe(const char *command)
Execute command and return output in TString.
Definition: TSystem.cxx:683
virtual Bool_t IsAbsoluteFileName(const char *dir)
Return true if dir is an absolute pathname.
Definition: TSystem.cxx:954
virtual const char * WorkingDirectory()
Return working directory.
Definition: TSystem.cxx:874
virtual int Unlink(const char *name)
Unlink, i.e.
Definition: TSystem.cxx:1384
virtual const char * TempDirectory() const
Return a user configured or systemwide directory to create temporary files in.
Definition: TSystem.cxx:1485
RVec< PromoteType< T > > trunc(const RVec< T > &v)
Definition: RVec.hxx:1814
const Int_t n
Definition: legend1.C:16
RLogChannel & WebGUILog()
Log channel for WebGUI diagnostics.
bool EndsWith(const std::string &theString, const std::string &theSubstring)
This file contains a specialised ROOT message handler to test for diagnostic in unit tests.
static constexpr double s
basic_json<> json
Definition: REveElement.hxx:62
TMarker m
Definition: textangle.C:8