c448cc921b122c5892df91a5fc18f06aec835605
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / profiler / CPUProfileView.js
1 /*
2  * Copyright (C) 2008 Apple Inc. All Rights Reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26
27 /**
28  * @constructor
29  * @extends {WebInspector.VBox}
30  * @param {!WebInspector.CPUProfileHeader} profileHeader
31  */
32 WebInspector.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
86 WebInspector.CPUProfileView._TypeFlame = "Flame";
87 WebInspector.CPUProfileView._TypeTree = "Tree";
88 WebInspector.CPUProfileView._TypeHeavy = "Heavy";
89
90 WebInspector.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  */
486 WebInspector.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
498 WebInspector.CPUProfileType.TypeId = "CPU";
499
500 WebInspector.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  */
683 WebInspector.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
689 WebInspector.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 }