Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(502)

Side by Side Diff: runtime/observatory/lib/src/app/view_model.dart

Issue 1833453004: Dramatically increase the performance of Observatory's profile UI (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Created 4 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 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 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 part of app; 5 part of app;
6 6
7 abstract class VirtualTreeRow {
8 // Number of ems each subtree is indented.
9 static const subtreeIndent = 2;
10
11 static const redColor = '#F44336';
12 static const blueColor = '#3F51B5';
13 static const purpleColor = '#673AB7';
14 static const greenColor = '#4CAF50';
15 static const orangeColor = '#FF9800';
16 static const lightGrayColor = '#FAFAFA';
17
18 List backgroundColors = const [
19 purpleColor,
20 redColor,
21 greenColor,
22 blueColor,
23 orangeColor,
24 ];
25
26 final VirtualTree tree;
27 final List<VirtualTreeRow> children = [];
28 final List<StreamSubscription> _listeners = [];
29 final int depth;
30 bool _expanded = false;
31
32 VirtualTreeRow(this.tree, this.depth);
33
34 bool get expanded => _expanded;
35
36 set expanded(bool expanded) {
37 var changed = _expanded != expanded;
38 _expanded = expanded;
39 if (!changed) {
40 return;
41 }
42 if (_expanded) {
43 _expand();
44 } else {
45 _collapse();
46 }
47 }
48
49 Element makeColorBar() {
50 var element = new SpanElement();
51 element.style.paddingLeft = '2px';
52 element.style.paddingRight = '2px';
53 var flexBasis = '2px';
54 element.style.flexBasis = flexBasis;
rmacnak 2016/03/24 23:20:36 Odd for this one to have a variable when the rest
Cutch 2016/03/25 03:04:08 Done here and elsewhere.
55 element.style.height = '${tree.rowHeight}px';
56 element.style.minHeight = '${tree.rowHeight}px';
57 if (depth > 0) {
58 var colorIndex = (depth - 1) % backgroundColors.length;
59 element.style.backgroundColor = backgroundColors[colorIndex];
60 }
61 return element;
62 }
63
64 Element makeExpander() {
65 SpanElement element = new SpanElement();
66 var flexBasis = '2em';
67 element.style.flexBasis = flexBasis;
68 if (!hasChildren()) {
69 element.style.visibility = 'hidden';
70 } else {
71 element.style.visibility = 'visible';
72 element.children.add(expanded ?
73 new Element.tag('icon-expand-more') :
74 new Element.tag('icon-chevron-right'));
75 }
76 _listeners.add(element.onClick.listen((e) {
77 e.stopPropagation();
78 toggle();
79 }));
80 return element;
81 }
82
83 Element makeIndenter() {
84 SpanElement element = new SpanElement();
85 var flexBasis = '${subtreeIndent * depth}em';
86 element.style.flexBasis = flexBasis;
87 return element;
88 }
89
90 Element makeText(String text, {String toolTip, String flexBasis: '7em'}) {
91 SpanElement element = new SpanElement();
92 element.text = text;
93 if (toolTip != null) {
94 element.title = toolTip;
95 }
96 if (flexBasis != null) {
97 element.style.flexBasis = flexBasis;
98 }
99 return element;
100 }
101
102 Element makeGap([int ems = 1]) {
103 SpanElement element = new SpanElement();
104 var flexBasis = '${ems}em';
105 element.style.flexBasis = flexBasis;
106 return element;
107 }
108
109 void _cleanupListeners() {
110 for (var listener in _listeners) {
111 listener.cancel();
112 }
113 _listeners.clear();
114 }
115
116 void _expand() {
117 tree._onExpand(this);
118 }
119
120 void _collapse() {
121 if (children.length == 0) {
122 // Nothing to do.
123 return;
124 }
125 for (var i = 0; i < children.length; i++) {
126 if (children[i].expanded) {
127 children[i]._collapse();
128 }
129 }
130 _expanded = false;
131 tree._onCollapse(this);
132 }
133
134 void toggle() {
135 expanded = !expanded;
136 }
137
138 void _render(DivElement rowDiv) {
139 rowDiv.style.display = 'flex';
140 rowDiv.style.alignItems = 'center';
141 _cleanupListeners();
142 onShow();
143 onRender(rowDiv);
144 }
145
146 /// Called when you should render into [rowDiv].
147 void onRender(DivElement rowDiv);
148
149 // Called when this row is visible.
150 void onShow();
151
152 // Return true if this node can be expanded.
153 bool hasChildren() {
154 return false;
155 }
156
157 // Called when this row is not visible.
158 void onHide() {
159 _cleanupListeners();
160 }
161 }
162
163 class VirtualTree {
164 final int rowHeight;
165 final List<VirtualTreeRow> rows = [];
166 final DivElement root;
167 final Stopwatch _clock = new Stopwatch();
168
169 DivElement _treeHeightElement;
170 DivElement _tree;
171
172 StreamSubscription _scrollSubscription;
173 StreamSubscription _resizeSubscription;
174 Timer _sweeperTimer;
175
176 // Height of [root] in pixels.
177 int viewHeight;
178
179 // Number of pixels view can be scrolled before a redraw occurs.
180 int redrawThresholdPixels;
181
182 // Number of rows visible at any given time.
183 int numVisibleRows;
184 // Number of rows above the current view that are in the dom.
185 int extraRowsAbove;
186 // Number of rows below the current view that are in the dom.
187 int extraRowsBelow;
188
189 // The time of the last scroll event.
190 int lastScrollTimeMilliseconds;
191
192 // The scroll top of the last scroll event.
193 int lastPaintScrollTop;
194
195 // The starting row of the last paint.
196 int lastPaintStartingRow;
197
198 static const scrollStopThresholdMilliseconds = 100;
199
200 VirtualTree(this.rowHeight, this.root) {
201 _clock.start();
202 _install();
203 _resize();
204 _paint(0);
205 }
206
207 void uninstall() => _uninstall();
208
209 void refresh() {
210 _resize();
211 _paint(lastPaintStartingRow);
212 }
213
214 // Clear the tree.
215 void clear() {
216 rows.clear();
217 _resize();
218 }
219
220 void _onExpand(VirtualTreeRow parent) {
221 int index = rows.indexOf(parent);
222 if (index == -1) {
223 return;
224 }
225 rows.insertAll(index + 1, parent.children);
226 refresh();
227 }
228
229 void _onCollapse(VirtualTreeRow parent) {
230 int index = rows.indexOf(parent);
231 if (index == -1) {
232 return;
233 }
234 int start = index + 1;
235 int end = start + parent.children.length;
236 rows.removeRange(start, end);
237 refresh();
238 }
239
240 void _resize() {
241 viewHeight = root.offsetHeight;
242 numVisibleRows = (viewHeight ~/ rowHeight) + 1;
243 extraRowsAbove = numVisibleRows ~/ 2;
244 extraRowsBelow = numVisibleRows - extraRowsAbove;
245 redrawThresholdPixels =
246 math.min(extraRowsAbove, extraRowsBelow) * rowHeight;
247 _treeHeightElement.style.height = '${_treeHeight()}px';
248 }
249
250 int _treeHeight() {
251 return rows.length * rowHeight;
252 }
253
254 int _now() => _clock.elapsedMilliseconds;
255
256 int _millisecondsSinceLastScroll() {
257 int now = _now();
258 if (lastScrollTimeMilliseconds == null) {
259 return now;
260 }
261 return now - lastScrollTimeMilliseconds;
262 }
263
264 int _pixelsFromLastScroll(int currentScrollTop) {
265 if (lastPaintScrollTop == null) {
266 return currentScrollTop;
267 }
268
269 return (currentScrollTop - lastPaintScrollTop).abs();
270 }
271
272 int _pixelToRow(int pixelY) {
273 int result = pixelY ~/ rowHeight;
274 return result;
275 }
276
277 void _install() {
278 // This element controls the height of the tree's scrollable region.
279 // It is one pixel wide and the height is set to rowHeight * numRows.
280 _treeHeightElement = new DivElement();
281 _treeHeightElement.style.position = 'absolute';
282 _treeHeightElement.style.top = '0';
283 _treeHeightElement.style.left = '0';
284 _treeHeightElement.style.width = '1px';
285
286 // This element holds the visible tree rows and the height controlling
287 // element. It takes the full width and height of its parent element.
288 _tree = new DivElement();
289 _tree.children.add(_treeHeightElement);
290 _tree.style.width = '100%';
291 _tree.style.height = '100%';
292 _tree.style.position = 'relative';
293 _tree.style.overflow = 'auto';
294
295 // Listen for scroll events on the tree.
296 _scrollSubscription = _tree.onScroll.listen(_onScroll);
297
298 root.children.add(_tree);
299
300 // Listen for resize events.
301 _resizeSubscription = window.onResize.listen((_) {
302 _resize();
303 int row =
304 lastPaintStartingRow != null ? lastPaintStartingRow : 0;
305 _paint(row);
306 });
307
308 // Regularly sweep non-visible rows.
309 _sweeperTimer =
310 new Timer.periodic(const Duration(milliseconds: 300), _sweepRows);
311 }
312
313 void _uninstall() {
314 root.children.clear();
315 _scrollSubscription?.cancel();
316 _scrollSubscription = null;
317 _resizeSubscription?.cancel();
318 _resizeSubscription = null;
319 _sweeperTimer?.cancel();
320 _sweeperTimer = null;
321 }
322
323 void _onScroll(Event scrollEvent) {
324 Element target = scrollEvent.target;
325
326 int scrollTop = target.scrollTop;
327
328 if (_pixelsFromLastScroll(scrollTop) > redrawThresholdPixels) {
329 int startingRow = math.max(_pixelToRow(scrollTop), 0);
330 _paint(startingRow);
331 lastPaintScrollTop = scrollTop;
332 }
333 lastScrollTimeMilliseconds = _now();
334 scrollEvent.preventDefault();
335 }
336
337 void _sweepRows(Timer timer) {
338 // It hasn't been long enough since the last scroll.
339 if (_millisecondsSinceLastScroll() <= scrollStopThresholdMilliseconds) {
340 return;
341 }
342 // Find all rows marked with the garbage class.
343 var garbageRows = _tree.querySelectorAll('.garbage');
344 // Remove them from the tree.
345 for (var row in garbageRows) {
346 _tree.children.remove(row);
347 }
348 }
349
350 void _paint(int startingRow) {
351 lastPaintStartingRow = startingRow;
352
353 // Hide all existing rows and mark them for collection. The first child
354 // is the height control element, so we skip that.
355 for (int i = 1; i < _tree.children.length; i++) {
356 _tree.children[i].style.display = 'none';
357 _tree.children[i].classes.add('garbage');
358 }
359
360 int endingRow =
361 math.min(rows.length, startingRow + numVisibleRows + extraRowsBelow);
362
363 startingRow =
364 math.max(0, startingRow - extraRowsAbove);
365
366 print('PAINT $startingRow $endingRow');
367
368 // Create visible rows and insert them into a document fragment.
369 DocumentFragment fragment = new DocumentFragment();
370 for (int i = startingRow; i < endingRow; i++) {
371 DivElement row = new DivElement();
372 row.style.position = 'absolute';
373 row.style.height = '${rowHeight}px';
374 row.style.maxHeight = '${rowHeight}px';
375 row.style.margin = '0';
376 row.style.width = '100%';
377 row.style.top = '${(i * rowHeight)}px';
378 // Render the row.
379 rows[i]._render(row);
380 fragment.children.add(row);
381 }
382 // Append the fragment to the DOM.
383 _tree.append(fragment);
384 }
385 }
386
7 abstract class TableTreeRow extends Observable { 387 abstract class TableTreeRow extends Observable {
8 static const arrowRight = '\u2192'; 388 static const arrowRight = '\u2192';
9 static const arrowDownRight = '\u21b3'; 389 static const arrowDownRight = '\u21b3';
10 // Number of ems each subtree is indented. 390 // Number of ems each subtree is indented.
11 static const subtreeIndent = 2; 391 static const subtreeIndent = 2;
12 392
13 TableTreeRow(this.tree, TableTreeRow parent) : 393 TableTreeRow(this.tree, TableTreeRow parent) :
14 parent = parent, 394 parent = parent,
15 depth = parent != null ? parent.depth + 1 : 0 { 395 depth = parent != null ? parent.depth + 1 : 0 {
16 } 396 }
(...skipping 346 matching lines...) Expand 10 before | Expand all | Expand 10 after
363 if (column != _sortColumnIndex) { 743 if (column != _sortColumnIndex) {
364 return columns[column].label + '\u2003'; 744 return columns[column].label + '\u2003';
365 } 745 }
366 return columns[column].label + (_sortDescending ? arrowUp : arrowDown); 746 return columns[column].label + (_sortDescending ? arrowUp : arrowDown);
367 } 747 }
368 748
369 dynamic getValue(int row, int column) { 749 dynamic getValue(int row, int column) {
370 return rows[row].values[column]; 750 return rows[row].values[column];
371 } 751 }
372 } 752 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698