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 if (!fWebWindow)
305 return;
306
307 fWebWindow->SetDefaultPage("file:rootui5sys/browser/browser.html");
308
309 std::string sortby = gEnv->GetValue("WebGui.Browser.SortBy", "name"),
310 reverse = gEnv->GetValue("WebGui.Browser.Reverse", "no"),
311 hidden = gEnv->GetValue("WebGui.Browser.ShowHidden", "no"),
312 lastcycle = gEnv->GetValue("WebGui.Browser.LastCycle", "");
313
314 if (sortby != "name" && sortby != "size" && sortby != "none")
315 sortby = "name";
316
317 reverse = (reverse == "on" || reverse == "yes" || reverse == "1") ? "true" : "false";
318 hidden = (hidden == "on" || hidden == "yes" || hidden == "1") ? "true" : "false";
319 if (lastcycle == "on" || lastcycle == "yes" || lastcycle == "1")
321 else if (lastcycle == "off" || lastcycle == "no" || lastcycle == "0")
323
324 fWebWindow->SetUserArgs(TString::Format("{ sort: \"%s\", reverse: %s, hidden: %s }", sortby.c_str(), reverse.c_str(), hidden.c_str()).Data());
325
326 // this is call-back, invoked when message received via websocket
327 fWebWindow->SetCallBacks([this](unsigned connid) { fConnId = connid; SendInitMsg(connid); },
328 [this](unsigned connid, const std::string &arg) { ProcessMsg(connid, arg); });
329 fWebWindow->SetGeometry(1200, 700); // configure predefined window geometry
330 fWebWindow->SetConnLimit(1); // the only connection is allowed
331 fWebWindow->SetMaxQueueLength(30); // number of allowed entries in the window queue
332
333 fWebWindow->GetManager()->SetShowCallback([this](RWebWindow &win, const RWebDisplayArgs &args) -> bool {
334
335 std::string kind;
336
337 if (args.GetWidgetKind() == "RCanvas")
338 kind = "rcanvas";
339 else if (args.GetWidgetKind() == "TCanvas")
340 kind = "tcanvas";
341 else if (args.GetWidgetKind() == "RGeomViewer")
342 kind = "geom";
343 else if (args.GetWidgetKind() == "RTreeViewer")
344 kind = "tree";
345
346 if (!fWebWindow || !fCatchWindowShow || kind.empty())
347 return false;
348
349 // before create new widget check if other disappear
351
353 if (widget) {
354 widget->fBrowser = this;
355 fWidgets.emplace_back(widget);
356 fActiveWidgetName = widget->GetName();
357 } else {
358 widget = AddCatchedWidget(&win, kind);
359 }
360
361 if (widget && fWebWindow && (fWebWindow->NumConnections() > 0))
362 fWebWindow->Send(0, NewWidgetMsg(widget));
363
364 return widget ? true : false;
365 });
366
367 fWebWindow->GetManager()->SetDeleteCallback([this](RWebWindow &win) -> void {
368 for (auto &widget : fWidgets) {
369 auto catched = dynamic_cast<RBrowserCatchedWidget *>(widget.get());
370 if (catched && (catched->fWindow == &win))
371 catched->fWindow = nullptr;
372 }
373
374 if (fWebWindow)
376 });
377
378 Show();
379}
380
381//////////////////////////////////////////////////////////////////////////////////////////////
382/// destructor
383
385{
386 if (fWebWindow) {
387 fWebWindow->GetManager()->SetShowCallback(nullptr);
388 fWebWindow->GetManager()->SetDeleteCallback(nullptr);
389 fWebWindow->Reset();
390 }
391}
392
393//////////////////////////////////////////////////////////////////////////////////////////////
394/// Process browser request
395
396std::string RBrowser::ProcessBrowserRequest(const std::string &msg)
397{
398 std::unique_ptr<RBrowserRequest> request;
399
400 if (msg.empty()) {
401 request = std::make_unique<RBrowserRequest>();
402 request->first = 0;
403 request->number = 100;
404 } else {
405 request = TBufferJSON::FromJSON<RBrowserRequest>(msg);
406 }
407
408 if (!request)
409 return ""s;
410
411 if (request->path.empty() && fWidgets.empty() && fBrowsable.GetWorkingPath().empty())
413
414 return "BREPL:"s + fBrowsable.ProcessRequest(*request.get());
415}
416
417/////////////////////////////////////////////////////////////////////////////////
418/// Process file save command in the editor
419
420void RBrowser::ProcessSaveFile(const std::string &fname, const std::string &content)
421{
422 if (fname.empty()) return;
423 R__LOG_DEBUG(0, BrowserLog()) << "SaveFile " << fname << " content length " << content.length();
424 std::ofstream f(fname);
425 f << content;
426}
427
428/////////////////////////////////////////////////////////////////////////////////
429/// Process run macro command in the editor
430
431void RBrowser::ProcessRunMacro(const std::string &file_path)
432{
433 if (file_path.rfind(".py") == file_path.length() - 3) {
434 TString exec;
435 exec.Form("TPython::ExecScript(\"%s\");", file_path.c_str());
436 gROOT->ProcessLine(exec.Data());
437 } else {
438 gInterpreter->ExecuteMacro(file_path.c_str());
439 }
440}
441
442/////////////////////////////////////////////////////////////////////////////////
443/// Process dbl click on browser item
444
445std::string RBrowser::ProcessDblClick(unsigned connid, std::vector<std::string> &args)
446{
447 args.pop_back(); // remove exec string, not used now
448
449 std::string opt = args.back();
450 args.pop_back(); // remove option
451
452 auto path = fBrowsable.GetWorkingPath();
453 path.insert(path.end(), args.begin(), args.end());
454
455 R__LOG_DEBUG(0, BrowserLog()) << "DoubleClick " << Browsable::RElement::GetPathAsString(path);
456
457 auto elem = fBrowsable.GetSubElement(path);
458 if (!elem) return ""s;
459
460 auto dflt_action = elem->GetDefaultAction();
461
462 // special case when canvas is clicked - always start new widget
464 std::string widget_kind;
465
466 if (elem->IsCapable(Browsable::RElement::kActDraw7))
467 widget_kind = "rcanvas";
468 else
469 widget_kind = "tcanvas";
470
471 std::string name = widget_kind + std::to_string(++fWidgetCnt);
472
474
475 if (!new_widget)
476 return ""s;
477
478 // assign back pointer
479 new_widget->fBrowser = this;
480 fWidgets.emplace_back(new_widget);
481 fActiveWidgetName = new_widget->GetName();
482
483 return NewWidgetMsg(new_widget);
484 }
485
486 // before display tree or geometry ensure that they read and cached inside element
488 elem->GetChildsIter();
489 }
490
492 Browsable::RProvider::ProgressHandle handle(elem.get(), [this, connid](float progress, void *) {
493 SendProgress(connid, progress);
494 });
495
496 auto widget = GetActiveWidget();
497 if (widget && widget->DrawElement(elem, opt)) {
498 widget->SetPath(path);
499 return widget->SendWidgetContent();
500 }
501
502 // check if element was drawn in other widget and just activate that widget
503 auto iter = std::find_if(fWidgets.begin(), fWidgets.end(),
504 [path](const std::shared_ptr<RBrowserWidget> &wg) { return path == wg->GetPath(); });
505
506 if (iter != fWidgets.end())
507 return "SELECT_WIDGET:"s + (*iter)->GetName();
508
509 // check if object can be drawn in RCanvas even when default action is drawing in TCanvas
512
513 std::string widget_kind;
514 switch(dflt_action) {
515 case Browsable::RElement::kActDraw6: widget_kind = "tcanvas"; break;
516 case Browsable::RElement::kActDraw7: widget_kind = "rcanvas"; break;
517 case Browsable::RElement::kActEdit: widget_kind = "editor"; break;
518 case Browsable::RElement::kActImage: widget_kind = "image"; break;
519 case Browsable::RElement::kActTree: widget_kind = "tree"; break;
520 case Browsable::RElement::kActGeom: widget_kind = "geom"; break;
521 default: widget_kind.clear();
522 }
523
524 if (!widget_kind.empty()) {
526 if (new_widget) {
527 // draw object before client side is created - should not be a problem
528 // after widget add in browser, connection will be established and data provided
529 if (new_widget->DrawElement(elem, opt))
530 new_widget->SetPath(path);
531 return NewWidgetMsg(new_widget);
532 }
533 }
534
535 if (elem->IsCapable(Browsable::RElement::kActBrowse) && (elem->GetNumChilds() > 0)) {
536 // remove extra index in subitems name
537 for (auto &pathelem : path)
541 }
542
543 return ""s;
544}
545
546/////////////////////////////////////////////////////////////////////////////////
547/// Process drop of item in the current tab
548
549std::string RBrowser::ProcessDrop(unsigned connid, std::vector<std::string> &args)
550{
551 auto path = fBrowsable.GetWorkingPath();
552 path.insert(path.end(), args.begin(), args.end());
553
554 R__LOG_DEBUG(0, BrowserLog()) << "DoubleClick " << Browsable::RElement::GetPathAsString(path);
555
556 auto elem = fBrowsable.GetSubElement(path);
557 if (!elem) return ""s;
558
560 Browsable::RProvider::ProgressHandle handle(elem.get(), [this, connid](float progress, void *) {
561 SendProgress(connid, progress);
562 });
563
564 auto widget = GetActiveWidget();
565 if (widget && widget->DrawElement(elem, "<append>")) {
566 widget->SetPath(path);
567 return widget->SendWidgetContent();
568 }
569
570 return ""s;
571}
572
573
574/////////////////////////////////////////////////////////////////////////////////
575/// Show or update RBrowser in web window
576/// If web window already started - just refresh it like "reload" button does
577/// If no web window exists or \param always_start_new_browser configured, starts new window
578/// \param args display arguments
579
581{
582 if (!fWebWindow->NumConnections() || always_start_new_browser) {
583 fWebWindow->Show(args);
584 }
585}
586
587///////////////////////////////////////////////////////////////////////////////////////////////////////
588/// Hide ROOT Browser
589
591{
592 if (fWebWindow)
593 fWebWindow->CloseConnections();
594}
595
596///////////////////////////////////////////////////////////////////////////////////////////////////////
597/// Return URL parameter for the window showing ROOT Browser
598/// See \ref ROOT::RWebWindow::GetUrl docu for more details
599
601{
602 if (fWebWindow)
603 return fWebWindow->GetUrl(remote);
604
605 return ""s;
606}
607
608
609//////////////////////////////////////////////////////////////////////////////////////////////
610/// Creates new widget
611
612std::shared_ptr<RBrowserWidget> RBrowser::AddWidget(const std::string &kind)
613{
614 std::string name = kind + std::to_string(++fWidgetCnt);
615
616 std::shared_ptr<RBrowserWidget> widget;
617
618 if (kind == "editor"s)
619 widget = std::make_shared<RBrowserEditorWidget>(name, true);
620 else if (kind == "image"s)
621 widget = std::make_shared<RBrowserEditorWidget>(name, false);
622 else if (kind == "info"s)
623 widget = std::make_shared<RBrowserInfoWidget>(name);
624 else
626
627 if (!widget) {
628 R__LOG_ERROR(BrowserLog()) << "Fail to create widget of kind " << kind;
629 return nullptr;
630 }
631
632 widget->fBrowser = this;
633 fWidgets.emplace_back(widget);
635
636 return widget;
637}
638
639//////////////////////////////////////////////////////////////////////////////////////////////
640/// Add widget catched from external scripts
641
642std::shared_ptr<RBrowserWidget> RBrowser::AddCatchedWidget(RWebWindow *win, const std::string &kind)
643{
644 if (!win || kind.empty())
645 return nullptr;
646
647 std::string name = "catched"s + std::to_string(++fWidgetCnt);
648
649 auto widget = std::make_shared<RBrowserCatchedWidget>(name, win, kind);
650
651 fWidgets.emplace_back(widget);
652
654
655 return widget;
656}
657
658
659//////////////////////////////////////////////////////////////////////////////////////////////
660/// Create new widget and send init message to the client
661
662void RBrowser::AddInitWidget(const std::string &kind)
663{
664 auto widget = AddWidget(kind);
665 if (widget && fWebWindow && (fWebWindow->NumConnections() > 0))
666 fWebWindow->Send(0, NewWidgetMsg(widget));
667}
668
669//////////////////////////////////////////////////////////////////////////////////////////////
670/// Find widget by name or kind
671
672std::shared_ptr<RBrowserWidget> RBrowser::FindWidget(const std::string &name, const std::string &kind) const
673{
674 auto iter = std::find_if(fWidgets.begin(), fWidgets.end(),
675 [name, kind](const std::shared_ptr<RBrowserWidget> &widget) {
676 return kind.empty() ? name == widget->GetName() : kind == widget->GetKind();
677 });
678
679 if (iter != fWidgets.end())
680 return *iter;
681
682 return nullptr;
683}
684
685//////////////////////////////////////////////////////////////////////////////////////////////
686/// Close and delete specified widget
687
688void RBrowser::CloseTab(const std::string &name)
689{
690 auto iter = std::find_if(fWidgets.begin(), fWidgets.end(), [name](std::shared_ptr<RBrowserWidget> &widget) { return name == widget->GetName(); });
691 if (iter != fWidgets.end())
692 fWidgets.erase(iter);
693
694 if (fActiveWidgetName == name)
695 fActiveWidgetName.clear();
696}
697
698//////////////////////////////////////////////////////////////////////////////////////////////
699/// Get content of history file
700
701std::vector<std::string> RBrowser::GetRootHistory()
702{
703 std::vector<std::string> arr;
704
705 std::string path = gSystem->UnixPathName(gSystem->HomeDirectory());
706 path += "/.root_hist" ;
707 std::ifstream infile(path);
708
709 if (infile) {
710 std::string line;
711 while (std::getline(infile, line) && (arr.size() < 1000)) {
712 if(!(std::find(arr.begin(), arr.end(), line) != arr.end())) {
713 arr.emplace_back(line);
714 }
715 }
716 }
717
718 return arr;
719}
720
721//////////////////////////////////////////////////////////////////////////////////////////////
722/// Get content of log file
723
724std::vector<std::string> RBrowser::GetRootLogs()
725{
726 std::vector<std::string> arr;
727
728 std::ifstream infile(fPromptFileOutput);
729 if (infile) {
730 std::string line;
731 while (std::getline(infile, line) && (arr.size() < 10000)) {
732 arr.emplace_back(line);
733 }
734 }
735
736 return arr;
737}
738
739//////////////////////////////////////////////////////////////////////////////////////////////
740/// Process client connect
741
742void RBrowser::SendInitMsg(unsigned connid)
743{
744 std::vector<std::vector<std::string>> reply;
745
746 reply.emplace_back(fBrowsable.GetWorkingPath()); // first element is current path
747
748 for (auto &widget : fWidgets) {
749 widget->ResetConn();
750 reply.emplace_back(std::vector<std::string>({ widget->GetKind(), widget->GetUrl(), widget->GetName(), widget->GetTitle() }));
751 }
752
753 if (!fActiveWidgetName.empty())
754 reply.emplace_back(std::vector<std::string>({ "active"s, fActiveWidgetName }));
755
756 auto history = GetRootHistory();
757 if (history.size() > 0) {
758 history.insert(history.begin(), "history"s);
759 reply.emplace_back(history);
760 }
761
762 auto logs = GetRootLogs();
763 if (logs.size() > 0) {
764 logs.insert(logs.begin(), "logs"s);
765 reply.emplace_back(logs);
766 }
767
768 reply.emplace_back(std::vector<std::string>({
769 "drawoptions"s,
773 }));
774
775 reply.emplace_back(std::vector<std::string>({
776 "settings"s,
777 gEnv->GetValue("WebGui.Browser.Expand", "no"),
779 }));
780
781 std::string msg = "INMSG:";
783
784 fWebWindow->Send(connid, msg);
785}
786
787//////////////////////////////////////////////////////////////////////////////////////////////
788/// Send generic progress message to the web window
789/// Should show progress bar on client side
790
791void RBrowser::SendProgress(unsigned connid, float progr)
792{
793 long long millisec = gSystem->Now();
794
795 // let process window events
796 fWebWindow->Sync();
797
798 if ((!fLastProgressSendTm || millisec > fLastProgressSendTm - 200) && (progr > fLastProgressSend + 0.04) && fWebWindow->CanSend(connid)) {
799 fWebWindow->Send(connid, "PROGRESS:"s + std::to_string(progr));
800
803 }
804}
805
806
807//////////////////////////////////////////////////////////////////////////////////////////////
808/// Return the current directory of ROOT
809
811{
812 return "WORKPATH:"s + TBufferJSON::ToJSON(&fBrowsable.GetWorkingPath()).Data();
813}
814
815//////////////////////////////////////////////////////////////////////////////////////////////
816/// Create message which send to client to create new widget
817
818std::string RBrowser::NewWidgetMsg(std::shared_ptr<RBrowserWidget> &widget)
819{
820 std::vector<std::string> arr = { widget->GetKind(), widget->GetUrl(), widget->GetName(), widget->GetTitle(),
822 return "NEWWIDGET:"s + TBufferJSON::ToJSON(&arr, TBufferJSON::kNoSpaces).Data();
823}
824
825//////////////////////////////////////////////////////////////////////////////////////////////
826/// Check if any widget was modified and update if necessary
827
829{
830 std::vector<std::string> del_names;
831
832 for (auto &widget : fWidgets)
833 if (!widget->IsValid())
834 del_names.push_back(widget->GetName());
835
836 if (!del_names.empty())
837 fWebWindow->Send(connid, "CLOSE_WIDGETS:"s + TBufferJSON::ToJSON(&del_names, TBufferJSON::kNoSpaces).Data());
838
839 for (auto name : del_names)
840 CloseTab(name);
841
842 for (auto &widget : fWidgets)
843 widget->CheckModified();
844}
845
846//////////////////////////////////////////////////////////////////////////////////////////////
847/// Process postponed requests - decouple from websocket handling
848/// Only requests which can take longer time should be postponed
849
851{
852 if (fPostponed.empty())
853 return;
854
855 auto arr = fPostponed[0];
856 fPostponed.erase(fPostponed.begin(), fPostponed.begin()+1);
857 if (fPostponed.empty())
858 fTimer->TurnOff();
859
860 std::string reply;
861 unsigned connid = std::stoul(arr.back()); arr.pop_back();
862 std::string kind = arr.back(); arr.pop_back();
863
864 if (kind == "DBLCLK") {
865 reply = ProcessDblClick(connid, arr);
866 if (reply.empty()) reply = "NOPE";
867 } else if (kind == "DROP") {
868 reply = ProcessDrop(connid, arr);
869 if (reply.empty()) reply = "NOPE";
870 }
871
872 if (!reply.empty())
873 fWebWindow->Send(connid, reply);
874}
875
876
877//////////////////////////////////////////////////////////////////////////////////////////////
878/// Process received message from the client
879
880void RBrowser::ProcessMsg(unsigned connid, const std::string &arg0)
881{
882 R__LOG_DEBUG(0, BrowserLog()) << "ProcessMsg len " << arg0.length() << " substr(30) " << arg0.substr(0, 30);
883
884 std::string kind, msg;
885 auto pos = arg0.find(":");
886 if (pos == std::string::npos) {
887 kind = arg0;
888 } else {
889 kind = arg0.substr(0, pos);
890 msg = arg0.substr(pos+1);
891 }
892
893 if (kind == "QUIT_ROOT") {
894
895 fWebWindow->TerminateROOT();
896
897 } else if (kind == "BRREQ") {
898 // central place for processing browser requests
900 if (!json.empty()) fWebWindow->Send(connid, json);
901
902 } else if (kind == "LASTCYCLE") {
903 // when changed on clients side
905
906 } else if (kind == "DBLCLK") {
907
908 auto arr = TBufferJSON::FromJSON<std::vector<std::string>>(msg);
909 if (arr && (arr->size() > 2)) {
910 arr->push_back(kind);
911 arr->push_back(std::to_string(connid));
912 fPostponed.push_back(*arr);
913 if (fPostponed.size() == 1)
914 fTimer->TurnOn();
915 } else {
916 fWebWindow->Send(connid, "NOPE");
917 }
918
919 } else if (kind == "DROP") {
920
921 auto arr = TBufferJSON::FromJSON<std::vector<std::string>>(msg);
922 if (arr && arr->size()) {
923 arr->push_back(kind);
924 arr->push_back(std::to_string(connid));
925 fPostponed.push_back(*arr);
926 if (fPostponed.size() == 1)
927 fTimer->TurnOn();
928 } else {
929 fWebWindow->Send(connid, "NOPE");
930 }
931
932 } else if (kind == "WIDGET_SELECTED") {
934 auto widget = GetActiveWidget();
935 if (widget) {
936 auto reply = widget->SendWidgetContent();
937 if (!reply.empty()) fWebWindow->Send(connid, reply);
938 }
939 } else if (kind == "CLOSE_TAB") {
940 CloseTab(msg);
941 } else if (kind == "GETWORKPATH") {
942 fWebWindow->Send(connid, GetCurrentWorkingDirectory());
943 } else if (kind == "CHPATH") {
944 auto path = TBufferJSON::FromJSON<Browsable::RElementPath_t>(msg);
945 if (path) fBrowsable.SetWorkingPath(*path);
946 fWebWindow->Send(connid, GetCurrentWorkingDirectory());
947 } else if (kind == "CMD") {
948 std::string sPrompt = "root []";
949 TApplication *app = gROOT->GetApplication();
950 if (app->InheritsFrom("TRint")) {
951 sPrompt = ((TRint*)gROOT->GetApplication())->GetPrompt();
952 Gl_histadd((char *)msg.c_str());
953 }
954
955 std::ofstream ofs(fPromptFileOutput, std::ofstream::out | std::ofstream::app);
956 ofs << sPrompt << msg << std::endl;
957 ofs.close();
958
960 gROOT->ProcessLine(msg.c_str());
961 gSystem->RedirectOutput(nullptr);
962
963 if (msg == ".g"s) {
964 auto widget = std::dynamic_pointer_cast<RBrowserInfoWidget>(FindWidget(""s, "info"s));
965 if (!widget) {
966 auto new_widget = AddWidget("info"s);
967 fWebWindow->Send(connid, NewWidgetMsg(new_widget));
968 widget = std::dynamic_pointer_cast<RBrowserInfoWidget>(new_widget);
969 } else if (fActiveWidgetName != widget->GetName()) {
970 fWebWindow->Send(connid, "SELECT_WIDGET:"s + widget->GetName());
971 fActiveWidgetName = widget->GetName();
972 }
973
974 if (widget)
975 widget->RefreshFromLogs(sPrompt + msg, GetRootLogs());
976 }
977
978 CheckWidgtesModified(connid);
979 } else if (kind == "GETHISTORY") {
980
981 auto history = GetRootHistory();
982
983 fWebWindow->Send(connid, "HISTORY:"s + TBufferJSON::ToJSON(&history, TBufferJSON::kNoSpaces).Data());
984 } else if (kind == "GETLOGS") {
985
986 auto logs = GetRootLogs();
987 fWebWindow->Send(connid, "LOGS:"s + TBufferJSON::ToJSON(&logs, TBufferJSON::kNoSpaces).Data());
988
990
992
993 } else if (kind == "SYNCEDITOR") {
994 auto arr = TBufferJSON::FromJSON<std::vector<std::string>>(msg);
995 if (arr && (arr->size() > 4)) {
996 auto editor = std::dynamic_pointer_cast<RBrowserEditorWidget>(FindWidget(arr->at(0)));
997 if (editor) {
998 editor->fFirstSend = true;
999 editor->fTitle = arr->at(1);
1000 editor->fFileName = arr->at(2);
1001 if (!arr->at(3).empty()) editor->fContent = arr->at(4);
1002 if ((arr->size() == 6) && (arr->at(5) == "SAVE"))
1003 ProcessSaveFile(editor->fFileName, editor->fContent);
1004 if ((arr->size() == 6) && (arr->at(5) == "RUN")) {
1005 ProcessSaveFile(editor->fFileName, editor->fContent);
1006 ProcessRunMacro(editor->fFileName);
1007 CheckWidgtesModified(connid);
1008 }
1009 }
1010 }
1011 } else if (kind == "GETINFO") {
1012 auto info = std::dynamic_pointer_cast<RBrowserInfoWidget>(FindWidget(msg));
1013 if (info) {
1014 info->Refresh();
1015 fWebWindow->Send(connid, info->SendWidgetContent());
1016 }
1017 } else if (kind == "NEWWIDGET") {
1018 auto widget = AddWidget(msg);
1019 if (widget)
1020 fWebWindow->Send(connid, NewWidgetMsg(widget));
1021 } else if (kind == "NEWCHANNEL") {
1022 auto arr = TBufferJSON::FromJSON<std::vector<std::string>>(msg);
1023 if (arr && (arr->size() == 2)) {
1024 auto widget = FindWidget((*arr)[0]);
1025 if (widget)
1026 RWebWindow::ShowWindow(widget->GetWindow(), { fWebWindow, connid, std::stoi((*arr)[1]) });
1027 }
1028 } else if (kind == "CDWORKDIR") {
1030 if (fBrowsable.GetWorkingPath() != wrkdir) {
1032 } else {
1034 }
1035 fWebWindow->Send(connid, GetCurrentWorkingDirectory());
1036 } else if (kind == "OPTIONS") {
1037 auto arr = TBufferJSON::FromJSON<std::vector<std::string>>(msg);
1038 if (arr && (arr->size() == 3)) {
1042 }
1043 }
1044}
1045
1046//////////////////////////////////////////////////////////////////////////////////////////////
1047/// Set working path in the browser
1048
1049void RBrowser::SetWorkingPath(const std::string &path)
1050{
1051 auto p = Browsable::RElement::ParsePath(path);
1053 if (elem) {
1055 if (fWebWindow && (fWebWindow->NumConnections() > 0))
1057 }
1058}
1059
1060//////////////////////////////////////////////////////////////////////////////////////////////
1061/// Activate widget in RBrowser
1062/// One should specify title and (optionally) kind of widget like "tcanvas" or "geom"
1063
1064bool RBrowser::ActivateWidget(const std::string &title, const std::string &kind)
1065{
1066 if (title.empty())
1067 return false;
1068
1069 for (auto &widget : fWidgets) {
1070
1071 if (widget->GetTitle() != title)
1072 continue;
1073
1074 if (!kind.empty() && (widget->GetKind() != kind))
1075 continue;
1076
1077 if (fWebWindow)
1078 fWebWindow->Send(0, "SELECT_WIDGET:"s + widget->GetName());
1079 else
1080 fActiveWidgetName = widget->GetName();
1081 return true;
1082 }
1083
1084 return false;
1085}
1086
1087//////////////////////////////////////////////////////////////////////////////////////////////
1088/// Set handle which will be cleared when connection is closed
1089
1090void RBrowser::ClearOnClose(const std::shared_ptr<void> &handle)
1091{
1092 fWebWindow->SetClearOnClose(handle);
1093}
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:612
std::vector< std::string > GetRootHistory()
Get content of history file.
Definition RBrowser.cxx:701
void AddInitWidget(const std::string &kind)
Create new widget and send init message to the client.
Definition RBrowser.cxx:662
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:549
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:445
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:590
std::string NewWidgetMsg(std::shared_ptr< RBrowserWidget > &widget)
Create message which send to client to create new widget.
Definition RBrowser.cxx:818
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:580
std::string GetCurrentWorkingDirectory()
Return the current directory of ROOT.
Definition RBrowser.cxx:810
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:672
std::shared_ptr< RBrowserWidget > AddCatchedWidget(RWebWindow *win, const std::string &kind)
Add widget catched from external scripts.
Definition RBrowser.cxx:642
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:384
void ProcessSaveFile(const std::string &fname, const std::string &content)
Process file save command in the editor.
Definition RBrowser.cxx:420
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:600
std::string ProcessBrowserRequest(const std::string &msg)
Process browser request.
Definition RBrowser.cxx:396
std::vector< std::string > GetRootLogs()
Get content of log file.
Definition RBrowser.cxx:724
void ProcessMsg(unsigned connid, const std::string &arg)
Process received message from the client.
Definition RBrowser.cxx:880
void CheckWidgtesModified(unsigned connid)
Check if any widget was modified and update if necessary.
Definition RBrowser.cxx:828
void CloseTab(const std::string &name)
Close and delete specified widget.
Definition RBrowser.cxx:688
void ProcessPostponedRequests()
Process postponed requests - decouple from websocket handling Only requests which can take longer tim...
Definition RBrowser.cxx:850
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:742
void SendProgress(unsigned connid, float progr)
Send generic progress message to the web window Should show progress bar on client side.
Definition RBrowser.cxx:791
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:431
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:490
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:2384
void Form(const char *fmt,...)
Formats a string using a printf style format descriptor.
Definition TString.cxx:2362
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:1726
virtual int GetPid()
Get process id.
Definition TSystem.cxx:716
virtual TTime Now()
Get current time in milliseconds since 0:00 Jan 1 1995.
Definition TSystem.cxx:461
virtual const char * UnixPathName(const char *unixpathname)
Convert from a local pathname to a Unix pathname.
Definition TSystem.cxx:1073
virtual const char * HomeDirectory(const char *userName=nullptr)
Return the user's home directory.
Definition TSystem.cxx:897
virtual int Unlink(const char *name)
Unlink, i.e.
Definition TSystem.cxx:1392
virtual const char * TempDirectory() const
Return a user configured or systemwide directory to create temporary files in.
Definition TSystem.cxx:1493
Handles synchronous and a-synchronous timer events.
Definition TTimer.h:51
TLine * line
ROOT::RLogChannel & BrowserLog()
Log channel for Browser diagnostics.