• Home
  • History
  • Annotate
  • 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     * @return {?WebInspector.Target}
93     */
94    target: function()
95    {
96        return this._profileHeader.target();
97    },
98
99    /**
100     * @param {!number} timeLeft
101     * @param {!number} timeRight
102     */
103    selectRange: function(timeLeft, timeRight)
104    {
105        if (!this._flameChart)
106            return;
107        this._flameChart.selectRange(timeLeft, timeRight);
108    },
109
110    get statusBarItems()
111    {
112        return [this.viewSelectComboBox.element, this._statusBarButtonsElement];
113    },
114
115    /**
116     * @return {!WebInspector.ProfileDataGridTree}
117     */
118    _getBottomUpProfileDataGridTree: function()
119    {
120        if (!this._bottomUpProfileDataGridTree)
121            this._bottomUpProfileDataGridTree = new WebInspector.BottomUpProfileDataGridTree(this, /** @type {!ProfilerAgent.CPUProfileNode} */ (this.profile.profileHead));
122        return this._bottomUpProfileDataGridTree;
123    },
124
125    /**
126     * @return {!WebInspector.ProfileDataGridTree}
127     */
128    _getTopDownProfileDataGridTree: function()
129    {
130        if (!this._topDownProfileDataGridTree)
131            this._topDownProfileDataGridTree = new WebInspector.TopDownProfileDataGridTree(this, /** @type {!ProfilerAgent.CPUProfileNode} */ (this.profile.profileHead));
132        return this._topDownProfileDataGridTree;
133    },
134
135    willHide: function()
136    {
137        this._currentSearchResultIndex = -1;
138    },
139
140    refresh: function()
141    {
142        var selectedProfileNode = this.dataGrid.selectedNode ? this.dataGrid.selectedNode.profileNode : null;
143
144        this.dataGrid.rootNode().removeChildren();
145
146        var children = this.profileDataGridTree.children;
147        var count = children.length;
148
149        for (var index = 0; index < count; ++index)
150            this.dataGrid.rootNode().appendChild(children[index]);
151
152        if (selectedProfileNode)
153            selectedProfileNode.selected = true;
154    },
155
156    refreshVisibleData: function()
157    {
158        var child = this.dataGrid.rootNode().children[0];
159        while (child) {
160            child.refresh();
161            child = child.traverseNextNode(false, null, true);
162        }
163    },
164
165    searchCanceled: function()
166    {
167        if (this._searchResults) {
168            for (var i = 0; i < this._searchResults.length; ++i) {
169                var profileNode = this._searchResults[i].profileNode;
170
171                delete profileNode._searchMatchedSelfColumn;
172                delete profileNode._searchMatchedTotalColumn;
173                delete profileNode._searchMatchedFunctionColumn;
174
175                profileNode.refresh();
176            }
177        }
178
179        delete this._searchFinishedCallback;
180        this._currentSearchResultIndex = -1;
181        this._searchResults = [];
182    },
183
184    performSearch: function(query, finishedCallback)
185    {
186        // Call searchCanceled since it will reset everything we need before doing a new search.
187        this.searchCanceled();
188
189        query = query.trim();
190
191        if (!query.length)
192            return;
193
194        this._searchFinishedCallback = finishedCallback;
195
196        var greaterThan = (query.startsWith(">"));
197        var lessThan = (query.startsWith("<"));
198        var equalTo = (query.startsWith("=") || ((greaterThan || lessThan) && query.indexOf("=") === 1));
199        var percentUnits = (query.lastIndexOf("%") === (query.length - 1));
200        var millisecondsUnits = (query.length > 2 && query.lastIndexOf("ms") === (query.length - 2));
201        var secondsUnits = (!millisecondsUnits && query.lastIndexOf("s") === (query.length - 1));
202
203        var queryNumber = parseFloat(query);
204        if (greaterThan || lessThan || equalTo) {
205            if (equalTo && (greaterThan || lessThan))
206                queryNumber = parseFloat(query.substring(2));
207            else
208                queryNumber = parseFloat(query.substring(1));
209        }
210
211        var queryNumberMilliseconds = (secondsUnits ? (queryNumber * 1000) : queryNumber);
212
213        // Make equalTo implicitly true if it wasn't specified there is no other operator.
214        if (!isNaN(queryNumber) && !(greaterThan || lessThan))
215            equalTo = true;
216
217        var matcher = createPlainTextSearchRegex(query, "i");
218
219        function matchesQuery(/*ProfileDataGridNode*/ profileDataGridNode)
220        {
221            delete profileDataGridNode._searchMatchedSelfColumn;
222            delete profileDataGridNode._searchMatchedTotalColumn;
223            delete profileDataGridNode._searchMatchedFunctionColumn;
224
225            if (percentUnits) {
226                if (lessThan) {
227                    if (profileDataGridNode.selfPercent < queryNumber)
228                        profileDataGridNode._searchMatchedSelfColumn = true;
229                    if (profileDataGridNode.totalPercent < queryNumber)
230                        profileDataGridNode._searchMatchedTotalColumn = true;
231                } else if (greaterThan) {
232                    if (profileDataGridNode.selfPercent > queryNumber)
233                        profileDataGridNode._searchMatchedSelfColumn = true;
234                    if (profileDataGridNode.totalPercent > queryNumber)
235                        profileDataGridNode._searchMatchedTotalColumn = true;
236                }
237
238                if (equalTo) {
239                    if (profileDataGridNode.selfPercent == queryNumber)
240                        profileDataGridNode._searchMatchedSelfColumn = true;
241                    if (profileDataGridNode.totalPercent == queryNumber)
242                        profileDataGridNode._searchMatchedTotalColumn = true;
243                }
244            } else if (millisecondsUnits || secondsUnits) {
245                if (lessThan) {
246                    if (profileDataGridNode.selfTime < queryNumberMilliseconds)
247                        profileDataGridNode._searchMatchedSelfColumn = true;
248                    if (profileDataGridNode.totalTime < queryNumberMilliseconds)
249                        profileDataGridNode._searchMatchedTotalColumn = true;
250                } else if (greaterThan) {
251                    if (profileDataGridNode.selfTime > queryNumberMilliseconds)
252                        profileDataGridNode._searchMatchedSelfColumn = true;
253                    if (profileDataGridNode.totalTime > queryNumberMilliseconds)
254                        profileDataGridNode._searchMatchedTotalColumn = true;
255                }
256
257                if (equalTo) {
258                    if (profileDataGridNode.selfTime == queryNumberMilliseconds)
259                        profileDataGridNode._searchMatchedSelfColumn = true;
260                    if (profileDataGridNode.totalTime == queryNumberMilliseconds)
261                        profileDataGridNode._searchMatchedTotalColumn = true;
262                }
263            }
264
265            if (profileDataGridNode.functionName.match(matcher) || (profileDataGridNode.url && profileDataGridNode.url.match(matcher)))
266                profileDataGridNode._searchMatchedFunctionColumn = true;
267
268            if (profileDataGridNode._searchMatchedSelfColumn ||
269                profileDataGridNode._searchMatchedTotalColumn ||
270                profileDataGridNode._searchMatchedFunctionColumn)
271            {
272                profileDataGridNode.refresh();
273                return true;
274            }
275
276            return false;
277        }
278
279        var current = this.profileDataGridTree.children[0];
280
281        while (current) {
282            if (matchesQuery(current)) {
283                this._searchResults.push({ profileNode: current });
284            }
285
286            current = current.traverseNextNode(false, null, false);
287        }
288
289        finishedCallback(this, this._searchResults.length);
290    },
291
292    jumpToFirstSearchResult: function()
293    {
294        if (!this._searchResults || !this._searchResults.length)
295            return;
296        this._currentSearchResultIndex = 0;
297        this._jumpToSearchResult(this._currentSearchResultIndex);
298    },
299
300    jumpToLastSearchResult: function()
301    {
302        if (!this._searchResults || !this._searchResults.length)
303            return;
304        this._currentSearchResultIndex = (this._searchResults.length - 1);
305        this._jumpToSearchResult(this._currentSearchResultIndex);
306    },
307
308    jumpToNextSearchResult: function()
309    {
310        if (!this._searchResults || !this._searchResults.length)
311            return;
312        if (++this._currentSearchResultIndex >= this._searchResults.length)
313            this._currentSearchResultIndex = 0;
314        this._jumpToSearchResult(this._currentSearchResultIndex);
315    },
316
317    jumpToPreviousSearchResult: function()
318    {
319        if (!this._searchResults || !this._searchResults.length)
320            return;
321        if (--this._currentSearchResultIndex < 0)
322            this._currentSearchResultIndex = (this._searchResults.length - 1);
323        this._jumpToSearchResult(this._currentSearchResultIndex);
324    },
325
326    /**
327     * @return {boolean}
328     */
329    showingFirstSearchResult: function()
330    {
331        return (this._currentSearchResultIndex === 0);
332    },
333
334    /**
335     * @return {boolean}
336     */
337    showingLastSearchResult: function()
338    {
339        return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1));
340    },
341
342    /**
343     * @return {number}
344     */
345    currentSearchResultIndex: function() {
346        return this._currentSearchResultIndex;
347    },
348
349    _jumpToSearchResult: function(index)
350    {
351        var searchResult = this._searchResults[index];
352        if (!searchResult)
353            return;
354
355        var profileNode = searchResult.profileNode;
356        profileNode.revealAndSelect();
357    },
358
359    _ensureFlameChartCreated: function()
360    {
361        if (this._flameChart)
362            return;
363        this._dataProvider = new WebInspector.CPUFlameChartDataProvider(this.profile, this._profileHeader.target());
364        this._flameChart = new WebInspector.CPUProfileFlameChart(this._dataProvider);
365        this._flameChart.addEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected.bind(this));
366    },
367
368    /**
369     * @param {!WebInspector.Event} event
370     */
371    _onEntrySelected: function(event)
372    {
373        var entryIndex = event.data;
374        var node = this._dataProvider._entryNodes[entryIndex];
375        var target = this._profileHeader.target();
376        if (!node || !node.scriptId || !target)
377            return;
378        var script = target.debuggerModel.scriptForId(node.scriptId)
379        if (!script)
380            return;
381        var location = /** @type {!WebInspector.DebuggerModel.Location} */ (script.target().debuggerModel.createRawLocation(script, node.lineNumber, 0));
382        WebInspector.Revealer.reveal(WebInspector.debuggerWorkspaceBinding.rawLocationToUILocation(location));
383    },
384
385    _changeView: function()
386    {
387        if (!this.profile)
388            return;
389
390        switch (this.viewSelectComboBox.selectedOption().value) {
391        case WebInspector.CPUProfileView._TypeFlame:
392            this._ensureFlameChartCreated();
393            this.dataGrid.detach();
394            this._flameChart.show(this.element);
395            this._viewType.set(WebInspector.CPUProfileView._TypeFlame);
396            this._statusBarButtonsElement.classList.toggle("hidden", true);
397            return;
398        case WebInspector.CPUProfileView._TypeTree:
399            this.profileDataGridTree = this._getTopDownProfileDataGridTree();
400            this._sortProfile();
401            this._viewType.set(WebInspector.CPUProfileView._TypeTree);
402            break;
403        case WebInspector.CPUProfileView._TypeHeavy:
404            this.profileDataGridTree = this._getBottomUpProfileDataGridTree();
405            this._sortProfile();
406            this._viewType.set(WebInspector.CPUProfileView._TypeHeavy);
407            break;
408        }
409
410        this._statusBarButtonsElement.classList.toggle("hidden", false);
411
412        if (this._flameChart)
413            this._flameChart.detach();
414        this.dataGrid.show(this.element);
415
416        if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
417            return;
418
419        // The current search needs to be performed again. First negate out previous match
420        // count by calling the search finished callback with a negative number of matches.
421        // Then perform the search again the with same query and callback.
422        this._searchFinishedCallback(this, -this._searchResults.length);
423        this.performSearch(this.currentQuery, this._searchFinishedCallback);
424    },
425
426    _focusClicked: function(event)
427    {
428        if (!this.dataGrid.selectedNode)
429            return;
430
431        this.resetButton.visible = true;
432        this.profileDataGridTree.focus(this.dataGrid.selectedNode);
433        this.refresh();
434        this.refreshVisibleData();
435    },
436
437    _excludeClicked: function(event)
438    {
439        var selectedNode = this.dataGrid.selectedNode
440
441        if (!selectedNode)
442            return;
443
444        selectedNode.deselect();
445
446        this.resetButton.visible = true;
447        this.profileDataGridTree.exclude(selectedNode);
448        this.refresh();
449        this.refreshVisibleData();
450    },
451
452    _resetClicked: function(event)
453    {
454        this.resetButton.visible = false;
455        this.profileDataGridTree.restore();
456        this._linkifier.reset();
457        this.refresh();
458        this.refreshVisibleData();
459    },
460
461    _dataGridNodeSelected: function(node)
462    {
463        this.focusButton.setEnabled(true);
464        this.excludeButton.setEnabled(true);
465    },
466
467    _dataGridNodeDeselected: function(node)
468    {
469        this.focusButton.setEnabled(false);
470        this.excludeButton.setEnabled(false);
471    },
472
473    _sortProfile: function()
474    {
475        var sortAscending = this.dataGrid.isSortOrderAscending();
476        var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier();
477        var sortProperty = {
478                "self": "selfTime",
479                "total": "totalTime",
480                "function": "functionName"
481            }[sortColumnIdentifier];
482
483        this.profileDataGridTree.sort(WebInspector.ProfileDataGridTree.propertyComparator(sortProperty, sortAscending));
484
485        this.refresh();
486    },
487
488    __proto__: WebInspector.VBox.prototype
489}
490
491/**
492 * @constructor
493 * @extends {WebInspector.ProfileType}
494 */
495WebInspector.CPUProfileType = function()
496{
497    WebInspector.ProfileType.call(this, WebInspector.CPUProfileType.TypeId, WebInspector.UIString("Collect JavaScript CPU Profile"));
498    this._recording = false;
499
500    this._nextAnonymousConsoleProfileNumber = 1;
501    this._anonymousConsoleProfileIdToTitle = {};
502
503    WebInspector.CPUProfileType.instance = this;
504    WebInspector.targetManager.addModelListener(WebInspector.CPUProfilerModel, WebInspector.CPUProfilerModel.EventTypes.ConsoleProfileStarted, this._consoleProfileStarted, this);
505    WebInspector.targetManager.addModelListener(WebInspector.CPUProfilerModel, WebInspector.CPUProfilerModel.EventTypes.ConsoleProfileFinished, this._consoleProfileFinished, this);
506}
507
508WebInspector.CPUProfileType.TypeId = "CPU";
509
510WebInspector.CPUProfileType.prototype = {
511    /**
512     * @override
513     * @return {string}
514     */
515    fileExtension: function()
516    {
517        return ".cpuprofile";
518    },
519
520    get buttonTooltip()
521    {
522        return this._recording ? WebInspector.UIString("Stop CPU profiling.") : WebInspector.UIString("Start CPU profiling.");
523    },
524
525    /**
526     * @override
527     * @return {boolean}
528     */
529    buttonClicked: function()
530    {
531        if (this._recording) {
532            this.stopRecordingProfile();
533            return false;
534        } else {
535            this.startRecordingProfile();
536            return true;
537        }
538    },
539
540    get treeItemTitle()
541    {
542        return WebInspector.UIString("CPU PROFILES");
543    },
544
545    get description()
546    {
547        return WebInspector.UIString("CPU profiles show where the execution time is spent in your page's JavaScript functions.");
548    },
549
550    /**
551     * @param {!WebInspector.Event} event
552     */
553    _consoleProfileStarted: function(event)
554    {
555        var protocolId = /** @type {string} */ (event.data.protocolId);
556        var scriptLocation = /** @type {!WebInspector.DebuggerModel.Location} */ (event.data.scriptLocation);
557        var resolvedTitle = /** @type {string|undefined} */ (event.data.title);
558        if (!resolvedTitle) {
559            resolvedTitle = WebInspector.UIString("Profile %s", this._nextAnonymousConsoleProfileNumber++);
560            this._anonymousConsoleProfileIdToTitle[protocolId] = resolvedTitle;
561        }
562        this._addMessageToConsole(WebInspector.ConsoleMessage.MessageType.Profile, scriptLocation, WebInspector.UIString("Profile '%s' started.", resolvedTitle));
563    },
564
565    /**
566     * @param {!WebInspector.Event} event
567     */
568    _consoleProfileFinished: function(event)
569    {
570        var protocolId = /** @type {string} */ (event.data.protocolId);
571        var scriptLocation = /** @type {!WebInspector.DebuggerModel.Location} */ (event.data.scriptLocation);
572        var cpuProfile = /** @type {!ProfilerAgent.CPUProfile} */ (event.data.cpuProfile);
573        var resolvedTitle = /** @type {string|undefined} */ (event.data.title);
574        if (typeof resolvedTitle === "undefined") {
575            resolvedTitle = this._anonymousConsoleProfileIdToTitle[protocolId];
576            delete this._anonymousConsoleProfileIdToTitle[protocolId];
577        }
578
579        var profile = new WebInspector.CPUProfileHeader(scriptLocation.target(), this, resolvedTitle);
580        profile.setProtocolProfile(cpuProfile);
581        this.addProfile(profile);
582        this._addMessageToConsole(WebInspector.ConsoleMessage.MessageType.ProfileEnd, scriptLocation, WebInspector.UIString("Profile '%s' finished.", resolvedTitle));
583    },
584
585    /**
586     * @param {string} type
587     * @param {!WebInspector.DebuggerModel.Location} scriptLocation
588     * @param {string} messageText
589     */
590    _addMessageToConsole: function(type, scriptLocation, messageText)
591    {
592        var script = scriptLocation.script();
593        var target = scriptLocation.target();
594        var message = new WebInspector.ConsoleMessage(
595            target,
596            WebInspector.ConsoleMessage.MessageSource.ConsoleAPI,
597            WebInspector.ConsoleMessage.MessageLevel.Debug,
598            messageText,
599            type,
600            undefined,
601            undefined,
602            undefined,
603            undefined,
604            undefined,
605            [{
606                functionName: "",
607                scriptId: scriptLocation.scriptId,
608                url: script ? script.contentURL() : "",
609                lineNumber: scriptLocation.lineNumber,
610                columnNumber: scriptLocation.columnNumber || 0
611            }]);
612
613        target.consoleModel.addMessage(message);
614    },
615
616    startRecordingProfile: function()
617    {
618        var target = WebInspector.context.flavor(WebInspector.Target);
619        if (this._profileBeingRecorded || !target)
620            return;
621        var profile = new WebInspector.CPUProfileHeader(target, this);
622        this.setProfileBeingRecorded(profile);
623        this.addProfile(profile);
624        profile.updateStatus(WebInspector.UIString("Recording\u2026"));
625        this._recording = true;
626        target.cpuProfilerModel.startRecording();
627    },
628
629    stopRecordingProfile: function()
630    {
631        this._recording = false;
632        if (!this._profileBeingRecorded || !this._profileBeingRecorded.target())
633            return;
634
635        /**
636         * @param {?string} error
637         * @param {?ProfilerAgent.CPUProfile} profile
638         * @this {WebInspector.CPUProfileType}
639         */
640        function didStopProfiling(error, profile)
641        {
642            if (!this._profileBeingRecorded)
643                return;
644            this._profileBeingRecorded.setProtocolProfile(profile);
645            this._profileBeingRecorded.updateStatus("");
646            var recordedProfile = this._profileBeingRecorded;
647            this.setProfileBeingRecorded(null);
648            this.dispatchEventToListeners(WebInspector.ProfileType.Events.ProfileComplete, recordedProfile);
649        }
650        this._profileBeingRecorded.target().cpuProfilerModel.stopRecording(didStopProfiling.bind(this));
651    },
652
653    /**
654     * @override
655     * @param {string} title
656     * @return {!WebInspector.ProfileHeader}
657     */
658    createProfileLoadedFromFile: function(title)
659    {
660        return new WebInspector.CPUProfileHeader(null, 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.console.error("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", String(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