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