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