1/*
2 * Copyright (C) 2009 280 North 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
26WebInspector.LegacyProfileDataGridNode = function(profileNode, owningTree, hasChildren, showTimeAsPercent)
27{
28    this.profileNode = profileNode;
29
30    WebInspector.DataGridNode.call(this, null, hasChildren);
31
32    this.addEventListener("populate", this._populate, this);
33
34    this.tree = owningTree;
35
36    this.childrenByCallUID = {};
37    this.lastComparator = null;
38
39    this.callUID = profileNode.callUID;
40    this.selfTime = profileNode.selfTime;
41    this.totalTime = profileNode.totalTime;
42    this.functionName = profileNode.functionName;
43    this.numberOfCalls = profileNode.numberOfCalls;
44    this.url = profileNode.url;
45
46    this._showTimeAsPercent = showTimeAsPercent || false;
47}
48
49WebInspector.LegacyProfileDataGridNode.prototype = {
50    get data()
51    {
52        function formatMilliseconds(time)
53        {
54            return Number.secondsToString(time / 1000, true);
55        }
56
57        var data = {};
58
59        data["function"] = this.functionName;
60        data["calls"] = this.numberOfCalls;
61
62        if (this._showTimeAsPercent) {
63            data["self"] = WebInspector.UIString("%.2f %%").format(this.selfPercent);
64            data["total"] = WebInspector.UIString("%.2f %%").format(this.totalPercent);
65            data["average"] = WebInspector.UIString("%.2f %%").format(this.averagePercent);
66        } else {
67            data["self"] = formatMilliseconds(this.selfTime);
68            data["total"] = formatMilliseconds(this.totalTime);
69            data["average"] = formatMilliseconds(this.averageTime);
70        }
71
72        return data;
73    },
74
75    get showTimeAsPercent()
76    {
77        return this._showTimeAsPercent;
78    },
79
80    refresh: function(showTimeAsPercent)
81    {
82        this._showTimeAsPercent = showTimeAsPercent;
83
84        WebInspector.DataGridNode.prototype.refresh.call(this);
85    },
86
87    createCell: function(columnIdentifier)
88    {
89        var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier);
90
91        if (columnIdentifier === "self" && this._searchMatchedSelfColumn)
92            cell.classList.add("highlight");
93        else if (columnIdentifier === "total" && this._searchMatchedTotalColumn)
94            cell.classList.add("highlight");
95        else if (columnIdentifier === "average" && this._searchMatchedAverageColumn)
96            cell.classList.add("highlight");
97        else if (columnIdentifier === "calls" && this._searchMatchedCallsColumn)
98            cell.classList.add("highlight");
99
100        if (columnIdentifier !== "function")
101            return cell;
102
103        if (this.profileNode._searchMatchedFunctionColumn)
104            cell.classList.add("highlight");
105
106        if (this.profileNode.url) {
107            // FIXME(62725): profileNode should reference a debugger location.
108            var lineNumber = this.profileNode.lineNumber ? this.profileNode.lineNumber - 1 : 0;
109            var urlElement = this._linkifyLocation(this.profileNode.url, lineNumber, "profile-node-file");
110            urlElement.style.maxWidth = "75%";
111            cell.insertBefore(urlElement, cell.firstChild);
112        }
113
114        return cell;
115    },
116
117    sort: function(comparator, force)
118    {
119        var gridNodeGroups = [[this]];
120
121        for (var gridNodeGroupIndex = 0; gridNodeGroupIndex < gridNodeGroups.length; ++gridNodeGroupIndex) {
122            var gridNodes = gridNodeGroups[gridNodeGroupIndex];
123            var count = gridNodes.length;
124
125            for (var index = 0; index < count; ++index) {
126                var gridNode = gridNodes[index];
127
128                // If the grid node is collapsed, then don't sort children (save operation for later).
129                // If the grid node has the same sorting as previously, then there is no point in sorting it again.
130                if (!force && (!gridNode.expanded || gridNode.lastComparator === comparator)) {
131                    if (gridNode.children.length)
132                        gridNode.shouldRefreshChildren = true;
133                    continue;
134                }
135
136                gridNode.lastComparator = comparator;
137
138                var children = gridNode.children;
139                var childCount = children.length;
140
141                if (childCount) {
142                    children.sort(comparator);
143
144                    for (var childIndex = 0; childIndex < childCount; ++childIndex)
145                        children[childIndex]._recalculateSiblings(childIndex);
146
147                    gridNodeGroups.push(children);
148                }
149            }
150        }
151    },
152
153    insertChild: function(profileDataGridNode, index)
154    {
155        WebInspector.DataGridNode.prototype.insertChild.call(this, profileDataGridNode, index);
156
157        this.childrenByCallUID[profileDataGridNode.callUID] = profileDataGridNode;
158    },
159
160    removeChild: function(profileDataGridNode)
161    {
162        WebInspector.DataGridNode.prototype.removeChild.call(this, profileDataGridNode);
163
164        delete this.childrenByCallUID[profileDataGridNode.callUID];
165    },
166
167    removeChildren: function(profileDataGridNode)
168    {
169        WebInspector.DataGridNode.prototype.removeChildren.call(this);
170
171        this.childrenByCallUID = {};
172    },
173
174    findChild: function(node)
175    {
176        if (!node)
177            return null;
178        return this.childrenByCallUID[node.callUID];
179    },
180
181    get averageTime()
182    {
183        return this.selfTime / Math.max(1, this.numberOfCalls);
184    },
185
186    get averagePercent()
187    {
188        return this.averageTime / this.tree.totalTime * 100.0;
189    },
190
191    get selfPercent()
192    {
193        return this.selfTime / this.tree.totalTime * 100.0;
194    },
195
196    get totalPercent()
197    {
198        return this.totalTime / this.tree.totalTime * 100.0;
199    },
200
201    get _parent()
202    {
203        return this.parent !== this.dataGrid ? this.parent : this.tree;
204    },
205
206    _populate: function(event)
207    {
208        this._sharedPopulate();
209
210        if (this._parent) {
211            var currentComparator = this._parent.lastComparator;
212
213            if (currentComparator)
214                this.sort(currentComparator, true);
215        }
216
217        if (this.removeEventListener)
218            this.removeEventListener("populate", this._populate, this);
219    },
220
221    // When focusing and collapsing we modify lots of nodes in the tree.
222    // This allows us to restore them all to their original state when we revert.
223    _save: function()
224    {
225        if (this._savedChildren)
226            return;
227
228        this._savedSelfTime = this.selfTime;
229        this._savedTotalTime = this.totalTime;
230        this._savedNumberOfCalls = this.numberOfCalls;
231
232        this._savedChildren = this.children.slice();
233    },
234
235    // When focusing and collapsing we modify lots of nodes in the tree.
236    // This allows us to restore them all to their original state when we revert.
237    _restore: function()
238    {
239        if (!this._savedChildren)
240            return;
241
242        this.selfTime = this._savedSelfTime;
243        this.totalTime = this._savedTotalTime;
244        this.numberOfCalls = this._savedNumberOfCalls;
245
246        this.removeChildren();
247
248        var children = this._savedChildren;
249        var count = children.length;
250
251        for (var index = 0; index < count; ++index) {
252            children[index]._restore();
253            this.appendChild(children[index]);
254        }
255    },
256
257    _merge: function(child, shouldAbsorb)
258    {
259        this.selfTime += child.selfTime;
260
261        if (!shouldAbsorb) {
262            this.totalTime += child.totalTime;
263            this.numberOfCalls += child.numberOfCalls;
264        }
265
266        var children = this.children.slice();
267
268        this.removeChildren();
269
270        var count = children.length;
271
272        for (var index = 0; index < count; ++index) {
273            if (!shouldAbsorb || children[index] !== child)
274                this.appendChild(children[index]);
275        }
276
277        children = child.children.slice();
278        count = children.length;
279
280        for (var index = 0; index < count; ++index) {
281            var orphanedChild = children[index],
282                existingChild = this.childrenByCallUID[orphanedChild.callUID];
283
284            if (existingChild)
285                existingChild._merge(orphanedChild, false);
286            else
287                this.appendChild(orphanedChild);
288        }
289    },
290
291    _linkifyLocation: function(url, lineNumber, className)
292    {
293        // FIXME: CPUProfileNode's need columnNumbers.
294        return WebInspector.linkifyLocation(url, lineNumber, 0, className);
295    }
296}
297
298WebInspector.LegacyProfileDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;
299
300WebInspector.LegacyProfileDataGridTree = function(profileNode)
301{
302    this.tree = this;
303    this.children = [];
304
305    this.totalTime = profileNode.totalTime;
306    this.lastComparator = null;
307
308    this.childrenByCallUID = {};
309}
310
311WebInspector.LegacyProfileDataGridTree.prototype = {
312    get expanded()
313    {
314        return true;
315    },
316
317    appendChild: function(child)
318    {
319        this.insertChild(child, this.children.length);
320    },
321
322    insertChild: function(child, index)
323    {
324        this.children.splice(index, 0, child);
325        this.childrenByCallUID[child.callUID] = child;
326    },
327
328    removeChildren: function()
329    {
330        this.children = [];
331        this.childrenByCallUID = {};
332    },
333
334    findChild: WebInspector.LegacyProfileDataGridNode.prototype.findChild,
335    sort: WebInspector.LegacyProfileDataGridNode.prototype.sort,
336
337    _save: function()
338    {
339        if (this._savedChildren)
340            return;
341
342        this._savedTotalTime = this.totalTime;
343        this._savedChildren = this.children.slice();
344    },
345
346    restore: function()
347    {
348        if (!this._savedChildren)
349            return;
350
351        this.children = this._savedChildren;
352        this.totalTime = this._savedTotalTime;
353
354        var children = this.children;
355        var count = children.length;
356
357        for (var index = 0; index < count; ++index)
358            children[index]._restore();
359
360        this._savedChildren = null;
361    }
362}
363
364WebInspector.LegacyProfileDataGridTree.propertyComparators = [{}, {}];
365
366WebInspector.LegacyProfileDataGridTree.propertyComparator = function(property, isAscending)
367{
368    var comparator = this.propertyComparators[(isAscending ? 1 : 0)][property];
369
370    if (!comparator) {
371        if (isAscending) {
372            comparator = function(lhs, rhs)
373            {
374                if (lhs[property] < rhs[property])
375                    return -1;
376
377                if (lhs[property] > rhs[property])
378                    return 1;
379
380                return 0;
381            };
382        } else {
383            comparator = function(lhs, rhs)
384            {
385                if (lhs[property] > rhs[property])
386                    return -1;
387
388                if (lhs[property] < rhs[property])
389                    return 1;
390
391                return 0;
392            };
393        }
394
395        this.propertyComparators[(isAscending ? 1 : 0)][property] = comparator;
396    }
397
398    return comparator;
399}
400