7c49d54568442f3075914861809ce3e4d126b572
[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._showingSnapshotList = true;
81
82         this._snapshotListPathComponent = new WebInspector.HierarchicalPathComponent(WebInspector.UIString("Snapshot List"), "snapshot-list-icon", "snapshot-list", false, false);
83         this._snapshotListPathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.Clicked, this._snapshotListPathComponentClicked, this);
84
85         this._dataGrid = new WebInspector.TimelineDataGrid(columns);
86         this._dataGrid.sortColumnIdentifier = "timestamp";
87         this._dataGrid.sortOrder = WebInspector.DataGrid.SortOrder.Ascending;
88         this._dataGrid.createSettings("heap-allocations-timeline-view");
89         this._dataGrid.addEventListener(WebInspector.DataGrid.Event.SelectedNodeChanged, this._dataGridNodeSelected, this);
90
91         this.addSubview(this._dataGrid);
92
93         this._contentViewContainer = new WebInspector.ContentViewContainer;
94         this._contentViewContainer.addEventListener(WebInspector.ContentViewContainer.Event.CurrentContentViewDidChange, this._currentContentViewDidChange, this);
95         WebInspector.ContentView.addEventListener(WebInspector.ContentView.Event.SelectionPathComponentsDidChange, this._contentViewSelectionPathComponentDidChange, this);
96
97         this._pendingRecords = [];
98         this._pendingZeroTimeDataGridNodes = [];
99
100         timeline.addEventListener(WebInspector.Timeline.Event.RecordAdded, this._heapAllocationsTimelineRecordAdded, this);
101
102         WebInspector.HeapSnapshotProxy.addEventListener(WebInspector.HeapSnapshotProxy.Event.Invalidated, this._heapSnapshotInvalidated, this);
103         WebInspector.HeapSnapshotWorkerProxy.singleton().addEventListener("HeapSnapshot.CollectionEvent", this._heapSnapshotCollectionEvent, this);
104     }
105
106     // Public
107
108     showHeapSnapshotList()
109     {
110         if (this._showingSnapshotList)
111             return;
112
113         this._showingSnapshotList = true;
114         this._heapSnapshotDiff = null;
115         this._cancelSelectComparisonHeapSnapshots();
116
117         this._contentViewContainer.hidden();
118         this.removeSubview(this._contentViewContainer);
119         this.addSubview(this._dataGrid);
120
121         this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
122         this.dispatchEventToListeners(WebInspector.ContentView.Event.NavigationItemsDidChange);
123     }
124
125     showHeapSnapshotTimelineRecord(heapSnapshotTimelineRecord)
126     {
127         if (this._showingSnapshotList) {
128             this.removeSubview(this._dataGrid);
129             this.addSubview(this._contentViewContainer);
130             this._contentViewContainer.shown();
131         }
132
133         this._showingSnapshotList = false;
134         this._heapSnapshotDiff = null;
135         this._cancelSelectComparisonHeapSnapshots();
136
137         for (let dataGridNode of this._dataGrid.children) {
138             if (dataGridNode.record === heapSnapshotTimelineRecord) {
139                 dataGridNode.select();
140                 break;
141             }
142         }
143
144         let shouldTriggerContentViewUpdate = this._contentViewContainer.currentContentView && this._contentViewContainer.currentContentView.representedObject === heapSnapshotTimelineRecord.heapSnapshot;
145
146         this._contentViewContainer.showContentViewForRepresentedObject(heapSnapshotTimelineRecord.heapSnapshot);
147
148         if (shouldTriggerContentViewUpdate)
149             this._currentContentViewDidChange();
150     }
151
152     showHeapSnapshotDiff(heapSnapshotDiff)
153     {
154         if (this._showingSnapshotList) {
155             this.removeSubview(this._dataGrid);
156             this.addSubview(this._contentViewContainer);
157             this._contentViewContainer.shown();
158         }
159
160         this._showingSnapshotList = false;
161         this._heapSnapshotDiff = heapSnapshotDiff;
162
163         this._contentViewContainer.showContentViewForRepresentedObject(heapSnapshotDiff);
164     }
165
166     // Protected
167
168     // FIXME: <https://webkit.org/b/157582> Web Inspector: Heap Snapshot Views should be searchable
169     get showsFilterBar() { return this._showingSnapshotList; }
170
171     get navigationItems()
172     {
173         if (this._showingSnapshotList) {
174             let items = [this._takeHeapSnapshotButtonItem, this._compareHeapSnapshotsButtonItem];
175             if (this._selectingComparisonHeapSnapshots)
176                 items.push(this._compareHeapSnapshotHelpTextItem);
177             return items;
178         }
179
180         return this._contentViewContainer.currentContentView.navigationItems;
181     }
182
183     get selectionPathComponents()
184     {
185         let components = [this._snapshotListPathComponent];
186
187         if (this._showingSnapshotList)
188             return components;
189
190         if (this._heapSnapshotDiff) {
191             let firstSnapshotIdentifier = this._heapSnapshotDiff.snapshot1.identifier;
192             let secondSnapshotIdentifier = this._heapSnapshotDiff.snapshot2.identifier;
193             let diffComponent = new WebInspector.HierarchicalPathComponent(WebInspector.UIString("Snapshot Comparison (%d and %d)").format(firstSnapshotIdentifier, secondSnapshotIdentifier), "snapshot-diff-icon", "snapshot-diff");
194             components.push(diffComponent);
195         } else if (this._dataGrid.selectedNode) {
196             let heapSnapshotPathComponent = new WebInspector.HeapAllocationsTimelineDataGridNodePathComponent(this._dataGrid.selectedNode);
197             heapSnapshotPathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this._snapshotPathComponentSelected, this);
198             components.push(heapSnapshotPathComponent);
199         }
200
201         return components.concat(this._contentViewContainer.currentContentView.selectionPathComponents);
202     }
203
204     selectRecord(record)
205     {
206         if (record)
207             this.showHeapSnapshotTimelineRecord(record);
208         else
209             this.showHeapSnapshotList();
210     }
211
212     shown()
213     {
214         super.shown();
215
216         this._dataGrid.shown();
217
218         if (!this._showingSnapshotList)
219             this._contentViewContainer.shown();
220     }
221
222     hidden()
223     {
224         super.hidden();
225
226         this._dataGrid.hidden();
227
228         if (!this._showingSnapshotList)
229             this._contentViewContainer.hidden();
230     }
231
232     closed()
233     {
234         console.assert(this.representedObject instanceof WebInspector.Timeline);
235         this.representedObject.removeEventListener(null, null, this);
236
237         this._dataGrid.closed();
238
239         this._contentViewContainer.closeAllContentViews();
240
241         WebInspector.ContentView.removeEventListener(null, null, this);
242         WebInspector.HeapSnapshotProxy.removeEventListener(null, null, this);
243         WebInspector.HeapSnapshotWorkerProxy.singleton().removeEventListener("HeapSnapshot.CollectionEvent", this._heapSnapshotCollectionEvent, this);
244     }
245
246     layout()
247     {
248         if (this._pendingZeroTimeDataGridNodes.length && this.zeroTime) {
249             for (let dataGridNode of this._pendingZeroTimeDataGridNodes)
250                 dataGridNode.updateTimestamp(this.zeroTime);
251             this._pendingZeroTimeDataGridNodes = [];
252             this._dataGrid._sort();
253         }
254
255         if (this._pendingRecords.length) {
256             for (let heapAllocationsTimelineRecord of this._pendingRecords) {
257                 let dataGridNode = new WebInspector.HeapAllocationsTimelineDataGridNode(heapAllocationsTimelineRecord, this.zeroTime, this);
258                 this._dataGrid.addRowInSortOrder(null, dataGridNode);
259                 if (!this.zeroTime)
260                     this._pendingZeroTimeDataGridNodes.push(dataGridNode);
261             }
262
263             this._pendingRecords = [];
264             this._updateCompareHeapSnapshotButton();
265         }
266     }
267
268     reset()
269     {
270         super.reset();
271
272         this._dataGrid.reset();
273
274         this.showHeapSnapshotList();
275         this._pendingRecords = [];
276         this._pendingZeroTimeDataGridNodes = [];
277         this._updateCompareHeapSnapshotButton();
278     }
279
280     updateFilter(filters)
281     {
282         this._dataGrid.filterText = filters ? filters.text : "";
283     }
284
285     // Private
286
287     _heapAllocationsTimelineRecordAdded(event)
288     {
289         this._pendingRecords.push(event.data.record);
290
291         this.needsLayout();
292     }
293
294     _heapSnapshotCollectionEvent(event)
295     {
296         function updateHeapSnapshotForEvent(heapSnapshot) {
297             if (heapSnapshot.invalid)
298                 return;
299             heapSnapshot.updateForCollectionEvent(event);
300         }
301
302         for (let node of this._dataGrid.children)
303             updateHeapSnapshotForEvent(node.record.heapSnapshot);
304         for (let record of this._pendingRecords)
305             updateHeapSnapshotForEvent(record.heapSnapshot);
306         if (this._heapSnapshotDiff)
307             updateHeapSnapshotForEvent(this._heapSnapshotDiff);
308     }
309
310     _snapshotListPathComponentClicked(event)
311     {
312         this.showHeapSnapshotList();
313     }
314
315     _snapshotPathComponentSelected(event)
316     {
317         console.assert(event.data.pathComponent.representedObject instanceof WebInspector.HeapAllocationsTimelineRecord);
318         this.showHeapSnapshotTimelineRecord(event.data.pathComponent.representedObject);
319     }
320
321     _currentContentViewDidChange(event)
322     {
323         this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
324         this.dispatchEventToListeners(WebInspector.ContentView.Event.NavigationItemsDidChange);
325     }
326
327     _contentViewSelectionPathComponentDidChange(event)
328     {
329         if (event.target !== this._contentViewContainer.currentContentView)
330             return;
331         this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
332     }
333
334     _heapSnapshotInvalidated(event)
335     {
336         let heapSnapshot = event.target;
337
338         if (this._baselineHeapSnapshotTimelineRecord) {
339             if (heapSnapshot === this._baselineHeapSnapshotTimelineRecord.heapSnapshot)
340                 this._cancelSelectComparisonHeapSnapshots();
341         }
342
343         if (this._heapSnapshotDiff) {
344             if (heapSnapshot === this._heapSnapshotDiff.snapshot1 || heapSnapshot === this._heapSnapshotDiff.snapshot2)
345                 this.showHeapSnapshotList();
346         } else if (this._dataGrid.selectedNode) {
347             if (heapSnapshot === this._dataGrid.selectedNode.record.heapSnapshot)
348                 this.showHeapSnapshotList();
349         }
350
351         this._updateCompareHeapSnapshotButton();
352     }
353
354     _updateCompareHeapSnapshotButton()
355     {
356         let hasAtLeastTwoValidSnapshots = false;
357
358         let count = 0;
359         for (let node of this._dataGrid.children) {
360             if (node.revealed && !node.hidden && !node.record.heapSnapshot.invalid) {
361                 count++;
362                 if (count === 2) {
363                     hasAtLeastTwoValidSnapshots = true;
364                     break;
365                 }
366             }
367         }
368
369         this._compareHeapSnapshotsButtonItem.enabled = hasAtLeastTwoValidSnapshots;
370     }
371
372     _takeHeapSnapshotClicked()
373     {
374         HeapAgent.snapshot(function(error, timestamp, snapshotStringData) {
375             let workerProxy = WebInspector.HeapSnapshotWorkerProxy.singleton();
376             workerProxy.createSnapshot(snapshotStringData, ({objectId, snapshot: serializedSnapshot}) => {
377                 let snapshot = WebInspector.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
378                 WebInspector.timelineManager.heapSnapshotAdded(timestamp, snapshot);
379             });
380         });
381     }
382
383     _cancelSelectComparisonHeapSnapshots()
384     {
385         if (!this._selectingComparisonHeapSnapshots)
386             return;
387
388         if (this._baselineDataGridNode)
389             this._baselineDataGridNode.clearBaseline();
390
391         this._selectingComparisonHeapSnapshots = false;
392         this._baselineDataGridNode = null;
393         this._baselineHeapSnapshotTimelineRecord = null;
394
395         this._compareHeapSnapshotsButtonItem.activated = false;
396
397         this.dispatchEventToListeners(WebInspector.ContentView.Event.NavigationItemsDidChange);
398     }
399
400     _compareHeapSnapshotsClicked(event)
401     {
402         let newActivated = !this._compareHeapSnapshotsButtonItem.activated;
403         this._compareHeapSnapshotsButtonItem.activated = newActivated;
404
405         if (!newActivated) {
406             this._cancelSelectComparisonHeapSnapshots();
407             return;
408         }
409
410         if (this._dataGrid.selectedNode)
411             this._dataGrid.selectedNode.deselect();
412
413         this._selectingComparisonHeapSnapshots = true;
414         this._baselineHeapSnapshotTimelineRecord = null;
415         this._compareHeapSnapshotHelpTextItem.text = WebInspector.UIString("Select baseline snapshot");
416
417         this.dispatchEventToListeners(WebInspector.ContentView.Event.NavigationItemsDidChange);
418     }
419
420     _dataGridNodeSelected(event)
421     {
422         if (!this._selectingComparisonHeapSnapshots)
423             return;
424
425         let dataGridNode = this._dataGrid.selectedNode;
426         if (!dataGridNode)
427             return;
428
429         let heapAllocationsTimelineRecord = dataGridNode.record;
430
431         // Cancel the selection if the heap snapshot is invalid, or was already selected as the baseline.
432         if (heapAllocationsTimelineRecord.heapSnapshot.invalid || this._baselineHeapSnapshotTimelineRecord === heapAllocationsTimelineRecord) {
433             this._dataGrid.selectedNode.deselect();
434             return;
435         }
436
437         // Selected Baseline.
438         if (!this._baselineHeapSnapshotTimelineRecord) {
439             this._baselineDataGridNode = dataGridNode;
440             this._baselineDataGridNode.markAsBaseline();
441             this._baselineHeapSnapshotTimelineRecord = heapAllocationsTimelineRecord;
442             this._dataGrid.selectedNode.deselect();
443             this._compareHeapSnapshotHelpTextItem.text = WebInspector.UIString("Select comparison snapshot");
444             this.dispatchEventToListeners(WebInspector.ContentView.Event.NavigationItemsDidChange);
445             return;
446         }
447
448         // Selected Comparison.
449         let snapshot1 = this._baselineHeapSnapshotTimelineRecord.heapSnapshot;
450         let snapshot2 = heapAllocationsTimelineRecord.heapSnapshot;
451         if (snapshot1.identifier > snapshot2.identifier)
452             [snapshot1, snapshot2] = [snapshot2, snapshot1];
453
454         let workerProxy = WebInspector.HeapSnapshotWorkerProxy.singleton();
455         workerProxy.createSnapshotDiff(snapshot1.proxyObjectId, snapshot2.proxyObjectId, ({objectId, snapshotDiff: serializedSnapshotDiff}) => {
456             let diff = WebInspector.HeapSnapshotDiffProxy.deserialize(objectId, serializedSnapshotDiff);
457             this.showHeapSnapshotDiff(diff);
458         });
459
460         this._baselineDataGridNode.clearBaseline();
461         this._baselineDataGridNode = null;
462         this._selectingComparisonHeapSnapshots = false;
463         this._compareHeapSnapshotsButtonItem.activated = false;
464     }
465 };