2 * Copyright (C) 2013 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
33 * @extends {WebInspector.VBox}
34 * @param {!WebInspector.CanvasProfileHeader} profile
36 WebInspector.CanvasProfileView = function(profile)
38 WebInspector.VBox.call(this);
39 this.registerRequiredCSS("canvasProfiler.css");
40 this.element.classList.add("canvas-profile-view");
41 this._profile = profile;
42 this._traceLogId = profile.traceLogId();
43 this._traceLogPlayer = /** @type {!WebInspector.CanvasTraceLogPlayerProxy} */ (profile.traceLogPlayer());
44 this._linkifier = new WebInspector.Linkifier();
46 this._replayInfoSplitView = new WebInspector.SplitView(true, true, "canvasProfileViewReplaySplitViewState", 0.34);
47 this._replayInfoSplitView.show(this.element);
49 this._imageSplitView = new WebInspector.SplitView(false, true, "canvasProfileViewSplitViewState", 300);
50 this._imageSplitView.show(this._replayInfoSplitView.mainElement());
52 var replayImageContainerView = new WebInspector.VBox();
53 replayImageContainerView.setMinimumSize(50, 28);
54 replayImageContainerView.show(this._imageSplitView.mainElement());
56 // NOTE: The replayImageContainer can NOT be a flex div (e.g. VBox or SplitView elements)!
57 var replayImageContainer = replayImageContainerView.element.createChild("div");
58 replayImageContainer.id = "canvas-replay-image-container";
59 this._replayImageElement = replayImageContainer.createChild("img", "canvas-replay-image");
60 this._debugInfoElement = replayImageContainer.createChild("div", "canvas-debug-info hidden");
61 this._spinnerIcon = replayImageContainer.createChild("div", "spinner-icon small hidden");
63 var replayLogContainerView = new WebInspector.VBox();
64 replayLogContainerView.setMinimumSize(22, 22);
65 replayLogContainerView.show(this._imageSplitView.sidebarElement());
67 var replayLogContainer = replayLogContainerView.element;
68 var controlsContainer = replayLogContainer.createChild("div", "status-bar");
69 var logGridContainer = replayLogContainer.createChild("div", "canvas-replay-log");
71 this._createControlButton(controlsContainer, "canvas-replay-first-step", WebInspector.UIString("First call."), this._onReplayFirstStepClick.bind(this));
72 this._createControlButton(controlsContainer, "canvas-replay-prev-step", WebInspector.UIString("Previous call."), this._onReplayStepClick.bind(this, false));
73 this._createControlButton(controlsContainer, "canvas-replay-next-step", WebInspector.UIString("Next call."), this._onReplayStepClick.bind(this, true));
74 this._createControlButton(controlsContainer, "canvas-replay-prev-draw", WebInspector.UIString("Previous drawing call."), this._onReplayDrawingCallClick.bind(this, false));
75 this._createControlButton(controlsContainer, "canvas-replay-next-draw", WebInspector.UIString("Next drawing call."), this._onReplayDrawingCallClick.bind(this, true));
76 this._createControlButton(controlsContainer, "canvas-replay-last-step", WebInspector.UIString("Last call."), this._onReplayLastStepClick.bind(this));
78 this._replayContextSelector = new WebInspector.StatusBarComboBox(this._onReplayContextChanged.bind(this));
79 this._replayContextSelector.createOption(WebInspector.UIString("<screenshot auto>"), WebInspector.UIString("Show screenshot of the last replayed resource."), "");
80 controlsContainer.appendChild(this._replayContextSelector.element);
82 this._installReplayInfoSidebarWidgets(controlsContainer);
84 this._replayStateView = new WebInspector.CanvasReplayStateView(this._traceLogPlayer);
85 this._replayStateView.show(this._replayInfoSplitView.sidebarElement());
87 /** @type {!Object.<string, boolean>} */
88 this._replayContexts = {};
91 {title: "#", sortable: false, width: "5%"},
92 {title: WebInspector.UIString("Call"), sortable: false, width: "75%", disclosure: true},
93 {title: WebInspector.UIString("Location"), sortable: false, width: "20%"}
96 this._logGrid = new WebInspector.DataGrid(columns);
97 this._logGrid.element.classList.add("fill");
98 this._logGrid.show(logGridContainer);
99 this._logGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._replayTraceLog, this);
101 this.element.addEventListener("mousedown", this._onMouseClick.bind(this), true);
103 this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.element, this._popoverAnchor.bind(this), this._resolveObjectForPopover.bind(this), this._onHidePopover.bind(this), true);
104 this._popoverHelper.setRemoteObjectFormatter(this._hexNumbersFormatter.bind(this));
106 this._requestTraceLog(0);
113 WebInspector.CanvasProfileView.TraceLogPollingInterval = 500;
115 WebInspector.CanvasProfileView.prototype = {
118 this._linkifier.reset();
128 return this._profile;
133 * @return {!Array.<!Element>}
135 elementsToRestoreScrollPositionsFor: function()
137 return [this._logGrid.scrollContainer];
141 * @param {!Element} controlsContainer
143 _installReplayInfoSidebarWidgets: function(controlsContainer)
145 this._replayInfoResizeWidgetElement = controlsContainer.createChild("div", "resizer-widget");
146 this._replayInfoSplitView.addEventListener(WebInspector.SplitView.Events.ShowModeChanged, this._updateReplayInfoResizeWidget, this);
147 this._updateReplayInfoResizeWidget();
148 this._replayInfoSplitView.installResizer(this._replayInfoResizeWidgetElement);
150 this._toggleReplayStateSidebarButton = this._replayInfoSplitView.createShowHideSidebarButton("sidebar", "canvas-sidebar-show-hide-button");
152 controlsContainer.appendChild(this._toggleReplayStateSidebarButton.element);
153 this._replayInfoSplitView.hideSidebar();
156 _updateReplayInfoResizeWidget: function()
158 this._replayInfoResizeWidgetElement.classList.toggle("hidden", this._replayInfoSplitView.showMode() !== WebInspector.SplitView.ShowMode.Both);
162 * @param {?Event} event
164 _onMouseClick: function(event)
166 var resourceLinkElement = event.target.enclosingNodeOrSelfWithClass("canvas-formatted-resource");
167 if (resourceLinkElement) {
168 this._replayInfoSplitView.showBoth();
169 this._replayStateView.selectResource(resourceLinkElement.__resourceId);
173 if (event.target.enclosingNodeOrSelfWithClass("webkit-html-resource-link"))
174 event.consume(false);
178 * @param {!Element} parent
179 * @param {string} className
180 * @param {string} title
181 * @param {function(this:WebInspector.CanvasProfileView)} clickCallback
183 _createControlButton: function(parent, className, title, clickCallback)
185 var button = new WebInspector.StatusBarButton(title, className + " canvas-replay-button");
186 parent.appendChild(button.element);
188 button.makeLongClickEnabled();
189 button.addEventListener("click", clickCallback, this);
190 button.addEventListener("longClickDown", clickCallback, this);
191 button.addEventListener("longClickPress", clickCallback, this);
194 _onReplayContextChanged: function()
196 var selectedContextId = this._replayContextSelector.selectedOption().value;
199 * @param {?CanvasAgent.ResourceState} resourceState
200 * @this {WebInspector.CanvasProfileView}
202 function didReceiveResourceState(resourceState)
204 this._enableWaitIcon(false);
205 if (selectedContextId !== this._replayContextSelector.selectedOption().value)
207 var imageURL = (resourceState && resourceState.imageURL) || "";
208 this._replayImageElement.src = imageURL;
209 this._replayImageElement.style.visibility = imageURL ? "" : "hidden";
212 this._enableWaitIcon(true);
213 this._traceLogPlayer.getResourceState(selectedContextId, didReceiveResourceState.bind(this));
217 * @param {boolean} forward
219 _onReplayStepClick: function(forward)
221 var selectedNode = this._logGrid.selectedNode;
224 var nextNode = selectedNode;
226 nextNode = forward ? nextNode.traverseNextNode(false) : nextNode.traversePreviousNode(false);
227 } while (nextNode && typeof nextNode.index !== "number");
228 (nextNode || selectedNode).revealAndSelect();
232 * @param {boolean} forward
234 _onReplayDrawingCallClick: function(forward)
236 var selectedNode = this._logGrid.selectedNode;
239 var nextNode = selectedNode;
241 var sibling = forward ? nextNode.nextSibling : nextNode.previousSibling;
244 if (nextNode.hasChildren || nextNode.call.isDrawingCall)
247 nextNode = nextNode.parent;
252 if (!nextNode && forward)
253 this._onReplayLastStepClick();
255 (nextNode || selectedNode).revealAndSelect();
258 _onReplayFirstStepClick: function()
260 var firstNode = this._logGrid.rootNode().children[0];
262 firstNode.revealAndSelect();
265 _onReplayLastStepClick: function()
267 var lastNode = this._logGrid.rootNode().children.peekLast();
270 while (lastNode.expanded) {
271 var lastChild = lastNode.children.peekLast();
274 lastNode = lastChild;
276 lastNode.revealAndSelect();
280 * @param {boolean} enable
282 _enableWaitIcon: function(enable)
284 this._spinnerIcon.classList.toggle("hidden", !enable);
285 this._debugInfoElement.classList.toggle("hidden", enable);
288 _replayTraceLog: function()
290 if (this._pendingReplayTraceLogEvent)
292 var index = this._selectedCallIndex();
293 if (index === -1 || index === this._lastReplayCallIndex)
295 this._lastReplayCallIndex = index;
296 this._pendingReplayTraceLogEvent = true;
299 * @param {?CanvasAgent.ResourceState} resourceState
300 * @param {number} replayTime
301 * @this {WebInspector.CanvasProfileView}
303 function didReplayTraceLog(resourceState, replayTime)
305 delete this._pendingReplayTraceLogEvent;
306 this._enableWaitIcon(false);
308 this._debugInfoElement.textContent = WebInspector.UIString("Replay time: %s", Number.secondsToString(replayTime / 1000, true));
309 this._onReplayContextChanged();
311 if (index !== this._selectedCallIndex())
312 this._replayTraceLog();
314 this._enableWaitIcon(true);
315 this._traceLogPlayer.replayTraceLog(index, didReplayTraceLog.bind(this));
319 * @param {number} offset
321 _requestTraceLog: function(offset)
324 * @param {?CanvasAgent.TraceLog} traceLog
325 * @this {WebInspector.CanvasProfileView}
327 function didReceiveTraceLog(traceLog)
329 this._enableWaitIcon(false);
333 var calls = traceLog.calls;
334 var index = traceLog.startOffset;
335 for (var i = 0, n = calls.length; i < n; ++i)
336 callNodes.push(this._createCallNode(index++, calls[i]));
337 var contexts = traceLog.contexts;
338 for (var i = 0, n = contexts.length; i < n; ++i) {
339 var contextId = contexts[i].resourceId || "";
340 var description = contexts[i].description || "";
341 if (this._replayContexts[contextId])
343 this._replayContexts[contextId] = true;
344 this._replayContextSelector.createOption(description, WebInspector.UIString("Show screenshot of this context's canvas."), contextId);
346 this._appendCallNodes(callNodes);
348 setTimeout(this._requestTraceLog.bind(this, index), WebInspector.CanvasProfileView.TraceLogPollingInterval);
350 this._flattenSingleFrameNode();
351 this._profile._updateCapturingStatus(traceLog);
352 this._onReplayLastStepClick(); // Automatically replay the last step.
354 this._enableWaitIcon(true);
355 this._traceLogPlayer.getTraceLog(offset, undefined, didReceiveTraceLog.bind(this));
361 _selectedCallIndex: function()
363 var node = this._logGrid.selectedNode;
364 return node ? this._peekLastRecursively(node).index : -1;
368 * @param {!WebInspector.DataGridNode} node
369 * @return {!WebInspector.DataGridNode}
371 _peekLastRecursively: function(node)
374 while ((lastChild = node.children.peekLast()))
380 * @param {!Array.<!WebInspector.DataGridNode>} callNodes
382 _appendCallNodes: function(callNodes)
384 var rootNode = this._logGrid.rootNode();
385 var frameNode = rootNode.children.peekLast();
386 if (frameNode && this._peekLastRecursively(frameNode).call.isFrameEndCall)
388 for (var i = 0, n = callNodes.length; i < n; ++i) {
390 var index = rootNode.children.length;
393 data[1] = WebInspector.UIString("Frame #%d", index + 1);
395 frameNode = new WebInspector.DataGridNode(data);
396 frameNode.selectable = true;
397 rootNode.appendChild(frameNode);
399 var nextFrameCallIndex = i + 1;
400 while (nextFrameCallIndex < n && !callNodes[nextFrameCallIndex - 1].call.isFrameEndCall)
401 ++nextFrameCallIndex;
402 this._appendCallNodesToFrameNode(frameNode, callNodes, i, nextFrameCallIndex);
403 i = nextFrameCallIndex - 1;
409 * @param {!WebInspector.DataGridNode} frameNode
410 * @param {!Array.<!WebInspector.DataGridNode>} callNodes
411 * @param {number} fromIndex
412 * @param {number} toIndex not inclusive
414 _appendCallNodesToFrameNode: function(frameNode, callNodes, fromIndex, toIndex)
417 function appendDrawCallGroup()
419 var index = self._drawCallGroupsCount || 0;
422 data[1] = WebInspector.UIString("Draw call group #%d", index + 1);
424 var node = new WebInspector.DataGridNode(data);
425 node.selectable = true;
426 self._drawCallGroupsCount = index + 1;
427 frameNode.appendChild(node);
431 function splitDrawCallGroup(drawCallGroup)
435 while ((splitNode = drawCallGroup.children[splitIndex])) {
436 if (splitNode.call.isDrawingCall)
440 var newDrawCallGroup = appendDrawCallGroup();
442 while ((lastNode = drawCallGroup.children[splitIndex + 1]))
443 newDrawCallGroup.appendChild(lastNode);
444 return newDrawCallGroup;
447 var drawCallGroup = frameNode.children.peekLast();
448 var groupHasDrawCall = false;
450 for (var i = 0, n = drawCallGroup.children.length; i < n; ++i) {
451 if (drawCallGroup.children[i].call.isDrawingCall) {
452 groupHasDrawCall = true;
457 drawCallGroup = appendDrawCallGroup();
459 for (var i = fromIndex; i < toIndex; ++i) {
460 var node = callNodes[i];
461 drawCallGroup.appendChild(node);
462 if (node.call.isDrawingCall) {
463 if (groupHasDrawCall)
464 drawCallGroup = splitDrawCallGroup(drawCallGroup);
466 groupHasDrawCall = true;
472 * @param {number} index
473 * @param {!CanvasAgent.Call} call
474 * @return {!WebInspector.DataGridNode}
476 _createCallNode: function(index, call)
478 var callViewElement = document.createElement("div");
482 data[1] = callViewElement;
484 if (call.sourceURL) {
485 // FIXME(62725): stack trace line/column numbers are one-based.
486 var lineNumber = Math.max(0, call.lineNumber - 1) || 0;
487 var columnNumber = Math.max(0, call.columnNumber - 1) || 0;
488 data[2] = this._linkifier.linkifyLocation(call.sourceURL, lineNumber, columnNumber);
491 callViewElement.createChild("span", "canvas-function-name").textContent = call.functionName || "context." + call.property;
493 if (call.arguments) {
494 callViewElement.createTextChild("(");
495 for (var i = 0, n = call.arguments.length; i < n; ++i) {
496 var argument = /** @type {!CanvasAgent.CallArgument} */ (call.arguments[i]);
498 callViewElement.createTextChild(", ");
499 var element = WebInspector.CanvasProfileDataGridHelper.createCallArgumentElement(argument);
500 element.__argumentIndex = i;
501 callViewElement.appendChild(element);
503 callViewElement.createTextChild(")");
504 } else if (call.value) {
505 callViewElement.createTextChild(" = ");
506 callViewElement.appendChild(WebInspector.CanvasProfileDataGridHelper.createCallArgumentElement(call.value));
510 callViewElement.createTextChild(" => ");
511 callViewElement.appendChild(WebInspector.CanvasProfileDataGridHelper.createCallArgumentElement(call.result));
514 var node = new WebInspector.DataGridNode(data);
516 node.selectable = true;
521 _popoverAnchor: function(element, event)
523 var argumentElement = element.enclosingNodeOrSelfWithClass("canvas-call-argument");
524 if (!argumentElement || argumentElement.__suppressPopover)
526 return argumentElement;
529 _resolveObjectForPopover: function(argumentElement, showCallback, objectGroupName)
532 * @param {?Protocol.Error} error
533 * @param {!RuntimeAgent.RemoteObject=} result
534 * @param {!CanvasAgent.ResourceState=} resourceState
535 * @this {WebInspector.CanvasProfileView}
537 function showObjectPopover(error, result, resourceState)
542 // FIXME: handle resourceState also
546 this._popoverAnchorElement = argumentElement.cloneNode(true);
547 this._popoverAnchorElement.classList.add("canvas-popover-anchor");
548 this._popoverAnchorElement.classList.add("source-frame-eval-expression");
549 argumentElement.parentElement.appendChild(this._popoverAnchorElement);
551 var diffLeft = this._popoverAnchorElement.boxInWindow().x - argumentElement.boxInWindow().x;
552 this._popoverAnchorElement.style.left = this._popoverAnchorElement.offsetLeft - diffLeft + "px";
554 showCallback(WebInspector.RemoteObject.fromPayload(result), false, this._popoverAnchorElement);
557 var evalResult = argumentElement.__evalResult;
559 showObjectPopover.call(this, null, evalResult);
561 var dataGridNode = this._logGrid.dataGridNodeFromNode(argumentElement);
562 if (!dataGridNode || typeof dataGridNode.index !== "number") {
563 this._popoverHelper.hidePopover();
566 var callIndex = dataGridNode.index;
567 var argumentIndex = argumentElement.__argumentIndex;
568 if (typeof argumentIndex !== "number")
570 CanvasAgent.evaluateTraceLogCallArgument(this._traceLogId, callIndex, argumentIndex, objectGroupName, showObjectPopover.bind(this));
575 * @param {!WebInspector.RemoteObject} object
578 _hexNumbersFormatter: function(object)
580 if (object.type === "number") {
581 // Show enum values in hex with min length of 4 (e.g. 0x0012).
582 var str = "0000" + Number(object.description).toString(16).toUpperCase();
583 str = str.replace(/^0+(.{4,})$/, "$1");
586 return object.description || "";
589 _onHidePopover: function()
591 if (this._popoverAnchorElement) {
592 this._popoverAnchorElement.remove()
593 delete this._popoverAnchorElement;
597 _flattenSingleFrameNode: function()
599 var rootNode = this._logGrid.rootNode();
600 if (rootNode.children.length !== 1)
602 var frameNode = rootNode.children[0];
603 while (frameNode.children[0])
604 rootNode.appendChild(frameNode.children[0]);
605 rootNode.removeChild(frameNode);
608 __proto__: WebInspector.VBox.prototype
613 * @extends {WebInspector.ProfileType}
615 WebInspector.CanvasProfileType = function()
617 WebInspector.ProfileType.call(this, WebInspector.CanvasProfileType.TypeId, WebInspector.UIString("Capture Canvas Frame"));
618 this._recording = false;
619 this._lastProfileHeader = null;
621 this._capturingModeSelector = new WebInspector.StatusBarComboBox(this._dispatchViewUpdatedEvent.bind(this));
622 this._capturingModeSelector.element.title = WebInspector.UIString("Canvas capture mode.");
623 this._capturingModeSelector.createOption(WebInspector.UIString("Single Frame"), WebInspector.UIString("Capture a single canvas frame."), "");
624 this._capturingModeSelector.createOption(WebInspector.UIString("Consecutive Frames"), WebInspector.UIString("Capture consecutive canvas frames."), "1");
626 /** @type {!Object.<string, !Element>} */
627 this._frameOptions = {};
629 /** @type {!Object.<string, boolean>} */
630 this._framesWithCanvases = {};
632 this._frameSelector = new WebInspector.StatusBarComboBox(this._dispatchViewUpdatedEvent.bind(this));
633 this._frameSelector.element.title = WebInspector.UIString("Frame containing the canvases to capture.");
634 this._frameSelector.element.classList.add("hidden");
635 WebInspector.resourceTreeModel.frames().forEach(this._addFrame, this);
636 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameAdded, this._frameAdded, this);
637 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameDetached, this._frameRemoved, this);
639 this._dispatcher = new WebInspector.CanvasDispatcher(this);
640 this._canvasAgentEnabled = false;
642 this._decorationElement = document.createElement("div");
643 this._decorationElement.className = "profile-canvas-decoration";
644 this._updateDecorationElement();
647 WebInspector.CanvasProfileType.TypeId = "CANVAS_PROFILE";
649 WebInspector.CanvasProfileType.prototype = {
652 return [this._capturingModeSelector.element, this._frameSelector.element];
657 if (this._isSingleFrameMode())
658 return WebInspector.UIString("Capture next canvas frame.");
660 return this._recording ? WebInspector.UIString("Stop capturing canvas frames.") : WebInspector.UIString("Start capturing canvas frames.");
667 buttonClicked: function()
669 if (!this._canvasAgentEnabled)
671 if (this._recording) {
672 this._recording = false;
673 this._stopFrameCapturing();
674 } else if (this._isSingleFrameMode()) {
675 this._recording = false;
676 this._runSingleFrameCapturing();
678 this._recording = true;
679 this._startFrameCapturing();
681 return this._recording;
684 _runSingleFrameCapturing: function()
686 var frameId = this._selectedFrameId();
687 CanvasAgent.captureFrame(frameId, this._didStartCapturingFrame.bind(this, frameId));
690 _startFrameCapturing: function()
692 var frameId = this._selectedFrameId();
693 CanvasAgent.startCapturing(frameId, this._didStartCapturingFrame.bind(this, frameId));
696 _stopFrameCapturing: function()
698 if (!this._lastProfileHeader)
700 var profileHeader = this._lastProfileHeader;
701 var traceLogId = profileHeader.traceLogId();
702 this._lastProfileHeader = null;
703 function didStopCapturing()
705 profileHeader._updateCapturingStatus();
707 CanvasAgent.stopCapturing(traceLogId, didStopCapturing);
711 * @param {string|undefined} frameId
712 * @param {?Protocol.Error} error
713 * @param {!CanvasAgent.TraceLogId} traceLogId
715 _didStartCapturingFrame: function(frameId, error, traceLogId)
717 if (error || this._lastProfileHeader && this._lastProfileHeader.traceLogId() === traceLogId)
719 var profileHeader = new WebInspector.CanvasProfileHeader(this, traceLogId, frameId);
720 this._lastProfileHeader = profileHeader;
721 this.addProfile(profileHeader);
722 profileHeader._updateCapturingStatus();
727 return WebInspector.UIString("CANVAS PROFILE");
732 return WebInspector.UIString("Canvas calls instrumentation");
739 decorationElement: function()
741 return this._decorationElement;
746 * @param {!WebInspector.ProfileHeader} profile
748 removeProfile: function(profile)
750 WebInspector.ProfileType.prototype.removeProfile.call(this, profile);
751 if (this._recording && profile === this._lastProfileHeader)
752 this._recording = false;
756 * @param {boolean=} forcePageReload
758 _updateDecorationElement: function(forcePageReload)
760 this._decorationElement.removeChildren();
761 this._decorationElement.createChild("div", "warning-icon-small");
762 this._decorationElement.appendChild(document.createTextNode(this._canvasAgentEnabled ? WebInspector.UIString("Canvas Profiler is enabled.") : WebInspector.UIString("Canvas Profiler is disabled.")));
763 var button = this._decorationElement.createChild("button");
764 button.type = "button";
765 button.textContent = this._canvasAgentEnabled ? WebInspector.UIString("Disable") : WebInspector.UIString("Enable");
766 button.addEventListener("click", this._onProfilerEnableButtonClick.bind(this, !this._canvasAgentEnabled), false);
769 * @param {?Protocol.Error} error
770 * @param {boolean} result
772 function hasUninstrumentedCanvasesCallback(error, result)
775 WebInspector.resourceTreeModel.reloadPage();
778 if (forcePageReload) {
779 if (this._canvasAgentEnabled) {
780 CanvasAgent.hasUninstrumentedCanvases(hasUninstrumentedCanvasesCallback);
782 for (var frameId in this._framesWithCanvases) {
783 if (this._framesWithCanvases.hasOwnProperty(frameId)) {
784 WebInspector.resourceTreeModel.reloadPage();
793 * @param {boolean} enable
795 _onProfilerEnableButtonClick: function(enable)
797 if (this._canvasAgentEnabled === enable)
801 * @param {?Protocol.Error} error
802 * @this {WebInspector.CanvasProfileType}
804 function callback(error)
808 this._canvasAgentEnabled = enable;
809 this._updateDecorationElement(true);
810 this._dispatchViewUpdatedEvent();
813 CanvasAgent.enable(callback.bind(this));
815 CanvasAgent.disable(callback.bind(this));
821 _isSingleFrameMode: function()
823 return !this._capturingModeSelector.selectedOption().value;
827 * @param {!WebInspector.Event} event
829 _frameAdded: function(event)
831 var frame = /** @type {!WebInspector.ResourceTreeFrame} */ (event.data);
832 this._addFrame(frame);
836 * @param {!WebInspector.ResourceTreeFrame} frame
838 _addFrame: function(frame)
840 var frameId = frame.id;
841 var option = document.createElement("option");
842 option.text = frame.displayName();
843 option.title = frame.url;
844 option.value = frameId;
846 this._frameOptions[frameId] = option;
848 if (this._framesWithCanvases[frameId]) {
849 this._frameSelector.addOption(option);
850 this._dispatchViewUpdatedEvent();
855 * @param {!WebInspector.Event} event
857 _frameRemoved: function(event)
859 var frame = /** @type {!WebInspector.ResourceTreeFrame} */ (event.data);
860 var frameId = frame.id;
861 var option = this._frameOptions[frameId];
862 if (option && this._framesWithCanvases[frameId]) {
863 this._frameSelector.removeOption(option);
864 this._dispatchViewUpdatedEvent();
866 delete this._frameOptions[frameId];
867 delete this._framesWithCanvases[frameId];
871 * @param {string} frameId
873 _contextCreated: function(frameId)
875 if (this._framesWithCanvases[frameId])
877 this._framesWithCanvases[frameId] = true;
878 var option = this._frameOptions[frameId];
880 this._frameSelector.addOption(option);
881 this._dispatchViewUpdatedEvent();
886 * @param {!PageAgent.FrameId=} frameId
887 * @param {!CanvasAgent.TraceLogId=} traceLogId
889 _traceLogsRemoved: function(frameId, traceLogId)
891 var sidebarElementsToDelete = [];
892 var sidebarElements = /** @type {!Array.<!WebInspector.ProfileSidebarTreeElement>} */ ((this.treeElement && this.treeElement.children) || []);
893 for (var i = 0, n = sidebarElements.length; i < n; ++i) {
894 var header = /** @type {!WebInspector.CanvasProfileHeader} */ (sidebarElements[i].profile);
897 if (frameId && frameId !== header.frameId())
899 if (traceLogId && traceLogId !== header.traceLogId())
901 sidebarElementsToDelete.push(sidebarElements[i]);
903 for (var i = 0, n = sidebarElementsToDelete.length; i < n; ++i)
904 sidebarElementsToDelete[i].ondelete();
908 * @return {string|undefined}
910 _selectedFrameId: function()
912 var option = this._frameSelector.selectedOption();
913 return option ? option.value : undefined;
916 _dispatchViewUpdatedEvent: function()
918 this._frameSelector.element.classList.toggle("hidden", this._frameSelector.size() <= 1);
919 this.dispatchEventToListeners(WebInspector.ProfileType.Events.ViewUpdated);
926 isInstantProfile: function()
928 return this._isSingleFrameMode();
935 isEnabled: function()
937 return this._canvasAgentEnabled;
940 __proto__: WebInspector.ProfileType.prototype
945 * @implements {CanvasAgent.Dispatcher}
946 * @param {!WebInspector.CanvasProfileType} profileType
948 WebInspector.CanvasDispatcher = function(profileType)
950 this._profileType = profileType;
951 InspectorBackend.registerCanvasDispatcher(this);
954 WebInspector.CanvasDispatcher.prototype = {
956 * @param {string} frameId
958 contextCreated: function(frameId)
960 this._profileType._contextCreated(frameId);
964 * @param {!PageAgent.FrameId=} frameId
965 * @param {!CanvasAgent.TraceLogId=} traceLogId
967 traceLogsRemoved: function(frameId, traceLogId)
969 this._profileType._traceLogsRemoved(frameId, traceLogId);
975 * @extends {WebInspector.ProfileHeader}
976 * @param {!WebInspector.CanvasProfileType} type
977 * @param {!CanvasAgent.TraceLogId=} traceLogId
978 * @param {!PageAgent.FrameId=} frameId
980 WebInspector.CanvasProfileHeader = function(type, traceLogId, frameId)
982 WebInspector.ProfileHeader.call(this, type, WebInspector.UIString("Trace Log %d", type._nextProfileUid));
983 /** @type {!CanvasAgent.TraceLogId} */
984 this._traceLogId = traceLogId || "";
985 this._frameId = frameId;
987 this._traceLogSize = 0;
988 this._traceLogPlayer = traceLogId ? new WebInspector.CanvasTraceLogPlayerProxy(traceLogId) : null;
991 WebInspector.CanvasProfileHeader.prototype = {
993 * @return {!CanvasAgent.TraceLogId}
995 traceLogId: function()
997 return this._traceLogId;
1001 * @return {?WebInspector.CanvasTraceLogPlayerProxy}
1003 traceLogPlayer: function()
1005 return this._traceLogPlayer;
1009 * @return {!PageAgent.FrameId|undefined}
1013 return this._frameId;
1018 * @return {!WebInspector.ProfileSidebarTreeElement}
1020 createSidebarTreeElement: function()
1022 return new WebInspector.ProfileSidebarTreeElement(this, "profile-sidebar-tree-item");
1027 * @return {!WebInspector.CanvasProfileView}
1029 createView: function()
1031 return new WebInspector.CanvasProfileView(this);
1039 if (this._traceLogPlayer)
1040 this._traceLogPlayer.dispose();
1041 clearTimeout(this._requestStatusTimer);
1042 this._alive = false;
1046 * @param {!CanvasAgent.TraceLog=} traceLog
1048 _updateCapturingStatus: function(traceLog)
1050 if (!this._traceLogId)
1054 this._alive = traceLog.alive;
1055 this._traceLogSize = traceLog.totalAvailableCalls;
1058 var subtitle = this._alive ? WebInspector.UIString("Capturing\u2026 %d calls", this._traceLogSize) : WebInspector.UIString("Captured %d calls", this._traceLogSize);
1059 this.updateStatus(subtitle, this._alive);
1062 clearTimeout(this._requestStatusTimer);
1063 this._requestStatusTimer = setTimeout(this._requestCapturingStatus.bind(this), WebInspector.CanvasProfileView.TraceLogPollingInterval);
1067 _requestCapturingStatus: function()
1070 * @param {?CanvasAgent.TraceLog} traceLog
1071 * @this {WebInspector.CanvasProfileHeader}
1073 function didReceiveTraceLog(traceLog)
1077 this._alive = traceLog.alive;
1078 this._traceLogSize = traceLog.totalAvailableCalls;
1079 this._updateCapturingStatus();
1081 this._traceLogPlayer.getTraceLog(0, 0, didReceiveTraceLog.bind(this));
1084 __proto__: WebInspector.ProfileHeader.prototype
1087 WebInspector.CanvasProfileDataGridHelper = {
1089 * @param {!CanvasAgent.CallArgument} callArgument
1090 * @return {!Element}
1092 createCallArgumentElement: function(callArgument)
1094 if (callArgument.enumName)
1095 return WebInspector.CanvasProfileDataGridHelper.createEnumValueElement(callArgument.enumName, +callArgument.description);
1096 var element = document.createElement("span");
1097 element.className = "canvas-call-argument";
1098 var description = callArgument.description;
1099 if (callArgument.type === "string") {
1100 const maxStringLength = 150;
1101 element.createTextChild("\"");
1102 element.createChild("span", "canvas-formatted-string").textContent = description.trimMiddle(maxStringLength);
1103 element.createTextChild("\"");
1104 element.__suppressPopover = (description.length <= maxStringLength && !/[\r\n]/.test(description));
1105 if (!element.__suppressPopover)
1106 element.__evalResult = WebInspector.RemoteObject.fromPrimitiveValue(description);
1108 var type = callArgument.subtype || callArgument.type;
1110 element.classList.add("canvas-formatted-" + type);
1111 if (["null", "undefined", "boolean", "number"].indexOf(type) >= 0)
1112 element.__suppressPopover = true;
1114 element.textContent = description;
1115 if (callArgument.remoteObject)
1116 element.__evalResult = WebInspector.RemoteObject.fromPayload(callArgument.remoteObject);
1118 if (callArgument.resourceId) {
1119 element.classList.add("canvas-formatted-resource");
1120 element.__resourceId = callArgument.resourceId;
1126 * @param {string} enumName
1127 * @param {number} enumValue
1128 * @return {!Element}
1130 createEnumValueElement: function(enumName, enumValue)
1132 var element = document.createElement("span");
1133 element.className = "canvas-call-argument canvas-formatted-number";
1134 element.textContent = enumName;
1135 element.__evalResult = WebInspector.RemoteObject.fromPrimitiveValue(enumValue);
1141 * @extends {WebInspector.Object}
1143 * @param {!CanvasAgent.TraceLogId} traceLogId
1145 WebInspector.CanvasTraceLogPlayerProxy = function(traceLogId)
1147 this._traceLogId = traceLogId;
1148 /** @type {!Object.<string, !CanvasAgent.ResourceState>} */
1149 this._currentResourceStates = {};
1150 /** @type {?CanvasAgent.ResourceId} */
1151 this._defaultResourceId = null;
1154 /** @enum {string} */
1155 WebInspector.CanvasTraceLogPlayerProxy.Events = {
1156 CanvasTraceLogReceived: "CanvasTraceLogReceived",
1157 CanvasReplayStateChanged: "CanvasReplayStateChanged",
1158 CanvasResourceStateReceived: "CanvasResourceStateReceived",
1161 WebInspector.CanvasTraceLogPlayerProxy.prototype = {
1163 * @param {number|undefined} startOffset
1164 * @param {number|undefined} maxLength
1165 * @param {function(?CanvasAgent.TraceLog):void} userCallback
1167 getTraceLog: function(startOffset, maxLength, userCallback)
1170 * @param {?Protocol.Error} error
1171 * @param {!CanvasAgent.TraceLog} traceLog
1172 * @this {WebInspector.CanvasTraceLogPlayerProxy}
1174 function callback(error, traceLog)
1176 if (error || !traceLog) {
1180 userCallback(traceLog);
1181 this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasTraceLogReceived, traceLog);
1183 CanvasAgent.getTraceLog(this._traceLogId, startOffset, maxLength, callback.bind(this));
1188 this._currentResourceStates = {};
1189 CanvasAgent.dropTraceLog(this._traceLogId);
1190 this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasReplayStateChanged);
1194 * @param {?CanvasAgent.ResourceId} resourceId
1195 * @param {function(?CanvasAgent.ResourceState):void} userCallback
1197 getResourceState: function(resourceId, userCallback)
1199 resourceId = resourceId || this._defaultResourceId;
1201 userCallback(null); // Has not been replayed yet.
1204 var effectiveResourceId = /** @type {!CanvasAgent.ResourceId} */ (resourceId);
1205 if (this._currentResourceStates[effectiveResourceId]) {
1206 userCallback(this._currentResourceStates[effectiveResourceId]);
1211 * @param {?Protocol.Error} error
1212 * @param {!CanvasAgent.ResourceState} resourceState
1213 * @this {WebInspector.CanvasTraceLogPlayerProxy}
1215 function callback(error, resourceState)
1217 if (error || !resourceState) {
1221 this._currentResourceStates[effectiveResourceId] = resourceState;
1222 userCallback(resourceState);
1223 this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasResourceStateReceived, resourceState);
1225 CanvasAgent.getResourceState(this._traceLogId, effectiveResourceId, callback.bind(this));
1229 * @param {number} index
1230 * @param {function(?CanvasAgent.ResourceState, number):void} userCallback
1232 replayTraceLog: function(index, userCallback)
1235 * @param {?Protocol.Error} error
1236 * @param {!CanvasAgent.ResourceState} resourceState
1237 * @param {number} replayTime
1238 * @this {WebInspector.CanvasTraceLogPlayerProxy}
1240 function callback(error, resourceState, replayTime)
1242 this._currentResourceStates = {};
1244 userCallback(null, replayTime);
1246 this._defaultResourceId = resourceState.id;
1247 this._currentResourceStates[resourceState.id] = resourceState;
1248 userCallback(resourceState, replayTime);
1250 this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasReplayStateChanged);
1252 this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasResourceStateReceived, resourceState);
1254 CanvasAgent.replayTraceLog(this._traceLogId, index, callback.bind(this));
1257 clearResourceStates: function()
1259 this._currentResourceStates = {};
1260 this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasReplayStateChanged);
1263 __proto__: WebInspector.Object.prototype