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");
42 this._profile = profile;
43 this._traceLogId = profile.traceLogId();
44 this._traceLogPlayer = /** @type {!WebInspector.CanvasTraceLogPlayerProxy} */ (profile.traceLogPlayer());
45 this._linkifier = new WebInspector.Linkifier();
47 this._replayInfoSplitView = new WebInspector.SplitView(true, true, "canvasProfileViewReplaySplitViewState", 0.34);
48 this._replayInfoSplitView.show(this.element);
50 this._imageSplitView = new WebInspector.SplitView(false, true, "canvasProfileViewSplitViewState", 300);
51 this._imageSplitView.show(this._replayInfoSplitView.mainElement());
53 var replayImageContainerView = new WebInspector.VBoxWithResizeCallback(this._onReplayImageResize.bind(this));
54 replayImageContainerView.setMinimumSize(50, 28);
55 replayImageContainerView.show(this._imageSplitView.mainElement());
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");
65 var replayLogContainerView = new WebInspector.VBox();
66 replayLogContainerView.setMinimumSize(22, 22);
67 replayLogContainerView.show(this._imageSplitView.sidebarElement());
69 var replayLogContainer = replayLogContainerView.element;
70 var controlsContainer = replayLogContainer.createChild("div", "status-bar");
71 var logGridContainer = replayLogContainer.createChild("div", "canvas-replay-log");
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));
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);
84 this._installReplayInfoSidebarWidgets(controlsContainer);
86 this._replayStateView = new WebInspector.CanvasReplayStateView(this._traceLogPlayer);
87 this._replayStateView.show(this._replayInfoSplitView.sidebarElement());
89 /** @type {!Object.<string, boolean>} */
90 this._replayContexts = {};
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%"}
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);
103 this.element.addEventListener("mousedown", this._onMouseClick.bind(this), true);
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));
108 this._requestTraceLog(0);
115 WebInspector.CanvasProfileView.TraceLogPollingInterval = 500;
117 WebInspector.CanvasProfileView.prototype = {
120 this._linkifier.reset();
130 return this._profile;
133 _onReplayImageResize: function()
135 var parent = this._replayImageElement.parentElement;
136 this._replayImageElement.style.maxWidth = parent.clientWidth + "px";
137 this._replayImageElement.style.maxHeight = parent.clientHeight + "px";
142 * @return {!Array.<!Element>}
144 elementsToRestoreScrollPositionsFor: function()
146 return [this._logGrid.scrollContainer];
150 * @param {!Element} controlsContainer
152 _installReplayInfoSidebarWidgets: function(controlsContainer)
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);
159 this._toggleReplayStateSidebarButton = this._replayInfoSplitView.createShowHideSidebarButton("sidebar", "canvas-sidebar-show-hide-button");
161 controlsContainer.appendChild(this._toggleReplayStateSidebarButton.element);
162 this._replayInfoSplitView.hideSidebar();
165 _updateReplayInfoResizeWidget: function()
167 this._replayInfoResizeWidgetElement.classList.toggle("hidden", this._replayInfoSplitView.showMode() !== WebInspector.SplitView.ShowMode.Both);
171 * @param {!Event} event
173 _onMouseClick: function(event)
175 var resourceLinkElement = event.target.enclosingNodeOrSelfWithClass("canvas-formatted-resource");
176 if (resourceLinkElement) {
177 this._replayInfoSplitView.showBoth();
178 this._replayStateView.selectResource(resourceLinkElement.__resourceId);
182 if (event.target.enclosingNodeOrSelfWithClass("webkit-html-resource-link"))
183 event.consume(false);
187 * @param {!Element} parent
188 * @param {string} className
189 * @param {string} title
190 * @param {function(this:WebInspector.CanvasProfileView)} clickCallback
192 _createControlButton: function(parent, className, title, clickCallback)
194 var button = new WebInspector.StatusBarButton(title, className + " canvas-replay-button");
195 parent.appendChild(button.element);
197 button.makeLongClickEnabled();
198 button.addEventListener("click", clickCallback, this);
199 button.addEventListener("longClickDown", clickCallback, this);
200 button.addEventListener("longClickPress", clickCallback, this);
203 _onReplayContextChanged: function()
205 var selectedContextId = this._replayContextSelector.selectedOption().value;
208 * @param {?CanvasAgent.ResourceState} resourceState
209 * @this {WebInspector.CanvasProfileView}
211 function didReceiveResourceState(resourceState)
213 this._enableWaitIcon(false);
214 if (selectedContextId !== this._replayContextSelector.selectedOption().value)
216 var imageURL = (resourceState && resourceState.imageURL) || "";
217 this._replayImageElement.src = imageURL;
218 this._replayImageElement.style.visibility = imageURL ? "" : "hidden";
221 this._enableWaitIcon(true);
222 this._traceLogPlayer.getResourceState(selectedContextId, didReceiveResourceState.bind(this));
226 * @param {boolean} forward
228 _onReplayStepClick: function(forward)
230 var selectedNode = this._logGrid.selectedNode;
233 var nextNode = selectedNode;
235 nextNode = forward ? nextNode.traverseNextNode(false) : nextNode.traversePreviousNode(false);
236 } while (nextNode && typeof nextNode.index !== "number");
237 (nextNode || selectedNode).revealAndSelect();
241 * @param {boolean} forward
243 _onReplayDrawingCallClick: function(forward)
245 var selectedNode = this._logGrid.selectedNode;
248 var nextNode = selectedNode;
250 var sibling = forward ? nextNode.nextSibling : nextNode.previousSibling;
253 if (nextNode.hasChildren || nextNode.call.isDrawingCall)
256 nextNode = nextNode.parent;
261 if (!nextNode && forward)
262 this._onReplayLastStepClick();
264 (nextNode || selectedNode).revealAndSelect();
267 _onReplayFirstStepClick: function()
269 var firstNode = this._logGrid.rootNode().children[0];
271 firstNode.revealAndSelect();
274 _onReplayLastStepClick: function()
276 var lastNode = this._logGrid.rootNode().children.peekLast();
279 while (lastNode.expanded) {
280 var lastChild = lastNode.children.peekLast();
283 lastNode = lastChild;
285 lastNode.revealAndSelect();
289 * @param {boolean} enable
291 _enableWaitIcon: function(enable)
293 this._spinnerIcon.classList.toggle("hidden", !enable);
294 this._debugInfoElement.classList.toggle("hidden", enable);
297 _replayTraceLog: function()
299 if (this._pendingReplayTraceLogEvent)
301 var index = this._selectedCallIndex();
302 if (index === -1 || index === this._lastReplayCallIndex)
304 this._lastReplayCallIndex = index;
305 this._pendingReplayTraceLogEvent = true;
308 * @param {?CanvasAgent.ResourceState} resourceState
309 * @param {number} replayTime
310 * @this {WebInspector.CanvasProfileView}
312 function didReplayTraceLog(resourceState, replayTime)
314 delete this._pendingReplayTraceLogEvent;
315 this._enableWaitIcon(false);
317 this._debugInfoElement.textContent = WebInspector.UIString("Replay time: %s", Number.secondsToString(replayTime / 1000, true));
318 this._onReplayContextChanged();
320 if (index !== this._selectedCallIndex())
321 this._replayTraceLog();
323 this._enableWaitIcon(true);
324 this._traceLogPlayer.replayTraceLog(index, didReplayTraceLog.bind(this));
328 * @param {number} offset
330 _requestTraceLog: function(offset)
333 * @param {?CanvasAgent.TraceLog} traceLog
334 * @this {WebInspector.CanvasProfileView}
336 function didReceiveTraceLog(traceLog)
338 this._enableWaitIcon(false);
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])
352 this._replayContexts[contextId] = true;
353 this._replayContextSelector.createOption(description, WebInspector.UIString("Show screenshot of this context's canvas."), contextId);
355 this._appendCallNodes(callNodes);
357 setTimeout(this._requestTraceLog.bind(this, index), WebInspector.CanvasProfileView.TraceLogPollingInterval);
359 this._flattenSingleFrameNode();
360 this._profile._updateCapturingStatus(traceLog);
361 this._onReplayLastStepClick(); // Automatically replay the last step.
363 this._enableWaitIcon(true);
364 this._traceLogPlayer.getTraceLog(offset, undefined, didReceiveTraceLog.bind(this));
370 _selectedCallIndex: function()
372 var node = this._logGrid.selectedNode;
373 return node ? this._peekLastRecursively(node).index : -1;
377 * @param {!WebInspector.DataGridNode} node
378 * @return {!WebInspector.DataGridNode}
380 _peekLastRecursively: function(node)
383 while ((lastChild = node.children.peekLast()))
389 * @param {!Array.<!WebInspector.DataGridNode>} callNodes
391 _appendCallNodes: function(callNodes)
393 var rootNode = this._logGrid.rootNode();
394 var frameNode = rootNode.children.peekLast();
395 if (frameNode && this._peekLastRecursively(frameNode).call.isFrameEndCall)
397 for (var i = 0, n = callNodes.length; i < n; ++i) {
399 var index = rootNode.children.length;
402 data[1] = WebInspector.UIString("Frame #%d", index + 1);
404 frameNode = new WebInspector.DataGridNode(data);
405 frameNode.selectable = true;
406 rootNode.appendChild(frameNode);
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;
418 * @param {!WebInspector.DataGridNode} frameNode
419 * @param {!Array.<!WebInspector.DataGridNode>} callNodes
420 * @param {number} fromIndex
421 * @param {number} toIndex not inclusive
423 _appendCallNodesToFrameNode: function(frameNode, callNodes, fromIndex, toIndex)
426 function appendDrawCallGroup()
428 var index = self._drawCallGroupsCount || 0;
431 data[1] = WebInspector.UIString("Draw call group #%d", index + 1);
433 var node = new WebInspector.DataGridNode(data);
434 node.selectable = true;
435 self._drawCallGroupsCount = index + 1;
436 frameNode.appendChild(node);
440 function splitDrawCallGroup(drawCallGroup)
444 while ((splitNode = drawCallGroup.children[splitIndex])) {
445 if (splitNode.call.isDrawingCall)
449 var newDrawCallGroup = appendDrawCallGroup();
451 while ((lastNode = drawCallGroup.children[splitIndex + 1]))
452 newDrawCallGroup.appendChild(lastNode);
453 return newDrawCallGroup;
456 var drawCallGroup = frameNode.children.peekLast();
457 var groupHasDrawCall = false;
459 for (var i = 0, n = drawCallGroup.children.length; i < n; ++i) {
460 if (drawCallGroup.children[i].call.isDrawingCall) {
461 groupHasDrawCall = true;
466 drawCallGroup = appendDrawCallGroup();
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);
475 groupHasDrawCall = true;
481 * @param {number} index
482 * @param {!CanvasAgent.Call} call
483 * @return {!WebInspector.DataGridNode}
485 _createCallNode: function(index, call)
487 var callViewElement = document.createElement("div");
491 data[1] = callViewElement;
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);
500 callViewElement.createChild("span", "canvas-function-name").textContent = call.functionName || "context." + call.property;
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]);
507 callViewElement.createTextChild(", ");
508 var element = WebInspector.CanvasProfileDataGridHelper.createCallArgumentElement(argument);
509 element.__argumentIndex = i;
510 callViewElement.appendChild(element);
512 callViewElement.createTextChild(")");
513 } else if (call.value) {
514 callViewElement.createTextChild(" = ");
515 callViewElement.appendChild(WebInspector.CanvasProfileDataGridHelper.createCallArgumentElement(call.value));
519 callViewElement.createTextChild(" => ");
520 callViewElement.appendChild(WebInspector.CanvasProfileDataGridHelper.createCallArgumentElement(call.result));
523 var node = new WebInspector.DataGridNode(data);
525 node.selectable = true;
531 * @param {!Element} element
532 * @param {!Event} event
533 * @return {!Element|!AnchorBox|undefined}
535 _popoverAnchor: function(element, event)
537 var argumentElement = element.enclosingNodeOrSelfWithClass("canvas-call-argument");
538 if (!argumentElement || argumentElement.__suppressPopover)
540 return argumentElement;
543 _resolveObjectForPopover: function(argumentElement, showCallback, objectGroupName)
546 * @param {?Protocol.Error} error
547 * @param {!RuntimeAgent.RemoteObject=} result
548 * @param {!CanvasAgent.ResourceState=} resourceState
549 * @this {WebInspector.CanvasProfileView}
551 function showObjectPopover(error, result, resourceState)
556 // FIXME: handle resourceState also
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);
565 var diffLeft = this._popoverAnchorElement.boxInWindow().x - argumentElement.boxInWindow().x;
566 this._popoverAnchorElement.style.left = this._popoverAnchorElement.offsetLeft - diffLeft + "px";
568 showCallback(WebInspector.runtimeModel.createRemoteObject(result), false, this._popoverAnchorElement);
571 var evalResult = argumentElement.__evalResult;
573 showObjectPopover.call(this, null, evalResult);
575 var dataGridNode = this._logGrid.dataGridNodeFromNode(argumentElement);
576 if (!dataGridNode || typeof dataGridNode.index !== "number") {
577 this._popoverHelper.hidePopover();
580 var callIndex = dataGridNode.index;
581 var argumentIndex = argumentElement.__argumentIndex;
582 if (typeof argumentIndex !== "number")
584 CanvasAgent.evaluateTraceLogCallArgument(this._traceLogId, callIndex, argumentIndex, objectGroupName, showObjectPopover.bind(this));
589 * @param {!WebInspector.RemoteObject} object
592 _hexNumbersFormatter: function(object)
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");
600 return object.description || "";
603 _onHidePopover: function()
605 if (this._popoverAnchorElement) {
606 this._popoverAnchorElement.remove()
607 delete this._popoverAnchorElement;
611 _flattenSingleFrameNode: function()
613 var rootNode = this._logGrid.rootNode();
614 if (rootNode.children.length !== 1)
616 var frameNode = rootNode.children[0];
617 while (frameNode.children[0])
618 rootNode.appendChild(frameNode.children[0]);
619 rootNode.removeChild(frameNode);
622 __proto__: WebInspector.VBox.prototype
627 * @implements {WebInspector.TargetManager.Observer}
628 * @extends {WebInspector.ProfileType}
630 WebInspector.CanvasProfileType = function()
632 WebInspector.ProfileType.call(this, WebInspector.CanvasProfileType.TypeId, WebInspector.UIString("Capture Canvas Frame"));
633 this._recording = false;
634 this._lastProfileHeader = null;
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");
641 /** @type {!Object.<string, !Element>} */
642 this._frameOptions = {};
644 /** @type {!Object.<string, boolean>} */
645 this._framesWithCanvases = {};
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");
652 WebInspector.targetManager.observeTargets(this);
654 this._canvasAgentEnabled = false;
656 this._decorationElement = document.createElement("div");
657 this._decorationElement.className = "profile-canvas-decoration";
658 this._updateDecorationElement();
661 WebInspector.CanvasProfileType.TypeId = "CANVAS_PROFILE";
663 WebInspector.CanvasProfileType.prototype = {
666 * @param {!WebInspector.Target} target
668 targetAdded: function(target)
670 if (this._target || target !== WebInspector.targetManager.mainTarget())
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);
681 * @param {!WebInspector.Target} target
683 targetRemoved: function(target)
685 if (this._target !== target)
688 this._target.resourceTreeModel.removeEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameAdded, this._frameAdded, this);
689 this._target.resourceTreeModel.removeEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameDetached, this._frameRemoved, this);
694 return [this._capturingModeSelector.element, this._frameSelector.element];
699 if (this._isSingleFrameMode())
700 return WebInspector.UIString("Capture next canvas frame.");
702 return this._recording ? WebInspector.UIString("Stop capturing canvas frames.") : WebInspector.UIString("Start capturing canvas frames.");
709 buttonClicked: function()
711 if (!this._canvasAgentEnabled)
713 if (this._recording) {
714 this._recording = false;
715 this._stopFrameCapturing();
716 } else if (this._isSingleFrameMode()) {
717 this._recording = false;
718 this._runSingleFrameCapturing();
720 this._recording = true;
721 this._startFrameCapturing();
723 return this._recording;
726 _runSingleFrameCapturing: function()
728 var frameId = this._selectedFrameId();
729 WebInspector.profilingLock().acquire();
730 CanvasAgent.captureFrame(frameId, this._didStartCapturingFrame.bind(this, frameId));
731 WebInspector.profilingLock().release();
734 _startFrameCapturing: function()
736 var frameId = this._selectedFrameId();
737 WebInspector.profilingLock().acquire();
738 CanvasAgent.startCapturing(frameId, this._didStartCapturingFrame.bind(this, frameId));
741 _stopFrameCapturing: function()
743 if (!this._lastProfileHeader) {
744 WebInspector.profilingLock().release();
747 var profileHeader = this._lastProfileHeader;
748 var traceLogId = profileHeader.traceLogId();
749 this._lastProfileHeader = null;
750 function didStopCapturing()
752 profileHeader._updateCapturingStatus();
754 CanvasAgent.stopCapturing(traceLogId, didStopCapturing);
755 WebInspector.profilingLock().release();
759 * @param {string|undefined} frameId
760 * @param {?Protocol.Error} error
761 * @param {!CanvasAgent.TraceLogId} traceLogId
763 _didStartCapturingFrame: function(frameId, error, traceLogId)
765 if (error || this._lastProfileHeader && this._lastProfileHeader.traceLogId() === traceLogId)
767 var profileHeader = new WebInspector.CanvasProfileHeader(this._target, this, traceLogId, frameId);
768 this._lastProfileHeader = profileHeader;
769 this.addProfile(profileHeader);
770 profileHeader._updateCapturingStatus();
775 return WebInspector.UIString("CANVAS PROFILE");
780 return WebInspector.UIString("Canvas calls instrumentation");
787 decorationElement: function()
789 return this._decorationElement;
794 * @param {!WebInspector.ProfileHeader} profile
796 removeProfile: function(profile)
798 WebInspector.ProfileType.prototype.removeProfile.call(this, profile);
799 if (this._recording && profile === this._lastProfileHeader)
800 this._recording = false;
804 * @param {boolean=} forcePageReload
806 _updateDecorationElement: function(forcePageReload)
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);
816 var target = this._target;
821 * @param {?Protocol.Error} error
822 * @param {boolean} result
824 function hasUninstrumentedCanvasesCallback(error, result)
827 target.resourceTreeModel.reloadPage();
830 if (forcePageReload) {
831 if (this._canvasAgentEnabled) {
832 target.canvasAgent().hasUninstrumentedCanvases(hasUninstrumentedCanvasesCallback);
834 for (var frameId in this._framesWithCanvases) {
835 if (this._framesWithCanvases.hasOwnProperty(frameId)) {
836 target.resourceTreeModel.reloadPage();
845 * @param {boolean} enable
847 _onProfilerEnableButtonClick: function(enable)
849 if (this._canvasAgentEnabled === enable)
853 * @param {?Protocol.Error} error
854 * @this {WebInspector.CanvasProfileType}
856 function callback(error)
860 this._canvasAgentEnabled = enable;
861 this._updateDecorationElement(true);
862 this._dispatchViewUpdatedEvent();
865 CanvasAgent.enable(callback.bind(this));
867 CanvasAgent.disable(callback.bind(this));
873 _isSingleFrameMode: function()
875 return !this._capturingModeSelector.selectedOption().value;
879 * @param {!WebInspector.Event} event
881 _frameAdded: function(event)
883 var frame = /** @type {!WebInspector.ResourceTreeFrame} */ (event.data);
884 this._addFrame(frame);
888 * @param {!WebInspector.ResourceTreeFrame} frame
890 _addFrame: function(frame)
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;
898 this._frameOptions[frameId] = option;
900 if (this._framesWithCanvases[frameId]) {
901 this._frameSelector.addOption(option);
902 this._dispatchViewUpdatedEvent();
907 * @param {!WebInspector.Event} event
909 _frameRemoved: function(event)
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();
918 delete this._frameOptions[frameId];
919 delete this._framesWithCanvases[frameId];
923 * @param {string} frameId
925 _contextCreated: function(frameId)
927 if (this._framesWithCanvases[frameId])
929 this._framesWithCanvases[frameId] = true;
930 var option = this._frameOptions[frameId];
932 this._frameSelector.addOption(option);
933 this._dispatchViewUpdatedEvent();
938 * @param {!PageAgent.FrameId=} frameId
939 * @param {!CanvasAgent.TraceLogId=} traceLogId
941 _traceLogsRemoved: function(frameId, traceLogId)
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);
949 if (frameId && frameId !== header.frameId())
951 if (traceLogId && traceLogId !== header.traceLogId())
953 sidebarElementsToDelete.push(sidebarElements[i]);
955 for (var i = 0, n = sidebarElementsToDelete.length; i < n; ++i)
956 sidebarElementsToDelete[i].ondelete();
960 * @return {string|undefined}
962 _selectedFrameId: function()
964 var option = this._frameSelector.selectedOption();
965 return option ? option.value : undefined;
968 _dispatchViewUpdatedEvent: function()
970 this._frameSelector.element.classList.toggle("hidden", this._frameSelector.size() <= 1);
971 this.dispatchEventToListeners(WebInspector.ProfileType.Events.ViewUpdated);
978 isInstantProfile: function()
980 return this._isSingleFrameMode();
987 isEnabled: function()
989 return this._canvasAgentEnabled;
992 __proto__: WebInspector.ProfileType.prototype
997 * @implements {CanvasAgent.Dispatcher}
998 * @param {!WebInspector.Target} target
999 * @param {!WebInspector.CanvasProfileType} profileType
1001 WebInspector.CanvasDispatcher = function(target, profileType)
1003 this._profileType = profileType;
1004 target.registerCanvasDispatcher(this);
1007 WebInspector.CanvasDispatcher.prototype = {
1009 * @param {string} frameId
1011 contextCreated: function(frameId)
1013 this._profileType._contextCreated(frameId);
1017 * @param {!PageAgent.FrameId=} frameId
1018 * @param {!CanvasAgent.TraceLogId=} traceLogId
1020 traceLogsRemoved: function(frameId, traceLogId)
1022 this._profileType._traceLogsRemoved(frameId, traceLogId);
1028 * @extends {WebInspector.ProfileHeader}
1029 * @param {?WebInspector.Target} target
1030 * @param {!WebInspector.CanvasProfileType} type
1031 * @param {!CanvasAgent.TraceLogId=} traceLogId
1032 * @param {!PageAgent.FrameId=} frameId
1034 WebInspector.CanvasProfileHeader = function(target, type, traceLogId, frameId)
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;
1041 this._traceLogSize = 0;
1042 this._traceLogPlayer = traceLogId ? new WebInspector.CanvasTraceLogPlayerProxy(traceLogId) : null;
1045 WebInspector.CanvasProfileHeader.prototype = {
1047 * @return {!CanvasAgent.TraceLogId}
1049 traceLogId: function()
1051 return this._traceLogId;
1055 * @return {?WebInspector.CanvasTraceLogPlayerProxy}
1057 traceLogPlayer: function()
1059 return this._traceLogPlayer;
1063 * @return {!PageAgent.FrameId|undefined}
1067 return this._frameId;
1072 * @param {!WebInspector.ProfilesPanel} panel
1073 * @return {!WebInspector.ProfileSidebarTreeElement}
1075 createSidebarTreeElement: function(panel)
1077 return new WebInspector.ProfileSidebarTreeElement(panel, this, "profile-sidebar-tree-item");
1082 * @return {!WebInspector.CanvasProfileView}
1084 createView: function()
1086 return new WebInspector.CanvasProfileView(this);
1094 if (this._traceLogPlayer)
1095 this._traceLogPlayer.dispose();
1096 clearTimeout(this._requestStatusTimer);
1097 this._alive = false;
1101 * @param {!CanvasAgent.TraceLog=} traceLog
1103 _updateCapturingStatus: function(traceLog)
1105 if (!this._traceLogId)
1109 this._alive = traceLog.alive;
1110 this._traceLogSize = traceLog.totalAvailableCalls;
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);
1117 clearTimeout(this._requestStatusTimer);
1118 this._requestStatusTimer = setTimeout(this._requestCapturingStatus.bind(this), WebInspector.CanvasProfileView.TraceLogPollingInterval);
1122 _requestCapturingStatus: function()
1125 * @param {?CanvasAgent.TraceLog} traceLog
1126 * @this {WebInspector.CanvasProfileHeader}
1128 function didReceiveTraceLog(traceLog)
1132 this._alive = traceLog.alive;
1133 this._traceLogSize = traceLog.totalAvailableCalls;
1134 this._updateCapturingStatus();
1136 this._traceLogPlayer.getTraceLog(0, 0, didReceiveTraceLog.bind(this));
1139 __proto__: WebInspector.ProfileHeader.prototype
1142 WebInspector.CanvasProfileDataGridHelper = {
1144 * @param {!CanvasAgent.CallArgument} callArgument
1145 * @return {!Element}
1147 createCallArgumentElement: function(callArgument)
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);
1163 var type = callArgument.subtype || callArgument.type;
1165 element.classList.add("canvas-formatted-" + type);
1166 if (["null", "undefined", "boolean", "number"].indexOf(type) >= 0)
1167 element.__suppressPopover = true;
1169 element.textContent = description;
1170 if (callArgument.remoteObject)
1171 element.__evalResult = WebInspector.runtimeModel.createRemoteObject(callArgument.remoteObject);
1173 if (callArgument.resourceId) {
1174 element.classList.add("canvas-formatted-resource");
1175 element.__resourceId = callArgument.resourceId;
1181 * @param {string} enumName
1182 * @param {number} enumValue
1183 * @return {!Element}
1185 createEnumValueElement: function(enumName, enumValue)
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);
1196 * @extends {WebInspector.Object}
1198 * @param {!CanvasAgent.TraceLogId} traceLogId
1200 WebInspector.CanvasTraceLogPlayerProxy = function(traceLogId)
1202 this._traceLogId = traceLogId;
1203 /** @type {!Object.<string, !CanvasAgent.ResourceState>} */
1204 this._currentResourceStates = {};
1205 /** @type {?CanvasAgent.ResourceId} */
1206 this._defaultResourceId = null;
1209 /** @enum {string} */
1210 WebInspector.CanvasTraceLogPlayerProxy.Events = {
1211 CanvasTraceLogReceived: "CanvasTraceLogReceived",
1212 CanvasReplayStateChanged: "CanvasReplayStateChanged",
1213 CanvasResourceStateReceived: "CanvasResourceStateReceived",
1216 WebInspector.CanvasTraceLogPlayerProxy.prototype = {
1218 * @param {number|undefined} startOffset
1219 * @param {number|undefined} maxLength
1220 * @param {function(?CanvasAgent.TraceLog):void} userCallback
1222 getTraceLog: function(startOffset, maxLength, userCallback)
1225 * @param {?Protocol.Error} error
1226 * @param {!CanvasAgent.TraceLog} traceLog
1227 * @this {WebInspector.CanvasTraceLogPlayerProxy}
1229 function callback(error, traceLog)
1231 if (error || !traceLog) {
1235 userCallback(traceLog);
1236 this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasTraceLogReceived, traceLog);
1238 CanvasAgent.getTraceLog(this._traceLogId, startOffset, maxLength, callback.bind(this));
1243 this._currentResourceStates = {};
1244 CanvasAgent.dropTraceLog(this._traceLogId);
1245 this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasReplayStateChanged);
1249 * @param {?CanvasAgent.ResourceId} resourceId
1250 * @param {function(?CanvasAgent.ResourceState):void} userCallback
1252 getResourceState: function(resourceId, userCallback)
1254 resourceId = resourceId || this._defaultResourceId;
1256 userCallback(null); // Has not been replayed yet.
1259 var effectiveResourceId = /** @type {!CanvasAgent.ResourceId} */ (resourceId);
1260 if (this._currentResourceStates[effectiveResourceId]) {
1261 userCallback(this._currentResourceStates[effectiveResourceId]);
1266 * @param {?Protocol.Error} error
1267 * @param {!CanvasAgent.ResourceState} resourceState
1268 * @this {WebInspector.CanvasTraceLogPlayerProxy}
1270 function callback(error, resourceState)
1272 if (error || !resourceState) {
1276 this._currentResourceStates[effectiveResourceId] = resourceState;
1277 userCallback(resourceState);
1278 this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasResourceStateReceived, resourceState);
1280 CanvasAgent.getResourceState(this._traceLogId, effectiveResourceId, callback.bind(this));
1284 * @param {number} index
1285 * @param {function(?CanvasAgent.ResourceState, number):void} userCallback
1287 replayTraceLog: function(index, userCallback)
1290 * @param {?Protocol.Error} error
1291 * @param {!CanvasAgent.ResourceState} resourceState
1292 * @param {number} replayTime
1293 * @this {WebInspector.CanvasTraceLogPlayerProxy}
1295 function callback(error, resourceState, replayTime)
1297 this._currentResourceStates = {};
1299 userCallback(null, replayTime);
1301 this._defaultResourceId = resourceState.id;
1302 this._currentResourceStates[resourceState.id] = resourceState;
1303 userCallback(resourceState, replayTime);
1305 this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasReplayStateChanged);
1307 this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasResourceStateReceived, resourceState);
1309 CanvasAgent.replayTraceLog(this._traceLogId, index, callback.bind(this));
1312 clearResourceStates: function()
1314 this._currentResourceStates = {};
1315 this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasReplayStateChanged);
1318 __proto__: WebInspector.Object.prototype