Web Inspector: Add back support for a heavy / bottom up profile view
authorjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 6 Mar 2016 00:28:39 +0000 (00:28 +0000)
committerjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 6 Mar 2016 00:28:39 +0000 (00:28 +0000)
https://bugs.webkit.org/show_bug.cgi?id=140578
<rdar://problem/19506794>

Reviewed by Timothy Hatcher.

* Localizations/en.lproj/localizedStrings.js:
* UserInterface/Main.html:
New strings and resources.

* UserInterface/Base/Utilities.js:
(Number.secondsToMillisecondsString):
Helper for providing a consistent milliseconds string used in profiles.

* UserInterface/Controllers/TimelineManager.js:
(WebInspector.TimelineManager.prototype.scriptProfilerTrackingCompleted):
The calling context tree should be stored on a Recording, not on the global
TimelineManager. Also create two trees, one top down and one bottom up.

* UserInterface/Models/CallingContextTree.js:
(WebInspector.CallingContextTree):
(WebInspector.CallingContextTree.prototype.get type):
(WebInspector.CallingContextTree.prototype.get totalExecutionTime):
(WebInspector.CallingContextTree.prototype.reset):
(WebInspector.CallingContextTree.prototype.numberOfSamplesInTimeRange):
(WebInspector.CallingContextTree.prototype.increaseExecutionTime):
Give a CallingContextTree a type (TopDown / BottomUp) and some getters.

(WebInspector.CallingContextTree.prototype.updateTreeWithStackTrace):
Build a bottom up or top down tree from samples.

(WebInspector.CallingContextTree.prototype.forEachChild):
Allow iterating from the root.

(WebInspector.CCTNode):
(WebInspector.CCTNode.prototype.hasChildrenInTimeRange):
(WebInspector.CCTNode.prototype.numberOfLeafTimestamps):
(WebInspector.CCTNode.prototype.addTimestampAndExpressionLocation):
(WebInspector.CCTNode.prototype.equals):
(WebInspector.CCTNode.prototype.hasChildren): Deleted.
Give a CCTNode a list of leaf timestamps alongside the list of all timestamps.
Leaf timestamps will count as "self time" in a profile view.

* UserInterface/Models/SourceCodeLocation.js:
(WebInspector.SourceCodeLocation.prototype._locationString):
Nobody was using "ColumnStyle.Hidden" so repurpose it to be even simpler.

* UserInterface/Models/TimelineRecording.js:
(WebInspector.TimelineRecording):
(WebInspector.TimelineRecording.prototype.get topDownCallingContextTree):
(WebInspector.TimelineRecording.prototype.get bottomUpCallingContextTree):
(WebInspector.TimelineRecording.prototype.reset):
Store the two types of calling context trees and allow reseting them.

* UserInterface/Protocol/InspectorFrontendAPI.js:
(InspectorFrontendAPI.contextMenuItemSelected):
Helper for debugging uncaught exceptions in context menus.

* UserInterface/Views/ContentView.js:
(WebInspector.ContentView.createFromRepresentedObject):
(WebInspector.ContentView.isViewable):
A ScriptTimeline now has a cluster view.
A CallingContextTree now has a ProfileView.

* UserInterface/Views/DataGrid.js:
(WebInspector.DataGrid.prototype.insertChild):
(WebInspector.DataGrid.prototype._contextMenuInDataTable):
(WebInspector.DataGridNode.prototype.refreshRecursively):
(WebInspector.DataGridNode.prototype.elementWithColumnIdentifier):
(WebInspector.DataGridNode.prototype.forEachImmediateChild):
(WebInspector.DataGridNode.prototype.forEachChildInSubtree):
(WebInspector.DataGridNode.prototype.isInSubtreeOfNode):
Provide some helpers for iterating DataGridNodes, useful when the
actual DataGrid comes from a DataGridTree.

(WebInspector.DataGridNode.prototype.select):
(WebInspector.DataGridNode.prototype.deselect):
The indent width of DataGridNodes was not getting reset when a
node was removed and re-added to a tree due to a cached padding.

(WebInspector.DataGridNode.prototype.appendContextMenuItems):
Allow DataGridNodes to provide context menu items by overriding this method.

* UserInterface/Views/PathComponentIcons.css:
(.function-icon .icon):
(.native-icon .icon):
(.program-icon .icon):
Icons for profile nodes in path components.

* UserInterface/Views/ProfileDataGridNode.js: Added.
(WebInspector.ProfileDataGridNode):
(WebInspector.ProfileDataGridNode.prototype.get node):
(WebInspector.ProfileDataGridNode.prototype.displayName):
(WebInspector.ProfileDataGridNode.prototype.iconClassName):
(WebInspector.ProfileDataGridNode.prototype.get data):
(WebInspector.ProfileDataGridNode.prototype.createCellContent):
(WebInspector.ProfileDataGridNode.prototype.sort):
(WebInspector.ProfileDataGridNode.prototype.refresh):
(WebInspector.ProfileDataGridNode.prototype.appendContextMenuItems):
(WebInspector.ProfileDataGridNode.prototype._updateChildrenForModifiers):
(WebInspector.ProfileDataGridNode.prototype._recalculateData):
(WebInspector.ProfileDataGridNode.prototype._totalTimeContent):
(WebInspector.ProfileDataGridNode.prototype._displayContent):
(WebInspector.ProfileDataGridNode.prototype._populate):
CCTNode DataGridNode. A row in the ProfileDataGridTree. Handles
tree modifiers like charge to caller.

* UserInterface/Views/ProfileDataGridTree.js: Added.
(WebInspector.ProfileDataGridTree):
(WebInspector.ProfileDataGridTree.buildSortComparator):
(WebInspector.ProfileDataGridTree.prototype.get callingContextTree):
(WebInspector.ProfileDataGridTree.prototype.get sampleInterval):
(WebInspector.ProfileDataGridTree.prototype.get focusNodes):
(WebInspector.ProfileDataGridTree.prototype.get currentFocusNode):
(WebInspector.ProfileDataGridTree.prototype.get modifiers):
(WebInspector.ProfileDataGridTree.prototype.get startTime):
(WebInspector.ProfileDataGridTree.prototype.get endTime):
(WebInspector.ProfileDataGridTree.prototype.get numberOfSamples):
(WebInspector.ProfileDataGridTree.prototype.get children):
(WebInspector.ProfileDataGridTree.prototype.appendChild):
(WebInspector.ProfileDataGridTree.prototype.insertChild):
(WebInspector.ProfileDataGridTree.prototype.removeChildren):
(WebInspector.ProfileDataGridTree.prototype.set sortComparator):
(WebInspector.ProfileDataGridTree.prototype.sort):
(WebInspector.ProfileDataGridTree.prototype.refresh):
(WebInspector.ProfileDataGridTree.prototype.addFocusNode):
(WebInspector.ProfileDataGridTree.prototype.rollbackFocusNode):
(WebInspector.ProfileDataGridTree.prototype.clearFocusNodes):
(WebInspector.ProfileDataGridTree.prototype.hasModifiers):
(WebInspector.ProfileDataGridTree.prototype.addModifier):
(WebInspector.ProfileDataGridTree.prototype.clearModifiers):
(WebInspector.ProfileDataGridTree.prototype._repopulate):
(WebInspector.ProfileDataGridTree.prototype._focusChanged):
(WebInspector.ProfileDataGridTree.prototype._updateCurrentFocusDetails):
(WebInspector.ProfileDataGridTree.prototype._restoreFocusedNodeToOriginalParent):
(WebInspector.ProfileDataGridTree.prototype._modifiersChanged):
Start of a DataGridTree for a CallingContextTree.
Contains special logic for focused nodes and modifiers.

* UserInterface/Views/ProfileView.css: Added.
(.profile > .data-grid):
(.profile > .data-grid th):
(.profile > .data-grid td .icon):
(.profile > .data-grid td .percentage):
(.profile > .data-grid td .location):
(.profile > .data-grid:matches(:focus, .force-focus) tr.selected td .location):
(.profile > .data-grid td .icon.function-icon):
(.profile > .data-grid td .icon.native-icon):
(.profile > .data-grid td .icon.program-icon):
(.profile > .data-grid tr:matches(.selected, :hover) .go-to-arrow):
(.profile > .data-grid td.function-column):
(.profile > .data-grid td .guidance):
(.profile > .data-grid td .guidance.hover):
(.profile > .data-grid td .guidance.base):
(.profile > .data-grid tr:not(.expanded) td .guidance.base):
(.profile > .data-grid tr.expanded td .guidance.base):
* UserInterface/Views/ProfileView.js: Added.
(WebInspector.ProfileView):
(WebInspector.ProfileView.prototype.get callingContextTree):
(WebInspector.ProfileView.prototype.get startTime):
(WebInspector.ProfileView.prototype.get endTime):
(WebInspector.ProfileView.prototype.setStartAndEndTime):
(WebInspector.ProfileView.prototype.hasFocusNodes):
(WebInspector.ProfileView.prototype.clearFocusNodes):
(WebInspector.ProfileView.prototype.get selectionPathComponents):
(WebInspector.ProfileView.prototype._recreate):
(WebInspector.ProfileView.prototype._repopulateDataGridFromTree):
(WebInspector.ProfileView.prototype._pathComponentClicked):
(WebInspector.ProfileView.prototype._dataGridTreeFocusChanged):
(WebInspector.ProfileView.prototype._dataGridTreeModifiersChanged):
(WebInspector.ProfileView.prototype._dataGridSortChanged):
(WebInspector.ProfileView.prototype._dataGridNodeSelected):
(WebInspector.ProfileView.prototype._dataGridNodeExpanded):
(WebInspector.ProfileView.prototype._mouseOverDataGrid):
(WebInspector.ProfileView.prototype._mouseLeaveDataGrid):
(WebInspector.ProfileView.prototype._guidanceElementKey):
(WebInspector.ProfileView.prototype._removeGuidanceElement):
(WebInspector.ProfileView.prototype._appendGuidanceElement):
ProfileView holds a data grid which is populated from the data grid tree.
Special handing for guidance markers when hovering / selecting parts of the tree.

* UserInterface/Views/ScriptClusterTimelineView.js: Added.
(WebInspector.ScriptClusterTimelineView.createPathComponent):
(WebInspector.ScriptClusterTimelineView):
(WebInspector.ScriptClusterTimelineView.prototype.get zeroTime):
(WebInspector.ScriptClusterTimelineView.prototype.set zeroTime):
(WebInspector.ScriptClusterTimelineView.prototype.get startTime):
(WebInspector.ScriptClusterTimelineView.prototype.set startTime):
(WebInspector.ScriptClusterTimelineView.prototype.get endTime):
(WebInspector.ScriptClusterTimelineView.prototype.set endTime):
(WebInspector.ScriptClusterTimelineView.prototype.get currentTime):
(WebInspector.ScriptClusterTimelineView.prototype.set currentTime):
(WebInspector.ScriptClusterTimelineView.prototype.get navigationSidebarTreeOutline):
(WebInspector.ScriptClusterTimelineView.prototype.reset):
(WebInspector.ScriptClusterTimelineView.prototype.filterDidChange):
(WebInspector.ScriptClusterTimelineView.prototype.matchTreeElementAgainstCustomFilters):
(WebInspector.ScriptClusterTimelineView.prototype.get detailsContentView):
(WebInspector.ScriptClusterTimelineView.prototype.get profileContentView):
(WebInspector.ScriptClusterTimelineView.prototype.get selectionPathComponents):
(WebInspector.ScriptClusterTimelineView.prototype.saveToCookie):
(WebInspector.ScriptClusterTimelineView.prototype.restoreFromCookie):
(WebInspector.ScriptClusterTimelineView.prototype.showDetails):
(WebInspector.ScriptClusterTimelineView.prototype.showProfile):
(WebInspector.ScriptClusterTimelineView.prototype._pathComponentForContentView):
(WebInspector.ScriptClusterTimelineView.prototype._identifierForContentView):
(WebInspector.ScriptClusterTimelineView.prototype._showContentViewForIdentifier):
(WebInspector.ScriptClusterTimelineView.prototype._pathComponentSelected):
(WebInspector.ScriptClusterTimelineView.prototype._scriptClusterViewCurrentContentViewDidChange):
Script Timeline ClusterContentView. Toggle between the normal "Details" data grid
and the new "Call Tree" profile view. Currently the recording expects child content
views to be TimelineViews, this ClusterContentView forwards TimelineView relevant
methods to the real TimelineView children.

* UserInterface/Views/ScriptDetailsTimelineView.js: Renamed from Source/WebInspectorUI/UserInterface/Views/ScriptTimelineView.js.
(WebInspector.ScriptDetailsTimelineView):
(WebInspector.ScriptDetailsTimelineView.prototype.get navigationSidebarTreeOutlineLabel):
(WebInspector.ScriptDetailsTimelineView.prototype.shown):
(WebInspector.ScriptDetailsTimelineView.prototype.hidden):
(WebInspector.ScriptDetailsTimelineView.prototype.closed):
(WebInspector.ScriptDetailsTimelineView.prototype.get selectionPathComponents):
(WebInspector.ScriptDetailsTimelineView.prototype.reset):
(WebInspector.ScriptDetailsTimelineView.prototype.canShowContentViewForTreeElement):
(WebInspector.ScriptDetailsTimelineView.prototype.showContentViewForTreeElement):
(WebInspector.ScriptDetailsTimelineView.prototype.treeElementPathComponentSelected):
(WebInspector.ScriptDetailsTimelineView.prototype.treeElementSelected):
(WebInspector.ScriptDetailsTimelineView.prototype.dataGridNodeForTreeElement):
(WebInspector.ScriptDetailsTimelineView.prototype.populateProfileNodeTreeElement):
(WebInspector.ScriptDetailsTimelineView.prototype.layout):
(WebInspector.ScriptDetailsTimelineView.prototype._processPendingRecords):
(WebInspector.ScriptDetailsTimelineView.prototype._scriptTimelineRecordAdded):
(WebInspector.ScriptDetailsTimelineView.prototype._scriptTimelineRecordRefreshed):
(WebInspector.ScriptDetailsTimelineView.prototype._dataGridFiltersDidChange):
(WebInspector.ScriptDetailsTimelineView.prototype._dataGridNodeSelected):
* UserInterface/Views/ScriptProfileTimelineView.js: Added.
(WebInspector.ScriptProfileTimelineView):
(WebInspector.ScriptProfileTimelineView.prototype.closed):
(WebInspector.ScriptProfileTimelineView.prototype.get navigationItems):
(WebInspector.ScriptProfileTimelineView.prototype.get selectionPathComponents):
(WebInspector.ScriptProfileTimelineView.prototype.layout):
(WebInspector.ScriptProfileTimelineView.prototype._callingContextTreeForOrientation):
(WebInspector.ScriptProfileTimelineView.prototype._profileViewSelectionPathComponentsDidChange):
(WebInspector.ScriptProfileTimelineView.prototype._scriptTimelineRecordRefreshed):
(WebInspector.ScriptProfileTimelineView.prototype._updateProfileOrientationButtonItem):
(WebInspector.ScriptProfileTimelineView.prototype._toggleProfileOrientation):
(WebInspector.ScriptProfileTimelineView.prototype._updateClearFocusNodesButtonItem):
(WebInspector.ScriptProfileTimelineView.prototype._clearFocusNodes):
The two TimelineViews.

* UserInterface/Views/TimelineRecordingContentView.js:
(WebInspector.TimelineRecordingContentView.prototype.get currentTimelineView):
(WebInspector.TimelineRecordingContentView.prototype.contentBrowserTreeElementForRepresentedObject):
The timeline content browser may now hold a ClusterContentView. It is not exactly a TimelineView,
but it holds TimelineViews, so treat it like one. Assume the ClusterContentView will add its own
path components.

(WebInspector.TimelineRecordingContentView.prototype._instrumentAdded):
Add extra information other than the sidebar to TimelineViews. The ProfileView looks
at the recording for the calling context trees.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@197619 268f45cc-cd09-0410-ab3c-d52691b4dbfc

23 files changed:
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Base/Utilities.js
Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js
Source/WebInspectorUI/UserInterface/Main.html
Source/WebInspectorUI/UserInterface/Models/CallingContextTree.js
Source/WebInspectorUI/UserInterface/Models/SourceCodeLocation.js
Source/WebInspectorUI/UserInterface/Models/TimelineRecording.js
Source/WebInspectorUI/UserInterface/Protocol/InspectorFrontendAPI.js
Source/WebInspectorUI/UserInterface/Test.html
Source/WebInspectorUI/UserInterface/Views/ContentView.js
Source/WebInspectorUI/UserInterface/Views/DataGrid.js
Source/WebInspectorUI/UserInterface/Views/PathComponentIcons.css
Source/WebInspectorUI/UserInterface/Views/ProfileDataGridNode.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/ProfileDataGridTree.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/ProfileView.css [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/ProfileView.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/ScriptClusterTimelineView.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/ScriptDetailsTimelineView.css [moved from Source/WebInspectorUI/UserInterface/Views/ScriptTimelineView.css with 100% similarity]
Source/WebInspectorUI/UserInterface/Views/ScriptDetailsTimelineView.js [moved from Source/WebInspectorUI/UserInterface/Views/ScriptTimelineView.js with 98% similarity]
Source/WebInspectorUI/UserInterface/Views/ScriptProfileTimelineView.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/TimelineRecordingContentView.js
Source/WebInspectorUI/UserInterface/Views/TimelineView.js

index 37bc08c..4ea2367 100644 (file)
@@ -1,3 +1,264 @@
+2016-03-05  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: Add back support for a heavy / bottom up profile view
+        https://bugs.webkit.org/show_bug.cgi?id=140578
+        <rdar://problem/19506794>
+
+        Reviewed by Timothy Hatcher.
+
+        * Localizations/en.lproj/localizedStrings.js:
+        * UserInterface/Main.html:
+        New strings and resources.
+
+        * UserInterface/Base/Utilities.js:
+        (Number.secondsToMillisecondsString):
+        Helper for providing a consistent milliseconds string used in profiles.
+
+        * UserInterface/Controllers/TimelineManager.js:
+        (WebInspector.TimelineManager.prototype.scriptProfilerTrackingCompleted):
+        The calling context tree should be stored on a Recording, not on the global
+        TimelineManager. Also create two trees, one top down and one bottom up.
+
+        * UserInterface/Models/CallingContextTree.js:
+        (WebInspector.CallingContextTree):
+        (WebInspector.CallingContextTree.prototype.get type):
+        (WebInspector.CallingContextTree.prototype.get totalExecutionTime):
+        (WebInspector.CallingContextTree.prototype.reset):
+        (WebInspector.CallingContextTree.prototype.numberOfSamplesInTimeRange):
+        (WebInspector.CallingContextTree.prototype.increaseExecutionTime):
+        Give a CallingContextTree a type (TopDown / BottomUp) and some getters.
+
+        (WebInspector.CallingContextTree.prototype.updateTreeWithStackTrace):
+        Build a bottom up or top down tree from samples.
+
+        (WebInspector.CallingContextTree.prototype.forEachChild):
+        Allow iterating from the root.
+
+        (WebInspector.CCTNode):
+        (WebInspector.CCTNode.prototype.hasChildrenInTimeRange):
+        (WebInspector.CCTNode.prototype.numberOfLeafTimestamps):
+        (WebInspector.CCTNode.prototype.addTimestampAndExpressionLocation):
+        (WebInspector.CCTNode.prototype.equals):
+        (WebInspector.CCTNode.prototype.hasChildren): Deleted.
+        Give a CCTNode a list of leaf timestamps alongside the list of all timestamps.
+        Leaf timestamps will count as "self time" in a profile view.
+
+        * UserInterface/Models/SourceCodeLocation.js:
+        (WebInspector.SourceCodeLocation.prototype._locationString):
+        Nobody was using "ColumnStyle.Hidden" so repurpose it to be even simpler.
+
+        * UserInterface/Models/TimelineRecording.js:
+        (WebInspector.TimelineRecording):
+        (WebInspector.TimelineRecording.prototype.get topDownCallingContextTree):
+        (WebInspector.TimelineRecording.prototype.get bottomUpCallingContextTree):
+        (WebInspector.TimelineRecording.prototype.reset):
+        Store the two types of calling context trees and allow reseting them.
+
+        * UserInterface/Protocol/InspectorFrontendAPI.js:
+        (InspectorFrontendAPI.contextMenuItemSelected):
+        Helper for debugging uncaught exceptions in context menus.
+
+        * UserInterface/Views/ContentView.js:
+        (WebInspector.ContentView.createFromRepresentedObject):
+        (WebInspector.ContentView.isViewable):
+        A ScriptTimeline now has a cluster view.
+        A CallingContextTree now has a ProfileView.
+
+        * UserInterface/Views/DataGrid.js:
+        (WebInspector.DataGrid.prototype.insertChild):
+        (WebInspector.DataGrid.prototype._contextMenuInDataTable):
+        (WebInspector.DataGridNode.prototype.refreshRecursively):
+        (WebInspector.DataGridNode.prototype.elementWithColumnIdentifier):
+        (WebInspector.DataGridNode.prototype.forEachImmediateChild):
+        (WebInspector.DataGridNode.prototype.forEachChildInSubtree):
+        (WebInspector.DataGridNode.prototype.isInSubtreeOfNode):
+        Provide some helpers for iterating DataGridNodes, useful when the
+        actual DataGrid comes from a DataGridTree.
+
+        (WebInspector.DataGridNode.prototype.select):
+        (WebInspector.DataGridNode.prototype.deselect):
+        The indent width of DataGridNodes was not getting reset when a
+        node was removed and re-added to a tree due to a cached padding.
+
+        (WebInspector.DataGridNode.prototype.appendContextMenuItems):
+        Allow DataGridNodes to provide context menu items by overriding this method.
+
+        * UserInterface/Views/PathComponentIcons.css:
+        (.function-icon .icon):
+        (.native-icon .icon):
+        (.program-icon .icon):
+        Icons for profile nodes in path components.
+
+        * UserInterface/Views/ProfileDataGridNode.js: Added.
+        (WebInspector.ProfileDataGridNode):
+        (WebInspector.ProfileDataGridNode.prototype.get node):
+        (WebInspector.ProfileDataGridNode.prototype.displayName):
+        (WebInspector.ProfileDataGridNode.prototype.iconClassName):
+        (WebInspector.ProfileDataGridNode.prototype.get data):
+        (WebInspector.ProfileDataGridNode.prototype.createCellContent):
+        (WebInspector.ProfileDataGridNode.prototype.sort):
+        (WebInspector.ProfileDataGridNode.prototype.refresh):
+        (WebInspector.ProfileDataGridNode.prototype.appendContextMenuItems):
+        (WebInspector.ProfileDataGridNode.prototype._updateChildrenForModifiers):
+        (WebInspector.ProfileDataGridNode.prototype._recalculateData):
+        (WebInspector.ProfileDataGridNode.prototype._totalTimeContent):
+        (WebInspector.ProfileDataGridNode.prototype._displayContent):
+        (WebInspector.ProfileDataGridNode.prototype._populate):
+        CCTNode DataGridNode. A row in the ProfileDataGridTree. Handles
+        tree modifiers like charge to caller.
+
+        * UserInterface/Views/ProfileDataGridTree.js: Added.
+        (WebInspector.ProfileDataGridTree):
+        (WebInspector.ProfileDataGridTree.buildSortComparator):
+        (WebInspector.ProfileDataGridTree.prototype.get callingContextTree):
+        (WebInspector.ProfileDataGridTree.prototype.get sampleInterval):
+        (WebInspector.ProfileDataGridTree.prototype.get focusNodes):
+        (WebInspector.ProfileDataGridTree.prototype.get currentFocusNode):
+        (WebInspector.ProfileDataGridTree.prototype.get modifiers):
+        (WebInspector.ProfileDataGridTree.prototype.get startTime):
+        (WebInspector.ProfileDataGridTree.prototype.get endTime):
+        (WebInspector.ProfileDataGridTree.prototype.get numberOfSamples):
+        (WebInspector.ProfileDataGridTree.prototype.get children):
+        (WebInspector.ProfileDataGridTree.prototype.appendChild):
+        (WebInspector.ProfileDataGridTree.prototype.insertChild):
+        (WebInspector.ProfileDataGridTree.prototype.removeChildren):
+        (WebInspector.ProfileDataGridTree.prototype.set sortComparator):
+        (WebInspector.ProfileDataGridTree.prototype.sort):
+        (WebInspector.ProfileDataGridTree.prototype.refresh):
+        (WebInspector.ProfileDataGridTree.prototype.addFocusNode):
+        (WebInspector.ProfileDataGridTree.prototype.rollbackFocusNode):
+        (WebInspector.ProfileDataGridTree.prototype.clearFocusNodes):
+        (WebInspector.ProfileDataGridTree.prototype.hasModifiers):
+        (WebInspector.ProfileDataGridTree.prototype.addModifier):
+        (WebInspector.ProfileDataGridTree.prototype.clearModifiers):
+        (WebInspector.ProfileDataGridTree.prototype._repopulate):
+        (WebInspector.ProfileDataGridTree.prototype._focusChanged):
+        (WebInspector.ProfileDataGridTree.prototype._updateCurrentFocusDetails):
+        (WebInspector.ProfileDataGridTree.prototype._restoreFocusedNodeToOriginalParent):
+        (WebInspector.ProfileDataGridTree.prototype._modifiersChanged):
+        Start of a DataGridTree for a CallingContextTree.
+        Contains special logic for focused nodes and modifiers.
+
+        * UserInterface/Views/ProfileView.css: Added.
+        (.profile > .data-grid):
+        (.profile > .data-grid th):
+        (.profile > .data-grid td .icon):
+        (.profile > .data-grid td .percentage):
+        (.profile > .data-grid td .location):
+        (.profile > .data-grid:matches(:focus, .force-focus) tr.selected td .location):
+        (.profile > .data-grid td .icon.function-icon):
+        (.profile > .data-grid td .icon.native-icon):
+        (.profile > .data-grid td .icon.program-icon):
+        (.profile > .data-grid tr:matches(.selected, :hover) .go-to-arrow):
+        (.profile > .data-grid td.function-column):
+        (.profile > .data-grid td .guidance):
+        (.profile > .data-grid td .guidance.hover):
+        (.profile > .data-grid td .guidance.base):
+        (.profile > .data-grid tr:not(.expanded) td .guidance.base):
+        (.profile > .data-grid tr.expanded td .guidance.base):
+        * UserInterface/Views/ProfileView.js: Added.
+        (WebInspector.ProfileView):
+        (WebInspector.ProfileView.prototype.get callingContextTree):
+        (WebInspector.ProfileView.prototype.get startTime):
+        (WebInspector.ProfileView.prototype.get endTime):
+        (WebInspector.ProfileView.prototype.setStartAndEndTime):
+        (WebInspector.ProfileView.prototype.hasFocusNodes):
+        (WebInspector.ProfileView.prototype.clearFocusNodes):
+        (WebInspector.ProfileView.prototype.get selectionPathComponents):
+        (WebInspector.ProfileView.prototype._recreate):
+        (WebInspector.ProfileView.prototype._repopulateDataGridFromTree):
+        (WebInspector.ProfileView.prototype._pathComponentClicked):
+        (WebInspector.ProfileView.prototype._dataGridTreeFocusChanged):
+        (WebInspector.ProfileView.prototype._dataGridTreeModifiersChanged):
+        (WebInspector.ProfileView.prototype._dataGridSortChanged):
+        (WebInspector.ProfileView.prototype._dataGridNodeSelected):
+        (WebInspector.ProfileView.prototype._dataGridNodeExpanded):
+        (WebInspector.ProfileView.prototype._mouseOverDataGrid):
+        (WebInspector.ProfileView.prototype._mouseLeaveDataGrid):
+        (WebInspector.ProfileView.prototype._guidanceElementKey):
+        (WebInspector.ProfileView.prototype._removeGuidanceElement):
+        (WebInspector.ProfileView.prototype._appendGuidanceElement):
+        ProfileView holds a data grid which is populated from the data grid tree.
+        Special handing for guidance markers when hovering / selecting parts of the tree.
+
+        * UserInterface/Views/ScriptClusterTimelineView.js: Added.
+        (WebInspector.ScriptClusterTimelineView.createPathComponent):
+        (WebInspector.ScriptClusterTimelineView):
+        (WebInspector.ScriptClusterTimelineView.prototype.get zeroTime):
+        (WebInspector.ScriptClusterTimelineView.prototype.set zeroTime):
+        (WebInspector.ScriptClusterTimelineView.prototype.get startTime):
+        (WebInspector.ScriptClusterTimelineView.prototype.set startTime):
+        (WebInspector.ScriptClusterTimelineView.prototype.get endTime):
+        (WebInspector.ScriptClusterTimelineView.prototype.set endTime):
+        (WebInspector.ScriptClusterTimelineView.prototype.get currentTime):
+        (WebInspector.ScriptClusterTimelineView.prototype.set currentTime):
+        (WebInspector.ScriptClusterTimelineView.prototype.get navigationSidebarTreeOutline):
+        (WebInspector.ScriptClusterTimelineView.prototype.reset):
+        (WebInspector.ScriptClusterTimelineView.prototype.filterDidChange):
+        (WebInspector.ScriptClusterTimelineView.prototype.matchTreeElementAgainstCustomFilters):
+        (WebInspector.ScriptClusterTimelineView.prototype.get detailsContentView):
+        (WebInspector.ScriptClusterTimelineView.prototype.get profileContentView):
+        (WebInspector.ScriptClusterTimelineView.prototype.get selectionPathComponents):
+        (WebInspector.ScriptClusterTimelineView.prototype.saveToCookie):
+        (WebInspector.ScriptClusterTimelineView.prototype.restoreFromCookie):
+        (WebInspector.ScriptClusterTimelineView.prototype.showDetails):
+        (WebInspector.ScriptClusterTimelineView.prototype.showProfile):
+        (WebInspector.ScriptClusterTimelineView.prototype._pathComponentForContentView):
+        (WebInspector.ScriptClusterTimelineView.prototype._identifierForContentView):
+        (WebInspector.ScriptClusterTimelineView.prototype._showContentViewForIdentifier):
+        (WebInspector.ScriptClusterTimelineView.prototype._pathComponentSelected):
+        (WebInspector.ScriptClusterTimelineView.prototype._scriptClusterViewCurrentContentViewDidChange):
+        Script Timeline ClusterContentView. Toggle between the normal "Details" data grid
+        and the new "Call Tree" profile view. Currently the recording expects child content
+        views to be TimelineViews, this ClusterContentView forwards TimelineView relevant
+        methods to the real TimelineView children.
+
+        * UserInterface/Views/ScriptDetailsTimelineView.js: Renamed from Source/WebInspectorUI/UserInterface/Views/ScriptTimelineView.js.
+        (WebInspector.ScriptDetailsTimelineView):
+        (WebInspector.ScriptDetailsTimelineView.prototype.get navigationSidebarTreeOutlineLabel):
+        (WebInspector.ScriptDetailsTimelineView.prototype.shown):
+        (WebInspector.ScriptDetailsTimelineView.prototype.hidden):
+        (WebInspector.ScriptDetailsTimelineView.prototype.closed):
+        (WebInspector.ScriptDetailsTimelineView.prototype.get selectionPathComponents):
+        (WebInspector.ScriptDetailsTimelineView.prototype.reset):
+        (WebInspector.ScriptDetailsTimelineView.prototype.canShowContentViewForTreeElement):
+        (WebInspector.ScriptDetailsTimelineView.prototype.showContentViewForTreeElement):
+        (WebInspector.ScriptDetailsTimelineView.prototype.treeElementPathComponentSelected):
+        (WebInspector.ScriptDetailsTimelineView.prototype.treeElementSelected):
+        (WebInspector.ScriptDetailsTimelineView.prototype.dataGridNodeForTreeElement):
+        (WebInspector.ScriptDetailsTimelineView.prototype.populateProfileNodeTreeElement):
+        (WebInspector.ScriptDetailsTimelineView.prototype.layout):
+        (WebInspector.ScriptDetailsTimelineView.prototype._processPendingRecords):
+        (WebInspector.ScriptDetailsTimelineView.prototype._scriptTimelineRecordAdded):
+        (WebInspector.ScriptDetailsTimelineView.prototype._scriptTimelineRecordRefreshed):
+        (WebInspector.ScriptDetailsTimelineView.prototype._dataGridFiltersDidChange):
+        (WebInspector.ScriptDetailsTimelineView.prototype._dataGridNodeSelected):
+        * UserInterface/Views/ScriptProfileTimelineView.js: Added.
+        (WebInspector.ScriptProfileTimelineView):
+        (WebInspector.ScriptProfileTimelineView.prototype.closed):
+        (WebInspector.ScriptProfileTimelineView.prototype.get navigationItems):
+        (WebInspector.ScriptProfileTimelineView.prototype.get selectionPathComponents):
+        (WebInspector.ScriptProfileTimelineView.prototype.layout):
+        (WebInspector.ScriptProfileTimelineView.prototype._callingContextTreeForOrientation):
+        (WebInspector.ScriptProfileTimelineView.prototype._profileViewSelectionPathComponentsDidChange):
+        (WebInspector.ScriptProfileTimelineView.prototype._scriptTimelineRecordRefreshed):
+        (WebInspector.ScriptProfileTimelineView.prototype._updateProfileOrientationButtonItem):
+        (WebInspector.ScriptProfileTimelineView.prototype._toggleProfileOrientation):
+        (WebInspector.ScriptProfileTimelineView.prototype._updateClearFocusNodesButtonItem):
+        (WebInspector.ScriptProfileTimelineView.prototype._clearFocusNodes):
+        The two TimelineViews.
+
+        * UserInterface/Views/TimelineRecordingContentView.js:
+        (WebInspector.TimelineRecordingContentView.prototype.get currentTimelineView):
+        (WebInspector.TimelineRecordingContentView.prototype.contentBrowserTreeElementForRepresentedObject):
+        The timeline content browser may now hold a ClusterContentView. It is not exactly a TimelineView,
+        but it holds TimelineViews, so treat it like one. Assume the ClusterContentView will add its own
+        path components.
+
+        (WebInspector.TimelineRecordingContentView.prototype._instrumentAdded):
+        Add extra information other than the sidebar to TimelineViews. The ProfileView looks
+        at the recording for the calling context trees.
+
 2016-03-03  Timothy Hatcher  <timothy@apple.com>
 
         Web Inspector: Temporarily hide the TimelineSidebarPanel
index 1e42e37..b70328f 100644 (file)
Binary files a/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js and b/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js differ
index b0656e5..8e41815 100644 (file)
@@ -875,6 +875,27 @@ Object.defineProperty(Number, "constrain",
     }
 });
 
+Object.defineProperty(Number, "percentageString",
+{
+    value: function(percent, precision = 1)
+    {
+        console.assert(percent >= 0 && percent <= 100);
+        return percent.toFixed(precision) + "%";
+    }
+});
+
+Object.defineProperty(Number, "secondsToMillisecondsString",
+{
+    value: function(seconds, higherResolution)
+    {
+        let ms = seconds * 1000;
+
+        if (higherResolution)
+            return WebInspector.UIString("%.2fms").format(ms);
+        return WebInspector.UIString("%.1fms").format(ms);
+    }
+});
+
 Object.defineProperty(Number, "secondsToString",
 {
     value: function(seconds, higherResolution)
index c9da693..ddf9d7f 100644 (file)
@@ -45,8 +45,6 @@ WebInspector.TimelineManager = class TimelineManager extends WebInspector.Object
         this._webTimelineScriptRecordsExpectingScriptProfilerEvents = null;
         this._scriptProfilerRecords = null;
 
-        this._callingContextTree = null;
-
         this.reset();
     }
 
@@ -712,17 +710,25 @@ WebInspector.TimelineManager = class TimelineManager extends WebInspector.Object
         console.assert(!this._webTimelineScriptRecordsExpectingScriptProfilerEvents || this._scriptProfilerRecords.length >= this._webTimelineScriptRecordsExpectingScriptProfilerEvents.length);
 
         if (samples) {
-            if (!this._callingContextTree)
-                this._callingContextTree = new WebInspector.CallingContextTree;
-
             // Associate the stackTraces with the ScriptProfiler created records.
-            let stackTraces = samples.stackTraces;
-            for (let i = 0; i < stackTraces.length; i++)
-                this._callingContextTree.updateTreeWithStackTrace(stackTraces[i]);
+            let {totalTime: totalExecutionTime, stackTraces} = samples;
+            let topDownCallingContextTree = this.activeRecording.topDownCallingContextTree;
+            let bottomUpCallingContextTree = this.activeRecording.bottomUpCallingContextTree;
+
+            topDownCallingContextTree.increaseExecutionTime(totalExecutionTime);
+            bottomUpCallingContextTree.increaseExecutionTime(totalExecutionTime);
+
+            for (let i = 0; i < stackTraces.length; i++) {
+                topDownCallingContextTree.updateTreeWithStackTrace(stackTraces[i]);
+                bottomUpCallingContextTree.updateTreeWithStackTrace(stackTraces[i]);
+            }
 
+            // FIXME: This transformation should not be needed after introducing ProfileView.
+            // Once we eliminate ProfileNodeTreeElements and ProfileNodeDataGridNodes.
+            // <https://webkit.org/b/154973> Web Inspector: Timelines UI redesign: Remove TimelineSidebarPanel
             for (let i = 0; i < this._scriptProfilerRecords.length; ++i) {
                 let record = this._scriptProfilerRecords[i];
-                record.profilePayload = this._callingContextTree.toCPUProfilePayload(record.startTime, record.endTime);
+                record.profilePayload = topDownCallingContextTree.toCPUProfilePayload(record.startTime, record.endTime);
             }
         }
 
index 5579540..d67a75c 100644 (file)
     <link rel="stylesheet" href="Views/Popover.css">
     <link rel="stylesheet" href="Views/ProbeDetailsSidebarPanel.css">
     <link rel="stylesheet" href="Views/ProbeSetDataGrid.css">
+    <link rel="stylesheet" href="Views/ProfileView.css">
     <link rel="stylesheet" href="Views/QuickConsole.css">
     <link rel="stylesheet" href="Views/RadioButtonNavigationItem.css">
     <link rel="stylesheet" href="Views/RenderingFrameTimelineOverviewGraph.css">
     <link rel="stylesheet" href="Views/ScopeChainDetailsSidebarPanel.css">
     <link rel="stylesheet" href="Views/ScopeRadioButtonNavigationItem.css">
     <link rel="stylesheet" href="Views/ScriptContentView.css">
+    <link rel="stylesheet" href="Views/ScriptDetailsTimelineView.css">
     <link rel="stylesheet" href="Views/ScriptTimelineOverviewGraph.css">
-    <link rel="stylesheet" href="Views/ScriptTimelineView.css">
     <link rel="stylesheet" href="Views/SearchBar.css">
     <link rel="stylesheet" href="Views/SearchIcons.css">
     <link rel="stylesheet" href="Views/SearchSidebarPanel.css">
     <script src="Views/ProbeSetDataGrid.js"></script>
     <script src="Views/ProbeSetDataGridNode.js"></script>
     <script src="Views/ProbeSetDetailsSection.js"></script>
+    <script src="Views/ProfileDataGridNode.js"></script>
+    <script src="Views/ProfileDataGridTree.js"></script>
     <script src="Views/ProfileNodeDataGridNode.js"></script>
     <script src="Views/ProfileNodeTreeElement.js"></script>
+    <script src="Views/ProfileView.js"></script>
     <script src="Views/QuickConsole.js"></script>
     <script src="Views/QuickConsoleNavigationBar.js"></script>
     <script src="Views/RadioButtonNavigationItem.js"></script>
     <script src="Views/ScopeBarItem.js"></script>
     <script src="Views/ScopeChainDetailsSidebarPanel.js"></script>
     <script src="Views/ScopeRadioButtonNavigationItem.js"></script>
+    <script src="Views/ScriptClusterTimelineView.js"></script>
     <script src="Views/ScriptContentView.js"></script>
+    <script src="Views/ScriptDetailsTimelineView.js"></script>
+    <script src="Views/ScriptProfileTimelineView.js"></script>
     <script src="Views/ScriptTimelineDataGrid.js"></script>
     <script src="Views/ScriptTimelineDataGridNode.js"></script>
     <script src="Views/ScriptTimelineOverviewGraph.js"></script>
-    <script src="Views/ScriptTimelineView.js"></script>
     <script src="Views/ScriptTreeElement.js"></script>
     <script src="Views/SearchBar.js"></script>
     <script src="Views/SearchResultTreeElement.js"></script>
index 7416a09..ce8ec34 100644 (file)
 
 WebInspector.CallingContextTree = class CallingContextTree extends WebInspector.Object
 {
-    constructor()
+    constructor(type)
     {
         super();
 
-        this._root = new WebInspector.CCTNode(-1, -1, -1, "<root>", null);
-        this._totalNumberOfSamples = 0;
+        this._type = type || WebInspector.CallingContextTree.Type.TopDown;
+
+        this.reset();
     }
 
     // Public
 
+    get type() { return this._type; }
+    get totalExecutionTime() { return this._totalExecutionTime; }
     get totalNumberOfSamples() { return this._totalNumberOfSamples; }
-    
+
+    reset()
+    {
+        this._root = new WebInspector.CCTNode(-1, -1, -1, "<root>", null);
+        this._totalExecutionTime = 0;
+        this._totalNumberOfSamples = 0;
+    }
+
+    numberOfSamplesInTimeRange(startTime, endTime)
+    {
+        return this._root.filteredTimestamps(startTime, endTime).length;
+    }
+
+    increaseExecutionTime(executionTime)
+    {
+        this._totalExecutionTime += executionTime;
+    }
+
     updateTreeWithStackTrace({timestamp, stackFrames})
     {
         this._totalNumberOfSamples++;
+
         let node = this._root;
         node.addTimestampAndExpressionLocation(timestamp, null);
-        for (let i = stackFrames.length; i--; ) {
-            let stackFrame = stackFrames[i];
-            node = node.findOrMakeChild(stackFrame);
-            node.addTimestampAndExpressionLocation(timestamp, stackFrame.expressionLocation || null);
+
+        if (this._type === WebInspector.CallingContextTree.Type.TopDown) {
+            for (let i = stackFrames.length; i--; ) {
+                let stackFrame = stackFrames[i];
+                node = node.findOrMakeChild(stackFrame);
+                node.addTimestampAndExpressionLocation(timestamp, stackFrame.expressionLocation || null, i === 0);
+            }
+        } else {
+            for (let i = 0; i < stackFrames.length; ++i) {
+                let stackFrame = stackFrames[i];
+                node = node.findOrMakeChild(stackFrame);
+                node.addTimestampAndExpressionLocation(timestamp, stackFrame.expressionLocation || null, i === 0);
+            }
         }
     }
 
@@ -53,7 +83,7 @@ WebInspector.CallingContextTree = class CallingContextTree extends WebInspector.
     {
         let cpuProfile = {};
         let roots = [];
-        let numSamplesInTimeRange = this._root.filteredTimestamps(startTime, endTime).length;
+        let numSamplesInTimeRange = this.numberOfSamplesInTimeRange(startTime, endTime);
 
         this._root.forEachChild((child) => {
             if (child.hasStackTraceInTimeRange(startTime, endTime))
@@ -64,6 +94,11 @@ WebInspector.CallingContextTree = class CallingContextTree extends WebInspector.
         return cpuProfile;
     }
 
+    forEachChild(callback)
+    {
+        this._root.forEachChild(callback);
+    }
+
     forEachNode(callback)
     {
         this._root.forEachNode(callback);
@@ -129,6 +164,7 @@ WebInspector.CCTNode = class CCTNode extends WebInspector.Object
         this._uid = WebInspector.CCTNode.__uid++;
 
         this._timestamps = [];
+        this._leafTimestamps = [];
         this._expressionLocations = {}; // Keys are "line:column" strings. Values are arrays of timestamps in sorted order.
     }
 
@@ -148,6 +184,16 @@ WebInspector.CCTNode = class CCTNode extends WebInspector.Object
     get uid() { return this._uid; }
     get url() { return this._url; }
 
+    hasChildrenInTimeRange(startTime, endTime)
+    {
+        for (let propertyName of Object.getOwnPropertyNames(this._children)) {
+            let child = this._children[propertyName];
+            if (child.hasStackTraceInTimeRange(startTime, endTime))
+                return true;
+        }
+        return false;
+    }
+    
     hasStackTraceInTimeRange(startTime, endTime)
     {
         console.assert(startTime <= endTime);
@@ -182,6 +228,20 @@ WebInspector.CCTNode = class CCTNode extends WebInspector.Object
         return result;
     }
 
+    numberOfLeafTimestamps(startTime, endTime)
+    {
+        let count = 0;
+        let lowerIndex = this._leafTimestamps.lowerBound(startTime);
+        for (let i = lowerIndex; i < this._leafTimestamps.length; ++i) {
+            let timestamp = this._leafTimestamps[i];
+            console.assert(startTime <= timestamp);
+            if (!(timestamp <= endTime))
+                break;
+            count++;
+        }
+        return count;
+    }
+
     hasChildren()
     {
         return !!Object.getOwnPropertyNames(this._children).length;
@@ -198,11 +258,14 @@ WebInspector.CCTNode = class CCTNode extends WebInspector.Object
         return node;
     }
 
-    addTimestampAndExpressionLocation(timestamp, expressionLocation)
+    addTimestampAndExpressionLocation(timestamp, expressionLocation, leaf)
     {
         console.assert(!this._timestamps.length || this._timestamps.lastValue <= timestamp, "Expected timestamps to be added in sorted, increasing, order.");
         this._timestamps.push(timestamp);
 
+        if (leaf)
+            this._leafTimestamps.push(timestamp);
+
         if (!expressionLocation)
             return;
 
@@ -231,6 +294,11 @@ WebInspector.CCTNode = class CCTNode extends WebInspector.Object
         });
     }
 
+    equals(other)
+    {
+        return WebInspector.CCTNode._hash(this) === WebInspector.CCTNode._hash(other);
+    }
+
     toCPUProfileNode(numSamples, startTime, endTime)
     {
         let children = [];
@@ -291,3 +359,7 @@ WebInspector.CCTNode = class CCTNode extends WebInspector.Object
 
 WebInspector.CCTNode.__uid = 0;
 
+WebInspector.CallingContextTree.Type = {
+    TopDown: Symbol("TopDown"),
+    BottomUp: Symbol("BottomUp"),
+};
index ebc0386..454c774 100644 (file)
@@ -321,11 +321,13 @@ WebInspector.SourceCodeLocation = class SourceCodeLocation extends WebInspector.
         nameStyle = nameStyle || WebInspector.SourceCodeLocation.NameStyle.Short;
         prefix = prefix || "";
 
-        var lineString = lineNumber + 1; // The user visible line number is 1-based.
+        let lineString = lineNumber + 1; // The user visible line number is 1-based.
         if (columnStyle === WebInspector.SourceCodeLocation.ColumnStyle.Shown && columnNumber > 0)
             lineString += ":" + (columnNumber + 1); // The user visible column number is 1-based.
         else if (columnStyle === WebInspector.SourceCodeLocation.ColumnStyle.OnlyIfLarge && columnNumber > WebInspector.SourceCodeLocation.LargeColumnNumber)
             lineString += ":" + (columnNumber + 1); // The user visible column number is 1-based.
+        else if (columnStyle === WebInspector.SourceCodeLocation.ColumnStyle.Hidden)
+            lineString = "";
 
         switch (nameStyle) {
         case WebInspector.SourceCodeLocation.NameStyle.None:
@@ -333,9 +335,12 @@ WebInspector.SourceCodeLocation = class SourceCodeLocation extends WebInspector.
 
         case WebInspector.SourceCodeLocation.NameStyle.Short:
         case WebInspector.SourceCodeLocation.NameStyle.Full:
-            const displayURL = sourceCode.displayURL;
-            const lineSuffix = displayURL ? ":" + lineString : WebInspector.UIString(" (line %s)").format(lineString);
-            return prefix + (nameStyle === WebInspector.SourceCodeLocation.NameStyle.Full && displayURL ? displayURL : sourceCode.displayName) + lineSuffix;
+            let displayURL = sourceCode.displayURL;
+            let name = nameStyle === WebInspector.SourceCodeLocation.NameStyle.Full && displayURL ? displayURL : sourceCode.displayName;
+            if (columnStyle === WebInspector.SourceCodeLocation.ColumnStyle.Hidden)
+                return prefix + name;
+            let lineSuffix = displayURL ? ":" + lineString : WebInspector.UIString(" (line %s)").format(lineString);
+            return  prefix + name + lineSuffix;
 
         default:
             console.error("Unknown nameStyle: " + nameStyle);
@@ -451,7 +456,7 @@ WebInspector.SourceCodeLocation.NameStyle = {
 };
 
 WebInspector.SourceCodeLocation.ColumnStyle = {
-    Hidden: "hidden",             // column numbers are not included.
+    Hidden: "hidden",             // line and column numbers are not included.
     OnlyIfLarge: "only-if-large", // column numbers greater than 80 are shown.
     Shown: "shown"                // non-zero column numbers are shown.
 };
index 8ab2a2d..60b33f2 100644 (file)
@@ -35,6 +35,8 @@ WebInspector.TimelineRecording = class TimelineRecording extends WebInspector.Ob
         this._capturing = false;
         this._readonly = false;
         this._instruments = instruments || [];
+        this._topDownCallingContextTree = new WebInspector.CallingContextTree(WebInspector.CallingContextTree.Type.TopDown);
+        this._bottomUpCallingContextTree = new WebInspector.CallingContextTree(WebInspector.CallingContextTree.Type.BottomUp);
 
         let timelines = [
             WebInspector.TimelineRecord.Type.Network,
@@ -100,6 +102,16 @@ WebInspector.TimelineRecording = class TimelineRecording extends WebInspector.Ob
         return this._endTime;
     }
 
+    get topDownCallingContextTree()
+    {
+        return this._topDownCallingContextTree;
+    }
+
+    get bottomUpCallingContextTree()
+    {
+        return this._bottomUpCallingContextTree;
+    }
+
     start()
     {
         console.assert(!this._capturing, "Attempted to start an already started session.");
@@ -156,6 +168,9 @@ WebInspector.TimelineRecording = class TimelineRecording extends WebInspector.Ob
         this._startTime = NaN;
         this._endTime = NaN;
 
+        this._topDownCallingContextTree.reset();
+        this._bottomUpCallingContextTree.reset();
+
         for (var timeline of this._timelines.values())
             timeline.reset(suppressEvents);
 
index 0c6ea61..e8f9bda 100644 (file)
@@ -101,7 +101,11 @@ InspectorFrontendAPI = {
 
     contextMenuItemSelected: function(id)
     {
-        WebInspector.ContextMenu.contextMenuItemSelected(id);
+        try {
+            WebInspector.ContextMenu.contextMenuItemSelected(id);
+        } catch (e) {
+            console.error("Uncaught exception in inspector page under contextMenuItemSelected", e);
+        }
     },
 
     contextMenuCleared: function()
index 58860e5..ea3a163 100644 (file)
     <script src="Models/CSSStyleDeclaration.js"></script>
     <script src="Models/CSSStyleSheet.js"></script>
     <script src="Models/CallFrame.js"></script>
+    <script src="Models/CallingContextTree.js"></script>
     <script src="Models/CollectionEntry.js"></script>
+    <script src="Models/CollectionEntryPreview.js"></script>
     <script src="Models/Color.js"></script>
-    <script src="Models/CookieStorageObject.js"></script>
     <script src="Models/ConsoleCommandResultMessage.js"></script>
     <script src="Models/ContentFlow.js"></script>
+    <script src="Models/CookieStorageObject.js"></script>
     <script src="Models/DOMNode.js"></script>
     <script src="Models/DOMNodeStyles.js"></script>
     <script src="Models/DOMStorageObject.js"></script>
     <script src="Models/DOMTree.js"></script>
-    <script src="Models/CollectionEntryPreview.js"></script>
     <script src="Models/ExecutionContext.js"></script>
     <script src="Models/ExecutionContextList.js"></script>
-    <script src="Models/Frame.js"></script>
     <script src="Models/FPSInstrument.js"></script>
+    <script src="Models/Frame.js"></script>
     <script src="Models/GarbageCollection.js"></script>
     <script src="Models/Geometry.js"></script>
     <script src="Models/IndexedDatabase.js"></script>
index 0224f9c..5a2041a 100644 (file)
@@ -66,7 +66,7 @@ WebInspector.ContentView = class ContentView extends WebInspector.View
                 return new WebInspector.LayoutTimelineView(representedObject, extraArguments);
 
             if (timelineType === WebInspector.TimelineRecord.Type.Script)
-                return new WebInspector.ScriptTimelineView(representedObject, extraArguments);
+                return new WebInspector.ScriptClusterTimelineView(representedObject, extraArguments);
 
             if (timelineType === WebInspector.TimelineRecord.Type.RenderingFrame)
                 return new WebInspector.RenderingFrameTimelineView(representedObject, extraArguments);
@@ -132,6 +132,9 @@ WebInspector.ContentView = class ContentView extends WebInspector.View
         if (representedObject instanceof WebInspector.ContentFlow)
             return new WebInspector.ContentFlowDOMTreeContentView(representedObject, extraArguments);
 
+        if (representedObject instanceof WebInspector.CallingContextTree)
+            return new WebInspector.ProfileView(representedObject, extraArguments);
+
         if (typeof representedObject === "string" || representedObject instanceof String)
             return new WebInspector.TextContentView(representedObject, extraArguments);
 
@@ -226,6 +229,8 @@ WebInspector.ContentView = class ContentView extends WebInspector.View
             return true;
         if (representedObject instanceof WebInspector.ContentFlow)
             return true;
+        if (representedObject instanceof WebInspector.CallingContextTree)
+            return true;
         if (typeof representedObject === "string" || representedObject instanceof String)
             return true;
         return false;
index 41b28c4..9e4eba7 100644 (file)
@@ -790,6 +790,7 @@ WebInspector.DataGrid = class DataGrid extends WebInspector.View
         delete child._depth;
         delete child._revealed;
         delete child._attached;
+        delete child._leftPadding;
         child._shouldRefreshChildren = true;
 
         var current = child.children[0];
@@ -798,6 +799,7 @@ WebInspector.DataGrid = class DataGrid extends WebInspector.View
             delete current._depth;
             delete current._revealed;
             delete current._attached;
+            delete current._leftPadding;
             current._shouldRefreshChildren = true;
             current = current.traverseNextNode(false, child, true);
         }
@@ -1206,6 +1208,10 @@ WebInspector.DataGrid = class DataGrid extends WebInspector.View
         let contextMenu = WebInspector.ContextMenu.createFromEvent(event);
 
         let gridNode = this.dataGridNodeFromNode(event.target);
+
+        if (gridNode)
+            gridNode.appendContextMenuItems(contextMenu);
+
         if (this.dataGrid._refreshCallback && (!gridNode || gridNode !== this.placeholderNode))
             contextMenu.appendItem(WebInspector.UIString("Refresh"), this._refreshCallback.bind(this));
 
@@ -1223,6 +1229,7 @@ WebInspector.DataGrid = class DataGrid extends WebInspector.View
                     contextMenu.appendItem(WebInspector.UIString("Edit ā€œ%sā€").format(columnTitle), this._startEditing.bind(this, event.target));
                 }
             }
+
             if (this.dataGrid._deleteCallback && gridNode !== this.placeholderNode)
                 contextMenu.appendItem(WebInspector.UIString("Delete"), this._deleteCallback.bind(this, gridNode));
         }
@@ -1688,6 +1695,12 @@ WebInspector.DataGridNode = class DataGridNode extends WebInspector.Object
         this.createCells();
     }
 
+    refreshRecursively()
+    {
+        this.refresh();
+        this.forEachChildInSubtree((node) => node.refresh());
+    }
+
     updateLayout()
     {
         // Implemented by subclasses if needed.
@@ -1733,11 +1746,14 @@ WebInspector.DataGridNode = class DataGridNode extends WebInspector.Object
 
     elementWithColumnIdentifier(columnIdentifier)
     {
-        var index = this.dataGrid.orderedColumns.indexOf(columnIdentifier);
+        if (!this.dataGrid)
+            return null;
+
+        let index = this.dataGrid.orderedColumns.indexOf(columnIdentifier);
         if (index === -1)
             return null;
 
-        return this._element.children[index];
+        return this.element.children[index];
     }
 
     // Share these functions with DataGrid. They are written to work with a DataGridNode this object.
@@ -1842,6 +1858,32 @@ WebInspector.DataGridNode = class DataGridNode extends WebInspector.Object
         }
     }
 
+    forEachImmediateChild(callback)
+    {
+        for (let node of this.children)
+            callback(node);
+    }
+
+    forEachChildInSubtree(callback)
+    {
+        let node = this.traverseNextNode(false, this, true);
+        while (node) {
+            callback(node);
+            node = node.traverseNextNode(false, this, true);
+        }
+    }
+
+    isInSubtreeOfNode(baseNode)
+    {
+        let node = baseNode;
+        while (node) {
+            if (node === this)
+                return true;
+            node = node.traverseNextNode(false, baseNode, true);
+        }
+        return false;
+    }
+
     reveal()
     {
         var currentAncestor = this.parent;
@@ -1861,8 +1903,9 @@ WebInspector.DataGridNode = class DataGridNode extends WebInspector.Object
         if (!this.dataGrid || !this.selectable || this.selected)
             return;
 
-        if (this.dataGrid.selectedNode)
-            this.dataGrid.selectedNode.deselect(true);
+        let oldSelectedNode = this.dataGrid.selectedNode;
+        if (oldSelectedNode)
+            oldSelectedNode.deselect(true);
 
         this._selected = true;
         this.dataGrid.selectedNode = this;
@@ -1871,7 +1914,7 @@ WebInspector.DataGridNode = class DataGridNode extends WebInspector.Object
             this._element.classList.add("selected");
 
         if (!suppressSelectedEvent)
-            this.dataGrid.dispatchEventToListeners(WebInspector.DataGrid.Event.SelectedNodeChanged);
+            this.dataGrid.dispatchEventToListeners(WebInspector.DataGrid.Event.SelectedNodeChanged, {oldSelectedNode});
     }
 
     revealAndSelect()
@@ -1892,7 +1935,7 @@ WebInspector.DataGridNode = class DataGridNode extends WebInspector.Object
             this._element.classList.remove("selected");
 
         if (!suppressDeselectedEvent)
-            this.dataGrid.dispatchEventToListeners(WebInspector.DataGrid.Event.SelectedNodeChanged);
+            this.dataGrid.dispatchEventToListeners(WebInspector.DataGrid.Event.SelectedNodeChanged, {oldSelectedNode: this});
     }
 
     traverseNextNode(skipHidden, stayWithin, dontPopulate, info)
@@ -2033,6 +2076,12 @@ WebInspector.DataGridNode = class DataGridNode extends WebInspector.Object
 
         this._savedPosition = null;
     }
+
+    appendContextMenuItems(contextMenu)
+    {
+        // Subclasses may override
+        return null;
+    }
 };
 
 // Used to create a new table row when entering new data by editing cells.
index 5e6891f..cafc424 100644 (file)
 .dom-node-icon .icon {
     content: url(../Images/DOMNode.svg);
 }
+
+.function-icon .icon {
+    content: url(../Images/Function.svg);
+}
+
+.native-icon .icon {
+    content: url(../Images/Native.svg);
+}
+
+.program-icon .icon {
+    content: url(../Images/Program.svg);
+}
diff --git a/Source/WebInspectorUI/UserInterface/Views/ProfileDataGridNode.js b/Source/WebInspectorUI/UserInterface/Views/ProfileDataGridNode.js
new file mode 100644 (file)
index 0000000..a6e80f5
--- /dev/null
@@ -0,0 +1,254 @@
+/*
+* 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.
+*/
+
+WebInspector.ProfileDataGridNode = class ProfileDataGridNode extends WebInspector.DataGridNode
+{
+    constructor(cctNode, tree)
+    {
+        super(cctNode, false);
+
+        this._node = cctNode;
+        this._tree = tree;
+
+        this._populated = false;
+        this._childrenToChargeToSelf = new Set;
+
+        // FIXME: Make profile data grid nodes copyable.
+        this.copyable = false;
+
+        this.addEventListener("populate", this._populate, this);
+
+        this._updateChildrenForModifiers();
+        this._recalculateData();
+    }
+
+    // Public
+
+    get node() { return this._node; }
+
+    displayName()
+    {
+        let title = this._node.name;
+        if (!title)
+            return WebInspector.UIString("(anonymous function)");
+        if (title === "(program)")
+            return WebInspector.UIString("(program)");
+        return title;
+    }
+
+    iconClassName()
+    {
+        let script = WebInspector.debuggerManager.scriptForIdentifier(this._node.sourceID);
+        if (!script || !script.url)
+            return "native-icon";
+        if (this._node.name === "(program)")
+            return "program-icon";
+        return "function-icon";
+    }
+
+    // Protected
+
+    get data()
+    {
+        return this._data;
+    }
+
+    createCellContent(columnIdentifier, cell)
+    {
+        switch (columnIdentifier) {
+        case "totalTime":
+            return this._totalTimeContent();
+        case "selfTime":
+            return Number.secondsToMillisecondsString(this._data.selfTime);
+        case "function":
+            return this._displayContent();
+        }
+
+        return super.createCellContent(columnIdentifier, cell);
+    }
+
+    sort()
+    {
+        let children = this.children;
+        children.sort(this._tree._sortComparator);
+
+        for (let i = 0; i < children.length; ++i) {
+            children[i]._recalculateSiblings(i);
+            children[i].sort();
+        }
+    }
+
+    refresh()
+    {
+        this._updateChildrenForModifiers();
+        this._recalculateData();
+
+        super.refresh();
+    }
+
+    appendContextMenuItems(contextMenu)
+    {
+        let disableFocus = this === this._tree.currentFocusNode;
+        contextMenu.appendItem(WebInspector.UIString("Focus on Subtree"), () => {
+            this._tree.addFocusNode(this);
+        }, disableFocus);
+
+        // FIXME: <https://webkit.org/b/155072> Web Inspector: Charge to Caller should work with Bottom Up Profile View
+        let disableChargeToCaller = this._tree.callingContextTree.type === WebInspector.CallingContextTree.Type.BottomUp;
+        contextMenu.appendItem(WebInspector.UIString("Charge ā€˜%sā€™ to Callers").format(this.displayName()), () => {
+            this._tree.addModifier({type: WebInspector.ProfileDataGridTree.ModifierType.ChargeToCaller, source: this._node});
+        }, disableChargeToCaller);
+
+        contextMenu.appendSeparator();
+    }
+
+    // Private
+
+    _updateChildrenForModifiers()
+    {
+        // NOTE: This currently assumes we either add modifiers or remove them all.
+        // This doesn't handle removing a single modifier and re-inserting a single child.
+
+        // FIXME: <https://webkit.org/b/155072> Web Inspector: Charge to Caller should work with Bottom Up Profile View
+        let isBottomUp = this._tree.callingContextTree.type === WebInspector.CallingContextTree.Type.BottomUp;
+        if (!this._tree.hasModifiers() || isBottomUp) {
+            // Add back child data grid nodes that were previously charged to us.
+            if (this._populated && this._childrenToChargeToSelf.size) {
+                for (let child of this._childrenToChargeToSelf) {
+                    console.assert(child.hasStackTraceInTimeRange(this._tree.startTime, this._tree.endTime));
+                    this.appendChild(new WebInspector.ProfileDataGridNode(child, this._tree));
+                }
+
+                this.sort();
+            }
+
+            this._extraSelfTimeFromChargedChildren = 0;
+            this._childrenToChargeToSelf.clear();
+            this.hasChildren = this._node.hasChildrenInTimeRange(this._tree.startTime, this._tree.endTime);
+            return;
+        }
+
+        this._extraSelfTimeFromChargedChildren = 0;
+        this._childrenToChargeToSelf.clear();
+
+        let hasNonChargedChild = false;
+        this._node.forEachChild((child) => {
+            if (child.hasStackTraceInTimeRange(this._tree.startTime, this._tree.endTime)) {
+                for (let {type, source} of this._tree.modifiers) {
+                    if (type === WebInspector.ProfileDataGridTree.ModifierType.ChargeToCaller) {
+                        if (child.equals(source)) {
+                            this._childrenToChargeToSelf.add(child);
+                            let childSubtreeSamples = child.filteredTimestamps(this._tree.startTime, this._tree.endTime).length;
+                            this._extraSelfTimeFromChargedChildren += childSubtreeSamples * this._tree.sampleInterval;
+                            continue;
+                        }
+                    }
+                    hasNonChargedChild = true;
+                }
+            }
+        });
+
+        this.hasChildren = hasNonChargedChild;
+
+        // Remove child data grid nodes that have been charged to us.
+        if (this._populated && this._childrenToChargeToSelf.size) {
+            for (let childDataGridNode of this.children) {
+                if (this._childrenToChargeToSelf.has(childDataGridNode.node))
+                    this.removeChild(childDataGridNode);
+            }
+        }
+    }
+
+    _recalculateData()
+    {
+        let timestamps = this._node.filteredTimestamps(this._tree.startTime, this._tree.endTime);
+        let leafs = this._node.numberOfLeafTimestamps(this._tree.startTime, this._tree.endTime);
+
+        let sampleInterval = this._tree.sampleInterval;
+        let totalTime = timestamps.length * sampleInterval;
+        let selfTime = (leafs * sampleInterval) + this._extraSelfTimeFromChargedChildren;
+        let percent = (totalTime / (this._tree.numberOfSamples * sampleInterval)) * 100;
+
+        this._data = {totalTime, selfTime, percent};
+    }
+
+    _totalTimeContent()
+    {
+        let {totalTime, percent} = this._data;
+
+        let fragment = document.createDocumentFragment();
+        let timeElement = fragment.appendChild(document.createElement("span"));
+        timeElement.classList.add("time");
+        timeElement.textContent = Number.secondsToMillisecondsString(totalTime);
+        let percentElement = fragment.appendChild(document.createElement("span"));
+        percentElement.classList.add("percentage");
+        percentElement.textContent = Number.percentageString(percent);
+        return fragment;
+    }
+
+    _displayContent()
+    {
+        let title = this.displayName();
+        let iconClassName = this.iconClassName();
+
+        let fragment = document.createDocumentFragment();
+        let iconElement = fragment.appendChild(document.createElement("img"));
+        iconElement.classList.add("icon", iconClassName);
+        let titleElement = fragment.appendChild(document.createElement("span"));
+        titleElement.textContent = title;
+
+        let script = WebInspector.debuggerManager.scriptForIdentifier(this._node.sourceID);
+        if (script && script.url && this._node.line >= 0 && this._node.column >= 0) {
+            let sourceCodeLocation = script.createSourceCodeLocation(this._node.line, this._node.column);
+
+            let locationElement = fragment.appendChild(document.createElement("span"));
+            locationElement.classList.add("location");
+            sourceCodeLocation.populateLiveDisplayLocationString(locationElement, "textContent", WebInspector.SourceCodeLocation.ColumnStyle.Hidden, WebInspector.SourceCodeLocation.NameStyle.Short);
+
+            let dontFloat = true;
+            let useGoToArrowButton = true;
+            let goToArrowButtonLink = WebInspector.createSourceCodeLocationLink(sourceCodeLocation, dontFloat, useGoToArrowButton);
+            fragment.appendChild(goToArrowButtonLink);
+        }
+
+        return fragment;
+    }
+
+    _populate()
+    {
+        this._populated = true;
+
+        this._node.forEachChild((child) => {
+            if (!this._childrenToChargeToSelf.has(child)) {
+                if (child.hasStackTraceInTimeRange(this._tree.startTime, this._tree.endTime))
+                    this.appendChild(new WebInspector.ProfileDataGridNode(child, this._tree));
+            }
+        });
+
+        this.sort();
+
+        this.removeEventListener("populate", this._populate, this);
+    }
+}
diff --git a/Source/WebInspectorUI/UserInterface/Views/ProfileDataGridTree.js b/Source/WebInspectorUI/UserInterface/Views/ProfileDataGridTree.js
new file mode 100644 (file)
index 0000000..000b8fc
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+* 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.
+*/
+
+WebInspector.ProfileDataGridTree = class ProfileDataGridTree extends WebInspector.Object
+{
+    constructor(callingContextTree, startTime, endTime, sortComparator)
+    {
+        super();
+
+        console.assert(callingContextTree instanceof WebInspector.CallingContextTree);
+        console.assert(typeof sortComparator === "function");
+
+        this._children = [];
+        this._sortComparator = sortComparator;
+
+        this._callingContextTree = callingContextTree;
+        this._startTime = startTime;
+        this._endTime = endTime;
+        this._numberOfSamples = this._callingContextTree.numberOfSamplesInTimeRange(startTime, endTime);
+
+        this._focusNodes = [];
+        this._modifiers = [];
+
+        this._repopulate();
+    }
+
+    // Static
+
+    static buildSortComparator(columnIdentifier, sortOrder)
+    {
+        let ascending = sortOrder == WebInspector.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 sampleInterval()
+    {
+        // FIXME: It would be good to bound this, but the startTime/endTime can be beyond the
+        // bounds of when the calling context tree was sampling. This could be improved when a
+        // subset within the timeline is selected (a better bounding start/end). For now just
+        // assume a constant rate, as that roughly matches what the intent is.
+        return 1 / 1000; // 1ms per sample
+    }
+
+    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 numberOfSamples()
+    {
+        if (this._focusNodes.length)
+            return this._currentFocusNumberOfSamples;
+        return this._numberOfSamples;
+    }
+
+    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 WebInspector.ProfileDataGridNode);
+
+        // Save the original parent for when we rollback.
+        profileDataGridNode.__previousParent = profileDataGridNode.parent === profileDataGridNode.dataGrid ? this : profileDataGridNode.parent;
+
+        this._focusNodes.push(profileDataGridNode);
+        this._focusChanged();
+    }
+
+    rollbackFocusNode(profileDataGridNode)
+    {
+        console.assert(profileDataGridNode instanceof WebInspector.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;
+
+        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()
+    {
+        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 WebInspector.ProfileDataGridNode(child, this));
+            });
+        }
+
+        this.sort();
+    }
+
+    _focusChanged()
+    {
+        let profileDataGridNode = this.currentFocusNode;
+        if (profileDataGridNode) {
+            this._updateCurrentFocusDetails(profileDataGridNode);
+
+            if (profileDataGridNode.parent)
+                profileDataGridNode.parent.removeChild(profileDataGridNode);
+        }
+
+        // FIXME: This re-creates top level children, without remembering their expanded / unexpanded state.
+        this._repopulate();
+
+        this.dispatchEventToListeners(WebInspector.ProfileDataGridTree.Event.FocusChanged);
+    }
+
+    _updateCurrentFocusDetails(focusDataGridNode)
+    {
+        let cctNode = focusDataGridNode.node;
+        let timestampsInRange = cctNode.filteredTimestamps(this._startTime, this._endTime);
+
+        this._currentFocusStartTime = timestampsInRange[0];
+        this._currentFocusEndTime = timestampsInRange.lastValue;
+        this._currentFocusNumberOfSamples = timestampsInRange.length;
+    }
+
+    _restoreFocusedNodeToOriginalParent(focusDataGridNode)
+    {
+        focusDataGridNode.__previousParent.appendChild(focusDataGridNode);
+        focusDataGridNode.__previousParent = undefined;
+    }
+
+    _modifiersChanged()
+    {
+        this.dispatchEventToListeners(WebInspector.ProfileDataGridTree.Event.ModifiersChanged);
+    }
+}
+
+WebInspector.ProfileDataGridTree.Event = {
+    FocusChanged: "profile-data-grid-tree-focus-changed",
+    ModifiersChanged: "profile-data-grid-tree-modifiers-changed",
+};
+
+WebInspector.ProfileDataGridTree.ModifierType = {
+    ChargeToCaller: "charge",
+};
diff --git a/Source/WebInspectorUI/UserInterface/Views/ProfileView.css b/Source/WebInspectorUI/UserInterface/Views/ProfileView.css
new file mode 100644 (file)
index 0000000..e3c5548
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+* 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.
+*/
+
+.profile > .data-grid {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+}
+
+.profile > .data-grid th {
+    border-top: 1px solid var(--border-color);
+}
+
+.profile > .data-grid td .icon {
+    position: relative;
+    width: 16px;
+    height: 16px;
+    margin-top: 1px;
+    margin-right: 3px;
+}
+
+.profile > .data-grid td .percentage {
+    width: 45px;
+    margin-left: 4px;
+    display: inline-block;
+}
+
+.profile > .data-grid td .location {
+    margin-left: 1ex;
+    color: hsl(0, 0%, 60%);
+}
+
+.profile > .data-grid:matches(:focus, .force-focus) tr.selected td .location {
+    color: hsl(0, 0%, 85%);
+}
+
+.profile > .data-grid td .icon.function-icon {
+    content: url(../Images/Function.svg);
+}
+
+.profile > .data-grid td .icon.native-icon {
+    content: url(../Images/Native.svg);
+}
+
+.profile > .data-grid td .icon.program-icon {
+    content: url(../Images/Program.svg);
+}
+
+.profile > .data-grid tr:matches(.selected, :hover) .go-to-arrow {
+    float: none;
+    display: inline-block;
+    vertical-align: top;
+    position: relative;
+    width: 16px;
+    height: 16px;
+    margin-top: 1px;
+    margin-left: 2px;
+}
+
+.profile > .data-grid td.function-column {
+    position: relative;
+}
+
+.profile > .data-grid td .guidance {
+    width: 10px;
+    height: 100%;
+    position: absolute;
+    top: 0;
+    background-color: hsla(0, 0%, 50%, 0.4);
+}
+
+.profile > .data-grid td .guidance.hover {
+    background-color: hsla(0, 0%, 50%, 0.2);
+}
+
+.profile > .data-grid td .guidance.base {
+    height: 70%;
+    top: 30%;
+}
+
+.profile > .data-grid tr:not(.expanded) td .guidance.base {
+    display: none;
+}
+
+.profile > .data-grid tr.expanded td .guidance.base {
+    display: block;
+}
diff --git a/Source/WebInspectorUI/UserInterface/Views/ProfileView.js b/Source/WebInspectorUI/UserInterface/Views/ProfileView.js
new file mode 100644 (file)
index 0000000..f1295ad
--- /dev/null
@@ -0,0 +1,276 @@
+/*
+* 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.
+*/
+
+WebInspector.ProfileView = class ProfileView extends WebInspector.ContentView
+{
+    constructor(callingContextTree)
+    {
+        super(callingContextTree);
+
+        console.assert(callingContextTree instanceof WebInspector.CallingContextTree);
+
+        this._startTime = 0;
+        this._endTime = Infinity;
+        this._callingContextTree = callingContextTree;
+
+        this._hoveredDataGridNode = null;
+
+        this.element.classList.add("profile");
+
+        let columns = {
+            totalTime: {
+                title: WebInspector.UIString("Total Time"),
+                width: "120px",
+                sortable: true,
+                aligned: "right",
+            },
+            selfTime: {
+                title: WebInspector.UIString("Self Time"),
+                width: "75px",
+                sortable: true,
+                aligned: "right",
+            },
+            function: {
+                title: WebInspector.UIString("Function"),
+                disclosure: true,
+            },
+        };
+
+        this._dataGrid = new WebInspector.DataGrid(columns);
+        this._dataGrid.addEventListener(WebInspector.DataGrid.Event.SortChanged, this._dataGridSortChanged, this);
+        this._dataGrid.addEventListener(WebInspector.DataGrid.Event.SelectedNodeChanged, this._dataGridNodeSelected, this);
+        this._dataGrid.addEventListener(WebInspector.DataGrid.Event.ExpandedNode, this._dataGridNodeExpanded, this);
+        this._dataGrid.element.addEventListener("mouseover", this._mouseOverDataGrid.bind(this));
+        this._dataGrid.element.addEventListener("mouseleave", this._mouseLeaveDataGrid.bind(this));
+        this._dataGrid.indentWidth = 20;
+        this._dataGrid.sortColumnIdentifierSetting = new WebInspector.Setting("profile-view-sort", "totalTime");
+        this._dataGrid.sortOrderSetting = new WebInspector.Setting("profile-view-sort-order", WebInspector.DataGrid.SortOrder.Descending);
+
+        this.addSubview(this._dataGrid);
+    }
+
+    // Public
+
+    get callingContextTree() { return this._callingContextTree; }
+    get startTime() { return this._startTime; }
+    get endTime() { return this._endTime; }
+
+    setStartAndEndTime(startTime, endTime)
+    {
+        console.assert(startTime >= 0);
+        console.assert(endTime >= 0);
+        console.assert(startTime <= endTime);
+
+        this._startTime = startTime;
+        this._endTime = endTime;
+
+        // FIXME: It would be ideal to update the existing tree, maintaining nodes that were expanded.
+        // For now just recreate the tree for the new time range.
+
+        this._recreate();
+    }
+
+    hasFocusNodes()
+    {
+        if (!this._profileDataGridTree)
+            return false;
+        return this._profileDataGridTree.focusNodes.length > 0
+    }
+
+    clearFocusNodes()
+    {
+        if (!this._profileDataGridTree)
+            return;
+        this._profileDataGridTree.clearFocusNodes();
+    }
+
+    // Protected
+
+    get selectionPathComponents()
+    {
+        let pathComponents = [];
+
+        if (this._profileDataGridTree) {
+            for (let profileDataGridNode of this._profileDataGridTree.focusNodes) {
+                let displayName = profileDataGridNode.displayName();
+                let className = profileDataGridNode.iconClassName();
+                let pathComponent = new WebInspector.HierarchicalPathComponent(displayName, className, profileDataGridNode);
+                pathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.Clicked, this._pathComponentClicked, this);
+                pathComponents.push(pathComponent);
+            }
+        }
+
+        return pathComponents;
+    }
+
+    // Private
+
+    _recreate()
+    {
+        let sortComparator = WebInspector.ProfileDataGridTree.buildSortComparator(this._dataGrid.sortColumnIdentifier, this._dataGrid.sortOrder);
+        this._profileDataGridTree = new WebInspector.ProfileDataGridTree(this._callingContextTree, this._startTime, this._endTime, sortComparator);
+        this._profileDataGridTree.addEventListener(WebInspector.ProfileDataGridTree.Event.FocusChanged, this._dataGridTreeFocusChanged, this);
+        this._profileDataGridTree.addEventListener(WebInspector.ProfileDataGridTree.Event.ModifiersChanged, this._dataGridTreeModifiersChanged, this);
+        this._repopulateDataGridFromTree();
+
+        this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
+    }
+
+    _repopulateDataGridFromTree(skipRefresh)
+    {
+        this._dataGrid.removeChildren();
+        for (let child of this._profileDataGridTree.children)
+            this._dataGrid.appendChild(child);
+    }
+
+    _pathComponentClicked(event)
+    {
+        if (!event.data.pathComponent)
+            return;
+
+        let profileDataGridNode = event.data.pathComponent.representedObject;
+        if (profileDataGridNode === this._profileDataGridTree.currentFocusNode)
+            return;
+
+        this._profileDataGridTree.rollbackFocusNode(event.data.pathComponent.representedObject);
+    }
+
+    _dataGridTreeFocusChanged(event)
+    {
+        this._repopulateDataGridFromTree();
+        this._profileDataGridTree.refresh();
+
+        this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
+    }
+
+    _dataGridTreeModifiersChanged(event)
+    {
+        this._profileDataGridTree.refresh();
+    }
+
+    _dataGridSortChanged()
+    {
+        if (!this._profileDataGridTree)
+            return;
+
+        this._profileDataGridTree.sortComparator = WebInspector.ProfileDataGridTree.buildSortComparator(this._dataGrid.sortColumnIdentifier, this._dataGrid.sortOrder);
+        this._repopulateDataGridFromTree();
+    }
+
+    _dataGridNodeSelected(event)
+    {
+        let oldSelectedNode = event.data.oldSelectedNode;
+        if (oldSelectedNode) {
+            this._removeGuidanceElement(WebInspector.ProfileView.GuidanceType.Selected, oldSelectedNode);
+            oldSelectedNode.forEachChildInSubtree((node) => this._removeGuidanceElement(WebInspector.ProfileView.GuidanceType.Selected, node));
+        }
+
+        let newSelectedNode = this._dataGrid.selectedNode;
+        if (newSelectedNode) {
+            this._removeGuidanceElement(WebInspector.ProfileView.GuidanceType.Selected, newSelectedNode);
+            newSelectedNode.forEachChildInSubtree((node) => this._appendGuidanceElement(WebInspector.ProfileView.GuidanceType.Selected, node, newSelectedNode));
+        }
+    }
+
+    _dataGridNodeExpanded(event)
+    {
+        let expandedNode = event.data.dataGridNode;
+
+        if (this._dataGrid.selectedNode) {
+            if (expandedNode.isInSubtreeOfNode(this._dataGrid.selectedNode))
+                expandedNode.forEachImmediateChild((node) => this._appendGuidanceElement(WebInspector.ProfileView.GuidanceType.Selected, node, this._dataGrid.selectedNode));
+        }
+
+        if (this._hoveredDataGridNode) {
+            if (expandedNode.isInSubtreeOfNode(this._hoveredDataGridNode))
+                expandedNode.forEachImmediateChild((node) => this._appendGuidanceElement(WebInspector.ProfileView.GuidanceType.Hover, node, this._hoveredDataGridNode));
+        }
+    }
+
+    _mouseOverDataGrid(event)
+    {
+        let hoveredDataGridNode = this._dataGrid.dataGridNodeFromNode(event.target);
+        if (hoveredDataGridNode === this._hoveredDataGridNode)
+            return;
+
+        if (this._hoveredDataGridNode) {
+            this._removeGuidanceElement(WebInspector.ProfileView.GuidanceType.Hover, this._hoveredDataGridNode);
+            this._hoveredDataGridNode.forEachChildInSubtree((node) => this._removeGuidanceElement(WebInspector.ProfileView.GuidanceType.Hover, node));
+        }
+
+        this._hoveredDataGridNode = hoveredDataGridNode;
+
+        if (this._hoveredDataGridNode) {
+            this._appendGuidanceElement(WebInspector.ProfileView.GuidanceType.Hover, this._hoveredDataGridNode, this._hoveredDataGridNode);
+            this._hoveredDataGridNode.forEachChildInSubtree((node) => this._appendGuidanceElement(WebInspector.ProfileView.GuidanceType.Hover, node, this._hoveredDataGridNode));
+        }
+    }
+
+    _mouseLeaveDataGrid(event)
+    {
+        if (!this._hoveredDataGridNode)
+            return;
+
+        this._removeGuidanceElement(WebInspector.ProfileView.GuidanceType.Hover, this._hoveredDataGridNode);
+        this._hoveredDataGridNode.forEachChildInSubtree((node) => this._removeGuidanceElement(WebInspector.ProfileView.GuidanceType.Hover, node));
+
+        this._hoveredDataGridNode = null;
+    }
+
+    _guidanceElementKey(type)
+    {
+        return "guidance-element-" + type;
+    }
+
+    _removeGuidanceElement(type, node)
+    {
+        let key = this._guidanceElementKey(type);
+        let element = node.elementWithColumnIdentifier("function");
+        if (!element || !element[key])
+            return;
+
+        element[key].remove();
+        element[key] = null;
+    }
+
+    _appendGuidanceElement(type, node, baseElement)
+    {
+        let depth = baseElement.depth;
+        let guidanceMarkerLeft = depth ? (depth * this._dataGrid.indentWidth) + 1.5 : 7.5;
+
+        let key = this._guidanceElementKey(type);
+        let element = node.elementWithColumnIdentifier("function");
+        let guidanceElement = element[key] || element.appendChild(document.createElement("div"));
+        element[key] = guidanceElement;
+        guidanceElement.classList.add("guidance", type);
+        guidanceElement.classList.toggle("base", node === baseElement);
+        guidanceElement.style.left = guidanceMarkerLeft + "px";
+    }
+};
+
+WebInspector.ProfileView.GuidanceType = {
+    Selected: "selected",
+    Hover: "hover",
+};
diff --git a/Source/WebInspectorUI/UserInterface/Views/ScriptClusterTimelineView.js b/Source/WebInspectorUI/UserInterface/Views/ScriptClusterTimelineView.js
new file mode 100644 (file)
index 0000000..e534fbe
--- /dev/null
@@ -0,0 +1,207 @@
+/*
+ * 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.
+ */
+
+WebInspector.ScriptClusterTimelineView = class ScriptClusterTimelineView extends WebInspector.ClusterContentView
+{
+    constructor(timeline, extraArguments)
+    {
+        super(timeline);
+
+        console.assert(timeline.type === WebInspector.TimelineRecord.Type.Script);
+
+        this._extraArguments = extraArguments;
+
+        this._currentContentViewSetting = new WebInspector.Setting("script-cluster-timeline-view-current-view", WebInspector.ScriptClusterTimelineView.DetailsIdentifier);
+
+        let showSelectorArrows = this._canShowProfileView();
+        function createPathComponent(displayName, className, identifier)
+        {
+            let pathComponent = new WebInspector.HierarchicalPathComponent(displayName, className, identifier, false, showSelectorArrows);
+            pathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this._pathComponentSelected, this);
+            return pathComponent;
+        }
+
+        let iconClassName = WebInspector.TimelineTabContentView.iconClassNameForTimeline(timeline);
+        this._detailsPathComponent = createPathComponent.call(this, WebInspector.UIString("Details"), iconClassName, WebInspector.ScriptClusterTimelineView.DetailsIdentifier);
+        this._profilePathComponent = createPathComponent.call(this, WebInspector.UIString("Call Trees"), iconClassName, WebInspector.ScriptClusterTimelineView.ProfileIdentifier);
+
+        if (this._canShowProfileView()) {
+            this._detailsPathComponent.nextSibling = this._profilePathComponent;
+            this._profilePathComponent.previousSibling = this._detailsPathComponent;
+        }
+
+        // FIXME: We should be able to create these lazily.
+        this._detailsContentView = new WebInspector.ScriptDetailsTimelineView(this.representedObject, this._extraArguments);
+        this._profileContentView = this._canShowProfileView() ? new WebInspector.ScriptProfileTimelineView(this.representedObject, this._extraArguments) : null;
+
+        this._showContentViewForIdentifier(this._currentContentViewSetting.value);
+
+        this.contentViewContainer.addEventListener(WebInspector.ContentViewContainer.Event.CurrentContentViewDidChange, this._scriptClusterViewCurrentContentViewDidChange, this)
+    }
+
+    // TimelineView
+
+    // FIXME: Determine a better way to bridge TimelineView methods to the sub-timeline views.
+    get zeroTime() { return this._contentViewContainer.currentContentView.zeroTime; }
+    set zeroTime(x) { this._contentViewContainer.currentContentView.zeroTime = x; }
+    get startTime() { return this._contentViewContainer.currentContentView.startTime; }
+    set startTime(x) { this._contentViewContainer.currentContentView.startTime = x; }
+    get endTime() { return this._contentViewContainer.currentContentView.endTime; }
+    set endTime(x) { this._contentViewContainer.currentContentView.endTime = x; }
+    get currentTime() { return this._contentViewContainer.currentContentView.currentTime; }
+    set currentTime(x) { this._contentViewContainer.currentContentView.currentTime = x; }
+    get navigationSidebarTreeOutline() { return this._contentViewContainer.currentContentView.navigationSidebarTreeOutline; }
+    reset() { return this._contentViewContainer.currentContentView.reset(); }
+    filterDidChange() { return this._contentViewContainer.currentContentView.filterDidChange(); }
+    matchTreeElementAgainstCustomFilters(treeElement) { return this._contentViewContainer.currentContentView.matchTreeElementAgainstCustomFilters(treeElement); }
+
+    // Public
+
+    get detailsContentView()
+    {
+        return this._detailsContentView;
+    }
+
+    get profileContentView()
+    {
+        return this._profileContentView;
+    }
+
+    get selectionPathComponents()
+    {
+        let currentContentView = this._contentViewContainer.currentContentView;
+        if (!currentContentView)
+            return [];
+
+        let components = [this._pathComponentForContentView(currentContentView)];
+        let subComponents = currentContentView.selectionPathComponents;
+        if (subComponents)
+            return components.concat(subComponents);
+        return components;
+    }
+
+    saveToCookie(cookie)
+    {
+        cookie[WebInspector.ScriptClusterTimelineView.ContentViewIdentifierCookieKey] = this._currentContentViewSetting.value;
+    }
+
+    restoreFromCookie(cookie)
+    {
+        this._showContentViewForIdentifier(cookie[WebInspector.ScriptClusterTimelineView.ContentViewIdentifierCookieKey]);
+    }
+
+    showDetails()
+    {
+        return this._showContentViewForIdentifier(WebInspector.ScriptClusterTimelineView.DetailsIdentifier);
+    }
+
+    showProfile()
+    {
+        if (!this._canShowProfileView())
+            return showDetails();
+
+        return this._showContentViewForIdentifier(WebInspector.ScriptClusterTimelineView.ProfileIdentifier);
+    }
+
+    // Private
+
+    _canShowProfileView()
+    {
+        // COMPATIBILITY (iOS 9): Legacy backends did not include CallingContextTree ScriptProfiler data.
+        return window.ScriptProfilerAgent;
+    }
+
+    _pathComponentForContentView(contentView)
+    {
+        console.assert(contentView);
+        if (!contentView)
+            return null;
+        if (contentView === this._detailsContentView)
+            return this._detailsPathComponent;
+        if (contentView === this._profileContentView)
+            return this._profilePathComponent;
+        console.error("Unknown contentView.");
+        return null;
+    }
+
+    _identifierForContentView(contentView)
+    {
+        console.assert(contentView);
+        if (!contentView)
+            return null;
+        if (contentView === this._detailsContentView)
+            return WebInspector.ScriptClusterTimelineView.DetailsIdentifier;
+        if (contentView === this._profileContentView)
+            return WebInspector.ScriptClusterTimelineView.ProfileIdentifier;
+        console.error("Unknown contentView.");
+        return null;
+    }
+
+    _showContentViewForIdentifier(identifier)
+    {
+        let contentViewToShow = null;
+
+        switch (identifier) {
+        case WebInspector.ScriptClusterTimelineView.DetailsIdentifier:
+            contentViewToShow = this.detailsContentView;
+            break;
+        case WebInspector.ScriptClusterTimelineView.ProfileIdentifier:
+            contentViewToShow = this.profileContentView;
+            break;
+        }
+
+        if (!contentViewToShow)
+            contentViewToShow = this.detailsContentView;
+
+        console.assert(contentViewToShow);
+
+        this._currentContentViewSetting.value = this._identifierForContentView(contentViewToShow);
+
+        return this.contentViewContainer.showContentView(contentViewToShow);
+    }
+
+    _pathComponentSelected(event)
+    {
+        this._showContentViewForIdentifier(event.data.pathComponent.representedObject);
+    }
+
+    _scriptClusterViewCurrentContentViewDidChange(event)
+    {
+        let currentContentView = this._contentViewContainer.currentContentView;
+        let previousContentView = currentContentView === this._detailsContentView ? this._profileContentView : this._detailsContentView;
+
+        currentContentView.zeroTime = previousContentView.zeroTime;
+        currentContentView.startTime = previousContentView.startTime;
+        currentContentView.endTime = previousContentView.endTime;
+        currentContentView.currentTime = previousContentView.currentTime;
+
+        currentContentView.timelineSidebarPanel.updateFilter();
+    }
+};
+
+WebInspector.ScriptClusterTimelineView.ContentViewIdentifierCookieKey = "script-cluster-timeline-view-identifier";
+
+WebInspector.ScriptClusterTimelineView.DetailsIdentifier = "details";
+WebInspector.ScriptClusterTimelineView.ProfileIdentifier = "profile";
@@ -23,7 +23,7 @@
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-WebInspector.ScriptTimelineView = class ScriptTimelineView extends WebInspector.TimelineView
+WebInspector.ScriptDetailsTimelineView = class ScriptDetailsTimelineView extends WebInspector.TimelineView
 {
     constructor(timeline, extraArguments)
     {
diff --git a/Source/WebInspectorUI/UserInterface/Views/ScriptProfileTimelineView.js b/Source/WebInspectorUI/UserInterface/Views/ScriptProfileTimelineView.js
new file mode 100644 (file)
index 0000000..faa2dac
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * 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.
+ */
+
+WebInspector.ScriptProfileTimelineView = class ScriptProfileTimelineView extends WebInspector.TimelineView
+{
+    constructor(timeline, extraArguments)
+    {
+        super(timeline, extraArguments);
+
+        console.assert(timeline.type === WebInspector.TimelineRecord.Type.Script);
+
+        this.element.classList.add("script");
+
+        this._recording = extraArguments.recording;
+
+        this._forceNextLayout = false;
+        this._lastLayoutStartTime = undefined;
+        this._lastLayoutEndTime = undefined;
+
+        if (!WebInspector.ScriptProfileTimelineView.profileOrientationSetting)
+            WebInspector.ScriptProfileTimelineView.profileOrientationSetting = new WebInspector.Setting("script-profile-timeline-view-profile-orientation-setting", WebInspector.ScriptProfileTimelineView.ProfileOrientation.TopDown);
+
+        let callingContextTree = this._callingContextTreeForOrientation(WebInspector.ScriptProfileTimelineView.profileOrientationSetting.value);
+        this._profileView = new WebInspector.ProfileView(callingContextTree);
+        this._profileView.addEventListener(WebInspector.ContentView.Event.SelectionPathComponentsDidChange, this._profileViewSelectionPathComponentsDidChange, this);
+        this.addSubview(this._profileView);
+
+        let scopeBarItems = [
+            new WebInspector.ScopeBarItem(WebInspector.ScriptProfileTimelineView.ProfileOrientation.TopDown, WebInspector.UIString("Top Down"), true),
+            new WebInspector.ScopeBarItem(WebInspector.ScriptProfileTimelineView.ProfileOrientation.BottomUp, WebInspector.UIString("Bottom Up"), true),
+        ];
+        let defaultScopeBarItem = WebInspector.ScriptProfileTimelineView.profileOrientationSetting.value === WebInspector.ScriptProfileTimelineView.ProfileOrientation.TopDown ? scopeBarItems[0] : scopeBarItems[1];
+        this._profileOrientationScopeBar = new WebInspector.ScopeBar("profile-orientation", scopeBarItems, defaultScopeBarItem);
+        this._profileOrientationScopeBar.addEventListener(WebInspector.ScopeBar.Event.SelectionChanged, this._scopeBarSelectionDidChange, this);
+
+        let clearTooltip = WebInspector.UIString("Clear focus");
+        this._clearFocusNodesButtonItem = new WebInspector.ButtonNavigationItem("clear-profile-focus", clearTooltip, "Images/Close.svg", 16, 16);
+        this._clearFocusNodesButtonItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._clearFocusNodes, this);
+        this._updateClearFocusNodesButtonItem();
+
+        timeline.addEventListener(WebInspector.Timeline.Event.Refreshed, this._scriptTimelineRecordRefreshed, this);
+
+        // FIXME: Support filtering the ProfileView.
+    }
+
+    // Protected
+
+    closed()
+    {
+        console.assert(this.representedObject instanceof WebInspector.Timeline);
+        this.representedObject.removeEventListener(null, null, this);
+    }
+
+    get navigationItems()
+    {
+        return [this._clearFocusNodesButtonItem, this._profileOrientationScopeBar];
+    }
+
+    get selectionPathComponents()
+    {
+        return this._profileView.selectionPathComponents;
+    }
+
+    layout()
+    {
+        if (!this._forceNextLayout && (this._lastLayoutStartTime === this.startTime && this._lastLayoutEndTime === this.endTime))
+            return;
+
+        this._forceNextLayout = false;
+        this._lastLayoutStartTime = this.startTime;
+        this._lastLayoutEndTime = this.endTime;
+
+        this._profileView.setStartAndEndTime(this.startTime, this.endTime);
+    }
+
+    // Private
+
+    _callingContextTreeForOrientation(orientation)
+    {
+        switch (orientation) {
+        case WebInspector.ScriptProfileTimelineView.ProfileOrientation.TopDown:
+            return this._recording.topDownCallingContextTree;
+        case WebInspector.ScriptProfileTimelineView.ProfileOrientation.BottomUp:
+            return this._recording.bottomUpCallingContextTree;
+        }
+    }
+
+    _profileViewSelectionPathComponentsDidChange(event)
+    {
+        this._updateClearFocusNodesButtonItem();
+        this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
+    }
+
+    _scriptTimelineRecordRefreshed(event)
+    {
+        this._forceNextLayout = true;
+        this.needsLayout();
+    }
+
+    _scopeBarSelectionDidChange()
+    {
+        let currentOrientation = WebInspector.ScriptProfileTimelineView.profileOrientationSetting.value;
+        let newOrientation = this._profileOrientationScopeBar.selectedItems[0].id;
+        let callingContextTree = this._callingContextTreeForOrientation(newOrientation);
+
+        WebInspector.ScriptProfileTimelineView.profileOrientationSetting.value = newOrientation;
+
+        this.removeSubview(this._profileView);
+        this._profileView.removeEventListener(WebInspector.ContentView.Event.SelectionPathComponentsDidChange, this._profileViewSelectionPathComponentsDidChange, this);
+
+        this._profileView = new WebInspector.ProfileView(callingContextTree);
+
+        this._profileView.addEventListener(WebInspector.ContentView.Event.SelectionPathComponentsDidChange, this._profileViewSelectionPathComponentsDidChange, this);
+        this.addSubview(this._profileView);
+
+        this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
+
+        this._forceNextLayout = true;
+        this.needsLayout();
+    }
+
+    _updateClearFocusNodesButtonItem()
+    {
+        this._clearFocusNodesButtonItem.enabled = this._profileView.hasFocusNodes();
+    }
+
+    _clearFocusNodes()
+    {
+        this._profileView.clearFocusNodes();
+    }
+};
+
+WebInspector.ScriptProfileTimelineView.ProfileOrientation = {
+    BottomUp: "bottom-up",
+    TopDown: "top-down",
+};
index ac1c239..ce76274 100644 (file)
@@ -162,8 +162,7 @@ WebInspector.TimelineRecordingContentView = class TimelineRecordingContentView e
 
     get currentTimelineView()
     {
-        let contentView = this._timelineContentBrowser.currentContentView;
-        return (contentView instanceof WebInspector.TimelineView) ? contentView : null;
+        return this._timelineContentBrowser.currentContentView;
     }
 
     get timelineOverviewHeight()
@@ -338,6 +337,9 @@ WebInspector.TimelineRecordingContentView = class TimelineRecordingContentView e
         if (!(representedObject instanceof WebInspector.Timeline) && !(representedObject instanceof WebInspector.TimelineRecording))
             return null;
 
+        if (this.currentTimelineView instanceof WebInspector.ClusterContentView)
+            return null;
+
         let iconClassName;
         let title;
         if (representedObject instanceof WebInspector.Timeline) {
@@ -602,7 +604,7 @@ WebInspector.TimelineRecordingContentView = class TimelineRecordingContentView e
         let timeline = this._recording.timelineForInstrument(instrument);
         console.assert(!this._timelineViewMap.has(timeline), timeline);
 
-        this._timelineViewMap.set(timeline, WebInspector.ContentView.createFromRepresentedObject(timeline, {timelineSidebarPanel: this._timelineSidebarPanel}));
+        this._timelineViewMap.set(timeline, WebInspector.ContentView.createFromRepresentedObject(timeline, {timelineSidebarPanel: this._timelineSidebarPanel, recording: this._recording}));
         if (timeline.type === WebInspector.TimelineRecord.Type.RenderingFrame)
             this._renderingFrameTimeline = timeline;
 
index 54eaf68..6fef752 100644 (file)
@@ -172,7 +172,6 @@ WebInspector.TimelineView = class TimelineView extends WebInspector.ContentView
         this._timelineSidebarPanel.hideEmptyContentPlaceholder();
     }
 
-
     filterDidChange()
     {
         // Implemented by sub-classes if needed.