/* * Copyright (C) 2016 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ WI.HeapAllocationsTimelineView = class HeapAllocationsTimelineView extends WI.TimelineView { constructor(timeline, extraArguments) { super(timeline, extraArguments); console.assert(timeline.type === WI.TimelineRecord.Type.HeapAllocations, timeline); this.element.classList.add("heap-allocations"); let columns = { name: { title: WI.UIString("Name"), width: "150px", icon: true, }, timestamp: { title: WI.UIString("Time"), width: "80px", sortable: true, aligned: "right", }, size: { title: WI.UIString("Size"), width: "80px", sortable: true, aligned: "right", }, liveSize: { title: WI.UIString("Live Size"), width: "80px", sortable: true, aligned: "right", }, }; this._importButtonNavigationItem = new WI.ButtonNavigationItem("import", WI.UIString("Import"), "Images/Import.svg", 15, 15); this._importButtonNavigationItem.tooltip = WI.UIString("Import"); this._importButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText; this._importButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._importButtonNavigationItemClicked, this); let snapshotTooltip = WI.UIString("Take snapshot"); this._takeHeapSnapshotButtonItem = new WI.ButtonNavigationItem("take-snapshot", snapshotTooltip, "Images/Camera.svg", 16, 16); this._takeHeapSnapshotButtonItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._takeHeapSnapshotClicked, this); let defaultToolTip = WI.UIString("Compare snapshots"); let activatedToolTip = WI.UIString("Cancel comparison"); this._compareHeapSnapshotsButtonItem = new WI.ActivateButtonNavigationItem("compare-heap-snapshots", defaultToolTip, activatedToolTip, "Images/Compare.svg", 16, 16); this._compareHeapSnapshotsButtonItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._compareHeapSnapshotsClicked, this); this._compareHeapSnapshotsButtonItem.enabled = false; this._compareHeapSnapshotsButtonItem.activated = false; this._compareHeapSnapshotHelpTextItem = new WI.TextNavigationItem("compare-heap-snapshot-help-text", ""); this._selectingComparisonHeapSnapshots = false; this._baselineDataGridNode = null; this._baselineHeapSnapshotTimelineRecord = null; this._heapSnapshotDiff = null; this._snapshotListScrollTop = 0; this._showingSnapshotList = true; this._snapshotListPathComponent = new WI.HierarchicalPathComponent(WI.UIString("Snapshot List"), "snapshot-list-icon", "snapshot-list", false, false); this._snapshotListPathComponent.addEventListener(WI.HierarchicalPathComponent.Event.Clicked, this._snapshotListPathComponentClicked, this); this._unseenRecords = []; this._unseenRecordsBannerView = new WI.BannerView(WI.UIString("There are new snapshots that have been filtered", "There are new snapshots that have been filtered @ Heap Allocations Timeline View", "Message displayed in a banner when one or more snapshots that the user has not yet seen are being filtered."), { actionButtonMessage: WI.UIString("Clear Filters", "Clear Filters @ Heap Allocations Timeline View", "Text for button that will clear both text filters and time range filters."), showDismissButton: true, }); this._unseenRecordsBannerView.addEventListener(WI.BannerView.Event.ActionButtonClicked, this._handleUnseenRecordsBannerClearFiltersClicked, this); this._unseenRecordsBannerView.addEventListener(WI.BannerView.Event.DismissButtonClicked, this._handleUnseenRecordsBannerDismissClicked, this); this._dataGrid = new WI.TimelineDataGrid(columns); this._dataGrid.sortColumnIdentifier = "timestamp"; this._dataGrid.sortOrder = WI.DataGrid.SortOrder.Ascending; this._dataGrid.createSettings("heap-allocations-timeline-view"); this._dataGrid.addEventListener(WI.DataGrid.Event.SelectedNodeChanged, this._dataGridNodeSelected, this); this.setupDataGrid(this._dataGrid); this.addSubview(this._dataGrid); this._contentViewContainer = new WI.ContentViewContainer; this._contentViewContainer.addEventListener(WI.ContentViewContainer.Event.CurrentContentViewDidChange, this._currentContentViewDidChange, this); WI.ContentView.addEventListener(WI.ContentView.Event.SelectionPathComponentsDidChange, this._contentViewSelectionPathComponentDidChange, this); this._pendingRecords = Array.from(timeline.records); timeline.addEventListener(WI.Timeline.Event.RecordAdded, this._heapAllocationsTimelineRecordAdded, this); WI.HeapSnapshotProxy.addEventListener(WI.HeapSnapshotProxy.Event.Invalidated, this._heapSnapshotInvalidated, this); WI.HeapSnapshotWorkerProxy.singleton().addEventListener(WI.HeapSnapshotWorkerProxy.Event.Collection, this._heapSnapshotCollectionEvent, this); } // Public get scrollableElements() { if (this._showingSnapshotList) return [this._dataGrid.scrollContainer]; if (this._contentViewContainer.currentContentView) return this._contentViewContainer.currentContentView.scrollableElements; return []; } showHeapSnapshotList() { if (this._showingSnapshotList) return; this._showingSnapshotList = true; this._heapSnapshotDiff = null; this._cancelSelectComparisonHeapSnapshots(); this.removeSubview(this._contentViewContainer); this.addSubview(this._dataGrid); this._dataGrid.scrollContainer.scrollTop = this._snapshotListScrollTop; this.dispatchEventToListeners(WI.ContentView.Event.SelectionPathComponentsDidChange); this.dispatchEventToListeners(WI.ContentView.Event.NavigationItemsDidChange); } showHeapSnapshotTimelineRecord(heapSnapshotTimelineRecord) { if (this._showingSnapshotList) { this._snapshotListScrollTop = this._dataGrid.scrollContainer.scrollTop; this.removeSubview(this._dataGrid); this.addSubview(this._contentViewContainer); } this._showingSnapshotList = false; this._heapSnapshotDiff = null; this._cancelSelectComparisonHeapSnapshots(); for (let dataGridNode of this._dataGrid.children) { if (dataGridNode.record === heapSnapshotTimelineRecord) { dataGridNode.hidden = false; dataGridNode.select(); break; } } let shouldTriggerContentViewUpdate = this._contentViewContainer.currentContentView && this._contentViewContainer.currentContentView.representedObject === heapSnapshotTimelineRecord.heapSnapshot; this._contentViewContainer.showContentViewForRepresentedObject(heapSnapshotTimelineRecord.heapSnapshot); if (shouldTriggerContentViewUpdate) this._currentContentViewDidChange(); } showHeapSnapshotDiff(heapSnapshotDiff) { if (this._showingSnapshotList) { this.removeSubview(this._dataGrid); this.addSubview(this._contentViewContainer); } this._showingSnapshotList = false; this._heapSnapshotDiff = heapSnapshotDiff; this._contentViewContainer.showContentViewForRepresentedObject(heapSnapshotDiff); } // Protected get navigationItems() { if (this._showingSnapshotList) { let items = [this._importButtonNavigationItem, this._takeHeapSnapshotButtonItem, this._compareHeapSnapshotsButtonItem]; if (this._selectingComparisonHeapSnapshots) items.push(this._compareHeapSnapshotHelpTextItem); return items; } if (this._contentViewContainer.currentContentView) return this._contentViewContainer.currentContentView.navigationItems; return []; } get selectionPathComponents() { let components = [this._snapshotListPathComponent]; if (this._showingSnapshotList) return components; if (this._heapSnapshotDiff) { let firstSnapshotIdentifier = this._heapSnapshotDiff.snapshot1.identifier; let secondSnapshotIdentifier = this._heapSnapshotDiff.snapshot2.identifier; let diffComponent = new WI.HierarchicalPathComponent(WI.UIString("Snapshot Comparison (%d and %d)").format(firstSnapshotIdentifier, secondSnapshotIdentifier), "snapshot-diff-icon", "snapshot-diff"); components.push(diffComponent); } else if (this._dataGrid.selectedNode) { let heapSnapshotPathComponent = new WI.HeapAllocationsTimelineDataGridNodePathComponent(this._dataGrid.selectedNode); heapSnapshotPathComponent.addEventListener(WI.HierarchicalPathComponent.Event.SiblingWasSelected, this._snapshotPathComponentSelected, this); components.push(heapSnapshotPathComponent); } if (this._contentViewContainer.currentContentView) components.pushAll(this._contentViewContainer.currentContentView.selectionPathComponents); return components; } selectRecord(record) { if (record) this.showHeapSnapshotTimelineRecord(record); else this.showHeapSnapshotList(); } closed() { this.representedObject.removeEventListener(WI.Timeline.Event.RecordAdded, this._heapAllocationsTimelineRecordAdded, this); this._dataGrid.closed(); this._contentViewContainer.closeAllContentViews(); WI.ContentView.removeEventListener(WI.ContentView.Event.SelectionPathComponentsDidChange, this._contentViewSelectionPathComponentDidChange, this); WI.HeapSnapshotProxy.removeEventListener(WI.HeapSnapshotProxy.Event.Invalidated, this._heapSnapshotInvalidated, this); WI.HeapSnapshotWorkerProxy.singleton().removeEventListener(WI.HeapSnapshotWorkerProxy.Event.Collection, this._heapSnapshotCollectionEvent, this); } layout() { super.layout(); if (!this._pendingRecords.length) return; for (let heapAllocationsTimelineRecord of this._pendingRecords) { let dataGridNode = new WI.HeapAllocationsTimelineDataGridNode(heapAllocationsTimelineRecord, { graphDataSource: this, heapAllocationsView: this, }); this._dataGrid.addRowInSortOrder(dataGridNode); if (dataGridNode.hidden) this._unseenRecords.push(dataGridNode); } this._pendingRecords = []; this._updateUnseenRecordsBannerView(); this._updateCompareHeapSnapshotButton(); } reset() { super.reset(); this._dataGrid.reset(); this.showHeapSnapshotList(); this._pendingRecords = []; this._unseenRecords = []; this._updateUnseenRecordsBannerView(); this._updateCompareHeapSnapshotButton(); } updateFilter(filters) { if (this._showingSnapshotList) { this._dataGrid.filterText = filters ? filters.text : ""; return; } console.assert(this._contentViewContainer.currentContentView); this._contentViewContainer.currentContentView.updateFilter(filters); } filterDidChange() { super.filterDidChange(); this._updateUnseenRecordsBannerView(); } // Private _heapAllocationsTimelineRecordAdded(event) { this._pendingRecords.push(event.data.record); this.needsLayout(); } _heapSnapshotCollectionEvent(event) { function updateHeapSnapshotForEvent(heapSnapshot) { if (heapSnapshot.invalid) return; heapSnapshot.updateForCollectionEvent(event); } for (let node of this._dataGrid.children) updateHeapSnapshotForEvent(node.record.heapSnapshot); for (let record of this._pendingRecords) updateHeapSnapshotForEvent(record.heapSnapshot); if (this._heapSnapshotDiff) updateHeapSnapshotForEvent(this._heapSnapshotDiff); } _snapshotListPathComponentClicked(event) { this.showHeapSnapshotList(); } _snapshotPathComponentSelected(event) { console.assert(event.data.pathComponent.representedObject instanceof WI.HeapAllocationsTimelineRecord); this.showHeapSnapshotTimelineRecord(event.data.pathComponent.representedObject); } _currentContentViewDidChange(event) { this.dispatchEventToListeners(WI.ContentView.Event.SelectionPathComponentsDidChange); this.dispatchEventToListeners(WI.ContentView.Event.NavigationItemsDidChange); } _contentViewSelectionPathComponentDidChange(event) { if (event.target !== this._contentViewContainer.currentContentView) return; this.dispatchEventToListeners(WI.ContentView.Event.SelectionPathComponentsDidChange); } _heapSnapshotInvalidated(event) { let heapSnapshot = event.target; if (this._baselineHeapSnapshotTimelineRecord) { if (heapSnapshot === this._baselineHeapSnapshotTimelineRecord.heapSnapshot) this._cancelSelectComparisonHeapSnapshots(); } if (this._heapSnapshotDiff) { if (heapSnapshot === this._heapSnapshotDiff.snapshot1 || heapSnapshot === this._heapSnapshotDiff.snapshot2) this.showHeapSnapshotList(); } else if (this._dataGrid.selectedNode) { if (heapSnapshot === this._dataGrid.selectedNode.record.heapSnapshot) this.showHeapSnapshotList(); } this._updateCompareHeapSnapshotButton(); } _updateCompareHeapSnapshotButton() { let hasAtLeastTwoValidSnapshots = false; let count = 0; for (let node of this._dataGrid.children) { if (node.revealed && !node.hidden && !node.record.heapSnapshot.invalid) { count++; if (count === 2) { hasAtLeastTwoValidSnapshots = true; break; } } } this._compareHeapSnapshotsButtonItem.enabled = hasAtLeastTwoValidSnapshots; } _updateUnseenRecordsBannerView() { this._unseenRecords = this._unseenRecords.filter((record) => record.hidden); if (this._unseenRecords.length) { if (this._unseenRecordsBannerView.parentView !== this) this.insertSubviewBefore(this._unseenRecordsBannerView, this._dataGrid); } else { if (this._unseenRecordsBannerView.parentView === this) this.removeSubview(this._unseenRecordsBannerView); } } _importButtonNavigationItemClicked() { WI.FileUtilities.importText((result) => { let snapshotStringData = result.text; let workerProxy = WI.HeapSnapshotWorkerProxy.singleton(); workerProxy.createImportedSnapshot(snapshotStringData, result.filename, ({objectId, snapshot: serializedSnapshot}) => { let snapshot = WI.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot); snapshot.snapshotStringData = snapshotStringData; const timestamp = NaN; WI.timelineManager.heapSnapshotAdded(timestamp, snapshot); this.dispatchEventToListeners(WI.TimelineView.Event.NeedsEntireSelectedRange); }); }, {multiple: true}); } _takeHeapSnapshotClicked() { WI.heapManager.snapshot((error, timestamp, snapshotStringData) => { let workerProxy = WI.HeapSnapshotWorkerProxy.singleton(); workerProxy.createSnapshot(snapshotStringData, ({objectId, snapshot: serializedSnapshot}) => { let snapshot = WI.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot); snapshot.snapshotStringData = snapshotStringData; WI.timelineManager.heapSnapshotAdded(timestamp, snapshot); this.dispatchEventToListeners(WI.TimelineView.Event.NeedsEntireSelectedRange); }); }); } _cancelSelectComparisonHeapSnapshots() { if (!this._selectingComparisonHeapSnapshots) return; if (this._baselineDataGridNode) this._baselineDataGridNode.clearBaseline(); this._selectingComparisonHeapSnapshots = false; this._baselineDataGridNode = null; this._baselineHeapSnapshotTimelineRecord = null; this._compareHeapSnapshotsButtonItem.activated = false; this.dispatchEventToListeners(WI.ContentView.Event.NavigationItemsDidChange); } _compareHeapSnapshotsClicked(event) { let newActivated = !this._compareHeapSnapshotsButtonItem.activated; this._compareHeapSnapshotsButtonItem.activated = newActivated; if (!newActivated) { this._cancelSelectComparisonHeapSnapshots(); return; } if (this._dataGrid.selectedNode) this._dataGrid.selectedNode.deselect(); this._selectingComparisonHeapSnapshots = true; this._baselineHeapSnapshotTimelineRecord = null; this._compareHeapSnapshotHelpTextItem.text = WI.UIString("Select baseline snapshot"); this.dispatchEventToListeners(WI.ContentView.Event.NavigationItemsDidChange); } _dataGridNodeSelected(event) { if (!this._selectingComparisonHeapSnapshots) return; let dataGridNode = this._dataGrid.selectedNode; if (!dataGridNode) return; let heapAllocationsTimelineRecord = dataGridNode.record; // Cancel the selection if the heap snapshot is invalid, or was already selected as the baseline. if (heapAllocationsTimelineRecord.heapSnapshot.invalid || this._baselineHeapSnapshotTimelineRecord === heapAllocationsTimelineRecord) { this._dataGrid.selectedNode.deselect(); return; } // Selected Baseline. if (!this._baselineHeapSnapshotTimelineRecord) { this._baselineDataGridNode = dataGridNode; this._baselineDataGridNode.markAsBaseline(); this._baselineHeapSnapshotTimelineRecord = heapAllocationsTimelineRecord; this._dataGrid.selectedNode.deselect(); this._compareHeapSnapshotHelpTextItem.text = WI.UIString("Select comparison snapshot"); this.dispatchEventToListeners(WI.ContentView.Event.NavigationItemsDidChange); return; } // Selected Comparison. let snapshot1 = this._baselineHeapSnapshotTimelineRecord.heapSnapshot; let snapshot2 = heapAllocationsTimelineRecord.heapSnapshot; if (snapshot1.identifier > snapshot2.identifier) [snapshot1, snapshot2] = [snapshot2, snapshot1]; let workerProxy = WI.HeapSnapshotWorkerProxy.singleton(); workerProxy.createSnapshotDiff(snapshot1.proxyObjectId, snapshot2.proxyObjectId, ({objectId, snapshotDiff: serializedSnapshotDiff}) => { let diff = WI.HeapSnapshotDiffProxy.deserialize(objectId, serializedSnapshotDiff); this.showHeapSnapshotDiff(diff); }); this._baselineDataGridNode.clearBaseline(); this._baselineDataGridNode = null; this._selectingComparisonHeapSnapshots = false; this._compareHeapSnapshotsButtonItem.activated = false; } _handleUnseenRecordsBannerClearFiltersClicked(event) { this.dispatchEventToListeners(WI.TimelineView.Event.NeedsFiltersCleared); this.dispatchEventToListeners(WI.TimelineView.Event.NeedsEntireSelectedRange); } _handleUnseenRecordsBannerDismissClicked(event) { this._unseenRecords = []; this._updateUnseenRecordsBannerView(); } }; WI.HeapAllocationsTimelineView.ReferencePage = WI.ReferencePage.TimelinesTab.JavaScriptAllocationsTimeline;