Web Inspector: DOM: provide a way to disable/breakpoint all event listeners for a...
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / ProfileView.js
1 /*
2 * Copyright (C) 2016 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 WI.ProfileView = class ProfileView extends WI.ContentView
27 {
28     constructor(callingContextTree, extraArguments)
29     {
30         super(callingContextTree);
31
32         console.assert(callingContextTree instanceof WI.CallingContextTree);
33
34         this._startTime = 0;
35         this._endTime = Infinity;
36         this._callingContextTree = callingContextTree;
37
38         this._hoveredDataGridNode = null;
39
40         this.element.classList.add("profile");
41
42         let columns = {
43             totalTime: {
44                 title: WI.UIString("Total Time"),
45                 width: "120px",
46                 sortable: true,
47                 aligned: "right",
48             },
49             selfTime: {
50                 title: WI.UIString("Self Time"),
51                 width: "75px",
52                 sortable: true,
53                 aligned: "right",
54             },
55             function: {
56                 title: WI.UIString("Function"),
57                 disclosure: true,
58             },
59         };
60
61         this._dataGrid = new WI.DataGrid(columns);
62         this._dataGrid.addEventListener(WI.DataGrid.Event.SortChanged, this._dataGridSortChanged, this);
63         this._dataGrid.addEventListener(WI.DataGrid.Event.SelectedNodeChanged, this._dataGridNodeSelected, this);
64         this._dataGrid.addEventListener(WI.DataGrid.Event.ExpandedNode, this._dataGridNodeExpanded, this);
65         this._dataGrid.element.addEventListener("mouseover", this._mouseOverDataGrid.bind(this));
66         this._dataGrid.element.addEventListener("mouseleave", this._mouseLeaveDataGrid.bind(this));
67         this._dataGrid.indentWidth = 20;
68         this._dataGrid.sortColumnIdentifier = "totalTime";
69         this._dataGrid.sortOrder = WI.DataGrid.SortOrder.Descending;
70         this._dataGrid.createSettings("profile-view");
71
72         // Currently we create a new ProfileView for each CallingContextTree, so
73         // to share state between them, use a common shared data object.
74         this._sharedData = extraArguments;
75
76         this.addSubview(this._dataGrid);
77     }
78
79     // Public
80
81     get callingContextTree() { return this._callingContextTree; }
82     get startTime() { return this._startTime; }
83     get endTime() { return this._endTime; }
84     get dataGrid() { return this._dataGrid; }
85
86     setStartAndEndTime(startTime, endTime)
87     {
88         console.assert(startTime >= 0);
89         console.assert(endTime >= 0);
90         console.assert(startTime <= endTime);
91
92         this._startTime = startTime;
93         this._endTime = endTime;
94
95         // FIXME: It would be ideal to update the existing tree, maintaining nodes that were expanded.
96         // For now just recreate the tree for the new time range.
97
98         this._recreate();
99     }
100
101     hasFocusNodes()
102     {
103         if (!this._profileDataGridTree)
104             return false;
105         return this._profileDataGridTree.focusNodes.length > 0;
106     }
107
108     clearFocusNodes()
109     {
110         if (!this._profileDataGridTree)
111             return;
112         this._profileDataGridTree.clearFocusNodes();
113     }
114
115     get scrollableElements()
116     {
117         return [this._dataGrid.scrollContainer];
118     }
119
120     // Protected
121
122     get selectionPathComponents()
123     {
124         let pathComponents = [];
125
126         if (this._profileDataGridTree) {
127             for (let profileDataGridNode of this._profileDataGridTree.focusNodes) {
128                 let displayName = profileDataGridNode.displayName();
129                 let className = profileDataGridNode.iconClassName();
130                 let pathComponent = new WI.HierarchicalPathComponent(displayName, className, profileDataGridNode);
131                 pathComponent.addEventListener(WI.HierarchicalPathComponent.Event.Clicked, this._pathComponentClicked, this);
132                 pathComponents.push(pathComponent);
133             }
134         }
135
136         return pathComponents;
137     }
138
139     // Private
140
141     _recreate()
142     {
143         let hadFocusNodes = this.hasFocusNodes();
144
145         let sortComparator = WI.ProfileDataGridTree.buildSortComparator(this._dataGrid.sortColumnIdentifier, this._dataGrid.sortOrder);
146         this._profileDataGridTree = new WI.ProfileDataGridTree(this._callingContextTree, this._startTime, this._endTime, sortComparator);
147         this._profileDataGridTree.addEventListener(WI.ProfileDataGridTree.Event.FocusChanged, this._dataGridTreeFocusChanged, this);
148         this._profileDataGridTree.addEventListener(WI.ProfileDataGridTree.Event.ModifiersChanged, this._dataGridTreeModifiersChanged, this);
149         this._repopulateDataGridFromTree();
150
151         if (hadFocusNodes)
152             this.dispatchEventToListeners(WI.ContentView.Event.SelectionPathComponentsDidChange);
153     }
154
155     _repopulateDataGridFromTree()
156     {
157         this._dataGrid.removeChildren();
158
159         for (let child of this._profileDataGridTree.children)
160             this._dataGrid.appendChild(child);
161
162         this._restoreSharedState();
163     }
164
165     _restoreSharedState()
166     {
167         const skipHidden = false;
168         const stayWithin = this._dataGrid;
169         const dontPopulate = true;
170
171         if (this._sharedData.selectedNodeHash) {
172             let nodeToSelect = this._dataGrid.findNode((node) => node.callingContextTreeNode.hash === this._sharedData.selectedNodeHash, skipHidden, stayWithin, dontPopulate);
173             if (nodeToSelect)
174                 nodeToSelect.revealAndSelect();
175         }
176     }
177
178     _pathComponentClicked(event)
179     {
180         if (!event.data.pathComponent)
181             return;
182
183         let profileDataGridNode = event.data.pathComponent.representedObject;
184         if (profileDataGridNode === this._profileDataGridTree.currentFocusNode)
185             return;
186
187         this._profileDataGridTree.rollbackFocusNode(event.data.pathComponent.representedObject);
188     }
189
190     _dataGridTreeFocusChanged(event)
191     {
192         this._repopulateDataGridFromTree();
193         this._profileDataGridTree.refresh();
194
195         this.dispatchEventToListeners(WI.ContentView.Event.SelectionPathComponentsDidChange);
196     }
197
198     _dataGridTreeModifiersChanged(event)
199     {
200         this._profileDataGridTree.refresh();
201     }
202
203     _dataGridSortChanged()
204     {
205         if (!this._profileDataGridTree)
206             return;
207
208         this._profileDataGridTree.sortComparator = WI.ProfileDataGridTree.buildSortComparator(this._dataGrid.sortColumnIdentifier, this._dataGrid.sortOrder);
209         this._repopulateDataGridFromTree();
210     }
211
212     _dataGridNodeSelected(event)
213     {
214         let oldSelectedNode = event.data.oldSelectedNode;
215         if (oldSelectedNode) {
216             this._removeGuidanceElement(WI.ProfileView.GuidanceType.Selected, oldSelectedNode);
217             oldSelectedNode.forEachChildInSubtree((node) => this._removeGuidanceElement(WI.ProfileView.GuidanceType.Selected, node));
218         }
219
220         let newSelectedNode = this._dataGrid.selectedNode;
221         if (newSelectedNode) {
222             this._removeGuidanceElement(WI.ProfileView.GuidanceType.Selected, newSelectedNode);
223             newSelectedNode.forEachChildInSubtree((node) => this._appendGuidanceElement(WI.ProfileView.GuidanceType.Selected, node, newSelectedNode));
224
225             this._sharedData.selectedNodeHash = newSelectedNode.callingContextTreeNode.hash;
226         }
227     }
228
229     _dataGridNodeExpanded(event)
230     {
231         let expandedNode = event.data.dataGridNode;
232
233         if (this._dataGrid.selectedNode) {
234             if (expandedNode.isInSubtreeOfNode(this._dataGrid.selectedNode))
235                 expandedNode.forEachImmediateChild((node) => this._appendGuidanceElement(WI.ProfileView.GuidanceType.Selected, node, this._dataGrid.selectedNode));
236         }
237
238         if (this._hoveredDataGridNode) {
239             if (expandedNode.isInSubtreeOfNode(this._hoveredDataGridNode))
240                 expandedNode.forEachImmediateChild((node) => this._appendGuidanceElement(WI.ProfileView.GuidanceType.Hover, node, this._hoveredDataGridNode));
241         }
242     }
243
244     _mouseOverDataGrid(event)
245     {
246         let hoveredDataGridNode = this._dataGrid.dataGridNodeFromNode(event.target);
247         if (hoveredDataGridNode === this._hoveredDataGridNode)
248             return;
249
250         if (this._hoveredDataGridNode) {
251             this._removeGuidanceElement(WI.ProfileView.GuidanceType.Hover, this._hoveredDataGridNode);
252             this._hoveredDataGridNode.forEachChildInSubtree((node) => this._removeGuidanceElement(WI.ProfileView.GuidanceType.Hover, node));
253         }
254
255         this._hoveredDataGridNode = hoveredDataGridNode;
256
257         if (this._hoveredDataGridNode) {
258             this._appendGuidanceElement(WI.ProfileView.GuidanceType.Hover, this._hoveredDataGridNode, this._hoveredDataGridNode);
259             this._hoveredDataGridNode.forEachChildInSubtree((node) => this._appendGuidanceElement(WI.ProfileView.GuidanceType.Hover, node, this._hoveredDataGridNode));
260         }
261     }
262
263     _mouseLeaveDataGrid(event)
264     {
265         if (!this._hoveredDataGridNode)
266             return;
267
268         this._removeGuidanceElement(WI.ProfileView.GuidanceType.Hover, this._hoveredDataGridNode);
269         this._hoveredDataGridNode.forEachChildInSubtree((node) => this._removeGuidanceElement(WI.ProfileView.GuidanceType.Hover, node));
270
271         this._hoveredDataGridNode = null;
272     }
273
274     _guidanceElementKey(type)
275     {
276         return "guidance-element-" + type;
277     }
278
279     _removeGuidanceElement(type, node)
280     {
281         let key = this._guidanceElementKey(type);
282         let element = node.elementWithColumnIdentifier("function");
283         if (!element || !element[key])
284             return;
285
286         element[key].remove();
287         element[key] = null;
288     }
289
290     _appendGuidanceElement(type, node, baseElement)
291     {
292         let depth = baseElement.depth;
293         let guidanceMarkerLeft = depth ? (depth * this._dataGrid.indentWidth) + 1.5 : 7.5;
294
295         let key = this._guidanceElementKey(type);
296         let element = node.elementWithColumnIdentifier("function");
297         let guidanceElement = element[key] || element.appendChild(document.createElement("div"));
298         element[key] = guidanceElement;
299         guidanceElement.classList.add("guidance", type);
300         guidanceElement.classList.toggle("base", node === baseElement);
301         guidanceElement.style.left = guidanceMarkerLeft + "px";
302     }
303 };
304
305 WI.ProfileView.GuidanceType = {
306     Selected: "selected",
307     Hover: "hover",
308 };