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