• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/**
28 * @constructor
29 * @extends {WebInspector.VBox}
30 * @param {!WebInspector.CPUProfileHeader} profileHeader
31 */
32WebInspector.CPUProfileView = function(profileHeader)
33{
34    WebInspector.VBox.call(this);
35    this.element.classList.add("cpu-profile-view");
36
37    this._viewType = WebInspector.settings.createSetting("cpuProfilerView", WebInspector.CPUProfileView._TypeHeavy);
38
39    var columns = [];
40    columns.push({id: "self", title: WebInspector.UIString("Self"), width: "120px", sort: WebInspector.DataGrid.Order.Descending, sortable: true});
41    columns.push({id: "total", title: WebInspector.UIString("Total"), width: "120px", sortable: true});
42    columns.push({id: "function", title: WebInspector.UIString("Function"), disclosure: true, sortable: true});
43
44    this.dataGrid = new WebInspector.DataGrid(columns);
45    this.dataGrid.addEventListener(WebInspector.DataGrid.Events.SortingChanged, this._sortProfile, this);
46    this.dataGrid.show(this.element);
47
48    this.viewSelectComboBox = new WebInspector.StatusBarComboBox(this._changeView.bind(this));
49
50    var options = {};
51    options[WebInspector.CPUProfileView._TypeFlame] = this.viewSelectComboBox.createOption(WebInspector.UIString("Chart"), "", WebInspector.CPUProfileView._TypeFlame);
52    options[WebInspector.CPUProfileView._TypeHeavy] = this.viewSelectComboBox.createOption(WebInspector.UIString("Heavy (Bottom Up)"), "", WebInspector.CPUProfileView._TypeHeavy);
53    options[WebInspector.CPUProfileView._TypeTree] = this.viewSelectComboBox.createOption(WebInspector.UIString("Tree (Top Down)"), "", WebInspector.CPUProfileView._TypeTree);
54
55    var optionName = this._viewType.get() || WebInspector.CPUProfileView._TypeFlame;
56    var option = options[optionName] || options[WebInspector.CPUProfileView._TypeFlame];
57    this.viewSelectComboBox.select(option);
58
59    this._statusBarButtonsElement = document.createElement("span");
60
61    this.focusButton = new WebInspector.StatusBarButton(WebInspector.UIString("Focus selected function."), "focus-profile-node-status-bar-item");
62    this.focusButton.setEnabled(false);
63    this.focusButton.addEventListener("click", this._focusClicked, this);
64    this._statusBarButtonsElement.appendChild(this.focusButton.element);
65
66    this.excludeButton = new WebInspector.StatusBarButton(WebInspector.UIString("Exclude selected function."), "exclude-profile-node-status-bar-item");
67    this.excludeButton.setEnabled(false);
68    this.excludeButton.addEventListener("click", this._excludeClicked, this);
69    this._statusBarButtonsElement.appendChild(this.excludeButton.element);
70
71    this.resetButton = new WebInspector.StatusBarButton(WebInspector.UIString("Restore all functions."), "reset-profile-status-bar-item");
72    this.resetButton.visible = false;
73    this.resetButton.addEventListener("click", this._resetClicked, this);
74    this._statusBarButtonsElement.appendChild(this.resetButton.element);
75
76    this._profileHeader = profileHeader;
77    this._linkifier = new WebInspector.Linkifier(new WebInspector.Linkifier.DefaultFormatter(30));
78
79    this.profile = new WebInspector.CPUProfileDataModel(profileHeader._profile || profileHeader.protocolProfile());
80
81    this._changeView();
82    if (this._flameChart)
83        this._flameChart.update();
84}
85
86WebInspector.CPUProfileView._TypeFlame = "Flame";
87WebInspector.CPUProfileView._TypeTree = "Tree";
88WebInspector.CPUProfileView._TypeHeavy = "Heavy";
89
90WebInspector.CPUProfileView.prototype = {
91    /**
92     * @param {!number} timeLeft
93     * @param {!number} timeRight
94     */
95    selectRange: function(timeLeft, timeRight)
96    {
97        if (!this._flameChart)
98            return;
99        this._flameChart.selectRange(timeLeft, timeRight);
100    },
101
102    get statusBarItems()
103    {
104        return [this.viewSelectComboBox.element, this._statusBarButtonsElement];
105    },
106
107    /**
108     * @return {!WebInspector.ProfileDataGridTree}
109     */
110    _getBottomUpProfileDataGridTree: function()
111    {
112        if (!this._bottomUpProfileDataGridTree)
113            this._bottomUpProfileDataGridTree = new WebInspector.BottomUpProfileDataGridTree(this, /** @type {!ProfilerAgent.CPUProfileNode} */ (this.profile.profileHead));
114        return this._bottomUpProfileDataGridTree;
115    },
116
117    /**
118     * @return {!WebInspector.ProfileDataGridTree}
119     */
120    _getTopDownProfileDataGridTree: function()
121    {
122        if (!this._topDownProfileDataGridTree)
123            this._topDownProfileDataGridTree = new WebInspector.TopDownProfileDataGridTree(this, /** @type {!ProfilerAgent.CPUProfileNode} */ (this.profile.profileHead));
124        return this._topDownProfileDataGridTree;
125    },
126
127    willHide: function()
128    {
129        this._currentSearchResultIndex = -1;
130    },
131
132    refresh: function()
133    {
134        var selectedProfileNode = this.dataGrid.selectedNode ? this.dataGrid.selectedNode.profileNode : null;
135
136        this.dataGrid.rootNode().removeChildren();
137
138        var children = this.profileDataGridTree.children;
139        var count = children.length;
140
141        for (var index = 0; index < count; ++index)
142            this.dataGrid.rootNode().appendChild(children[index]);
143
144        if (selectedProfileNode)
145            selectedProfileNode.selected = true;
146    },
147
148    refreshVisibleData: function()
149    {
150        var child = this.dataGrid.rootNode().children[0];
151        while (child) {
152            child.refresh();
153            child = child.traverseNextNode(false, null, true);
154        }
155    },
156
157    searchCanceled: function()
158    {
159        if (this._searchResults) {
160            for (var i = 0; i < this._searchResults.length; ++i) {
161                var profileNode = this._searchResults[i].profileNode;
162
163                delete profileNode._searchMatchedSelfColumn;
164                delete profileNode._searchMatchedTotalColumn;
165                delete profileNode._searchMatchedFunctionColumn;
166
167                profileNode.refresh();
168            }
169        }
170
171        delete this._searchFinishedCallback;
172        this._currentSearchResultIndex = -1;
173        this._searchResults = [];
174    },
175
176    performSearch: function(query, finishedCallback)
177    {
178        // Call searchCanceled since it will reset everything we need before doing a new search.
179        this.searchCanceled();
180
181        query = query.trim();
182
183        if (!query.length)
184            return;
185
186        this._searchFinishedCallback = finishedCallback;
187
188        var greaterThan = (query.startsWith(">"));
189        var lessThan = (query.startsWith("<"));
190        var equalTo = (query.startsWith("=") || ((greaterThan || lessThan) && query.indexOf("=") === 1));
191        var percentUnits = (query.lastIndexOf("%") === (query.length - 1));
192        var millisecondsUnits = (query.length > 2 && query.lastIndexOf("ms") === (query.length - 2));
193        var secondsUnits = (!millisecondsUnits && query.lastIndexOf("s") === (query.length - 1));
194
195        var queryNumber = parseFloat(query);
196        if (greaterThan || lessThan || equalTo) {
197            if (equalTo && (greaterThan || lessThan))
198                queryNumber = parseFloat(query.substring(2));
199            else
200                queryNumber = parseFloat(query.substring(1));
201        }
202
203        var queryNumberMilliseconds = (secondsUnits ? (queryNumber * 1000) : queryNumber);
204
205        // Make equalTo implicitly true if it wasn't specified there is no other operator.
206        if (!isNaN(queryNumber) && !(greaterThan || lessThan))
207            equalTo = true;
208
209        var matcher = createPlainTextSearchRegex(query, "i");
210
211        function matchesQuery(/*ProfileDataGridNode*/ profileDataGridNode)
212        {
213            delete profileDataGridNode._searchMatchedSelfColumn;
214            delete profileDataGridNode._searchMatchedTotalColumn;
215            delete profileDataGridNode._searchMatchedFunctionColumn;
216
217            if (percentUnits) {
218                if (lessThan) {
219                    if (profileDataGridNode.selfPercent < queryNumber)
220                        profileDataGridNode._searchMatchedSelfColumn = true;
221                    if (profileDataGridNode.totalPercent < queryNumber)
222                        profileDataGridNode._searchMatchedTotalColumn = true;
223                } else if (greaterThan) {
224                    if (profileDataGridNode.selfPercent > queryNumber)
225                        profileDataGridNode._searchMatchedSelfColumn = true;
226                    if (profileDataGridNode.totalPercent > queryNumber)
227                        profileDataGridNode._searchMatchedTotalColumn = true;
228                }
229
230                if (equalTo) {
231                    if (profileDataGridNode.selfPercent == queryNumber)
232                        profileDataGridNode._searchMatchedSelfColumn = true;
233                    if (profileDataGridNode.totalPercent == queryNumber)
234                        profileDataGridNode._searchMatchedTotalColumn = true;
235                }
236            } else if (millisecondsUnits || secondsUnits) {
237                if (lessThan) {
238                    if (profileDataGridNode.selfTime < queryNumberMilliseconds)
239                        profileDataGridNode._searchMatchedSelfColumn = true;
240                    if (profileDataGridNode.totalTime < queryNumberMilliseconds)
241                        profileDataGridNode._searchMatchedTotalColumn = true;
242                } else if (greaterThan) {
243                    if (profileDataGridNode.selfTime > queryNumberMilliseconds)
244                        profileDataGridNode._searchMatchedSelfColumn = true;
245                    if (profileDataGridNode.totalTime > queryNumberMilliseconds)
246                        profileDataGridNode._searchMatchedTotalColumn = true;
247                }
248
249                if (equalTo) {
250                    if (profileDataGridNode.selfTime == queryNumberMilliseconds)
251                        profileDataGridNode._searchMatchedSelfColumn = true;
252                    if (profileDataGridNode.totalTime == queryNumberMilliseconds)
253                        profileDataGridNode._searchMatchedTotalColumn = true;
254                }
255            }
256
257            if (profileDataGridNode.functionName.match(matcher) || (profileDataGridNode.url && profileDataGridNode.url.match(matcher)))
258                profileDataGridNode._searchMatchedFunctionColumn = true;
259
260            if (profileDataGridNode._searchMatchedSelfColumn ||
261                profileDataGridNode._searchMatchedTotalColumn ||
262                profileDataGridNode._searchMatchedFunctionColumn)
263            {
264                profileDataGridNode.refresh();
265                return true;
266            }
267
268            return false;
269        }
270
271        var current = this.profileDataGridTree.children[0];
272
273        while (current) {
274            if (matchesQuery(current)) {
275                this._searchResults.push({ profileNode: current });
276            }
277
278            current = current.traverseNextNode(false, null, false);
279        }
280
281        finishedCallback(this, this._searchResults.length);
282    },
283
284    jumpToFirstSearchResult: function()
285    {
286        if (!this._searchResults || !this._searchResults.length)
287            return;
288        this._currentSearchResultIndex = 0;
289        this._jumpToSearchResult(this._currentSearchResultIndex);
290    },
291
292    jumpToLastSearchResult: function()
293    {
294        if (!this._searchResults || !this._searchResults.length)
295            return;
296        this._currentSearchResultIndex = (this._searchResults.length - 1);
297        this._jumpToSearchResult(this._currentSearchResultIndex);
298    },
299
300    jumpToNextSearchResult: function()
301    {
302        if (!this._searchResults || !this._searchResults.length)
303            return;
304        if (++this._currentSearchResultIndex >= this._searchResults.length)
305            this._currentSearchResultIndex = 0;
306        this._jumpToSearchResult(this._currentSearchResultIndex);
307    },
308
309    jumpToPreviousSearchResult: function()
310    {
311        if (!this._searchResults || !this._searchResults.length)
312            return;
313        if (--this._currentSearchResultIndex < 0)
314            this._currentSearchResultIndex = (this._searchResults.length - 1);
315        this._jumpToSearchResult(this._currentSearchResultIndex);
316    },
317
318    /**
319     * @return {boolean}
320     */
321    showingFirstSearchResult: function()
322    {
323        return (this._currentSearchResultIndex === 0);
324    },
325
326    /**
327     * @return {boolean}
328     */
329    showingLastSearchResult: function()
330    {
331        return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1));
332    },
333
334    /**
335     * @return {number}
336     */
337    currentSearchResultIndex: function() {
338        return this._currentSearchResultIndex;
339    },
340
341    _jumpToSearchResult: function(index)
342    {
343        var searchResult = this._searchResults[index];
344        if (!searchResult)
345            return;
346
347        var profileNode = searchResult.profileNode;
348        profileNode.revealAndSelect();
349    },
350
351    _ensureFlameChartCreated: function()
352    {
353        if (this._flameChart)
354            return;
355        this._dataProvider = new WebInspector.CPUFlameChartDataProvider(this.profile, this._profileHeader.target());
356        this._flameChart = new WebInspector.CPUProfileFlameChart(this._dataProvider);
357        this._flameChart.addEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected.bind(this));
358    },
359
360    /**
361     * @param {!WebInspector.Event} event
362     */
363    _onEntrySelected: function(event)
364    {
365        var entryIndex = event.data;
366        var node = this._dataProvider._entryNodes[entryIndex];
367        if (!node || !node.scriptId)
368            return;
369        var script = WebInspector.debuggerModel.scriptForId(node.scriptId)
370        if (!script)
371            return;
372        WebInspector.Revealer.reveal(script.rawLocationToUILocation(node.lineNumber));
373    },
374
375    _changeView: function()
376    {
377        if (!this.profile)
378            return;
379
380        switch (this.viewSelectComboBox.selectedOption().value) {
381        case WebInspector.CPUProfileView._TypeFlame:
382            this._ensureFlameChartCreated();
383            this.dataGrid.detach();
384            this._flameChart.show(this.element);
385            this._viewType.set(WebInspector.CPUProfileView._TypeFlame);
386            this._statusBarButtonsElement.classList.toggle("hidden", true);
387            return;
388        case WebInspector.CPUProfileView._TypeTree:
389            this.profileDataGridTree = this._getTopDownProfileDataGridTree();
390            this._sortProfile();
391            this._viewType.set(WebInspector.CPUProfileView._TypeTree);
392            break;
393        case WebInspector.CPUProfileView._TypeHeavy:
394            this.profileDataGridTree = this._getBottomUpProfileDataGridTree();
395            this._sortProfile();
396            this._viewType.set(WebInspector.CPUProfileView._TypeHeavy);
397            break;
398        }
399
400        this._statusBarButtonsElement.classList.toggle("hidden", false);
401
402        if (this._flameChart)
403            this._flameChart.detach();
404        this.dataGrid.show(this.element);
405
406        if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
407            return;
408
409        // The current search needs to be performed again. First negate out previous match
410        // count by calling the search finished callback with a negative number of matches.
411        // Then perform the search again the with same query and callback.
412        this._searchFinishedCallback(this, -this._searchResults.length);
413        this.performSearch(this.currentQuery, this._searchFinishedCallback);
414    },
415
416    _focusClicked: function(event)
417    {
418        if (!this.dataGrid.selectedNode)
419            return;
420
421        this.resetButton.visible = true;
422        this.profileDataGridTree.focus(this.dataGrid.selectedNode);
423        this.refresh();
424        this.refreshVisibleData();
425    },
426
427    _excludeClicked: function(event)
428    {
429        var selectedNode = this.dataGrid.selectedNode
430
431        if (!selectedNode)
432            return;
433
434        selectedNode.deselect();
435
436        this.resetButton.visible = true;
437        this.profileDataGridTree.exclude(selectedNode);
438        this.refresh();
439        this.refreshVisibleData();
440    },
441
442    _resetClicked: function(event)
443    {
444        this.resetButton.visible = false;
445        this.profileDataGridTree.restore();
446        this._linkifier.reset();
447        this.refresh();
448        this.refreshVisibleData();
449    },
450
451    _dataGridNodeSelected: function(node)
452    {
453        this.focusButton.setEnabled(true);
454        this.excludeButton.setEnabled(true);
455    },
456
457    _dataGridNodeDeselected: function(node)
458    {
459        this.focusButton.setEnabled(false);
460        this.excludeButton.setEnabled(false);
461    },
462
463    _sortProfile: function()
464    {
465        var sortAscending = this.dataGrid.isSortOrderAscending();
466        var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier();
467        var sortProperty = {
468                "self": "selfTime",
469                "total": "totalTime",
470                "function": "functionName"
471            }[sortColumnIdentifier];
472
473        this.profileDataGridTree.sort(WebInspector.ProfileDataGridTree.propertyComparator(sortProperty, sortAscending));
474
475        this.refresh();
476    },
477
478    __proto__: WebInspector.VBox.prototype
479}
480
481/**
482 * @constructor
483 * @extends {WebInspector.ProfileType}
484 * @implements {WebInspector.CPUProfilerModel.Delegate}
485 */
486WebInspector.CPUProfileType = function()
487{
488    WebInspector.ProfileType.call(this, WebInspector.CPUProfileType.TypeId, WebInspector.UIString("Collect JavaScript CPU Profile"));
489    this._recording = false;
490
491    this._nextAnonymousConsoleProfileNumber = 1;
492    this._anonymousConsoleProfileIdToTitle = {};
493
494    WebInspector.CPUProfileType.instance = this;
495    WebInspector.cpuProfilerModel.setDelegate(this);
496}
497
498WebInspector.CPUProfileType.TypeId = "CPU";
499
500WebInspector.CPUProfileType.prototype = {
501    /**
502     * @override
503     * @return {string}
504     */
505    fileExtension: function()
506    {
507        return ".cpuprofile";
508    },
509
510    get buttonTooltip()
511    {
512        return this._recording ? WebInspector.UIString("Stop CPU profiling.") : WebInspector.UIString("Start CPU profiling.");
513    },
514
515    /**
516     * @override
517     * @return {boolean}
518     */
519    buttonClicked: function()
520    {
521        if (this._recording) {
522            this.stopRecordingProfile();
523            return false;
524        } else {
525            this.startRecordingProfile();
526            return true;
527        }
528    },
529
530    get treeItemTitle()
531    {
532        return WebInspector.UIString("CPU PROFILES");
533    },
534
535    get description()
536    {
537        return WebInspector.UIString("CPU profiles show where the execution time is spent in your page's JavaScript functions.");
538    },
539
540    /**
541     * @param {string} id
542     * @param {!WebInspector.DebuggerModel.Location} scriptLocation
543     * @param {string=} title
544     */
545    consoleProfileStarted: function(id, scriptLocation, title)
546    {
547        var resolvedTitle = title;
548        if (!resolvedTitle) {
549            resolvedTitle = WebInspector.UIString("Profile %s", this._nextAnonymousConsoleProfileNumber++);
550            this._anonymousConsoleProfileIdToTitle[id] = resolvedTitle;
551        }
552        this._addMessageToConsole(WebInspector.ConsoleMessage.MessageType.Profile, scriptLocation, WebInspector.UIString("Profile '%s' started.", resolvedTitle));
553    },
554
555    /**
556     * @param {string} protocolId
557     * @param {!WebInspector.DebuggerModel.Location} scriptLocation
558     * @param {!ProfilerAgent.CPUProfile} cpuProfile
559     * @param {string=} title
560     */
561    consoleProfileFinished: function(protocolId, scriptLocation, cpuProfile, title)
562    {
563        var resolvedTitle = title;
564        if (typeof title === "undefined") {
565            resolvedTitle = this._anonymousConsoleProfileIdToTitle[protocolId];
566            delete this._anonymousConsoleProfileIdToTitle[protocolId];
567        }
568
569        var target = /** @type {!WebInspector.Target} */ (WebInspector.targetManager.activeTarget());
570        var profile = new WebInspector.CPUProfileHeader(target, this, resolvedTitle);
571        profile.setProtocolProfile(cpuProfile);
572        this.addProfile(profile);
573        this._addMessageToConsole(WebInspector.ConsoleMessage.MessageType.ProfileEnd, scriptLocation, WebInspector.UIString("Profile '%s' finished.", resolvedTitle));
574    },
575
576    /**
577     * @param {string} type
578     * @param {!WebInspector.DebuggerModel.Location} scriptLocation
579     * @param {string} messageText
580     */
581    _addMessageToConsole: function(type, scriptLocation, messageText)
582    {
583        var script = scriptLocation.script();
584        var message = new WebInspector.ConsoleMessage(
585            WebInspector.console.target(),
586            WebInspector.ConsoleMessage.MessageSource.ConsoleAPI,
587            WebInspector.ConsoleMessage.MessageLevel.Debug,
588            messageText,
589            type,
590            undefined,
591            undefined,
592            undefined,
593            undefined,
594            undefined,
595            [{
596                functionName: "",
597                scriptId: scriptLocation.scriptId,
598                url: script ? script.contentURL() : "",
599                lineNumber: scriptLocation.lineNumber,
600                columnNumber: scriptLocation.columnNumber || 0
601            }]);
602
603        WebInspector.console.addMessage(message);
604    },
605
606    /**
607     * @return {boolean}
608     */
609    isRecordingProfile: function()
610    {
611        return this._recording;
612    },
613
614    startRecordingProfile: function()
615    {
616        if (this._profileBeingRecorded)
617            return;
618        var target = /** @type {!WebInspector.Target} */ (WebInspector.targetManager.activeTarget());
619        var profile = new WebInspector.CPUProfileHeader(target, this);
620        this.setProfileBeingRecorded(profile);
621        this.addProfile(profile);
622        profile.updateStatus(WebInspector.UIString("Recording\u2026"));
623        this._recording = true;
624        WebInspector.cpuProfilerModel.setRecording(true);
625        WebInspector.userMetrics.ProfilesCPUProfileTaken.record();
626        ProfilerAgent.start();
627    },
628
629    stopRecordingProfile: function()
630    {
631        this._recording = false;
632        WebInspector.cpuProfilerModel.setRecording(false);
633
634        /**
635         * @param {?string} error
636         * @param {?ProfilerAgent.CPUProfile} profile
637         * @this {WebInspector.CPUProfileType}
638         */
639        function didStopProfiling(error, profile)
640        {
641            if (!this._profileBeingRecorded)
642                return;
643            this._profileBeingRecorded.setProtocolProfile(profile);
644            this._profileBeingRecorded.updateStatus("");
645            var recordedProfile = this._profileBeingRecorded;
646            this.setProfileBeingRecorded(null);
647            this.dispatchEventToListeners(WebInspector.ProfileType.Events.ProfileComplete, recordedProfile);
648        }
649        ProfilerAgent.stop(didStopProfiling.bind(this));
650    },
651
652    /**
653     * @override
654     * @param {string} title
655     * @return {!WebInspector.ProfileHeader}
656     */
657    createProfileLoadedFromFile: function(title)
658    {
659        var target = /** @type {!WebInspector.Target} */ (WebInspector.targetManager.activeTarget());
660        return new WebInspector.CPUProfileHeader(target, this, title);
661    },
662
663    /**
664     * @override
665     */
666    profileBeingRecordedRemoved: function()
667    {
668        this.stopRecordingProfile();
669    },
670
671    __proto__: WebInspector.ProfileType.prototype
672}
673
674/**
675 * @constructor
676 * @extends {WebInspector.ProfileHeader}
677 * @implements {WebInspector.OutputStream}
678 * @implements {WebInspector.OutputStreamDelegate}
679 * @param {!WebInspector.Target} target
680 * @param {!WebInspector.CPUProfileType} type
681 * @param {string=} title
682 */
683WebInspector.CPUProfileHeader = function(target, type, title)
684{
685    WebInspector.ProfileHeader.call(this, target, type, title || WebInspector.UIString("Profile %d", type._nextProfileUid));
686    this._tempFile = null;
687}
688
689WebInspector.CPUProfileHeader.prototype = {
690    onTransferStarted: function()
691    {
692        this._jsonifiedProfile = "";
693        this.updateStatus(WebInspector.UIString("Loading\u2026 %s", Number.bytesToString(this._jsonifiedProfile.length)), true);
694    },
695
696    /**
697     * @param {!WebInspector.ChunkedReader} reader
698     */
699    onChunkTransferred: function(reader)
700    {
701        this.updateStatus(WebInspector.UIString("Loading\u2026 %d\%", Number.bytesToString(this._jsonifiedProfile.length)));
702    },
703
704    onTransferFinished: function()
705    {
706        this.updateStatus(WebInspector.UIString("Parsing\u2026"), true);
707        this._profile = JSON.parse(this._jsonifiedProfile);
708        this._jsonifiedProfile = null;
709        this.updateStatus(WebInspector.UIString("Loaded"), false);
710
711        if (this._profileType.profileBeingRecorded() === this)
712            this._profileType.setProfileBeingRecorded(null);
713    },
714
715    /**
716     * @param {!WebInspector.ChunkedReader} reader
717     * @param {?Event} e
718     */
719    onError: function(reader, e)
720    {
721        var subtitle;
722        switch(e.target.error.code) {
723        case e.target.error.NOT_FOUND_ERR:
724            subtitle = WebInspector.UIString("'%s' not found.", reader.fileName());
725            break;
726        case e.target.error.NOT_READABLE_ERR:
727            subtitle = WebInspector.UIString("'%s' is not readable", reader.fileName());
728            break;
729        case e.target.error.ABORT_ERR:
730            return;
731        default:
732            subtitle = WebInspector.UIString("'%s' error %d", reader.fileName(), e.target.error.code);
733        }
734        this.updateStatus(subtitle);
735    },
736
737    /**
738     * @param {string} text
739     */
740    write: function(text)
741    {
742        this._jsonifiedProfile += text;
743    },
744
745    close: function() { },
746
747    /**
748     * @override
749     */
750    dispose: function()
751    {
752        this.removeTempFile();
753    },
754
755    /**
756     * @override
757     * @param {!WebInspector.ProfilesPanel} panel
758     * @return {!WebInspector.ProfileSidebarTreeElement}
759     */
760    createSidebarTreeElement: function(panel)
761    {
762        return new WebInspector.ProfileSidebarTreeElement(panel, this, "profile-sidebar-tree-item");
763    },
764
765    /**
766     * @override
767     * @return {!WebInspector.CPUProfileView}
768     */
769    createView: function()
770    {
771        return new WebInspector.CPUProfileView(this);
772    },
773
774    /**
775     * @override
776     * @return {boolean}
777     */
778    canSaveToFile: function()
779    {
780        return !this.fromFile() && this._protocolProfile;
781    },
782
783    saveToFile: function()
784    {
785        var fileOutputStream = new WebInspector.FileOutputStream();
786
787        /**
788         * @param {boolean} accepted
789         * @this {WebInspector.CPUProfileHeader}
790         */
791        function onOpenForSave(accepted)
792        {
793            if (!accepted)
794                return;
795            function didRead(data)
796            {
797                if (data)
798                    fileOutputStream.write(data, fileOutputStream.close.bind(fileOutputStream));
799                else
800                    fileOutputStream.close();
801            }
802            if (this._failedToCreateTempFile) {
803                WebInspector.messageSink.addErrorMessage("Failed to open temp file with heap snapshot");
804                fileOutputStream.close();
805            } else if (this._tempFile) {
806                this._tempFile.read(didRead);
807            } else {
808                this._onTempFileReady = onOpenForSave.bind(this, accepted);
809            }
810        }
811        this._fileName = this._fileName || "CPU-" + new Date().toISO8601Compact() + this._profileType.fileExtension();
812        fileOutputStream.open(this._fileName, onOpenForSave.bind(this));
813    },
814
815    /**
816     * @param {!File} file
817     */
818    loadFromFile: function(file)
819    {
820        this.updateStatus(WebInspector.UIString("Loading\u2026"), true);
821        var fileReader = new WebInspector.ChunkedFileReader(file, 10000000, this);
822        fileReader.start(this);
823    },
824
825
826    /**
827     * @return {?ProfilerAgent.CPUProfile}
828     */
829    protocolProfile: function()
830    {
831        return this._protocolProfile;
832    },
833
834    /**
835     * @param {!ProfilerAgent.CPUProfile} cpuProfile
836     */
837    setProtocolProfile: function(cpuProfile)
838    {
839        this._protocolProfile = cpuProfile;
840        this._saveProfileDataToTempFile(cpuProfile);
841        if (this.canSaveToFile())
842            this.dispatchEventToListeners(WebInspector.ProfileHeader.Events.ProfileReceived);
843    },
844
845    /**
846     * @param {!ProfilerAgent.CPUProfile} data
847     */
848    _saveProfileDataToTempFile: function(data)
849    {
850        var serializedData = JSON.stringify(data);
851
852        /**
853         * @this {WebInspector.CPUProfileHeader}
854         */
855        function didCreateTempFile(tempFile)
856        {
857            this._writeToTempFile(tempFile, serializedData);
858        }
859        new WebInspector.TempFile("cpu-profiler", this.uid,  didCreateTempFile.bind(this));
860    },
861
862    /**
863     * @param {?WebInspector.TempFile} tempFile
864     * @param {string} serializedData
865     */
866    _writeToTempFile: function(tempFile, serializedData)
867    {
868        this._tempFile = tempFile;
869        if (!tempFile) {
870            this._failedToCreateTempFile = true;
871            this._notifyTempFileReady();
872            return;
873        }
874        /**
875         * @param {boolean} success
876         * @this {WebInspector.CPUProfileHeader}
877         */
878        function didWriteToTempFile(success)
879        {
880            if (!success)
881                this._failedToCreateTempFile = true;
882            tempFile.finishWriting();
883            this._notifyTempFileReady();
884        }
885        tempFile.write(serializedData, didWriteToTempFile.bind(this));
886    },
887
888    _notifyTempFileReady: function()
889    {
890        if (this._onTempFileReady) {
891            this._onTempFileReady();
892            this._onTempFileReady = null;
893        }
894    },
895
896    __proto__: WebInspector.ProfileHeader.prototype
897}
898