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/**
27 * @constructor
28 * @extends {WebInspector.View}
29 * @param {WebInspector.CPUProfileHeader} profile
30 */
31WebInspector.CPUProfileView = function(profile)
32{
33    WebInspector.View.call(this);
34
35    this.element.addStyleClass("profile-view");
36
37    this.showSelfTimeAsPercent = WebInspector.settings.createSetting("cpuProfilerShowSelfTimeAsPercent", true);
38    this.showTotalTimeAsPercent = WebInspector.settings.createSetting("cpuProfilerShowTotalTimeAsPercent", true);
39    this.showAverageTimeAsPercent = WebInspector.settings.createSetting("cpuProfilerShowAverageTimeAsPercent", true);
40    this._viewType = WebInspector.settings.createSetting("cpuProfilerView", WebInspector.CPUProfileView._TypeHeavy);
41
42    var columns = [];
43    columns.push({id: "self", title: WebInspector.UIString("Self"), width: "72px", sort: WebInspector.DataGrid.Order.Descending, sortable: true});
44    columns.push({id: "total", title: WebInspector.UIString("Total"), width: "72px", sortable: true});
45    if (!Capabilities.samplingCPUProfiler) {
46        columns.push({id: "average", title: WebInspector.UIString("Average"), width: "72px", sortable: true});
47        columns.push({id: "calls", title: WebInspector.UIString("Calls"), width: "54px", sortable: true});
48    }
49    columns.push({id: "function", title: WebInspector.UIString("Function"), disclosure: true, sortable: true});
50
51    this.dataGrid = new WebInspector.DataGrid(columns);
52    this.dataGrid.addEventListener(WebInspector.DataGrid.Events.SortingChanged, this._sortProfile, this);
53    this.dataGrid.element.addEventListener("mousedown", this._mouseDownInDataGrid.bind(this), true);
54
55    if (WebInspector.experimentsSettings.cpuFlameChart.isEnabled()) {
56        this._splitView = new WebInspector.SplitView(false, "flameChartSplitLocation");
57        this._splitView.show(this.element);
58
59        this.flameChart = new WebInspector.FlameChart(this);
60        this.flameChart.addEventListener(WebInspector.FlameChart.Events.SelectedNode, this._revealProfilerNode.bind(this));
61        this.flameChart.show(this._splitView.firstElement());
62
63        this.dataGrid.show(this._splitView.secondElement());
64    } else
65        this.dataGrid.show(this.element);
66
67    this.viewSelectComboBox = new WebInspector.StatusBarComboBox(this._changeView.bind(this));
68
69    var heavyViewOption = this.viewSelectComboBox.createOption(WebInspector.UIString("Heavy (Bottom Up)"), "", WebInspector.CPUProfileView._TypeHeavy);
70    var treeViewOption = this.viewSelectComboBox.createOption(WebInspector.UIString("Tree (Top Down)"), "", WebInspector.CPUProfileView._TypeTree);
71    this.viewSelectComboBox.select(this._viewType.get() === WebInspector.CPUProfileView._TypeHeavy ? heavyViewOption : treeViewOption);
72
73    this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item");
74    this.percentButton.addEventListener("click", this._percentClicked, this);
75
76    this.focusButton = new WebInspector.StatusBarButton(WebInspector.UIString("Focus selected function."), "focus-profile-node-status-bar-item");
77    this.focusButton.setEnabled(false);
78    this.focusButton.addEventListener("click", this._focusClicked, this);
79
80    this.excludeButton = new WebInspector.StatusBarButton(WebInspector.UIString("Exclude selected function."), "exclude-profile-node-status-bar-item");
81    this.excludeButton.setEnabled(false);
82    this.excludeButton.addEventListener("click", this._excludeClicked, this);
83
84    this.resetButton = new WebInspector.StatusBarButton(WebInspector.UIString("Restore all functions."), "reset-profile-status-bar-item");
85    this.resetButton.visible = false;
86    this.resetButton.addEventListener("click", this._resetClicked, this);
87
88    this.profileHead = /** @type {?ProfilerAgent.CPUProfileNode} */ (null);
89    this.profile = profile;
90
91    this._linkifier = new WebInspector.Linkifier(new WebInspector.Linkifier.DefaultFormatter(30));
92
93    ProfilerAgent.getCPUProfile(this.profile.uid, this._getCPUProfileCallback.bind(this));
94}
95
96WebInspector.CPUProfileView._TypeTree = "Tree";
97WebInspector.CPUProfileView._TypeHeavy = "Heavy";
98
99WebInspector.CPUProfileView.prototype = {
100    _revealProfilerNode: function(event)
101    {
102        var current = this.profileDataGridTree.children[0];
103
104        while (current && current.profileNode !== event.data)
105            current = current.traverseNextNode(false, null, false);
106
107        if (current)
108            current.revealAndSelect();
109    },
110
111    /**
112     * @param {?Protocol.Error} error
113     * @param {ProfilerAgent.CPUProfile} profile
114     */
115    _getCPUProfileCallback: function(error, profile)
116    {
117        if (error)
118            return;
119
120        if (!profile.head) {
121            // Profiling was tentatively terminated with the "Clear all profiles." button.
122            return;
123        }
124        this.profileHead = profile.head;
125        this.samples = profile.samples;
126
127        if (profile.idleTime)
128            this._injectIdleTimeNode(profile);
129
130        this._assignParentsInProfile();
131        if (this.samples)
132            this._buildIdToNodeMap();
133        this._changeView();
134        this._updatePercentButton();
135        if (this.flameChart)
136            this.flameChart.update();
137    },
138
139    statusBarItems: function()
140    {
141        return [this.viewSelectComboBox.element, this.percentButton.element, this.focusButton.element, this.excludeButton.element, this.resetButton.element];
142    },
143
144    /**
145     * @return {!WebInspector.ProfileDataGridTree}
146     */
147    _getBottomUpProfileDataGridTree: function()
148    {
149        if (!this._bottomUpProfileDataGridTree)
150            this._bottomUpProfileDataGridTree = new WebInspector.BottomUpProfileDataGridTree(this, this.profileHead);
151        return this._bottomUpProfileDataGridTree;
152    },
153
154    /**
155     * @return {!WebInspector.ProfileDataGridTree}
156     */
157    _getTopDownProfileDataGridTree: function()
158    {
159        if (!this._topDownProfileDataGridTree)
160            this._topDownProfileDataGridTree = new WebInspector.TopDownProfileDataGridTree(this, this.profileHead);
161        return this._topDownProfileDataGridTree;
162    },
163
164    willHide: function()
165    {
166        this._currentSearchResultIndex = -1;
167    },
168
169    refresh: function()
170    {
171        var selectedProfileNode = this.dataGrid.selectedNode ? this.dataGrid.selectedNode.profileNode : null;
172
173        this.dataGrid.rootNode().removeChildren();
174
175        var children = this.profileDataGridTree.children;
176        var count = children.length;
177
178        for (var index = 0; index < count; ++index)
179            this.dataGrid.rootNode().appendChild(children[index]);
180
181        if (selectedProfileNode)
182            selectedProfileNode.selected = true;
183    },
184
185    refreshVisibleData: function()
186    {
187        var child = this.dataGrid.rootNode().children[0];
188        while (child) {
189            child.refresh();
190            child = child.traverseNextNode(false, null, true);
191        }
192    },
193
194    refreshShowAsPercents: function()
195    {
196        this._updatePercentButton();
197        this.refreshVisibleData();
198    },
199
200    searchCanceled: function()
201    {
202        if (this._searchResults) {
203            for (var i = 0; i < this._searchResults.length; ++i) {
204                var profileNode = this._searchResults[i].profileNode;
205
206                delete profileNode._searchMatchedSelfColumn;
207                delete profileNode._searchMatchedTotalColumn;
208                delete profileNode._searchMatchedAverageColumn;
209                delete profileNode._searchMatchedCallsColumn;
210                delete profileNode._searchMatchedFunctionColumn;
211
212                profileNode.refresh();
213            }
214        }
215
216        delete this._searchFinishedCallback;
217        this._currentSearchResultIndex = -1;
218        this._searchResults = [];
219    },
220
221    performSearch: function(query, finishedCallback)
222    {
223        // Call searchCanceled since it will reset everything we need before doing a new search.
224        this.searchCanceled();
225
226        query = query.trim();
227
228        if (!query.length)
229            return;
230
231        this._searchFinishedCallback = finishedCallback;
232
233        var greaterThan = (query.startsWith(">"));
234        var lessThan = (query.startsWith("<"));
235        var equalTo = (query.startsWith("=") || ((greaterThan || lessThan) && query.indexOf("=") === 1));
236        var percentUnits = (query.lastIndexOf("%") === (query.length - 1));
237        var millisecondsUnits = (query.length > 2 && query.lastIndexOf("ms") === (query.length - 2));
238        var secondsUnits = (!millisecondsUnits && query.lastIndexOf("s") === (query.length - 1));
239
240        var queryNumber = parseFloat(query);
241        if (greaterThan || lessThan || equalTo) {
242            if (equalTo && (greaterThan || lessThan))
243                queryNumber = parseFloat(query.substring(2));
244            else
245                queryNumber = parseFloat(query.substring(1));
246        }
247
248        var queryNumberMilliseconds = (secondsUnits ? (queryNumber * 1000) : queryNumber);
249
250        // Make equalTo implicitly true if it wasn't specified there is no other operator.
251        if (!isNaN(queryNumber) && !(greaterThan || lessThan))
252            equalTo = true;
253
254        var matcher = new RegExp(query.escapeForRegExp(), "i");
255
256        function matchesQuery(/*ProfileDataGridNode*/ profileDataGridNode)
257        {
258            delete profileDataGridNode._searchMatchedSelfColumn;
259            delete profileDataGridNode._searchMatchedTotalColumn;
260            delete profileDataGridNode._searchMatchedAverageColumn;
261            delete profileDataGridNode._searchMatchedCallsColumn;
262            delete profileDataGridNode._searchMatchedFunctionColumn;
263
264            if (percentUnits) {
265                if (lessThan) {
266                    if (profileDataGridNode.selfPercent < queryNumber)
267                        profileDataGridNode._searchMatchedSelfColumn = true;
268                    if (profileDataGridNode.totalPercent < queryNumber)
269                        profileDataGridNode._searchMatchedTotalColumn = true;
270                    if (profileDataGridNode.averagePercent < queryNumberMilliseconds)
271                        profileDataGridNode._searchMatchedAverageColumn = true;
272                } else if (greaterThan) {
273                    if (profileDataGridNode.selfPercent > queryNumber)
274                        profileDataGridNode._searchMatchedSelfColumn = true;
275                    if (profileDataGridNode.totalPercent > queryNumber)
276                        profileDataGridNode._searchMatchedTotalColumn = true;
277                    if (profileDataGridNode.averagePercent < queryNumberMilliseconds)
278                        profileDataGridNode._searchMatchedAverageColumn = true;
279                }
280
281                if (equalTo) {
282                    if (profileDataGridNode.selfPercent == queryNumber)
283                        profileDataGridNode._searchMatchedSelfColumn = true;
284                    if (profileDataGridNode.totalPercent == queryNumber)
285                        profileDataGridNode._searchMatchedTotalColumn = true;
286                    if (profileDataGridNode.averagePercent < queryNumberMilliseconds)
287                        profileDataGridNode._searchMatchedAverageColumn = true;
288                }
289            } else if (millisecondsUnits || secondsUnits) {
290                if (lessThan) {
291                    if (profileDataGridNode.selfTime < queryNumberMilliseconds)
292                        profileDataGridNode._searchMatchedSelfColumn = true;
293                    if (profileDataGridNode.totalTime < queryNumberMilliseconds)
294                        profileDataGridNode._searchMatchedTotalColumn = true;
295                    if (profileDataGridNode.averageTime < queryNumberMilliseconds)
296                        profileDataGridNode._searchMatchedAverageColumn = true;
297                } else if (greaterThan) {
298                    if (profileDataGridNode.selfTime > queryNumberMilliseconds)
299                        profileDataGridNode._searchMatchedSelfColumn = true;
300                    if (profileDataGridNode.totalTime > queryNumberMilliseconds)
301                        profileDataGridNode._searchMatchedTotalColumn = true;
302                    if (profileDataGridNode.averageTime > queryNumberMilliseconds)
303                        profileDataGridNode._searchMatchedAverageColumn = true;
304                }
305
306                if (equalTo) {
307                    if (profileDataGridNode.selfTime == queryNumberMilliseconds)
308                        profileDataGridNode._searchMatchedSelfColumn = true;
309                    if (profileDataGridNode.totalTime == queryNumberMilliseconds)
310                        profileDataGridNode._searchMatchedTotalColumn = true;
311                    if (profileDataGridNode.averageTime == queryNumberMilliseconds)
312                        profileDataGridNode._searchMatchedAverageColumn = true;
313                }
314            } else {
315                if (equalTo && profileDataGridNode.numberOfCalls == queryNumber)
316                    profileDataGridNode._searchMatchedCallsColumn = true;
317                if (greaterThan && profileDataGridNode.numberOfCalls > queryNumber)
318                    profileDataGridNode._searchMatchedCallsColumn = true;
319                if (lessThan && profileDataGridNode.numberOfCalls < queryNumber)
320                    profileDataGridNode._searchMatchedCallsColumn = true;
321            }
322
323            if (profileDataGridNode.functionName.match(matcher) || (profileDataGridNode.url && profileDataGridNode.url.match(matcher)))
324                profileDataGridNode._searchMatchedFunctionColumn = true;
325
326            if (profileDataGridNode._searchMatchedSelfColumn ||
327                profileDataGridNode._searchMatchedTotalColumn ||
328                profileDataGridNode._searchMatchedAverageColumn ||
329                profileDataGridNode._searchMatchedCallsColumn ||
330                profileDataGridNode._searchMatchedFunctionColumn)
331            {
332                profileDataGridNode.refresh();
333                return true;
334            }
335
336            return false;
337        }
338
339        var current = this.profileDataGridTree.children[0];
340
341        while (current) {
342            if (matchesQuery(current)) {
343                this._searchResults.push({ profileNode: current });
344            }
345
346            current = current.traverseNextNode(false, null, false);
347        }
348
349        finishedCallback(this, this._searchResults.length);
350    },
351
352    jumpToFirstSearchResult: function()
353    {
354        if (!this._searchResults || !this._searchResults.length)
355            return;
356        this._currentSearchResultIndex = 0;
357        this._jumpToSearchResult(this._currentSearchResultIndex);
358    },
359
360    jumpToLastSearchResult: function()
361    {
362        if (!this._searchResults || !this._searchResults.length)
363            return;
364        this._currentSearchResultIndex = (this._searchResults.length - 1);
365        this._jumpToSearchResult(this._currentSearchResultIndex);
366    },
367
368    jumpToNextSearchResult: function()
369    {
370        if (!this._searchResults || !this._searchResults.length)
371            return;
372        if (++this._currentSearchResultIndex >= this._searchResults.length)
373            this._currentSearchResultIndex = 0;
374        this._jumpToSearchResult(this._currentSearchResultIndex);
375    },
376
377    jumpToPreviousSearchResult: function()
378    {
379        if (!this._searchResults || !this._searchResults.length)
380            return;
381        if (--this._currentSearchResultIndex < 0)
382            this._currentSearchResultIndex = (this._searchResults.length - 1);
383        this._jumpToSearchResult(this._currentSearchResultIndex);
384    },
385
386    showingFirstSearchResult: function()
387    {
388        return (this._currentSearchResultIndex === 0);
389    },
390
391    showingLastSearchResult: function()
392    {
393        return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1));
394    },
395
396    _jumpToSearchResult: function(index)
397    {
398        var searchResult = this._searchResults[index];
399        if (!searchResult)
400            return;
401
402        var profileNode = searchResult.profileNode;
403        profileNode.revealAndSelect();
404    },
405
406    _changeView: function()
407    {
408        if (!this.profile)
409            return;
410
411        switch (this.viewSelectComboBox.selectedOption().value) {
412        case WebInspector.CPUProfileView._TypeTree:
413            this.profileDataGridTree = this._getTopDownProfileDataGridTree();
414            this._sortProfile();
415            this._viewType.set(WebInspector.CPUProfileView._TypeTree);
416            break;
417        case WebInspector.CPUProfileView._TypeHeavy:
418            this.profileDataGridTree = this._getBottomUpProfileDataGridTree();
419            this._sortProfile();
420            this._viewType.set(WebInspector.CPUProfileView._TypeHeavy);
421        }
422
423        if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
424            return;
425
426        // The current search needs to be performed again. First negate out previous match
427        // count by calling the search finished callback with a negative number of matches.
428        // Then perform the search again the with same query and callback.
429        this._searchFinishedCallback(this, -this._searchResults.length);
430        this.performSearch(this.currentQuery, this._searchFinishedCallback);
431    },
432
433    _percentClicked: function(event)
434    {
435        var currentState = this.showSelfTimeAsPercent.get() && this.showTotalTimeAsPercent.get() && this.showAverageTimeAsPercent.get();
436        this.showSelfTimeAsPercent.set(!currentState);
437        this.showTotalTimeAsPercent.set(!currentState);
438        this.showAverageTimeAsPercent.set(!currentState);
439        this.refreshShowAsPercents();
440    },
441
442    _updatePercentButton: function()
443    {
444        if (this.showSelfTimeAsPercent.get() && this.showTotalTimeAsPercent.get() && this.showAverageTimeAsPercent.get()) {
445            this.percentButton.title = WebInspector.UIString("Show absolute total and self times.");
446            this.percentButton.toggled = true;
447        } else {
448            this.percentButton.title = WebInspector.UIString("Show total and self times as percentages.");
449            this.percentButton.toggled = false;
450        }
451    },
452
453    _focusClicked: function(event)
454    {
455        if (!this.dataGrid.selectedNode)
456            return;
457
458        this.resetButton.visible = true;
459        this.profileDataGridTree.focus(this.dataGrid.selectedNode);
460        this.refresh();
461        this.refreshVisibleData();
462    },
463
464    _excludeClicked: function(event)
465    {
466        var selectedNode = this.dataGrid.selectedNode
467
468        if (!selectedNode)
469            return;
470
471        selectedNode.deselect();
472
473        this.resetButton.visible = true;
474        this.profileDataGridTree.exclude(selectedNode);
475        this.refresh();
476        this.refreshVisibleData();
477    },
478
479    _resetClicked: function(event)
480    {
481        this.resetButton.visible = false;
482        this.profileDataGridTree.restore();
483        this._linkifier.reset();
484        this.refresh();
485        this.refreshVisibleData();
486    },
487
488    _dataGridNodeSelected: function(node)
489    {
490        this.focusButton.setEnabled(true);
491        this.excludeButton.setEnabled(true);
492    },
493
494    _dataGridNodeDeselected: function(node)
495    {
496        this.focusButton.setEnabled(false);
497        this.excludeButton.setEnabled(false);
498    },
499
500    _sortProfile: function()
501    {
502        var sortAscending = this.dataGrid.isSortOrderAscending();
503        var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier();
504        var sortProperty = {
505                "average": "averageTime",
506                "self": "selfTime",
507                "total": "totalTime",
508                "calls": "numberOfCalls",
509                "function": "functionName"
510            }[sortColumnIdentifier];
511
512        this.profileDataGridTree.sort(WebInspector.ProfileDataGridTree.propertyComparator(sortProperty, sortAscending));
513
514        this.refresh();
515    },
516
517    _mouseDownInDataGrid: function(event)
518    {
519        if (event.detail < 2)
520            return;
521
522        var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
523        if (!cell || (!cell.hasStyleClass("total-column") && !cell.hasStyleClass("self-column") && !cell.hasStyleClass("average-column")))
524            return;
525
526        if (cell.hasStyleClass("total-column"))
527            this.showTotalTimeAsPercent.set(!this.showTotalTimeAsPercent.get());
528        else if (cell.hasStyleClass("self-column"))
529            this.showSelfTimeAsPercent.set(!this.showSelfTimeAsPercent.get());
530        else if (cell.hasStyleClass("average-column"))
531            this.showAverageTimeAsPercent.set(!this.showAverageTimeAsPercent.get());
532
533        this.refreshShowAsPercents();
534
535        event.consume(true);
536    },
537
538    _assignParentsInProfile: function()
539    {
540        var head = this.profileHead;
541        head.parent = null;
542        head.head = null;
543        var nodesToTraverse = [ { parent: head, children: head.children } ];
544        while (nodesToTraverse.length > 0) {
545            var pair = nodesToTraverse.pop();
546            var parent = pair.parent;
547            var children = pair.children;
548            var length = children.length;
549            for (var i = 0; i < length; ++i) {
550                children[i].head = head;
551                children[i].parent = parent;
552                if (children[i].children.length > 0)
553                    nodesToTraverse.push({ parent: children[i], children: children[i].children });
554            }
555        }
556    },
557
558    _buildIdToNodeMap: function()
559    {
560        var idToNode = this._idToNode = {};
561        var stack = [this.profileHead];
562        while (stack.length) {
563            var node = stack.pop();
564            idToNode[node.id] = node;
565            for (var i = 0; i < node.children.length; i++)
566                stack.push(node.children[i]);
567        }
568    },
569
570    /**
571     * @param {ProfilerAgent.CPUProfile} profile
572     */
573    _injectIdleTimeNode: function(profile)
574    {
575        var idleTime = profile.idleTime;
576        var nodes = profile.head.children;
577
578        var programNode = {selfTime: 0};
579        for (var i = nodes.length - 1; i >= 0; --i) {
580            if (nodes[i].functionName === "(program)") {
581                programNode = nodes[i];
582                break;
583            }
584        }
585        var programTime = programNode.selfTime;
586        if (idleTime > programTime)
587            idleTime = programTime;
588        programTime = programTime - idleTime;
589        programNode.selfTime = programTime;
590        programNode.totalTime = programTime;
591        var idleNode = {
592            functionName: "(idle)",
593            url: null,
594            lineNumber: 0,
595            totalTime: idleTime,
596            selfTime: idleTime,
597            numberOfCalls: 0,
598            visible: true,
599            callUID: 0,
600            children: []
601        };
602        nodes.push(idleNode);
603    },
604
605    __proto__: WebInspector.View.prototype
606}
607
608/**
609 * @constructor
610 * @extends {WebInspector.ProfileType}
611 * @implements {ProfilerAgent.Dispatcher}
612 */
613WebInspector.CPUProfileType = function()
614{
615    WebInspector.ProfileType.call(this, WebInspector.CPUProfileType.TypeId, WebInspector.UIString("Collect JavaScript CPU Profile"));
616    InspectorBackend.registerProfilerDispatcher(this);
617    this._recording = false;
618    WebInspector.CPUProfileType.instance = this;
619}
620
621WebInspector.CPUProfileType.TypeId = "CPU";
622
623WebInspector.CPUProfileType.prototype = {
624    get buttonTooltip()
625    {
626        return this._recording ? WebInspector.UIString("Stop CPU profiling.") : WebInspector.UIString("Start CPU profiling.");
627    },
628
629    /**
630     * @override
631     * @return {boolean}
632     */
633    buttonClicked: function()
634    {
635        if (this._recording) {
636            this.stopRecordingProfile();
637            return false;
638        } else {
639            this.startRecordingProfile();
640            return true;
641        }
642    },
643
644    get treeItemTitle()
645    {
646        return WebInspector.UIString("CPU PROFILES");
647    },
648
649    get description()
650    {
651        return WebInspector.UIString("CPU profiles show where the execution time is spent in your page's JavaScript functions.");
652    },
653
654    /**
655     * @param {ProfilerAgent.ProfileHeader} profileHeader
656     */
657    addProfileHeader: function(profileHeader)
658    {
659        this.addProfile(this.createProfile(profileHeader));
660    },
661
662    isRecordingProfile: function()
663    {
664        return this._recording;
665    },
666
667    startRecordingProfile: function()
668    {
669        this._recording = true;
670        WebInspector.userMetrics.ProfilesCPUProfileTaken.record();
671        ProfilerAgent.start();
672    },
673
674    stopRecordingProfile: function()
675    {
676        this._recording = false;
677        ProfilerAgent.stop();
678    },
679
680    /**
681     * @param {boolean} isProfiling
682     */
683    setRecordingProfile: function(isProfiling)
684    {
685        this._recording = isProfiling;
686    },
687
688    /**
689     * @override
690     * @param {string=} title
691     * @return {!WebInspector.ProfileHeader}
692     */
693    createTemporaryProfile: function(title)
694    {
695        title = title || WebInspector.UIString("Recording\u2026");
696        return new WebInspector.CPUProfileHeader(this, title);
697    },
698
699    /**
700     * @override
701     * @param {ProfilerAgent.ProfileHeader} profile
702     * @return {!WebInspector.ProfileHeader}
703     */
704    createProfile: function(profile)
705    {
706        return new WebInspector.CPUProfileHeader(this, profile.title, profile.uid);
707    },
708
709    /**
710     * @override
711     * @param {!WebInspector.ProfileHeader} profile
712     */
713    removeProfile: function(profile)
714    {
715        WebInspector.ProfileType.prototype.removeProfile.call(this, profile);
716        if (!profile.isTemporary)
717            ProfilerAgent.removeProfile(this.id, profile.uid);
718    },
719
720    /**
721     * @override
722     * @param {function(this:WebInspector.ProfileType, ?string, Array.<ProfilerAgent.ProfileHeader>)} populateCallback
723     */
724    _requestProfilesFromBackend: function(populateCallback)
725    {
726        ProfilerAgent.getProfileHeaders(populateCallback);
727    },
728
729    /**
730     * @override
731     */
732    resetProfiles: function()
733    {
734        this._reset();
735    },
736
737    /** @deprecated To be removed from the protocol */
738    addHeapSnapshotChunk: function(uid, chunk)
739    {
740        throw new Error("Never called");
741    },
742
743    /** @deprecated To be removed from the protocol */
744    finishHeapSnapshot: function(uid)
745    {
746        throw new Error("Never called");
747    },
748
749    /** @deprecated To be removed from the protocol */
750    reportHeapSnapshotProgress: function(done, total)
751    {
752        throw new Error("Never called");
753    },
754
755    __proto__: WebInspector.ProfileType.prototype
756}
757
758/**
759 * @constructor
760 * @extends {WebInspector.ProfileHeader}
761 * @param {!WebInspector.CPUProfileType} type
762 * @param {string} title
763 * @param {number=} uid
764 */
765WebInspector.CPUProfileHeader = function(type, title, uid)
766{
767    WebInspector.ProfileHeader.call(this, type, title, uid);
768}
769
770WebInspector.CPUProfileHeader.prototype = {
771    /**
772     * @override
773     */
774    createSidebarTreeElement: function()
775    {
776        return new WebInspector.ProfileSidebarTreeElement(this, WebInspector.UIString("Profile %d"), "profile-sidebar-tree-item");
777    },
778
779    /**
780     * @override
781     * @param {WebInspector.ProfilesPanel} profilesPanel
782     */
783    createView: function(profilesPanel)
784    {
785        return new WebInspector.CPUProfileView(this);
786    },
787
788    __proto__: WebInspector.ProfileHeader.prototype
789}
790