| // 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. |
| |
| library instance_view_element; |
| |
| import 'dart:async'; |
| |
| import 'package:web/web.dart'; |
| |
| import 'package:observatory/models.dart' as M; |
| import 'package:observatory/src/elements/curly_block.dart'; |
| import 'package:observatory/src/elements/eval_box.dart'; |
| import 'package:observatory/src/elements/helpers/any_ref.dart'; |
| import 'package:observatory/src/elements/helpers/custom_element.dart'; |
| import 'package:observatory/src/elements/helpers/element_utils.dart'; |
| import 'package:observatory/src/elements/helpers/nav_bar.dart'; |
| import 'package:observatory/src/elements/helpers/nav_menu.dart'; |
| import 'package:observatory/src/elements/helpers/rendering_scheduler.dart'; |
| import 'package:observatory/src/elements/instance_ref.dart'; |
| import 'package:observatory/src/elements/nav/class_menu.dart'; |
| import 'package:observatory/src/elements/nav/isolate_menu.dart'; |
| import 'package:observatory/src/elements/nav/library_menu.dart'; |
| import 'package:observatory/src/elements/nav/notify.dart'; |
| import 'package:observatory/src/elements/nav/refresh.dart'; |
| import 'package:observatory/src/elements/nav/top_menu.dart'; |
| import 'package:observatory/src/elements/nav/vm_menu.dart'; |
| import 'package:observatory/src/elements/object_common.dart'; |
| import 'package:observatory/src/elements/source_inset.dart'; |
| import 'package:observatory/utils.dart'; |
| |
| class InstanceViewElement extends CustomElement implements Renderable { |
| late RenderingScheduler<InstanceViewElement> _r; |
| |
| Stream<RenderedEvent<InstanceViewElement>> get onRendered => _r.onRendered; |
| |
| late M.VM _vm; |
| late M.IsolateRef _isolate; |
| late M.EventRepository _events; |
| late M.NotificationRepository _notifications; |
| late M.Instance _instance; |
| M.LibraryRef? _library; |
| late M.ObjectRepository _objects; |
| late M.ClassRepository _classes; |
| late M.RetainedSizeRepository _retainedSizes; |
| late M.ReachableSizeRepository _reachableSizes; |
| late M.InboundReferencesRepository _references; |
| late M.RetainingPathRepository _retainingPaths; |
| late M.ScriptRepository _scripts; |
| late M.EvalRepository _eval; |
| M.TypeArguments? _typeArguments; |
| late M.TypeArgumentsRepository _arguments; |
| late M.BreakpointRepository _breakpoints; |
| late M.FunctionRepository _functions; |
| M.SourceLocation? _location; |
| |
| M.VMRef get vm => _vm; |
| M.IsolateRef get isolate => _isolate; |
| M.NotificationRepository get notifications => _notifications; |
| M.Instance get instance => _instance; |
| |
| factory InstanceViewElement( |
| M.VM vm, |
| M.IsolateRef isolate, |
| M.Instance instance, |
| M.EventRepository events, |
| M.NotificationRepository notifications, |
| M.ObjectRepository objects, |
| M.ClassRepository classes, |
| M.RetainedSizeRepository retainedSizes, |
| M.ReachableSizeRepository reachableSizes, |
| M.InboundReferencesRepository references, |
| M.RetainingPathRepository retainingPaths, |
| M.ScriptRepository scripts, |
| M.EvalRepository eval, |
| M.TypeArgumentsRepository arguments, |
| M.BreakpointRepository breakpoints, |
| M.FunctionRepository functions, |
| {RenderingQueue? queue}) { |
| InstanceViewElement e = new InstanceViewElement.created(); |
| e._r = new RenderingScheduler<InstanceViewElement>(e, queue: queue); |
| e._vm = vm; |
| e._isolate = isolate; |
| e._events = events; |
| e._notifications = notifications; |
| e._instance = instance; |
| e._objects = objects; |
| e._classes = classes; |
| e._retainedSizes = retainedSizes; |
| e._reachableSizes = reachableSizes; |
| e._references = references; |
| e._retainingPaths = retainingPaths; |
| e._scripts = scripts; |
| e._eval = eval; |
| e._arguments = arguments; |
| e._breakpoints = breakpoints; |
| e._functions = functions; |
| return e; |
| } |
| |
| InstanceViewElement.created() : super.created('instance-view'); |
| |
| @override |
| attached() { |
| super.attached(); |
| _r.enable(); |
| _loadExtraData(); |
| } |
| |
| @override |
| detached() { |
| super.detached(); |
| _r.disable(notify: true); |
| removeChildren(); |
| } |
| |
| void render() { |
| final content = <HTMLElement>[ |
| new HTMLHeadingElement.h2() |
| ..textContent = M.isAbstractType(_instance.kind) |
| ? 'type ${_instance.name}' |
| : 'instance of ${_instance.clazz!.name}', |
| new HTMLHRElement(), |
| new ObjectCommonElement(_isolate, _instance, _retainedSizes, |
| _reachableSizes, _references, _retainingPaths, _objects, |
| queue: _r.queue) |
| .element, |
| new HTMLBRElement(), |
| new HTMLDivElement() |
| ..className = 'memberList' |
| ..appendChildren(_createMembers()), |
| new HTMLHRElement(), |
| new EvalBoxElement(_isolate, _instance, _objects, _eval, |
| quickExpressions: const ['toString()', 'runtimeType'], |
| queue: _r.queue) |
| .element |
| ]; |
| if (_location != null) { |
| content.addAll([ |
| new HTMLHRElement(), |
| new SourceInsetElement( |
| _isolate, _location!, _scripts, _objects, _events, |
| queue: _r.queue) |
| .element |
| ]); |
| } |
| children = <HTMLElement>[ |
| navBar(_createMenu()), |
| new HTMLDivElement() |
| ..className = 'content-centered-big' |
| ..appendChildren(content) |
| ]; |
| } |
| |
| List<HTMLElement> _createMenu() { |
| final menu = <HTMLElement>[ |
| new NavTopMenuElement(queue: _r.queue).element, |
| new NavVMMenuElement(_vm, _events, queue: _r.queue).element, |
| new NavIsolateMenuElement(_isolate, _events, queue: _r.queue).element |
| ]; |
| if (_library != null) { |
| menu.add(new NavLibraryMenuElement(_isolate, _library!, queue: _r.queue) |
| .element); |
| } |
| menu.addAll(<HTMLElement>[ |
| new NavClassMenuElement(_isolate, _instance.clazz!, queue: _r.queue) |
| .element, |
| navMenu('instance'), |
| (new NavRefreshElement(queue: _r.queue) |
| ..onRefresh.listen((e) { |
| e.element.disabled = true; |
| _refresh(); |
| })) |
| .element, |
| new NavNotifyElement(_notifications, queue: _r.queue).element |
| ]); |
| return menu; |
| } |
| |
| HTMLElement memberHalf(String cssClass, dynamic half) { |
| var result = new HTMLDivElement()..className = cssClass; |
| if (half is String) { |
| result.textContent = half; |
| } else { |
| result.setChildren( |
| <HTMLElement>[anyRef(_isolate, half, _objects, queue: _r.queue)]); |
| } |
| return result; |
| } |
| |
| HTMLElement member(dynamic name, dynamic value) { |
| return new HTMLDivElement() |
| ..className = 'memberItem' |
| ..appendChildren(<HTMLElement>[ |
| memberHalf('memberName', name), |
| memberHalf('memberValue', value), |
| ]); |
| } |
| |
| List<HTMLElement> _createMembers() { |
| final members = <HTMLElement>[]; |
| if (_instance.valueAsString != null) { |
| if (_instance.kind == M.InstanceKind.string) { |
| members.add(member( |
| 'value as literal', |
| Utils.formatStringAsLiteral(_instance.valueAsString!, |
| _instance.valueAsStringIsTruncated!))); |
| } else { |
| members.add(member('value', _instance.valueAsString)); |
| } |
| } |
| if (_instance.typeClass != null) { |
| members.add(member('type class', _instance.typeClass)); |
| } |
| if (_typeArguments != null && _typeArguments!.types!.isNotEmpty) { |
| members.add(new HTMLDivElement() |
| ..className = 'memberItem' |
| ..appendChildren(<HTMLElement>[ |
| new HTMLDivElement() |
| ..className = 'memberName' |
| ..textContent = 'type arguments', |
| new HTMLDivElement() |
| ..className = 'memberValue' |
| ..appendChildren(([new HTMLSpanElement()..textContent = '< '] |
| ..addAll(_typeArguments!.types!.expand((type) => [ |
| new InstanceRefElement(_isolate, type, _objects, |
| queue: _r.queue) |
| .element, |
| new HTMLSpanElement()..textContent = ', ' |
| ])) |
| ..removeLast() |
| ..add(new HTMLSpanElement()..textContent = ' >'))) |
| ])); |
| } |
| if (_instance.parameterizedClass != null) { |
| members.add(member('parameterized class', _instance.parameterizedClass)); |
| } |
| if (_instance.parameterIndex != null) { |
| members.add(member('parameter index', '${_instance.parameterIndex}')); |
| } |
| if (_instance.bound != null) { |
| members.add(member('bound', _instance.bound)); |
| } |
| if (_instance.closureFunction != null) { |
| members.add(member('closure function', _instance.closureFunction)); |
| } |
| if (_instance.closureContext != null) { |
| members.add(member('closure context', _instance.closureContext)); |
| } |
| if (_instance.closureReceiver != null) { |
| members.add(member('closure receiver', _instance.closureReceiver)); |
| } |
| if (_instance.kind == M.InstanceKind.closure) { |
| late HTMLButtonElement btn; |
| members.add(new HTMLDivElement() |
| ..className = 'memberItem' |
| ..appendChildren(<HTMLElement>[ |
| new HTMLDivElement() |
| ..className = 'memberName' |
| ..textContent = 'closure breakpoint', |
| new HTMLDivElement() |
| ..className = 'memberValue' |
| ..appendChildren(<HTMLElement>[ |
| btn = new HTMLButtonElement() |
| ..textContent = _instance.activationBreakpoint == null |
| ? 'break on activation' |
| : 'remove' |
| ..onClick.listen((_) { |
| btn.disabled = true; |
| _toggleBreakpoint(); |
| }) |
| ]) |
| ])); |
| } |
| |
| if (_instance.nativeFields != null && _instance.nativeFields!.isNotEmpty) { |
| int i = 0; |
| members.add(new HTMLDivElement() |
| ..className = 'memberItem' |
| ..appendChildren(<HTMLElement>[ |
| new HTMLDivElement() |
| ..className = 'memberName' |
| ..textContent = 'native fields (${_instance.nativeFields!.length})', |
| new HTMLDivElement() |
| ..className = 'memberName' |
| ..appendChildren(<HTMLElement>[ |
| (new CurlyBlockElement( |
| expanded: _instance.nativeFields!.length <= 100, |
| queue: _r.queue) |
| ..content = <HTMLElement>[ |
| new HTMLDivElement() |
| ..className = 'memberList' |
| ..appendChildren(_instance.nativeFields! |
| .map<HTMLElement>( |
| (f) => member('[ ${i++} ]', '[ ${f.value} ]'))) |
| ]) |
| .element |
| ]) |
| ])); |
| } |
| |
| if (_instance.fields != null && _instance.fields!.isNotEmpty) { |
| final fields = _instance.fields!.toList(); |
| members.add(new HTMLDivElement() |
| ..className = 'memberItem' |
| ..appendChildren(<HTMLElement>[ |
| new HTMLDivElement() |
| ..className = 'memberName' |
| ..textContent = 'fields (${fields.length})', |
| new HTMLDivElement() |
| ..className = 'memberName' |
| ..appendChildren(<HTMLElement>[ |
| (new CurlyBlockElement( |
| expanded: fields.length <= 100, queue: _r.queue) |
| ..content = <HTMLElement>[ |
| new HTMLDivElement() |
| ..className = 'memberList' |
| ..appendChildren(fields.map<HTMLElement>((f) { |
| final name = _instance.kind == M.InstanceKind.record |
| ? f.name |
| : f.decl; |
| return member(name, f.value); |
| })) |
| ]) |
| .element |
| ]) |
| ])); |
| } |
| |
| if (_instance.elements != null && _instance.elements!.isNotEmpty) { |
| final elements = _instance.elements!.toList(); |
| int i = 0; |
| members.add(new HTMLDivElement() |
| ..className = 'memberItem' |
| ..appendChildren(<HTMLElement>[ |
| new HTMLDivElement() |
| ..className = 'memberName' |
| ..textContent = 'elements (${_instance.length})', |
| new HTMLDivElement() |
| ..className = 'memberValue' |
| ..appendChildren(<HTMLElement>[ |
| (new CurlyBlockElement( |
| expanded: elements.length <= 100, queue: _r.queue) |
| ..content = <HTMLElement>[ |
| new HTMLDivElement() |
| ..className = 'memberList' |
| ..appendChildren(elements.map<HTMLElement>( |
| (element) => member('[ ${i++} ]', element))) |
| ]) |
| .element |
| ]) |
| ])); |
| if (_instance.length != elements.length) { |
| members.add(member( |
| '...', '${_instance.length! - elements.length} omitted elements')); |
| } |
| } |
| |
| if (_instance.associations != null && _instance.associations!.isNotEmpty) { |
| final associations = _instance.associations!.toList(); |
| members.add(new HTMLDivElement() |
| ..className = 'memberItem' |
| ..appendChildren(<HTMLElement>[ |
| new HTMLDivElement() |
| ..className = 'memberName' |
| ..textContent = 'associations (${_instance.length})', |
| new HTMLDivElement() |
| ..className = 'memberName' |
| ..appendChildren(<HTMLElement>[ |
| (new CurlyBlockElement( |
| expanded: associations.length <= 100, queue: _r.queue) |
| ..content = <HTMLElement>[ |
| new HTMLDivElement() |
| ..className = 'memberList' |
| ..appendChildren(associations |
| .map<HTMLElement>((a) => new HTMLDivElement() |
| ..className = 'memberItem' |
| ..appendChildren(<HTMLElement>[ |
| new HTMLDivElement() |
| ..className = 'memberName' |
| ..appendChildren(<HTMLElement>[ |
| new HTMLSpanElement()..textContent = '[ ', |
| anyRef(_isolate, a.key, _objects, |
| queue: _r.queue), |
| new HTMLSpanElement()..textContent = ' ]', |
| ]), |
| new HTMLDivElement() |
| ..className = 'memberValue' |
| ..appendChildren(<HTMLElement>[ |
| anyRef(_isolate, a.value, _objects, |
| queue: _r.queue) |
| ]) |
| ]))) |
| ]) |
| .element |
| ]) |
| ])); |
| if (_instance.length != associations.length) { |
| members.add(member('...', |
| '${_instance.length! - associations.length} omitted elements')); |
| } |
| } |
| |
| if (_instance.typedElements != null && |
| _instance.typedElements!.isNotEmpty) { |
| final typedElements = _instance.typedElements!.toList(); |
| int i = 0; |
| members.add(new HTMLDivElement() |
| ..className = 'memberItem' |
| ..appendChildren(<HTMLElement>[ |
| new HTMLDivElement() |
| ..className = 'memberName' |
| ..textContent = 'elements (${_instance.length})', |
| new HTMLDivElement() |
| ..className = 'memberValue' |
| ..appendChildren(<HTMLElement>[ |
| (new CurlyBlockElement( |
| expanded: typedElements.length <= 100, queue: _r.queue) |
| ..content = <HTMLElement>[ |
| new HTMLDivElement() |
| ..className = 'memberList' |
| ..appendChildren(typedElements.map<HTMLElement>( |
| (e) => member('[ ${i++} ]', '$e'))) |
| ]) |
| .element |
| ]) |
| ])); |
| if (_instance.length != typedElements.length) { |
| members.add(member('...', |
| '${_instance.length! - typedElements.length} omitted elements')); |
| } |
| } |
| |
| if (_instance.kind == M.InstanceKind.regExp) { |
| members.add(member('pattern', _instance.pattern)); |
| members.add( |
| member('isCaseSensitive', _instance.isCaseSensitive! ? 'yes' : 'no')); |
| members.add(member('isMultiLine', _instance.isMultiLine! ? 'yes' : 'no')); |
| if (_instance.oneByteFunction != null) { |
| members.add(member('oneByteFunction', _instance.oneByteFunction)); |
| } |
| if (_instance.twoByteFunction != null) { |
| members.add(member('twoByteFunction', _instance.twoByteFunction)); |
| } |
| if (_instance.oneByteBytecode != null) { |
| members.add(member('oneByteBytecode', _instance.oneByteBytecode)); |
| } |
| if (_instance.twoByteBytecode != null) { |
| members.add(member('twoByteBytecode', _instance.twoByteBytecode)); |
| } |
| } |
| |
| if (_instance.kind == M.InstanceKind.mirrorReference) { |
| members.add(member('referent', _instance.referent)); |
| } |
| |
| if (_instance.kind == M.InstanceKind.weakReference) { |
| members.add(member('target', _instance.target)); |
| } |
| |
| if (_instance.kind == M.InstanceKind.weakProperty) { |
| members.add(member('key', _instance.key)); |
| members.add(member('value', _instance.value)); |
| } |
| |
| if (_instance.kind == M.InstanceKind.finalizer) { |
| members.add(member('callback', _instance.callback)); |
| members.add(member('allEntries', _instance.allEntries)); |
| } |
| |
| if (_instance.kind == M.InstanceKind.nativeFinalizer) { |
| members.add(member('callback', _instance.allEntries)); |
| } |
| |
| if (_instance.kind == M.InstanceKind.finalizerEntry) { |
| members.add(member('value', _instance.value)); |
| members.add(member('detach', _instance.detach)); |
| members.add(member('token', _instance.token)); |
| } |
| |
| return members; |
| } |
| |
| Future _refresh() async { |
| _instance = await _objects.get(_isolate, _instance.id!) as M.Instance; |
| await _loadExtraData(); |
| _r.dirty(); |
| } |
| |
| Future _loadExtraData() async { |
| _library = (await _classes.get(_isolate, _instance.clazz!.id!)).library!; |
| if (_instance.typeArguments != null) { |
| _typeArguments = |
| await _arguments.get(_isolate, _instance.typeArguments!.id!); |
| } else { |
| _typeArguments = null; |
| } |
| if (_instance.closureFunction != null) { |
| _location = |
| (await _functions.get(_isolate, _instance.closureFunction!.id!)) |
| .location; |
| } else if (_instance.typeClass != null) { |
| _location = |
| (await _classes.get(_isolate, _instance.typeClass!.id!)).location; |
| } |
| _r.dirty(); |
| } |
| |
| Future _toggleBreakpoint() async { |
| if (_instance.activationBreakpoint == null) { |
| await _breakpoints.addOnActivation(_isolate, _instance); |
| } else { |
| await _breakpoints.remove(_isolate, _instance.activationBreakpoint!); |
| } |
| await _refresh(); |
| } |
| } |