Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RWebDisplayHandle.cxx
Go to the documentation of this file.
1// Author: Sergey Linev <s.linev@gsi.de>
2// Date: 2018-10-17
3// Warning: This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback is welcome!
4
5/*************************************************************************
6 * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers. *
7 * All rights reserved. *
8 * *
9 * For the licensing terms see $ROOTSYS/LICENSE. *
10 * For the list of contributors see $ROOTSYS/README/CREDITS. *
11 *************************************************************************/
12
14
15#include <ROOT/RLogger.hxx>
16
17#include "RConfigure.h"
18#include "TSystem.h"
19#include "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, gSystem->UnixPathName(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 TestProg("/usr/bin/google-chrome-stable");
399 TestProg("/usr/bin/google-chrome");
400#endif
401
402#ifdef _MSC_VER
403 fBatchExec = gEnv->GetValue((fEnvPrefix + "Batch").c_str(), "$prog --headless $geometry $url");
404 fHeadlessExec = gEnv->GetValue((fEnvPrefix + "Headless").c_str(), "$prog --headless --disable-gpu $geometry \"$url\" --dump-dom &");
405 fExec = gEnv->GetValue((fEnvPrefix + "Interactive").c_str(), "$prog $geometry --new-window --app=$url &"); // & in windows mean usage of spawn
406#else
407 fBatchExec = gEnv->GetValue((fEnvPrefix + "Batch").c_str(), "$prog --headless --no-sandbox --no-zygote --disable-extensions --disable-gpu --disable-audio-output $geometry $url");
408 fHeadlessExec = gEnv->GetValue((fEnvPrefix + "Headless").c_str(), "$prog --headless --no-sandbox --no-zygote --disable-extensions --disable-gpu --disable-audio-output $geometry \'$url\' --dump-dom >/dev/null &");
409 fExec = gEnv->GetValue((fEnvPrefix + "Interactive").c_str(), "$prog $geometry --new-window --app=\'$url\' &");
410#endif
411}
412
413
414//////////////////////////////////////////////////////////////////////////////////////////////////
415/// Replace $geometry placeholder with geometry settings
416/// Also RWebDisplayArgs::GetExtraArgs() are appended
417
419{
420 std::string geometry;
421 if ((args.GetWidth() > 0) && (args.GetHeight() > 0))
422 geometry = "--window-size="s + std::to_string(args.GetWidth())
423 + (args.IsHeadless() ? "x"s : ","s)
424 + std::to_string(args.GetHeight());
425
426 if (((args.GetX() >= 0) || (args.GetY() >= 0)) && !args.IsHeadless()) {
427 if (!geometry.empty()) geometry.append(" ");
428 geometry.append("--window-position="s + std::to_string(args.GetX() >= 0 ? args.GetX() : 0) + ","s +
429 std::to_string(args.GetY() >= 0 ? args.GetY() : 0));
430 }
431
432 if (!args.GetExtraArgs().empty()) {
433 if (!geometry.empty()) geometry.append(" ");
434 geometry.append(args.GetExtraArgs());
435 }
436
437 exec = std::regex_replace(exec, std::regex("\\$geometry"), geometry);
438}
439
440
441//////////////////////////////////////////////////////////////////////////////////////////////////
442/// Handle profile argument
443
444std::string RWebDisplayHandle::ChromeCreator::MakeProfile(std::string &exec, bool)
445{
446 std::string rmdir, profile_arg;
447
448 if (exec.find("$profile") == std::string::npos)
449 return rmdir;
450
451 const char *chrome_profile = gEnv->GetValue((fEnvPrefix + "Profile").c_str(), "");
452 if (chrome_profile && *chrome_profile) {
453 profile_arg = chrome_profile;
454 } else {
455 gRandom->SetSeed(0);
456 std::string rnd_profile = "root_chrome_profile_"s + std::to_string(gRandom->Integer(0x100000));
457 profile_arg = gSystem->TempDirectory();
458
459#ifdef _MSC_VER
460 profile_arg += "\\"s + rnd_profile;
461#else
462 profile_arg += "/"s + rnd_profile;
463#endif
464 rmdir = profile_arg;
465 }
466
467 exec = std::regex_replace(exec, std::regex("\\$profile"), profile_arg);
468
469 return rmdir;
470}
471
472
473//////////////////////////////////////////////////////////////////////////////////////////////////
474/// Constructor
475
477{
478 TestProg(gEnv->GetValue("WebGui.Firefox", ""));
479
480#ifdef _MSC_VER
481 TestProg("\\Mozilla Firefox\\firefox.exe", true);
482#endif
483#ifdef R__MACOSX
484 TestProg("/Applications/Firefox.app/Contents/MacOS/firefox");
485#endif
486#ifdef R__LINUX
487 TestProg("/usr/bin/firefox");
488 TestProg("/usr/bin/firefox-bin");
489#endif
490
491#ifdef _MSC_VER
492 // there is a problem when specifying the window size with wmic on windows:
493 // It gives: Invalid format. Hint: <paramlist> = <param> [, <paramlist>].
494 fBatchExec = gEnv->GetValue("WebGui.FirefoxBatch", "$prog -headless -no-remote $profile $url");
495 fHeadlessExec = gEnv->GetValue("WebGui.FirefoxHeadless", "$prog -headless -no-remote $profile $url &");
496 fExec = gEnv->GetValue("WebGui.FirefoxInteractive", "$prog -no-remote $profile $url &");
497#else
498 fBatchExec = gEnv->GetValue("WebGui.FirefoxBatch", "$prog --headless --private-window --no-remote $profile $url");
499 fHeadlessExec = gEnv->GetValue("WebGui.FirefoxHeadless", "fork:--headless --private-window --no-remote $profile $url");
500 fExec = gEnv->GetValue("WebGui.FirefoxInteractive", "$prog --private-window \'$url\' &");
501#endif
502}
503
504//////////////////////////////////////////////////////////////////////////////////////////////////
505/// Create Firefox profile to run independent browser window
506
507std::string RWebDisplayHandle::FirefoxCreator::MakeProfile(std::string &exec, bool batch_mode)
508{
509 std::string rmdir, profile_arg;
510
511 if (exec.find("$profile") == std::string::npos)
512 return rmdir;
513
514 const char *ff_profile = gEnv->GetValue("WebGui.FirefoxProfile", "");
515 const char *ff_profilepath = gEnv->GetValue("WebGui.FirefoxProfilePath", "");
516 Int_t ff_randomprofile = gEnv->GetValue("WebGui.FirefoxRandomProfile", (Int_t) 0);
517 if (ff_profile && *ff_profile) {
518 profile_arg = "-P "s + ff_profile;
519 } else if (ff_profilepath && *ff_profilepath) {
520 profile_arg = "-profile "s + ff_profilepath;
521 } else if ((ff_randomprofile > 0) || (batch_mode && (ff_randomprofile >= 0))) {
522
523 gRandom->SetSeed(0);
524 std::string rnd_profile = "root_ff_profile_"s + std::to_string(gRandom->Integer(0x100000));
525 std::string profile_dir = gSystem->TempDirectory();
526
527#ifdef _MSC_VER
528 profile_dir += "\\"s + rnd_profile;
529#else
530 profile_dir += "/"s + rnd_profile;
531#endif
532
533 profile_arg = "-profile "s + profile_dir;
534
535 if (gSystem->mkdir(profile_dir.c_str()) == 0) {
536 rmdir = profile_dir;
537
538 if (batch_mode) {
539 std::ofstream user_js(profile_dir + "/user.js", std::ios::trunc);
540 user_js << "user_pref(\"browser.dom.window.dump.enabled\", true);" << std::endl;
541 // workaround for current Firefox, without such settings it fail to close window and terminate it from batch
542 user_js << "user_pref(\"datareporting.policy.dataSubmissionPolicyAcceptedVersion\", 2);" << std::endl;
543 user_js << "user_pref(\"datareporting.policy.dataSubmissionPolicyNotifiedTime\", \"1635760572813\");" << std::endl;
544 }
545
546 } else {
547 R__LOG_ERROR(WebGUILog()) << "Cannot create Firefox profile directory " << profile_dir;
548 }
549 }
550
551 exec = std::regex_replace(exec, std::regex("\\$profile"), profile_arg);
552
553 return rmdir;
554}
555
556///////////////////////////////////////////////////////////////////////////////////////////////////
557/// Create web display
558/// \param args - defines where and how to display web window
559/// Returns RWebDisplayHandle, which holds information of running browser application
560/// Can be used fully independent from RWebWindow classes just to show any web page
561
562std::unique_ptr<RWebDisplayHandle> RWebDisplayHandle::Display(const RWebDisplayArgs &args)
563{
564 std::unique_ptr<RWebDisplayHandle> handle;
565
567 return handle;
568
569 auto try_creator = [&](std::unique_ptr<Creator> &creator) {
570 if (!creator || !creator->IsActive())
571 return false;
572 handle = creator->Display(args);
573 return handle ? true : false;
574 };
575
577 if (try_creator(FindCreator("cef", "libROOTCefDisplay")))
578 return handle;
579 }
580
582 if (try_creator(FindCreator("qt5", "libROOTQt5WebDisplay")))
583 return handle;
584 }
585
587 if (try_creator(FindCreator("qt6", "libROOTQt6WebDisplay")))
588 return handle;
589 }
590
591 if (args.IsLocalDisplay()) {
592 R__LOG_ERROR(WebGUILog()) << "Neither Qt5/6 nor CEF libraries were found to provide local display";
593 return handle;
594 }
595
596 bool handleAsNative = (args.GetBrowserKind() == RWebDisplayArgs::kNative) ||
598
599#ifdef _MSC_VER
600 if (handleAsNative || (args.GetBrowserKind() == RWebDisplayArgs::kEdge)) {
601 if (try_creator(FindCreator("edge", "ChromeCreator")))
602 return handle;
603 }
604#endif
605
606 if (handleAsNative || (args.GetBrowserKind() == RWebDisplayArgs::kChrome)) {
607 if (try_creator(FindCreator("chrome", "ChromeCreator")))
608 return handle;
609 }
610
611 if (handleAsNative || (args.GetBrowserKind() == RWebDisplayArgs::kFirefox)) {
612 if (try_creator(FindCreator("firefox", "FirefoxCreator")))
613 return handle;
614 }
615
616 if (handleAsNative || (args.GetBrowserKind() == RWebDisplayArgs::kChrome) || (args.GetBrowserKind() == RWebDisplayArgs::kFirefox) || (args.GetBrowserKind() == RWebDisplayArgs::kEdge)) {
617 // R__LOG_ERROR(WebGUILog()) << "Neither Chrome nor Firefox browser cannot be started to provide display";
618 return handle;
619 }
620
622 std::unique_ptr<Creator> creator = std::make_unique<BrowserCreator>(false, args.GetCustomExec());
623 try_creator(creator);
624 } else {
625 try_creator(FindCreator("browser", "BrowserCreator"));
626 }
627
628 return handle;
629}
630
631///////////////////////////////////////////////////////////////////////////////////////////////////
632/// Display provided url in configured web browser
633/// \param url - specified URL address like https://root.cern
634/// Browser can specified when starting `root --web=firefox`
635/// Returns true when browser started
636/// It is convenience method, equivalent to:
637/// ~~~
638/// RWebDisplayArgs args;
639/// args.SetUrl(url);
640/// args.SetStandalone(false);
641/// auto handle = RWebDisplayHandle::Display(args);
642/// ~~~
643
644bool RWebDisplayHandle::DisplayUrl(const std::string &url)
645{
646 RWebDisplayArgs args;
647 args.SetUrl(url);
648 args.SetStandalone(false);
649
650 auto handle = Display(args);
651
652 return !!handle;
653}
654
655
656///////////////////////////////////////////////////////////////////////////////////////////////////
657/// Produce image file using JSON data as source
658/// Invokes JSROOT drawing functionality in headless browser - Google Chrome or Mozilla Firefox
659
660bool RWebDisplayHandle::ProduceImage(const std::string &fname, const std::string &json, int width, int height, const char *batch_file)
661{
662 if (json.empty())
663 return false;
664
665 std::string _fname = fname;
666 std::transform(_fname.begin(), _fname.end(), _fname.begin(), ::tolower);
667
668 auto EndsWith = [_fname](const std::string &suffix) {
669 return (_fname.length() > suffix.length()) ? (0 == _fname.compare (_fname.length() - suffix.length(), suffix.length(), suffix)) : false;
670 };
671
672 if (EndsWith(".json")) {
673 std::ofstream ofs(fname);
674 ofs << json;
675 return true;
676 }
677
678 const char *jsrootsys = gSystem->Getenv("JSROOTSYS");
679 TString jsrootsysdflt;
680 if (!jsrootsys) {
681 jsrootsysdflt = TROOT::GetDataDir() + "/js";
682 if (gSystem->ExpandPathName(jsrootsysdflt)) {
683 R__LOG_ERROR(WebGUILog()) << "Fail to locate JSROOT " << jsrootsysdflt;
684 return false;
685 }
686 jsrootsys = jsrootsysdflt.Data();
687 }
688
689 RWebDisplayArgs args; // set default browser kind, only Chrome or Firefox or CEF or Qt5 can be used here
693
694 std::string draw_kind;
695
696 if (EndsWith(".pdf"))
697 draw_kind = "draw"; // not a JSROOT drawing but Chrome capability to create PDF out of HTML page is used
698 else if (EndsWith("shot.png"))
699 draw_kind = (args.GetBrowserKind() == RWebDisplayArgs::kChrome) ? "draw" : "png";
700 else if (EndsWith(".svg"))
701 draw_kind = "svg";
702 else if (EndsWith(".png"))
703 draw_kind = "png";
704 else if (EndsWith(".jpg") || EndsWith(".jpeg"))
705 draw_kind = "jpeg";
706 else if (EndsWith(".webp"))
707 draw_kind = "webp";
708 else
709 return false;
710
711 if (!batch_file || !*batch_file)
712 batch_file = "/js/files/canv_batch.htm";
713
714 TString origin = TROOT::GetDataDir() + batch_file;
715 if (gSystem->ExpandPathName(origin)) {
716 R__LOG_ERROR(WebGUILog()) << "Fail to find " << origin;
717 return false;
718 }
719
720 auto filecont = THttpServer::ReadFileContent(origin.Data());
721 if (filecont.empty()) {
722 R__LOG_ERROR(WebGUILog()) << "Fail to read content of " << origin;
723 return false;
724 }
725
726 filecont = std::regex_replace(filecont, std::regex("\\$draw_width"), std::to_string(width));
727 filecont = std::regex_replace(filecont, std::regex("\\$draw_height"), std::to_string(height));
728
729 if (strstr(jsrootsys,"http://") || strstr(jsrootsys,"https://") || strstr(jsrootsys,"file://"))
730 filecont = std::regex_replace(filecont, std::regex("\\$jsrootsys"), jsrootsys);
731 else
732 filecont = std::regex_replace(filecont, std::regex("\\$jsrootsys"), "file://"s + jsrootsys);
733
734 filecont = std::regex_replace(filecont, std::regex("\\$draw_kind"), draw_kind);
735
736 filecont = std::regex_replace(filecont, std::regex("\\$draw_object"), json);
737
738 TString dump_name;
739 if (draw_kind == "draw") {
741 R__LOG_ERROR(WebGUILog()) << "Creation of PDF files supported only by Chrome browser";
742 return false;
743 }
745 dump_name = "canvasdump";
746 FILE *df = gSystem->TempFileName(dump_name);
747 if (!df) {
748 R__LOG_ERROR(WebGUILog()) << "Fail to create temporary file for dump-dom";
749 return false;
750 }
751 fputs("placeholder", df);
752 fclose(df);
753 }
754
755 // When true, place HTML file into home directory
756 // Some Chrome installation do not allow run html code from files, created in /tmp directory
757 static bool chrome_tmp_workaround = false;
758
759 TString tmp_name, html_name;
760
761try_again:
762
764 args.SetUrl(""s);
765 args.SetPageContent(filecont);
766
767 tmp_name.Clear();
768 html_name.Clear();
769
770 R__LOG_DEBUG(0, WebGUILog()) << "Using file content_len " << filecont.length() << " to produce batch image " << fname;
771
772 } else {
773 tmp_name = "canvasbody";
774 FILE *hf = gSystem->TempFileName(tmp_name);
775 if (!hf) {
776 R__LOG_ERROR(WebGUILog()) << "Fail to create temporary file for batch job";
777 return false;
778 }
779 fputs(filecont.c_str(), hf);
780 fclose(hf);
781
782 html_name = tmp_name + ".html";
783
784 if (chrome_tmp_workaround) {
785 std::string homedir = gSystem->GetHomeDirectory();
786 auto pos = html_name.Last('/');
787 if (pos == kNPOS)
788 html_name = TString::Format("/random%d.html", gRandom->Integer(1000000));
789 else
790 html_name.Remove(0, pos);
791 html_name = homedir + html_name.Data();
792 gSystem->Unlink(html_name.Data());
793 gSystem->Unlink(tmp_name.Data());
794
795 std::ofstream ofs(html_name.Data(), std::ofstream::out);
796 ofs << filecont;
797 } else {
798 if (gSystem->Rename(tmp_name.Data(), html_name.Data()) != 0) {
799 R__LOG_ERROR(WebGUILog()) << "Fail to rename temp file " << tmp_name << " into " << html_name;
800 gSystem->Unlink(tmp_name.Data());
801 return false;
802 }
803 }
804
805 args.SetUrl("file://"s + gSystem->UnixPathName(html_name.Data()));
806 args.SetPageContent(""s);
807
808 R__LOG_DEBUG(0, WebGUILog()) << "Using " << html_name << " content_len " << filecont.length() << " to produce batch image " << fname;
809 }
810
811 TString tgtfilename = fname.c_str();
812 if (!gSystem->IsAbsoluteFileName(tgtfilename.Data()))
814
815 TString wait_file_name;
816
817 args.SetStandalone(true);
818 args.SetHeadless(true);
819 args.SetBatchMode(true);
820 args.SetSize(width, height);
821
822 if (draw_kind == "draw") {
823
824 wait_file_name = tgtfilename;
825
826 if (EndsWith(".pdf"))
827 args.SetExtraArgs("--print-to-pdf="s + gSystem->UnixPathName(tgtfilename.Data()));
828 else
829 args.SetExtraArgs("--screenshot="s + gSystem->UnixPathName(tgtfilename.Data()));
830
831 } else if (args.GetBrowserKind() == RWebDisplayArgs::kFirefox) {
832 // firefox will use window.dump to output produced result
833 args.SetRedirectOutput(dump_name.Data());
834 gSystem->Unlink(dump_name.Data());
835 } else if (args.GetBrowserKind() == RWebDisplayArgs::kChrome) {
836 // require temporary output file
837 args.SetExtraArgs("--dump-dom");
838 args.SetRedirectOutput(dump_name.Data());
839
840 // wait_file_name = dump_name;
841
842 gSystem->Unlink(dump_name.Data());
843 }
844
845 // remove target image file - we use it as detection when chrome is ready
846 gSystem->Unlink(tgtfilename.Data());
847
848 auto handle = RWebDisplayHandle::Display(args);
849
850 if (!handle) {
851 R__LOG_DEBUG(0, WebGUILog()) << "Cannot start " << args.GetBrowserName() << " to produce image " << fname;
852 return false;
853 }
854
855 // delete temporary HTML file
856 if (html_name.Length() > 0)
857 gSystem->Unlink(html_name.Data());
858
859 if (!wait_file_name.IsNull() && gSystem->AccessPathName(wait_file_name.Data())) {
860 R__LOG_ERROR(WebGUILog()) << "Fail to produce image " << fname;
861 return false;
862 }
863
864 if (draw_kind != "draw") {
865
866 auto dumpcont = handle->GetContent();
867
868 if ((dumpcont.length() > 20) && (dumpcont.length() < 60) && !chrome_tmp_workaround && (args.GetBrowserKind() == RWebDisplayArgs::kChrome)) {
869 // chrome creates dummy html file with mostly no content
870 // problem running chrome from /tmp directory, lets try work from home directory
871 chrome_tmp_workaround = true;
872 goto try_again;
873 }
874
875 if (dumpcont.length() < 100) {
876 R__LOG_ERROR(WebGUILog()) << "Fail to dump HTML code into " << (dump_name.IsNull() ? "CEF" : dump_name.Data());
877 return false;
878 }
879
880 if (draw_kind == "svg") {
881 auto p1 = dumpcont.find("<svg");
882 auto p2 = dumpcont.rfind("</svg>");
883
884 std::ofstream ofs(tgtfilename);
885 if ((p1 != std::string::npos) && (p2 != std::string::npos) && (p1 < p2)) {
886 ofs << dumpcont.substr(p1,p2-p1+6);
887 } else {
888 R__LOG_ERROR(WebGUILog()) << "Fail to extract SVG from HTML dump " << dump_name;
889 ofs << "Failure!!!\n" << dumpcont;
890 return false;
891 }
892 } else {
893
894 auto p1 = dumpcont.rfind(";base64,");
895 auto p2 = dumpcont.rfind("></div>");
896
897 if ((p1 != std::string::npos) && (p2 != std::string::npos) && (p1 < p2)) {
898
899 auto base64 = dumpcont.substr(p1+8, p2-p1-9);
900 auto binary = TBase64::Decode(base64.c_str());
901
902 std::ofstream ofs(tgtfilename, std::ios::binary);
903 ofs.write(binary.Data(), binary.Length());
904 } else {
905 R__LOG_ERROR(WebGUILog()) << "Fail to extract image from dump HTML code " << dump_name;
906
907 return false;
908 }
909 }
910 }
911
912 R__LOG_DEBUG(0, WebGUILog()) << "Create file " << fname;
913
914 return true;
915}
916
nlohmann::json json
#define R__LOG_ERROR(...)
Definition RLogger.hxx:362
#define R__LOG_DEBUG(DEBUGLEVEL,...)
Definition RLogger.hxx:365
#define c(i)
Definition RSha256.hxx:101
constexpr 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, const char *batch_file=nullptr)
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:525
static const TString & GetDataDir()
Get the data directory in the installation. Static utility function.
Definition TROOT.cxx:2994
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:139
Ssiz_t Length() const
Definition TString.h:421
void Clear()
Clear string without changing its capacity.
Definition TString.cxx:1221
const char * Data() const
Definition TString.h:380
Ssiz_t Last(char c) const
Find last occurrence of a character c.
Definition TString.cxx:924
TObjArray * Tokenize(const TString &delim) const
This function is used to isolate sequential tokens in a TString.
Definition TString.cxx:2242
Bool_t IsNull() const
Definition TString.h:418
TString & Remove(Ssiz_t pos)
Definition TString.h:685
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:2356
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
const Int_t n
Definition legend1.C:16
RLogChannel & WebGUILog()
Log channel for WebGUI diagnostics.
This file contains a specialised ROOT message handler to test for diagnostic in unit tests.
TMarker m
Definition textangle.C:8