Upstream version 10.39.225.0
[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      * @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  */
495 WebInspector.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
508 WebInspector.CPUProfileType.TypeId = "CPU";
509
510 WebInspector.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  */
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.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 }