Adds a percentage toggle button the profile view status bar that
[WebKit-https.git] / WebCore / page / inspector / ProfileView.js
1 /*
2  * Copyright (C) 2008 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. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WebInspector.ProfileView = function(profile)
27 {
28     WebInspector.View.call(this);
29
30     this.element.addStyleClass("profile-view");
31
32     this.profile = profile;
33
34     this.showSelfTimeAsPercent = true;
35     this.showTotalTimeAsPercent = true;
36
37     var columns = { "self": { title: WebInspector.UIString("Self"), width: "72px", sortable: true },
38                     "total": { title: WebInspector.UIString("Total"), width: "72px", sort: "descending", sortable: true },
39                     "calls": { title: WebInspector.UIString("Calls"), width: "54px", sortable: true },
40                     "function": { title: WebInspector.UIString("Function"), disclosure: true, sortable: true } };
41
42     this.dataGrid = new WebInspector.DataGrid(columns);
43     this.dataGrid.addEventListener("sorting changed", this._sortData, this);
44     this.dataGrid.element.addEventListener("mousedown", this._mouseDownInDataGrid.bind(this), true);
45     this.element.appendChild(this.dataGrid.element);
46
47     this.percentButton = document.createElement("button");
48     this.percentButton.className = "percent-time-status-bar-item status-bar-item";
49     this.percentButton.addEventListener("click", this._percentClicked.bind(this), false);
50
51     this.focusButton = document.createElement("button");
52     this.focusButton.title = WebInspector.UIString("Focus selected function.");
53     this.focusButton.className = "focus-profile-node-status-bar-item status-bar-item";
54     this.focusButton.disabled = true;
55     this.focusButton.addEventListener("click", this._focusClicked.bind(this), false);
56
57     this.excludeButton = document.createElement("button");
58     this.excludeButton.title = WebInspector.UIString("Exclude selected function.");
59     this.excludeButton.className = "exclude-profile-node-status-bar-item status-bar-item";
60     this.excludeButton.disabled = true;
61     this.excludeButton.addEventListener("click", this._excludeClicked.bind(this), false);
62
63     this.resetButton = document.createElement("button");
64     this.resetButton.title = WebInspector.UIString("Restore all functions.");
65     this.resetButton.className = "reset-profile-status-bar-item status-bar-item hidden";
66     this.resetButton.addEventListener("click", this._resetClicked.bind(this), false);
67
68     // By default the profile isn't sorted, so sort based on our default sort
69     // column and direction padded to the DataGrid above.
70     profile.head.sortTotalTimeDescending();
71
72     this._updatePercentButton();
73
74     this.refresh();
75 }
76
77 WebInspector.ProfileView.prototype = {
78     get statusBarItems()
79     {
80         return [this.percentButton, this.focusButton, this.excludeButton, this.resetButton];
81     },
82
83     refresh: function()
84     {
85         var selectedProfileNode = this.dataGrid.selectedNode ? this.dataGrid.selectedNode.profileNode : null;
86
87         this.dataGrid.removeChildren();
88
89         var children = this.profile.head.children;
90         var childrenLength = children.length;
91         for (var i = 0; i < childrenLength; ++i)
92             if (children[i].visible)
93                 this.dataGrid.appendChild(new WebInspector.ProfileDataGridNode(this, children[i]));
94
95         if (selectedProfileNode && selectedProfileNode._dataGridNode)
96             selectedProfileNode._dataGridNode.selected = true;
97     },
98
99     refreshShowAsPercents: function()
100     {
101         this._updatePercentButton();
102
103         var child = this.dataGrid.children[0];
104         while (child) {
105             child.refresh();
106             child = child.traverseNextNode(false, null, true);
107         }
108     },
109
110     _percentClicked: function(event)
111     {
112         var currentState = this.showSelfTimeAsPercent && this.showTotalTimeAsPercent;
113         this.showSelfTimeAsPercent = !currentState;
114         this.showTotalTimeAsPercent = !currentState;
115         this.refreshShowAsPercents();
116     },
117
118     _updatePercentButton: function()
119     {
120         if (this.showSelfTimeAsPercent && this.showTotalTimeAsPercent) {
121             this.percentButton.title = WebInspector.UIString("Show absolute total and self times.");
122             this.percentButton.addStyleClass("toggled-on");
123         } else {
124             this.percentButton.title = WebInspector.UIString("Show total and self times as percentages.");
125             this.percentButton.removeStyleClass("toggled-on");
126         }
127     },
128
129     _focusClicked: function(event)
130     {
131         if (!this.dataGrid.selectedNode || !this.dataGrid.selectedNode.profileNode)
132             return;
133         this.resetButton.removeStyleClass("hidden");
134         this.profile.focus(this.dataGrid.selectedNode.profileNode);
135         this.refresh();
136     },
137
138     _excludeClicked: function(event)
139     {
140         if (!this.dataGrid.selectedNode || !this.dataGrid.selectedNode.profileNode)
141             return;
142         this.resetButton.removeStyleClass("hidden");
143         this.profile.exclude(this.dataGrid.selectedNode.profileNode);
144         this.dataGrid.selectedNode.deselect();
145         this.refresh();
146     },
147
148     _resetClicked: function(event)
149     {
150         this.resetButton.addStyleClass("hidden");
151         this.profile.restoreAll();
152         this.refresh();
153     },
154
155     _dataGridNodeSelected: function(node)
156     {
157         this.focusButton.disabled = false;
158         this.excludeButton.disabled = false;
159     },
160
161     _dataGridNodeDeselected: function(node)
162     {
163         this.focusButton.disabled = true;
164         this.excludeButton.disabled = true;
165     },
166
167     _sortData: function(event)
168     {
169         var sortOrder = this.dataGrid.sortOrder;
170         var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier;
171
172         var sortingFunctionName = "sort";
173
174         if (sortColumnIdentifier === "self")
175             sortingFunctionName += "SelfTime";
176         else if (sortColumnIdentifier === "total")
177             sortingFunctionName += "TotalTime";
178         else if (sortColumnIdentifier === "calls")
179             sortingFunctionName += "Calls";
180         else if (sortColumnIdentifier === "function")
181             sortingFunctionName += "FunctionName";
182
183         if (sortOrder === "ascending")
184             sortingFunctionName += "Ascending";
185         else
186             sortingFunctionName += "Descending";
187
188         if (!(sortingFunctionName in this.profile.head))
189             return;
190
191         this.profile.head[sortingFunctionName]();
192
193         this.refresh();
194     },
195
196     _mouseDownInDataGrid: function(event)
197     {
198         if (event.detail < 2)
199             return;
200
201         var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
202         if (!cell || (!cell.hasStyleClass("total-column") && !cell.hasStyleClass("self-column")))
203             return;
204
205         if (cell.hasStyleClass("total-column"))
206             this.showTotalTimeAsPercent = !this.showTotalTimeAsPercent;
207         else if (cell.hasStyleClass("self-column"))
208             this.showSelfTimeAsPercent = !this.showSelfTimeAsPercent;
209
210         this.refreshShowAsPercents();
211
212         event.preventDefault();
213         event.stopPropagation();
214     }
215 }
216
217 WebInspector.ProfileView.prototype.__proto__ = WebInspector.View.prototype;
218
219 WebInspector.ProfileDataGridNode = function(profileView, profileNode)
220 {
221     this.profileView = profileView;
222
223     this.profileNode = profileNode;
224     profileNode._dataGridNode = this;
225
226     // Find the first child that is visible. Since we don't want to claim
227     // we have children if all the children are invisible.
228     var hasChildren = false;
229     var children = this.profileNode.children;
230     var childrenLength = children.length;
231     for (var i = 0; i < childrenLength; ++i) {
232         if (children[i].visible) {
233             hasChildren = true;
234             break;
235         }
236     }
237
238     WebInspector.DataGridNode.call(this, null, hasChildren);
239
240     this.addEventListener("populate", this._populate, this);
241
242     this.expanded = profileNode._expanded;
243 }
244
245 WebInspector.ProfileDataGridNode.prototype = {
246     get data()
247     {
248         function formatMilliseconds(time)
249         {
250             return Number.secondsToString(time / 1000, WebInspector.UIString.bind(WebInspector), true);
251         }
252
253         var data = {};
254         data["function"] = this.profileNode.functionName;
255         data["calls"] = this.profileNode.numberOfCalls;
256
257         if (this.profileView.showSelfTimeAsPercent)
258             data["self"] = WebInspector.UIString("%.2f%%", this.profileNode.selfPercent);
259         else
260             data["self"] = formatMilliseconds(this.profileNode.selfTime);
261
262         if (this.profileView.showTotalTimeAsPercent)
263             data["total"] = WebInspector.UIString("%.2f%%", this.profileNode.totalPercent);
264         else
265             data["total"] = formatMilliseconds(this.profileNode.totalTime);
266
267         return data;
268     },
269
270     createCell: function(columnIdentifier)
271     {
272         var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier);
273         if (columnIdentifier !== "function")
274             return cell;
275
276         if (this.profileNode.url) {
277             var fileName = WebInspector.displayNameForURL(this.profileNode.url);
278
279             var urlElement = document.createElement("a");
280             urlElement.className = "profile-node-file webkit-html-resource-link";
281             urlElement.href = this.profileNode.url;
282             urlElement.lineNumber = this.profileNode.lineNumber;
283
284             if (this.profileNode.lineNumber > 0)
285                 urlElement.textContent = fileName + ":" + this.profileNode.lineNumber;
286             else
287                 urlElement.textContent = fileName;
288
289             cell.insertBefore(urlElement, cell.firstChild);
290         }
291
292         return cell;
293     },
294
295     select: function(supressSelectedEvent)
296     {
297         WebInspector.DataGridNode.prototype.select.call(this, supressSelectedEvent);
298         this.profileView._dataGridNodeSelected(this);
299     },
300
301     deselect: function(supressDeselectedEvent)
302     {
303         WebInspector.DataGridNode.prototype.deselect.call(this, supressDeselectedEvent);
304         this.profileView._dataGridNodeDeselected(this);
305     },
306
307     expand: function()
308     {
309         WebInspector.DataGridNode.prototype.expand.call(this);
310         this.profileNode._expanded = true;
311     },
312
313     collapse: function()
314     {
315         WebInspector.DataGridNode.prototype.collapse.call(this);
316         this.profileNode._expanded = false;
317     },
318
319     _populate: function(event)
320     {
321         var children = this.profileNode.children;
322         var childrenLength = children.length;
323         for (var i = 0; i < childrenLength; ++i)
324             if (children[i].visible)
325                 this.appendChild(new WebInspector.ProfileDataGridNode(this.profileView, children[i]));
326         this.removeEventListener("populate", this._populate, this);
327     }
328 }
329
330 WebInspector.ProfileDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;