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