| // 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. |
| |
| import 'dart:async'; |
| |
| import 'package:web/web.dart'; |
| |
| import '../../models.dart' as M; |
| import 'helpers/custom_element.dart'; |
| import 'helpers/element_utils.dart'; |
| import 'helpers/rendering_scheduler.dart'; |
| |
| enum ProfileTreeMode { code, function } |
| |
| class StackTraceTreeConfigChangedEvent { |
| final StackTraceTreeConfigElement element; |
| StackTraceTreeConfigChangedEvent(this.element); |
| } |
| |
| class StackTraceTreeConfigElement extends CustomElement implements Renderable { |
| late RenderingScheduler<StackTraceTreeConfigElement> _r; |
| |
| Stream<RenderedEvent<StackTraceTreeConfigElement>> get onRendered => |
| _r.onRendered; |
| |
| StreamController<StackTraceTreeConfigChangedEvent> _onModeChange = |
| new StreamController<StackTraceTreeConfigChangedEvent>.broadcast(); |
| StreamController<StackTraceTreeConfigChangedEvent> _onDirectionChange = |
| new StreamController<StackTraceTreeConfigChangedEvent>.broadcast(); |
| StreamController<StackTraceTreeConfigChangedEvent> _onFilterChange = |
| new StreamController<StackTraceTreeConfigChangedEvent>.broadcast(); |
| Stream<StackTraceTreeConfigChangedEvent> get onModeChange => |
| _onModeChange.stream; |
| Stream<StackTraceTreeConfigChangedEvent> get onDirectionChange => |
| _onDirectionChange.stream; |
| Stream<StackTraceTreeConfigChangedEvent> get onFilterChange => |
| _onFilterChange.stream; |
| |
| late bool _showMode; |
| late bool _showDirection; |
| late bool _showFilter; |
| late ProfileTreeMode _mode; |
| late M.ProfileTreeDirection _direction; |
| late String _filter; |
| |
| bool get showMode => _showMode; |
| bool get showDirection => _showDirection; |
| bool get showFilter => _showFilter; |
| ProfileTreeMode get mode => _mode; |
| M.ProfileTreeDirection get direction => _direction; |
| String get filter => _filter; |
| |
| set showMode(bool value) => _showMode = _r.checkAndReact(_showMode, value); |
| set showDirection(bool value) => |
| _showDirection = _r.checkAndReact(_showDirection, value); |
| set showFilter(bool value) => |
| _showFilter = _r.checkAndReact(_showFilter, value); |
| set mode(ProfileTreeMode value) => _mode = _r.checkAndReact(_mode, value); |
| set direction(M.ProfileTreeDirection value) => |
| _direction = _r.checkAndReact(_direction, value); |
| set filter(String value) => _filter = _r.checkAndReact(_filter, value); |
| |
| factory StackTraceTreeConfigElement({ |
| bool showMode = true, |
| bool showDirection = true, |
| bool showFilter = true, |
| String filter = '', |
| ProfileTreeMode mode = ProfileTreeMode.function, |
| M.ProfileTreeDirection direction = M.ProfileTreeDirection.exclusive, |
| RenderingQueue? queue, |
| }) { |
| StackTraceTreeConfigElement e = new StackTraceTreeConfigElement.created(); |
| e._r = new RenderingScheduler<StackTraceTreeConfigElement>(e, queue: queue); |
| e._showMode = showMode; |
| e._showDirection = showDirection; |
| e._showFilter = showFilter; |
| e._mode = mode; |
| e._direction = direction; |
| e._filter = filter; |
| return e; |
| } |
| |
| StackTraceTreeConfigElement.created() |
| : super.created('stack-trace-tree-config'); |
| |
| @override |
| void attached() { |
| super.attached(); |
| _r.enable(); |
| } |
| |
| @override |
| void detached() { |
| super.detached(); |
| _r.disable(notify: true); |
| children = const []; |
| } |
| |
| void render() { |
| children = <HTMLElement>[ |
| new HTMLDivElement() |
| ..className = 'content-centered-big' |
| ..appendChildren(<HTMLElement>[ |
| new HTMLHeadingElement.h2()..textContent = 'Tree display', |
| new HTMLHRElement(), |
| new HTMLDivElement() |
| ..className = 'row' |
| ..appendChildren(<HTMLElement>[ |
| new HTMLDivElement() |
| ..className = 'memberList' |
| ..appendChildren(_createMembers()), |
| ]), |
| ]), |
| ]; |
| } |
| |
| List<HTMLElement> _createMembers() { |
| var members = <HTMLElement>[]; |
| if (_showMode) { |
| members.add( |
| new HTMLDivElement() |
| ..className = 'memberItem' |
| ..appendChildren(<HTMLElement>[ |
| new HTMLDivElement() |
| ..className = 'memberName' |
| ..textContent = 'Mode', |
| new HTMLDivElement() |
| ..className = 'memberValue' |
| ..appendChildren(_createModeSelect()), |
| ]), |
| ); |
| } |
| if (_showDirection) { |
| members.add( |
| new HTMLDivElement() |
| ..className = 'memberItem' |
| ..appendChildren(<HTMLElement>[ |
| new HTMLDivElement() |
| ..className = 'memberName' |
| ..textContent = 'Call Tree Direction', |
| new HTMLSpanElement() |
| ..className = 'memberValue' |
| ..appendChildren(_createDirectionSelect()), |
| ]), |
| ); |
| } |
| if (showFilter) { |
| members.add( |
| new HTMLDivElement() |
| ..className = 'memberItem' |
| ..appendChildren(<HTMLElement>[ |
| new HTMLDivElement() |
| ..className = 'memberName' |
| ..textContent = 'Call Tree Filter' |
| ..title = 'case-sensitive substring match', |
| new HTMLSpanElement() |
| ..className = 'memberValue' |
| ..appendChildren(_createFilter()), |
| ]), |
| ); |
| } |
| return members; |
| } |
| |
| String get modeDescription { |
| if (_mode == ProfileTreeMode.function) { |
| return 'Inlined frames expanded.'; |
| } else { |
| return 'Inlined frames not expanded.'; |
| } |
| } |
| |
| List<HTMLElement> _createModeSelect() { |
| final s = HTMLSelectElement() |
| ..className = 'mode-select' |
| ..value = modeToString(_mode) |
| ..appendChildren( |
| ProfileTreeMode.values.map( |
| (mode) => HTMLOptionElement() |
| ..value = modeToString(mode) |
| ..selected = _mode == mode |
| ..text = modeToString(mode), |
| ), |
| ); |
| return [ |
| s |
| ..onChange.listen((_) { |
| _mode = ProfileTreeMode.values[s.selectedIndex]; |
| _r.dirty(); |
| }) |
| ..onChange.map(_toEvent).listen(_triggerModeChange), |
| HTMLSpanElement()..textContent = ' $modeDescription', |
| ]; |
| } |
| |
| String get directionDescription { |
| if (_direction == M.ProfileTreeDirection.inclusive) { |
| return 'Tree is rooted at "main". Child nodes are callees.'; |
| } else { |
| return 'Tree is rooted at top-of-stack. Child nodes are callers.'; |
| } |
| } |
| |
| List<HTMLElement> _createDirectionSelect() { |
| final s = HTMLSelectElement() |
| ..className = 'direction-select' |
| ..value = directionToString(_direction) |
| ..appendChildren( |
| M.ProfileTreeDirection.values.map((direction) { |
| return HTMLOptionElement() |
| ..value = directionToString(direction) |
| ..selected = _direction == direction |
| ..text = directionToString(direction); |
| }), |
| ); |
| return [ |
| s |
| ..onChange.listen((_) { |
| _direction = M.ProfileTreeDirection.values[s.selectedIndex]; |
| _r.dirty(); |
| }) |
| ..onChange.map(_toEvent).listen(_triggerDirectionChange), |
| new HTMLSpanElement()..textContent = ' $directionDescription', |
| ]; |
| } |
| |
| List<HTMLElement> _createFilter() { |
| var t; |
| return [ |
| t = new HTMLInputElement() |
| ..placeholder = 'Search filter' |
| ..value = filter |
| ..onChange.listen((_) { |
| _filter = t.value; |
| }) |
| ..onChange.map(_toEvent).listen(_triggerFilterChange), |
| ]; |
| } |
| |
| static String modeToString(ProfileTreeMode mode) { |
| switch (mode) { |
| case ProfileTreeMode.code: |
| return 'Code'; |
| case ProfileTreeMode.function: |
| return 'Function'; |
| } |
| } |
| |
| static String directionToString(M.ProfileTreeDirection direction) { |
| switch (direction) { |
| case M.ProfileTreeDirection.inclusive: |
| return 'Top down'; |
| case M.ProfileTreeDirection.exclusive: |
| return 'Bottom up'; |
| } |
| } |
| |
| StackTraceTreeConfigChangedEvent _toEvent(_) { |
| return new StackTraceTreeConfigChangedEvent(this); |
| } |
| |
| void _triggerModeChange(e) => _onModeChange.add(e); |
| void _triggerDirectionChange(e) => _onDirectionChange.add(e); |
| void _triggerFilterChange(e) => _onFilterChange.add(e); |
| } |