| // 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 '../../models.dart' as M; | |
| import 'curly_block.dart'; | |
| import 'eval_box.dart'; | |
| import 'helpers/any_ref.dart'; | |
| import 'helpers/custom_element.dart'; | |
| import 'helpers/element_utils.dart'; | |
| import 'helpers/nav_bar.dart'; | |
| import 'helpers/nav_menu.dart'; | |
| import 'helpers/rendering_scheduler.dart'; | |
| import 'instance_ref.dart'; | |
| import 'nav/class_menu.dart'; | |
| import 'nav/isolate_menu.dart'; | |
| import 'nav/library_menu.dart'; | |
| import 'nav/notify.dart'; | |
| import 'nav/refresh.dart'; | |
| import 'nav/top_menu.dart'; | |
| import 'nav/vm_menu.dart'; | |
| import 'object_common.dart'; | |
| import 'source_inset.dart'; | |
| import '../../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(); | |
| } | |
| } |