blob: 2063c281800add21468b21b87324f3d6351e23fc [file] [log] [blame]
// 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();
}
}