blob: 56dd4e6cc446cceac4aeeaa842ba94dac99d6028 [file] [log] [blame]
/*
* 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("img", "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 {PageAgent.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 {PageAgent.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 {PageAgent.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 {PageAgent.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
}