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