Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RBrowser.cxx
Go to the documentation of this file.
1// Authors: Bertrand Bellenot <bertrand.bellenot@cern.ch> Sergey Linev <S.Linev@gsi.de>
2// Date: 2019-02-28
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-2021, 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
13#include <ROOT/RBrowser.hxx>
14
18
19#include <ROOT/RLogger.hxx>
20#include <ROOT/RFileDialog.hxx>
22
23#include "RBrowserWidget.hxx"
24
25#include "TVirtualPad.h"
26#include "TString.h"
27#include "TSystem.h"
28#include "TError.h"
29#include "TTimer.h"
30#include "TEnv.h"
31#include "TROOT.h"
32#include "TBufferJSON.h"
33#include "TApplication.h"
34#include "TRint.h"
35#include "Getline.h"
36
37#include <sstream>
38#include <iostream>
39#include <algorithm>
40#include <memory>
41#include <mutex>
42#include <thread>
43#include <fstream>
44
45using namespace std::string_literals;
46
47namespace ROOT {
48
49class RBrowserTimer : public TTimer {
50public:
51 RBrowser &fBrowser; ///!< browser processing postponed requests
52
53 /// constructor
55
56 /// timeout handler
57 /// used to process postponed requests in main ROOT thread
59};
60
61
63public:
64
65 bool fIsEditor{true}; ///<! either editor or image viewer
66 std::string fTitle;
67 std::string fFileName;
68 std::string fContent;
69 bool fFirstSend{false}; ///<! if editor content was send at least once
70 std::string fItemPath; ///<! item path in the browser
71
72 RBrowserEditorWidget(const std::string &name, bool is_editor = true) : RBrowserWidget(name), fIsEditor(is_editor) {}
73 ~RBrowserEditorWidget() override = default;
74
75 void ResetConn() override { fFirstSend = false; }
76
77 std::string GetKind() const override { return fIsEditor ? "editor"s : "image"s; }
78 std::string GetTitle() override { return fTitle; }
79
80 bool DrawElement(std::shared_ptr<Browsable::RElement> &elem, const std::string & = "") override
81 {
82 if (fIsEditor && elem->IsCapable(Browsable::RElement::kActEdit)) {
83 auto code = elem->GetContent("text");
84 if (!code.empty()) {
85 fFirstSend = false;
86 fContent = code;
87 fTitle = elem->GetName();
88 fFileName = elem->GetContent("filename");
89 } else {
90 auto json = elem->GetContent("json");
91 if (!json.empty()) {
92 fFirstSend = false;
93 fContent = json;
94 fTitle = elem->GetName() + ".json";
95 fFileName = "";
96 }
97 }
98 if (!fContent.empty()) {
99 // page->fItemPath = item_path;
100 return true;
101 }
102 }
103
104 if (!fIsEditor && elem->IsCapable(Browsable::RElement::kActImage)) {
105 auto img = elem->GetContent("image64");
106 if (!img.empty()) {
107 fFirstSend = false;
108 fContent = img;
109 fTitle = elem->GetName();
110 fFileName = elem->GetContent("filename");
111 // fItemPath = item_path;
112
113 return true;
114 }
115 }
116
117 return false;
118 }
119
120 std::string SendWidgetContent() override
121 {
122 if (fFirstSend) return ""s;
123
124 fFirstSend = true;
125 std::vector<std::string> args = { GetName(), fTitle, fFileName, fContent };
126
127 std::string msg = fIsEditor ? "EDITOR:"s : "IMAGE:"s;
128 msg += TBufferJSON::ToJSON(&args).Data();
129 return msg;
130 }
131
132};
133
134
136public:
137
138 enum { kMaxContentLen = 10000000 };
139
140 std::string fTitle;
141 std::string fContent;
142 bool fFirstSend{false}; ///<! if editor content was send at least once
143
145 {
146 fTitle = "Cling info"s;
147 Refresh();
148 }
149
150 ~RBrowserInfoWidget() override = default;
151
152 void ResetConn() override { fFirstSend = false; }
153
154 std::string GetKind() const override { return "info"s; }
155 std::string GetTitle() override { return fTitle; }
156
157 bool DrawElement(std::shared_ptr<Browsable::RElement> &, const std::string & = "") override { return false; }
158
159 void Refresh()
160 {
161 fFirstSend = false;
162 fContent = "";
163
164 std::ostringstream pathtmp;
165 pathtmp << gSystem->TempDirectory() << "/info." << gSystem->GetPid() << ".log";
166
167 std::ofstream ofs(pathtmp.str(), std::ofstream::out | std::ofstream::app);
168 ofs << "";
169 ofs.close();
170
171 gSystem->RedirectOutput(pathtmp.str().c_str(), "a");
172 gROOT->ProcessLine(".g");
173 gSystem->RedirectOutput(nullptr);
174
175 std::ifstream infile(pathtmp.str());
176 if (infile) {
177 std::string line;
178 while (std::getline(infile, line) && (fContent.length() < kMaxContentLen)) {
179 fContent.append(line);
180 fContent.append("\n");
181 }
182 }
183
184 gSystem->Unlink(pathtmp.str().c_str());
185 }
186
187 void RefreshFromLogs(const std::string &promt, const std::vector<std::string> &logs)
188 {
189 int indx = 0, last_prompt = -1;
190 for (auto &line : logs) {
191 if (line == promt)
193 indx++;
194 }
195
196 if (last_prompt < 0) {
197 Refresh();
198 return;
199 }
200
201 fFirstSend = false;
202 fContent = "";
203
204 indx = 0;
205 for (auto &line : logs) {
206 if ((indx++ > last_prompt) && (fContent.length() < kMaxContentLen)) {
207 fContent.append(line);
208 fContent.append("\n");
209 }
210 }
211 }
212
213
214 std::string SendWidgetContent() override
215 {
216 if (fFirstSend)
217 return ""s;
218
219 if (fContent.empty())
220 Refresh();
221
222 fFirstSend = true;
223 std::vector<std::string> args = { GetName(), fTitle, fContent };
224
225 return "INFO:"s + TBufferJSON::ToJSON(&args).Data();
226 }
227
228};
229
230
232public:
233
234 RWebWindow *fWindow{nullptr}; // catched widget, TODO: to be changed to shared_ptr
235 std::string fCatchedKind; // kind of catched widget
236
237 std::string GetKind() const override { return "catched"s; }
238
239 std::string GetUrl() override { return fWindow ? ".."s + fWindow->GetUrl(false) : ""s; }
240
241 std::string GetTitle() override { return fCatchedKind; }
242
243 bool IsValid() override { return fWindow != nullptr; }
244
245 RBrowserCatchedWidget(const std::string &name, RWebWindow *win, const std::string &kind) :
247 fWindow(win),
248 fCatchedKind(kind)
249 {
250 }
251};
252
253} // namespace ROOT
254
255using namespace ROOT;
256
257
258/** \class ROOT::RBrowser
259\ingroup rbrowser
260\ingroup webwidgets
261
262\brief Web-based %ROOT files and objects browser
263
264\image html v7_rbrowser.png
265
266For normal interactive mode, any modern web browser should be able to display it.
267Chrome or Firefox browsers are though required when running ROOT in batch mode.
268
269Most configuration options for RBrowser, such as default web browser, server mode are not specific to this class,
270but are rather applied for all web widgets: canvases, geometry viewer, eve7, browser, fit panel, etc.
271
272Following `.rootrc` parameters can be configured for the browser:
273
274 * WebGui.Browser.SortBy: sort by "name", "size", "none" (default "name")
275 * WebGui.Browser.Reverse: reverse item order (default off)
276 * WebGui.Browser.ShowHidden: show hidden files (default off)
277 * WebGui.Browser.LastCycle: show only last key cycle (default off)
278 * WebGui.Browser.Expand: expand browsable area (default off)
279
280\note See major settings in RWebWindowWindowsManager::CreateServer and RWebWindowsManager::ShowWindow
281*/
282
283//////////////////////////////////////////////////////////////////////////////////////////////
284/// constructor
285
287{
288 if (gROOT->IsWebDisplayBatch()) {
289 ::Warning("RBrowser::RBrowser", "The RBrowser cannot run in web batch mode");
290 return;
291 }
292
293 std::ostringstream pathtmp;
294 pathtmp << gSystem->TempDirectory() << "/command." << gSystem->GetPid() << ".log";
296
298
300
301 fTimer = std::make_unique<RBrowserTimer>(10, kTRUE, *this);
302
304 fWebWindow->SetDefaultPage("file:rootui5sys/browser/browser.html");
305
306 std::string sortby = gEnv->GetValue("WebGui.Browser.SortBy", "name"),
307 reverse = gEnv->GetValue("WebGui.Browser.Reverse", "no"),
308 hidden = gEnv->GetValue("WebGui.Browser.ShowHidden", "no"),
309 lastcycle = gEnv->GetValue("WebGui.Browser.LastCycle", "");
310
311 if (sortby != "name" && sortby != "size" && sortby != "none")
312 sortby = "name";
313
314 reverse = (reverse == "on" || reverse == "yes" || reverse == "1") ? "true" : "false";
315 hidden = (hidden == "on" || hidden == "yes" || hidden == "1") ? "true" : "false";
316 if (lastcycle == "on" || lastcycle == "yes" || lastcycle == "1")
318 else if (lastcycle == "off" || lastcycle == "no" || lastcycle == "0")
320
321 fWebWindow->SetUserArgs(TString::Format("{ sort: \"%s\", reverse: %s, hidden: %s }", sortby.c_str(), reverse.c_str(), hidden.c_str()).Data());
322
323 // this is call-back, invoked when message received via websocket
324 fWebWindow->SetCallBacks([this](unsigned connid) { fConnId = connid; SendInitMsg(connid); },
325 [this](unsigned connid, const std::string &arg) { ProcessMsg(connid, arg); });
326 fWebWindow->SetGeometry(1200, 700); // configure predefined window geometry
327 fWebWindow->SetConnLimit(1); // the only connection is allowed
328 fWebWindow->SetMaxQueueLength(30); // number of allowed entries in the window queue
329
330 fWebWindow->GetManager()->SetShowCallback([this](RWebWindow &win, const RWebDisplayArgs &args) -> bool {
331
332 std::string kind;
333
334 if (args.GetWidgetKind() == "RCanvas")
335 kind = "rcanvas";
336 else if (args.GetWidgetKind() == "TCanvas")
337 kind = "tcanvas";
338 else if (args.GetWidgetKind() == "RGeomViewer")
339 kind = "geom";
340 else if (args.GetWidgetKind() == "RTreeViewer")
341 kind = "tree";
342
343 if (!fWebWindow || !fCatchWindowShow || kind.empty())
344 return false;
345
346 // before create new widget check if other disappear
348
350 if (widget) {
351 widget->fBrowser = this;
352 fWidgets.emplace_back(widget);
353 fActiveWidgetName = widget->GetName();
354 } else {
355 widget = AddCatchedWidget(&win, kind);
356 }
357
358 if (widget && fWebWindow && (fWebWindow->NumConnections() > 0))
359 fWebWindow->Send(0, NewWidgetMsg(widget));
360
361 return widget ? true : false;
362 });
363
364 fWebWindow->GetManager()->SetDeleteCallback([this](RWebWindow &win) -> void {
365 for (auto &widget : fWidgets) {
366 auto catched = dynamic_cast<RBrowserCatchedWidget *>(widget.get());
367 if (catched && (catched->fWindow == &win))
368 catched->fWindow = nullptr;
369 }
370
371 if (fWebWindow)
373 });
374
375 Show();
376}
377
378//////////////////////////////////////////////////////////////////////////////////////////////
379/// destructor
380
382{
383 if (fWebWindow) {
384 fWebWindow->GetManager()->SetShowCallback(nullptr);
385 fWebWindow->GetManager()->SetDeleteCallback(nullptr);
386 fWebWindow->Reset();
387 }
388}
389
390//////////////////////////////////////////////////////////////////////////////////////////////
391/// Process browser request
392
393std::string RBrowser::ProcessBrowserRequest(const std::string &msg)
394{
395 std::unique_ptr<RBrowserRequest> request;
396
397 if (msg.empty()) {
398 request = std::make_unique<RBrowserRequest>();
399 request->first = 0;
400 request->number = 100;
401 } else {
402 request = TBufferJSON::FromJSON<RBrowserRequest>(msg);
403 }
404
405 if (!request)
406 return ""s;
407
408 if (request->path.empty() && fWidgets.empty() && fBrowsable.GetWorkingPath().empty())
410
411 return "BREPL:"s + fBrowsable.ProcessRequest(*request.get());
412}
413
414/////////////////////////////////////////////////////////////////////////////////
415/// Process file save command in the editor
416
417void RBrowser::ProcessSaveFile(const std::string &fname, const std::string &content)
418{
419 if (fname.empty()) return;
420 R__LOG_DEBUG(0, BrowserLog()) << "SaveFile " << fname << " content length " << content.length();
421 std::ofstream f(fname);
422 f << content;
423}
424
425/////////////////////////////////////////////////////////////////////////////////
426/// Process run macro command in the editor
427
428void RBrowser::ProcessRunMacro(const std::string &file_path)
429{
430 if (file_path.rfind(".py") == file_path.length() - 3) {
431 TString exec;
432 exec.Form("TPython::ExecScript(\"%s\");", file_path.c_str());
433 gROOT->ProcessLine(exec.Data());
434 } else {
435 gInterpreter->ExecuteMacro(file_path.c_str());
436 }
437}
438
439/////////////////////////////////////////////////////////////////////////////////
440/// Process dbl click on browser item
441
442std::string RBrowser::ProcessDblClick(unsigned connid, std::vector<std::string> &args)
443{
444 args.pop_back(); // remove exec string, not used now
445
446 std::string opt = args.back();
447 args.pop_back(); // remove option
448
449 auto path = fBrowsable.GetWorkingPath();
450 path.insert(path.end(), args.begin(), args.end());
451
452 R__LOG_DEBUG(0, BrowserLog()) << "DoubleClick " << Browsable::RElement::GetPathAsString(path);
453
454 auto elem = fBrowsable.GetSubElement(path);
455 if (!elem) return ""s;
456
457 auto dflt_action = elem->GetDefaultAction();
458
459 // special case when canvas is clicked - always start new widget
461 std::string widget_kind;
462
463 if (elem->IsCapable(Browsable::RElement::kActDraw7))
464 widget_kind = "rcanvas";
465 else
466 widget_kind = "tcanvas";
467
468 std::string name = widget_kind + std::to_string(++fWidgetCnt);
469
471
472 if (!new_widget)
473 return ""s;
474
475 // assign back pointer
476 new_widget->fBrowser = this;
477 fWidgets.emplace_back(new_widget);
478 fActiveWidgetName = new_widget->GetName();
479
480 return NewWidgetMsg(new_widget);
481 }
482
483 // before display tree or geometry ensure that they read and cached inside element
485 elem->GetChildsIter();
486 }
487
489 Browsable::RProvider::ProgressHandle handle(elem.get(), [this, connid](float progress, void *) {
490 SendProgress(connid, progress);
491 });
492
493 auto widget = GetActiveWidget();
494 if (widget && widget->DrawElement(elem, opt)) {
495 widget->SetPath(path);
496 return widget->SendWidgetContent();
497 }
498
499 // check if element was drawn in other widget and just activate that widget
500 auto iter = std::find_if(fWidgets.begin(), fWidgets.end(),
501 [path](const std::shared_ptr<RBrowserWidget> &wg) { return path == wg->GetPath(); });
502
503 if (iter != fWidgets.end())
504 return "SELECT_WIDGET:"s + (*iter)->GetName();
505
506 // check if object can be drawn in RCanvas even when default action is drawing in TCanvas
509
510 std::string widget_kind;
511 switch(dflt_action) {
512 case Browsable::RElement::kActDraw6: widget_kind = "tcanvas"; break;
513 case Browsable::RElement::kActDraw7: widget_kind = "rcanvas"; break;
514 case Browsable::RElement::kActEdit: widget_kind = "editor"; break;
515 case Browsable::RElement::kActImage: widget_kind = "image"; break;
516 case Browsable::RElement::kActTree: widget_kind = "tree"; break;
517 case Browsable::RElement::kActGeom: widget_kind = "geom"; break;
518 default: widget_kind.clear();
519 }
520
521 if (!widget_kind.empty()) {
523 if (new_widget) {
524 // draw object before client side is created - should not be a problem
525 // after widget add in browser, connection will be established and data provided
526 if (new_widget->DrawElement(elem, opt))
527 new_widget->SetPath(path);
528 return NewWidgetMsg(new_widget);
529 }
530 }
531
532 if (elem->IsCapable(Browsable::RElement::kActBrowse) && (elem->GetNumChilds() > 0)) {
533 // remove extra index in subitems name
534 for (auto &pathelem : path)
538 }
539
540 return ""s;
541}
542
543/////////////////////////////////////////////////////////////////////////////////
544/// Process drop of item in the current tab
545
546std::string RBrowser::ProcessDrop(unsigned connid, std::vector<std::string> &args)
547{
548 auto path = fBrowsable.GetWorkingPath();
549 path.insert(path.end(), args.begin(), args.end());
550
551 R__LOG_DEBUG(0, BrowserLog()) << "DoubleClick " << Browsable::RElement::GetPathAsString(path);
552
553 auto elem = fBrowsable.GetSubElement(path);
554 if (!elem) return ""s;
555
557 Browsable::RProvider::ProgressHandle handle(elem.get(), [this, connid](float progress, void *) {
558 SendProgress(connid, progress);
559 });
560
561 auto widget = GetActiveWidget();
562 if (widget && widget->DrawElement(elem, "<append>")) {
563 widget->SetPath(path);
564 return widget->SendWidgetContent();
565 }
566
567 return ""s;
568}
569
570
571/////////////////////////////////////////////////////////////////////////////////
572/// Show or update RBrowser in web window
573/// If web window already started - just refresh it like "reload" button does
574/// If no web window exists or \param always_start_new_browser configured, starts new window
575/// \param args display arguments
576
578{
579 if (!fWebWindow->NumConnections() || always_start_new_browser) {
580 fWebWindow->Show(args);
581 }
582}
583
584///////////////////////////////////////////////////////////////////////////////////////////////////////
585/// Hide ROOT Browser
586
588{
589 if (fWebWindow)
590 fWebWindow->CloseConnections();
591}
592
593///////////////////////////////////////////////////////////////////////////////////////////////////////
594/// Return URL parameter for the window showing ROOT Browser
595/// See \ref ROOT::RWebWindow::GetUrl docu for more details
596
598{
599 if (fWebWindow)
600 return fWebWindow->GetUrl(remote);
601
602 return ""s;
603}
604
605
606//////////////////////////////////////////////////////////////////////////////////////////////
607/// Creates new widget
608
609std::shared_ptr<RBrowserWidget> RBrowser::AddWidget(const std::string &kind)
610{
611 std::string name = kind + std::to_string(++fWidgetCnt);
612
613 std::shared_ptr<RBrowserWidget> widget;
614
615 if (kind == "editor"s)
616 widget = std::make_shared<RBrowserEditorWidget>(name, true);
617 else if (kind == "image"s)
618 widget = std::make_shared<RBrowserEditorWidget>(name, false);
619 else if (kind == "info"s)
620 widget = std::make_shared<RBrowserInfoWidget>(name);
621 else
623
624 if (!widget) {
625 R__LOG_ERROR(BrowserLog()) << "Fail to create widget of kind " << kind;
626 return nullptr;
627 }
628
629 widget->fBrowser = this;
630 fWidgets.emplace_back(widget);
632
633 return widget;
634}
635
636//////////////////////////////////////////////////////////////////////////////////////////////
637/// Add widget catched from external scripts
638
639std::shared_ptr<RBrowserWidget> RBrowser::AddCatchedWidget(RWebWindow *win, const std::string &kind)
640{
641 if (!win || kind.empty())
642 return nullptr;
643
644 std::string name = "catched"s + std::to_string(++fWidgetCnt);
645
646 auto widget = std::make_shared<RBrowserCatchedWidget>(name, win, kind);
647
648 fWidgets.emplace_back(widget);
649
651
652 return widget;
653}
654
655
656//////////////////////////////////////////////////////////////////////////////////////////////
657/// Create new widget and send init message to the client
658
659void RBrowser::AddInitWidget(const std::string &kind)
660{
661 auto widget = AddWidget(kind);
662 if (widget && fWebWindow && (fWebWindow->NumConnections() > 0))
663 fWebWindow->Send(0, NewWidgetMsg(widget));
664}
665
666//////////////////////////////////////////////////////////////////////////////////////////////
667/// Find widget by name or kind
668
669std::shared_ptr<RBrowserWidget> RBrowser::FindWidget(const std::string &name, const std::string &kind) const
670{
671 auto iter = std::find_if(fWidgets.begin(), fWidgets.end(),
672 [name, kind](const std::shared_ptr<RBrowserWidget> &widget) {
673 return kind.empty() ? name == widget->GetName() : kind == widget->GetKind();
674 });
675
676 if (iter != fWidgets.end())
677 return *iter;
678
679 return nullptr;
680}
681
682//////////////////////////////////////////////////////////////////////////////////////////////
683/// Close and delete specified widget
684
685void RBrowser::CloseTab(const std::string &name)
686{
687 auto iter = std::find_if(fWidgets.begin(), fWidgets.end(), [name](std::shared_ptr<RBrowserWidget> &widget) { return name == widget->GetName(); });
688 if (iter != fWidgets.end())
689 fWidgets.erase(iter);
690
691 if (fActiveWidgetName == name)
692 fActiveWidgetName.clear();
693}
694
695//////////////////////////////////////////////////////////////////////////////////////////////
696/// Get content of history file
697
698std::vector<std::string> RBrowser::GetRootHistory()
699{
700 std::vector<std::string> arr;
701
702 std::string path = gSystem->UnixPathName(gSystem->HomeDirectory());
703 path += "/.root_hist" ;
704 std::ifstream infile(path);
705
706 if (infile) {
707 std::string line;
708 while (std::getline(infile, line) && (arr.size() < 1000)) {
709 if(!(std::find(arr.begin(), arr.end(), line) != arr.end())) {
710 arr.emplace_back(line);
711 }
712 }
713 }
714
715 return arr;
716}
717
718//////////////////////////////////////////////////////////////////////////////////////////////
719/// Get content of log file
720
721std::vector<std::string> RBrowser::GetRootLogs()
722{
723 std::vector<std::string> arr;
724
725 std::ifstream infile(fPromptFileOutput);
726 if (infile) {
727 std::string line;
728 while (std::getline(infile, line) && (arr.size() < 10000)) {
729 arr.emplace_back(line);
730 }
731 }
732
733 return arr;
734}
735
736//////////////////////////////////////////////////////////////////////////////////////////////
737/// Process client connect
738
739void RBrowser::SendInitMsg(unsigned connid)
740{
741 std::vector<std::vector<std::string>> reply;
742
743 reply.emplace_back(fBrowsable.GetWorkingPath()); // first element is current path
744
745 for (auto &widget : fWidgets) {
746 widget->ResetConn();
747 reply.emplace_back(std::vector<std::string>({ widget->GetKind(), widget->GetUrl(), widget->GetName(), widget->GetTitle() }));
748 }
749
750 if (!fActiveWidgetName.empty())
751 reply.emplace_back(std::vector<std::string>({ "active"s, fActiveWidgetName }));
752
753 auto history = GetRootHistory();
754 if (history.size() > 0) {
755 history.insert(history.begin(), "history"s);
756 reply.emplace_back(history);
757 }
758
759 auto logs = GetRootLogs();
760 if (logs.size() > 0) {
761 logs.insert(logs.begin(), "logs"s);
762 reply.emplace_back(logs);
763 }
764
765 reply.emplace_back(std::vector<std::string>({
766 "drawoptions"s,
770 }));
771
772 reply.emplace_back(std::vector<std::string>({
773 "settings"s,
774 gEnv->GetValue("WebGui.Browser.Expand", "no"),
776 }));
777
778 std::string msg = "INMSG:";
780
781 fWebWindow->Send(connid, msg);
782}
783
784//////////////////////////////////////////////////////////////////////////////////////////////
785/// Send generic progress message to the web window
786/// Should show progress bar on client side
787
788void RBrowser::SendProgress(unsigned connid, float progr)
789{
790 long long millisec = gSystem->Now();
791
792 // let process window events
793 fWebWindow->Sync();
794
795 if ((!fLastProgressSendTm || millisec > fLastProgressSendTm - 200) && (progr > fLastProgressSend + 0.04) && fWebWindow->CanSend(connid)) {
796 fWebWindow->Send(connid, "PROGRESS:"s + std::to_string(progr));
797
800 }
801}
802
803
804//////////////////////////////////////////////////////////////////////////////////////////////
805/// Return the current directory of ROOT
806
808{
809 return "WORKPATH:"s + TBufferJSON::ToJSON(&fBrowsable.GetWorkingPath()).Data();
810}
811
812//////////////////////////////////////////////////////////////////////////////////////////////
813/// Create message which send to client to create new widget
814
815std::string RBrowser::NewWidgetMsg(std::shared_ptr<RBrowserWidget> &widget)
816{
817 std::vector<std::string> arr = { widget->GetKind(), widget->GetUrl(), widget->GetName(), widget->GetTitle(),
819 return "NEWWIDGET:"s + TBufferJSON::ToJSON(&arr, TBufferJSON::kNoSpaces).Data();
820}
821
822//////////////////////////////////////////////////////////////////////////////////////////////
823/// Check if any widget was modified and update if necessary
824
826{
827 std::vector<std::string> del_names;
828
829 for (auto &widget : fWidgets)
830 if (!widget->IsValid())
831 del_names.push_back(widget->GetName());
832
833 if (!del_names.empty())
834 fWebWindow->Send(connid, "CLOSE_WIDGETS:"s + TBufferJSON::ToJSON(&del_names, TBufferJSON::kNoSpaces).Data());
835
836 for (auto name : del_names)
837 CloseTab(name);
838
839 for (auto &widget : fWidgets)
840 widget->CheckModified();
841}
842
843//////////////////////////////////////////////////////////////////////////////////////////////
844/// Process postponed requests - decouple from websocket handling
845/// Only requests which can take longer time should be postponed
846
848{
849 if (fPostponed.empty())
850 return;
851
852 auto arr = fPostponed[0];
853 fPostponed.erase(fPostponed.begin(), fPostponed.begin()+1);
854 if (fPostponed.empty())
855 fTimer->TurnOff();
856
857 std::string reply;
858 unsigned connid = std::stoul(arr.back()); arr.pop_back();
859 std::string kind = arr.back(); arr.pop_back();
860
861 if (kind == "DBLCLK") {
862 reply = ProcessDblClick(connid, arr);
863 if (reply.empty()) reply = "NOPE";
864 } else if (kind == "DROP") {
865 reply = ProcessDrop(connid, arr);
866 if (reply.empty()) reply = "NOPE";
867 }
868
869 if (!reply.empty())
870 fWebWindow->Send(connid, reply);
871}
872
873
874//////////////////////////////////////////////////////////////////////////////////////////////
875/// Process received message from the client
876
877void RBrowser::ProcessMsg(unsigned connid, const std::string &arg0)
878{
879 R__LOG_DEBUG(0, BrowserLog()) << "ProcessMsg len " << arg0.length() << " substr(30) " << arg0.substr(0, 30);
880
881 std::string kind, msg;
882 auto pos = arg0.find(":");
883 if (pos == std::string::npos) {
884 kind = arg0;
885 } else {
886 kind = arg0.substr(0, pos);
887 msg = arg0.substr(pos+1);
888 }
889
890 if (kind == "QUIT_ROOT") {
891
892 fWebWindow->TerminateROOT();
893
894 } else if (kind == "BRREQ") {
895 // central place for processing browser requests
897 if (!json.empty()) fWebWindow->Send(connid, json);
898
899 } else if (kind == "LASTCYCLE") {
900 // when changed on clients side
902
903 } else if (kind == "DBLCLK") {
904
905 auto arr = TBufferJSON::FromJSON<std::vector<std::string>>(msg);
906 if (arr && (arr->size() > 2)) {
907 arr->push_back(kind);
908 arr->push_back(std::to_string(connid));
909 fPostponed.push_back(*arr);
910 if (fPostponed.size() == 1)
911 fTimer->TurnOn();
912 } else {
913 fWebWindow->Send(connid, "NOPE");
914 }
915
916 } else if (kind == "DROP") {
917
918 auto arr = TBufferJSON::FromJSON<std::vector<std::string>>(msg);
919 if (arr && arr->size()) {
920 arr->push_back(kind);
921 arr->push_back(std::to_string(connid));
922 fPostponed.push_back(*arr);
923 if (fPostponed.size() == 1)
924 fTimer->TurnOn();
925 } else {
926 fWebWindow->Send(connid, "NOPE");
927 }
928
929 } else if (kind == "WIDGET_SELECTED") {
931 auto widget = GetActiveWidget();
932 if (widget) {
933 auto reply = widget->SendWidgetContent();
934 if (!reply.empty()) fWebWindow->Send(connid, reply);
935 }
936 } else if (kind == "CLOSE_TAB") {
937 CloseTab(msg);
938 } else if (kind == "GETWORKPATH") {
939 fWebWindow->Send(connid, GetCurrentWorkingDirectory());
940 } else if (kind == "CHPATH") {
941 auto path = TBufferJSON::FromJSON<Browsable::RElementPath_t>(msg);
942 if (path) fBrowsable.SetWorkingPath(*path);
943 fWebWindow->Send(connid, GetCurrentWorkingDirectory());
944 } else if (kind == "CMD") {
945 std::string sPrompt = "root []";
946 TApplication *app = gROOT->GetApplication();
947 if (app->InheritsFrom("TRint")) {
948 sPrompt = ((TRint*)gROOT->GetApplication())->GetPrompt();
949 Gl_histadd((char *)msg.c_str());
950 }
951
952 std::ofstream ofs(fPromptFileOutput, std::ofstream::out | std::ofstream::app);
953 ofs << sPrompt << msg << std::endl;
954 ofs.close();
955
957 gROOT->ProcessLine(msg.c_str());
958 gSystem->RedirectOutput(nullptr);
959
960 if (msg == ".g"s) {
961 auto widget = std::dynamic_pointer_cast<RBrowserInfoWidget>(FindWidget(""s, "info"s));
962 if (!widget) {
963 auto new_widget = AddWidget("info"s);
964 fWebWindow->Send(connid, NewWidgetMsg(new_widget));
965 widget = std::dynamic_pointer_cast<RBrowserInfoWidget>(new_widget);
966 } else if (fActiveWidgetName != widget->GetName()) {
967 fWebWindow->Send(connid, "SELECT_WIDGET:"s + widget->GetName());
968 fActiveWidgetName = widget->GetName();
969 }
970
971 if (widget)
972 widget->RefreshFromLogs(sPrompt + msg, GetRootLogs());
973 }
974
975 CheckWidgtesModified(connid);
976 } else if (kind == "GETHISTORY") {
977
978 auto history = GetRootHistory();
979
980 fWebWindow->Send(connid, "HISTORY:"s + TBufferJSON::ToJSON(&history, TBufferJSON::kNoSpaces).Data());
981 } else if (kind == "GETLOGS") {
982
983 auto logs = GetRootLogs();
984 fWebWindow->Send(connid, "LOGS:"s + TBufferJSON::ToJSON(&logs, TBufferJSON::kNoSpaces).Data());
985
987
989
990 } else if (kind == "SYNCEDITOR") {
991 auto arr = TBufferJSON::FromJSON<std::vector<std::string>>(msg);
992 if (arr && (arr->size() > 4)) {
993 auto editor = std::dynamic_pointer_cast<RBrowserEditorWidget>(FindWidget(arr->at(0)));
994 if (editor) {
995 editor->fFirstSend = true;
996 editor->fTitle = arr->at(1);
997 editor->fFileName = arr->at(2);
998 if (!arr->at(3).empty()) editor->fContent = arr->at(4);
999 if ((arr->size() == 6) && (arr->at(5) == "SAVE"))
1000 ProcessSaveFile(editor->fFileName, editor->fContent);
1001 if ((arr->size() == 6) && (arr->at(5) == "RUN")) {
1002 ProcessSaveFile(editor->fFileName, editor->fContent);
1003 ProcessRunMacro(editor->fFileName);
1004 CheckWidgtesModified(connid);
1005 }
1006 }
1007 }
1008 } else if (kind == "GETINFO") {
1009 auto info = std::dynamic_pointer_cast<RBrowserInfoWidget>(FindWidget(msg));
1010 if (info) {
1011 info->Refresh();
1012 fWebWindow->Send(connid, info->SendWidgetContent());
1013 }
1014 } else if (kind == "NEWWIDGET") {
1015 auto widget = AddWidget(msg);
1016 if (widget)
1017 fWebWindow->Send(connid, NewWidgetMsg(widget));
1018 } else if (kind == "NEWCHANNEL") {
1019 auto arr = TBufferJSON::FromJSON<std::vector<std::string>>(msg);
1020 if (arr && (arr->size() == 2)) {
1021 auto widget = FindWidget((*arr)[0]);
1022 if (widget)
1023 RWebWindow::ShowWindow(widget->GetWindow(), { fWebWindow, connid, std::stoi((*arr)[1]) });
1024 }
1025 } else if (kind == "CDWORKDIR") {
1027 if (fBrowsable.GetWorkingPath() != wrkdir) {
1029 } else {
1031 }
1032 fWebWindow->Send(connid, GetCurrentWorkingDirectory());
1033 } else if (kind == "OPTIONS") {
1034 auto arr = TBufferJSON::FromJSON<std::vector<std::string>>(msg);
1035 if (arr && (arr->size() == 3)) {
1039 }
1040 }
1041}
1042
1043//////////////////////////////////////////////////////////////////////////////////////////////
1044/// Set working path in the browser
1045
1046void RBrowser::SetWorkingPath(const std::string &path)
1047{
1048 auto p = Browsable::RElement::ParsePath(path);
1050 if (elem) {
1052 if (fWebWindow && (fWebWindow->NumConnections() > 0))
1054 }
1055}
1056
1057//////////////////////////////////////////////////////////////////////////////////////////////
1058/// Activate widget in RBrowser
1059/// One should specify title and (optionally) kind of widget like "tcanvas" or "geom"
1060
1061bool RBrowser::ActivateWidget(const std::string &title, const std::string &kind)
1062{
1063 if (title.empty())
1064 return false;
1065
1066 for (auto &widget : fWidgets) {
1067
1068 if (widget->GetTitle() != title)
1069 continue;
1070
1071 if (!kind.empty() && (widget->GetKind() != kind))
1072 continue;
1073
1074 if (fWebWindow)
1075 fWebWindow->Send(0, "SELECT_WIDGET:"s + widget->GetName());
1076 else
1077 fActiveWidgetName = widget->GetName();
1078 return true;
1079 }
1080
1081 return false;
1082}
1083
1084//////////////////////////////////////////////////////////////////////////////////////////////
1085/// Set handle which will be cleared when connection is closed
1086
1087void RBrowser::ClearOnClose(const std::shared_ptr<void> &handle)
1088{
1089 fWebWindow->SetClearOnClose(handle);
1090}
nlohmann::json json
#define R__LOG_ERROR(...)
Definition RLogger.hxx:357
#define R__LOG_DEBUG(DEBUGLEVEL,...)
Definition RLogger.hxx:360
#define f(i)
Definition RSha256.hxx:104
long Long_t
Signed long integer 4 bytes (long). Size depends on architecture.
Definition RtypesCore.h:68
constexpr Bool_t kTRUE
Definition RtypesCore.h:107
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
R__EXTERN TEnv * gEnv
Definition TEnv.h:170
void Warning(const char *location, const char *msgfmt,...)
Use this function in warning situations.
Definition TError.cxx:252
winID h TVirtualViewer3D TVirtualGLPainter p
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t win
Option_t Option_t TPoint TPoint const char mode
char name[80]
Definition TGX11.cxx:110
#define gInterpreter
#define gROOT
Definition TROOT.h:411
R__EXTERN TSystem * gSystem
Definition TSystem.h:572
static bool IsLastKeyCycle()
Is only last cycle from the list of keys is shown.
Definition RElement.cxx:195
static void SetLastKeyCycle(bool on=true)
Set flag to show only last cycle from the list of keys.
Definition RElement.cxx:203
static int ExtractItemIndex(std::string &name)
Extract index from name Index coded by client with ###<indx>$$$ suffix Such coding used by browser to...
Definition RElement.cxx:180
@ kActImage
can be shown in image viewer, can provide image
Definition RElement.hxx:54
@ kActDraw6
can be drawn inside ROOT6 canvas
Definition RElement.hxx:55
@ kActCanvas
indicate that it is canvas and should be drawn directly
Definition RElement.hxx:57
@ kActTree
can be shown in tree viewer
Definition RElement.hxx:58
@ kActGeom
can be shown in geometry viewer
Definition RElement.hxx:59
@ kActBrowse
just browse (expand) item
Definition RElement.hxx:52
@ kActEdit
can provide data for text editor
Definition RElement.hxx:53
@ kActDraw7
can be drawn inside ROOT7 canvas
Definition RElement.hxx:56
static std::string GetPathAsString(const RElementPath_t &path)
Converts element path back to string.
Definition RElement.cxx:162
static RElementPath_t ParsePath(const std::string &str)
Parse string path to produce RElementPath_t One should avoid to use string pathes as much as possible...
Definition RElement.cxx:118
static bool SetClassDrawOption(const ClassArg &, const std::string &)
Set draw option for the class Return true if entry for the class exists.
static std::string GetClassDrawOption(const ClassArg &)
Return configured draw option for the class.
static RElementPath_t GetWorkingPath(const std::string &workdir="")
Return working path in browser hierarchy.
Definition RSysFile.cxx:548
RBrowserCatchedWidget(const std::string &name, RWebWindow *win, const std::string &kind)
Definition RBrowser.cxx:245
std::string GetUrl() override
Definition RBrowser.cxx:239
std::string GetKind() const override
Definition RBrowser.cxx:237
std::string GetTitle() override
Definition RBrowser.cxx:241
std::shared_ptr< Browsable::RElement > GetSubElement(const Browsable::RElementPath_t &path)
Returns sub-element starting from top, using cached data.
void ClearCache()
Clear internal objects cache.
std::string ProcessRequest(const RBrowserRequest &request)
Process browser request, returns string with JSON of RBrowserReply data.
void SetWorkingPath(const Browsable::RElementPath_t &path)
set working directory relative to top element
const Browsable::RElementPath_t & GetWorkingPath() const
void CreateDefaultElements()
Create default elements shown in the RBrowser.
std::string GetTitle() override
Definition RBrowser.cxx:78
std::string fItemPath
! item path in the browser
Definition RBrowser.cxx:70
void ResetConn() override
Definition RBrowser.cxx:75
std::string GetKind() const override
Definition RBrowser.cxx:77
bool fFirstSend
! if editor content was send at least once
Definition RBrowser.cxx:69
RBrowserEditorWidget(const std::string &name, bool is_editor=true)
Definition RBrowser.cxx:72
bool fIsEditor
! either editor or image viewer
Definition RBrowser.cxx:65
bool DrawElement(std::shared_ptr< Browsable::RElement > &elem, const std::string &="") override
Definition RBrowser.cxx:80
~RBrowserEditorWidget() override=default
std::string SendWidgetContent() override
Definition RBrowser.cxx:120
void RefreshFromLogs(const std::string &promt, const std::vector< std::string > &logs)
Definition RBrowser.cxx:187
void ResetConn() override
Definition RBrowser.cxx:152
RBrowserInfoWidget(const std::string &name)
Definition RBrowser.cxx:144
std::string GetTitle() override
Definition RBrowser.cxx:155
std::string GetKind() const override
Definition RBrowser.cxx:154
bool fFirstSend
! if editor content was send at least once
Definition RBrowser.cxx:142
std::string SendWidgetContent() override
Definition RBrowser.cxx:214
~RBrowserInfoWidget() override=default
bool DrawElement(std::shared_ptr< Browsable::RElement > &, const std::string &="") override
Definition RBrowser.cxx:157
RBrowser & fBrowser
Definition RBrowser.cxx:51
RBrowserTimer(Long_t milliSec, Bool_t mode, RBrowser &br)
!< browser processing postponed requests
Definition RBrowser.cxx:54
void Timeout() override
timeout handler used to process postponed requests in main ROOT thread
Definition RBrowser.cxx:58
static std::shared_ptr< RBrowserWidget > DetectCatchedWindow(const std::string &kind, RWebWindow &win)
Check if catch window can be identified and normal widget can be created Used for TCanvas created in ...
static std::shared_ptr< RBrowserWidget > CreateWidgetFor(const std::string &kind, const std::string &name, std::shared_ptr< Browsable::RElement > &element)
Create specified widget for existing object.
static std::shared_ptr< RBrowserWidget > CreateWidget(const std::string &kind, const std::string &name)
Create specified widget.
Abstract Web-based widget, which can be used in the RBrowser Used to embed canvas,...
const std::string & GetName() const
Web-based ROOT files and objects browser.
Definition RBrowser.hxx:26
std::unique_ptr< RBrowserTimer > fTimer
! timer to handle postponed requests
Definition RBrowser.hxx:47
RBrowserData fBrowsable
! central browsing element
Definition RBrowser.hxx:46
std::shared_ptr< RBrowserWidget > AddWidget(const std::string &kind)
Creates new widget.
Definition RBrowser.cxx:609
std::vector< std::string > GetRootHistory()
Get content of history file.
Definition RBrowser.cxx:698
void AddInitWidget(const std::string &kind)
Create new widget and send init message to the client.
Definition RBrowser.cxx:659
std::vector< std::vector< std::string > > fPostponed
! postponed messages, handled in timer
Definition RBrowser.hxx:48
std::shared_ptr< RWebWindow > fWebWindow
! web window to browser
Definition RBrowser.hxx:44
std::string ProcessDrop(unsigned connid, std::vector< std::string > &args)
Process drop of item in the current tab.
Definition RBrowser.cxx:546
int fWidgetCnt
! counter for created widgets
Definition RBrowser.hxx:39
std::shared_ptr< RBrowserWidget > GetActiveWidget() const
Definition RBrowser.hxx:53
std::string ProcessDblClick(unsigned connid, std::vector< std::string > &args)
Process dbl click on browser item.
Definition RBrowser.cxx:442
void ClearOnClose(const std::shared_ptr< void > &handle)
Set handle which will be cleared when connection is closed.
std::string fActiveWidgetName
! name of active widget
Definition RBrowser.hxx:37
RBrowser(bool use_rcanvas=false)
constructor
Definition RBrowser.cxx:286
void SetWorkingPath(const std::string &path)
Set working path in the browser.
void Hide()
hide Browser
Definition RBrowser.cxx:587
std::string NewWidgetMsg(std::shared_ptr< RBrowserWidget > &widget)
Create message which send to client to create new widget.
Definition RBrowser.cxx:815
bool fCatchWindowShow
! if arbitrary RWebWindow::Show calls should be catched by browser
Definition RBrowser.hxx:36
std::string fPromptFileOutput
! file name for prompt output
Definition RBrowser.hxx:40
void Show(const RWebDisplayArgs &args="", bool always_start_new_browser=false)
show Browser in specified place
Definition RBrowser.cxx:577
std::string GetCurrentWorkingDirectory()
Return the current directory of ROOT.
Definition RBrowser.cxx:807
void SetUseRCanvas(bool on=true)
Definition RBrowser.hxx:83
std::shared_ptr< RBrowserWidget > FindWidget(const std::string &name, const std::string &kind="") const
Find widget by name or kind.
Definition RBrowser.cxx:669
std::shared_ptr< RBrowserWidget > AddCatchedWidget(RWebWindow *win, const std::string &kind)
Add widget catched from external scripts.
Definition RBrowser.cxx:639
bool GetUseRCanvas() const
Definition RBrowser.hxx:82
std::vector< std::shared_ptr< RBrowserWidget > > fWidgets
! all browser widgets
Definition RBrowser.hxx:38
virtual ~RBrowser()
destructor
Definition RBrowser.cxx:381
void ProcessSaveFile(const std::string &fname, const std::string &content)
Process file save command in the editor.
Definition RBrowser.cxx:417
float fLastProgressSend
! last value of send progress
Definition RBrowser.hxx:41
std::string GetWindowUrl(bool remote)
Return URL parameter for the window showing ROOT Browser See ROOT::RWebWindow::GetUrl docu for more d...
Definition RBrowser.cxx:597
std::string ProcessBrowserRequest(const std::string &msg)
Process browser request.
Definition RBrowser.cxx:393
std::vector< std::string > GetRootLogs()
Get content of log file.
Definition RBrowser.cxx:721
void ProcessMsg(unsigned connid, const std::string &arg)
Process received message from the client.
Definition RBrowser.cxx:877
void CheckWidgtesModified(unsigned connid)
Check if any widget was modified and update if necessary.
Definition RBrowser.cxx:825
void CloseTab(const std::string &name)
Close and delete specified widget.
Definition RBrowser.cxx:685
void ProcessPostponedRequests()
Process postponed requests - decouple from websocket handling Only requests which can take longer tim...
Definition RBrowser.cxx:847
unsigned fConnId
! default connection id
Definition RBrowser.hxx:33
bool ActivateWidget(const std::string &title, const std::string &kind="")
Activate widget in RBrowser One should specify title and (optionally) kind of widget like "tcanvas" o...
void SendInitMsg(unsigned connid)
Process client connect.
Definition RBrowser.cxx:739
void SendProgress(unsigned connid, float progr)
Send generic progress message to the web window Should show progress bar on client side.
Definition RBrowser.cxx:788
long long fLastProgressSendTm
! time when last progress message was send
Definition RBrowser.hxx:42
void ProcessRunMacro(const std::string &file_path)
Process run macro command in the editor.
Definition RBrowser.cxx:428
static bool IsMessageToStartDialog(const std::string &msg)
Check if this could be the message send by client to start new file dialog If returns true,...
static std::shared_ptr< RFileDialog > Embed(const std::shared_ptr< RWebWindow > &window, unsigned connid, const std::string &args)
Create dialog instance to use as embedded dialog inside other widget Embedded dialog started on the c...
const_iterator begin() const
const_iterator end() const
Holds different arguments for starting browser with RWebDisplayHandle::Display() method.
const std::string & GetWidgetKind() const
returns widget kind
Represents web window, which can be shown in web browser or any other supported environment.
std::string GetUrl(bool remote=true)
Return URL string to connect web window URL typically includes extra parameters required for connecti...
static std::shared_ptr< RWebWindow > Create()
Create new RWebWindow Using default RWebWindowsManager.
static unsigned ShowWindow(std::shared_ptr< RWebWindow > window, const RWebDisplayArgs &args="")
Static method to show web window Has to be used instead of RWebWindow::Show() when window potentially...
This class creates the ROOT Application Environment that interfaces to the windowing system eventloop...
static TString ToJSON(const T *obj, Int_t compact=0, const char *member_name=nullptr)
Definition TBufferJSON.h:75
@ kNoSpaces
no new lines plus remove all spaces around "," and ":" symbols
Definition TBufferJSON.h:39
virtual Int_t GetValue(const char *name, Int_t dflt) const
Returns the integer value for a resource.
Definition TEnv.cxx:491
Definition TRint.h:31
Basic string class.
Definition TString.h:138
const char * Data() const
Definition TString.h:384
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:2385
void Form(const char *fmt,...)
Formats a string using a printf style format descriptor.
Definition TString.cxx:2363
virtual Int_t RedirectOutput(const char *name, const char *mode="a", RedirectHandle_t *h=nullptr)
Redirect standard output (stdout, stderr) to the specified file.
Definition TSystem.cxx:1728
virtual int GetPid()
Get process id.
Definition TSystem.cxx:718
virtual TTime Now()
Get current time in milliseconds since 0:00 Jan 1 1995.
Definition TSystem.cxx:463
virtual const char * UnixPathName(const char *unixpathname)
Convert from a local pathname to a Unix pathname.
Definition TSystem.cxx:1075
virtual const char * HomeDirectory(const char *userName=nullptr)
Return the user's home directory.
Definition TSystem.cxx:899
virtual int Unlink(const char *name)
Unlink, i.e.
Definition TSystem.cxx:1394
virtual const char * TempDirectory() const
Return a user configured or systemwide directory to create temporary files in.
Definition TSystem.cxx:1495
Handles synchronous and a-synchronous timer events.
Definition TTimer.h:51
TLine * line
Namespace for new ROOT classes and functions.
ROOT::RLogChannel & BrowserLog()
Log channel for Browser diagnostics.