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.View}
34 * @param {!WebInspector.CanvasProfileHeader} profile
36 WebInspector.CanvasProfileView = function(profile)
38 WebInspector.View.call(this);
39 this.registerRequiredCSS("canvasProfiler.css");
40 this.element.addStyleClass("canvas-profile-view");
41 this._profile = profile;
42 this._traceLogId = profile.traceLogId();
43 this._traceLogPlayer = profile.traceLogPlayer();
44 this._linkifier = new WebInspector.Linkifier();
46 const defaultReplayLogWidthPercent = 0.34;
47 this._replayInfoSplitView = new WebInspector.SplitView(true, "canvasProfileViewReplaySplitLocation", defaultReplayLogWidthPercent);
48 this._replayInfoSplitView.setMainElementConstraints(defaultReplayLogWidthPercent, defaultReplayLogWidthPercent);
49 this._replayInfoSplitView.show(this.element);
51 this._imageSplitView = new WebInspector.SplitView(false, "canvasProfileViewSplitLocation", 300);
52 this._imageSplitView.show(this._replayInfoSplitView.firstElement());
54 var replayImageContainer = this._imageSplitView.firstElement();
55 replayImageContainer.id = "canvas-replay-image-container";
56 this._replayImageElement = replayImageContainer.createChild("img", "canvas-replay-image");
57 this._debugInfoElement = replayImageContainer.createChild("div", "canvas-debug-info hidden");
58 this._spinnerIcon = replayImageContainer.createChild("img", "canvas-spinner-icon hidden");
60 var replayLogContainer = this._imageSplitView.secondElement();
61 var controlsContainer = replayLogContainer.createChild("div", "status-bar");
62 var logGridContainer = replayLogContainer.createChild("div", "canvas-replay-log");
64 this._createControlButton(controlsContainer, "canvas-replay-first-step", WebInspector.UIString("First call."), this._onReplayFirstStepClick.bind(this));
65 this._createControlButton(controlsContainer, "canvas-replay-prev-step", WebInspector.UIString("Previous call."), this._onReplayStepClick.bind(this, false));
66 this._createControlButton(controlsContainer, "canvas-replay-next-step", WebInspector.UIString("Next call."), this._onReplayStepClick.bind(this, true));
67 this._createControlButton(controlsContainer, "canvas-replay-prev-draw", WebInspector.UIString("Previous drawing call."), this._onReplayDrawingCallClick.bind(this, false));
68 this._createControlButton(controlsContainer, "canvas-replay-next-draw", WebInspector.UIString("Next drawing call."), this._onReplayDrawingCallClick.bind(this, true));
69 this._createControlButton(controlsContainer, "canvas-replay-last-step", WebInspector.UIString("Last call."), this._onReplayLastStepClick.bind(this));
71 this._replayContextSelector = new WebInspector.StatusBarComboBox(this._onReplayContextChanged.bind(this));
72 this._replayContextSelector.createOption(WebInspector.UIString("<screenshot auto>"), WebInspector.UIString("Show screenshot of the last replayed resource."), "");
73 controlsContainer.appendChild(this._replayContextSelector.element);
75 this._installReplayInfoSidebarWidgets(controlsContainer);
77 this._replayStateView = new WebInspector.CanvasReplayStateView(this._traceLogPlayer);
78 this._replayStateView.show(this._replayInfoSplitView.secondElement());
80 /** @type {!Object.<string, boolean>} */
81 this._replayContexts = {};
84 {title: "#", sortable: false, width: "5%"},
85 {title: WebInspector.UIString("Call"), sortable: false, width: "75%", disclosure: true},
86 {title: WebInspector.UIString("Location"), sortable: false, width: "20%"}
89 this._logGrid = new WebInspector.DataGrid(columns);
90 this._logGrid.element.addStyleClass("fill");
91 this._logGrid.show(logGridContainer);
92 this._logGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._replayTraceLog, this);
94 this.element.addEventListener("mousedown", this._onMouseClick.bind(this), true);
96 this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.element, this._popoverAnchor.bind(this), this._resolveObjectForPopover.bind(this), this._onHidePopover.bind(this), true);
97 this._popoverHelper.setRemoteObjectFormatter(this._hexNumbersFormatter.bind(this));
99 this._requestTraceLog(0);
106 WebInspector.CanvasProfileView.TraceLogPollingInterval = 500;
108 WebInspector.CanvasProfileView.prototype = {
111 this._linkifier.reset();
121 return this._profile;
126 * @return {Array.<Element>}
128 elementsToRestoreScrollPositionsFor: function()
130 return [this._logGrid.scrollContainer];
134 * @param {!Element} controlsContainer
136 _installReplayInfoSidebarWidgets: function(controlsContainer)
138 this._replayInfoResizeWidgetElement = controlsContainer.createChild("div", "resizer-widget");
139 this._replayInfoSplitView.installResizer(this._replayInfoResizeWidgetElement);
141 this._toggleReplayStateSidebarButton = new WebInspector.StatusBarButton("", "right-sidebar-show-hide-button canvas-sidebar-show-hide-button", 3);
142 this._toggleReplayStateSidebarButton.addEventListener("click", clickHandler, this);
143 controlsContainer.appendChild(this._toggleReplayStateSidebarButton.element);
144 this._enableReplayInfoSidebar(false);
146 function clickHandler()
148 this._enableReplayInfoSidebar(this._toggleReplayStateSidebarButton.state === "left");
153 * @param {boolean} show
155 _enableReplayInfoSidebar: function(show)
158 this._toggleReplayStateSidebarButton.state = "right";
159 this._toggleReplayStateSidebarButton.title = WebInspector.UIString("Hide sidebar.");
160 this._replayInfoSplitView.showBoth();
162 this._toggleReplayStateSidebarButton.state = "left";
163 this._toggleReplayStateSidebarButton.title = WebInspector.UIString("Show sidebar.");
164 this._replayInfoSplitView.showOnlyFirst();
166 this._replayInfoResizeWidgetElement.enableStyleClass("hidden", !show);
170 * @param {Event} event
172 _onMouseClick: function(event)
174 var resourceLinkElement = event.target.enclosingNodeOrSelfWithClass("canvas-formatted-resource");
175 if (resourceLinkElement) {
176 this._enableReplayInfoSidebar(true);
177 this._replayStateView.selectResource(resourceLinkElement.__resourceId);
181 if (event.target.enclosingNodeOrSelfWithClass("webkit-html-resource-link"))
182 event.consume(false);
186 * @param {Element} parent
187 * @param {string} className
188 * @param {string} title
189 * @param {function(this:WebInspector.CanvasProfileView)} clickCallback
191 _createControlButton: function(parent, className, title, clickCallback)
193 var button = new WebInspector.StatusBarButton(title, className + " canvas-replay-button");
194 parent.appendChild(button.element);
196 button.makeLongClickEnabled();
197 button.addEventListener("click", clickCallback, this);
198 button.addEventListener("longClickDown", clickCallback, this);
199 button.addEventListener("longClickPress", clickCallback, this);
202 _onReplayContextChanged: function()
204 var selectedContextId = this._replayContextSelector.selectedOption().value;
207 * @param {?CanvasAgent.ResourceState} resourceState
209 function didReceiveResourceState(resourceState)
211 this._enableWaitIcon(false);
212 if (selectedContextId !== this._replayContextSelector.selectedOption().value)
214 var imageURL = (resourceState && resourceState.imageURL) || "";
215 this._replayImageElement.src = imageURL;
216 this._replayImageElement.style.visibility = imageURL ? "" : "hidden";
219 this._enableWaitIcon(true);
220 this._traceLogPlayer.getResourceState(selectedContextId, didReceiveResourceState.bind(this));
224 * @param {boolean} forward
226 _onReplayStepClick: function(forward)
228 var selectedNode = this._logGrid.selectedNode;
231 var nextNode = selectedNode;
233 nextNode = forward ? nextNode.traverseNextNode(false) : nextNode.traversePreviousNode(false);
234 } while (nextNode && typeof nextNode.index !== "number");
235 (nextNode || selectedNode).revealAndSelect();
239 * @param {boolean} forward
241 _onReplayDrawingCallClick: function(forward)
243 var selectedNode = this._logGrid.selectedNode;
246 var nextNode = selectedNode;
248 var sibling = forward ? nextNode.nextSibling : nextNode.previousSibling;
251 if (nextNode.hasChildren || nextNode.call.isDrawingCall)
254 nextNode = nextNode.parent;
259 if (!nextNode && forward)
260 this._onReplayLastStepClick();
262 (nextNode || selectedNode).revealAndSelect();
265 _onReplayFirstStepClick: function()
267 var firstNode = this._logGrid.rootNode().children[0];
269 firstNode.revealAndSelect();
272 _onReplayLastStepClick: function()
274 var lastNode = this._logGrid.rootNode().children.peekLast();
277 while (lastNode.expanded) {
278 var lastChild = lastNode.children.peekLast();
281 lastNode = lastChild;
283 lastNode.revealAndSelect();
287 * @param {boolean} enable
289 _enableWaitIcon: function(enable)
291 this._spinnerIcon.enableStyleClass("hidden", !enable);
292 this._debugInfoElement.enableStyleClass("hidden", enable);
295 _replayTraceLog: function()
297 if (this._pendingReplayTraceLogEvent)
299 var index = this._selectedCallIndex();
300 if (index === -1 || index === this._lastReplayCallIndex)
302 this._lastReplayCallIndex = index;
303 this._pendingReplayTraceLogEvent = true;
305 * @param {CanvasAgent.ResourceState} resourceState
306 * @param {number} replayTime
308 function didReplayTraceLog(resourceState, replayTime)
310 delete this._pendingReplayTraceLogEvent;
311 this._enableWaitIcon(false);
313 this._debugInfoElement.textContent = "Replay time: " + Number.secondsToString(replayTime / 1000, true);
314 this._onReplayContextChanged();
316 if (index !== this._selectedCallIndex())
317 this._replayTraceLog();
319 this._enableWaitIcon(true);
320 this._traceLogPlayer.replayTraceLog(index, didReplayTraceLog.bind(this));
324 * @param {number} offset
326 _requestTraceLog: function(offset)
329 * @param {?CanvasAgent.TraceLog} traceLog
331 function didReceiveTraceLog(traceLog)
333 this._enableWaitIcon(false);
337 var calls = traceLog.calls;
338 var index = traceLog.startOffset;
339 for (var i = 0, n = calls.length; i < n; ++i)
340 callNodes.push(this._createCallNode(index++, calls[i]));
341 var contexts = traceLog.contexts;
342 for (var i = 0, n = contexts.length; i < n; ++i) {
343 var contextId = contexts[i].resourceId || "";
344 var description = contexts[i].description || "";
345 if (this._replayContexts[contextId])
347 this._replayContexts[contextId] = true;
348 this._replayContextSelector.createOption(description, WebInspector.UIString("Show screenshot of this context's canvas."), contextId);
350 this._appendCallNodes(callNodes);
352 setTimeout(this._requestTraceLog.bind(this, index), WebInspector.CanvasProfileView.TraceLogPollingInterval);
354 this._flattenSingleFrameNode();
355 this._profile._updateCapturingStatus(traceLog);
356 this._onReplayLastStepClick(); // Automatically replay the last step.
358 this._enableWaitIcon(true);
359 this._traceLogPlayer.getTraceLog(offset, undefined, didReceiveTraceLog.bind(this));
365 _selectedCallIndex: function()
367 var node = this._logGrid.selectedNode;
368 return node ? this._peekLastRecursively(node).index : -1;
372 * @param {!WebInspector.DataGridNode} node
373 * @return {!WebInspector.DataGridNode}
375 _peekLastRecursively: function(node)
378 while ((lastChild = node.children.peekLast()))
384 * @param {!Array.<!WebInspector.DataGridNode>} callNodes
386 _appendCallNodes: function(callNodes)
388 var rootNode = this._logGrid.rootNode();
389 var frameNode = rootNode.children.peekLast();
390 if (frameNode && this._peekLastRecursively(frameNode).call.isFrameEndCall)
392 for (var i = 0, n = callNodes.length; i < n; ++i) {
394 var index = rootNode.children.length;
397 data[1] = "Frame #" + (index + 1);
399 frameNode = new WebInspector.DataGridNode(data);
400 frameNode.selectable = true;
401 rootNode.appendChild(frameNode);
403 var nextFrameCallIndex = i + 1;
404 while (nextFrameCallIndex < n && !callNodes[nextFrameCallIndex - 1].call.isFrameEndCall)
405 ++nextFrameCallIndex;
406 this._appendCallNodesToFrameNode(frameNode, callNodes, i, nextFrameCallIndex);
407 i = nextFrameCallIndex - 1;
413 * @param {!WebInspector.DataGridNode} frameNode
414 * @param {!Array.<!WebInspector.DataGridNode>} callNodes
415 * @param {number} fromIndex
416 * @param {number} toIndex not inclusive
418 _appendCallNodesToFrameNode: function(frameNode, callNodes, fromIndex, toIndex)
421 function appendDrawCallGroup()
423 var index = self._drawCallGroupsCount || 0;
426 data[1] = "Draw call group #" + (index + 1);
428 var node = new WebInspector.DataGridNode(data);
429 node.selectable = true;
430 self._drawCallGroupsCount = index + 1;
431 frameNode.appendChild(node);
435 function splitDrawCallGroup(drawCallGroup)
439 while ((splitNode = drawCallGroup.children[splitIndex])) {
440 if (splitNode.call.isDrawingCall)
444 var newDrawCallGroup = appendDrawCallGroup();
446 while ((lastNode = drawCallGroup.children[splitIndex + 1]))
447 newDrawCallGroup.appendChild(lastNode);
448 return newDrawCallGroup;
451 var drawCallGroup = frameNode.children.peekLast();
452 var groupHasDrawCall = false;
454 for (var i = 0, n = drawCallGroup.children.length; i < n; ++i) {
455 if (drawCallGroup.children[i].call.isDrawingCall) {
456 groupHasDrawCall = true;
461 drawCallGroup = appendDrawCallGroup();
463 for (var i = fromIndex; i < toIndex; ++i) {
464 var node = callNodes[i];
465 drawCallGroup.appendChild(node);
466 if (node.call.isDrawingCall) {
467 if (groupHasDrawCall)
468 drawCallGroup = splitDrawCallGroup(drawCallGroup);
470 groupHasDrawCall = true;
476 * @param {number} index
477 * @param {CanvasAgent.Call} call
478 * @return {!WebInspector.DataGridNode}
480 _createCallNode: function(index, call)
482 var callViewElement = document.createElement("div");
486 data[1] = callViewElement;
488 if (call.sourceURL) {
489 // FIXME(62725): stack trace line/column numbers are one-based.
490 var lineNumber = Math.max(0, call.lineNumber - 1) || 0;
491 var columnNumber = Math.max(0, call.columnNumber - 1) || 0;
492 data[2] = this._linkifier.linkifyLocation(call.sourceURL, lineNumber, columnNumber);
495 callViewElement.createChild("span", "canvas-function-name").textContent = call.functionName || "context." + call.property;
497 if (call.arguments) {
498 callViewElement.createTextChild("(");
499 for (var i = 0, n = call.arguments.length; i < n; ++i) {
500 var argument = /** @type {!CanvasAgent.CallArgument} */ (call.arguments[i]);
502 callViewElement.createTextChild(", ");
503 var element = WebInspector.CanvasProfileDataGridHelper.createCallArgumentElement(argument);
504 element.__argumentIndex = i;
505 callViewElement.appendChild(element);
507 callViewElement.createTextChild(")");
508 } else if (call.value) {
509 callViewElement.createTextChild(" = ");
510 callViewElement.appendChild(WebInspector.CanvasProfileDataGridHelper.createCallArgumentElement(call.value));
514 callViewElement.createTextChild(" => ");
515 callViewElement.appendChild(WebInspector.CanvasProfileDataGridHelper.createCallArgumentElement(call.result));
518 var node = new WebInspector.DataGridNode(data);
520 node.selectable = true;
525 _popoverAnchor: function(element, event)
527 var argumentElement = element.enclosingNodeOrSelfWithClass("canvas-call-argument");
528 if (!argumentElement || argumentElement.__suppressPopover)
530 return argumentElement;
533 _resolveObjectForPopover: function(argumentElement, showCallback, objectGroupName)
536 * @param {?Protocol.Error} error
537 * @param {RuntimeAgent.RemoteObject=} result
538 * @param {CanvasAgent.ResourceState=} resourceState
540 function showObjectPopover(error, result, resourceState)
545 // FIXME: handle resourceState also
549 this._popoverAnchorElement = argumentElement.cloneNode(true);
550 this._popoverAnchorElement.addStyleClass("canvas-popover-anchor");
551 this._popoverAnchorElement.addStyleClass("source-frame-eval-expression");
552 argumentElement.parentElement.appendChild(this._popoverAnchorElement);
554 var diffLeft = this._popoverAnchorElement.boxInWindow().x - argumentElement.boxInWindow().x;
555 this._popoverAnchorElement.style.left = this._popoverAnchorElement.offsetLeft - diffLeft + "px";
557 showCallback(WebInspector.RemoteObject.fromPayload(result), false, this._popoverAnchorElement);
560 var evalResult = argumentElement.__evalResult;
562 showObjectPopover.call(this, null, evalResult);
564 var dataGridNode = this._logGrid.dataGridNodeFromNode(argumentElement);
565 if (!dataGridNode || typeof dataGridNode.index !== "number") {
566 this._popoverHelper.hidePopover();
569 var callIndex = dataGridNode.index;
570 var argumentIndex = argumentElement.__argumentIndex;
571 if (typeof argumentIndex !== "number")
573 CanvasAgent.evaluateTraceLogCallArgument(this._traceLogId, callIndex, argumentIndex, objectGroupName, showObjectPopover.bind(this));
578 * @param {WebInspector.RemoteObject} object
581 _hexNumbersFormatter: function(object)
583 if (object.type === "number") {
584 // Show enum values in hex with min length of 4 (e.g. 0x0012).
585 var str = "0000" + Number(object.description).toString(16).toUpperCase();
586 str = str.replace(/^0+(.{4,})$/, "$1");
589 return object.description || "";
592 _onHidePopover: function()
594 if (this._popoverAnchorElement) {
595 this._popoverAnchorElement.remove()
596 delete this._popoverAnchorElement;
600 _flattenSingleFrameNode: function()
602 var rootNode = this._logGrid.rootNode();
603 if (rootNode.children.length !== 1)
605 var frameNode = rootNode.children[0];
606 while (frameNode.children[0])
607 rootNode.appendChild(frameNode.children[0]);
608 rootNode.removeChild(frameNode);
611 __proto__: WebInspector.View.prototype
616 * @extends {WebInspector.ProfileType}
618 WebInspector.CanvasProfileType = function()
620 WebInspector.ProfileType.call(this, WebInspector.CanvasProfileType.TypeId, WebInspector.UIString("Capture Canvas Frame"));
621 this._nextProfileUid = 1;
622 this._recording = false;
623 this._lastProfileHeader = null;
625 this._capturingModeSelector = new WebInspector.StatusBarComboBox(this._dispatchViewUpdatedEvent.bind(this));
626 this._capturingModeSelector.element.title = WebInspector.UIString("Canvas capture mode.");
627 this._capturingModeSelector.createOption(WebInspector.UIString("Single Frame"), WebInspector.UIString("Capture a single canvas frame."), "");
628 this._capturingModeSelector.createOption(WebInspector.UIString("Consecutive Frames"), WebInspector.UIString("Capture consecutive canvas frames."), "1");
630 /** @type {!Object.<string, Element>} */
631 this._frameOptions = {};
633 /** @type {!Object.<string, boolean>} */
634 this._framesWithCanvases = {};
636 this._frameSelector = new WebInspector.StatusBarComboBox(this._dispatchViewUpdatedEvent.bind(this));
637 this._frameSelector.element.title = WebInspector.UIString("Frame containing the canvases to capture.");
638 this._frameSelector.element.addStyleClass("hidden");
639 WebInspector.runtimeModel.contextLists().forEach(this._addFrame, this);
640 WebInspector.runtimeModel.addEventListener(WebInspector.RuntimeModel.Events.FrameExecutionContextListAdded, this._frameAdded, this);
641 WebInspector.runtimeModel.addEventListener(WebInspector.RuntimeModel.Events.FrameExecutionContextListRemoved, this._frameRemoved, this);
643 this._dispatcher = new WebInspector.CanvasDispatcher(this);
644 this._canvasAgentEnabled = false;
646 this._decorationElement = document.createElement("div");
647 this._decorationElement.className = "profile-canvas-decoration";
648 this._updateDecorationElement();
651 WebInspector.CanvasProfileType.TypeId = "CANVAS_PROFILE";
653 WebInspector.CanvasProfileType.prototype = {
656 return [this._capturingModeSelector.element, this._frameSelector.element];
661 if (this._isSingleFrameMode())
662 return WebInspector.UIString("Capture next canvas frame.");
664 return this._recording ? WebInspector.UIString("Stop capturing canvas frames.") : WebInspector.UIString("Start capturing canvas frames.");
671 buttonClicked: function()
673 if (!this._canvasAgentEnabled)
675 if (this._recording) {
676 this._recording = false;
677 this._stopFrameCapturing();
678 } else if (this._isSingleFrameMode()) {
679 this._recording = false;
680 this._runSingleFrameCapturing();
682 this._recording = true;
683 this._startFrameCapturing();
685 return this._recording;
688 _runSingleFrameCapturing: function()
690 var frameId = this._selectedFrameId();
691 CanvasAgent.captureFrame(frameId, this._didStartCapturingFrame.bind(this, frameId));
694 _startFrameCapturing: function()
696 var frameId = this._selectedFrameId();
697 CanvasAgent.startCapturing(frameId, this._didStartCapturingFrame.bind(this, frameId));
700 _stopFrameCapturing: function()
702 if (!this._lastProfileHeader)
704 var profileHeader = this._lastProfileHeader;
705 var traceLogId = profileHeader.traceLogId();
706 this._lastProfileHeader = null;
707 function didStopCapturing()
709 profileHeader._updateCapturingStatus();
711 CanvasAgent.stopCapturing(traceLogId, didStopCapturing.bind(this));
715 * @param {string|undefined} frameId
716 * @param {?Protocol.Error} error
717 * @param {CanvasAgent.TraceLogId} traceLogId
719 _didStartCapturingFrame: function(frameId, error, traceLogId)
721 if (error || this._lastProfileHeader && this._lastProfileHeader.traceLogId() === traceLogId)
723 var profileHeader = new WebInspector.CanvasProfileHeader(this, WebInspector.UIString("Trace Log %d", this._nextProfileUid), this._nextProfileUid, traceLogId, frameId);
724 ++this._nextProfileUid;
725 this._lastProfileHeader = profileHeader;
726 this.addProfile(profileHeader);
727 profileHeader._updateCapturingStatus();
732 return WebInspector.UIString("CANVAS PROFILE");
737 return WebInspector.UIString("Canvas calls instrumentation");
744 decorationElement: function()
746 return this._decorationElement;
754 WebInspector.ProfileType.prototype._reset.call(this);
755 this._nextProfileUid = 1;
760 * @param {!WebInspector.ProfileHeader} profile
762 removeProfile: function(profile)
764 WebInspector.ProfileType.prototype.removeProfile.call(this, profile);
765 if (this._recording && profile === this._lastProfileHeader)
766 this._recording = false;
769 setRecordingProfile: function(isProfiling)
771 this._recording = isProfiling;
776 * @param {string=} title
777 * @return {!WebInspector.ProfileHeader}
779 createTemporaryProfile: function(title)
781 title = title || WebInspector.UIString("Capturing\u2026");
782 return new WebInspector.CanvasProfileHeader(this, title);
787 * @param {ProfilerAgent.ProfileHeader} profile
788 * @return {!WebInspector.ProfileHeader}
790 createProfile: function(profile)
792 return new WebInspector.CanvasProfileHeader(this, profile.title, -1);
796 * @param {boolean=} forcePageReload
798 _updateDecorationElement: function(forcePageReload)
800 this._decorationElement.removeChildren();
801 this._decorationElement.createChild("div", "warning-icon-small");
802 this._decorationElement.appendChild(document.createTextNode(this._canvasAgentEnabled ? WebInspector.UIString("Canvas Profiler is enabled.") : WebInspector.UIString("Canvas Profiler is disabled.")));
803 var button = this._decorationElement.createChild("button");
804 button.type = "button";
805 button.textContent = this._canvasAgentEnabled ? WebInspector.UIString("Disable") : WebInspector.UIString("Enable");
806 button.addEventListener("click", this._onProfilerEnableButtonClick.bind(this, !this._canvasAgentEnabled), false);
808 if (forcePageReload) {
809 if (this._canvasAgentEnabled) {
811 * @param {?Protocol.Error} error
812 * @param {boolean} result
814 function hasUninstrumentedCanvasesCallback(error, result)
819 CanvasAgent.hasUninstrumentedCanvases(hasUninstrumentedCanvasesCallback.bind(this));
821 for (var frameId in this._framesWithCanvases) {
822 if (this._framesWithCanvases.hasOwnProperty(frameId)) {
832 * @param {boolean} enable
834 _onProfilerEnableButtonClick: function(enable)
836 if (this._canvasAgentEnabled === enable)
839 * @param {?Protocol.Error} error
841 function callback(error)
845 this._canvasAgentEnabled = enable;
846 this._updateDecorationElement(true);
847 this._dispatchViewUpdatedEvent();
850 CanvasAgent.enable(callback.bind(this));
852 CanvasAgent.disable(callback.bind(this));
858 _isSingleFrameMode: function()
860 return !this._capturingModeSelector.selectedOption().value;
864 * @param {WebInspector.Event} event
866 _frameAdded: function(event)
868 var contextList = /** @type {WebInspector.FrameExecutionContextList} */ (event.data);
869 this._addFrame(contextList);
873 * @param {WebInspector.FrameExecutionContextList} contextList
875 _addFrame: function(contextList)
877 var frameId = contextList.frameId;
878 var option = document.createElement("option");
879 option.text = contextList.displayName;
880 option.title = contextList.url;
881 option.value = frameId;
883 this._frameOptions[frameId] = option;
885 if (this._framesWithCanvases[frameId]) {
886 this._frameSelector.addOption(option);
887 this._dispatchViewUpdatedEvent();
892 * @param {WebInspector.Event} event
894 _frameRemoved: function(event)
896 var contextList = /** @type {WebInspector.FrameExecutionContextList} */ (event.data);
897 var frameId = contextList.frameId;
898 var option = this._frameOptions[frameId];
899 if (option && this._framesWithCanvases[frameId]) {
900 this._frameSelector.removeOption(option);
901 this._dispatchViewUpdatedEvent();
903 delete this._frameOptions[frameId];
904 delete this._framesWithCanvases[frameId];
908 * @param {string} frameId
910 _contextCreated: function(frameId)
912 if (this._framesWithCanvases[frameId])
914 this._framesWithCanvases[frameId] = true;
915 var option = this._frameOptions[frameId];
917 this._frameSelector.addOption(option);
918 this._dispatchViewUpdatedEvent();
923 * @param {PageAgent.FrameId=} frameId
924 * @param {CanvasAgent.TraceLogId=} traceLogId
926 _traceLogsRemoved: function(frameId, traceLogId)
928 var sidebarElementsToDelete = [];
929 var sidebarElements = /** @type {!Array.<WebInspector.ProfileSidebarTreeElement>} */ ((this.treeElement && this.treeElement.children) || []);
930 for (var i = 0, n = sidebarElements.length; i < n; ++i) {
931 var header = /** @type {WebInspector.CanvasProfileHeader} */ (sidebarElements[i].profile);
934 if (frameId && frameId !== header.frameId())
936 if (traceLogId && traceLogId !== header.traceLogId())
938 sidebarElementsToDelete.push(sidebarElements[i]);
940 for (var i = 0, n = sidebarElementsToDelete.length; i < n; ++i)
941 sidebarElementsToDelete[i].ondelete();
945 * @return {string|undefined}
947 _selectedFrameId: function()
949 var option = this._frameSelector.selectedOption();
950 return option ? option.value : undefined;
953 _dispatchViewUpdatedEvent: function()
955 this._frameSelector.element.enableStyleClass("hidden", this._frameSelector.size() <= 1);
956 this.dispatchEventToListeners(WebInspector.ProfileType.Events.ViewUpdated);
963 isInstantProfile: function()
965 return this._isSingleFrameMode();
972 isEnabled: function()
974 return this._canvasAgentEnabled;
977 __proto__: WebInspector.ProfileType.prototype
982 * @implements {CanvasAgent.Dispatcher}
983 * @param {WebInspector.CanvasProfileType} profileType
985 WebInspector.CanvasDispatcher = function(profileType)
987 this._profileType = profileType;
988 InspectorBackend.registerCanvasDispatcher(this);
991 WebInspector.CanvasDispatcher.prototype = {
993 * @param {string} frameId
995 contextCreated: function(frameId)
997 this._profileType._contextCreated(frameId);
1001 * @param {PageAgent.FrameId=} frameId
1002 * @param {CanvasAgent.TraceLogId=} traceLogId
1004 traceLogsRemoved: function(frameId, traceLogId)
1006 this._profileType._traceLogsRemoved(frameId, traceLogId);
1012 * @extends {WebInspector.ProfileHeader}
1013 * @param {!WebInspector.CanvasProfileType} type
1014 * @param {string} title
1015 * @param {number=} uid
1016 * @param {CanvasAgent.TraceLogId=} traceLogId
1017 * @param {PageAgent.FrameId=} frameId
1019 WebInspector.CanvasProfileHeader = function(type, title, uid, traceLogId, frameId)
1021 WebInspector.ProfileHeader.call(this, type, title, uid);
1022 /** @type {CanvasAgent.TraceLogId} */
1023 this._traceLogId = traceLogId || "";
1024 this._frameId = frameId;
1026 this._traceLogSize = 0;
1027 this._traceLogPlayer = traceLogId ? new WebInspector.CanvasTraceLogPlayerProxy(traceLogId) : null;
1030 WebInspector.CanvasProfileHeader.prototype = {
1032 * @return {CanvasAgent.TraceLogId}
1034 traceLogId: function()
1036 return this._traceLogId;
1040 * @return {WebInspector.CanvasTraceLogPlayerProxy}
1042 traceLogPlayer: function()
1044 return this._traceLogPlayer;
1048 * @return {PageAgent.FrameId|undefined}
1052 return this._frameId;
1057 * @return {WebInspector.ProfileSidebarTreeElement}
1059 createSidebarTreeElement: function()
1061 return new WebInspector.ProfileSidebarTreeElement(this, WebInspector.UIString("Trace Log %d"), "profile-sidebar-tree-item");
1066 * @param {WebInspector.ProfilesPanel} profilesPanel
1068 createView: function(profilesPanel)
1070 return new WebInspector.CanvasProfileView(this);
1078 if (this._traceLogPlayer)
1079 this._traceLogPlayer.dispose();
1080 clearTimeout(this._requestStatusTimer);
1081 this._alive = false;
1085 * @param {CanvasAgent.TraceLog=} traceLog
1087 _updateCapturingStatus: function(traceLog)
1089 if (!this.sidebarElement || !this._traceLogId)
1093 this._alive = traceLog.alive;
1094 this._traceLogSize = traceLog.totalAvailableCalls;
1097 this.sidebarElement.subtitle = this._alive ? WebInspector.UIString("Capturing\u2026 %d calls", this._traceLogSize) : WebInspector.UIString("Captured %d calls", this._traceLogSize);
1098 this.sidebarElement.wait = this._alive;
1101 clearTimeout(this._requestStatusTimer);
1102 this._requestStatusTimer = setTimeout(this._requestCapturingStatus.bind(this), WebInspector.CanvasProfileView.TraceLogPollingInterval);
1106 _requestCapturingStatus: function()
1109 * @param {?CanvasAgent.TraceLog} traceLog
1111 function didReceiveTraceLog(traceLog)
1115 this._alive = traceLog.alive;
1116 this._traceLogSize = traceLog.totalAvailableCalls;
1117 this._updateCapturingStatus();
1119 this._traceLogPlayer.getTraceLog(0, 0, didReceiveTraceLog.bind(this));
1122 __proto__: WebInspector.ProfileHeader.prototype
1125 WebInspector.CanvasProfileDataGridHelper = {
1127 * @param {!CanvasAgent.CallArgument} callArgument
1128 * @return {!Element}
1130 createCallArgumentElement: function(callArgument)
1132 if (callArgument.enumName)
1133 return WebInspector.CanvasProfileDataGridHelper.createEnumValueElement(callArgument.enumName, +callArgument.description);
1134 var element = document.createElement("span");
1135 element.className = "canvas-call-argument";
1136 var description = callArgument.description;
1137 if (callArgument.type === "string") {
1138 const maxStringLength = 150;
1139 element.createTextChild("\"");
1140 element.createChild("span", "canvas-formatted-string").textContent = description.trimMiddle(maxStringLength);
1141 element.createTextChild("\"");
1142 element.__suppressPopover = (description.length <= maxStringLength && !/[\r\n]/.test(description));
1143 if (!element.__suppressPopover)
1144 element.__evalResult = WebInspector.RemoteObject.fromPrimitiveValue(description);
1146 var type = callArgument.subtype || callArgument.type;
1148 element.addStyleClass("canvas-formatted-" + type);
1149 if (["null", "undefined", "boolean", "number"].indexOf(type) >= 0)
1150 element.__suppressPopover = true;
1152 element.textContent = description;
1153 if (callArgument.remoteObject)
1154 element.__evalResult = WebInspector.RemoteObject.fromPayload(callArgument.remoteObject);
1156 if (callArgument.resourceId) {
1157 element.addStyleClass("canvas-formatted-resource");
1158 element.__resourceId = callArgument.resourceId;
1164 * @param {string} enumName
1165 * @param {number} enumValue
1166 * @return {!Element}
1168 createEnumValueElement: function(enumName, enumValue)
1170 var element = document.createElement("span");
1171 element.className = "canvas-call-argument canvas-formatted-number";
1172 element.textContent = enumName;
1173 element.__evalResult = WebInspector.RemoteObject.fromPrimitiveValue(enumValue);
1179 * @extends {WebInspector.Object}
1181 * @param {CanvasAgent.TraceLogId} traceLogId
1183 WebInspector.CanvasTraceLogPlayerProxy = function(traceLogId)
1185 this._traceLogId = traceLogId;
1186 /** @type {!Object.<string, !CanvasAgent.ResourceState>} */
1187 this._currentResourceStates = {};
1188 /** @type {?CanvasAgent.ResourceId} */
1189 this._defaultResourceId = null;
1192 /** @enum {string} */
1193 WebInspector.CanvasTraceLogPlayerProxy.Events = {
1194 CanvasTraceLogReceived: "CanvasTraceLogReceived",
1195 CanvasReplayStateChanged: "CanvasReplayStateChanged",
1196 CanvasResourceStateReceived: "CanvasResourceStateReceived",
1199 WebInspector.CanvasTraceLogPlayerProxy.prototype = {
1201 * @param {number|undefined} startOffset
1202 * @param {number|undefined} maxLength
1203 * @param {function(?CanvasAgent.TraceLog):void} userCallback
1205 getTraceLog: function(startOffset, maxLength, userCallback)
1208 * @param {?Protocol.Error} error
1209 * @param {CanvasAgent.TraceLog} traceLog
1211 function callback(error, traceLog)
1213 if (error || !traceLog) {
1217 userCallback(traceLog);
1218 this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasTraceLogReceived, traceLog);
1220 CanvasAgent.getTraceLog(this._traceLogId, startOffset, maxLength, callback.bind(this));
1225 this._currentResourceStates = {};
1226 CanvasAgent.dropTraceLog(this._traceLogId);
1227 this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasReplayStateChanged);
1231 * @param {?CanvasAgent.ResourceId} resourceId
1232 * @param {function(?CanvasAgent.ResourceState):void} userCallback
1234 getResourceState: function(resourceId, userCallback)
1236 resourceId = resourceId || this._defaultResourceId;
1238 userCallback(null); // Has not been replayed yet.
1241 if (this._currentResourceStates[resourceId]) {
1242 userCallback(this._currentResourceStates[resourceId]);
1246 * @param {?Protocol.Error} error
1247 * @param {CanvasAgent.ResourceState} resourceState
1249 function callback(error, resourceState)
1251 if (error || !resourceState) {
1255 this._currentResourceStates[resourceId] = resourceState;
1256 userCallback(resourceState);
1257 this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasResourceStateReceived, resourceState);
1259 CanvasAgent.getResourceState(this._traceLogId, resourceId, callback.bind(this));
1263 * @param {number} index
1264 * @param {function(?CanvasAgent.ResourceState, number):void} userCallback
1266 replayTraceLog: function(index, userCallback)
1269 * @param {?Protocol.Error} error
1270 * @param {CanvasAgent.ResourceState} resourceState
1271 * @param {number} replayTime
1273 function callback(error, resourceState, replayTime)
1275 this._currentResourceStates = {};
1276 if (error || !resourceState) {
1277 resourceState = null;
1278 userCallback(null, replayTime);
1280 this._defaultResourceId = resourceState.id;
1281 this._currentResourceStates[resourceState.id] = resourceState;
1282 userCallback(resourceState, replayTime);
1284 this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasReplayStateChanged);
1286 this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasResourceStateReceived, resourceState);
1288 CanvasAgent.replayTraceLog(this._traceLogId, index, callback.bind(this));
1291 clearResourceStates: function()
1293 this._currentResourceStates = {};
1294 this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasReplayStateChanged);
1297 __proto__: WebInspector.Object.prototype