| // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| part of app; | |
| /// The observatory application. Instances of this are created and owned | |
| /// by the observatory_application custom element. | |
| class ObservatoryApplication { | |
| static late ObservatoryApplication app; | |
| final RenderingQueue queue = new RenderingQueue(); | |
| final TargetRepository targets = new TargetRepository(isConnectedVMTarget); | |
| final EventRepository events = new EventRepository(); | |
| final NotificationRepository notifications = new NotificationRepository(); | |
| final _pageRegistry = <Page>[]; | |
| late LocationManager _locationManager; | |
| LocationManager get locationManager => _locationManager; | |
| Page? currentPage; | |
| bool _vmConnected = false; | |
| VM? _vm; | |
| VM get vm => _vm!; | |
| static bool isConnectedVMTarget(M.Target target) { | |
| if (app._vm is CommonWebSocketVM) { | |
| if ((app._vm as CommonWebSocketVM).target == target) { | |
| return app._vm!.isConnected; | |
| } | |
| } | |
| return false; | |
| } | |
| _switchVM(VM? newVM) { | |
| final VM? oldVM = _vm; | |
| Logger.root.info('_switchVM from:${oldVM} to:${newVM}'); | |
| if (oldVM == newVM) { | |
| // Do nothing. | |
| return; | |
| } | |
| if (oldVM != null) { | |
| print('disconnecting from the old VM ${oldVM}--'); | |
| // Disconnect from current VM. | |
| stopGCEventListener(); | |
| notifications.deleteAll(); | |
| oldVM.disconnect(); | |
| } | |
| if (newVM != null) { | |
| // Mark that we haven't connected yet. | |
| _vmConnected = false; | |
| // On connect: | |
| newVM.onConnect.then((_) async { | |
| // We connected. | |
| _vmConnected = true; | |
| notifications.deleteDisconnectEvents(); | |
| await newVM.load(); | |
| // TODO(cbernaschina) smart connection of streams in the events object. | |
| newVM.listenEventStream(VM.kVMStream, _onEvent); | |
| newVM.listenEventStream(VM.kIsolateStream, _onEvent); | |
| newVM.listenEventStream(VM.kDebugStream, _onEvent); | |
| newVM.listenEventStream(VM.kServiceStream, _onEvent); | |
| }); | |
| // On disconnect: | |
| newVM.onDisconnect.then((String reason) { | |
| if (this.vm != newVM) { | |
| // This disconnect event occurred *after* a new VM was installed. | |
| return; | |
| } | |
| // Let anyone looking at the targets know that we have disconnected | |
| // from one. | |
| targets.emitDisconnectEvent(); | |
| if (!_vmConnected) { | |
| // Connection error. Navigate back to the connect page. | |
| Logger.root.info('Connection failed, navigating to VM connect page.'); | |
| // Clear the vm. | |
| _vm = null; | |
| app.locationManager.go(Uris.vmConnect()); | |
| } else { | |
| // Disconnect. Stay at the current page and push an a connection | |
| // closed event. | |
| Logger.root.info('Lost an existing connection to a VM'); | |
| events.add(new ConnectionClosedEvent(new DateTime.now(), reason)); | |
| } | |
| }); | |
| } | |
| _vm = newVM; | |
| } | |
| StreamSubscription? _gcSubscription; | |
| StreamSubscription? _loggingSubscription; | |
| Future startGCEventListener() async { | |
| if (_gcSubscription != null || _vm == null) { | |
| return; | |
| } | |
| _gcSubscription = await _vm!.listenEventStream(VM.kGCStream, _onEvent); | |
| } | |
| Future startLoggingEventListener() async { | |
| if (_loggingSubscription != null || _vm == null) { | |
| return; | |
| } | |
| _loggingSubscription = await _vm!.listenEventStream( | |
| Isolate.kLoggingStream, | |
| _onEvent, | |
| ); | |
| } | |
| Future stopGCEventListener() async { | |
| if (_gcSubscription == null) { | |
| return; | |
| } | |
| _gcSubscription!.cancel(); | |
| _gcSubscription = null; | |
| } | |
| Future stopLoggingEventListener() async { | |
| if (_loggingSubscription == null) { | |
| return; | |
| } | |
| _loggingSubscription!.cancel(); | |
| _loggingSubscription = null; | |
| } | |
| final ObservatoryApplicationElement rootElement; | |
| ServiceObject? lastErrorOrException; | |
| void _initOnce() { | |
| app = this; | |
| _registerPages(); | |
| // Visit the current page. | |
| locationManager._visit(); | |
| } | |
| void _deletePauseEvents(e) { | |
| notifications.deletePauseEvents(isolate: e.isolate); | |
| } | |
| void _addNotification(M.Event e) { | |
| notifications.add(new EventNotification(e)); | |
| } | |
| void _onEvent(ServiceEvent event) { | |
| assert(event.kind != ServiceEvent.kNone); | |
| M.Event? e = createEventFromServiceEvent(event); | |
| if (e != null) { | |
| events.add(e); | |
| } | |
| } | |
| void _registerPages() { | |
| _pageRegistry.add(new VMPage(this)); | |
| _pageRegistry.add(new FlagsPage(this)); | |
| _pageRegistry.add(new NativeMemoryProfilerPage(this)); | |
| _pageRegistry.add(new InspectPage(this)); | |
| _pageRegistry.add(new ClassTreePage(this)); | |
| _pageRegistry.add(new DebuggerPage(this)); | |
| _pageRegistry.add(new ObjectStorePage(this)); | |
| _pageRegistry.add(new CpuProfilerPage(this)); | |
| _pageRegistry.add(new TableCpuProfilerPage(this)); | |
| _pageRegistry.add(new AllocationProfilerPage(this)); | |
| _pageRegistry.add(new HeapMapPage(this)); | |
| _pageRegistry.add(new HeapSnapshotPage(this)); | |
| _pageRegistry.add(new VMConnectPage(this)); | |
| _pageRegistry.add(new IsolateReconnectPage(this)); | |
| _pageRegistry.add(new ErrorViewPage(this)); | |
| _pageRegistry.add(new MetricsPage(this)); | |
| _pageRegistry.add(new PersistentHandlesPage(this)); | |
| _pageRegistry.add(new PortsPage(this)); | |
| _pageRegistry.add(new LoggingPage(this)); | |
| _pageRegistry.add(new TimelinePage(this)); | |
| _pageRegistry.add(new ProcessSnapshotPage(this)); | |
| // Note that ErrorPage must be the last entry in the list as it is | |
| // the catch all. | |
| _pageRegistry.add(new ErrorPage(this)); | |
| } | |
| void _visit(Uri uri, Map<String, String> internalArguments) { | |
| if (internalArguments['trace'] != null) { | |
| var traceArg = internalArguments['trace']; | |
| if (traceArg == 'on') { | |
| Tracer.start(); | |
| } else if (traceArg == 'off') { | |
| Tracer.stop(); | |
| } | |
| } | |
| if (Tracer.current != null) { | |
| Tracer.current!.reset(); | |
| } | |
| for (var i = 0; i < _pageRegistry.length; i++) { | |
| var page = _pageRegistry[i]; | |
| if (page.canVisit(uri)) { | |
| _installPage(page); | |
| page.visit(uri, internalArguments); | |
| return; | |
| } | |
| } | |
| throw new ArgumentError.value(uri, 'uri'); | |
| } | |
| /// Set the Observatory application page. | |
| void _installPage(Page page) { | |
| if (currentPage == page) { | |
| // Already installed. | |
| return; | |
| } | |
| if (currentPage != null) { | |
| Logger.root.info('Uninstalling page: $currentPage'); | |
| currentPage!.onUninstall(); | |
| // Clear children. | |
| rootElement.removeChildren(); | |
| } | |
| Logger.root.info('Installing page: $page'); | |
| try { | |
| page.onInstall(); | |
| } catch (e) { | |
| Logger.root.severe('Failed to install page: $e'); | |
| } | |
| // Add new page. | |
| rootElement.appendChild(page.element!); | |
| // Remember page. | |
| currentPage = page; | |
| } | |
| ObservatoryApplication(this.rootElement) { | |
| _locationManager = new LocationManager(this); | |
| targets.onChange.listen((TargetChangeEvent e) { | |
| if (e.disconnected) { | |
| // We don't care about disconnected events. | |
| return; | |
| } | |
| if (targets.current == null) { | |
| _switchVM(null); | |
| } else { | |
| final bool currentTarget = | |
| (_vm as WebSocketVM?)?.target == targets.current; | |
| final bool currentTargetConnected = (_vm != null) && _vm!.isConnected; | |
| if (!currentTarget || !currentTargetConnected) { | |
| _switchVM(new WebSocketVM(targets.current!)); | |
| _vm!.onConnect.then((_) { | |
| app.locationManager.go(Uris.vm()); | |
| }); | |
| _vm!.load(); | |
| } else if (currentTargetConnected) { | |
| app.locationManager.go(Uris.vm()); | |
| } | |
| } | |
| }); | |
| Logger.root.info('Setting initial target to ${targets.current!.name}'); | |
| _switchVM(new WebSocketVM(targets.current!)); | |
| _initOnce(); | |
| // delete pause events. | |
| events.onIsolateExit.listen(_deletePauseEvents); | |
| events.onResume.listen(_deletePauseEvents); | |
| events.onPauseStart.listen(_deletePauseEvents); | |
| events.onPauseExit.listen(_deletePauseEvents); | |
| events.onPauseBreakpoint.listen(_deletePauseEvents); | |
| events.onPauseInterrupted.listen(_deletePauseEvents); | |
| events.onPauseException.listen(_deletePauseEvents); | |
| // show notification for an event. | |
| events.onIsolateReload.listen(_addNotification); | |
| events.onPauseExit.listen(_addNotification); | |
| events.onPauseBreakpoint.listen(_addNotification); | |
| events.onPauseInterrupted.listen(_addNotification); | |
| events.onPauseException.listen(_addNotification); | |
| events.onInspect.listen(_addNotification); | |
| events.onConnectionClosed.listen(_addNotification); | |
| } | |
| void handleException(e, st) { | |
| if (e is ServerRpcException) { | |
| if (e.code == ServerRpcException.kFeatureDisabled) return; | |
| if (e.code == ServerRpcException.kIsolateMustBePaused) return; | |
| if (e.code == ServerRpcException.kCannotAddBreakpoint) return; | |
| Logger.root.fine('Dropping exception: ${e}\n${st}'); | |
| } | |
| // TODO(turnidge): Report this failure via analytics. | |
| Logger.root.warning('Caught exception: ${e}\n${st}'); | |
| notifications.add(new ExceptionNotification(e, stacktrace: st)); | |
| } | |
| // This map keeps track of which curly-blocks have been expanded by the user. | |
| Map<String, bool> expansions = {}; | |
| } |