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