Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / profiler / CanvasProfileView.js
1 /*
2  * Copyright (C) 2013 Google 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 are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 /**
32  * @constructor
33  * @extends {WebInspector.VBox}
34  * @param {!WebInspector.CanvasProfileHeader} profile
35  */
36 WebInspector.CanvasProfileView = function(profile)
37 {
38     WebInspector.VBox.call(this);
39     this.registerRequiredCSS("canvasProfiler.css");
40     this.element.classList.add("canvas-profile-view");
41
42     this._profile = profile;
43     this._traceLogId = profile.traceLogId();
44     this._traceLogPlayer = /** @type {!WebInspector.CanvasTraceLogPlayerProxy} */ (profile.traceLogPlayer());
45     this._linkifier = new WebInspector.Linkifier();
46
47     this._replayInfoSplitView = new WebInspector.SplitView(true, true, "canvasProfileViewReplaySplitViewState", 0.34);
48     this._replayInfoSplitView.show(this.element);
49
50     this._imageSplitView = new WebInspector.SplitView(false, true, "canvasProfileViewSplitViewState", 300);
51     this._imageSplitView.show(this._replayInfoSplitView.mainElement());
52
53     var replayImageContainerView = new WebInspector.VBox();
54     replayImageContainerView.setMinimumSize(50, 28);
55     replayImageContainerView.show(this._imageSplitView.mainElement());
56
57     // NOTE: The replayImageContainer can NOT be a flex div (e.g. VBox or SplitView elements)!
58     var replayImageContainer = replayImageContainerView.element.createChild("div");
59     replayImageContainer.id = "canvas-replay-image-container";
60     this._replayImageElement = replayImageContainer.createChild("img", "canvas-replay-image");
61     this._debugInfoElement = replayImageContainer.createChild("div", "canvas-debug-info hidden");
62     this._spinnerIcon = replayImageContainer.createChild("div", "spinner-icon small hidden");
63
64     var replayLogContainerView = new WebInspector.VBox();
65     replayLogContainerView.setMinimumSize(22, 22);
66     replayLogContainerView.show(this._imageSplitView.sidebarElement());
67
68     var replayLogContainer = replayLogContainerView.element;
69     var controlsContainer = replayLogContainer.createChild("div", "status-bar");
70     var logGridContainer = replayLogContainer.createChild("div", "canvas-replay-log");
71
72     this._createControlButton(controlsContainer, "canvas-replay-first-step", WebInspector.UIString("First call."), this._onReplayFirstStepClick.bind(this));
73     this._createControlButton(controlsContainer, "canvas-replay-prev-step", WebInspector.UIString("Previous call."), this._onReplayStepClick.bind(this, false));
74     this._createControlButton(controlsContainer, "canvas-replay-next-step", WebInspector.UIString("Next call."), this._onReplayStepClick.bind(this, true));
75     this._createControlButton(controlsContainer, "canvas-replay-prev-draw", WebInspector.UIString("Previous drawing call."), this._onReplayDrawingCallClick.bind(this, false));
76     this._createControlButton(controlsContainer, "canvas-replay-next-draw", WebInspector.UIString("Next drawing call."), this._onReplayDrawingCallClick.bind(this, true));
77     this._createControlButton(controlsContainer, "canvas-replay-last-step", WebInspector.UIString("Last call."), this._onReplayLastStepClick.bind(this));
78
79     this._replayContextSelector = new WebInspector.StatusBarComboBox(this._onReplayContextChanged.bind(this));
80     this._replayContextSelector.createOption(WebInspector.UIString("<screenshot auto>"), WebInspector.UIString("Show screenshot of the last replayed resource."), "");
81     controlsContainer.appendChild(this._replayContextSelector.element);
82
83     this._installReplayInfoSidebarWidgets(controlsContainer);
84
85     this._replayStateView = new WebInspector.CanvasReplayStateView(this._traceLogPlayer);
86     this._replayStateView.show(this._replayInfoSplitView.sidebarElement());
87
88     /** @type {!Object.<string, boolean>} */
89     this._replayContexts = {};
90
91     var columns = [
92         {title: "#", sortable: false, width: "5%"},
93         {title: WebInspector.UIString("Call"), sortable: false, width: "75%", disclosure: true},
94         {title: WebInspector.UIString("Location"), sortable: false, width: "20%"}
95     ];
96
97     this._logGrid = new WebInspector.DataGrid(columns);
98     this._logGrid.element.classList.add("fill");
99     this._logGrid.show(logGridContainer);
100     this._logGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._replayTraceLog, this);
101
102     this.element.addEventListener("mousedown", this._onMouseClick.bind(this), true);
103
104     this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.element, this._popoverAnchor.bind(this), this._resolveObjectForPopover.bind(this), this._onHidePopover.bind(this), true);
105     this._popoverHelper.setRemoteObjectFormatter(this._hexNumbersFormatter.bind(this));
106
107     this._requestTraceLog(0);
108 }
109
110 /**
111  * @const
112  * @type {number}
113  */
114 WebInspector.CanvasProfileView.TraceLogPollingInterval = 500;
115
116 WebInspector.CanvasProfileView.prototype = {
117     dispose: function()
118     {
119         this._linkifier.reset();
120     },
121
122     get statusBarItems()
123     {
124         return [];
125     },
126
127     get profile()
128     {
129         return this._profile;
130     },
131
132     /**
133      * @override
134      * @return {!Array.<!Element>}
135      */
136     elementsToRestoreScrollPositionsFor: function()
137     {
138         return [this._logGrid.scrollContainer];
139     },
140
141     /**
142      * @param {!Element} controlsContainer
143      */
144     _installReplayInfoSidebarWidgets: function(controlsContainer)
145     {
146         this._replayInfoResizeWidgetElement = controlsContainer.createChild("div", "resizer-widget");
147         this._replayInfoSplitView.addEventListener(WebInspector.SplitView.Events.ShowModeChanged, this._updateReplayInfoResizeWidget, this);
148         this._updateReplayInfoResizeWidget();
149         this._replayInfoSplitView.installResizer(this._replayInfoResizeWidgetElement);
150
151         this._toggleReplayStateSidebarButton = this._replayInfoSplitView.createShowHideSidebarButton("sidebar", "canvas-sidebar-show-hide-button");
152
153         controlsContainer.appendChild(this._toggleReplayStateSidebarButton.element);
154         this._replayInfoSplitView.hideSidebar();
155     },
156
157     _updateReplayInfoResizeWidget: function()
158     {
159         this._replayInfoResizeWidgetElement.classList.toggle("hidden", this._replayInfoSplitView.showMode() !== WebInspector.SplitView.ShowMode.Both);
160     },
161
162     /**
163      * @param {?Event} event
164      */
165     _onMouseClick: function(event)
166     {
167         var resourceLinkElement = event.target.enclosingNodeOrSelfWithClass("canvas-formatted-resource");
168         if (resourceLinkElement) {
169             this._replayInfoSplitView.showBoth();
170             this._replayStateView.selectResource(resourceLinkElement.__resourceId);
171             event.consume(true);
172             return;
173         }
174         if (event.target.enclosingNodeOrSelfWithClass("webkit-html-resource-link"))
175             event.consume(false);
176     },
177
178     /**
179      * @param {!Element} parent
180      * @param {string} className
181      * @param {string} title
182      * @param {function(this:WebInspector.CanvasProfileView)} clickCallback
183      */
184     _createControlButton: function(parent, className, title, clickCallback)
185     {
186         var button = new WebInspector.StatusBarButton(title, className + " canvas-replay-button");
187         parent.appendChild(button.element);
188
189         button.makeLongClickEnabled();
190         button.addEventListener("click", clickCallback, this);
191         button.addEventListener("longClickDown", clickCallback, this);
192         button.addEventListener("longClickPress", clickCallback, this);
193     },
194
195     _onReplayContextChanged: function()
196     {
197         var selectedContextId = this._replayContextSelector.selectedOption().value;
198
199         /**
200          * @param {?CanvasAgent.ResourceState} resourceState
201          * @this {WebInspector.CanvasProfileView}
202          */
203         function didReceiveResourceState(resourceState)
204         {
205             this._enableWaitIcon(false);
206             if (selectedContextId !== this._replayContextSelector.selectedOption().value)
207                 return;
208             var imageURL = (resourceState && resourceState.imageURL) || "";
209             this._replayImageElement.src = imageURL;
210             this._replayImageElement.style.visibility = imageURL ? "" : "hidden";
211         }
212
213         this._enableWaitIcon(true);
214         this._traceLogPlayer.getResourceState(selectedContextId, didReceiveResourceState.bind(this));
215     },
216
217     /**
218      * @param {boolean} forward
219      */
220     _onReplayStepClick: function(forward)
221     {
222         var selectedNode = this._logGrid.selectedNode;
223         if (!selectedNode)
224             return;
225         var nextNode = selectedNode;
226         do {
227             nextNode = forward ? nextNode.traverseNextNode(false) : nextNode.traversePreviousNode(false);
228         } while (nextNode && typeof nextNode.index !== "number");
229         (nextNode || selectedNode).revealAndSelect();
230     },
231
232     /**
233      * @param {boolean} forward
234      */
235     _onReplayDrawingCallClick: function(forward)
236     {
237         var selectedNode = this._logGrid.selectedNode;
238         if (!selectedNode)
239             return;
240         var nextNode = selectedNode;
241         while (nextNode) {
242             var sibling = forward ? nextNode.nextSibling : nextNode.previousSibling;
243             if (sibling) {
244                 nextNode = sibling;
245                 if (nextNode.hasChildren || nextNode.call.isDrawingCall)
246                     break;
247             } else {
248                 nextNode = nextNode.parent;
249                 if (!forward)
250                     break;
251             }
252         }
253         if (!nextNode && forward)
254             this._onReplayLastStepClick();
255         else
256             (nextNode || selectedNode).revealAndSelect();
257     },
258
259     _onReplayFirstStepClick: function()
260     {
261         var firstNode = this._logGrid.rootNode().children[0];
262         if (firstNode)
263             firstNode.revealAndSelect();
264     },
265
266     _onReplayLastStepClick: function()
267     {
268         var lastNode = this._logGrid.rootNode().children.peekLast();
269         if (!lastNode)
270             return;
271         while (lastNode.expanded) {
272             var lastChild = lastNode.children.peekLast();
273             if (!lastChild)
274                 break;
275             lastNode = lastChild;
276         }
277         lastNode.revealAndSelect();
278     },
279
280     /**
281      * @param {boolean} enable
282      */
283     _enableWaitIcon: function(enable)
284     {
285         this._spinnerIcon.classList.toggle("hidden", !enable);
286         this._debugInfoElement.classList.toggle("hidden", enable);
287     },
288
289     _replayTraceLog: function()
290     {
291         if (this._pendingReplayTraceLogEvent)
292             return;
293         var index = this._selectedCallIndex();
294         if (index === -1 || index === this._lastReplayCallIndex)
295             return;
296         this._lastReplayCallIndex = index;
297         this._pendingReplayTraceLogEvent = true;
298
299         /**
300          * @param {?CanvasAgent.ResourceState} resourceState
301          * @param {number} replayTime
302          * @this {WebInspector.CanvasProfileView}
303          */
304         function didReplayTraceLog(resourceState, replayTime)
305         {
306             delete this._pendingReplayTraceLogEvent;
307             this._enableWaitIcon(false);
308
309             this._debugInfoElement.textContent = WebInspector.UIString("Replay time: %s", Number.secondsToString(replayTime / 1000, true));
310             this._onReplayContextChanged();
311
312             if (index !== this._selectedCallIndex())
313                 this._replayTraceLog();
314         }
315         this._enableWaitIcon(true);
316         this._traceLogPlayer.replayTraceLog(index, didReplayTraceLog.bind(this));
317     },
318
319     /**
320      * @param {number} offset
321      */
322     _requestTraceLog: function(offset)
323     {
324         /**
325          * @param {?CanvasAgent.TraceLog} traceLog
326          * @this {WebInspector.CanvasProfileView}
327          */
328         function didReceiveTraceLog(traceLog)
329         {
330             this._enableWaitIcon(false);
331             if (!traceLog)
332                 return;
333             var callNodes = [];
334             var calls = traceLog.calls;
335             var index = traceLog.startOffset;
336             for (var i = 0, n = calls.length; i < n; ++i)
337                 callNodes.push(this._createCallNode(index++, calls[i]));
338             var contexts = traceLog.contexts;
339             for (var i = 0, n = contexts.length; i < n; ++i) {
340                 var contextId = contexts[i].resourceId || "";
341                 var description = contexts[i].description || "";
342                 if (this._replayContexts[contextId])
343                     continue;
344                 this._replayContexts[contextId] = true;
345                 this._replayContextSelector.createOption(description, WebInspector.UIString("Show screenshot of this context's canvas."), contextId);
346             }
347             this._appendCallNodes(callNodes);
348             if (traceLog.alive)
349                 setTimeout(this._requestTraceLog.bind(this, index), WebInspector.CanvasProfileView.TraceLogPollingInterval);
350             else
351                 this._flattenSingleFrameNode();
352             this._profile._updateCapturingStatus(traceLog);
353             this._onReplayLastStepClick(); // Automatically replay the last step.
354         }
355         this._enableWaitIcon(true);
356         this._traceLogPlayer.getTraceLog(offset, undefined, didReceiveTraceLog.bind(this));
357     },
358
359     /**
360      * @return {number}
361      */
362     _selectedCallIndex: function()
363     {
364         var node = this._logGrid.selectedNode;
365         return node ? this._peekLastRecursively(node).index : -1;
366     },
367
368     /**
369      * @param {!WebInspector.DataGridNode} node
370      * @return {!WebInspector.DataGridNode}
371      */
372     _peekLastRecursively: function(node)
373     {
374         var lastChild;
375         while ((lastChild = node.children.peekLast()))
376             node = lastChild;
377         return node;
378     },
379
380     /**
381      * @param {!Array.<!WebInspector.DataGridNode>} callNodes
382      */
383     _appendCallNodes: function(callNodes)
384     {
385         var rootNode = this._logGrid.rootNode();
386         var frameNode = rootNode.children.peekLast();
387         if (frameNode && this._peekLastRecursively(frameNode).call.isFrameEndCall)
388             frameNode = null;
389         for (var i = 0, n = callNodes.length; i < n; ++i) {
390             if (!frameNode) {
391                 var index = rootNode.children.length;
392                 var data = {};
393                 data[0] = "";
394                 data[1] = WebInspector.UIString("Frame #%d", index + 1);
395                 data[2] = "";
396                 frameNode = new WebInspector.DataGridNode(data);
397                 frameNode.selectable = true;
398                 rootNode.appendChild(frameNode);
399             }
400             var nextFrameCallIndex = i + 1;
401             while (nextFrameCallIndex < n && !callNodes[nextFrameCallIndex - 1].call.isFrameEndCall)
402                 ++nextFrameCallIndex;
403             this._appendCallNodesToFrameNode(frameNode, callNodes, i, nextFrameCallIndex);
404             i = nextFrameCallIndex - 1;
405             frameNode = null;
406         }
407     },
408
409     /**
410      * @param {!WebInspector.DataGridNode} frameNode
411      * @param {!Array.<!WebInspector.DataGridNode>} callNodes
412      * @param {number} fromIndex
413      * @param {number} toIndex not inclusive
414      */
415     _appendCallNodesToFrameNode: function(frameNode, callNodes, fromIndex, toIndex)
416     {
417         var self = this;
418         function appendDrawCallGroup()
419         {
420             var index = self._drawCallGroupsCount || 0;
421             var data = {};
422             data[0] = "";
423             data[1] = WebInspector.UIString("Draw call group #%d", index + 1);
424             data[2] = "";
425             var node = new WebInspector.DataGridNode(data);
426             node.selectable = true;
427             self._drawCallGroupsCount = index + 1;
428             frameNode.appendChild(node);
429             return node;
430         }
431
432         function splitDrawCallGroup(drawCallGroup)
433         {
434             var splitIndex = 0;
435             var splitNode;
436             while ((splitNode = drawCallGroup.children[splitIndex])) {
437                 if (splitNode.call.isDrawingCall)
438                     break;
439                 ++splitIndex;
440             }
441             var newDrawCallGroup = appendDrawCallGroup();
442             var lastNode;
443             while ((lastNode = drawCallGroup.children[splitIndex + 1]))
444                 newDrawCallGroup.appendChild(lastNode);
445             return newDrawCallGroup;
446         }
447
448         var drawCallGroup = frameNode.children.peekLast();
449         var groupHasDrawCall = false;
450         if (drawCallGroup) {
451             for (var i = 0, n = drawCallGroup.children.length; i < n; ++i) {
452                 if (drawCallGroup.children[i].call.isDrawingCall) {
453                     groupHasDrawCall = true;
454                     break;
455                 }
456             }
457         } else
458             drawCallGroup = appendDrawCallGroup();
459
460         for (var i = fromIndex; i < toIndex; ++i) {
461             var node = callNodes[i];
462             drawCallGroup.appendChild(node);
463             if (node.call.isDrawingCall) {
464                 if (groupHasDrawCall)
465                     drawCallGroup = splitDrawCallGroup(drawCallGroup);
466                 else
467                     groupHasDrawCall = true;
468             }
469         }
470     },
471
472     /**
473      * @param {number} index
474      * @param {!CanvasAgent.Call} call
475      * @return {!WebInspector.DataGridNode}
476      */
477     _createCallNode: function(index, call)
478     {
479         var callViewElement = document.createElement("div");
480
481         var data = {};
482         data[0] = index + 1;
483         data[1] = callViewElement;
484         data[2] = "";
485         if (call.sourceURL) {
486             // FIXME(62725): stack trace line/column numbers are one-based.
487             var lineNumber = Math.max(0, call.lineNumber - 1) || 0;
488             var columnNumber = Math.max(0, call.columnNumber - 1) || 0;
489             data[2] = this._linkifier.linkifyLocation(this.profile.target(), call.sourceURL, lineNumber, columnNumber);
490         }
491
492         callViewElement.createChild("span", "canvas-function-name").textContent = call.functionName || "context." + call.property;
493
494         if (call.arguments) {
495             callViewElement.createTextChild("(");
496             for (var i = 0, n = call.arguments.length; i < n; ++i) {
497                 var argument = /** @type {!CanvasAgent.CallArgument} */ (call.arguments[i]);
498                 if (i)
499                     callViewElement.createTextChild(", ");
500                 var element = WebInspector.CanvasProfileDataGridHelper.createCallArgumentElement(argument);
501                 element.__argumentIndex = i;
502                 callViewElement.appendChild(element);
503             }
504             callViewElement.createTextChild(")");
505         } else if (call.value) {
506             callViewElement.createTextChild(" = ");
507             callViewElement.appendChild(WebInspector.CanvasProfileDataGridHelper.createCallArgumentElement(call.value));
508         }
509
510         if (call.result) {
511             callViewElement.createTextChild(" => ");
512             callViewElement.appendChild(WebInspector.CanvasProfileDataGridHelper.createCallArgumentElement(call.result));
513         }
514
515         var node = new WebInspector.DataGridNode(data);
516         node.index = index;
517         node.selectable = true;
518         node.call = call;
519         return node;
520     },
521
522     _popoverAnchor: function(element, event)
523     {
524         var argumentElement = element.enclosingNodeOrSelfWithClass("canvas-call-argument");
525         if (!argumentElement || argumentElement.__suppressPopover)
526             return null;
527         return argumentElement;
528     },
529
530     _resolveObjectForPopover: function(argumentElement, showCallback, objectGroupName)
531     {
532         /**
533          * @param {?Protocol.Error} error
534          * @param {!RuntimeAgent.RemoteObject=} result
535          * @param {!CanvasAgent.ResourceState=} resourceState
536          * @this {WebInspector.CanvasProfileView}
537          */
538         function showObjectPopover(error, result, resourceState)
539         {
540             if (error)
541                 return;
542
543             // FIXME: handle resourceState also
544             if (!result)
545                 return;
546
547             this._popoverAnchorElement = argumentElement.cloneNode(true);
548             this._popoverAnchorElement.classList.add("canvas-popover-anchor");
549             this._popoverAnchorElement.classList.add("source-frame-eval-expression");
550             argumentElement.parentElement.appendChild(this._popoverAnchorElement);
551
552             var diffLeft = this._popoverAnchorElement.boxInWindow().x - argumentElement.boxInWindow().x;
553             this._popoverAnchorElement.style.left = this._popoverAnchorElement.offsetLeft - diffLeft + "px";
554
555             showCallback(WebInspector.runtimeModel.createRemoteObject(result), false, this._popoverAnchorElement);
556         }
557
558         var evalResult = argumentElement.__evalResult;
559         if (evalResult)
560             showObjectPopover.call(this, null, evalResult);
561         else {
562             var dataGridNode = this._logGrid.dataGridNodeFromNode(argumentElement);
563             if (!dataGridNode || typeof dataGridNode.index !== "number") {
564                 this._popoverHelper.hidePopover();
565                 return;
566             }
567             var callIndex = dataGridNode.index;
568             var argumentIndex = argumentElement.__argumentIndex;
569             if (typeof argumentIndex !== "number")
570                 argumentIndex = -1;
571             CanvasAgent.evaluateTraceLogCallArgument(this._traceLogId, callIndex, argumentIndex, objectGroupName, showObjectPopover.bind(this));
572         }
573     },
574
575     /**
576      * @param {!WebInspector.RemoteObject} object
577      * @return {string}
578      */
579     _hexNumbersFormatter: function(object)
580     {
581         if (object.type === "number") {
582             // Show enum values in hex with min length of 4 (e.g. 0x0012).
583             var str = "0000" + Number(object.description).toString(16).toUpperCase();
584             str = str.replace(/^0+(.{4,})$/, "$1");
585             return "0x" + str;
586         }
587         return object.description || "";
588     },
589
590     _onHidePopover: function()
591     {
592         if (this._popoverAnchorElement) {
593             this._popoverAnchorElement.remove()
594             delete this._popoverAnchorElement;
595         }
596     },
597
598     _flattenSingleFrameNode: function()
599     {
600         var rootNode = this._logGrid.rootNode();
601         if (rootNode.children.length !== 1)
602             return;
603         var frameNode = rootNode.children[0];
604         while (frameNode.children[0])
605             rootNode.appendChild(frameNode.children[0]);
606         rootNode.removeChild(frameNode);
607     },
608
609     __proto__: WebInspector.VBox.prototype
610 }
611
612 /**
613  * @constructor
614  * @extends {WebInspector.ProfileType}
615  */
616 WebInspector.CanvasProfileType = function()
617 {
618     WebInspector.ProfileType.call(this, WebInspector.CanvasProfileType.TypeId, WebInspector.UIString("Capture Canvas Frame"));
619     this._recording = false;
620     this._lastProfileHeader = null;
621
622     this._capturingModeSelector = new WebInspector.StatusBarComboBox(this._dispatchViewUpdatedEvent.bind(this));
623     this._capturingModeSelector.element.title = WebInspector.UIString("Canvas capture mode.");
624     this._capturingModeSelector.createOption(WebInspector.UIString("Single Frame"), WebInspector.UIString("Capture a single canvas frame."), "");
625     this._capturingModeSelector.createOption(WebInspector.UIString("Consecutive Frames"), WebInspector.UIString("Capture consecutive canvas frames."), "1");
626
627     /** @type {!Object.<string, !Element>} */
628     this._frameOptions = {};
629
630     /** @type {!Object.<string, boolean>} */
631     this._framesWithCanvases = {};
632
633     this._frameSelector = new WebInspector.StatusBarComboBox(this._dispatchViewUpdatedEvent.bind(this));
634     this._frameSelector.element.title = WebInspector.UIString("Frame containing the canvases to capture.");
635     this._frameSelector.element.classList.add("hidden");
636
637     this._target = /** @type {!WebInspector.Target} */ (WebInspector.targetManager.activeTarget());
638     this._target.resourceTreeModel.frames().forEach(this._addFrame, this);
639     this._target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameAdded, this._frameAdded, this);
640     this._target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameDetached, this._frameRemoved, this);
641
642     this._dispatcher = new WebInspector.CanvasDispatcher(this);
643     this._canvasAgentEnabled = false;
644
645     this._decorationElement = document.createElement("div");
646     this._decorationElement.className = "profile-canvas-decoration";
647     this._updateDecorationElement();
648 }
649
650 WebInspector.CanvasProfileType.TypeId = "CANVAS_PROFILE";
651
652 WebInspector.CanvasProfileType.prototype = {
653     get statusBarItems()
654     {
655         return [this._capturingModeSelector.element, this._frameSelector.element];
656     },
657
658     get buttonTooltip()
659     {
660         if (this._isSingleFrameMode())
661             return WebInspector.UIString("Capture next canvas frame.");
662         else
663             return this._recording ? WebInspector.UIString("Stop capturing canvas frames.") : WebInspector.UIString("Start capturing canvas frames.");
664     },
665
666     /**
667      * @override
668      * @return {boolean}
669      */
670     buttonClicked: function()
671     {
672         if (!this._canvasAgentEnabled)
673             return false;
674         if (this._recording) {
675             this._recording = false;
676             this._stopFrameCapturing();
677         } else if (this._isSingleFrameMode()) {
678             this._recording = false;
679             this._runSingleFrameCapturing();
680         } else {
681             this._recording = true;
682             this._startFrameCapturing();
683         }
684         return this._recording;
685     },
686
687     _runSingleFrameCapturing: function()
688     {
689         var frameId = this._selectedFrameId();
690         this._target.profilingLock.acquire();
691         CanvasAgent.captureFrame(frameId, this._didStartCapturingFrame.bind(this, frameId));
692         this._target.profilingLock.release();
693     },
694
695     _startFrameCapturing: function()
696     {
697         var frameId = this._selectedFrameId();
698         this._target.profilingLock.acquire();
699         CanvasAgent.startCapturing(frameId, this._didStartCapturingFrame.bind(this, frameId));
700     },
701
702     _stopFrameCapturing: function()
703     {
704         if (!this._lastProfileHeader) {
705             this._target.profilingLock.release();
706             return;
707         }
708         var profileHeader = this._lastProfileHeader;
709         var traceLogId = profileHeader.traceLogId();
710         this._lastProfileHeader = null;
711         function didStopCapturing()
712         {
713             profileHeader._updateCapturingStatus();
714         }
715         CanvasAgent.stopCapturing(traceLogId, didStopCapturing);
716         this._target.profilingLock.release();
717     },
718
719     /**
720      * @param {string|undefined} frameId
721      * @param {?Protocol.Error} error
722      * @param {!CanvasAgent.TraceLogId} traceLogId
723      */
724     _didStartCapturingFrame: function(frameId, error, traceLogId)
725     {
726         if (error || this._lastProfileHeader && this._lastProfileHeader.traceLogId() === traceLogId)
727             return;
728         var profileHeader = new WebInspector.CanvasProfileHeader(this._target, this, traceLogId, frameId);
729         this._lastProfileHeader = profileHeader;
730         this.addProfile(profileHeader);
731         profileHeader._updateCapturingStatus();
732     },
733
734     get treeItemTitle()
735     {
736         return WebInspector.UIString("CANVAS PROFILE");
737     },
738
739     get description()
740     {
741         return WebInspector.UIString("Canvas calls instrumentation");
742     },
743
744     /**
745      * @override
746      * @return {!Element}
747      */
748     decorationElement: function()
749     {
750         return this._decorationElement;
751     },
752
753     /**
754      * @override
755      * @param {!WebInspector.ProfileHeader} profile
756      */
757     removeProfile: function(profile)
758     {
759         WebInspector.ProfileType.prototype.removeProfile.call(this, profile);
760         if (this._recording && profile === this._lastProfileHeader)
761             this._recording = false;
762     },
763
764     /**
765      * @param {boolean=} forcePageReload
766      */
767     _updateDecorationElement: function(forcePageReload)
768     {
769         this._decorationElement.removeChildren();
770         this._decorationElement.createChild("div", "warning-icon-small");
771         this._decorationElement.appendChild(document.createTextNode(this._canvasAgentEnabled ? WebInspector.UIString("Canvas Profiler is enabled.") : WebInspector.UIString("Canvas Profiler is disabled.")));
772         var button = this._decorationElement.createChild("button");
773         button.type = "button";
774         button.textContent = this._canvasAgentEnabled ? WebInspector.UIString("Disable") : WebInspector.UIString("Enable");
775         button.addEventListener("click", this._onProfilerEnableButtonClick.bind(this, !this._canvasAgentEnabled), false);
776
777         var target = this._target;
778         /**
779          * @param {?Protocol.Error} error
780          * @param {boolean} result
781          */
782         function hasUninstrumentedCanvasesCallback(error, result)
783         {
784             if (error || result)
785                 target.resourceTreeModel.reloadPage();
786         }
787
788         if (forcePageReload) {
789             if (this._canvasAgentEnabled) {
790                 CanvasAgent.hasUninstrumentedCanvases(hasUninstrumentedCanvasesCallback);
791             } else {
792                 for (var frameId in this._framesWithCanvases) {
793                     if (this._framesWithCanvases.hasOwnProperty(frameId)) {
794                         target.resourceTreeModel.reloadPage();
795                         break;
796                     }
797                 }
798             }
799         }
800     },
801
802     /**
803      * @param {boolean} enable
804      */
805     _onProfilerEnableButtonClick: function(enable)
806     {
807         if (this._canvasAgentEnabled === enable)
808             return;
809
810         /**
811          * @param {?Protocol.Error} error
812          * @this {WebInspector.CanvasProfileType}
813          */
814         function callback(error)
815         {
816             if (error)
817                 return;
818             this._canvasAgentEnabled = enable;
819             this._updateDecorationElement(true);
820             this._dispatchViewUpdatedEvent();
821         }
822         if (enable)
823             CanvasAgent.enable(callback.bind(this));
824         else
825             CanvasAgent.disable(callback.bind(this));
826     },
827
828     /**
829      * @return {boolean}
830      */
831     _isSingleFrameMode: function()
832     {
833         return !this._capturingModeSelector.selectedOption().value;
834     },
835
836     /**
837      * @param {!WebInspector.Event} event
838      */
839     _frameAdded: function(event)
840     {
841         var frame = /** @type {!WebInspector.ResourceTreeFrame} */ (event.data);
842         this._addFrame(frame);
843     },
844
845     /**
846      * @param {!WebInspector.ResourceTreeFrame} frame
847      */
848     _addFrame: function(frame)
849     {
850         var frameId = frame.id;
851         var option = document.createElement("option");
852         option.text = frame.displayName();
853         option.title = frame.url;
854         option.value = frameId;
855
856         this._frameOptions[frameId] = option;
857
858         if (this._framesWithCanvases[frameId]) {
859             this._frameSelector.addOption(option);
860             this._dispatchViewUpdatedEvent();
861         }
862     },
863
864     /**
865      * @param {!WebInspector.Event} event
866      */
867     _frameRemoved: function(event)
868     {
869         var frame = /** @type {!WebInspector.ResourceTreeFrame} */ (event.data);
870         var frameId = frame.id;
871         var option = this._frameOptions[frameId];
872         if (option && this._framesWithCanvases[frameId]) {
873             this._frameSelector.removeOption(option);
874             this._dispatchViewUpdatedEvent();
875         }
876         delete this._frameOptions[frameId];
877         delete this._framesWithCanvases[frameId];
878     },
879
880     /**
881      * @param {string} frameId
882      */
883     _contextCreated: function(frameId)
884     {
885         if (this._framesWithCanvases[frameId])
886             return;
887         this._framesWithCanvases[frameId] = true;
888         var option = this._frameOptions[frameId];
889         if (option) {
890             this._frameSelector.addOption(option);
891             this._dispatchViewUpdatedEvent();
892         }
893     },
894
895     /**
896      * @param {!PageAgent.FrameId=} frameId
897      * @param {!CanvasAgent.TraceLogId=} traceLogId
898      */
899     _traceLogsRemoved: function(frameId, traceLogId)
900     {
901         var sidebarElementsToDelete = [];
902         var sidebarElements = /** @type {!Array.<!WebInspector.ProfileSidebarTreeElement>} */ ((this.treeElement && this.treeElement.children) || []);
903         for (var i = 0, n = sidebarElements.length; i < n; ++i) {
904             var header = /** @type {!WebInspector.CanvasProfileHeader} */ (sidebarElements[i].profile);
905             if (!header)
906                 continue;
907             if (frameId && frameId !== header.frameId())
908                 continue;
909             if (traceLogId && traceLogId !== header.traceLogId())
910                 continue;
911             sidebarElementsToDelete.push(sidebarElements[i]);
912         }
913         for (var i = 0, n = sidebarElementsToDelete.length; i < n; ++i)
914             sidebarElementsToDelete[i].ondelete();
915     },
916
917     /**
918      * @return {string|undefined}
919      */
920     _selectedFrameId: function()
921     {
922         var option = this._frameSelector.selectedOption();
923         return option ? option.value : undefined;
924     },
925
926     _dispatchViewUpdatedEvent: function()
927     {
928         this._frameSelector.element.classList.toggle("hidden", this._frameSelector.size() <= 1);
929         this.dispatchEventToListeners(WebInspector.ProfileType.Events.ViewUpdated);
930     },
931
932     /**
933      * @override
934      * @return {boolean}
935      */
936     isInstantProfile: function()
937     {
938         return this._isSingleFrameMode();
939     },
940
941     /**
942      * @override
943      * @return {boolean}
944      */
945     isEnabled: function()
946     {
947         return this._canvasAgentEnabled;
948     },
949
950     __proto__: WebInspector.ProfileType.prototype
951 }
952
953 /**
954  * @constructor
955  * @implements {CanvasAgent.Dispatcher}
956  * @param {!WebInspector.CanvasProfileType} profileType
957  */
958 WebInspector.CanvasDispatcher = function(profileType)
959 {
960     this._profileType = profileType;
961     InspectorBackend.registerCanvasDispatcher(this);
962 }
963
964 WebInspector.CanvasDispatcher.prototype = {
965     /**
966      * @param {string} frameId
967      */
968     contextCreated: function(frameId)
969     {
970         this._profileType._contextCreated(frameId);
971     },
972
973     /**
974      * @param {!PageAgent.FrameId=} frameId
975      * @param {!CanvasAgent.TraceLogId=} traceLogId
976      */
977     traceLogsRemoved: function(frameId, traceLogId)
978     {
979         this._profileType._traceLogsRemoved(frameId, traceLogId);
980     }
981 }
982
983 /**
984  * @constructor
985  * @extends {WebInspector.ProfileHeader}
986  * @param {!WebInspector.Target} target
987  * @param {!WebInspector.CanvasProfileType} type
988  * @param {!CanvasAgent.TraceLogId=} traceLogId
989  * @param {!PageAgent.FrameId=} frameId
990  */
991 WebInspector.CanvasProfileHeader = function(target, type, traceLogId, frameId)
992 {
993     WebInspector.ProfileHeader.call(this, target, type, WebInspector.UIString("Trace Log %d", type._nextProfileUid));
994     /** @type {!CanvasAgent.TraceLogId} */
995     this._traceLogId = traceLogId || "";
996     this._frameId = frameId;
997     this._alive = true;
998     this._traceLogSize = 0;
999     this._traceLogPlayer = traceLogId ? new WebInspector.CanvasTraceLogPlayerProxy(traceLogId) : null;
1000 }
1001
1002 WebInspector.CanvasProfileHeader.prototype = {
1003     /**
1004      * @return {!CanvasAgent.TraceLogId}
1005      */
1006     traceLogId: function()
1007     {
1008         return this._traceLogId;
1009     },
1010
1011     /**
1012      * @return {?WebInspector.CanvasTraceLogPlayerProxy}
1013      */
1014     traceLogPlayer: function()
1015     {
1016         return this._traceLogPlayer;
1017     },
1018
1019     /**
1020      * @return {!PageAgent.FrameId|undefined}
1021      */
1022     frameId: function()
1023     {
1024         return this._frameId;
1025     },
1026
1027     /**
1028      * @override
1029      * @param {!WebInspector.ProfilesPanel} panel
1030      * @return {!WebInspector.ProfileSidebarTreeElement}
1031      */
1032     createSidebarTreeElement: function(panel)
1033     {
1034         return new WebInspector.ProfileSidebarTreeElement(panel, this, "profile-sidebar-tree-item");
1035     },
1036
1037     /**
1038      * @override
1039      * @return {!WebInspector.CanvasProfileView}
1040      */
1041     createView: function()
1042     {
1043         return new WebInspector.CanvasProfileView(this);
1044     },
1045
1046     /**
1047      * @override
1048      */
1049     dispose: function()
1050     {
1051         if (this._traceLogPlayer)
1052             this._traceLogPlayer.dispose();
1053         clearTimeout(this._requestStatusTimer);
1054         this._alive = false;
1055     },
1056
1057     /**
1058      * @param {!CanvasAgent.TraceLog=} traceLog
1059      */
1060     _updateCapturingStatus: function(traceLog)
1061     {
1062         if (!this._traceLogId)
1063             return;
1064
1065         if (traceLog) {
1066             this._alive = traceLog.alive;
1067             this._traceLogSize = traceLog.totalAvailableCalls;
1068         }
1069
1070         var subtitle = this._alive ? WebInspector.UIString("Capturing\u2026 %d calls", this._traceLogSize) : WebInspector.UIString("Captured %d calls", this._traceLogSize);
1071         this.updateStatus(subtitle, this._alive);
1072
1073         if (this._alive) {
1074             clearTimeout(this._requestStatusTimer);
1075             this._requestStatusTimer = setTimeout(this._requestCapturingStatus.bind(this), WebInspector.CanvasProfileView.TraceLogPollingInterval);
1076         }
1077     },
1078
1079     _requestCapturingStatus: function()
1080     {
1081         /**
1082          * @param {?CanvasAgent.TraceLog} traceLog
1083          * @this {WebInspector.CanvasProfileHeader}
1084          */
1085         function didReceiveTraceLog(traceLog)
1086         {
1087             if (!traceLog)
1088                 return;
1089             this._alive = traceLog.alive;
1090             this._traceLogSize = traceLog.totalAvailableCalls;
1091             this._updateCapturingStatus();
1092         }
1093         this._traceLogPlayer.getTraceLog(0, 0, didReceiveTraceLog.bind(this));
1094     },
1095
1096     __proto__: WebInspector.ProfileHeader.prototype
1097 }
1098
1099 WebInspector.CanvasProfileDataGridHelper = {
1100     /**
1101      * @param {!CanvasAgent.CallArgument} callArgument
1102      * @return {!Element}
1103      */
1104     createCallArgumentElement: function(callArgument)
1105     {
1106         if (callArgument.enumName)
1107             return WebInspector.CanvasProfileDataGridHelper.createEnumValueElement(callArgument.enumName, +callArgument.description);
1108         var element = document.createElement("span");
1109         element.className = "canvas-call-argument";
1110         var description = callArgument.description;
1111         if (callArgument.type === "string") {
1112             const maxStringLength = 150;
1113             element.createTextChild("\"");
1114             element.createChild("span", "canvas-formatted-string").textContent = description.trimMiddle(maxStringLength);
1115             element.createTextChild("\"");
1116             element.__suppressPopover = (description.length <= maxStringLength && !/[\r\n]/.test(description));
1117             if (!element.__suppressPopover)
1118                 element.__evalResult = WebInspector.runtimeModel.createRemoteObjectFromPrimitiveValue(description);
1119         } else {
1120             var type = callArgument.subtype || callArgument.type;
1121             if (type) {
1122                 element.classList.add("canvas-formatted-" + type);
1123                 if (["null", "undefined", "boolean", "number"].indexOf(type) >= 0)
1124                     element.__suppressPopover = true;
1125             }
1126             element.textContent = description;
1127             if (callArgument.remoteObject)
1128                 element.__evalResult = WebInspector.runtimeModel.createRemoteObject(callArgument.remoteObject);
1129         }
1130         if (callArgument.resourceId) {
1131             element.classList.add("canvas-formatted-resource");
1132             element.__resourceId = callArgument.resourceId;
1133         }
1134         return element;
1135     },
1136
1137     /**
1138      * @param {string} enumName
1139      * @param {number} enumValue
1140      * @return {!Element}
1141      */
1142     createEnumValueElement: function(enumName, enumValue)
1143     {
1144         var element = document.createElement("span");
1145         element.className = "canvas-call-argument canvas-formatted-number";
1146         element.textContent = enumName;
1147         element.__evalResult = WebInspector.runtimeModel.createRemoteObjectFromPrimitiveValue(enumValue);
1148         return element;
1149     }
1150 }
1151
1152 /**
1153  * @extends {WebInspector.Object}
1154  * @constructor
1155  * @param {!CanvasAgent.TraceLogId} traceLogId
1156  */
1157 WebInspector.CanvasTraceLogPlayerProxy = function(traceLogId)
1158 {
1159     this._traceLogId = traceLogId;
1160     /** @type {!Object.<string, !CanvasAgent.ResourceState>} */
1161     this._currentResourceStates = {};
1162     /** @type {?CanvasAgent.ResourceId} */
1163     this._defaultResourceId = null;
1164 }
1165
1166 /** @enum {string} */
1167 WebInspector.CanvasTraceLogPlayerProxy.Events = {
1168     CanvasTraceLogReceived: "CanvasTraceLogReceived",
1169     CanvasReplayStateChanged: "CanvasReplayStateChanged",
1170     CanvasResourceStateReceived: "CanvasResourceStateReceived",
1171 }
1172
1173 WebInspector.CanvasTraceLogPlayerProxy.prototype = {
1174     /**
1175      * @param {number|undefined} startOffset
1176      * @param {number|undefined} maxLength
1177      * @param {function(?CanvasAgent.TraceLog):void} userCallback
1178      */
1179     getTraceLog: function(startOffset, maxLength, userCallback)
1180     {
1181         /**
1182          * @param {?Protocol.Error} error
1183          * @param {!CanvasAgent.TraceLog} traceLog
1184          * @this {WebInspector.CanvasTraceLogPlayerProxy}
1185          */
1186         function callback(error, traceLog)
1187         {
1188             if (error || !traceLog) {
1189                 userCallback(null);
1190                 return;
1191             }
1192             userCallback(traceLog);
1193             this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasTraceLogReceived, traceLog);
1194         }
1195         CanvasAgent.getTraceLog(this._traceLogId, startOffset, maxLength, callback.bind(this));
1196     },
1197
1198     dispose: function()
1199     {
1200         this._currentResourceStates = {};
1201         CanvasAgent.dropTraceLog(this._traceLogId);
1202         this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasReplayStateChanged);
1203     },
1204
1205     /**
1206      * @param {?CanvasAgent.ResourceId} resourceId
1207      * @param {function(?CanvasAgent.ResourceState):void} userCallback
1208      */
1209     getResourceState: function(resourceId, userCallback)
1210     {
1211         resourceId = resourceId || this._defaultResourceId;
1212         if (!resourceId) {
1213             userCallback(null); // Has not been replayed yet.
1214             return;
1215         }
1216         var effectiveResourceId = /** @type {!CanvasAgent.ResourceId} */ (resourceId);
1217         if (this._currentResourceStates[effectiveResourceId]) {
1218             userCallback(this._currentResourceStates[effectiveResourceId]);
1219             return;
1220         }
1221
1222         /**
1223          * @param {?Protocol.Error} error
1224          * @param {!CanvasAgent.ResourceState} resourceState
1225          * @this {WebInspector.CanvasTraceLogPlayerProxy}
1226          */
1227         function callback(error, resourceState)
1228         {
1229             if (error || !resourceState) {
1230                 userCallback(null);
1231                 return;
1232             }
1233             this._currentResourceStates[effectiveResourceId] = resourceState;
1234             userCallback(resourceState);
1235             this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasResourceStateReceived, resourceState);
1236         }
1237         CanvasAgent.getResourceState(this._traceLogId, effectiveResourceId, callback.bind(this));
1238     },
1239
1240     /**
1241      * @param {number} index
1242      * @param {function(?CanvasAgent.ResourceState, number):void} userCallback
1243      */
1244     replayTraceLog: function(index, userCallback)
1245     {
1246         /**
1247          * @param {?Protocol.Error} error
1248          * @param {!CanvasAgent.ResourceState} resourceState
1249          * @param {number} replayTime
1250          * @this {WebInspector.CanvasTraceLogPlayerProxy}
1251          */
1252         function callback(error, resourceState, replayTime)
1253         {
1254             this._currentResourceStates = {};
1255             if (error) {
1256                 userCallback(null, replayTime);
1257             } else {
1258                 this._defaultResourceId = resourceState.id;
1259                 this._currentResourceStates[resourceState.id] = resourceState;
1260                 userCallback(resourceState, replayTime);
1261             }
1262             this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasReplayStateChanged);
1263             if (!error)
1264                 this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasResourceStateReceived, resourceState);
1265         }
1266         CanvasAgent.replayTraceLog(this._traceLogId, index, callback.bind(this));
1267     },
1268
1269     clearResourceStates: function()
1270     {
1271         this._currentResourceStates = {};
1272         this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasReplayStateChanged);
1273     },
1274
1275     __proto__: WebInspector.Object.prototype
1276 }