Web Inspector: scrolled Snapshot list is reset to top and drawn blank after switching...
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / HeapAllocationsTimelineView.js
1 /*
2  * Copyright (C) 2016 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WebInspector.HeapAllocationsTimelineView = class HeapAllocationsTimelineView extends WebInspector.TimelineView
27 {
28     constructor(timeline, extraArguments)
29     {
30         super(timeline, extraArguments);
31
32         console.assert(timeline.type === WebInspector.TimelineRecord.Type.HeapAllocations, timeline);
33
34         this.element.classList.add("heap-allocations");
35
36         let columns = {
37             name: {
38                 title: WebInspector.UIString("Name"),
39                 width: "150px",
40                 icon: true,
41             },
42             timestamp: {
43                 title: WebInspector.UIString("Time"),
44                 width: "80px",
45                 sortable: true,
46                 aligned: "right",
47             },
48             size: {
49                 title: WebInspector.UIString("Size"),
50                 width: "80px",
51                 sortable: true,
52                 aligned: "right",
53             },
54             liveSize: {
55                 title: WebInspector.UIString("Live Size"),
56                 width: "80px",
57                 sortable: true,
58                 aligned: "right",
59             },
60         };
61
62         let snapshotTooltip = WebInspector.UIString("Take snapshot");
63         this._takeHeapSnapshotButtonItem = new WebInspector.ButtonNavigationItem("take-snapshot", snapshotTooltip, "Images/Camera.svg", 16, 16);
64         this._takeHeapSnapshotButtonItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._takeHeapSnapshotClicked, this);
65
66         let defaultToolTip = WebInspector.UIString("Compare snapshots");
67         let activatedToolTip = WebInspector.UIString("Cancel comparison");
68         this._compareHeapSnapshotsButtonItem = new WebInspector.ActivateButtonNavigationItem("compare-heap-snapshots", defaultToolTip, activatedToolTip, "Images/Compare.svg", 16, 16);
69         this._compareHeapSnapshotsButtonItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._compareHeapSnapshotsClicked, this);
70         this._compareHeapSnapshotsButtonItem.enabled = false;
71         this._compareHeapSnapshotsButtonItem.activated = false;
72
73         this._compareHeapSnapshotHelpTextItem = new WebInspector.TextNavigationItem("compare-heap-snapshot-help-text", "");
74
75         this._selectingComparisonHeapSnapshots = false;
76         this._baselineDataGridNode = null;
77         this._baselineHeapSnapshotTimelineRecord = null;
78         this._heapSnapshotDiff = null;
79
80         this._snapshotListScrollTop = 0;
81         this._showingSnapshotList = true;
82
83         this._snapshotListPathComponent = new WebInspector.HierarchicalPathComponent(WebInspector.UIString("Snapshot List"), "snapshot-list-icon", "snapshot-list", false, false);
84         this._snapshotListPathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.Clicked, this._snapshotListPathComponentClicked, this);
85
86         this._dataGrid = new WebInspector.TimelineDataGrid(columns);
87         this._dataGrid.sortColumnIdentifier = "timestamp";
88         this._dataGrid.sortOrder = WebInspector.DataGrid.SortOrder.Ascending;
89         this._dataGrid.createSettings("heap-allocations-timeline-view");
90         this._dataGrid.addEventListener(WebInspector.DataGrid.Event.SelectedNodeChanged, this._dataGridNodeSelected, this);
91
92         this.addSubview(this._dataGrid);
93
94         this._contentViewContainer = new WebInspector.ContentViewContainer;
95         this._contentViewContainer.addEventListener(WebInspector.ContentViewContainer.Event.CurrentContentViewDidChange, this._currentContentViewDidChange, this);
96         WebInspector.ContentView.addEventListener(WebInspector.ContentView.Event.SelectionPathComponentsDidChange, this._contentViewSelectionPathComponentDidChange, this);
97
98         this._pendingRecords = [];
99         this._pendingZeroTimeDataGridNodes = [];
100
101         timeline.addEventListener(WebInspector.Timeline.Event.RecordAdded, this._heapAllocationsTimelineRecordAdded, this);
102
103         WebInspector.HeapSnapshotProxy.addEventListener(WebInspector.HeapSnapshotProxy.Event.Invalidated, this._heapSnapshotInvalidated, this);
104         WebInspector.HeapSnapshotWorkerProxy.singleton().addEventListener("HeapSnapshot.CollectionEvent", this._heapSnapshotCollectionEvent, this);
105     }
106
107     // Public
108
109     get scrollableElements()
110     {
111         if (this._showingSnapshotList)
112             return [this._dataGrid.scrollContainer];
113         if (this._contentViewContainer.currentContentView)
114             return this._contentViewContainer.currentContentView.scrollableElements;
115         return [];
116     }
117
118     showHeapSnapshotList()
119     {
120         if (this._showingSnapshotList)
121             return;
122
123         this._showingSnapshotList = true;
124         this._heapSnapshotDiff = null;
125         this._cancelSelectComparisonHeapSnapshots();
126
127         this._contentViewContainer.hidden();
128         this.removeSubview(this._contentViewContainer);
129         this.addSubview(this._dataGrid);
130
131         this._dataGrid.scrollContainer.scrollTop = this._snapshotListScrollTop;
132
133         this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
134         this.dispatchEventToListeners(WebInspector.ContentView.Event.NavigationItemsDidChange);
135     }
136
137     showHeapSnapshotTimelineRecord(heapSnapshotTimelineRecord)
138     {
139         if (this._showingSnapshotList) {
140             this._snapshotListScrollTop = this._dataGrid.scrollContainer.scrollTop;
141             this.removeSubview(this._dataGrid);
142             this.addSubview(this._contentViewContainer);
143             this._contentViewContainer.shown();
144         }
145
146         this._showingSnapshotList = false;
147         this._heapSnapshotDiff = null;
148         this._cancelSelectComparisonHeapSnapshots();
149
150         for (let dataGridNode of this._dataGrid.children) {
151             if (dataGridNode.record === heapSnapshotTimelineRecord) {
152                 dataGridNode.select();
153                 break;
154             }
155         }
156
157         let shouldTriggerContentViewUpdate = this._contentViewContainer.currentContentView && this._contentViewContainer.currentContentView.representedObject === heapSnapshotTimelineRecord.heapSnapshot;
158
159         this._contentViewContainer.showContentViewForRepresentedObject(heapSnapshotTimelineRecord.heapSnapshot);
160
161         if (shouldTriggerContentViewUpdate)
162             this._currentContentViewDidChange();
163     }
164
165     showHeapSnapshotDiff(heapSnapshotDiff)
166     {
167         if (this._showingSnapshotList) {
168             this.removeSubview(this._dataGrid);
169             this.addSubview(this._contentViewContainer);
170             this._contentViewContainer.shown();
171         }
172
173         this._showingSnapshotList = false;
174         this._heapSnapshotDiff = heapSnapshotDiff;
175
176         this._contentViewContainer.showContentViewForRepresentedObject(heapSnapshotDiff);
177     }
178
179     // Protected
180
181     // FIXME: <https://webkit.org/b/157582> Web Inspector: Heap Snapshot Views should be searchable
182     get showsFilterBar() { return this._showingSnapshotList; }
183
184     get navigationItems()
185     {
186         if (this._showingSnapshotList) {
187             let items = [this._takeHeapSnapshotButtonItem, this._compareHeapSnapshotsButtonItem];
188             if (this._selectingComparisonHeapSnapshots)
189                 items.push(this._compareHeapSnapshotHelpTextItem);
190             return items;
191         }
192
193         return this._contentViewContainer.currentContentView.navigationItems;
194     }
195
196     get selectionPathComponents()
197     {
198         let components = [this._snapshotListPathComponent];
199
200         if (this._showingSnapshotList)
201             return components;
202
203         if (this._heapSnapshotDiff) {
204             let firstSnapshotIdentifier = this._heapSnapshotDiff.snapshot1.identifier;
205             let secondSnapshotIdentifier = this._heapSnapshotDiff.snapshot2.identifier;
206             let diffComponent = new WebInspector.HierarchicalPathComponent(WebInspector.UIString("Snapshot Comparison (%d and %d)").format(firstSnapshotIdentifier, secondSnapshotIdentifier), "snapshot-diff-icon", "snapshot-diff");
207             components.push(diffComponent);
208         } else if (this._dataGrid.selectedNode) {
209             let heapSnapshotPathComponent = new WebInspector.HeapAllocationsTimelineDataGridNodePathComponent(this._dataGrid.selectedNode);
210             heapSnapshotPathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this._snapshotPathComponentSelected, this);
211             components.push(heapSnapshotPathComponent);
212         }
213
214         return components.concat(this._contentViewContainer.currentContentView.selectionPathComponents);
215     }
216
217     selectRecord(record)
218     {
219         if (record)
220             this.showHeapSnapshotTimelineRecord(record);
221         else
222             this.showHeapSnapshotList();
223     }
224
225     shown()
226     {
227         super.shown();
228
229         this._dataGrid.shown();
230
231         if (!this._showingSnapshotList)
232             this._contentViewContainer.shown();
233     }
234
235     hidden()
236     {
237         super.hidden();
238
239         this._dataGrid.hidden();
240
241         if (!this._showingSnapshotList)
242             this._contentViewContainer.hidden();
243     }
244
245     closed()
246     {
247         console.assert(this.representedObject instanceof WebInspector.Timeline);
248         this.representedObject.removeEventListener(null, null, this);
249
250         this._dataGrid.closed();
251
252         this._contentViewContainer.closeAllContentViews();
253
254         WebInspector.ContentView.removeEventListener(null, null, this);
255         WebInspector.HeapSnapshotProxy.removeEventListener(null, null, this);
256         WebInspector.HeapSnapshotWorkerProxy.singleton().removeEventListener("HeapSnapshot.CollectionEvent", this._heapSnapshotCollectionEvent, this);
257     }
258
259     layout()
260     {
261         if (this._pendingZeroTimeDataGridNodes.length && this.zeroTime) {
262             for (let dataGridNode of this._pendingZeroTimeDataGridNodes)
263                 dataGridNode.updateTimestamp(this.zeroTime);
264             this._pendingZeroTimeDataGridNodes = [];
265             this._dataGrid._sort();
266         }
267
268         if (this._pendingRecords.length) {
269             for (let heapAllocationsTimelineRecord of this._pendingRecords) {
270                 let dataGridNode = new WebInspector.HeapAllocationsTimelineDataGridNode(heapAllocationsTimelineRecord, this.zeroTime, this);
271                 this._dataGrid.addRowInSortOrder(null, dataGridNode);
272                 if (!this.zeroTime)
273                     this._pendingZeroTimeDataGridNodes.push(dataGridNode);
274             }
275
276             this._pendingRecords = [];
277             this._updateCompareHeapSnapshotButton();
278         }
279     }
280
281     reset()
282     {
283         super.reset();
284
285         this._dataGrid.reset();
286
287         this.showHeapSnapshotList();
288         this._pendingRecords = [];
289         this._pendingZeroTimeDataGridNodes = [];
290         this._updateCompareHeapSnapshotButton();
291     }
292
293     updateFilter(filters)
294     {
295         this._dataGrid.filterText = filters ? filters.text : "";
296     }
297
298     // Private
299
300     _heapAllocationsTimelineRecordAdded(event)
301     {
302         this._pendingRecords.push(event.data.record);
303
304         this.needsLayout();
305     }
306
307     _heapSnapshotCollectionEvent(event)
308     {
309         function updateHeapSnapshotForEvent(heapSnapshot) {
310             if (heapSnapshot.invalid)
311                 return;
312             heapSnapshot.updateForCollectionEvent(event);
313         }
314
315         for (let node of this._dataGrid.children)
316             updateHeapSnapshotForEvent(node.record.heapSnapshot);
317         for (let record of this._pendingRecords)
318             updateHeapSnapshotForEvent(record.heapSnapshot);
319         if (this._heapSnapshotDiff)
320             updateHeapSnapshotForEvent(this._heapSnapshotDiff);
321     }
322
323     _snapshotListPathComponentClicked(event)
324     {
325         this.showHeapSnapshotList();
326     }
327
328     _snapshotPathComponentSelected(event)
329     {
330         console.assert(event.data.pathComponent.representedObject instanceof WebInspector.HeapAllocationsTimelineRecord);
331         this.showHeapSnapshotTimelineRecord(event.data.pathComponent.representedObject);
332     }
333
334     _currentContentViewDidChange(event)
335     {
336         this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
337         this.dispatchEventToListeners(WebInspector.ContentView.Event.NavigationItemsDidChange);
338     }
339
340     _contentViewSelectionPathComponentDidChange(event)
341     {
342         if (event.target !== this._contentViewContainer.currentContentView)
343             return;
344         this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
345     }
346
347     _heapSnapshotInvalidated(event)
348     {
349         let heapSnapshot = event.target;
350
351         if (this._baselineHeapSnapshotTimelineRecord) {
352             if (heapSnapshot === this._baselineHeapSnapshotTimelineRecord.heapSnapshot)
353                 this._cancelSelectComparisonHeapSnapshots();
354         }
355
356         if (this._heapSnapshotDiff) {
357             if (heapSnapshot === this._heapSnapshotDiff.snapshot1 || heapSnapshot === this._heapSnapshotDiff.snapshot2)
358                 this.showHeapSnapshotList();
359         } else if (this._dataGrid.selectedNode) {
360             if (heapSnapshot === this._dataGrid.selectedNode.record.heapSnapshot)
361                 this.showHeapSnapshotList();
362         }
363
364         this._updateCompareHeapSnapshotButton();
365     }
366
367     _updateCompareHeapSnapshotButton()
368     {
369         let hasAtLeastTwoValidSnapshots = false;
370
371         let count = 0;
372         for (let node of this._dataGrid.children) {
373             if (node.revealed && !node.hidden && !node.record.heapSnapshot.invalid) {
374                 count++;
375                 if (count === 2) {
376                     hasAtLeastTwoValidSnapshots = true;
377                     break;
378                 }
379             }
380         }
381
382         this._compareHeapSnapshotsButtonItem.enabled = hasAtLeastTwoValidSnapshots;
383     }
384
385     _takeHeapSnapshotClicked()
386     {
387         HeapAgent.snapshot(function(error, timestamp, snapshotStringData) {
388             let workerProxy = WebInspector.HeapSnapshotWorkerProxy.singleton();
389             workerProxy.createSnapshot(snapshotStringData, ({objectId, snapshot: serializedSnapshot}) => {
390                 let snapshot = WebInspector.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
391                 WebInspector.timelineManager.heapSnapshotAdded(timestamp, snapshot);
392             });
393         });
394     }
395
396     _cancelSelectComparisonHeapSnapshots()
397     {
398         if (!this._selectingComparisonHeapSnapshots)
399             return;
400
401         if (this._baselineDataGridNode)
402             this._baselineDataGridNode.clearBaseline();
403
404         this._selectingComparisonHeapSnapshots = false;
405         this._baselineDataGridNode = null;
406         this._baselineHeapSnapshotTimelineRecord = null;
407
408         this._compareHeapSnapshotsButtonItem.activated = false;
409
410         this.dispatchEventToListeners(WebInspector.ContentView.Event.NavigationItemsDidChange);
411     }
412
413     _compareHeapSnapshotsClicked(event)
414     {
415         let newActivated = !this._compareHeapSnapshotsButtonItem.activated;
416         this._compareHeapSnapshotsButtonItem.activated = newActivated;
417
418         if (!newActivated) {
419             this._cancelSelectComparisonHeapSnapshots();
420             return;
421         }
422
423         if (this._dataGrid.selectedNode)
424             this._dataGrid.selectedNode.deselect();
425
426         this._selectingComparisonHeapSnapshots = true;
427         this._baselineHeapSnapshotTimelineRecord = null;
428         this._compareHeapSnapshotHelpTextItem.text = WebInspector.UIString("Select baseline snapshot");
429
430         this.dispatchEventToListeners(WebInspector.ContentView.Event.NavigationItemsDidChange);
431     }
432
433     _dataGridNodeSelected(event)
434     {
435         if (!this._selectingComparisonHeapSnapshots)
436             return;
437
438         let dataGridNode = this._dataGrid.selectedNode;
439         if (!dataGridNode)
440             return;
441
442         let heapAllocationsTimelineRecord = dataGridNode.record;
443
444         // Cancel the selection if the heap snapshot is invalid, or was already selected as the baseline.
445         if (heapAllocationsTimelineRecord.heapSnapshot.invalid || this._baselineHeapSnapshotTimelineRecord === heapAllocationsTimelineRecord) {
446             this._dataGrid.selectedNode.deselect();
447             return;
448         }
449
450         // Selected Baseline.
451         if (!this._baselineHeapSnapshotTimelineRecord) {
452             this._baselineDataGridNode = dataGridNode;
453             this._baselineDataGridNode.markAsBaseline();
454             this._baselineHeapSnapshotTimelineRecord = heapAllocationsTimelineRecord;
455             this._dataGrid.selectedNode.deselect();
456             this._compareHeapSnapshotHelpTextItem.text = WebInspector.UIString("Select comparison snapshot");
457             this.dispatchEventToListeners(WebInspector.ContentView.Event.NavigationItemsDidChange);
458             return;
459         }
460
461         // Selected Comparison.
462         let snapshot1 = this._baselineHeapSnapshotTimelineRecord.heapSnapshot;
463         let snapshot2 = heapAllocationsTimelineRecord.heapSnapshot;
464         if (snapshot1.identifier > snapshot2.identifier)
465             [snapshot1, snapshot2] = [snapshot2, snapshot1];
466
467         let workerProxy = WebInspector.HeapSnapshotWorkerProxy.singleton();
468         workerProxy.createSnapshotDiff(snapshot1.proxyObjectId, snapshot2.proxyObjectId, ({objectId, snapshotDiff: serializedSnapshotDiff}) => {
469             let diff = WebInspector.HeapSnapshotDiffProxy.deserialize(objectId, serializedSnapshotDiff);
470             this.showHeapSnapshotDiff(diff);
471         });
472
473         this._baselineDataGridNode.clearBaseline();
474         this._baselineDataGridNode = null;
475         this._selectingComparisonHeapSnapshots = false;
476         this._compareHeapSnapshotsButtonItem.activated = false;
477     }
478 };