| // 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 'dart:html'; | 
 | import 'package:observatory/models.dart' as M; | 
 | import 'package:observatory/src/elements/class_ref.dart'; | 
 | import 'package:observatory/src/elements/context_ref.dart'; | 
 | import 'package:observatory/src/elements/curly_block.dart'; | 
 | import 'package:observatory/src/elements/eval_box.dart'; | 
 | import 'package:observatory/src/elements/field_ref.dart'; | 
 | import 'package:observatory/src/elements/function_ref.dart'; | 
 | import 'package:observatory/src/elements/helpers/any_ref.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/helpers/custom_element.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/src/elements/source_link.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}) { | 
 |     assert(vm != null); | 
 |     assert(isolate != null); | 
 |     assert(events != null); | 
 |     assert(notifications != null); | 
 |     assert(instance != null); | 
 |     assert(objects != null); | 
 |     assert(classes != null); | 
 |     assert(retainedSizes != null); | 
 |     assert(reachableSizes != null); | 
 |     assert(references != null); | 
 |     assert(retainingPaths != null); | 
 |     assert(scripts != null); | 
 |     assert(eval != null); | 
 |     assert(arguments != null); | 
 |     assert(breakpoints != null); | 
 |     assert(functions != null); | 
 |     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); | 
 |     children = <Element>[]; | 
 |   } | 
 |  | 
 |   void render() { | 
 |     final content = <Element>[ | 
 |       new HeadingElement.h2() | 
 |         ..text = M.isAbstractType(_instance.kind) | 
 |             ? 'type ${_instance.name}' | 
 |             : 'instance of ${_instance.clazz!.name}', | 
 |       new HRElement(), | 
 |       new ObjectCommonElement(_isolate, _instance, _retainedSizes, | 
 |               _reachableSizes, _references, _retainingPaths, _objects, | 
 |               queue: _r.queue) | 
 |           .element, | 
 |       new BRElement(), | 
 |       new DivElement() | 
 |         ..classes = ['memberList'] | 
 |         ..children = _createMembers(), | 
 |       new HRElement(), | 
 |       new EvalBoxElement(_isolate, _instance, _objects, _eval, | 
 |               quickExpressions: const ['toString()', 'runtimeType'], | 
 |               queue: _r.queue) | 
 |           .element | 
 |     ]; | 
 |     if (_location != null) { | 
 |       content.addAll([ | 
 |         new HRElement(), | 
 |         new SourceInsetElement( | 
 |                 _isolate, _location!, _scripts, _objects, _events, | 
 |                 queue: _r.queue) | 
 |             .element | 
 |       ]); | 
 |     } | 
 |     children = <Element>[ | 
 |       navBar(_createMenu()), | 
 |       new DivElement() | 
 |         ..classes = ['content-centered-big'] | 
 |         ..children = content | 
 |     ]; | 
 |   } | 
 |  | 
 |   List<Element> _createMenu() { | 
 |     final menu = <Element>[ | 
 |       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(<Element>[ | 
 |       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; | 
 |   } | 
 |  | 
 |   Element memberHalf(String cssClass, dynamic half) { | 
 |     var result = new DivElement()..classes = [cssClass]; | 
 |     if (half is String) { | 
 |       result.text = half; | 
 |     } else { | 
 |       result.children = <Element>[ | 
 |         anyRef(_isolate, half, _objects, queue: _r.queue) | 
 |       ]; | 
 |     } | 
 |     return result; | 
 |   } | 
 |  | 
 |   Element member(dynamic name, dynamic value) { | 
 |     return new DivElement() | 
 |       ..classes = ['memberItem'] | 
 |       ..children = <Element>[ | 
 |         memberHalf('memberName', name), | 
 |         memberHalf('memberValue', value), | 
 |       ]; | 
 |   } | 
 |  | 
 |   List<Element> _createMembers() { | 
 |     final members = <Element>[]; | 
 |     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 DivElement() | 
 |         ..classes = ['memberItem'] | 
 |         ..children = <Element>[ | 
 |           new DivElement() | 
 |             ..classes = ['memberName'] | 
 |             ..text = 'type arguments', | 
 |           new DivElement() | 
 |             ..classes = ['memberValue'] | 
 |             ..children = ([new SpanElement()..text = '< '] | 
 |               ..addAll(_typeArguments!.types!.expand((type) => [ | 
 |                     new InstanceRefElement(_isolate, type, _objects, | 
 |                             queue: _r.queue) | 
 |                         .element, | 
 |                     new SpanElement()..text = ', ' | 
 |                   ])) | 
 |               ..removeLast() | 
 |               ..add(new SpanElement()..text = ' >')) | 
 |         ]); | 
 |     } | 
 |     if (_instance.parameterizedClass != null) { | 
 |       members.add(member('parameterized class', _instance.parameterizedClass)); | 
 |     } | 
 |     if (_instance.parameterIndex != null) { | 
 |       members.add(member('parameter index', '${_instance.parameterIndex}')); | 
 |     } | 
 |     if (_instance.targetType != null) { | 
 |       members.add(member('target type', _instance.targetType)); | 
 |     } | 
 |     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.kind == M.InstanceKind.closure) { | 
 |       late ButtonElement btn; | 
 |       members.add(new DivElement() | 
 |         ..classes = ['memberItem'] | 
 |         ..children = <Element>[ | 
 |           new DivElement() | 
 |             ..classes = ['memberName'] | 
 |             ..text = 'closure breakpoint', | 
 |           new DivElement() | 
 |             ..classes = ['memberValue'] | 
 |             ..children = <Element>[ | 
 |               btn = new ButtonElement() | 
 |                 ..text = _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 DivElement() | 
 |         ..classes = ['memberItem'] | 
 |         ..children = <Element>[ | 
 |           new DivElement() | 
 |             ..classes = ['memberName'] | 
 |             ..text = 'native fields (${_instance.nativeFields!.length})', | 
 |           new DivElement() | 
 |             ..classes = ['memberName'] | 
 |             ..children = <Element>[ | 
 |               (new CurlyBlockElement( | 
 |                       expanded: _instance.nativeFields!.length <= 100, | 
 |                       queue: _r.queue) | 
 |                     ..content = <Element>[ | 
 |                       new DivElement() | 
 |                         ..classes = ['memberList'] | 
 |                         ..children = _instance.nativeFields! | 
 |                             .map<Element>( | 
 |                                 (f) => member('[ ${i++} ]', '[ ${f.value} ]')) | 
 |                             .toList() | 
 |                     ]) | 
 |                   .element | 
 |             ] | 
 |         ]); | 
 |     } | 
 |  | 
 |     if (_instance.fields != null && _instance.fields!.isNotEmpty) { | 
 |       final fields = _instance.fields!.toList(); | 
 |       members.add(new DivElement() | 
 |         ..classes = ['memberItem'] | 
 |         ..children = <Element>[ | 
 |           new DivElement() | 
 |             ..classes = ['memberName'] | 
 |             ..text = 'fields (${fields.length})', | 
 |           new DivElement() | 
 |             ..classes = ['memberName'] | 
 |             ..children = <Element>[ | 
 |               (new CurlyBlockElement( | 
 |                       expanded: fields.length <= 100, queue: _r.queue) | 
 |                     ..content = <Element>[ | 
 |                       new DivElement() | 
 |                         ..classes = ['memberList'] | 
 |                         ..children = fields | 
 |                             .map<Element>((f) => member(f.decl, f.value)) | 
 |                             .toList() | 
 |                     ]) | 
 |                   .element | 
 |             ] | 
 |         ]); | 
 |     } | 
 |  | 
 |     if (_instance.elements != null && _instance.elements!.isNotEmpty) { | 
 |       final elements = _instance.elements!.toList(); | 
 |       int i = 0; | 
 |       members.add(new DivElement() | 
 |         ..classes = ['memberItem'] | 
 |         ..children = <Element>[ | 
 |           new DivElement() | 
 |             ..classes = ['memberName'] | 
 |             ..text = 'elements (${_instance.length})', | 
 |           new DivElement() | 
 |             ..classes = ['memberValue'] | 
 |             ..children = <Element>[ | 
 |               (new CurlyBlockElement( | 
 |                       expanded: elements.length <= 100, queue: _r.queue) | 
 |                     ..content = <Element>[ | 
 |                       new DivElement() | 
 |                         ..classes = ['memberList'] | 
 |                         ..children = elements | 
 |                             .map<Element>( | 
 |                                 (element) => member('[ ${i++} ]', element)) | 
 |                             .toList() | 
 |                     ]) | 
 |                   .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 DivElement() | 
 |         ..classes = ['memberItem'] | 
 |         ..children = <Element>[ | 
 |           new DivElement() | 
 |             ..classes = ['memberName'] | 
 |             ..text = 'associations (${_instance.length})', | 
 |           new DivElement() | 
 |             ..classes = ['memberName'] | 
 |             ..children = <Element>[ | 
 |               (new CurlyBlockElement( | 
 |                       expanded: associations.length <= 100, queue: _r.queue) | 
 |                     ..content = <Element>[ | 
 |                       new DivElement() | 
 |                         ..classes = ['memberList'] | 
 |                         ..children = associations | 
 |                             .map<Element>((a) => new DivElement() | 
 |                               ..classes = ['memberItem'] | 
 |                               ..children = <Element>[ | 
 |                                 new DivElement() | 
 |                                   ..classes = ['memberName'] | 
 |                                   ..children = <Element>[ | 
 |                                     new SpanElement()..text = '[ ', | 
 |                                     anyRef(_isolate, a.key, _objects, | 
 |                                         queue: _r.queue), | 
 |                                     new SpanElement()..text = ' ]', | 
 |                                   ], | 
 |                                 new DivElement() | 
 |                                   ..classes = ['memberValue'] | 
 |                                   ..children = <Element>[ | 
 |                                     anyRef(_isolate, a.value, _objects, | 
 |                                         queue: _r.queue) | 
 |                                   ] | 
 |                               ]) | 
 |                             .toList() | 
 |                     ]) | 
 |                   .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 DivElement() | 
 |         ..classes = ['memberItem'] | 
 |         ..children = <Element>[ | 
 |           new DivElement() | 
 |             ..classes = ['memberName'] | 
 |             ..text = 'elements (${_instance.length})', | 
 |           new DivElement() | 
 |             ..classes = ['memberValue'] | 
 |             ..children = <Element>[ | 
 |               (new CurlyBlockElement( | 
 |                       expanded: typedElements.length <= 100, queue: _r.queue) | 
 |                     ..content = <Element>[ | 
 |                       new DivElement() | 
 |                         ..classes = ['memberList'] | 
 |                         ..children = typedElements | 
 |                             .map<Element>((e) => member('[ ${i++} ]', '$e')) | 
 |                             .toList() | 
 |                     ]) | 
 |                   .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.externalOneByteFunction != null) { | 
 |         members.add(member( | 
 |             'externalOneByteFunction', _instance.externalOneByteFunction)); | 
 |       } | 
 |       if (_instance.externalTwoByteFunction != null) { | 
 |         members.add(member( | 
 |             'externalTwoByteFunction', _instance.externalTwoByteFunction)); | 
 |       } | 
 |       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.weakProperty) { | 
 |       members.add(member('key', _instance.key)); | 
 |       members.add(member('value', _instance.value)); | 
 |     } | 
 |  | 
 |     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(); | 
 |   } | 
 | } |