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.VBox();
54 replayImageContainerView.setMinimumSize(50, 28);
55 replayImageContainerView.show(this._imageSplitView.mainElement());
57 // NOTE: The replayImageContainer can NOT be a flex div (e.g. VBox or SplitView elements)!
58 var replayImageContainer = replayImageContainerView.element.createChild("div");
59 replayImageContainer.id = "canvas-replay-image-container";
60 this._replayImageElement = replayImageContainer.createChild("img", "canvas-replay-image");
61 this._debugInfoElement = replayImageContainer.createChild("div", "canvas-debug-info hidden");
62 this._spinnerIcon = replayImageContainer.createChild("div", "spinner-icon small hidden");
64 var replayLogContainerView = new WebInspector.VBox();
65 replayLogContainerView.setMinimumSize(22, 22);
66 replayLogContainerView.show(this._imageSplitView.sidebarElement());
68 var replayLogContainer = replayLogContainerView.element;
69 var controlsContainer = replayLogContainer.createChild("div", "status-bar");
70 var logGridContainer = replayLogContainer.createChild("div", "canvas-replay-log");
72 this._createControlButton(controlsContainer, "canvas-replay-first-step", WebInspector.UIString("First call."), this._onReplayFirstStepClick.bind(this));
73 this._createControlButton(controlsContainer, "canvas-replay-prev-step", WebInspector.UIString("Previous call."), this._onReplayStepClick.bind(this, false));
74 this._createControlButton(controlsContainer, "canvas-replay-next-step", WebInspector.UIString("Next call."), this._onReplayStepClick.bind(this, true));
75 this._createControlButton(controlsContainer, "canvas-replay-prev-draw", WebInspector.UIString("Previous drawing call."), this._onReplayDrawingCallClick.bind(this, false));
76 this._createControlButton(controlsContainer, "canvas-replay-next-draw", WebInspector.UIString("Next drawing call."), this._onReplayDrawingCallClick.bind(this, true));
77 this._createControlButton(controlsContainer, "canvas-replay-last-step", WebInspector.UIString("Last call."), this._onReplayLastStepClick.bind(this));
79 this._replayContextSelector = new WebInspector.StatusBarComboBox(this._onReplayContextChanged.bind(this));
80 this._replayContextSelector.createOption(WebInspector.UIString("<screenshot auto>"), WebInspector.UIString("Show screenshot of the last replayed resource."), "");
81 controlsContainer.appendChild(this._replayContextSelector.element);
83 this._installReplayInfoSidebarWidgets(controlsContainer);
85 this._replayStateView = new WebInspector.CanvasReplayStateView(this._traceLogPlayer);
86 this._replayStateView.show(this._replayInfoSplitView.sidebarElement());
88 /** @type {!Object.<string, boolean>} */
89 this._replayContexts = {};
92 {title: "#", sortable: false, width: "5%"},
93 {title: WebInspector.UIString("Call"), sortable: false, width: "75%", disclosure: true},
94 {title: WebInspector.UIString("Location"), sortable: false, width: "20%"}
97 this._logGrid = new WebInspector.DataGrid(columns);
98 this._logGrid.element.classList.add("fill");
99 this._logGrid.show(logGridContainer);
100 this._logGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._replayTraceLog, this);
102 this.element.addEventListener("mousedown", this._onMouseClick.bind(this), true);
104 this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.element, this._popoverAnchor.bind(this), this._resolveObjectForPopover.bind(this), this._onHidePopover.bind(this), true);
105 this._popoverHelper.setRemoteObjectFormatter(this._hexNumbersFormatter.bind(this));
107 this._requestTraceLog(0);
114 WebInspector.CanvasProfileView.TraceLogPollingInterval = 500;
116 WebInspector.CanvasProfileView.prototype = {
119 this._linkifier.reset();
129 return this._profile;
134 * @return {!Array.<!Element>}
136 elementsToRestoreScrollPositionsFor: function()
138 return [this._logGrid.scrollContainer];
142 * @param {!Element} controlsContainer
144 _installReplayInfoSidebarWidgets: function(controlsContainer)
146 this._replayInfoResizeWidgetElement = controlsContainer.createChild("div", "resizer-widget");
147 this._replayInfoSplitView.addEventListener(WebInspector.SplitView.Events.ShowModeChanged, this._updateReplayInfoResizeWidget, this);
148 this._updateReplayInfoResizeWidget();
149 this._replayInfoSplitView.installResizer(this._replayInfoResizeWidgetElement);
151 this._toggleReplayStateSidebarButton = this._replayInfoSplitView.createShowHideSidebarButton("sidebar", "canvas-sidebar-show-hide-button");
153 controlsContainer.appendChild(this._toggleReplayStateSidebarButton.element);
154 this._replayInfoSplitView.hideSidebar();
157 _updateReplayInfoResizeWidget: function()
159 this._replayInfoResizeWidgetElement.classList.toggle("hidden", this._replayInfoSplitView.showMode() !== WebInspector.SplitView.ShowMode.Both);
163 * @param {?Event} event
165 _onMouseClick: function(event)
167 var resourceLinkElement = event.target.enclosingNodeOrSelfWithClass("canvas-formatted-resource");
168 if (resourceLinkElement) {
169 this._replayInfoSplitView.showBoth();
170 this._replayStateView.selectResource(resourceLinkElement.__resourceId);
174 if (event.target.enclosingNodeOrSelfWithClass("webkit-html-resource-link"))
175 event.consume(false);
179 * @param {!Element} parent
180 * @param {string} className
181 * @param {string} title
182 * @param {function(this:WebInspector.CanvasProfileView)} clickCallback
184 _createControlButton: function(parent, className, title, clickCallback)
186 var button = new WebInspector.StatusBarButton(title, className + " canvas-replay-button");
187 parent.appendChild(button.element);
189 button.makeLongClickEnabled();
190 button.addEventListener("click", clickCallback, this);
191 button.addEventListener("longClickDown", clickCallback, this);
192 button.addEventListener("longClickPress", clickCallback, this);
195 _onReplayContextChanged: function()
197 var selectedContextId = this._replayContextSelector.selectedOption().value;
200 * @param {?CanvasAgent.ResourceState} resourceState
201 * @this {WebInspector.CanvasProfileView}
203 function didReceiveResourceState(resourceState)
205 this._enableWaitIcon(false);
206 if (selectedContextId !== this._replayContextSelector.selectedOption().value)
208 var imageURL = (resourceState && resourceState.imageURL) || "";
209 this._replayImageElement.src = imageURL;
210 this._replayImageElement.style.visibility = imageURL ? "" : "hidden";
213 this._enableWaitIcon(true);
214 this._traceLogPlayer.getResourceState(selectedContextId, didReceiveResourceState.bind(this));
218 * @param {boolean} forward
220 _onReplayStepClick: function(forward)
222 var selectedNode = this._logGrid.selectedNode;
225 var nextNode = selectedNode;
227 nextNode = forward ? nextNode.traverseNextNode(false) : nextNode.traversePreviousNode(false);
228 } while (nextNode && typeof nextNode.index !== "number");
229 (nextNode || selectedNode).revealAndSelect();
233 * @param {boolean} forward
235 _onReplayDrawingCallClick: function(forward)
237 var selectedNode = this._logGrid.selectedNode;
240 var nextNode = selectedNode;
242 var sibling = forward ? nextNode.nextSibling : nextNode.previousSibling;
245 if (nextNode.hasChildren || nextNode.call.isDrawingCall)
248 nextNode = nextNode.parent;
253 if (!nextNode && forward)
254 this._onReplayLastStepClick();
256 (nextNode || selectedNode).revealAndSelect();
259 _onReplayFirstStepClick: function()
261 var firstNode = this._logGrid.rootNode().children[0];
263 firstNode.revealAndSelect();
266 _onReplayLastStepClick: function()
268 var lastNode = this._logGrid.rootNode().children.peekLast();
271 while (lastNode.expanded) {
272 var lastChild = lastNode.children.peekLast();
275 lastNode = lastChild;
277 lastNode.revealAndSelect();
281 * @param {boolean} enable
283 _enableWaitIcon: function(enable)
285 this._spinnerIcon.classList.toggle("hidden", !enable);
286 this._debugInfoElement.classList.toggle("hidden", enable);
289 _replayTraceLog: function()
291 if (this._pendingReplayTraceLogEvent)
293 var index = this._selectedCallIndex();
294 if (index === -1 || index === this._lastReplayCallIndex)
296 this._lastReplayCallIndex = index;
297 this._pendingReplayTraceLogEvent = true;
300 * @param {?CanvasAgent.ResourceState} resourceState
301 * @param {number} replayTime
302 * @this {WebInspector.CanvasProfileView}
304 function didReplayTraceLog(resourceState, replayTime)
306 delete this._pendingReplayTraceLogEvent;
307 this._enableWaitIcon(false);
309 this._debugInfoElement.textContent = WebInspector.UIString("Replay time: %s", Number.secondsToString(replayTime / 1000, true));
310 this._onReplayContextChanged();
312 if (index !== this._selectedCallIndex())
313 this._replayTraceLog();
315 this._enableWaitIcon(true);
316 this._traceLogPlayer.replayTraceLog(index, didReplayTraceLog.bind(this));
320 * @param {number} offset
322 _requestTraceLog: function(offset)
325 * @param {?CanvasAgent.TraceLog} traceLog
326 * @this {WebInspector.CanvasProfileView}
328 function didReceiveTraceLog(traceLog)
330 this._enableWaitIcon(false);
334 var calls = traceLog.calls;
335 var index = traceLog.startOffset;
336 for (var i = 0, n = calls.length; i < n; ++i)
337 callNodes.push(this._createCallNode(index++, calls[i]));
338 var contexts = traceLog.contexts;
339 for (var i = 0, n = contexts.length; i < n; ++i) {
340 var contextId = contexts[i].resourceId || "";
341 var description = contexts[i].description || "";
342 if (this._replayContexts[contextId])
344 this._replayContexts[contextId] = true;
345 this._replayContextSelector.createOption(description, WebInspector.UIString("Show screenshot of this context's canvas."), contextId);
347 this._appendCallNodes(callNodes);
349 setTimeout(this._requestTraceLog.bind(this, index), WebInspector.CanvasProfileView.TraceLogPollingInterval);
351 this._flattenSingleFrameNode();
352 this._profile._updateCapturingStatus(traceLog);
353 this._onReplayLastStepClick(); // Automatically replay the last step.
355 this._enableWaitIcon(true);
356 this._traceLogPlayer.getTraceLog(offset, undefined, didReceiveTraceLog.bind(this));
362 _selectedCallIndex: function()
364 var node = this._logGrid.selectedNode;
365 return node ? this._peekLastRecursively(node).index : -1;
369 * @param {!WebInspector.DataGridNode} node
370 * @return {!WebInspector.DataGridNode}
372 _peekLastRecursively: function(node)
375 while ((lastChild = node.children.peekLast()))
381 * @param {!Array.<!WebInspector.DataGridNode>} callNodes
383 _appendCallNodes: function(callNodes)
385 var rootNode = this._logGrid.rootNode();
386 var frameNode = rootNode.children.peekLast();
387 if (frameNode && this._peekLastRecursively(frameNode).call.isFrameEndCall)
389 for (var i = 0, n = callNodes.length; i < n; ++i) {
391 var index = rootNode.children.length;
394 data[1] = WebInspector.UIString("Frame #%d", index + 1);
396 frameNode = new WebInspector.DataGridNode(data);
397 frameNode.selectable = true;
398 rootNode.appendChild(frameNode);
400 var nextFrameCallIndex = i + 1;
401 while (nextFrameCallIndex < n && !callNodes[nextFrameCallIndex - 1].call.isFrameEndCall)
402 ++nextFrameCallIndex;
403 this._appendCallNodesToFrameNode(frameNode, callNodes, i, nextFrameCallIndex);
404 i = nextFrameCallIndex - 1;
410 * @param {!WebInspector.DataGridNode} frameNode
411 * @param {!Array.<!WebInspector.DataGridNode>} callNodes
412 * @param {number} fromIndex
413 * @param {number} toIndex not inclusive
415 _appendCallNodesToFrameNode: function(frameNode, callNodes, fromIndex, toIndex)
418 function appendDrawCallGroup()
420 var index = self._drawCallGroupsCount || 0;
423 data[1] = WebInspector.UIString("Draw call group #%d", index + 1);
425 var node = new WebInspector.DataGridNode(data);
426 node.selectable = true;
427 self._drawCallGroupsCount = index + 1;
428 frameNode.appendChild(node);
432 function splitDrawCallGroup(drawCallGroup)
436 while ((splitNode = drawCallGroup.children[splitIndex])) {
437 if (splitNode.call.isDrawingCall)
441 var newDrawCallGroup = appendDrawCallGroup();
443 while ((lastNode = drawCallGroup.children[splitIndex + 1]))
444 newDrawCallGroup.appendChild(lastNode);
445 return newDrawCallGroup;
448 var drawCallGroup = frameNode.children.peekLast();
449 var groupHasDrawCall = false;
451 for (var i = 0, n = drawCallGroup.children.length; i < n; ++i) {
452 if (drawCallGroup.children[i].call.isDrawingCall) {
453 groupHasDrawCall = true;
458 drawCallGroup = appendDrawCallGroup();
460 for (var i = fromIndex; i < toIndex; ++i) {
461 var node = callNodes[i];
462 drawCallGroup.appendChild(node);
463 if (node.call.isDrawingCall) {
464 if (groupHasDrawCall)
465 drawCallGroup = splitDrawCallGroup(drawCallGroup);
467 groupHasDrawCall = true;
473 * @param {number} index
474 * @param {!CanvasAgent.Call} call
475 * @return {!WebInspector.DataGridNode}
477 _createCallNode: function(index, call)
479 var callViewElement = document.createElement("div");
483 data[1] = callViewElement;
485 if (call.sourceURL) {
486 // FIXME(62725): stack trace line/column numbers are one-based.
487 var lineNumber = Math.max(0, call.lineNumber - 1) || 0;
488 var columnNumber = Math.max(0, call.columnNumber - 1) || 0;
489 data[2] = this._linkifier.linkifyLocation(this.profile.target(), call.sourceURL, lineNumber, columnNumber);
492 callViewElement.createChild("span", "canvas-function-name").textContent = call.functionName || "context." + call.property;
494 if (call.arguments) {
495 callViewElement.createTextChild("(");
496 for (var i = 0, n = call.arguments.length; i < n; ++i) {
497 var argument = /** @type {!CanvasAgent.CallArgument} */ (call.arguments[i]);
499 callViewElement.createTextChild(", ");
500 var element = WebInspector.CanvasProfileDataGridHelper.createCallArgumentElement(argument);
501 element.__argumentIndex = i;
502 callViewElement.appendChild(element);
504 callViewElement.createTextChild(")");
505 } else if (call.value) {
506 callViewElement.createTextChild(" = ");
507 callViewElement.appendChild(WebInspector.CanvasProfileDataGridHelper.createCallArgumentElement(call.value));
511 callViewElement.createTextChild(" => ");
512 callViewElement.appendChild(WebInspector.CanvasProfileDataGridHelper.createCallArgumentElement(call.result));
515 var node = new WebInspector.DataGridNode(data);
517 node.selectable = true;
522 _popoverAnchor: function(element, event)
524 var argumentElement = element.enclosingNodeOrSelfWithClass("canvas-call-argument");
525 if (!argumentElement || argumentElement.__suppressPopover)
527 return argumentElement;
530 _resolveObjectForPopover: function(argumentElement, showCallback, objectGroupName)
533 * @param {?Protocol.Error} error
534 * @param {!RuntimeAgent.RemoteObject=} result
535 * @param {!CanvasAgent.ResourceState=} resourceState
536 * @this {WebInspector.CanvasProfileView}
538 function showObjectPopover(error, result, resourceState)
543 // FIXME: handle resourceState also
547 this._popoverAnchorElement = argumentElement.cloneNode(true);
548 this._popoverAnchorElement.classList.add("canvas-popover-anchor");
549 this._popoverAnchorElement.classList.add("source-frame-eval-expression");
550 argumentElement.parentElement.appendChild(this._popoverAnchorElement);
552 var diffLeft = this._popoverAnchorElement.boxInWindow().x - argumentElement.boxInWindow().x;
553 this._popoverAnchorElement.style.left = this._popoverAnchorElement.offsetLeft - diffLeft + "px";
555 showCallback(WebInspector.runtimeModel.createRemoteObject(result), false, this._popoverAnchorElement);
558 var evalResult = argumentElement.__evalResult;
560 showObjectPopover.call(this, null, evalResult);
562 var dataGridNode = this._logGrid.dataGridNodeFromNode(argumentElement);
563 if (!dataGridNode || typeof dataGridNode.index !== "number") {
564 this._popoverHelper.hidePopover();
567 var callIndex = dataGridNode.index;
568 var argumentIndex = argumentElement.__argumentIndex;
569 if (typeof argumentIndex !== "number")
571 CanvasAgent.evaluateTraceLogCallArgument(this._traceLogId, callIndex, argumentIndex, objectGroupName, showObjectPopover.bind(this));
576 * @param {!WebInspector.RemoteObject} object
579 _hexNumbersFormatter: function(object)
581 if (object.type === "number") {
582 // Show enum values in hex with min length of 4 (e.g. 0x0012).
583 var str = "0000" + Number(object.description).toString(16).toUpperCase();
584 str = str.replace(/^0+(.{4,})$/, "$1");
587 return object.description || "";
590 _onHidePopover: function()
592 if (this._popoverAnchorElement) {
593 this._popoverAnchorElement.remove()
594 delete this._popoverAnchorElement;
598 _flattenSingleFrameNode: function()
600 var rootNode = this._logGrid.rootNode();
601 if (rootNode.children.length !== 1)
603 var frameNode = rootNode.children[0];
604 while (frameNode.children[0])
605 rootNode.appendChild(frameNode.children[0]);
606 rootNode.removeChild(frameNode);
609 __proto__: WebInspector.VBox.prototype
614 * @extends {WebInspector.ProfileType}
616 WebInspector.CanvasProfileType = function()
618 WebInspector.ProfileType.call(this, WebInspector.CanvasProfileType.TypeId, WebInspector.UIString("Capture Canvas Frame"));
619 this._recording = false;
620 this._lastProfileHeader = null;
622 this._capturingModeSelector = new WebInspector.StatusBarComboBox(this._dispatchViewUpdatedEvent.bind(this));
623 this._capturingModeSelector.element.title = WebInspector.UIString("Canvas capture mode.");
624 this._capturingModeSelector.createOption(WebInspector.UIString("Single Frame"), WebInspector.UIString("Capture a single canvas frame."), "");
625 this._capturingModeSelector.createOption(WebInspector.UIString("Consecutive Frames"), WebInspector.UIString("Capture consecutive canvas frames."), "1");
627 /** @type {!Object.<string, !Element>} */
628 this._frameOptions = {};
630 /** @type {!Object.<string, boolean>} */
631 this._framesWithCanvases = {};
633 this._frameSelector = new WebInspector.StatusBarComboBox(this._dispatchViewUpdatedEvent.bind(this));
634 this._frameSelector.element.title = WebInspector.UIString("Frame containing the canvases to capture.");
635 this._frameSelector.element.classList.add("hidden");
637 this._target = /** @type {!WebInspector.Target} */ (WebInspector.targetManager.activeTarget());
638 this._target.resourceTreeModel.frames().forEach(this._addFrame, this);
639 this._target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameAdded, this._frameAdded, this);
640 this._target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameDetached, this._frameRemoved, this);
642 this._dispatcher = new WebInspector.CanvasDispatcher(this);
643 this._canvasAgentEnabled = false;
645 this._decorationElement = document.createElement("div");
646 this._decorationElement.className = "profile-canvas-decoration";
647 this._updateDecorationElement();
650 WebInspector.CanvasProfileType.TypeId = "CANVAS_PROFILE";
652 WebInspector.CanvasProfileType.prototype = {
655 return [this._capturingModeSelector.element, this._frameSelector.element];
660 if (this._isSingleFrameMode())
661 return WebInspector.UIString("Capture next canvas frame.");
663 return this._recording ? WebInspector.UIString("Stop capturing canvas frames.") : WebInspector.UIString("Start capturing canvas frames.");
670 buttonClicked: function()
672 if (!this._canvasAgentEnabled)
674 if (this._recording) {
675 this._recording = false;
676 this._stopFrameCapturing();
677 } else if (this._isSingleFrameMode()) {
678 this._recording = false;
679 this._runSingleFrameCapturing();
681 this._recording = true;
682 this._startFrameCapturing();
684 return this._recording;
687 _runSingleFrameCapturing: function()
689 var frameId = this._selectedFrameId();
690 this._target.profilingLock.acquire();
691 CanvasAgent.captureFrame(frameId, this._didStartCapturingFrame.bind(this, frameId));
692 this._target.profilingLock.release();
695 _startFrameCapturing: function()
697 var frameId = this._selectedFrameId();
698 this._target.profilingLock.acquire();
699 CanvasAgent.startCapturing(frameId, this._didStartCapturingFrame.bind(this, frameId));
702 _stopFrameCapturing: function()
704 if (!this._lastProfileHeader) {
705 this._target.profilingLock.release();
708 var profileHeader = this._lastProfileHeader;
709 var traceLogId = profileHeader.traceLogId();
710 this._lastProfileHeader = null;
711 function didStopCapturing()
713 profileHeader._updateCapturingStatus();
715 CanvasAgent.stopCapturing(traceLogId, didStopCapturing);
716 this._target.profilingLock.release();
720 * @param {string|undefined} frameId
721 * @param {?Protocol.Error} error
722 * @param {!CanvasAgent.TraceLogId} traceLogId
724 _didStartCapturingFrame: function(frameId, error, traceLogId)
726 if (error || this._lastProfileHeader && this._lastProfileHeader.traceLogId() === traceLogId)
728 var profileHeader = new WebInspector.CanvasProfileHeader(this._target, this, traceLogId, frameId);
729 this._lastProfileHeader = profileHeader;
730 this.addProfile(profileHeader);
731 profileHeader._updateCapturingStatus();
736 return WebInspector.UIString("CANVAS PROFILE");
741 return WebInspector.UIString("Canvas calls instrumentation");
748 decorationElement: function()
750 return this._decorationElement;
755 * @param {!WebInspector.ProfileHeader} profile
757 removeProfile: function(profile)
759 WebInspector.ProfileType.prototype.removeProfile.call(this, profile);
760 if (this._recording && profile === this._lastProfileHeader)
761 this._recording = false;
765 * @param {boolean=} forcePageReload
767 _updateDecorationElement: function(forcePageReload)
769 this._decorationElement.removeChildren();
770 this._decorationElement.createChild("div", "warning-icon-small");
771 this._decorationElement.appendChild(document.createTextNode(this._canvasAgentEnabled ? WebInspector.UIString("Canvas Profiler is enabled.") : WebInspector.UIString("Canvas Profiler is disabled.")));
772 var button = this._decorationElement.createChild("button");
773 button.type = "button";
774 button.textContent = this._canvasAgentEnabled ? WebInspector.UIString("Disable") : WebInspector.UIString("Enable");
775 button.addEventListener("click", this._onProfilerEnableButtonClick.bind(this, !this._canvasAgentEnabled), false);
777 var target = this._target;
779 * @param {?Protocol.Error} error
780 * @param {boolean} result
782 function hasUninstrumentedCanvasesCallback(error, result)
785 target.resourceTreeModel.reloadPage();
788 if (forcePageReload) {
789 if (this._canvasAgentEnabled) {
790 CanvasAgent.hasUninstrumentedCanvases(hasUninstrumentedCanvasesCallback);
792 for (var frameId in this._framesWithCanvases) {
793 if (this._framesWithCanvases.hasOwnProperty(frameId)) {
794 target.resourceTreeModel.reloadPage();
803 * @param {boolean} enable
805 _onProfilerEnableButtonClick: function(enable)
807 if (this._canvasAgentEnabled === enable)
811 * @param {?Protocol.Error} error
812 * @this {WebInspector.CanvasProfileType}
814 function callback(error)
818 this._canvasAgentEnabled = enable;
819 this._updateDecorationElement(true);
820 this._dispatchViewUpdatedEvent();
823 CanvasAgent.enable(callback.bind(this));
825 CanvasAgent.disable(callback.bind(this));
831 _isSingleFrameMode: function()
833 return !this._capturingModeSelector.selectedOption().value;
837 * @param {!WebInspector.Event} event
839 _frameAdded: function(event)
841 var frame = /** @type {!WebInspector.ResourceTreeFrame} */ (event.data);
842 this._addFrame(frame);
846 * @param {!WebInspector.ResourceTreeFrame} frame
848 _addFrame: function(frame)
850 var frameId = frame.id;
851 var option = document.createElement("option");
852 option.text = frame.displayName();
853 option.title = frame.url;
854 option.value = frameId;
856 this._frameOptions[frameId] = option;
858 if (this._framesWithCanvases[frameId]) {
859 this._frameSelector.addOption(option);
860 this._dispatchViewUpdatedEvent();
865 * @param {!WebInspector.Event} event
867 _frameRemoved: function(event)
869 var frame = /** @type {!WebInspector.ResourceTreeFrame} */ (event.data);
870 var frameId = frame.id;
871 var option = this._frameOptions[frameId];
872 if (option && this._framesWithCanvases[frameId]) {
873 this._frameSelector.removeOption(option);
874 this._dispatchViewUpdatedEvent();
876 delete this._frameOptions[frameId];
877 delete this._framesWithCanvases[frameId];
881 * @param {string} frameId
883 _contextCreated: function(frameId)
885 if (this._framesWithCanvases[frameId])
887 this._framesWithCanvases[frameId] = true;
888 var option = this._frameOptions[frameId];
890 this._frameSelector.addOption(option);
891 this._dispatchViewUpdatedEvent();
896 * @param {!PageAgent.FrameId=} frameId
897 * @param {!CanvasAgent.TraceLogId=} traceLogId
899 _traceLogsRemoved: function(frameId, traceLogId)
901 var sidebarElementsToDelete = [];
902 var sidebarElements = /** @type {!Array.<!WebInspector.ProfileSidebarTreeElement>} */ ((this.treeElement && this.treeElement.children) || []);
903 for (var i = 0, n = sidebarElements.length; i < n; ++i) {
904 var header = /** @type {!WebInspector.CanvasProfileHeader} */ (sidebarElements[i].profile);
907 if (frameId && frameId !== header.frameId())
909 if (traceLogId && traceLogId !== header.traceLogId())
911 sidebarElementsToDelete.push(sidebarElements[i]);
913 for (var i = 0, n = sidebarElementsToDelete.length; i < n; ++i)
914 sidebarElementsToDelete[i].ondelete();
918 * @return {string|undefined}
920 _selectedFrameId: function()
922 var option = this._frameSelector.selectedOption();
923 return option ? option.value : undefined;
926 _dispatchViewUpdatedEvent: function()
928 this._frameSelector.element.classList.toggle("hidden", this._frameSelector.size() <= 1);
929 this.dispatchEventToListeners(WebInspector.ProfileType.Events.ViewUpdated);
936 isInstantProfile: function()
938 return this._isSingleFrameMode();
945 isEnabled: function()
947 return this._canvasAgentEnabled;
950 __proto__: WebInspector.ProfileType.prototype
955 * @implements {CanvasAgent.Dispatcher}
956 * @param {!WebInspector.CanvasProfileType} profileType
958 WebInspector.CanvasDispatcher = function(profileType)
960 this._profileType = profileType;
961 InspectorBackend.registerCanvasDispatcher(this);
964 WebInspector.CanvasDispatcher.prototype = {
966 * @param {string} frameId
968 contextCreated: function(frameId)
970 this._profileType._contextCreated(frameId);
974 * @param {!PageAgent.FrameId=} frameId
975 * @param {!CanvasAgent.TraceLogId=} traceLogId
977 traceLogsRemoved: function(frameId, traceLogId)
979 this._profileType._traceLogsRemoved(frameId, traceLogId);
985 * @extends {WebInspector.ProfileHeader}
986 * @param {!WebInspector.Target} target
987 * @param {!WebInspector.CanvasProfileType} type
988 * @param {!CanvasAgent.TraceLogId=} traceLogId
989 * @param {!PageAgent.FrameId=} frameId
991 WebInspector.CanvasProfileHeader = function(target, type, traceLogId, frameId)
993 WebInspector.ProfileHeader.call(this, target, type, WebInspector.UIString("Trace Log %d", type._nextProfileUid));
994 /** @type {!CanvasAgent.TraceLogId} */
995 this._traceLogId = traceLogId || "";
996 this._frameId = frameId;
998 this._traceLogSize = 0;
999 this._traceLogPlayer = traceLogId ? new WebInspector.CanvasTraceLogPlayerProxy(traceLogId) : null;
1002 WebInspector.CanvasProfileHeader.prototype = {
1004 * @return {!CanvasAgent.TraceLogId}
1006 traceLogId: function()
1008 return this._traceLogId;
1012 * @return {?WebInspector.CanvasTraceLogPlayerProxy}
1014 traceLogPlayer: function()
1016 return this._traceLogPlayer;
1020 * @return {!PageAgent.FrameId|undefined}
1024 return this._frameId;
1029 * @param {!WebInspector.ProfilesPanel} panel
1030 * @return {!WebInspector.ProfileSidebarTreeElement}
1032 createSidebarTreeElement: function(panel)
1034 return new WebInspector.ProfileSidebarTreeElement(panel, this, "profile-sidebar-tree-item");
1039 * @return {!WebInspector.CanvasProfileView}
1041 createView: function()
1043 return new WebInspector.CanvasProfileView(this);
1051 if (this._traceLogPlayer)
1052 this._traceLogPlayer.dispose();
1053 clearTimeout(this._requestStatusTimer);
1054 this._alive = false;
1058 * @param {!CanvasAgent.TraceLog=} traceLog
1060 _updateCapturingStatus: function(traceLog)
1062 if (!this._traceLogId)
1066 this._alive = traceLog.alive;
1067 this._traceLogSize = traceLog.totalAvailableCalls;
1070 var subtitle = this._alive ? WebInspector.UIString("Capturing\u2026 %d calls", this._traceLogSize) : WebInspector.UIString("Captured %d calls", this._traceLogSize);
1071 this.updateStatus(subtitle, this._alive);
1074 clearTimeout(this._requestStatusTimer);
1075 this._requestStatusTimer = setTimeout(this._requestCapturingStatus.bind(this), WebInspector.CanvasProfileView.TraceLogPollingInterval);
1079 _requestCapturingStatus: function()
1082 * @param {?CanvasAgent.TraceLog} traceLog
1083 * @this {WebInspector.CanvasProfileHeader}
1085 function didReceiveTraceLog(traceLog)
1089 this._alive = traceLog.alive;
1090 this._traceLogSize = traceLog.totalAvailableCalls;
1091 this._updateCapturingStatus();
1093 this._traceLogPlayer.getTraceLog(0, 0, didReceiveTraceLog.bind(this));
1096 __proto__: WebInspector.ProfileHeader.prototype
1099 WebInspector.CanvasProfileDataGridHelper = {
1101 * @param {!CanvasAgent.CallArgument} callArgument
1102 * @return {!Element}
1104 createCallArgumentElement: function(callArgument)
1106 if (callArgument.enumName)
1107 return WebInspector.CanvasProfileDataGridHelper.createEnumValueElement(callArgument.enumName, +callArgument.description);
1108 var element = document.createElement("span");
1109 element.className = "canvas-call-argument";
1110 var description = callArgument.description;
1111 if (callArgument.type === "string") {
1112 const maxStringLength = 150;
1113 element.createTextChild("\"");
1114 element.createChild("span", "canvas-formatted-string").textContent = description.trimMiddle(maxStringLength);
1115 element.createTextChild("\"");
1116 element.__suppressPopover = (description.length <= maxStringLength && !/[\r\n]/.test(description));
1117 if (!element.__suppressPopover)
1118 element.__evalResult = WebInspector.runtimeModel.createRemoteObjectFromPrimitiveValue(description);
1120 var type = callArgument.subtype || callArgument.type;
1122 element.classList.add("canvas-formatted-" + type);
1123 if (["null", "undefined", "boolean", "number"].indexOf(type) >= 0)
1124 element.__suppressPopover = true;
1126 element.textContent = description;
1127 if (callArgument.remoteObject)
1128 element.__evalResult = WebInspector.runtimeModel.createRemoteObject(callArgument.remoteObject);
1130 if (callArgument.resourceId) {
1131 element.classList.add("canvas-formatted-resource");
1132 element.__resourceId = callArgument.resourceId;
1138 * @param {string} enumName
1139 * @param {number} enumValue
1140 * @return {!Element}
1142 createEnumValueElement: function(enumName, enumValue)
1144 var element = document.createElement("span");
1145 element.className = "canvas-call-argument canvas-formatted-number";
1146 element.textContent = enumName;
1147 element.__evalResult = WebInspector.runtimeModel.createRemoteObjectFromPrimitiveValue(enumValue);
1153 * @extends {WebInspector.Object}
1155 * @param {!CanvasAgent.TraceLogId} traceLogId
1157 WebInspector.CanvasTraceLogPlayerProxy = function(traceLogId)
1159 this._traceLogId = traceLogId;
1160 /** @type {!Object.<string, !CanvasAgent.ResourceState>} */
1161 this._currentResourceStates = {};
1162 /** @type {?CanvasAgent.ResourceId} */
1163 this._defaultResourceId = null;
1166 /** @enum {string} */
1167 WebInspector.CanvasTraceLogPlayerProxy.Events = {
1168 CanvasTraceLogReceived: "CanvasTraceLogReceived",
1169 CanvasReplayStateChanged: "CanvasReplayStateChanged",
1170 CanvasResourceStateReceived: "CanvasResourceStateReceived",
1173 WebInspector.CanvasTraceLogPlayerProxy.prototype = {
1175 * @param {number|undefined} startOffset
1176 * @param {number|undefined} maxLength
1177 * @param {function(?CanvasAgent.TraceLog):void} userCallback
1179 getTraceLog: function(startOffset, maxLength, userCallback)
1182 * @param {?Protocol.Error} error
1183 * @param {!CanvasAgent.TraceLog} traceLog
1184 * @this {WebInspector.CanvasTraceLogPlayerProxy}
1186 function callback(error, traceLog)
1188 if (error || !traceLog) {
1192 userCallback(traceLog);
1193 this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasTraceLogReceived, traceLog);
1195 CanvasAgent.getTraceLog(this._traceLogId, startOffset, maxLength, callback.bind(this));
1200 this._currentResourceStates = {};
1201 CanvasAgent.dropTraceLog(this._traceLogId);
1202 this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasReplayStateChanged);
1206 * @param {?CanvasAgent.ResourceId} resourceId
1207 * @param {function(?CanvasAgent.ResourceState):void} userCallback
1209 getResourceState: function(resourceId, userCallback)
1211 resourceId = resourceId || this._defaultResourceId;
1213 userCallback(null); // Has not been replayed yet.
1216 var effectiveResourceId = /** @type {!CanvasAgent.ResourceId} */ (resourceId);
1217 if (this._currentResourceStates[effectiveResourceId]) {
1218 userCallback(this._currentResourceStates[effectiveResourceId]);
1223 * @param {?Protocol.Error} error
1224 * @param {!CanvasAgent.ResourceState} resourceState
1225 * @this {WebInspector.CanvasTraceLogPlayerProxy}
1227 function callback(error, resourceState)
1229 if (error || !resourceState) {
1233 this._currentResourceStates[effectiveResourceId] = resourceState;
1234 userCallback(resourceState);
1235 this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasResourceStateReceived, resourceState);
1237 CanvasAgent.getResourceState(this._traceLogId, effectiveResourceId, callback.bind(this));
1241 * @param {number} index
1242 * @param {function(?CanvasAgent.ResourceState, number):void} userCallback
1244 replayTraceLog: function(index, userCallback)
1247 * @param {?Protocol.Error} error
1248 * @param {!CanvasAgent.ResourceState} resourceState
1249 * @param {number} replayTime
1250 * @this {WebInspector.CanvasTraceLogPlayerProxy}
1252 function callback(error, resourceState, replayTime)
1254 this._currentResourceStates = {};
1256 userCallback(null, replayTime);
1258 this._defaultResourceId = resourceState.id;
1259 this._currentResourceStates[resourceState.id] = resourceState;
1260 userCallback(resourceState, replayTime);
1262 this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasReplayStateChanged);
1264 this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasResourceStateReceived, resourceState);
1266 CanvasAgent.replayTraceLog(this._traceLogId, index, callback.bind(this));
1269 clearResourceStates: function()
1271 this._currentResourceStates = {};
1272 this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasReplayStateChanged);
1275 __proto__: WebInspector.Object.prototype