| /* |
| * Copyright (C) 2013 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| /** |
| * @constructor |
| * @extends {WebInspector.View} |
| * @param {!WebInspector.CanvasProfileHeader} profile |
| */ |
| WebInspector.CanvasProfileView = function(profile) |
| { |
| WebInspector.View.call(this); |
| this.registerRequiredCSS("canvasProfiler.css"); |
| this.element.addStyleClass("canvas-profile-view"); |
| this._profile = profile; |
| this._traceLogId = profile.traceLogId(); |
| this._traceLogPlayer = profile.traceLogPlayer(); |
| this._linkifier = new WebInspector.Linkifier(); |
| |
| const defaultReplayLogWidthPercent = 0.34; |
| this._replayInfoSplitView = new WebInspector.SplitView(true, "canvasProfileViewReplaySplitLocation", defaultReplayLogWidthPercent); |
| this._replayInfoSplitView.setMainElementConstraints(defaultReplayLogWidthPercent, defaultReplayLogWidthPercent); |
| this._replayInfoSplitView.show(this.element); |
| |
| this._imageSplitView = new WebInspector.SplitView(false, "canvasProfileViewSplitLocation", 300); |
| this._imageSplitView.show(this._replayInfoSplitView.firstElement()); |
| |
| var replayImageContainer = this._imageSplitView.firstElement(); |
| replayImageContainer.id = "canvas-replay-image-container"; |
| this._replayImageElement = replayImageContainer.createChild("image", "canvas-replay-image"); |
| this._debugInfoElement = replayImageContainer.createChild("div", "canvas-debug-info hidden"); |
| this._spinnerIcon = replayImageContainer.createChild("img", "canvas-spinner-icon hidden"); |
| |
| var replayLogContainer = this._imageSplitView.secondElement(); |
| var controlsContainer = replayLogContainer.createChild("div", "status-bar"); |
| var logGridContainer = replayLogContainer.createChild("div", "canvas-replay-log"); |
| |
| this._createControlButton(controlsContainer, "canvas-replay-first-step", WebInspector.UIString("First call."), this._onReplayFirstStepClick.bind(this)); |
| this._createControlButton(controlsContainer, "canvas-replay-prev-step", WebInspector.UIString("Previous call."), this._onReplayStepClick.bind(this, false)); |
| this._createControlButton(controlsContainer, "canvas-replay-next-step", WebInspector.UIString("Next call."), this._onReplayStepClick.bind(this, true)); |
| this._createControlButton(controlsContainer, "canvas-replay-prev-draw", WebInspector.UIString("Previous drawing call."), this._onReplayDrawingCallClick.bind(this, false)); |
| this._createControlButton(controlsContainer, "canvas-replay-next-draw", WebInspector.UIString("Next drawing call."), this._onReplayDrawingCallClick.bind(this, true)); |
| this._createControlButton(controlsContainer, "canvas-replay-last-step", WebInspector.UIString("Last call."), this._onReplayLastStepClick.bind(this)); |
| |
| this._replayContextSelector = new WebInspector.StatusBarComboBox(this._onReplayContextChanged.bind(this)); |
| this._replayContextSelector.createOption(WebInspector.UIString("<screenshot auto>"), WebInspector.UIString("Show screenshot of the last replayed resource."), ""); |
| controlsContainer.appendChild(this._replayContextSelector.element); |
| |
| this._installReplayInfoSidebarWidgets(controlsContainer); |
| |
| this._replayStateView = new WebInspector.CanvasReplayStateView(this._traceLogPlayer); |
| this._replayStateView.show(this._replayInfoSplitView.secondElement()); |
| |
| /** @type {!Object.<string, boolean>} */ |
| this._replayContexts = {}; |
| |
| var columns = [ |
| {title: "#", sortable: false, width: "5%"}, |
| {title: WebInspector.UIString("Call"), sortable: false, width: "75%", disclosure: true}, |
| {title: WebInspector.UIString("Location"), sortable: false, width: "20%"} |
| ]; |
| |
| this._logGrid = new WebInspector.DataGrid(columns); |
| this._logGrid.element.addStyleClass("fill"); |
| this._logGrid.show(logGridContainer); |
| this._logGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._replayTraceLog, this); |
| |
| this.element.addEventListener("mousedown", this._onMouseClick.bind(this), true); |
| |
| this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.element, this._popoverAnchor.bind(this), this._resolveObjectForPopover.bind(this), this._onHidePopover.bind(this), true); |
| this._popoverHelper.setRemoteObjectFormatter(this._hexNumbersFormatter.bind(this)); |
| |
| this._requestTraceLog(0); |
| } |
| |
| /** |
| * @const |
| * @type {number} |
| */ |
| WebInspector.CanvasProfileView.TraceLogPollingInterval = 500; |
| |
| WebInspector.CanvasProfileView.prototype = { |
| dispose: function() |
| { |
| this._linkifier.reset(); |
| }, |
| |
| get statusBarItems() |
| { |
| return []; |
| }, |
| |
| get profile() |
| { |
| return this._profile; |
| }, |
| |
| /** |
| * @override |
| * @return {Array.<Element>} |
| */ |
| elementsToRestoreScrollPositionsFor: function() |
| { |
| return [this._logGrid.scrollContainer]; |
| }, |
| |
| /** |
| * @param {!Element} controlsContainer |
| */ |
| _installReplayInfoSidebarWidgets: function(controlsContainer) |
| { |
| this._replayInfoResizeWidgetElement = controlsContainer.createChild("div", "resizer-widget"); |
| this._replayInfoSplitView.installResizer(this._replayInfoResizeWidgetElement); |
| |
| this._toggleReplayStateSidebarButton = new WebInspector.StatusBarButton("", "right-sidebar-show-hide-button canvas-sidebar-show-hide-button", 3); |
| this._toggleReplayStateSidebarButton.addEventListener("click", clickHandler, this); |
| controlsContainer.appendChild(this._toggleReplayStateSidebarButton.element); |
| this._enableReplayInfoSidebar(false); |
| |
| function clickHandler() |
| { |
| this._enableReplayInfoSidebar(this._toggleReplayStateSidebarButton.state === "left"); |
| } |
| }, |
| |
| /** |
| * @param {boolean} show |
| */ |
| _enableReplayInfoSidebar: function(show) |
| { |
| if (show) { |
| this._toggleReplayStateSidebarButton.state = "right"; |
| this._toggleReplayStateSidebarButton.title = WebInspector.UIString("Hide sidebar."); |
| this._replayInfoSplitView.showBoth(); |
| } else { |
| this._toggleReplayStateSidebarButton.state = "left"; |
| this._toggleReplayStateSidebarButton.title = WebInspector.UIString("Show sidebar."); |
| this._replayInfoSplitView.showOnlyFirst(); |
| } |
| this._replayInfoResizeWidgetElement.enableStyleClass("hidden", !show); |
| }, |
| |
| /** |
| * @param {Event} event |
| */ |
| _onMouseClick: function(event) |
| { |
| var resourceLinkElement = event.target.enclosingNodeOrSelfWithClass("canvas-formatted-resource"); |
| if (resourceLinkElement) { |
| this._enableReplayInfoSidebar(true); |
| this._replayStateView.selectResource(resourceLinkElement.__resourceId); |
| event.consume(true); |
| return; |
| } |
| if (event.target.enclosingNodeOrSelfWithClass("webkit-html-resource-link")) |
| event.consume(false); |
| }, |
| |
| /** |
| * @param {Element} parent |
| * @param {string} className |
| * @param {string} title |
| * @param {function(this:WebInspector.CanvasProfileView)} clickCallback |
| */ |
| _createControlButton: function(parent, className, title, clickCallback) |
| { |
| var button = new WebInspector.StatusBarButton(title, className + " canvas-replay-button"); |
| parent.appendChild(button.element); |
| |
| button.makeLongClickEnabled(); |
| button.addEventListener("click", clickCallback, this); |
| button.addEventListener("longClickDown", clickCallback, this); |
| button.addEventListener("longClickPress", clickCallback, this); |
| }, |
| |
| _onReplayContextChanged: function() |
| { |
| var selectedContextId = this._replayContextSelector.selectedOption().value; |
| |
| /** |
| * @param {?CanvasAgent.ResourceState} resourceState |
| */ |
| function didReceiveResourceState(resourceState) |
| { |
| this._enableWaitIcon(false); |
| if (selectedContextId !== this._replayContextSelector.selectedOption().value) |
| return; |
| var imageURL = (resourceState && resourceState.imageURL) || ""; |
| this._replayImageElement.src = imageURL; |
| this._replayImageElement.style.visibility = imageURL ? "" : "hidden"; |
| } |
| |
| this._enableWaitIcon(true); |
| this._traceLogPlayer.getResourceState(selectedContextId, didReceiveResourceState.bind(this)); |
| }, |
| |
| /** |
| * @param {boolean} forward |
| */ |
| _onReplayStepClick: function(forward) |
| { |
| var selectedNode = this._logGrid.selectedNode; |
| if (!selectedNode) |
| return; |
| var nextNode = selectedNode; |
| do { |
| nextNode = forward ? nextNode.traverseNextNode(false) : nextNode.traversePreviousNode(false); |
| } while (nextNode && typeof nextNode.index !== "number"); |
| (nextNode || selectedNode).revealAndSelect(); |
| }, |
| |
| /** |
| * @param {boolean} forward |
| */ |
| _onReplayDrawingCallClick: function(forward) |
| { |
| var selectedNode = this._logGrid.selectedNode; |
| if (!selectedNode) |
| return; |
| var nextNode = selectedNode; |
| while (nextNode) { |
| var sibling = forward ? nextNode.nextSibling : nextNode.previousSibling; |
| if (sibling) { |
| nextNode = sibling; |
| if (nextNode.hasChildren || nextNode.call.isDrawingCall) |
| break; |
| } else { |
| nextNode = nextNode.parent; |
| if (!forward) |
| break; |
| } |
| } |
| if (!nextNode && forward) |
| this._onReplayLastStepClick(); |
| else |
| (nextNode || selectedNode).revealAndSelect(); |
| }, |
| |
| _onReplayFirstStepClick: function() |
| { |
| var firstNode = this._logGrid.rootNode().children[0]; |
| if (firstNode) |
| firstNode.revealAndSelect(); |
| }, |
| |
| _onReplayLastStepClick: function() |
| { |
| var lastNode = this._logGrid.rootNode().children.peekLast(); |
| if (!lastNode) |
| return; |
| while (lastNode.expanded) { |
| var lastChild = lastNode.children.peekLast(); |
| if (!lastChild) |
| break; |
| lastNode = lastChild; |
| } |
| lastNode.revealAndSelect(); |
| }, |
| |
| /** |
| * @param {boolean} enable |
| */ |
| _enableWaitIcon: function(enable) |
| { |
| this._spinnerIcon.enableStyleClass("hidden", !enable); |
| this._debugInfoElement.enableStyleClass("hidden", enable); |
| }, |
| |
| _replayTraceLog: function() |
| { |
| if (this._pendingReplayTraceLogEvent) |
| return; |
| var index = this._selectedCallIndex(); |
| if (index === -1 || index === this._lastReplayCallIndex) |
| return; |
| this._lastReplayCallIndex = index; |
| this._pendingReplayTraceLogEvent = true; |
| /** |
| * @param {CanvasAgent.ResourceState} resourceState |
| * @param {number} replayTime |
| */ |
| function didReplayTraceLog(resourceState, replayTime) |
| { |
| delete this._pendingReplayTraceLogEvent; |
| this._enableWaitIcon(false); |
| |
| this._debugInfoElement.textContent = "Replay time: " + Number.secondsToString(replayTime / 1000, true); |
| this._onReplayContextChanged(); |
| |
| if (index !== this._selectedCallIndex()) |
| this._replayTraceLog(); |
| } |
| this._enableWaitIcon(true); |
| this._traceLogPlayer.replayTraceLog(index, didReplayTraceLog.bind(this)); |
| }, |
| |
| /** |
| * @param {number} offset |
| */ |
| _requestTraceLog: function(offset) |
| { |
| /** |
| * @param {?CanvasAgent.TraceLog} traceLog |
| */ |
| function didReceiveTraceLog(traceLog) |
| { |
| this._enableWaitIcon(false); |
| if (!traceLog) |
| return; |
| var callNodes = []; |
| var calls = traceLog.calls; |
| var index = traceLog.startOffset; |
| for (var i = 0, n = calls.length; i < n; ++i) |
| callNodes.push(this._createCallNode(index++, calls[i])); |
| var contexts = traceLog.contexts; |
| for (var i = 0, n = contexts.length; i < n; ++i) { |
| var contextId = contexts[i].resourceId || ""; |
| var description = contexts[i].description || ""; |
| if (this._replayContexts[contextId]) |
| continue; |
| this._replayContexts[contextId] = true; |
| this._replayContextSelector.createOption(description, WebInspector.UIString("Show screenshot of this context's canvas."), contextId); |
| } |
| this._appendCallNodes(callNodes); |
| if (traceLog.alive) |
| setTimeout(this._requestTraceLog.bind(this, index), WebInspector.CanvasProfileView.TraceLogPollingInterval); |
| else |
| this._flattenSingleFrameNode(); |
| this._profile._updateCapturingStatus(traceLog); |
| this._onReplayLastStepClick(); // Automatically replay the last step. |
| } |
| this._enableWaitIcon(true); |
| this._traceLogPlayer.getTraceLog(offset, undefined, didReceiveTraceLog.bind(this)); |
| }, |
| |
| /** |
| * @return {number} |
| */ |
| _selectedCallIndex: function() |
| { |
| var node = this._logGrid.selectedNode; |
| return node ? this._peekLastRecursively(node).index : -1; |
| }, |
| |
| /** |
| * @param {!WebInspector.DataGridNode} node |
| * @return {!WebInspector.DataGridNode} |
| */ |
| _peekLastRecursively: function(node) |
| { |
| var lastChild; |
| while ((lastChild = node.children.peekLast())) |
| node = lastChild; |
| return node; |
| }, |
| |
| /** |
| * @param {!Array.<!WebInspector.DataGridNode>} callNodes |
| */ |
| _appendCallNodes: function(callNodes) |
| { |
| var rootNode = this._logGrid.rootNode(); |
| var frameNode = rootNode.children.peekLast(); |
| if (frameNode && this._peekLastRecursively(frameNode).call.isFrameEndCall) |
| frameNode = null; |
| for (var i = 0, n = callNodes.length; i < n; ++i) { |
| if (!frameNode) { |
| var index = rootNode.children.length; |
| var data = {}; |
| data[0] = ""; |
| data[1] = "Frame #" + (index + 1); |
| data[2] = ""; |
| frameNode = new WebInspector.DataGridNode(data); |
| frameNode.selectable = true; |
| rootNode.appendChild(frameNode); |
| } |
| var nextFrameCallIndex = i + 1; |
| while (nextFrameCallIndex < n && !callNodes[nextFrameCallIndex - 1].call.isFrameEndCall) |
| ++nextFrameCallIndex; |
| this._appendCallNodesToFrameNode(frameNode, callNodes, i, nextFrameCallIndex); |
| i = nextFrameCallIndex - 1; |
| frameNode = null; |
| } |
| }, |
| |
| /** |
| * @param {!WebInspector.DataGridNode} frameNode |
| * @param {!Array.<!WebInspector.DataGridNode>} callNodes |
| * @param {number} fromIndex |
| * @param {number} toIndex not inclusive |
| */ |
| _appendCallNodesToFrameNode: function(frameNode, callNodes, fromIndex, toIndex) |
| { |
| var self = this; |
| function appendDrawCallGroup() |
| { |
| var index = self._drawCallGroupsCount || 0; |
| var data = {}; |
| data[0] = ""; |
| data[1] = "Draw call group #" + (index + 1); |
| data[2] = ""; |
| var node = new WebInspector.DataGridNode(data); |
| node.selectable = true; |
| self._drawCallGroupsCount = index + 1; |
| frameNode.appendChild(node); |
| return node; |
| } |
| |
| function splitDrawCallGroup(drawCallGroup) |
| { |
| var splitIndex = 0; |
| var splitNode; |
| while ((splitNode = drawCallGroup.children[splitIndex])) { |
| if (splitNode.call.isDrawingCall) |
| break; |
| ++splitIndex; |
| } |
| var newDrawCallGroup = appendDrawCallGroup(); |
| var lastNode; |
| while ((lastNode = drawCallGroup.children[splitIndex + 1])) |
| newDrawCallGroup.appendChild(lastNode); |
| return newDrawCallGroup; |
| } |
| |
| var drawCallGroup = frameNode.children.peekLast(); |
| var groupHasDrawCall = false; |
| if (drawCallGroup) { |
| for (var i = 0, n = drawCallGroup.children.length; i < n; ++i) { |
| if (drawCallGroup.children[i].call.isDrawingCall) { |
| groupHasDrawCall = true; |
| break; |
| } |
| } |
| } else |
| drawCallGroup = appendDrawCallGroup(); |
| |
| for (var i = fromIndex; i < toIndex; ++i) { |
| var node = callNodes[i]; |
| drawCallGroup.appendChild(node); |
| if (node.call.isDrawingCall) { |
| if (groupHasDrawCall) |
| drawCallGroup = splitDrawCallGroup(drawCallGroup); |
| else |
| groupHasDrawCall = true; |
| } |
| } |
| }, |
| |
| /** |
| * @param {number} index |
| * @param {CanvasAgent.Call} call |
| * @return {!WebInspector.DataGridNode} |
| */ |
| _createCallNode: function(index, call) |
| { |
| var callViewElement = document.createElement("div"); |
| |
| var data = {}; |
| data[0] = index + 1; |
| data[1] = callViewElement; |
| data[2] = ""; |
| if (call.sourceURL) { |
| // FIXME(62725): stack trace line/column numbers are one-based. |
| var lineNumber = Math.max(0, call.lineNumber - 1) || 0; |
| var columnNumber = Math.max(0, call.columnNumber - 1) || 0; |
| data[2] = this._linkifier.linkifyLocation(call.sourceURL, lineNumber, columnNumber); |
| } |
| |
| callViewElement.createChild("span", "canvas-function-name").textContent = call.functionName || "context." + call.property; |
| |
| if (call.arguments) { |
| callViewElement.createTextChild("("); |
| for (var i = 0, n = call.arguments.length; i < n; ++i) { |
| var argument = /** @type {!CanvasAgent.CallArgument} */ (call.arguments[i]); |
| if (i) |
| callViewElement.createTextChild(", "); |
| var element = WebInspector.CanvasProfileDataGridHelper.createCallArgumentElement(argument); |
| element.__argumentIndex = i; |
| callViewElement.appendChild(element); |
| } |
| callViewElement.createTextChild(")"); |
| } else if (call.value) { |
| callViewElement.createTextChild(" = "); |
| callViewElement.appendChild(WebInspector.CanvasProfileDataGridHelper.createCallArgumentElement(call.value)); |
| } |
| |
| if (call.result) { |
| callViewElement.createTextChild(" => "); |
| callViewElement.appendChild(WebInspector.CanvasProfileDataGridHelper.createCallArgumentElement(call.result)); |
| } |
| |
| var node = new WebInspector.DataGridNode(data); |
| node.index = index; |
| node.selectable = true; |
| node.call = call; |
| return node; |
| }, |
| |
| _popoverAnchor: function(element, event) |
| { |
| var argumentElement = element.enclosingNodeOrSelfWithClass("canvas-call-argument"); |
| if (!argumentElement || argumentElement.__suppressPopover) |
| return null; |
| return argumentElement; |
| }, |
| |
| _resolveObjectForPopover: function(argumentElement, showCallback, objectGroupName) |
| { |
| /** |
| * @param {?Protocol.Error} error |
| * @param {RuntimeAgent.RemoteObject=} result |
| * @param {CanvasAgent.ResourceState=} resourceState |
| */ |
| function showObjectPopover(error, result, resourceState) |
| { |
| if (error) |
| return; |
| |
| // FIXME: handle resourceState also |
| if (!result) |
| return; |
| |
| this._popoverAnchorElement = argumentElement.cloneNode(true); |
| this._popoverAnchorElement.addStyleClass("canvas-popover-anchor"); |
| this._popoverAnchorElement.addStyleClass("source-frame-eval-expression"); |
| argumentElement.parentElement.appendChild(this._popoverAnchorElement); |
| |
| var diffLeft = this._popoverAnchorElement.boxInWindow().x - argumentElement.boxInWindow().x; |
| this._popoverAnchorElement.style.left = this._popoverAnchorElement.offsetLeft - diffLeft + "px"; |
| |
| showCallback(WebInspector.RemoteObject.fromPayload(result), false, this._popoverAnchorElement); |
| } |
| |
| var evalResult = argumentElement.__evalResult; |
| if (evalResult) |
| showObjectPopover.call(this, null, evalResult); |
| else { |
| var dataGridNode = this._logGrid.dataGridNodeFromNode(argumentElement); |
| if (!dataGridNode || typeof dataGridNode.index !== "number") { |
| this._popoverHelper.hidePopover(); |
| return; |
| } |
| var callIndex = dataGridNode.index; |
| var argumentIndex = argumentElement.__argumentIndex; |
| if (typeof argumentIndex !== "number") |
| argumentIndex = -1; |
| CanvasAgent.evaluateTraceLogCallArgument(this._traceLogId, callIndex, argumentIndex, objectGroupName, showObjectPopover.bind(this)); |
| } |
| }, |
| |
| /** |
| * @param {WebInspector.RemoteObject} object |
| * @return {string} |
| */ |
| _hexNumbersFormatter: function(object) |
| { |
| if (object.type === "number") { |
| // Show enum values in hex with min length of 4 (e.g. 0x0012). |
| var str = "0000" + Number(object.description).toString(16).toUpperCase(); |
| str = str.replace(/^0+(.{4,})$/, "$1"); |
| return "0x" + str; |
| } |
| return object.description || ""; |
| }, |
| |
| _onHidePopover: function() |
| { |
| if (this._popoverAnchorElement) { |
| this._popoverAnchorElement.remove() |
| delete this._popoverAnchorElement; |
| } |
| }, |
| |
| _flattenSingleFrameNode: function() |
| { |
| var rootNode = this._logGrid.rootNode(); |
| if (rootNode.children.length !== 1) |
| return; |
| var frameNode = rootNode.children[0]; |
| while (frameNode.children[0]) |
| rootNode.appendChild(frameNode.children[0]); |
| rootNode.removeChild(frameNode); |
| }, |
| |
| __proto__: WebInspector.View.prototype |
| } |
| |
| /** |
| * @constructor |
| * @extends {WebInspector.ProfileType} |
| */ |
| WebInspector.CanvasProfileType = function() |
| { |
| WebInspector.ProfileType.call(this, WebInspector.CanvasProfileType.TypeId, WebInspector.UIString("Capture Canvas Frame")); |
| this._nextProfileUid = 1; |
| this._recording = false; |
| this._lastProfileHeader = null; |
| |
| this._capturingModeSelector = new WebInspector.StatusBarComboBox(this._dispatchViewUpdatedEvent.bind(this)); |
| this._capturingModeSelector.element.title = WebInspector.UIString("Canvas capture mode."); |
| this._capturingModeSelector.createOption(WebInspector.UIString("Single Frame"), WebInspector.UIString("Capture a single canvas frame."), ""); |
| this._capturingModeSelector.createOption(WebInspector.UIString("Consecutive Frames"), WebInspector.UIString("Capture consecutive canvas frames."), "1"); |
| |
| /** @type {!Object.<string, Element>} */ |
| this._frameOptions = {}; |
| |
| /** @type {!Object.<string, boolean>} */ |
| this._framesWithCanvases = {}; |
| |
| this._frameSelector = new WebInspector.StatusBarComboBox(this._dispatchViewUpdatedEvent.bind(this)); |
| this._frameSelector.element.title = WebInspector.UIString("Frame containing the canvases to capture."); |
| this._frameSelector.element.addStyleClass("hidden"); |
| WebInspector.runtimeModel.contextLists().forEach(this._addFrame, this); |
| WebInspector.runtimeModel.addEventListener(WebInspector.RuntimeModel.Events.FrameExecutionContextListAdded, this._frameAdded, this); |
| WebInspector.runtimeModel.addEventListener(WebInspector.RuntimeModel.Events.FrameExecutionContextListRemoved, this._frameRemoved, this); |
| |
| this._dispatcher = new WebInspector.CanvasDispatcher(this); |
| this._canvasAgentEnabled = false; |
| |
| this._decorationElement = document.createElement("div"); |
| this._decorationElement.className = "profile-canvas-decoration"; |
| this._updateDecorationElement(); |
| } |
| |
| WebInspector.CanvasProfileType.TypeId = "CANVAS_PROFILE"; |
| |
| WebInspector.CanvasProfileType.prototype = { |
| get statusBarItems() |
| { |
| return [this._capturingModeSelector.element, this._frameSelector.element]; |
| }, |
| |
| get buttonTooltip() |
| { |
| if (this._isSingleFrameMode()) |
| return WebInspector.UIString("Capture next canvas frame."); |
| else |
| return this._recording ? WebInspector.UIString("Stop capturing canvas frames.") : WebInspector.UIString("Start capturing canvas frames."); |
| }, |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| buttonClicked: function() |
| { |
| if (!this._canvasAgentEnabled) |
| return false; |
| if (this._recording) { |
| this._recording = false; |
| this._stopFrameCapturing(); |
| } else if (this._isSingleFrameMode()) { |
| this._recording = false; |
| this._runSingleFrameCapturing(); |
| } else { |
| this._recording = true; |
| this._startFrameCapturing(); |
| } |
| return this._recording; |
| }, |
| |
| _runSingleFrameCapturing: function() |
| { |
| var frameId = this._selectedFrameId(); |
| CanvasAgent.captureFrame(frameId, this._didStartCapturingFrame.bind(this, frameId)); |
| }, |
| |
| _startFrameCapturing: function() |
| { |
| var frameId = this._selectedFrameId(); |
| CanvasAgent.startCapturing(frameId, this._didStartCapturingFrame.bind(this, frameId)); |
| }, |
| |
| _stopFrameCapturing: function() |
| { |
| if (!this._lastProfileHeader) |
| return; |
| var profileHeader = this._lastProfileHeader; |
| var traceLogId = profileHeader.traceLogId(); |
| this._lastProfileHeader = null; |
| function didStopCapturing() |
| { |
| profileHeader._updateCapturingStatus(); |
| } |
| CanvasAgent.stopCapturing(traceLogId, didStopCapturing.bind(this)); |
| }, |
| |
| /** |
| * @param {string|undefined} frameId |
| * @param {?Protocol.Error} error |
| * @param {CanvasAgent.TraceLogId} traceLogId |
| */ |
| _didStartCapturingFrame: function(frameId, error, traceLogId) |
| { |
| if (error || this._lastProfileHeader && this._lastProfileHeader.traceLogId() === traceLogId) |
| return; |
| var profileHeader = new WebInspector.CanvasProfileHeader(this, WebInspector.UIString("Trace Log %d", this._nextProfileUid), this._nextProfileUid, traceLogId, frameId); |
| ++this._nextProfileUid; |
| this._lastProfileHeader = profileHeader; |
| this.addProfile(profileHeader); |
| profileHeader._updateCapturingStatus(); |
| }, |
| |
| get treeItemTitle() |
| { |
| return WebInspector.UIString("CANVAS PROFILE"); |
| }, |
| |
| get description() |
| { |
| return WebInspector.UIString("Canvas calls instrumentation"); |
| }, |
| |
| /** |
| * @override |
| * @return {Element} |
| */ |
| decorationElement: function() |
| { |
| return this._decorationElement; |
| }, |
| |
| /** |
| * @override |
| */ |
| _reset: function() |
| { |
| WebInspector.ProfileType.prototype._reset.call(this); |
| this._nextProfileUid = 1; |
| }, |
| |
| /** |
| * @override |
| * @param {!WebInspector.ProfileHeader} profile |
| */ |
| removeProfile: function(profile) |
| { |
| WebInspector.ProfileType.prototype.removeProfile.call(this, profile); |
| if (this._recording && profile === this._lastProfileHeader) |
| this._recording = false; |
| }, |
| |
| setRecordingProfile: function(isProfiling) |
| { |
| this._recording = isProfiling; |
| }, |
| |
| /** |
| * @override |
| * @param {string=} title |
| * @return {!WebInspector.ProfileHeader} |
| */ |
| createTemporaryProfile: function(title) |
| { |
| title = title || WebInspector.UIString("Capturing\u2026"); |
| return new WebInspector.CanvasProfileHeader(this, title); |
| }, |
| |
| /** |
| * @override |
| * @param {ProfilerAgent.ProfileHeader} profile |
| * @return {!WebInspector.ProfileHeader} |
| */ |
| createProfile: function(profile) |
| { |
| return new WebInspector.CanvasProfileHeader(this, profile.title, -1); |
| }, |
| |
| /** |
| * @param {boolean=} forcePageReload |
| */ |
| _updateDecorationElement: function(forcePageReload) |
| { |
| this._decorationElement.removeChildren(); |
| this._decorationElement.createChild("div", "warning-icon-small"); |
| this._decorationElement.appendChild(document.createTextNode(this._canvasAgentEnabled ? WebInspector.UIString("Canvas Profiler is enabled.") : WebInspector.UIString("Canvas Profiler is disabled."))); |
| var button = this._decorationElement.createChild("button"); |
| button.type = "button"; |
| button.textContent = this._canvasAgentEnabled ? WebInspector.UIString("Disable") : WebInspector.UIString("Enable"); |
| button.addEventListener("click", this._onProfilerEnableButtonClick.bind(this, !this._canvasAgentEnabled), false); |
| |
| if (forcePageReload) { |
| if (this._canvasAgentEnabled) { |
| /** |
| * @param {?Protocol.Error} error |
| * @param {boolean} result |
| */ |
| function hasUninstrumentedCanvasesCallback(error, result) |
| { |
| if (error || result) |
| PageAgent.reload(); |
| } |
| CanvasAgent.hasUninstrumentedCanvases(hasUninstrumentedCanvasesCallback.bind(this)); |
| } else { |
| for (var frameId in this._framesWithCanvases) { |
| if (this._framesWithCanvases.hasOwnProperty(frameId)) { |
| PageAgent.reload(); |
| break; |
| } |
| } |
| } |
| } |
| }, |
| |
| /** |
| * @param {boolean} enable |
| */ |
| _onProfilerEnableButtonClick: function(enable) |
| { |
| if (this._canvasAgentEnabled === enable) |
| return; |
| /** |
| * @param {?Protocol.Error} error |
| */ |
| function callback(error) |
| { |
| if (error) |
| return; |
| this._canvasAgentEnabled = enable; |
| this._updateDecorationElement(true); |
| this._dispatchViewUpdatedEvent(); |
| } |
| if (enable) |
| CanvasAgent.enable(callback.bind(this)); |
| else |
| CanvasAgent.disable(callback.bind(this)); |
| }, |
| |
| /** |
| * @return {boolean} |
| */ |
| _isSingleFrameMode: function() |
| { |
| return !this._capturingModeSelector.selectedOption().value; |
| }, |
| |
| /** |
| * @param {WebInspector.Event} event |
| */ |
| _frameAdded: function(event) |
| { |
| var contextList = /** @type {WebInspector.FrameExecutionContextList} */ (event.data); |
| this._addFrame(contextList); |
| }, |
| |
| /** |
| * @param {WebInspector.FrameExecutionContextList} contextList |
| */ |
| _addFrame: function(contextList) |
| { |
| var frameId = contextList.frameId; |
| var option = document.createElement("option"); |
| option.text = contextList.displayName; |
| option.title = contextList.url; |
| option.value = frameId; |
| |
| this._frameOptions[frameId] = option; |
| |
| if (this._framesWithCanvases[frameId]) { |
| this._frameSelector.addOption(option); |
| this._dispatchViewUpdatedEvent(); |
| } |
| }, |
| |
| /** |
| * @param {WebInspector.Event} event |
| */ |
| _frameRemoved: function(event) |
| { |
| var contextList = /** @type {WebInspector.FrameExecutionContextList} */ (event.data); |
| var frameId = contextList.frameId; |
| var option = this._frameOptions[frameId]; |
| if (option && this._framesWithCanvases[frameId]) { |
| this._frameSelector.removeOption(option); |
| this._dispatchViewUpdatedEvent(); |
| } |
| delete this._frameOptions[frameId]; |
| delete this._framesWithCanvases[frameId]; |
| }, |
| |
| /** |
| * @param {string} frameId |
| */ |
| _contextCreated: function(frameId) |
| { |
| if (this._framesWithCanvases[frameId]) |
| return; |
| this._framesWithCanvases[frameId] = true; |
| var option = this._frameOptions[frameId]; |
| if (option) { |
| this._frameSelector.addOption(option); |
| this._dispatchViewUpdatedEvent(); |
| } |
| }, |
| |
| /** |
| * @param {NetworkAgent.FrameId=} frameId |
| * @param {CanvasAgent.TraceLogId=} traceLogId |
| */ |
| _traceLogsRemoved: function(frameId, traceLogId) |
| { |
| var sidebarElementsToDelete = []; |
| var sidebarElements = /** @type {!Array.<WebInspector.ProfileSidebarTreeElement>} */ ((this.treeElement && this.treeElement.children) || []); |
| for (var i = 0, n = sidebarElements.length; i < n; ++i) { |
| var header = /** @type {WebInspector.CanvasProfileHeader} */ (sidebarElements[i].profile); |
| if (!header) |
| continue; |
| if (frameId && frameId !== header.frameId()) |
| continue; |
| if (traceLogId && traceLogId !== header.traceLogId()) |
| continue; |
| sidebarElementsToDelete.push(sidebarElements[i]); |
| } |
| for (var i = 0, n = sidebarElementsToDelete.length; i < n; ++i) |
| sidebarElementsToDelete[i].ondelete(); |
| }, |
| |
| /** |
| * @return {string|undefined} |
| */ |
| _selectedFrameId: function() |
| { |
| var option = this._frameSelector.selectedOption(); |
| return option ? option.value : undefined; |
| }, |
| |
| _dispatchViewUpdatedEvent: function() |
| { |
| this._frameSelector.element.enableStyleClass("hidden", this._frameSelector.size() <= 1); |
| this.dispatchEventToListeners(WebInspector.ProfileType.Events.ViewUpdated); |
| }, |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| isInstantProfile: function() |
| { |
| return this._isSingleFrameMode(); |
| }, |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| isEnabled: function() |
| { |
| return this._canvasAgentEnabled; |
| }, |
| |
| __proto__: WebInspector.ProfileType.prototype |
| } |
| |
| /** |
| * @constructor |
| * @implements {CanvasAgent.Dispatcher} |
| * @param {WebInspector.CanvasProfileType} profileType |
| */ |
| WebInspector.CanvasDispatcher = function(profileType) |
| { |
| this._profileType = profileType; |
| InspectorBackend.registerCanvasDispatcher(this); |
| } |
| |
| WebInspector.CanvasDispatcher.prototype = { |
| /** |
| * @param {string} frameId |
| */ |
| contextCreated: function(frameId) |
| { |
| this._profileType._contextCreated(frameId); |
| }, |
| |
| /** |
| * @param {NetworkAgent.FrameId=} frameId |
| * @param {CanvasAgent.TraceLogId=} traceLogId |
| */ |
| traceLogsRemoved: function(frameId, traceLogId) |
| { |
| this._profileType._traceLogsRemoved(frameId, traceLogId); |
| } |
| } |
| |
| /** |
| * @constructor |
| * @extends {WebInspector.ProfileHeader} |
| * @param {!WebInspector.CanvasProfileType} type |
| * @param {string} title |
| * @param {number=} uid |
| * @param {CanvasAgent.TraceLogId=} traceLogId |
| * @param {NetworkAgent.FrameId=} frameId |
| */ |
| WebInspector.CanvasProfileHeader = function(type, title, uid, traceLogId, frameId) |
| { |
| WebInspector.ProfileHeader.call(this, type, title, uid); |
| /** @type {CanvasAgent.TraceLogId} */ |
| this._traceLogId = traceLogId || ""; |
| this._frameId = frameId; |
| this._alive = true; |
| this._traceLogSize = 0; |
| this._traceLogPlayer = traceLogId ? new WebInspector.CanvasTraceLogPlayerProxy(traceLogId) : null; |
| } |
| |
| WebInspector.CanvasProfileHeader.prototype = { |
| /** |
| * @return {CanvasAgent.TraceLogId} |
| */ |
| traceLogId: function() |
| { |
| return this._traceLogId; |
| }, |
| |
| /** |
| * @return {WebInspector.CanvasTraceLogPlayerProxy} |
| */ |
| traceLogPlayer: function() |
| { |
| return this._traceLogPlayer; |
| }, |
| |
| /** |
| * @return {NetworkAgent.FrameId|undefined} |
| */ |
| frameId: function() |
| { |
| return this._frameId; |
| }, |
| |
| /** |
| * @override |
| * @return {WebInspector.ProfileSidebarTreeElement} |
| */ |
| createSidebarTreeElement: function() |
| { |
| return new WebInspector.ProfileSidebarTreeElement(this, WebInspector.UIString("Trace Log %d"), "profile-sidebar-tree-item"); |
| }, |
| |
| /** |
| * @override |
| * @param {WebInspector.ProfilesPanel} profilesPanel |
| */ |
| createView: function(profilesPanel) |
| { |
| return new WebInspector.CanvasProfileView(this); |
| }, |
| |
| /** |
| * @override |
| */ |
| dispose: function() |
| { |
| if (this._traceLogPlayer) |
| this._traceLogPlayer.dispose(); |
| clearTimeout(this._requestStatusTimer); |
| this._alive = false; |
| }, |
| |
| /** |
| * @param {CanvasAgent.TraceLog=} traceLog |
| */ |
| _updateCapturingStatus: function(traceLog) |
| { |
| if (!this.sidebarElement || !this._traceLogId) |
| return; |
| |
| if (traceLog) { |
| this._alive = traceLog.alive; |
| this._traceLogSize = traceLog.totalAvailableCalls; |
| } |
| |
| this.sidebarElement.subtitle = this._alive ? WebInspector.UIString("Capturing\u2026 %d calls", this._traceLogSize) : WebInspector.UIString("Captured %d calls", this._traceLogSize); |
| this.sidebarElement.wait = this._alive; |
| |
| if (this._alive) { |
| clearTimeout(this._requestStatusTimer); |
| this._requestStatusTimer = setTimeout(this._requestCapturingStatus.bind(this), WebInspector.CanvasProfileView.TraceLogPollingInterval); |
| } |
| }, |
| |
| _requestCapturingStatus: function() |
| { |
| /** |
| * @param {?CanvasAgent.TraceLog} traceLog |
| */ |
| function didReceiveTraceLog(traceLog) |
| { |
| if (!traceLog) |
| return; |
| this._alive = traceLog.alive; |
| this._traceLogSize = traceLog.totalAvailableCalls; |
| this._updateCapturingStatus(); |
| } |
| this._traceLogPlayer.getTraceLog(0, 0, didReceiveTraceLog.bind(this)); |
| }, |
| |
| __proto__: WebInspector.ProfileHeader.prototype |
| } |
| |
| WebInspector.CanvasProfileDataGridHelper = { |
| /** |
| * @param {!CanvasAgent.CallArgument} callArgument |
| * @return {!Element} |
| */ |
| createCallArgumentElement: function(callArgument) |
| { |
| if (callArgument.enumName) |
| return WebInspector.CanvasProfileDataGridHelper.createEnumValueElement(callArgument.enumName, +callArgument.description); |
| var element = document.createElement("span"); |
| element.className = "canvas-call-argument"; |
| var description = callArgument.description; |
| if (callArgument.type === "string") { |
| const maxStringLength = 150; |
| element.createTextChild("\""); |
| element.createChild("span", "canvas-formatted-string").textContent = description.trimMiddle(maxStringLength); |
| element.createTextChild("\""); |
| element.__suppressPopover = (description.length <= maxStringLength && !/[\r\n]/.test(description)); |
| if (!element.__suppressPopover) |
| element.__evalResult = WebInspector.RemoteObject.fromPrimitiveValue(description); |
| } else { |
| var type = callArgument.subtype || callArgument.type; |
| if (type) { |
| element.addStyleClass("canvas-formatted-" + type); |
| if (["null", "undefined", "boolean", "number"].indexOf(type) >= 0) |
| element.__suppressPopover = true; |
| } |
| element.textContent = description; |
| if (callArgument.remoteObject) |
| element.__evalResult = WebInspector.RemoteObject.fromPayload(callArgument.remoteObject); |
| } |
| if (callArgument.resourceId) { |
| element.addStyleClass("canvas-formatted-resource"); |
| element.__resourceId = callArgument.resourceId; |
| } |
| return element; |
| }, |
| |
| /** |
| * @param {string} enumName |
| * @param {number} enumValue |
| * @return {!Element} |
| */ |
| createEnumValueElement: function(enumName, enumValue) |
| { |
| var element = document.createElement("span"); |
| element.className = "canvas-call-argument canvas-formatted-number"; |
| element.textContent = enumName; |
| element.__evalResult = WebInspector.RemoteObject.fromPrimitiveValue(enumValue); |
| return element; |
| } |
| } |
| |
| /** |
| * @extends {WebInspector.Object} |
| * @constructor |
| * @param {CanvasAgent.TraceLogId} traceLogId |
| */ |
| WebInspector.CanvasTraceLogPlayerProxy = function(traceLogId) |
| { |
| this._traceLogId = traceLogId; |
| /** @type {!Object.<string, !CanvasAgent.ResourceState>} */ |
| this._currentResourceStates = {}; |
| /** @type {?CanvasAgent.ResourceId} */ |
| this._defaultResourceId = null; |
| } |
| |
| /** @enum {string} */ |
| WebInspector.CanvasTraceLogPlayerProxy.Events = { |
| CanvasTraceLogReceived: "CanvasTraceLogReceived", |
| CanvasReplayStateChanged: "CanvasReplayStateChanged", |
| CanvasResourceStateReceived: "CanvasResourceStateReceived", |
| } |
| |
| WebInspector.CanvasTraceLogPlayerProxy.prototype = { |
| /** |
| * @param {number|undefined} startOffset |
| * @param {number|undefined} maxLength |
| * @param {function(?CanvasAgent.TraceLog):void} userCallback |
| */ |
| getTraceLog: function(startOffset, maxLength, userCallback) |
| { |
| /** |
| * @param {?Protocol.Error} error |
| * @param {CanvasAgent.TraceLog} traceLog |
| */ |
| function callback(error, traceLog) |
| { |
| if (error || !traceLog) { |
| userCallback(null); |
| return; |
| } |
| userCallback(traceLog); |
| this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasTraceLogReceived, traceLog); |
| } |
| CanvasAgent.getTraceLog(this._traceLogId, startOffset, maxLength, callback.bind(this)); |
| }, |
| |
| dispose: function() |
| { |
| this._currentResourceStates = {}; |
| CanvasAgent.dropTraceLog(this._traceLogId); |
| this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasReplayStateChanged); |
| }, |
| |
| /** |
| * @param {?CanvasAgent.ResourceId} resourceId |
| * @param {function(?CanvasAgent.ResourceState):void} userCallback |
| */ |
| getResourceState: function(resourceId, userCallback) |
| { |
| resourceId = resourceId || this._defaultResourceId; |
| if (!resourceId) { |
| userCallback(null); // Has not been replayed yet. |
| return; |
| } |
| if (this._currentResourceStates[resourceId]) { |
| userCallback(this._currentResourceStates[resourceId]); |
| return; |
| } |
| /** |
| * @param {?Protocol.Error} error |
| * @param {CanvasAgent.ResourceState} resourceState |
| */ |
| function callback(error, resourceState) |
| { |
| if (error || !resourceState) { |
| userCallback(null); |
| return; |
| } |
| this._currentResourceStates[resourceId] = resourceState; |
| userCallback(resourceState); |
| this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasResourceStateReceived, resourceState); |
| } |
| CanvasAgent.getResourceState(this._traceLogId, resourceId, callback.bind(this)); |
| }, |
| |
| /** |
| * @param {number} index |
| * @param {function(?CanvasAgent.ResourceState, number):void} userCallback |
| */ |
| replayTraceLog: function(index, userCallback) |
| { |
| /** |
| * @param {?Protocol.Error} error |
| * @param {CanvasAgent.ResourceState} resourceState |
| * @param {number} replayTime |
| */ |
| function callback(error, resourceState, replayTime) |
| { |
| this._currentResourceStates = {}; |
| if (error || !resourceState) { |
| resourceState = null; |
| userCallback(null, replayTime); |
| } else { |
| this._defaultResourceId = resourceState.id; |
| this._currentResourceStates[resourceState.id] = resourceState; |
| userCallback(resourceState, replayTime); |
| } |
| this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasReplayStateChanged); |
| if (resourceState) |
| this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasResourceStateReceived, resourceState); |
| } |
| CanvasAgent.replayTraceLog(this._traceLogId, index, callback.bind(this)); |
| }, |
| |
| clearResourceStates: function() |
| { |
| this._currentResourceStates = {}; |
| this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasReplayStateChanged); |
| }, |
| |
| __proto__: WebInspector.Object.prototype |
| } |