├── .gitignore ├── web ├── favicon.ico ├── viewer.css ├── index.html └── viewer.dart ├── dump-info-viewer.png ├── lib ├── dump_viz.dart └── src │ ├── program_info_view.html │ ├── hierarchy_view.html │ ├── drag_drop_view.html │ ├── drag_drop_view.dart │ ├── diff_alg.dart │ ├── dependency_view.html │ ├── program_info_view.dart │ ├── tree_table.html │ ├── diff_view.dart │ ├── diff_view.html │ ├── dependency_view.dart │ ├── logical_row.dart │ ├── history_state.dart │ ├── tree_table.dart │ ├── info_helper.dart │ ├── deps_icon.svg │ └── hierarchy_view.dart ├── pubspec.yaml ├── test ├── info_helper_test.dart └── diff_test.dart ├── README.md ├── LICENSE └── pubspec.lock /.gitignore: -------------------------------------------------------------------------------- 1 | packages 2 | .buildlog 3 | .pub 4 | build/ 5 | .packages 6 | .idea/ 7 | *.iml 8 | -------------------------------------------------------------------------------- /web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-archive/dump-info-visualizer/master/web/favicon.ico -------------------------------------------------------------------------------- /dump-info-viewer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-archive/dump-info-visualizer/master/dump-info-viewer.png -------------------------------------------------------------------------------- /lib/dump_viz.dart: -------------------------------------------------------------------------------- 1 | library dump_viz; 2 | 3 | export 'package:dump_viz/src/dependency_view.dart'; 4 | export 'package:dump_viz/src/diff_view.dart'; 5 | export 'package:dump_viz/src/drag_drop_view.dart'; 6 | export 'package:dump_viz/src/hierarchy_view.dart'; 7 | export 'package:dump_viz/src/history_state.dart'; 8 | export 'package:dump_viz/src/info_helper.dart'; 9 | export 'package:dump_viz/src/program_info_view.dart'; 10 | -------------------------------------------------------------------------------- /lib/src/program_info_view.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dump_viz 2 | author: Dart Team 3 | homepage: https://github.com/dart-lang/dump-info-visualizer/ 4 | description: A visualizer for the JSON data produced by the dart2js --dump-info command 5 | environment: 6 | sdk: '>=1.11.0-dev <2.0.0' 7 | dependencies: 8 | core_elements: '^0.7.1+2' 9 | paper_elements: '^0.7.1' 10 | path: '^1.3.5' 11 | polymer: '^0.16.3+1' 12 | dev_dependencies: 13 | test: '^0.12.0' 14 | transformers: 15 | - polymer: 16 | entry_points: web/index.html 17 | - $dart2js: 18 | $include: "**/*.polymer.bootstrap.dart" 19 | commandLineOptions: 20 | - --trust-type-annotations 21 | - --trust-primitives 22 | - --dump-info 23 | -------------------------------------------------------------------------------- /test/info_helper_test.dart: -------------------------------------------------------------------------------- 1 | @TestOn('vm') 2 | import 'dart:convert'; 3 | import 'dart:io'; 4 | 5 | import 'package:path/path.dart' as p; 6 | import 'package:test/test.dart'; 7 | 8 | import 'package:dump_viz/src/info_helper.dart'; 9 | 10 | Map _content; 11 | 12 | void main() { 13 | setUp(() async { 14 | if (_content == null) { 15 | var samplePath = p.join(p.current, 'test', 'sample.info.json'); 16 | var file = new File(samplePath); 17 | var jsonString = await file.readAsString(); 18 | _content = JSON.decode(jsonString); 19 | } 20 | }); 21 | 22 | test('bootstrap', () { 23 | expect(_content, isNotEmpty); 24 | }); 25 | 26 | test("basics", () { 27 | var info = new InfoHelper.fromJson(_content); 28 | 29 | expect(info.dumpVersion, 3); 30 | expect(info.size, 929444); 31 | expect(info.joinedPaths, hasLength(5148)); 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /lib/src/hierarchy_view.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /lib/src/drag_drop_view.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Repo deprecation notice 2 | 3 | **NOTE**: This repository has been deprecated; for information and tooling related to 4 | understanding the size of your compiled web applications, see instead 5 | [dart.dev/go/dart2js-info](https://dart.dev/go/dart2js-info). 6 | 7 | ## Dump-Info visualizer 8 | 9 | A web based visualizer for the dart2js `--dump-info` option. 10 | 11 | [Live Website](https://dart-lang.github.io/dump-info-visualizer/) 12 | 13 | ## Screenshot 14 | 15 | ![](dump-info-viewer.png) 16 | 17 | ## How to Build 18 | 19 | The dump-info-visualizer is a Pub project, so running `pub build` will 20 | generate all the files for the viewer. 21 | 22 | This repository also hosts the public version of the viewer which is located 23 | on the `gh-pages` branch. Any files pushed to `gh-pages` will be made public. 24 | 25 | In order to make your changes public, follow these instructions. 26 | 27 | * `git checkout master` Your changes should already be on the master branch 28 | when you deploy. 29 | * `pub build` Build all of the javascript and HTML files. 30 | * `mv build ../` Copy built files out of the project structure. 31 | * `git checkout gh-pages` The destination branch. 32 | * `rm -rf build` Remove old build. 33 | * `mv ../build ./` Copy new build in. 34 | * `git commit -a -m "your message here"` Commit the new build. 35 | * `git push origin gh-pages` Deploy to gh-pages. 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014, the Dart project authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided 12 | with the distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived 15 | from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /lib/src/drag_drop_view.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library dump_viz.dragdrop; 6 | 7 | import 'dart:async'; 8 | import 'dart:html'; 9 | 10 | import 'package:polymer/polymer.dart'; 11 | 12 | @CustomTag('drag-drop-view') 13 | class DragDropView extends PolymerElement { 14 | Element get _dropZone => $['drag-target']; 15 | Element get _fileUpload => $['file_upload']; 16 | 17 | DragDropView.created() : super.created(); 18 | 19 | final StreamController _streamController = 20 | new StreamController(); 21 | 22 | Stream get onFile => _streamController.stream; 23 | 24 | void ready() { 25 | _fileUpload.onChange.listen((event) { 26 | var file = (event.target as InputElement).files.first; 27 | _loadFile(file); 28 | }); 29 | 30 | _dropZone.onDragOver.listen((e) { 31 | e.stopPropagation(); 32 | e.preventDefault(); 33 | _dropZone.style.backgroundColor = 'rgb(200,200,200)'; 34 | }); 35 | 36 | _dropZone.onDrop.listen((e) { 37 | e.stopPropagation(); 38 | e.preventDefault(); 39 | File file = e.dataTransfer.files.first; 40 | _loadFile(file); 41 | }); 42 | } 43 | 44 | void hide() { 45 | _dropZone.style.display = 'none'; 46 | } 47 | 48 | void show() { 49 | _dropZone.style.display = 'block'; 50 | } 51 | 52 | void _loadFile(File file) { 53 | FileReader reader = new FileReader(); 54 | 55 | reader.onLoad.first.then((_) { 56 | String fileContents = reader.result; 57 | // Substring because fileContents contains the mime type 58 | var contents = 59 | window.atob(fileContents.substring(fileContents.indexOf(',') + 1)); 60 | _dropZone.style.backgroundColor = ''; 61 | _streamController.add(contents); 62 | }); 63 | reader.readAsDataUrl(file); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /test/diff_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | library dump_viz.test.diff_test; 5 | 6 | import 'package:dump_viz/src/info_helper.dart'; 7 | import 'package:dump_viz/src/diff_alg.dart'; 8 | 9 | import 'package:test/test.dart'; 10 | 11 | class FakeInfoHelper { 12 | InfoHelper info; 13 | FakeInfoHelper() { 14 | info = new InfoHelper(3, {}, {}, {}); 15 | } 16 | FakeInfoHelper.fromFuncs(List funcs) { 17 | int i = 0; 18 | Map>> elements = {}; 19 | Map> functions = {}; 20 | Map> libraries = {}; 21 | 22 | elements['function'] = functions; 23 | elements['library'] = libraries; 24 | 25 | Map liba = {'name': "LibA"}; 26 | libraries['0'] = liba; 27 | List children = []; 28 | liba['children'] = children; 29 | 30 | for (var func in funcs) { 31 | functions['$i'] = func; 32 | children.add('function/$i'); 33 | func['id'] = 'function/$i'; 34 | i++; 35 | } 36 | info = new InfoHelper(3, elements, {}, {}); 37 | } 38 | } 39 | 40 | void main() { 41 | test('empty', () { 42 | var d = diff(new FakeInfoHelper().info, new FakeInfoHelper().info); 43 | expect(d, equals([])); 44 | }); 45 | test('change', () { 46 | var one = [{'name': 'foo', 'size': 10}]; 47 | var two = [{'name': 'foo', 'size': 20}]; 48 | expect(diff(new FakeInfoHelper.fromFuncs(one).info, 49 | new FakeInfoHelper.fromFuncs(two).info), 50 | equals([new DiffItem('partial-add', 'LibA.foo', 10)])); 51 | }); 52 | test('add/remove', () { 53 | var one = [{'name': 'foo', 'size': 10}]; 54 | var two = [{'name': 'bar', 'size': 20}]; 55 | expect(diff(new FakeInfoHelper.fromFuncs(one).info, 56 | new FakeInfoHelper.fromFuncs(two).info), equals([ 57 | new DiffItem('full-add', 'LibA.bar', 20), 58 | new DiffItem('full-remove', 'LibA.foo', -10) 59 | ])); 60 | }); 61 | } 62 | -------------------------------------------------------------------------------- /lib/src/diff_alg.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library dump_viz.diff_alg; 6 | 7 | import 'info_helper.dart'; 8 | 9 | class DiffItem { 10 | final String kind; 11 | final String path; 12 | final int diff; 13 | 14 | DiffItem(this.kind, this.path, this.diff); 15 | bool operator ==(other) { 16 | return other.kind == kind && other.path == path && other.diff == diff; 17 | } 18 | int get hashCode { 19 | int result = 17; 20 | result = 37 * result + kind.hashCode; 21 | result = 37 * result + path.hashCode; 22 | result = 37 * result + diff.hashCode; 23 | return result; 24 | } 25 | } 26 | 27 | List diff(InfoHelper before, InfoHelper after) { 28 | List changedElements = []; 29 | for (String path in before.joinedPaths) { 30 | String beforeId = before.idFromJoinedPath(path); 31 | int beforeSize = before.sizeOf(beforeId); 32 | if (beforeSize == null) continue; 33 | if (after.idFromJoinedPath(path) != null) { 34 | String afterId = after.idFromJoinedPath(path); 35 | int afterSize = after.sizeOf(afterId); 36 | if (afterSize == null) continue; 37 | int diff = afterSize - beforeSize; 38 | if (diff == 0) { 39 | continue; 40 | } else if (diff > 0) { 41 | changedElements.add(new DiffItem('partial-add', path, diff)); 42 | } else { 43 | changedElements.add(new DiffItem('partial-remove', path, diff)); 44 | } 45 | } else { 46 | changedElements.add(new DiffItem("full-remove", path, -beforeSize)); 47 | } 48 | } 49 | 50 | for (String path in after.joinedPaths) { 51 | String afterId = after.idFromJoinedPath(path); 52 | int afterSize = after.sizeOf(afterId); 53 | if (afterSize == null) continue; 54 | if (before.idFromJoinedPath(path) == null) { 55 | changedElements.add(new DiffItem("full-add", path, afterSize)); 56 | } 57 | } 58 | 59 | changedElements.sort((a, b) => -a.diff.abs().compareTo(b.diff.abs())); 60 | return changedElements; 61 | } 62 | -------------------------------------------------------------------------------- /lib/src/dependency_view.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /lib/src/program_info_view.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library dump_viz.program_info_view; 6 | 7 | import 'dart:html'; 8 | 9 | import 'package:polymer/polymer.dart'; 10 | 11 | import 'info_helper.dart'; 12 | 13 | @CustomTag('program-info-view') 14 | class ProgramInfoView extends PolymerElement { 15 | InfoHelper _model; 16 | 17 | ProgramInfoView.created() : super.created(); 18 | 19 | TableElement get _treeTable => $['prog-info']; 20 | 21 | set dumpInfo(InfoHelper dumpInfo) { 22 | _model = dumpInfo; 23 | _setupProgramwideInfo(); 24 | } 25 | 26 | void _extractClick(_) { 27 | String text = 28 | _model.allOfType('function').map((a) => "${a['name']}").join(', '); 29 | text = Uri.encodeComponent('[$text]'); 30 | String encoded = 'data:text/plain;charset=utf-8,$text'; 31 | 32 | AnchorElement downloadLink = new AnchorElement(href: encoded); 33 | downloadLink.text = 'download file'; 34 | downloadLink.setAttribute('download', 'functions.txt'); 35 | downloadLink.click(); 36 | } 37 | 38 | void _setupProgramwideInfo() { 39 | _treeTable.children.clear(); 40 | var map = { 41 | 'Program Size': '${_model.size} bytes', 42 | 'Compile Time': _model.compilationMoment, 43 | 'Compile Duration': _model.compilationDuration, 44 | 'noSuchMethod Enabled': new SpanElement() 45 | ..text = _model.noSuchMethodEnabled.toString() 46 | ..style.background = _model.noSuchMethodEnabled ? "red" : "white", 47 | // TODO(tyoverby): add support for loading files generated by 48 | // TRACE_CALLS and compare them to the functions that are produced 49 | // by dart2js. 50 | 'Extract Function Names': new ButtonElement() 51 | ..text = 'extract' 52 | ..onClick.listen(_extractClick) 53 | }; 54 | 55 | map.forEach((k, v) { 56 | var row = _treeTable.addRow(); 57 | row.addCell()..text = k; 58 | if (v is String) { 59 | row.addCell()..text = v; 60 | } else if (v is Element) { 61 | row.addCell()..children.add(v); 62 | } else { 63 | throw new ArgumentError("Unexpected value in map: $v"); 64 | } 65 | }); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/src/tree_table.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 34 | 35 | 36 | 37 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /web/viewer.css: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file */ 2 | /* for details. All rights reserved. Use of this source code is governed by a */ 3 | /* BSD-style license that can be found in the LICENSE file. */ 4 | 5 | body { 6 | font-family: 'RobotoDraft', 'Open Sans', sans-serif; 7 | font-size: 14px; 8 | font-weight: normal; 9 | line-height: 1.2em; 10 | margin: 0px; 11 | } 12 | 13 | h1, p { 14 | color: #333; 15 | } 16 | 17 | #load-slide { 18 | text-align: center; 19 | } 20 | 21 | #load-slide button { 22 | margin: 1em; 23 | } 24 | 25 | #sample_container_id { 26 | width: 100%; 27 | height: 400px; 28 | position: relative; 29 | border: 1px solid #ccc; 30 | background-color: #fff; 31 | } 32 | 33 | paper-tabs { 34 | width: 600px; 35 | height: 50px; 36 | position: relative; 37 | top: -7px; 38 | } 39 | 40 | #dummy-toolbar { 41 | width: 100%; 42 | height: 50px; 43 | } 44 | 45 | core-toolbar { 46 | background-color: #00bcd4; 47 | color: #fff; 48 | box-shadow: 0px 3px 2px rgba(0,0,0,0.2); 49 | 50 | height: 50px; 51 | position: fixed; 52 | top: -60px; 53 | width: 100%; 54 | z-index: 2; 55 | 56 | -webkit-transition: top 0.30s ease-in-out; 57 | transition: top 0.30s ease-in-out; 58 | } 59 | 60 | #title { 61 | font-size: 21px; 62 | position: relative; 63 | top: -8px; 64 | } 65 | 66 | paper-tab { 67 | font-size: 14px; 68 | } 69 | 70 | .slidecontainer { 71 | background: red; 72 | margin: 0 auto; 73 | width: 95%; 74 | position: relative; 75 | } 76 | 77 | .slide { 78 | display: block; 79 | position: absolute; 80 | height: auto; 81 | left: 0; 82 | right: 0; 83 | margin-left: auto; 84 | margin-right: auto; 85 | margin-bottom: 15px; 86 | padding: 20px; 87 | 88 | 89 | overflow: hidden; 90 | -webkit-transition: opacity 0.10s ease-in-out, 91 | left 0.10s ease-in-out, 92 | max-height 0.2s ease-out 0.10s; 93 | transition: opacity 0.10s ease-in-out, 94 | left 0.10s ease-in-out, 95 | max-height 0.2s ease-out 0.10s; 96 | opacity: 0; 97 | box-shadow: 0px 2px 5px rgba(0,0,0,0.26); 98 | border-radius: 2px; 99 | } 100 | 101 | .slide.hidden { 102 | box-shadow: none; 103 | padding: 2px; 104 | } 105 | 106 | body /deep/ .preSpan { 107 | font-family: monospace; 108 | /*white-space: pre;*/ 109 | } 110 | -------------------------------------------------------------------------------- /lib/src/diff_view.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library dump_viz.diff_view; 6 | 7 | import 'dart:convert'; 8 | import 'dart:html' hide Selection; 9 | 10 | import 'package:polymer/polymer.dart'; 11 | 12 | import 'diff_alg.dart'; 13 | import 'drag_drop_view.dart'; 14 | import 'info_helper.dart'; 15 | 16 | @CustomTag('diff-view') 17 | class DiffView extends PolymerElement { 18 | UListElement get _list => $['list']; 19 | InfoHelper currentlyLoaded; 20 | 21 | DiffView.created() : super.created(); 22 | 23 | InfoHelper _beforeContent; 24 | InfoHelper _afterContent; 25 | 26 | DragDropView get _beforeView => $['before-drop'] as DragDropView; 27 | DragDropView get _afterView => $['after-drop'] as DragDropView; 28 | 29 | ButtonElement get _beforeUseCurrent => 30 | $['before-current-btn'] as ButtonElement; 31 | ButtonElement get _afterUseCurrent => $['after-current-btn'] as ButtonElement; 32 | 33 | void ready() { 34 | assert(currentlyLoaded != null); 35 | 36 | _beforeView.onFile.map(_strToHelper).listen((InfoHelper ih) { 37 | _update(ih, null); 38 | }); 39 | 40 | _afterView.onFile.map(_strToHelper).listen((InfoHelper ih) { 41 | _update(null, ih); 42 | }); 43 | 44 | _beforeUseCurrent.onClick.listen((_) { 45 | _update(currentlyLoaded, null); 46 | }); 47 | 48 | _afterUseCurrent.onClick.listen((_) { 49 | _update(null, currentlyLoaded); 50 | }); 51 | } 52 | 53 | void _update(InfoHelper before, InfoHelper after) { 54 | if (before != null) { 55 | _beforeContent = before; 56 | } 57 | 58 | if (after != null) { 59 | _afterContent = after; 60 | } 61 | 62 | _diff(); 63 | } 64 | 65 | void _diff() { 66 | _list.children.clear(); 67 | 68 | if (_beforeContent == null || _afterContent == null) { 69 | return; 70 | } 71 | 72 | var diffItems = diff(_beforeContent, _afterContent); 73 | 74 | for (DiffItem diffItem in diffItems) { 75 | _addRow(diffItem); 76 | } 77 | } 78 | 79 | void _addRow(DiffItem row) { 80 | var e = new Element.tag('li') 81 | ..classes.add(row.kind) 82 | ..children.addAll([ 83 | new SpanElement()..text = row.path, 84 | new SpanElement() 85 | ..text = row.diff.toString() 86 | ..style.float = "right" 87 | ]); 88 | _list.children.add(e); 89 | } 90 | } 91 | 92 | InfoHelper _strToHelper(String input) => 93 | new InfoHelper.fromJson(JSON.decode(input)); 94 | -------------------------------------------------------------------------------- /lib/src/diff_view.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /lib/src/dependency_view.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library dump_viz.dependency_view; 6 | 7 | import 'dart:html' hide Selection; 8 | 9 | import 'package:polymer/polymer.dart'; 10 | 11 | import 'history_state.dart'; 12 | import 'info_helper.dart'; 13 | 14 | @CustomTag('dependency-view') 15 | class DependencyView extends PolymerElement { 16 | InfoHelper _dumpInfo; 17 | String _currentlyTargeting; 18 | 19 | TableSectionElement ownersTable; 20 | TableSectionElement currentTable; 21 | TableSectionElement ownedTable; 22 | 23 | DependencyView.created() : super.created() {} 24 | 25 | set target(String id) { 26 | $['information'].style.display = 'none'; 27 | $['tables'].style.display = 'block'; 28 | _currentlyTargeting = id; 29 | _populate(id); 30 | } 31 | 32 | String get target { 33 | return _currentlyTargeting; 34 | } 35 | 36 | set dumpInfo(InfoHelper dumpInfo) { 37 | TableSectionElement getTbody(TableElement table) => 38 | table.querySelector('tbody'); 39 | 40 | _dumpInfo = dumpInfo; 41 | ownersTable = getTbody($['in']); 42 | currentTable = getTbody($['current']); 43 | ownedTable = getTbody($['out']); 44 | } 45 | 46 | TableRowElement _generateRow(String id, String mask) { 47 | List path = _dumpInfo.path(id); 48 | if (path == null) return null; 49 | // TODO(TyOverby): Make a polymer element to abstract this mess 50 | return new TableRowElement() 51 | ..children.addAll([ 52 | // Name Column 53 | new TableCellElement()..text = path.join('.'), 54 | new TableCellElement()..text = mask, 55 | // Stats Column 56 | new TableCellElement() 57 | ..children.add(new SpanElement() 58 | ..text = '↖ ${_dumpInfo.reverseDependencies(id).length} | ' 59 | '${_dumpInfo.dependencies(id).length} ↘' 60 | ..style.float = 'right') 61 | ]) 62 | ..onClick.listen( 63 | (_) => HistoryState.switchTo(new HistoryState('dep', depTarget: id))); 64 | } 65 | 66 | void _populate(String id) { 67 | ownersTable.children.clear(); 68 | currentTable.children.clear(); 69 | ownedTable.children.clear(); 70 | 71 | List owners = _dumpInfo.reverseDependencies(id); 72 | List owned = _dumpInfo.dependencies(id); 73 | 74 | ownersTable.children.addAll(_sortedRows(owners)); 75 | currentTable.children.add(_generateRow(id, "")); 76 | ownedTable.children.addAll(_sortedRows(owned)); 77 | } 78 | 79 | Iterable _sortedRows(Iterable ids) { 80 | var sorted = ids.toList() 81 | ..sort( 82 | (sel1, sel2) => _dumpInfo.reverseDependencies(sel1.elementId).length - 83 | _dumpInfo.reverseDependencies(sel2.elementId).length); 84 | return sorted 85 | .map((s) => _generateRow(s.elementId, s.mask)) 86 | .where((a) => a != null); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | Dump Info Visualizer 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | PROGRAM INFO 31 | 32 | 33 | HIERARCHY 34 | 35 | 36 | DEPENDENCIES 37 | 38 | 39 | DIFF 40 | 41 | 47 | 48 | 49 |
50 |
51 | 52 | 53 |
54 | 55 |
56 | 57 | 58 |
59 | 60 |
61 | 62 | 63 |
64 | 65 |
66 | 67 |
68 | 69 |
70 | 71 | 72 | 74 |
75 | 76 | 77 | 78 |
79 | 80 |
81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /lib/src/logical_row.dart: -------------------------------------------------------------------------------- 1 | import 'dart:html'; 2 | 3 | import 'tree_table.dart'; 4 | 5 | typedef LogicalRow GenerateRowFunction(); 6 | 7 | class LogicalRow { 8 | // If the row is currently in an opened state. 9 | bool open = false; 10 | 11 | // A pointer into the data 12 | final Map data; 13 | 14 | String get id => data['id']; 15 | 16 | // A list of 'soon to be children' row functions. 17 | // This is the key part of having the entire data 18 | // structure be lazily evaluated. 19 | final List generatorFns = []; 20 | 21 | // After the generatorFns array is processed when this 22 | // node is expanded, this children array will be filled with 23 | // [LogicalRow]s 24 | final List children = []; 25 | 26 | // If this row is sortable. An example of a non-sortable row 27 | // would be the 'code' and 'parameters' rows of a function 28 | // element properties. 29 | bool sortable = true; 30 | 31 | // If this row is not sortable, use this priority instead. 32 | int nonSortablePriority = 0; 33 | 34 | // A function that is called when this tree needs to be rendered 35 | // into the DOM. 36 | final RenderFunction renderFunction; 37 | 38 | // The actual rendered row. 39 | TreeTableRow rowElement; 40 | 41 | // The TreeTableRow element. Stored here to make show/hide easier. 42 | HtmlElement parentElement; 43 | 44 | // The depth of this row in the overall tree. 45 | final int level; 46 | 47 | // Stored comparator for the sorting function. 48 | // Because the tree is generated lazily, this needs to be stored to 49 | // be called on generation of future children. 50 | LogicalRowComparer sortComparator; 51 | 52 | LogicalRow(this.data, this.renderFunction, this.parentElement, this.level); 53 | 54 | // Add a child function lazily. 55 | GenerateRowFunction addChild(GenerateRowFunction genRowFn) { 56 | this.generatorFns.add(genRowFn); 57 | return genRowFn; 58 | } 59 | 60 | void click() { 61 | open = !open; 62 | if (open) { 63 | if (children.isEmpty) { 64 | children.addAll(generatorFns.map((a) => a())); 65 | // Resort because more children were generated. 66 | sort(sortComparator); 67 | } 68 | this.children.forEach((child) => child.show(before: this.rowElement)); 69 | } else { 70 | this.children.forEach((child) => child.hide()); 71 | } 72 | this.rowElement.setArrow(this.children.isNotEmpty, open); 73 | } 74 | 75 | void hide() { 76 | this.getElement().remove(); 77 | if (this.open) { 78 | this.children.forEach((child) => child.hide()); 79 | } 80 | } 81 | 82 | /** 83 | * Adds this row to the TreeTableElement. 84 | * [before] is an optional element that this row 85 | * will be inserted after. 86 | */ 87 | void show({HtmlElement before}) { 88 | if (before != null) { 89 | // Place this element right after [before] 90 | int loc = this.parentElement.children.indexOf(before) + 1; 91 | print(loc); 92 | this.parentElement.children.insert(loc, this.getElement()); 93 | } else { 94 | // Prepend this element into the table 95 | this.parentElement.children.insert(0, this.getElement()); 96 | //this.parentElement.append(this.getElement()); 97 | } 98 | this.rowElement.level = this.level; 99 | if (!this.rowElement.populated) { 100 | this.renderFunction(this.rowElement, this); 101 | this.rowElement.setArrow(this.generatorFns.isNotEmpty, open); 102 | this.rowElement.populated = true; 103 | } 104 | if (this.open) { 105 | // Open up children spatially after this element 106 | this.children.forEach((child) => child.show(before: this.rowElement)); 107 | } 108 | } 109 | 110 | TreeTableRow getElement() { 111 | if (rowElement != null) { 112 | return rowElement; 113 | } else { 114 | this.rowElement = new TreeTableRow(this); 115 | return this.rowElement; 116 | } 117 | } 118 | 119 | void sort(LogicalRowComparer comparator) { 120 | sortComparator = comparator; 121 | this.children.sort(comparator); 122 | this.children.forEach((child) => child.sort(comparator)); 123 | } 124 | } 125 | 126 | typedef int LogicalRowComparer(LogicalRow a, LogicalRow b); 127 | -------------------------------------------------------------------------------- /lib/src/history_state.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library dump_viz.history; 6 | 7 | import 'dart:async'; 8 | import 'dart:html'; 9 | 10 | import 'dependency_view.dart'; 11 | 12 | abstract class HistoryState { 13 | /// Apply this history state and modify the view. 14 | void apply(); 15 | 16 | /// Called when a HistoryState is about to be 17 | /// moved out of. 18 | void finalize(); 19 | 20 | String get asUrl; 21 | 22 | /// Convert this history state to JSON to be serialized 23 | Map toJson(); 24 | 25 | factory HistoryState(String type, {String depTarget: null}) { 26 | switch (type) { 27 | case 'info': 28 | return new _InfoHistoryState(); 29 | case 'hier': 30 | return new _HierHistoryState(_lastHierPos); 31 | case 'dep': 32 | var target = depTarget; 33 | if (target == null) { 34 | target = _lastDepFocus; 35 | } 36 | return new _DepHistoryState(target); 37 | case 'diff': 38 | return new _DiffHistoryState(_lastDiffPos); 39 | default: 40 | return null; 41 | } 42 | } 43 | 44 | static void setup(Function slideSwitcher, Duration animationTime) { 45 | window.onPopState.listen((popStateEvent) { 46 | var newState = HistoryState.fromJson(popStateEvent.state); 47 | switchTo(newState, fromPop: true); 48 | }); 49 | HistoryState._slideSwitcher = slideSwitcher; 50 | HistoryState._animationTime = animationTime; 51 | } 52 | 53 | static void switchTo(HistoryState newState, {fromPop: false}) { 54 | if (_currentState != null) { 55 | _currentState.finalize(); 56 | } 57 | if (!fromPop) { 58 | window.history.pushState(newState.toJson(), "test", "?" + newState.asUrl); 59 | } 60 | newState.apply(); 61 | _currentState = newState; 62 | } 63 | 64 | /// Convert json to a HistoryState 65 | static HistoryState fromJson(Map json) { 66 | switch (json['kind']) { 67 | case 'info': 68 | return new _InfoHistoryState(); 69 | case 'hier': 70 | return new _HierHistoryState(json['pos']); 71 | case 'dep': 72 | return new _DepHistoryState(json['focus']); 73 | case 'diff': 74 | return new _DiffHistoryState(json['pos']); 75 | default: 76 | return null; 77 | } 78 | } 79 | 80 | // When modifying the history from outside the 81 | // back / forward buttons, we need to track these 82 | // values ourselves. 83 | static int _lastHierPos = 0; 84 | static int _lastDiffPos = 0; 85 | static String _lastDepFocus = null; 86 | static HistoryState _currentState = null; 87 | static Function _slideSwitcher; 88 | static Duration _animationTime; 89 | } 90 | 91 | class _InfoHistoryState implements HistoryState { 92 | String get asUrl => "slide=info"; 93 | 94 | void apply() { 95 | HistoryState._slideSwitcher('info'); 96 | } 97 | 98 | void finalize() {} 99 | 100 | dynamic toJson() { 101 | return {'kind': 'info'}; 102 | } 103 | } 104 | 105 | class _DiffHistoryState implements HistoryState { 106 | int pos; 107 | _DiffHistoryState(this.pos); 108 | 109 | String get asUrl => "slide=diff"; 110 | 111 | void apply() { 112 | HistoryState._slideSwitcher('diff'); 113 | new Timer(HistoryState._animationTime * 3, () { 114 | document.body.scrollTop = pos; 115 | }); 116 | } 117 | 118 | void finalize() { 119 | pos = document.body.scrollTop; 120 | HistoryState._lastDiffPos = pos; 121 | window.history.replaceState(this.toJson(), "", ""); 122 | } 123 | 124 | dynamic toJson() { 125 | return {'kind': 'diff', 'pos': pos}; 126 | } 127 | } 128 | 129 | class _HierHistoryState implements HistoryState { 130 | int pos; 131 | _HierHistoryState(this.pos); 132 | 133 | String get asUrl => "slide=hier"; 134 | 135 | void apply() { 136 | HistoryState._slideSwitcher('hier'); 137 | new Timer(HistoryState._animationTime * 3, () { 138 | document.body.scrollTop = pos; 139 | }); 140 | } 141 | 142 | void finalize() { 143 | pos = document.body.scrollTop; 144 | HistoryState._lastHierPos = pos; 145 | window.history.replaceState(this.toJson(), "", ""); 146 | } 147 | 148 | dynamic toJson() { 149 | return {'kind': 'hier', 'pos': pos}; 150 | } 151 | } 152 | 153 | class _DepHistoryState implements HistoryState { 154 | final String focus; 155 | _DepHistoryState(this.focus); 156 | 157 | String get asUrl => "slide=dep&focus=$focus"; 158 | 159 | void apply() { 160 | DependencyView depview = querySelector('dependency-view'); 161 | if (focus != null) { 162 | depview.target = focus; 163 | } 164 | HistoryState._slideSwitcher('dep'); 165 | HistoryState._lastDepFocus = focus; 166 | } 167 | 168 | void finalize() { 169 | HistoryState._lastDepFocus = focus; 170 | } 171 | 172 | dynamic toJson() { 173 | return {'kind': 'dep', 'focus': focus}; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /web/viewer.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library dump_viz.viewer; 6 | 7 | import 'dart:async'; 8 | import 'dart:convert'; 9 | import 'dart:html'; 10 | import 'dart:js'; 11 | 12 | import 'package:paper_elements/paper_ripple.dart'; 13 | import 'package:paper_elements/paper_tab.dart'; 14 | import 'package:polymer/polymer.dart'; 15 | 16 | import 'package:dump_viz/dump_viz.dart'; 17 | 18 | export 'package:polymer/init.dart'; 19 | 20 | const List _slides = const [ 21 | 'info', 22 | 'hier', 23 | 'dep', 24 | 'load', 25 | 'diff' 26 | ]; 27 | const Duration _animationTime = const Duration(milliseconds: 10); 28 | 29 | const _localStorageKey = 'dump_viz.last_file'; 30 | 31 | bool get hasCache => window.localStorage.containsKey(_localStorageKey); 32 | 33 | String _getCache() { 34 | if (!hasCache) { 35 | throw 'No value stored!'; 36 | } 37 | return window.localStorage[_localStorageKey]; 38 | } 39 | 40 | String _setCache(String value) => window.localStorage[_localStorageKey] = value; 41 | 42 | void _noSlide() { 43 | // Disable all of the slides and tabs 44 | for (String id in _slides) { 45 | var slide = document.querySelector('#$id-slide'); 46 | slide.style.opacity = '0'; 47 | //slide.style.left = '100px'; 48 | slide.style.left = '0px'; 49 | slide.style.maxHeight = '0px'; 50 | slide.style.zIndex = '0'; 51 | 52 | var tab = document.querySelector('#$id-tab'); 53 | if (tab != null) { 54 | tab.classes.remove('core-selected'); 55 | } 56 | } 57 | } 58 | 59 | void _switchSlide(String id, {bool fromMouse: false}) { 60 | _noSlide(); 61 | var slide = document.querySelector('#$id-slide'); 62 | slide.style.maxHeight = 'none'; 63 | slide.style.zIndex = '1'; 64 | 65 | new Timer(_animationTime, () { 66 | slide.style.opacity = '1'; 67 | slide.style.left = '0px'; 68 | var tab = document.querySelector('#$id-tab'); 69 | 70 | if (tab != null) { 71 | tab.classes.add('core-selected'); 72 | var tabs = document.querySelector('paper-tabs'); 73 | tabs.attributes['selected'] = tab.attributes['offset']; 74 | // Draw a ripple on the tab if we didn't already click on it. 75 | if (!fromMouse) { 76 | PaperRipple ripple = tab.shadowRoot.querySelector('paper-ripple'); 77 | var pos = { 78 | 'x': tabs.offsetLeft + tab.offsetLeft + tab.clientWidth / 2, 79 | 'y': 0 80 | }; 81 | ripple.jsElement.callMethod('downAction', [new JsObject.jsify(pos)]); 82 | window.animationFrame 83 | .then((_) => ripple.jsElement.callMethod('upAction', [])); 84 | } 85 | } 86 | }); 87 | } 88 | 89 | ButtonElement get _useLastButton => querySelector('#useLast') as ButtonElement; 90 | 91 | ButtonElement get _clearCacheButton => 92 | querySelector('#clearCache') as ButtonElement; 93 | 94 | @whenPolymerReady 95 | void init() { 96 | HistoryState.setup(_switchSlide, _animationTime); 97 | _noSlide(); 98 | _switchSlide('load'); 99 | 100 | bool alreadyLoaded = false; 101 | 102 | var dragDrop = querySelector('drag-drop-view') as DragDropView; 103 | var dependencyView = querySelector('dependency-view') as DependencyView; 104 | var diffView = querySelector('diff-view') as DiffView; 105 | var hierarchyView = querySelector('hierarchy-view') as HierarchyView; 106 | var programInfoView = querySelector('program-info-view') as ProgramInfoView; 107 | 108 | var tabs = querySelectorAll('paper-tab'); 109 | for (PaperTab tab in tabs) { 110 | tab.onClick.listen((_) { 111 | String link = tab.attributes['slide']; 112 | HistoryState.switchTo(new HistoryState(link)); 113 | }); 114 | } 115 | 116 | void loadJson(String jsonString) { 117 | var json; 118 | try { 119 | json = JSON.decode(jsonString) as Map; 120 | } catch (e) { 121 | window.console.error("Error parsing json"); 122 | window.console.error(e); 123 | return; 124 | } 125 | document.querySelector('core-toolbar').style.top = "0"; 126 | 127 | var info = new InfoHelper.fromJson(json); 128 | 129 | diffView.currentlyLoaded = info; 130 | 131 | if (alreadyLoaded) { 132 | hierarchyView.clear(); 133 | } else { 134 | HistoryState.switchTo(new HistoryState('info')); 135 | } 136 | 137 | var dumpVersion = info.dumpVersion as num; 138 | 139 | if (dumpVersion < 1 || dumpVersion > 5) { 140 | window.alert('Unknown dump-info version: $dumpVersion'); 141 | return; 142 | } 143 | 144 | dependencyView.dumpInfo = info; 145 | hierarchyView.dumpInfo = info; 146 | programInfoView.dumpInfo = info; 147 | 148 | alreadyLoaded = true; 149 | _updateButton(); 150 | } 151 | 152 | // When a file is loaded 153 | dragDrop.onFile.listen((json) { 154 | try { 155 | _setCache(json); 156 | } catch (e) { 157 | window.console.error( 158 | 'Could not populate cache. May be too big. Try the clear button.'); 159 | window.console.error(e); 160 | } 161 | loadJson(json); 162 | }); 163 | 164 | _clearCacheButton.onClick.listen((_) { 165 | window.localStorage.clear(); 166 | _updateButton(); 167 | }); 168 | 169 | _useLastButton.onClick.listen((_) { 170 | loadJson(_getCache()); 171 | }); 172 | 173 | _updateButton(); 174 | } 175 | 176 | void _updateButton() { 177 | _useLastButton.disabled = !hasCache; 178 | _clearCacheButton.disabled = window.localStorage.isEmpty; 179 | } 180 | -------------------------------------------------------------------------------- /lib/src/tree_table.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library tree_table; 6 | 7 | import 'dart:collection' show Queue; 8 | import 'dart:html'; 9 | 10 | import 'package:observe/observe.dart'; 11 | import 'package:polymer/polymer.dart'; 12 | 13 | import 'logical_row.dart'; 14 | 15 | typedef void RenderFunction(TreeTableRow ttr, LogicalRow logicalRow); 16 | 17 | /// The amount of padding to be added to each level in the tree. 18 | const int _PADDING_SIZE = 25; 19 | 20 | /** 21 | * A Polymer TreeTable element. 22 | */ 23 | @CustomTag('tree-table') 24 | class TreeTable extends PolymerElement { 25 | // The top-level rows in the tree. 26 | List _rootNodes = []; 27 | 28 | // A set of the nodes that were opened in the tree-table 29 | // before the json was reloaded. Storing this information 30 | // makes it possible to re-open the tree to where it 31 | // was before. 32 | Set _previouslyOpened = new Set(); 33 | 34 | TreeTable.created() : super.created() {} 35 | 36 | factory TreeTable() { 37 | return document.createElement('tree-table') as TreeTable; 38 | } 39 | 40 | /** 41 | * Adds a row to the table that is at the top level of the 42 | * tree in the structure. 43 | */ 44 | LogicalRow addTopLevel(LogicalRow child) { 45 | _rootNodes.add(child); 46 | return child; 47 | } 48 | 49 | HtmlElement get tbody => this.$['inner_table_body']; 50 | 51 | /** 52 | * Clears the table. Used when reloading the file. 53 | */ 54 | void clear() { 55 | Set openedPaths = new Set(); 56 | Queue possiblyOpen = new Queue(); 57 | possiblyOpen.addAll(_rootNodes); 58 | while (possiblyOpen.isNotEmpty) { 59 | LogicalRow next = possiblyOpen.removeFirst(); 60 | if (next.open) { 61 | openedPaths.add(next.id); 62 | possiblyOpen.addAll(next.children); 63 | } 64 | } 65 | 66 | this._previouslyOpened = openedPaths; 67 | 68 | _rootNodes.clear(); 69 | this.children.clear(); 70 | this.$['inner_table_head'].children.clear(); 71 | } 72 | 73 | void reset() { 74 | Queue couldBeOpened = new Queue(); 75 | couldBeOpened.addAll(_rootNodes); 76 | while (couldBeOpened.isNotEmpty) { 77 | LogicalRow next = couldBeOpened.removeFirst(); 78 | if (_previouslyOpened.contains(next.id)) { 79 | next.click(); 80 | couldBeOpened.addAll(next.children); 81 | } 82 | } 83 | } 84 | 85 | /** 86 | * Sets the titles for the columns of the table. 87 | */ 88 | void columnInfo(List names, List helps, List sizes) { 89 | for (int i = 0; i < names.length; i++) { 90 | TableCellElement cell = new TableCellElement(); 91 | cell.style.textAlign = 'center'; 92 | cell.text = names[i]; 93 | cell.title = helps[i]; 94 | String size = sizes[i]; 95 | if (size != null) { 96 | cell.style.width = size; 97 | } 98 | this.$['inner_table_head'].children.add(cell); 99 | } 100 | } 101 | 102 | /** 103 | * Sorts this TreeTable by creating a comparator function 104 | * for LogicalRows sorting on a key in the data section. 105 | */ 106 | void sort(String key) { 107 | var comparator = (LogicalRow a, LogicalRow b) { 108 | if (a.sortable && !b.sortable) { 109 | return 1; 110 | } else if (!a.sortable && b.sortable) { 111 | return -1; 112 | } else if (!a.sortable && !b.sortable) { 113 | return a.nonSortablePriority.compareTo(b.nonSortablePriority); 114 | } 115 | 116 | var d1 = a.data[key]; 117 | var d2 = b.data[key]; 118 | if (d1 == null) d1 = ''; 119 | if (d2 == null) d2 = ''; 120 | if (d1 is num && d2 is num) { 121 | return d1.compareTo(d2); 122 | } 123 | return d2.toString().compareTo(d1.toString()); 124 | }; 125 | 126 | // Clear all of the rows in the table because 127 | // we will be sorting the `logical` rows and then 128 | // re-add them all. 129 | tbody.children.clear(); 130 | this._rootNodes.sort(comparator); 131 | 132 | for (var rootNode in this._rootNodes) { 133 | rootNode.sort(comparator); 134 | } 135 | 136 | // Re-add the now-sorted rows. 137 | for (var rootNode in this._rootNodes) { 138 | rootNode.show(); 139 | } 140 | } 141 | } 142 | 143 | /** 144 | * A TreeTableRow element. TreeTableRows are only to be inserted into 145 | * a TreeTable element. Because TreeTableRows are nodes in the tree 146 | * structure, they can have children added to them via the [addChild] 147 | * method. 148 | */ 149 | @CustomTag('tree-table-row') 150 | class TreeTableRow extends TableRowElement with Polymer, Observable { 151 | LogicalRow logicalRow; 152 | // True if data has been rendered into this row. 153 | bool populated = false; 154 | 155 | TreeTableRow.created() : super.created() { 156 | polymerCreated(); 157 | } 158 | 159 | factory TreeTableRow(LogicalRow logicalRow) { 160 | TreeTableRow ttr = document.createElement('tr', 'tree-table-row'); 161 | ttr.logicalRow = logicalRow; 162 | ttr.onClick.listen((_) => ttr.logicalRow.click()); 163 | return ttr; 164 | } 165 | 166 | // Set the cells in this row. 167 | void set data(List elements) { 168 | if (elements.isNotEmpty) { 169 | this.$['content'].text = elements.first.text; 170 | } 171 | for (TableCellElement cell in elements.skip(1)) { 172 | this.shadowRoot.append(cell); 173 | } 174 | } 175 | 176 | // Set the level of indentation for this row. 177 | void set level(int level) { 178 | this.$['first-cell'].style.paddingLeft = "${level * _PADDING_SIZE}px"; 179 | } 180 | 181 | void setArrow(bool hasChildren, bool open) { 182 | if (hasChildren) { 183 | if (open) { 184 | this.$['arrow'].text = '▼'; 185 | } else { 186 | this.$['arrow'].text = '▶'; 187 | } 188 | } else { 189 | this.$['arrow'].text = '○'; 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /lib/src/info_helper.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library dump_viz.info_helper; 6 | 7 | import 'dart:collection' show Queue; 8 | 9 | class Selection { 10 | String elementId; 11 | final String mask; 12 | Selection(this.elementId, this.mask); 13 | } 14 | 15 | class InfoHelper { 16 | final int dumpVersion; 17 | 18 | // A Map of type (kind -> (id -> element properties)) that 19 | // stores the properties of elements. 20 | final Map>> _elementProperties; 21 | 22 | // A map storing the raw representation of the whole-program 23 | // properties that dump-info generates. 24 | final Map _programProperties; 25 | 26 | // A map from the ids of an element to the 27 | // properties of that element. 28 | final Map> _idToProperties = {}; 29 | 30 | // A map of dependencies from an ID of an element 31 | // to the IDs of the elements that it depends on. 32 | final Map> _dependencies = {}; 33 | 34 | // A map of depedencies from an ID of an element 35 | // to the IDS of the elements that depend on it. 36 | final Map> _reverseDependencies = {}; 37 | 38 | // A mapping from an ID of an element to that elements path. 39 | // A path for a function might look like 40 | // [library name, class name, function name] 41 | final Map> _path = {}; 42 | 43 | // A mapping from an id to a joined path. 44 | // A joined path might look like "libname.classname.functionname" 45 | final Map _joinedPath = {}; 46 | 47 | // A mapping from a joined path to an id. 48 | final Map _reverseJoinedPath = {}; 49 | 50 | Iterable> allOfType(String type) => 51 | _elementProperties[type].values; 52 | 53 | List dependencies(String id) { 54 | var deps = _dependencies[id]; 55 | if (deps == null) { 56 | return const []; 57 | } 58 | return deps; 59 | } 60 | 61 | List reverseDependencies(String id) { 62 | if (_reverseDependencies[id] != null) { 63 | return _reverseDependencies[id]; 64 | } else { 65 | return const []; 66 | } 67 | } 68 | 69 | Iterable get joinedPaths => _reverseJoinedPath.keys; 70 | 71 | String joinedPathFromId(String id) { 72 | return _joinedPath[id]; 73 | } 74 | 75 | String idFromJoinedPath(String path) { 76 | return _reverseJoinedPath[path]; 77 | } 78 | 79 | int sizeOf(String id) => _idToProperties[id]['size']; 80 | 81 | Map properties(String id) => _idToProperties[id]; 82 | List path(String id) => _path[id]; 83 | 84 | String get compilationMoment => 85 | _programProperties['compilationMoment'].toString(); 86 | String get compilationDuration => 87 | _programProperties['compilationDuration'].toString(); 88 | String get dart2jsVersion => _programProperties['dart2jsVersion']; 89 | int get size => _programProperties['size']; 90 | bool get noSuchMethodEnabled => _programProperties['noSuchMethodEnabled']; 91 | 92 | // Given an id, returns the node associated with it. 93 | dynamic elementById(String id) { 94 | var split = id.split("/"); 95 | return _elementProperties[split[0]][split[1]]; 96 | } 97 | 98 | Selection selectionFor(dynamic input) { 99 | if (input is String) { 100 | return new Selection(input, null); 101 | } else if (input is Map) { 102 | return new Selection(input['id'], input['mask']); 103 | } else { 104 | throw new ArgumentError("$input is unexpected."); 105 | } 106 | } 107 | 108 | factory InfoHelper.fromJson(Map json) => new InfoHelper( 109 | json['dump_version'], json['elements'], json['holding'], json['program']); 110 | 111 | InfoHelper( 112 | this.dumpVersion, 113 | Map>> properties, 114 | Map> deps, 115 | Map programProperties) 116 | : _elementProperties = properties, 117 | _programProperties = programProperties { 118 | // Set up dependencies 119 | for (Map section in properties.values) { 120 | for (var prop in section.values) { 121 | String id = prop['id']; 122 | _idToProperties[id] = prop; 123 | if (deps[id] != null) { 124 | _dependencies[id] = deps[id].map(selectionFor).toList(); 125 | } 126 | } 127 | } 128 | 129 | // Set up reverse dependencies 130 | deps.forEach((e, deps) { 131 | for (var dep in deps) { 132 | Selection selection = selectionFor(dep); 133 | _reverseDependencies 134 | .putIfAbsent(selection.elementId, () => []) 135 | .add(selection..elementId = e); 136 | } 137 | }); 138 | 139 | // Set up paths 140 | void traverseNames(Map node, List prevPath) { 141 | List newPath = new List.from(prevPath)..add(node['name']); 142 | String id = node['id']; 143 | _path[id] = newPath; 144 | String joined = newPath.join('.'); 145 | _joinedPath[id] = joined; 146 | _reverseJoinedPath[joined] = id; 147 | 148 | if (node['children'] != null) { 149 | for (String id in node['children']) { 150 | traverseNames(elementById(id), newPath); 151 | } 152 | } 153 | } 154 | 155 | if (properties.containsKey('library')) { 156 | for (var node in properties['library'].values) { 157 | traverseNames(node, []); 158 | } 159 | } 160 | } 161 | 162 | bool _parentsAllContained(String id, Set container) => 163 | reverseDependencies(id).every((a) => container.contains(a.elementId)); 164 | 165 | Set _triviallyReachedFrom(String id) { 166 | Queue queue = new Queue(); 167 | Set owns = new Set(); 168 | 169 | queue.add(id); 170 | owns.add(id); 171 | 172 | while (queue.isNotEmpty) { 173 | String next = queue.removeFirst(); 174 | for (String child in dependencies(next).map((a) => a.elementId)) { 175 | if (!owns.contains(child) && _parentsAllContained(child, owns)) { 176 | queue.add(child); 177 | owns.add(child); 178 | } 179 | } 180 | } 181 | return owns; 182 | } 183 | 184 | int triviallyOwnedSize(String id) => _triviallyReachedFrom(id) 185 | .map((a) => properties(a)['size']) 186 | .reduce((a, b) => a + b); 187 | } 188 | -------------------------------------------------------------------------------- /lib/src/deps_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 27 | 33 | 34 | 41 | 47 | 48 | 55 | 61 | 62 | 69 | 75 | 76 | 83 | 89 | 90 | 91 | 111 | 115 | 119 | 123 | 124 | 126 | 127 | 129 | image/svg+xml 130 | 132 | 133 | 134 | 135 | 136 | 141 | 148 | 152 | 155 | 160 | 165 | 166 | 174 | 175 | 179 | 182 | 187 | 192 | 193 | 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See http://pub.dartlang.org/doc/glossary.html#lockfile 3 | packages: 4 | analyzer: 5 | description: 6 | name: analyzer 7 | url: "https://pub.dartlang.org" 8 | source: hosted 9 | version: "0.27.6" 10 | args: 11 | description: 12 | name: args 13 | url: "https://pub.dartlang.org" 14 | source: hosted 15 | version: "0.13.6" 16 | async: 17 | description: 18 | name: async 19 | url: "https://pub.dartlang.org" 20 | source: hosted 21 | version: "1.11.2" 22 | barback: 23 | description: 24 | name: barback 25 | url: "https://pub.dartlang.org" 26 | source: hosted 27 | version: "0.15.2+9" 28 | boolean_selector: 29 | description: 30 | name: boolean_selector 31 | url: "https://pub.dartlang.org" 32 | source: hosted 33 | version: "1.0.2" 34 | browser: 35 | description: 36 | name: browser 37 | url: "https://pub.dartlang.org" 38 | source: hosted 39 | version: "0.10.0+2" 40 | charcode: 41 | description: 42 | name: charcode 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.1.0" 46 | cli_util: 47 | description: 48 | name: cli_util 49 | url: "https://pub.dartlang.org" 50 | source: hosted 51 | version: "0.0.1+2" 52 | code_transformers: 53 | description: 54 | name: code_transformers 55 | url: "https://pub.dartlang.org" 56 | source: hosted 57 | version: "0.4.2+3" 58 | collection: 59 | description: 60 | name: collection 61 | url: "https://pub.dartlang.org" 62 | source: hosted 63 | version: "1.9.1" 64 | convert: 65 | description: 66 | name: convert 67 | url: "https://pub.dartlang.org" 68 | source: hosted 69 | version: "2.0.1" 70 | core_elements: 71 | description: 72 | name: core_elements 73 | url: "https://pub.dartlang.org" 74 | source: hosted 75 | version: "0.7.1+6" 76 | crypto: 77 | description: 78 | name: crypto 79 | url: "https://pub.dartlang.org" 80 | source: hosted 81 | version: "2.0.1" 82 | csslib: 83 | description: 84 | name: csslib 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "0.13.2" 88 | dart_style: 89 | description: 90 | name: dart_style 91 | url: "https://pub.dartlang.org" 92 | source: hosted 93 | version: "0.2.10" 94 | glob: 95 | description: 96 | name: glob 97 | url: "https://pub.dartlang.org" 98 | source: hosted 99 | version: "1.1.3" 100 | html: 101 | description: 102 | name: html 103 | url: "https://pub.dartlang.org" 104 | source: hosted 105 | version: "0.12.2+2" 106 | http: 107 | description: 108 | name: http 109 | url: "https://pub.dartlang.org" 110 | source: hosted 111 | version: "0.11.3+9" 112 | http_multi_server: 113 | description: 114 | name: http_multi_server 115 | url: "https://pub.dartlang.org" 116 | source: hosted 117 | version: "2.0.2" 118 | http_parser: 119 | description: 120 | name: http_parser 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "3.0.2" 124 | initialize: 125 | description: 126 | name: initialize 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "0.6.2+2" 130 | isolate: 131 | description: 132 | name: isolate 133 | url: "https://pub.dartlang.org" 134 | source: hosted 135 | version: "0.2.3" 136 | logging: 137 | description: 138 | name: logging 139 | url: "https://pub.dartlang.org" 140 | source: hosted 141 | version: "0.11.3+1" 142 | matcher: 143 | description: 144 | name: matcher 145 | url: "https://pub.dartlang.org" 146 | source: hosted 147 | version: "0.12.0+2" 148 | mime: 149 | description: 150 | name: mime 151 | url: "https://pub.dartlang.org" 152 | source: hosted 153 | version: "0.9.3" 154 | observe: 155 | description: 156 | name: observe 157 | url: "https://pub.dartlang.org" 158 | source: hosted 159 | version: "0.13.3+1" 160 | package_config: 161 | description: 162 | name: package_config 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "1.0.0" 166 | package_resolver: 167 | description: 168 | name: package_resolver 169 | url: "https://pub.dartlang.org" 170 | source: hosted 171 | version: "1.0.2" 172 | paper_elements: 173 | description: 174 | name: paper_elements 175 | url: "https://pub.dartlang.org" 176 | source: hosted 177 | version: "0.7.1+2" 178 | path: 179 | description: 180 | name: path 181 | url: "https://pub.dartlang.org" 182 | source: hosted 183 | version: "1.3.9" 184 | plugin: 185 | description: 186 | name: plugin 187 | url: "https://pub.dartlang.org" 188 | source: hosted 189 | version: "0.2.0" 190 | polymer: 191 | description: 192 | name: polymer 193 | url: "https://pub.dartlang.org" 194 | source: hosted 195 | version: "0.16.4+2" 196 | polymer_expressions: 197 | description: 198 | name: polymer_expressions 199 | url: "https://pub.dartlang.org" 200 | source: hosted 201 | version: "0.13.1" 202 | polymer_interop: 203 | description: 204 | name: polymer_interop 205 | url: "https://pub.dartlang.org" 206 | source: hosted 207 | version: "0.1.2+1" 208 | pool: 209 | description: 210 | name: pool 211 | url: "https://pub.dartlang.org" 212 | source: hosted 213 | version: "1.2.4" 214 | pub_semver: 215 | description: 216 | name: pub_semver 217 | url: "https://pub.dartlang.org" 218 | source: hosted 219 | version: "1.3.0" 220 | quiver: 221 | description: 222 | name: quiver 223 | url: "https://pub.dartlang.org" 224 | source: hosted 225 | version: "0.21.4" 226 | shelf: 227 | description: 228 | name: shelf 229 | url: "https://pub.dartlang.org" 230 | source: hosted 231 | version: "0.6.5+3" 232 | shelf_packages_handler: 233 | description: 234 | name: shelf_packages_handler 235 | url: "https://pub.dartlang.org" 236 | source: hosted 237 | version: "1.0.0" 238 | shelf_static: 239 | description: 240 | name: shelf_static 241 | url: "https://pub.dartlang.org" 242 | source: hosted 243 | version: "0.2.4" 244 | shelf_web_socket: 245 | description: 246 | name: shelf_web_socket 247 | url: "https://pub.dartlang.org" 248 | source: hosted 249 | version: "0.2.1" 250 | smoke: 251 | description: 252 | name: smoke 253 | url: "https://pub.dartlang.org" 254 | source: hosted 255 | version: "0.3.6+2" 256 | source_map_stack_trace: 257 | description: 258 | name: source_map_stack_trace 259 | url: "https://pub.dartlang.org" 260 | source: hosted 261 | version: "1.1.3" 262 | source_maps: 263 | description: 264 | name: source_maps 265 | url: "https://pub.dartlang.org" 266 | source: hosted 267 | version: "0.10.1+1" 268 | source_span: 269 | description: 270 | name: source_span 271 | url: "https://pub.dartlang.org" 272 | source: hosted 273 | version: "1.2.3" 274 | stack_trace: 275 | description: 276 | name: stack_trace 277 | url: "https://pub.dartlang.org" 278 | source: hosted 279 | version: "1.6.7" 280 | stream_channel: 281 | description: 282 | name: stream_channel 283 | url: "https://pub.dartlang.org" 284 | source: hosted 285 | version: "1.5.0" 286 | string_scanner: 287 | description: 288 | name: string_scanner 289 | url: "https://pub.dartlang.org" 290 | source: hosted 291 | version: "1.0.0" 292 | template_binding: 293 | description: 294 | name: template_binding 295 | url: "https://pub.dartlang.org" 296 | source: hosted 297 | version: "0.14.0+4" 298 | test: 299 | description: 300 | name: test 301 | url: "https://pub.dartlang.org" 302 | source: hosted 303 | version: "0.12.15+7" 304 | typed_data: 305 | description: 306 | name: typed_data 307 | url: "https://pub.dartlang.org" 308 | source: hosted 309 | version: "1.1.3" 310 | utf: 311 | description: 312 | name: utf 313 | url: "https://pub.dartlang.org" 314 | source: hosted 315 | version: "0.9.0+3" 316 | watcher: 317 | description: 318 | name: watcher 319 | url: "https://pub.dartlang.org" 320 | source: hosted 321 | version: "0.9.7+3" 322 | web_components: 323 | description: 324 | name: web_components 325 | url: "https://pub.dartlang.org" 326 | source: hosted 327 | version: "0.12.3" 328 | web_socket_channel: 329 | description: 330 | name: web_socket_channel 331 | url: "https://pub.dartlang.org" 332 | source: hosted 333 | version: "1.0.4" 334 | when: 335 | description: 336 | name: when 337 | url: "https://pub.dartlang.org" 338 | source: hosted 339 | version: "0.2.0" 340 | which: 341 | description: 342 | name: which 343 | url: "https://pub.dartlang.org" 344 | source: hosted 345 | version: "0.1.3" 346 | yaml: 347 | description: 348 | name: yaml 349 | url: "https://pub.dartlang.org" 350 | source: hosted 351 | version: "2.1.10" 352 | sdks: 353 | dart: ">=1.17.0-dev.6.2 <2.0.0" 354 | -------------------------------------------------------------------------------- /lib/src/hierarchy_view.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library dump_viz.hierarchy_view; 6 | 7 | import 'dart:html'; 8 | 9 | import 'package:polymer/polymer.dart'; 10 | 11 | import 'history_state.dart'; 12 | import 'info_helper.dart'; 13 | import 'logical_row.dart'; 14 | import 'tree_table.dart'; 15 | 16 | const _columnNames = const ['Kind', 'Name', 'Bytes', 'Bytes R', '%', 'Type']; 17 | 18 | const _columnHelp = const [ 19 | '', 20 | 'The given name of the element', 21 | 'The direct size attributed to the element', 22 | 'The sum of the sizes of all the elements that can ' 23 | 'only be reached from this element', 24 | 'The percentage of the direct size compared to the program size', 25 | 'The given type of the element' 26 | ]; 27 | 28 | const _columnSizes = const ["200px", null, "100px", "100px", "70px", null]; 29 | 30 | @CustomTag('hierarchy-view') 31 | class HierarchyView extends PolymerElement { 32 | HierarchyView.created() : super.created(); 33 | 34 | InfoHelper _model; 35 | 36 | TreeTable get _treeTable => $['treeTable']; 37 | SelectElement get _select => $['selectSort']; 38 | 39 | void ready() { 40 | super.ready(); 41 | 42 | // Sort by chosen sorting methods. 43 | _select.value = 'name'; 44 | 45 | _select.onChange.listen((e) { 46 | _treeTable.sort(_sortBy); 47 | }); 48 | } 49 | 50 | String get _sortBy => _select.value; 51 | 52 | void clear() { 53 | _treeTable.clear(); 54 | } 55 | 56 | void set dumpInfo(InfoHelper info) { 57 | this._model = info; 58 | _display(); 59 | _treeTable.sort(_sortBy); 60 | _treeTable.reset(); 61 | } 62 | 63 | void _display() { 64 | _treeTable.columnInfo(_columnNames, _columnHelp, _columnSizes); 65 | 66 | int programSize = _model.size; 67 | 68 | // A helper function for lazily constructing the tree 69 | LogicalRow buildTree(String id, bool isTop, HtmlElement tbody, int level) { 70 | Map node = _model.elementById(id); 71 | if (node['size'] == null) { 72 | node['size'] = _computeSize(node, _model.elementById); 73 | } 74 | node['size_percent'] = 75 | (100 * node['size'] / programSize).toStringAsFixed(2) + '%'; 76 | 77 | var row = new LogicalRow(node, _renderRow, tbody, level); 78 | _addMetadata(node, row, tbody, level + 1, _model.elementById); 79 | 80 | if (isTop) { 81 | _treeTable.addTopLevel(row); 82 | } 83 | 84 | if (node['children'] != null) { 85 | for (var childId in node['children']) { 86 | // Thunk! Lazy tree creation happens in this closure. 87 | row.addChild(() => buildTree(childId, false, tbody, level + 1)); 88 | } 89 | } 90 | return row; 91 | } 92 | 93 | // Start building the tree from the libraries because 94 | // libraries are always the top level. 95 | for (String libraryId in _model.allOfType('library').map((a) => a['id'])) { 96 | buildTree('$libraryId', true, _treeTable.tbody, 0).show(); 97 | } 98 | } 99 | 100 | /** 101 | * A helper method for adding rows that are not elements but instead provide 102 | * extra information about an element. 103 | */ 104 | void _addMetadata(Map node, LogicalRow row, 105 | HtmlElement tbody, int level, Function fetch) { 106 | 107 | // A helper method for generating a row-generating function. 108 | GenerateRowFunction renderSelfWith(Function renderFn, 109 | {int sortPriority: 0}) { 110 | void render(TreeTableRow row, LogicalRow lRow) { 111 | row.data = renderFn(); 112 | } 113 | return () { 114 | LogicalRow lrow = 115 | new LogicalRow(node, render, row.parentElement, level); 116 | lrow.sortable = false; 117 | lrow.nonSortablePriority = sortPriority; 118 | return lrow; 119 | }; 120 | } 121 | 122 | switch (node['kind']) { 123 | case 'function': 124 | case 'closure': 125 | case 'constructor': 126 | case 'method': 127 | // Side Effects 128 | row.addChild(renderSelfWith(() => [ 129 | _cell('side effects'), 130 | _cell(node['sideEffects'], colspan: '5') 131 | ])); 132 | // Modifiers 133 | if (node.containsKey('modifiers')) { 134 | (node['modifiers'] as Map).forEach((k, v) { 135 | if (v) { 136 | row.addChild(renderSelfWith( 137 | () => [_cell('modifier'), _cell(k, colspan: '5')])); 138 | } 139 | }); 140 | } 141 | // Return type 142 | row.addChild(renderSelfWith(() => [ 143 | _cell('return type'), 144 | _typeCell(node['returnType'], node['inferredReturnType'], 145 | colspan: '5') 146 | ])); 147 | // Parameters 148 | if (node.containsKey('parameters')) { 149 | for (Map param in node['parameters']) { 150 | String declaredType = param['declaredType'] == null 151 | ? "unavailable" 152 | : param['declaredType']; 153 | row.addChild(renderSelfWith(() => [ 154 | _cell('parameter'), 155 | _cell(param['name']), 156 | _typeCell(declaredType, param['type'], colspan: '4') 157 | ])); 158 | } 159 | } 160 | // Code 161 | if (node['code'] != null && node['code'].length != 0) { 162 | row.addChild(renderSelfWith(() => [ 163 | _cell('code'), 164 | _cell(node['code'], colspan: '5', pre: true) 165 | ], sortPriority: -1)); 166 | } 167 | break; 168 | case 'field': 169 | // Code 170 | if (node['code'] != null && node['code'].length != 0) { 171 | row.addChild(renderSelfWith(() => [ 172 | _cell('code'), 173 | _cell(node['code'], colspan: '5', pre: true) 174 | ], sortPriority: -1)); 175 | } 176 | // Types 177 | if (node['inferredType'] != null && node['type'] != null) { 178 | row.addChild(renderSelfWith(() => [ 179 | _cell('type'), 180 | _typeCell(node['type'], node['inferredType'], colspan: '5') 181 | ])); 182 | } 183 | break; 184 | case 'class': 185 | case 'library': 186 | // Show how much of the size we can't account for. 187 | row.addChild(renderSelfWith(() => [ 188 | _cell('scaffolding'), 189 | _cell('(unaccounted for)'), 190 | _cell(node['size'] - _computeSize(node, fetch, force: true), 191 | align: 'right') 192 | ])); 193 | break; 194 | } 195 | } 196 | 197 | void _renderRow(TreeTableRow row, LogicalRow logicalRow) { 198 | Map props = logicalRow.data; 199 | List cells = [_cell(props['kind']),]; 200 | 201 | switch (props['kind']) { 202 | case 'function': 203 | case 'closure': 204 | case 'constructor': 205 | case 'method': 206 | case 'field': 207 | var span = new SpanElement(); 208 | span.text = props['name']; 209 | 210 | var anchor = new AnchorElement(); 211 | anchor.onClick.listen((_) { 212 | HistoryState 213 | .switchTo(new HistoryState('dep', depTarget: props['id'])); 214 | }); 215 | anchor.children.add( 216 | new ImageElement(src: 'packages/dump_viz/src/deps_icon.svg') 217 | ..style.float = 'right'); 218 | 219 | cells.addAll([ 220 | new TableCellElement()..children.addAll([span, anchor]), 221 | _cell(props['size'], align: 'right'), 222 | _cell(_model.triviallyOwnedSize(props['id']), align: 'right'), 223 | _cell(props['size_percent'], align: 'right'), 224 | _cell(props['type'], pre: true) 225 | ]); 226 | break; 227 | case 'library': 228 | cells.addAll([ 229 | _cell(props['canonicalUri']), 230 | _cell(props['size'], align: 'right'), 231 | _cell(''), 232 | _cell(props['size_percent'], align: 'right'), 233 | _cell('') 234 | ]); 235 | break; 236 | case 'typedef': 237 | cells.addAll([ 238 | _cell(props['name']), 239 | _cell('0', align: 'right'), 240 | _cell('0', align: 'right'), 241 | _cell('0.00%', align: 'right') 242 | ]); 243 | break; 244 | case 'class': 245 | cells.addAll([ 246 | _cell(props['name']), 247 | _cell(props['size'], align: 'right'), 248 | _cell(''), 249 | _cell(props['size_percent'], align: 'right'), 250 | _cell(props['name'], pre: true) 251 | ]); 252 | break; 253 | default: 254 | throw new StateError("Unknown element type: ${props['kind']}"); 255 | } 256 | row.data = cells; 257 | } 258 | } 259 | 260 | TableCellElement _typeCell(String declaredType, String inferredType, 261 | {colspan: '1'}) => _verticalCell(new SpanElement() 262 | ..appendText('inferred: ') 263 | ..append(_span(inferredType, cssClass: 'preSpan')), new SpanElement() 264 | ..appendText('declared: ') 265 | ..append(_span(declaredType, cssClass: 'preSpan')), colspan: colspan); 266 | 267 | TableCellElement _verticalCell(dynamic upper, dynamic lower, 268 | {String align: 'left', String colspan: '1'}) { 269 | DivElement div = new DivElement(); 270 | div.children.addAll([ 271 | upper is SpanElement ? upper : _span(upper), 272 | new BRElement(), 273 | lower is SpanElement ? lower : _span(lower) 274 | ]); 275 | return _cell(div, align: align, colspan: colspan, pre: false); 276 | } 277 | 278 | SpanElement _span(dynamic contents, {String cssClass}) { 279 | SpanElement span = new SpanElement(); 280 | if (cssClass != null) span.classes.add(cssClass); 281 | if (contents is Node) { 282 | span.append(contents); 283 | } else { 284 | span.appendText('$contents'); 285 | } 286 | return span; 287 | } 288 | 289 | /** 290 | * A helper method for creating TableCellElements with options 291 | * for alignment, colspan and wrapping the inner text inside of 292 | * a
 element.
293 |  */
294 | TableCellElement _cell(dynamic text,
295 |     {String align: 'left', String colspan: '1', bool pre: false}) {
296 |   TableCellElement element = new TableCellElement()
297 |     ..style.textAlign = align
298 |     ..attributes['colspan'] = colspan;
299 | 
300 |   if (pre) {
301 |     PreElement pre = new PreElement();
302 |     pre.text = text.toString();
303 |     element.append(pre);
304 |   } else if (text is Node) {
305 |     element.children.add(text);
306 |   } else {
307 |     element.text = text.toString();
308 |   }
309 | 
310 |   return element;
311 | }
312 | 
313 | /**
314 |  * Compute the size of a node in the node tree by first checking to see if
315 |  * that node has a size property.  If it does, use that unless [force] is
316 |  * set to true.  Otherwise, aquire the size by summing the sizes of
317 |  * the children.
318 |  */
319 | int _computeSize(Map info, Function fetchElement,
320 |     {bool force: false}) {
321 |   if (info.containsKey('size') && info['size'] != null && !force) {
322 |     return _toInt(info['size']);
323 |   } else if (info.containsKey('children')) {
324 |     return info['children']
325 |         .map(fetchElement)
326 |         .map((a) => _computeSize(a, fetchElement))
327 |         .fold(0, (a, b) => a + b);
328 |   } else {
329 |     return 0;
330 |   }
331 | }
332 | 
333 | int _toInt(dynamic n) {
334 |   if (n is int) {
335 |     return n;
336 |   } else if (n is String) {
337 |     return int.parse(n);
338 |   } else {
339 |     return 0;
340 |   }
341 | }
342 | 


--------------------------------------------------------------------------------