Logo ROOT  
Reference Guide
RWebDisplayHandle.cxx
Go to the documentation of this file.
1/// \file RWebDisplayHandle.cxx
2/// \ingroup WebGui ROOT7
3/// \author Sergey Linev <s.linev@gsi.de>
4/// \date 2018-10-17
5/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback
6/// is welcome!
7
8/*************************************************************************
9 * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers. *
10 * All rights reserved. *
11 * *
12 * For the licensing terms see $ROOTSYS/LICENSE. *
13 * For the list of contributors see $ROOTSYS/README/CREDITS. *
14 *************************************************************************/
15
17
18#include <ROOT/RMakeUnique.hxx>
19#include <ROOT/RLogger.hxx>
20
21#include "RConfigure.h"
22#include "TSystem.h"
23#include "TRandom.h"
24#include "TString.h"
25#include "TObjArray.h"
26#include "THttpServer.h"
27#include "TEnv.h"
28#include "TROOT.h"
29#include "TBase64.h"
30
31#include <regex>
32#include <fstream>
33
34#ifdef _MSC_VER
35#include <process.h>
36#else
37#include <unistd.h>
38#include <stdlib.h>
39#include <signal.h>
40#include <spawn.h>
41#endif
42
43using namespace std::string_literals;
44
45//////////////////////////////////////////////////////////////////////////////////////////////////
46/// Static holder of registered creators of web displays
47
48std::map<std::string, std::unique_ptr<ROOT::Experimental::RWebDisplayHandle::Creator>> &ROOT::Experimental::RWebDisplayHandle::GetMap()
49{
50 static std::map<std::string, std::unique_ptr<ROOT::Experimental::RWebDisplayHandle::Creator>> sMap;
51 return sMap;
52}
53
54//////////////////////////////////////////////////////////////////////////////////////////////////
55/// Search for specific browser creator
56/// If not found, try to add one
57/// \param name - creator name like ChromeCreator
58/// \param libname - shared library name where creator could be provided
59
60std::unique_ptr<ROOT::Experimental::RWebDisplayHandle::Creator> &ROOT::Experimental::RWebDisplayHandle::FindCreator(const std::string &name, const std::string &libname)
61{
62 auto &m = GetMap();
63 auto search = m.find(name);
64 if (search == m.end()) {
65
66 if (libname == "ChromeCreator") {
67 m.emplace(name, std::make_unique<ChromeCreator>());
68 } else if (libname == "FirefoxCreator") {
69 m.emplace(name, std::make_unique<FirefoxCreator>());
70 } else if (libname == "BrowserCreator") {
71 m.emplace(name, std::make_unique<BrowserCreator>(false));
72 } else if (!libname.empty()) {
73 gSystem->Load(libname.c_str());
74 }
75
76 search = m.find(name); // try again
77 }
78
79 if (search != m.end())
80 return search->second;
81
82 static std::unique_ptr<ROOT::Experimental::RWebDisplayHandle::Creator> dummy;
83 return dummy;
84}
85
86namespace ROOT {
87namespace Experimental {
88
89//////////////////////////////////////////////////////////////////////////////////////////////////
90/// Specialized handle to hold information about running browser process
91/// Used to correctly cleanup all processes and temporary directories
92
93class RWebBrowserHandle : public RWebDisplayHandle {
94
95#ifdef _MSC_VER
96 typedef int browser_process_id;
97#else
98 typedef pid_t browser_process_id;
99#endif
100 std::string fTmpDir;
101 bool fHasPid{false};
102 browser_process_id fPid;
103
104public:
105 RWebBrowserHandle(const std::string &url, const std::string &tmpdir) : RWebDisplayHandle(url), fTmpDir(tmpdir) {}
106
107 RWebBrowserHandle(const std::string &url, const std::string &tmpdir, browser_process_id pid)
108 : RWebDisplayHandle(url), fTmpDir(tmpdir), fHasPid(true), fPid(pid)
109 {
110 }
111
112 virtual ~RWebBrowserHandle()
113 {
114#ifdef _MSC_VER
115 if (fHasPid)
116 gSystem->Exec(("taskkill /F /PID "s + std::to_string(fPid) + " >NUL 2>NUL").c_str());
117 std::string rmdir = "rmdir /S /Q ";
118#else
119 if (fHasPid)
120 kill(fPid, SIGKILL);
121 std::string rmdir = "rm -rf ";
122#endif
123 if (!fTmpDir.empty())
124 gSystem->Exec((rmdir + fTmpDir).c_str());
125 }
126};
127
128} // namespace Experimental
129} // namespace ROOT
130
131//////////////////////////////////////////////////////////////////////////////////////////////////
132/// Class to handle starting of web-browsers like Chrome or Firefox
133
135{
136 if (custom) return;
137
138 if (!exec.empty()) {
139 if (exec.find("$url") == std::string::npos) {
140 fProg = exec;
141#ifdef _MSC_VER
142 fExec = exec + " $url";
143#else
144 fExec = exec + " $url &";
145#endif
146 } else {
147 fExec = exec;
148 auto pos = exec.find(" ");
149 if (pos != std::string::npos)
150 fProg = exec.substr(0, pos);
151 }
152 } else if (gSystem->InheritsFrom("TMacOSXSystem")) {
153 fExec = "open \'$url\'";
154 } else if (gSystem->InheritsFrom("TWinNTSystem")) {
155 fExec = "start $url";
156 } else {
157 fExec = "xdg-open \'$url\' &";
158 }
159}
160
161//////////////////////////////////////////////////////////////////////////////////////////////////
162/// Check if browser executable exists and can be used
163
164void ROOT::Experimental::RWebDisplayHandle::BrowserCreator::TestProg(const std::string &nexttry, bool check_std_paths)
165{
166 if (nexttry.empty() || !fProg.empty())
167 return;
168
169 if (!gSystem->AccessPathName(nexttry.c_str(), kExecutePermission)) {
170#ifdef R__MACOSX
171 fProg = std::regex_replace(nexttry, std::regex("%20"), " ");
172#else
173 fProg = nexttry;
174#endif
175 return;
176 }
177
178 if (!check_std_paths)
179 return;
180
181#ifdef _MSC_VER
182 std::string ProgramFiles = gSystem->Getenv("ProgramFiles");
183 auto pos = ProgramFiles.find(" (x86)");
184 if (pos != std::string::npos)
185 ProgramFiles.erase(pos, 6);
186 std::string ProgramFilesx86 = gSystem->Getenv("ProgramFiles(x86)");
187
188 if (!ProgramFiles.empty())
189 TestProg(ProgramFiles + nexttry, false);
190 if (!ProgramFilesx86.empty())
191 TestProg(ProgramFilesx86 + nexttry, false);
192#endif
193}
194
195//////////////////////////////////////////////////////////////////////////////////////////////////
196/// Display given URL in web browser
197
198std::unique_ptr<ROOT::Experimental::RWebDisplayHandle>
200{
201 std::string url = args.GetFullUrl();
202 if (url.empty())
203 return nullptr;
204
205 std::string exec;
206 if (args.IsHeadless())
207 exec = fBatchExec;
208 else if (args.IsStandalone())
209 exec = fExec;
210 else
211 exec = "$prog $url &";
212
213 if (exec.empty())
214 return nullptr;
215
216 std::string swidth = std::to_string(args.GetWidth() > 0 ? args.GetWidth() : 800),
217 sheight = std::to_string(args.GetHeight() > 0 ? args.GetHeight() : 600),
218 sposx = std::to_string(args.GetX() >= 0 ? args.GetX() : 0),
219 sposy = std::to_string(args.GetY() >= 0 ? args.GetY() : 0);
220
221 ProcessGeometry(exec, args);
222
223 std::string rmdir = MakeProfile(exec, args.IsHeadless());
224
225 exec = std::regex_replace(exec, std::regex("\\$url"), url);
226 exec = std::regex_replace(exec, std::regex("\\$width"), swidth);
227 exec = std::regex_replace(exec, std::regex("\\$height"), sheight);
228 exec = std::regex_replace(exec, std::regex("\\$posx"), sposx);
229 exec = std::regex_replace(exec, std::regex("\\$posy"), sposy);
230
231 if (exec.compare(0,5,"fork:") == 0) {
232 if (fProg.empty()) {
233 R__ERROR_HERE("WebDisplay") << "Fork instruction without executable";
234 return nullptr;
235 }
236
237 exec.erase(0, 5);
238
239#ifndef _MSC_VER
240
241 std::unique_ptr<TObjArray> fargs(TString(exec.c_str()).Tokenize(" "));
242 if (!fargs || (fargs->GetLast()<=0)) {
243 R__ERROR_HERE("WebDisplay") << "Fork instruction is empty";
244 return nullptr;
245 }
246
247 std::vector<char *> argv;
248 argv.push_back((char *) fProg.c_str());
249 for (Int_t n = 0; n <= fargs->GetLast(); ++n)
250 argv.push_back((char *)fargs->At(n)->GetName());
251 argv.push_back(nullptr);
252
253 R__DEBUG_HERE("WebDisplay") << "Show web window in browser with posix_spawn:\n" << fProg << " " << exec;
254
255 pid_t pid;
256 int status = posix_spawn(&pid, argv[0], nullptr, nullptr, argv.data(), nullptr);
257 if (status != 0) {
258 R__ERROR_HERE("WebDisplay") << "Fail to launch " << argv[0];
259 return nullptr;
260 }
261
262 // add processid and rm dir
263
264 return std::make_unique<RWebBrowserHandle>(url, rmdir, pid);
265
266#else
267
268 if (fProg.empty()) {
269 R__ERROR_HERE("WebDisplay") << "No Web browser found";
270 return nullptr;
271 }
272
273 // use UnixPathName to simplify handling of backslashes
274 exec = "wmic process call create '"s + gSystem->UnixPathName(fProg.c_str()) + exec + "' | find \"ProcessId\" "s;
275 std::string process_id = gSystem->GetFromPipe(exec.c_str());
276 std::stringstream ss(process_id);
277 std::string tmp;
278 char c;
279 int pid = 0;
280 ss >> tmp >> c >> pid;
281
282 if (pid <= 0) {
283 R__ERROR_HERE("WebDisplay") << "Fail to launch " << fProg;
284 return nullptr;
285 }
286
287 // add processid and rm dir
288 return std::make_unique<RWebBrowserHandle>(url, rmdir, pid);
289#endif
290 }
291
292#ifdef _MSC_VER
293
294 if (exec.rfind("&") == exec.length() - 1) {
295
296 // if last symbol is &, use _spawn to detach execution
297 exec.resize(exec.length() - 1);
298
299 std::vector<char *> argv;
300 std::string firstarg = fProg;
301 auto slashpos = firstarg.find_last_of("/\\");
302 if (slashpos != std::string::npos)
303 firstarg.erase(0, slashpos + 1);
304 argv.push_back((char *)firstarg.c_str());
305
306 std::unique_ptr<TObjArray> fargs(TString(exec.c_str()).Tokenize(" "));
307 for (Int_t n = 1; n <= fargs->GetLast(); ++n)
308 argv.push_back((char *)fargs->At(n)->GetName());
309 argv.push_back(nullptr);
310
311 R__DEBUG_HERE("WebDisplay") << "Showing web window in " << fProg << " with:\n" << exec;
312
313 _spawnv(_P_NOWAIT, fProg.c_str(), argv.data());
314
315 return std::make_unique<RWebBrowserHandle>(url, rmdir);
316 }
317
318 std::string prog = "\""s + gSystem->UnixPathName(fProg.c_str()) + "\""s;
319
320#else
321
322#ifdef R__MACOSX
323 std::string prog = std::regex_replace(fProg, std::regex(" "), "\\ ");
324#else
325 std::string prog = fProg;
326#endif
327
328#endif
329
330 exec = std::regex_replace(exec, std::regex("\\$prog"), prog);
331
332 if (!args.GetRedirectOutput().empty()) {
333 auto p = exec.length();
334 if (exec.rfind("&") == p-1) --p;
335 exec.insert(p, " >"s + args.GetRedirectOutput() + " "s);
336 }
337
338 R__DEBUG_HERE("WebDisplay") << "Showing web window in browser with:\n" << exec;
339
340 gSystem->Exec(exec.c_str());
341
342 // add rmdir if required
343 return std::make_unique<RWebBrowserHandle>(url, rmdir);
344}
345
346//////////////////////////////////////////////////////////////////////////////////////////////////
347/// Constructor
348
350{
351 TestProg(gEnv->GetValue("WebGui.Chrome", ""));
352
353#ifdef _MSC_VER
354 TestProg("\\Google\\Chrome\\Application\\chrome.exe", true);
355#endif
356#ifdef R__MACOSX
357 TestProg("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome");
358#endif
359#ifdef R__LINUX
360 TestProg("/usr/bin/chromium");
361 TestProg("/usr/bin/chromium-browser");
362 TestProg("/usr/bin/chrome-browser");
363#endif
364
365#ifdef _MSC_VER
366 // fBatchExec = gEnv->GetValue("WebGui.ChromeBatch", "fork: --headless --disable-gpu $geometry $url");
367
368 fBatchExec = gEnv->GetValue("WebGui.ChromeBatch", "$prog --headless $geometry $url");
369 fExec = gEnv->GetValue("WebGui.ChromeInteractive", "$prog $geometry --no-first-run --app=$url &"); // & in windows mean usage of spawn
370#else
371 fBatchExec = gEnv->GetValue("WebGui.ChromeBatch", "$prog --headless --incognito $geometry $url");
372 fExec = gEnv->GetValue("WebGui.ChromeInteractive", "$prog $geometry --no-first-run --incognito --app=\'$url\' &");
373#endif
374}
375
376
377//////////////////////////////////////////////////////////////////////////////////////////////////
378/// Replace $geometry placeholder with geometry settings
379/// Also RWebDisplayArgs::GetExtraArgs() are appended
380
382{
383 std::string geometry;
384 if ((args.GetWidth() > 0) && (args.GetHeight() > 0))
385 geometry = "--window-size="s + std::to_string(args.GetWidth())
386 + (args.IsHeadless() ? "x"s : ","s)
387 + std::to_string(args.GetHeight());
388
389 if (((args.GetX() >= 0) || (args.GetY() >= 0)) && !args.IsHeadless()) {
390 if (!geometry.empty()) geometry.append(" ");
391 geometry.append("--window-position="s + std::to_string(args.GetX() >= 0 ? args.GetX() : 0) + ","s +
392 std::to_string(args.GetY() >= 0 ? args.GetY() : 0));
393 }
394
395 if (!args.GetExtraArgs().empty()) {
396 if (!geometry.empty()) geometry.append(" ");
397 geometry.append(args.GetExtraArgs());
398 }
399
400 exec = std::regex_replace(exec, std::regex("\\$geometry"), geometry);
401}
402
403
404//////////////////////////////////////////////////////////////////////////////////////////////////
405/// Handle profile argument
406
408{
409 std::string rmdir, profile_arg;
410
411 if (exec.find("$profile") == std::string::npos)
412 return rmdir;
413
414 const char *chrome_profile = gEnv->GetValue("WebGui.ChromeProfile", "");
415 if (chrome_profile && *chrome_profile) {
416 profile_arg = chrome_profile;
417 } else {
418 gRandom->SetSeed(0);
419 rmdir = profile_arg = std::string(gSystem->TempDirectory()) + "/root_chrome_profile_"s + std::to_string(gRandom->Integer(0x100000));
420 }
421
422 exec = std::regex_replace(exec, std::regex("\\$profile"), profile_arg);
423
424 return rmdir;
425}
426
427
428//////////////////////////////////////////////////////////////////////////////////////////////////
429/// Constructor
430
432{
433 TestProg(gEnv->GetValue("WebGui.Firefox", ""));
434
435#ifdef _MSC_VER
436 TestProg("\\Mozilla Firefox\\firefox.exe", true);
437#endif
438#ifdef R__MACOSX
439 TestProg("/Applications/Firefox.app/Contents/MacOS/firefox");
440#endif
441#ifdef R__LINUX
442 TestProg("/usr/bin/firefox");
443#endif
444
445#ifdef _MSC_VER
446 // there is a problem when specifying the window size with wmic on windows:
447 // It gives: Invalid format. Hint: <paramlist> = <param> [, <paramlist>].
448 // fBatchExec = gEnv->GetValue("WebGui.FirefoxBatch", "fork: -headless -no-remote $profile $url");
449 fExec = gEnv->GetValue("WebGui.FirefoxInteractive", "$prog -no-remote $profile $url &");
450#else
451 // fBatchExec = gEnv->GetValue("WebGui.FirefoxBatch", "fork:--headless --private-window --no-remote $profile $url");
452 fExec = gEnv->GetValue("WebGui.FirefoxInteractive", "$prog --private-window \'$url\' &");
453#endif
454}
455
456//////////////////////////////////////////////////////////////////////////////////////////////////
457/// Create Firefox profile to run independent browser window
458
459std::string ROOT::Experimental::RWebDisplayHandle::FirefoxCreator::MakeProfile(std::string &exec, bool batch_mode)
460{
461 std::string rmdir, profile_arg;
462
463 if (exec.find("$profile") == std::string::npos)
464 return rmdir;
465
466 const char *ff_profile = gEnv->GetValue("WebGui.FirefoxProfile", "");
467 const char *ff_profilepath = gEnv->GetValue("WebGui.FirefoxProfilePath", "");
468 Int_t ff_randomprofile = gEnv->GetValue("WebGui.FirefoxRandomProfile", (Int_t) 0);
469 if (ff_profile && *ff_profile) {
470 profile_arg = "-P "s + ff_profile;
471 } else if (ff_profilepath && *ff_profilepath) {
472 profile_arg = "-profile "s + ff_profilepath;
473 } else if ((ff_randomprofile > 0) || (batch_mode && (ff_randomprofile >= 0))) {
474
475 gRandom->SetSeed(0);
476 std::string rnd_profile = "root_ff_profile_"s + std::to_string(gRandom->Integer(0x100000));
477 std::string profile_dir = std::string(gSystem->TempDirectory()) + "/"s + rnd_profile;
478
479 profile_arg = "-profile "s + profile_dir;
480
481 if (gSystem->mkdir(profile_dir.c_str()) == 0) {
482 rmdir = profile_dir;
483 } else {
484 R__ERROR_HERE("WebDisplay") << "Cannot create Firefox profile directory " << profile_dir;
485 }
486 }
487
488 exec = std::regex_replace(exec, std::regex("\\$profile"), profile_arg);
489
490 return rmdir;
491}
492
493
494///////////////////////////////////////////////////////////////////////////////////////////////////
495/// Create web display
496/// \param args - defines where and how to display web window
497/// Returns RWebDisplayHandle, which holds information of running browser application
498/// Can be used fully independent from RWebWindow classes just to show any web page
499
500std::unique_ptr<ROOT::Experimental::RWebDisplayHandle> ROOT::Experimental::RWebDisplayHandle::Display(const RWebDisplayArgs &args)
501{
502 std::unique_ptr<RWebDisplayHandle> handle;
503
504 auto try_creator = [&](std::unique_ptr<Creator> &creator) {
505 if (!creator || !creator->IsActive())
506 return false;
507 handle = creator->Display(args);
508 return handle ? true : false;
509 };
510
512 if (try_creator(FindCreator("cef", "libROOTCefDisplay")))
513 return handle;
514 }
515
517 if (try_creator(FindCreator("qt5", "libROOTQt5WebDisplay")))
518 return handle;
519 }
520
521 if (args.IsLocalDisplay()) {
522 R__ERROR_HERE("WebDisplay") << "Neither Qt5 nor CEF libraries were found to provide local display";
523 return handle;
524 }
525
527 if (try_creator(FindCreator("chrome", "ChromeCreator")))
528 return handle;
529 }
530
532 if (try_creator(FindCreator("firefox", "FirefoxCreator")))
533 return handle;
534 }
535
537 // R__ERROR_HERE("WebDisplay") << "Neither Chrome nor Firefox browser cannot be started to provide display";
538 return handle;
539 }
540
542 std::unique_ptr<Creator> creator = std::make_unique<BrowserCreator>(false, args.GetCustomExec());
543 try_creator(creator);
544 } else {
545 try_creator(FindCreator("browser", "BrowserCreator"));
546 }
547
548 return handle;
549}
550
551///////////////////////////////////////////////////////////////////////////////////////////////////
552/// Display provided url in configured web browser
553/// \param url - specified URL address like https://root.cern
554/// Browser can specified when starting `root --web=firefox`
555/// Returns true when browser started
556/// It is convenience method, equivalent to:
557/// ~~~
558/// RWebDisplayArgs args;
559/// args.SetUrl(url);
560/// args.SetStandalone(false);
561/// auto handle = RWebDisplayHandle::Display(args);
562/// ~~~
563
565{
566 RWebDisplayArgs args;
567 args.SetUrl(url);
568 args.SetStandalone(false);
569
570 auto handle = Display(args);
571
572 return !!handle;
573}
574
575
576///////////////////////////////////////////////////////////////////////////////////////////////////
577/// Produce image file using JSON data as source
578/// Invokes JSROOT drawing functionality in headless browser - Google Chrome
579
580bool ROOT::Experimental::RWebDisplayHandle::ProduceImage(const std::string &fname, const std::string &json, int width, int height)
581{
582 if (json.empty())
583 return false;
584
585 std::string _fname = fname;
586 std::transform(_fname.begin(), _fname.end(), _fname.begin(), ::tolower);
587
588 auto EndsWith = [_fname](const std::string &suffix) {
589 return (_fname.length() > suffix.length()) ? (0 == _fname.compare (_fname.length() - suffix.length(), suffix.length(), suffix)) : false;
590 };
591
592 if (EndsWith(".json")) {
593 std::ofstream ofs(fname);
594 ofs << json;
595 return true;
596 }
597
598 const char *jsrootsys = gSystem->Getenv("JSROOTSYS");
599 TString jsrootsysdflt;
600 if (!jsrootsys) {
601 jsrootsysdflt = TROOT::GetDataDir() + "/js";
602 if (gSystem->ExpandPathName(jsrootsysdflt)) {
603 R__ERROR_HERE("CanvasPainter") << "Fail to locate JSROOT " << jsrootsysdflt;
604 return false;
605 }
606 jsrootsys = jsrootsysdflt.Data();
607 }
608
609 std::string draw_kind;
610
611 if (EndsWith(".pdf") || EndsWith("shot.png"))
612 draw_kind = "draw";
613 else if (EndsWith(".svg"))
614 draw_kind = "svg";
615 else if (EndsWith(".png"))
616 draw_kind = "png";
617 else if (EndsWith(".jpg") || EndsWith(".jpeg"))
618 draw_kind = "jpeg";
619 else if (EndsWith(".webp"))
620 draw_kind = "webp";
621 else
622 return false;
623
624 TString origin = TROOT::GetDataDir() + "/js/files/canv_batch.htm";
625 if (gSystem->ExpandPathName(origin)) {
626 R__ERROR_HERE("CanvasPainter") << "Fail to find " << origin;
627 return false;
628 }
629
630 auto filecont = THttpServer::ReadFileContent(origin.Data());
631 if (filecont.empty()) {
632 R__ERROR_HERE("CanvasPainter") << "Fail to read content of " << origin;
633 return false;
634 }
635
636 filecont = std::regex_replace(filecont, std::regex("\\$draw_width"), std::to_string(width));
637 filecont = std::regex_replace(filecont, std::regex("\\$draw_height"), std::to_string(height));
638
639 if (strstr(jsrootsys,"http://") || strstr(jsrootsys,"https://") || strstr(jsrootsys,"file://"))
640 filecont = std::regex_replace(filecont, std::regex("\\$jsrootsys"), jsrootsys);
641 else
642 filecont = std::regex_replace(filecont, std::regex("\\$jsrootsys"), "file://"s + jsrootsys);
643
644 filecont = std::regex_replace(filecont, std::regex("\\$draw_kind"), draw_kind);
645
646 filecont = std::regex_replace(filecont, std::regex("\\$draw_object"), json);
647
648
649 TString dump_name;
650 if (draw_kind != "draw") {
651 dump_name = "canvasdump";
652 FILE *df = gSystem->TempFileName(dump_name);
653 if (!df) {
654 R__ERROR_HERE("CanvasPainter") << "Fail to create temporary file for dump-dom";
655 return false;
656 }
657 fputs("placeholder", df);
658 fclose(df);
659 }
660
661 // When true, place HTML file into home directory
662 // Some Crome installation do not allow run html code from files, created in /tmp directory
663 static bool chrome_tmp_workaround = false;
664
665try_again:
666
667 TString tmp_name("canvasbody");
668 FILE *hf = gSystem->TempFileName(tmp_name);
669 if (!hf) {
670 R__ERROR_HERE("CanvasPainter") << "Fail to create temporary file for batch job";
671 return false;
672 }
673 fputs(filecont.c_str(), hf);
674 fclose(hf);
675
676 TString html_name = tmp_name + ".html";
677
678 if (chrome_tmp_workaround) {
679 std::string homedir = gSystem->GetHomeDirectory();
680 auto pos = html_name.Last('/');
681 if (pos == kNPOS)
682 html_name = TString::Format("/random%d.html", gRandom->Integer(1000000));
683 else
684 html_name.Remove(0,pos);
685 html_name = homedir + html_name.Data();
686 gSystem->Unlink(html_name.Data());
687 gSystem->Unlink(tmp_name.Data());
688
689 std::ofstream ofs (html_name.Data(), std::ofstream::out);
690 ofs << filecont;
691 } else {
692 if (gSystem->Rename(tmp_name.Data(), html_name.Data()) != 0) {
693 R__ERROR_HERE("CanvasPainter") << "Fail to rename temp file " << tmp_name << " into " << html_name;
694 gSystem->Unlink(tmp_name.Data());
695 return false;
696 }
697 }
698
699 R__DEBUG_HERE("CanvasPainter") << "Using " << html_name << " content_len " << filecont.length() << " to produce batch image " << fname;
700
701 TString tgtfilename = fname.c_str();
702 if (!gSystem->IsAbsoluteFileName(tgtfilename.Data()))
704
705 TString wait_file_name;
706
709 args.SetStandalone(true);
710 args.SetHeadless(true);
711 args.SetSize(width, height);
712 args.SetUrl("file://"s + gSystem->UnixPathName(html_name.Data()));
713 if (draw_kind == "draw") {
714
715 wait_file_name = tgtfilename;
716
717 if (EndsWith(".pdf"))
718 args.SetExtraArgs("--print-to-pdf="s + gSystem->UnixPathName(tgtfilename.Data()));
719 else
720 args.SetExtraArgs("--screenshot="s + gSystem->UnixPathName(tgtfilename.Data()));
721
722 } else {
723 // require temporary output file
724 args.SetExtraArgs("--dump-dom");
725 args.SetRedirectOutput(dump_name.Data());
726
727 wait_file_name = dump_name;
728
729 gSystem->Unlink(dump_name.Data());
730 // printf("Redirect to %s\n", dump_name.Data());
731 }
732
733 // remove target image file - we use it as detection when chrome is ready
734 gSystem->Unlink(tgtfilename.Data());
735
737
738 if (!handle) {
739 R__DEBUG_HERE("CanvasPainter") << "Cannot start Chrome to produce image " << fname;
740 return false;
741 }
742
743 // delete temporary HTML file
744 gSystem->Unlink(html_name.Data());
745
746 if (gSystem->AccessPathName(wait_file_name.Data())) {
747 R__ERROR_HERE("CanvasPainter") << "Fail to produce image " << fname;
748 return false;
749 }
750
751 if (draw_kind != "draw") {
752
753 auto dumpcont = THttpServer::ReadFileContent(dump_name.Data());
754
755 gSystem->Unlink(dump_name.Data());
756
757 if ((dumpcont.length() > 20) && (dumpcont.length() < 60) && !chrome_tmp_workaround) {
758 // chrome creates dummy html file with mostly no content
759 // problem running chrome from /tmp directory, lets try work from home directory
760 chrome_tmp_workaround = true;
761 goto try_again;
762 }
763
764 if (dumpcont.length() < 100) {
765 R__ERROR_HERE("CanvasPainter") << "Fail to dump HTML code into " << dump_name;
766 return false;
767 }
768
769 if (draw_kind == "svg") {
770 auto p1 = dumpcont.find("<svg");
771 auto p2 = dumpcont.rfind("</svg>");
772
773 std::ofstream ofs(tgtfilename);
774 if ((p1 != std::string::npos) && (p2 != std::string::npos) && (p1 < p2)) {
775 ofs << dumpcont.substr(p1,p2-p1+6);
776 } else {
777 R__ERROR_HERE("CanvasPainter") << "Fail to extract SVG from HTML dump " << dump_name;
778 ofs << "Failure!!!\n" << dumpcont;
779 return false;
780 }
781 } else {
782
783 auto p1 = dumpcont.find(";base64,");
784 auto p2 = dumpcont.rfind("></div>");
785
786 if ((p1 != std::string::npos) && (p2 != std::string::npos) && (p1 < p2)) {
787
788 auto base64 = dumpcont.substr(p1+8, p2-p1-9);
789 auto binary = TBase64::Decode(base64.c_str());
790
791 std::ofstream ofs(tgtfilename, std::ios::binary);
792 ofs.write(binary.Data(), binary.Length());
793 } else {
794 R__ERROR_HERE("CanvasPainter") << "Fail to extract image from dump HTML code " << dump_name;
795
796 return false;
797 }
798 }
799 }
800
801 R__DEBUG_HERE("CanvasPainter") << "Create file " << fname;
802
803 return true;
804}
805
#define R__ERROR_HERE(GROUP)
Definition: RLogger.hxx:183
#define R__DEBUG_HERE(GROUP)
Definition: RLogger.hxx:186
#define c(i)
Definition: RSha256.hxx:101
static RooMathCoreReg dummy
const Ssiz_t kNPOS
Definition: RtypesCore.h:113
include TDocParser_001 C image html pict1_TDocParser_001 png width
Definition: TDocParser.cxx:121
R__EXTERN TEnv * gEnv
Definition: TEnv.h:171
char name[80]
Definition: TGX11.cxx:109
R__EXTERN TRandom * gRandom
Definition: TRandom.h:62
@ kExecutePermission
Definition: TSystem.h:44
R__EXTERN TSystem * gSystem
Definition: TSystem.h:556
Holds different arguments for starting browser with RWebDisplayHandle::Display() method.
std::string GetCustomExec() const
returns custom executable to start web browser
RWebDisplayArgs & SetBrowserKind(const std::string &kind)
Set browser kind as string argument Recognized values: chrome - use Google Chrome web browser,...
bool IsHeadless() const
returns headless mode
void SetExtraArgs(const std::string &args)
RWebDisplayArgs & SetUrl(const std::string &url)
set window url
int GetHeight() const
returns preferable web window height
void SetHeadless(bool on=true)
set headless mode
std::string GetFullUrl() const
returns window url with append options
EBrowserKind GetBrowserKind() const
returns configured browser kind, see EBrowserKind for supported values
const std::string & GetExtraArgs() const
void SetStandalone(bool on=true)
Set standalone mode for running browser, default on When disabled, normal browser window (or just tab...
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="")
@ kFirefox
Mozilla Firefox browser.
@ kCEF
Chromium Embedded Framework - local display with CEF libs.
@ kCustom
custom web browser, execution string should be provided
@ kNative
either Chrome or Firefox - both support major functionality
@ kLocal
either CEF or Qt5 - both runs on local display without real http server
@ kQt5
QWebEngine libraries - Chrome code packed in qt5.
const std::string & GetRedirectOutput() const
RWebDisplayArgs & SetSize(int w, int h)
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
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.
std::unique_ptr< RWebDisplayHandle > Display(const RWebDisplayArgs &args) override
Display given URL in web browser.
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.
static bool DisplayUrl(const std::string &url)
Display provided url in configured web browser.
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 std::unique_ptr< RWebDisplayHandle > Display(const RWebDisplayArgs &args)
Create web display.
static std::map< std::string, std::unique_ptr< Creator > > & GetMap()
!< URL used to launch display
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 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:443
static const TString & GetDataDir()
Get the data directory in the installation. Static utility function.
Definition: TROOT.cxx:2949
virtual void SetSeed(ULong_t seed=0)
Set the random generator seed.
Definition: TRandom.cxx:597
virtual UInt_t Integer(UInt_t imax)
Returns a random integer uniformly distributed on the interval [ 0, imax-1 ].
Definition: TRandom.cxx:349
Basic string class.
Definition: TString.h:131
const char * Data() const
Definition: TString.h:364
Ssiz_t Last(char c) const
Find last occurrence of a character c.
Definition: TString.cxx:892
TObjArray * Tokenize(const TString &delim) const
This function is used to isolate sequential tokens in a TString.
Definition: TString.cxx:2197
TString & Remove(Ssiz_t pos)
Definition: TString.h:668
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:2311
virtual Bool_t ExpandPathName(TString &path)
Expand a pathname getting rid of special shell characters like ~.
Definition: TSystem.cxx:1269
virtual const char * Getenv(const char *env)
Get environment variable.
Definition: TSystem.cxx:1658
virtual int mkdir(const char *name, Bool_t recursive=kFALSE)
Make a file system directory.
Definition: TSystem.cxx:902
virtual Int_t Exec(const char *shellcmd)
Execute a command.
Definition: TSystem.cxx:651
virtual int Load(const char *module, const char *entry="", Bool_t system=kFALSE)
Load a shared library.
Definition: TSystem.cxx:1850
virtual const char * PrependPathName(const char *dir, TString &name)
Concatenate a directory and a file name.
Definition: TSystem.cxx:1076
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:1291
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:1492
virtual std::string GetHomeDirectory(const char *userName=nullptr) const
Return the user's home directory.
Definition: TSystem.cxx:891
virtual const char * UnixPathName(const char *unixpathname)
Convert from a local pathname to a Unix pathname.
Definition: TSystem.cxx:1058
virtual int Rename(const char *from, const char *to)
Rename a file.
Definition: TSystem.cxx:1345
virtual TString GetFromPipe(const char *command)
Execute command and return output in TString.
Definition: TSystem.cxx:678
virtual Bool_t IsAbsoluteFileName(const char *dir)
Return true if dir is an absolute pathname.
Definition: TSystem.cxx:947
virtual const char * WorkingDirectory()
Return working directory.
Definition: TSystem.cxx:867
virtual int Unlink(const char *name)
Unlink, i.e.
Definition: TSystem.cxx:1376
virtual const char * TempDirectory() const
Return a user configured or systemwide directory to create temporary files in.
Definition: TSystem.cxx:1477
const Int_t n
Definition: legend1.C:16
bool EndsWith(const std::string &theString, const std::string &theSubstring)
tbb::task_arena is an alias of tbb::interface7::task_arena, which doesn't allow to forward declare tb...
Definition: StringConv.hxx:21
static constexpr double s
basic_json<> json
default JSON class
Definition: REveElement.hxx:88
auto * m
Definition: textangle.C:8