| /* |
| * 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.ProfileDataGridTree = class ProfileDataGridTree extends WI.Object |
| { |
| constructor(callingContextTree, startTime, endTime, sortComparator) |
| { |
| super(); |
| |
| console.assert(callingContextTree instanceof WI.CallingContextTree); |
| console.assert(typeof sortComparator === "function"); |
| |
| this._children = []; |
| this._sortComparator = sortComparator; |
| |
| this._callingContextTree = callingContextTree; |
| this._startTime = startTime; |
| this._endTime = endTime; |
| this._totalSampleTime = this._callingContextTree.totalDurationInTimeRange(startTime, endTime); |
| |
| this._focusNodes = []; |
| this._modifiers = []; |
| |
| this._repopulate(); |
| } |
| |
| // Static |
| |
| static buildSortComparator(columnIdentifier, sortOrder) |
| { |
| let ascending = sortOrder === WI.DataGrid.SortOrder.Ascending; |
| return function(a, b) { |
| let result = a.data[columnIdentifier] - b.data[columnIdentifier]; |
| return ascending ? result : -result; |
| }; |
| } |
| |
| // Public |
| |
| get callingContextTree() { return this._callingContextTree; } |
| |
| get focusNodes() |
| { |
| return this._focusNodes; |
| } |
| |
| get currentFocusNode() |
| { |
| return this._focusNodes.lastValue; |
| } |
| |
| get modifiers() |
| { |
| return this._modifiers; |
| } |
| |
| get startTime() |
| { |
| if (this._focusNodes.length) |
| return this._currentFocusStartTime; |
| return this._startTime; |
| } |
| |
| get endTime() |
| { |
| if (this._focusNodes.length) |
| return this._currentFocusEndTime; |
| return this._endTime; |
| } |
| |
| get totalSampleTime() |
| { |
| if (this._focusNodes.length) |
| return this._currentFocusTotalSampleTime; |
| return this._totalSampleTime; |
| } |
| |
| get children() |
| { |
| return this._children; |
| } |
| |
| appendChild(node) |
| { |
| this._children.push(node); |
| } |
| |
| insertChild(node, index) |
| { |
| this._children.splice(index, 0, node); |
| } |
| |
| removeChildren() |
| { |
| this._children = []; |
| } |
| |
| set sortComparator(comparator) |
| { |
| this._sortComparator = comparator; |
| this.sort(); |
| } |
| |
| sort() |
| { |
| let children = this._children; |
| children.sort(this._sortComparator); |
| |
| for (let i = 0; i < children.length; ++i) { |
| children[i]._recalculateSiblings(i); |
| children[i].sort(); |
| } |
| } |
| |
| refresh() |
| { |
| for (let child of this._children) |
| child.refreshRecursively(); |
| } |
| |
| addFocusNode(profileDataGridNode) |
| { |
| console.assert(profileDataGridNode instanceof WI.ProfileDataGridNode); |
| |
| // Save the original parent for when we rollback. |
| this._saveFocusedNodeOriginalParent(profileDataGridNode); |
| |
| this._focusNodes.push(profileDataGridNode); |
| this._focusChanged(); |
| } |
| |
| rollbackFocusNode(profileDataGridNode) |
| { |
| console.assert(profileDataGridNode instanceof WI.ProfileDataGridNode); |
| |
| let index = this._focusNodes.indexOf(profileDataGridNode); |
| console.assert(index !== -1, "rollbackFocusNode should be rolling back to a previous focused node"); |
| console.assert(index !== this._focusNodes.length - 1, "rollbackFocusNode should be rolling back to a previous focused node"); |
| if (index === -1) |
| return; |
| |
| this._focusParentsToExpand = []; |
| |
| for (let i = index + 1; i < this._focusNodes.length; ++i) |
| this._restoreFocusedNodeToOriginalParent(this._focusNodes[i]); |
| |
| // Remove everything after this node. |
| this._focusNodes.splice(index + 1); |
| this._focusChanged(); |
| } |
| |
| clearFocusNodes() |
| { |
| this._focusParentsToExpand = []; |
| |
| for (let profileDataGridNode of this._focusNodes) |
| this._restoreFocusedNodeToOriginalParent(profileDataGridNode); |
| |
| this._focusNodes = []; |
| this._focusChanged(); |
| } |
| |
| hasModifiers() |
| { |
| return this._modifiers.length > 0; |
| } |
| |
| addModifier(modifier) |
| { |
| this._modifiers.push(modifier); |
| this._modifiersChanged(); |
| } |
| |
| clearModifiers() |
| { |
| this._modifiers = []; |
| this._modifiersChanged(); |
| } |
| |
| // Private |
| |
| _repopulate() |
| { |
| this.removeChildren(); |
| |
| if (this._focusNodes.length) { |
| // The most recently focused node. |
| this.appendChild(this.currentFocusNode); |
| } else { |
| // All nodes in the time range in the calling context tree. |
| this._callingContextTree.forEachChild((child) => { |
| if (child.hasStackTraceInTimeRange(this._startTime, this._endTime)) |
| this.appendChild(new WI.ProfileDataGridNode(child, this)); |
| }); |
| } |
| |
| this.sort(); |
| } |
| |
| _focusChanged() |
| { |
| let profileDataGridNode = this.currentFocusNode; |
| if (profileDataGridNode) |
| this._updateCurrentFocusDetails(profileDataGridNode); |
| |
| // FIXME: This re-creates top level children, without remembering their expanded / unexpanded state. |
| this._repopulate(); |
| |
| this.dispatchEventToListeners(WI.ProfileDataGridTree.Event.FocusChanged); |
| |
| if (this._focusParentsToExpand) { |
| for (let profileDataGridNode of this._focusParentsToExpand) |
| profileDataGridNode.expand(); |
| this._focusParentsToExpand = null; |
| } |
| } |
| |
| _updateCurrentFocusDetails(focusDataGridNode) |
| { |
| let {timestamps, duration} = focusDataGridNode.callingContextTreeNode.filteredTimestampsAndDuration(this._startTime, this._endTime); |
| |
| this._currentFocusStartTime = timestamps[0]; |
| this._currentFocusEndTime = timestamps.lastValue; |
| this._currentFocusTotalSampleTime = duration; |
| } |
| |
| _saveFocusedNodeOriginalParent(focusDataGridNode) |
| { |
| focusDataGridNode.__previousParent = focusDataGridNode.parent; |
| focusDataGridNode.__previousParent.removeChild(focusDataGridNode); |
| } |
| |
| _restoreFocusedNodeToOriginalParent(focusDataGridNode) |
| { |
| // NOTE: A DataGridTree maintains a list of children but those |
| // children get adopted by the DataGrid in ProfileView and |
| // actually displayed. When we focused this DataGridNode, its |
| // parent was removed from the real DataGrid, but if it was not |
| // at the top level it was not detached. When we re-append |
| // ourselves onto the previous parent, if it still thinks it is |
| // attached and expanded it will attach us immediately, which |
| // can create an orphaned <tr> in the DataGrid that is not one |
| // of the DataGrid's immediate children. |
| // |
| // Workaround this by: |
| // - collapsing our parent to prevent attaching when we get added. |
| // - reparent to our previous parent. |
| // - expanding our parent after the DataGrid has had a chance to update. |
| focusDataGridNode.__previousParent.collapse(); |
| this._focusParentsToExpand.push(focusDataGridNode.__previousParent); |
| |
| focusDataGridNode.__previousParent.appendChild(focusDataGridNode); |
| focusDataGridNode.__previousParent = undefined; |
| } |
| |
| _modifiersChanged() |
| { |
| this.dispatchEventToListeners(WI.ProfileDataGridTree.Event.ModifiersChanged); |
| } |
| }; |
| |
| WI.ProfileDataGridTree.Event = { |
| FocusChanged: "profile-data-grid-tree-focus-changed", |
| ModifiersChanged: "profile-data-grid-tree-modifiers-changed", |
| }; |
| |
| WI.ProfileDataGridTree.ModifierType = { |
| ChargeToCaller: "charge", |
| }; |