1 /* 2 * Copyright (C) 2012 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 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 13 * distribution. 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. 17 * 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. 29 */ 30 31 /** 32 * @constructor 33 * @extends {WebInspector.View} 34 * @param {!WebInspector.CanvasProfileHeader} profile 35 */ 36 WebInspector.CanvasProfileView = function(profile) 37 { 38 WebInspector.View.call(this); 39 this.registerRequiredCSS("canvasProfiler.css"); 40 this._profile = profile; 41 this._traceLogId = profile.traceLogId(); 42 this.element.addStyleClass("canvas-profile-view"); 43 44 this._linkifier = new WebInspector.Linkifier(); 45 this._splitView = new WebInspector.SplitView(false, "canvasProfileViewSplitLocation", 300); 46 47 var replayImageContainer = this._splitView.firstElement(); 48 replayImageContainer.id = "canvas-replay-image-container"; 49 this._replayImageElement = replayImageContainer.createChild("image", "canvas-replay-image"); 50 this._debugInfoElement = replayImageContainer.createChild("div", "canvas-debug-info hidden"); 51 this._spinnerIcon = replayImageContainer.createChild("img", "canvas-spinner-icon hidden"); 52 53 var replayInfoContainer = this._splitView.secondElement(); 54 var controlsContainer = replayInfoContainer.createChild("div", "status-bar"); 55 var logGridContainer = replayInfoContainer.createChild("div", "canvas-replay-log"); 56 57 this._createControlButton(controlsContainer, "canvas-replay-first-step", WebInspector.UIString("First call."), this._onReplayFirstStepClick.bind(this)); 58 this._createControlButton(controlsContainer, "canvas-replay-prev-step", WebInspector.UIString("Previous call."), this._onReplayStepClick.bind(this, false)); 59 this._createControlButton(controlsContainer, "canvas-replay-next-step", WebInspector.UIString("Next call."), this._onReplayStepClick.bind(this, true)); 60 this._createControlButton(controlsContainer, "canvas-replay-prev-draw", WebInspector.UIString("Previous drawing call."), this._onReplayDrawingCallClick.bind(this, false)); 61 this._createControlButton(controlsContainer, "canvas-replay-next-draw", WebInspector.UIString("Next drawing call."), this._onReplayDrawingCallClick.bind(this, true)); 62 this._createControlButton(controlsContainer, "canvas-replay-last-step", WebInspector.UIString("Last call."), this._onReplayLastStepClick.bind(this)); 63 64 this._replayContextSelector = new WebInspector.StatusBarComboBox(this._onReplayContextChanged.bind(this)); 65 this._replayContextSelector.createOption("<screenshot auto>", WebInspector.UIString("Show screenshot of the last replayed resource."), ""); 66 controlsContainer.appendChild(this._replayContextSelector.element); 67 68 /** @type {!Object.<string, boolean>} */ 69 this._replayContexts = {}; 70 /** @type {!Object.<string, CanvasAgent.ResourceState>} */ 71 this._currentResourceStates = {}; 72 73 var columns = [ 74 {title: "#", sortable: true, width: "5%"}, 75 {title: WebInspector.UIString("Call"), sortable: true, width: "75%", disclosure: true}, 76 {title: WebInspector.UIString("Location"), sortable: true, width: "20%"} 77 ]; 78 79 this._logGrid = new WebInspector.DataGrid(columns); 80 this._logGrid.element.addStyleClass("fill"); 81 this._logGrid.show(logGridContainer); 82 this._logGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._replayTraceLog.bind(this)); 83 84 this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.element, this._popoverAnchor.bind(this), this._resolveObjectForPopover.bind(this), this._onHidePopover.bind(this), true); 85 86 this._splitView.show(this.element); 87 this._requestTraceLog(0); 88 } 89 90 /** 91 * @const 92 * @type {number} 93 */ 94 WebInspector.CanvasProfileView.TraceLogPollingInterval = 500; 95 96 WebInspector.CanvasProfileView.prototype = { 97 dispose: function() 98 { 99 this._linkifier.reset(); 100 }, 101 102 get statusBarItems() 103 { 104 return []; 105 }, 106 107 get profile() 108 { 109 return this._profile; 110 }, 111 112 /** 113 * @override 114 * @return {Array.<Element>} 115 */ 116 elementsToRestoreScrollPositionsFor: function() 117 { 118 return [this._logGrid.scrollContainer]; 119 }, 120 121 /** 122 * @param {Element} parent 123 * @param {string} className 124 * @param {string} title 125 * @param {function(this:WebInspector.CanvasProfileView)} clickCallback 126 */ 127 _createControlButton: function(parent, className, title, clickCallback) 128 { 129 var button = new WebInspector.StatusBarButton(title, className); 130 parent.appendChild(button.element); 131 132 button.makeLongClickEnabled(); 133 button.addEventListener("click", clickCallback, this); 134 button.addEventListener("longClickDown", clickCallback, this); 135 button.addEventListener("longClickPress", clickCallback, this); 136 }, 137 138 _onReplayContextChanged: function() 139 { 140 /** 141 * @param {?Protocol.Error} error 142 * @param {CanvasAgent.ResourceState} resourceState 143 */ 144 function didReceiveResourceState(error, resourceState) 145 { 146 const emptyTransparentImageURL = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; 147 this._enableWaitIcon(false); 148 if (error) 149 return; 150 151 this._currentResourceStates[resourceState.id] = resourceState; 152 153 var selectedContextId = this._replayContextSelector.selectedOption().value; 154 if (selectedContextId === resourceState.id) 155 this._replayImageElement.src = resourceState.imageURL || emptyTransparentImageURL; 156 } 157 158 var selectedContextId = this._replayContextSelector.selectedOption().value || "auto"; 159 var resourceState = this._currentResourceStates[selectedContextId]; 160 if (resourceState) 161 this._replayImageElement.src = resourceState.imageURL; 162 else { 163 this._enableWaitIcon(true); 164 CanvasAgent.getResourceState(this._traceLogId, selectedContextId, didReceiveResourceState.bind(this)); 165 } 166 }, 167 168 /** 169 * @param {boolean} forward 170 */ 171 _onReplayStepClick: function(forward) 172 { 173 var selectedNode = this._logGrid.selectedNode; 174 if (!selectedNode) 175 return; 176 var nextNode = selectedNode; 177 do { 178 nextNode = forward ? nextNode.traverseNextNode(false) : nextNode.traversePreviousNode(false); 179 } while (nextNode && typeof nextNode.index !== "number"); 180 (nextNode || selectedNode).revealAndSelect(); 181 }, 182 183 /** 184 * @param {boolean} forward 185 */ 186 _onReplayDrawingCallClick: function(forward) 187 { 188 var selectedNode = this._logGrid.selectedNode; 189 if (!selectedNode) 190 return; 191 var nextNode = selectedNode; 192 while (nextNode) { 193 var sibling = forward ? nextNode.nextSibling : nextNode.previousSibling; 194 if (sibling) { 195 nextNode = sibling; 196 if (nextNode.hasChildren || nextNode.call.isDrawingCall) 197 break; 198 } else { 199 nextNode = nextNode.parent; 200 if (!forward) 201 break; 202 } 203 } 204 if (!nextNode && forward) 205 this._onReplayLastStepClick(); 206 else 207 (nextNode || selectedNode).revealAndSelect(); 208 }, 209 210 _onReplayFirstStepClick: function() 211 { 212 var firstNode = this._logGrid.rootNode().children[0]; 213 if (firstNode) 214 firstNode.revealAndSelect(); 215 }, 216 217 _onReplayLastStepClick: function() 218 { 219 var lastNode = this._logGrid.rootNode().children.peekLast(); 220 if (!lastNode) 221 return; 222 while (lastNode.expanded) { 223 var lastChild = lastNode.children.peekLast(); 224 if (!lastChild) 225 break; 226 lastNode = lastChild; 227 } 228 lastNode.revealAndSelect(); 229 }, 230 231 /** 232 * @param {boolean} enable 233 */ 234 _enableWaitIcon: function(enable) 235 { 236 this._spinnerIcon.enableStyleClass("hidden", !enable); 237 this._debugInfoElement.enableStyleClass("hidden", enable); 238 }, 239 240 _replayTraceLog: function() 241 { 242 if (this._pendingReplayTraceLogEvent) 243 return; 244 var index = this._selectedCallIndex(); 245 if (index === -1 || index === this._lastReplayCallIndex) 246 return; 247 this._lastReplayCallIndex = index; 248 this._pendingReplayTraceLogEvent = true; 249 /** 250 * @param {?Protocol.Error} error 251 * @param {CanvasAgent.ResourceState} resourceState 252 * @param {number} replayTime 253 */ 254 function didReplayTraceLog(error, resourceState, replayTime) 255 { 256 delete this._pendingReplayTraceLogEvent; 257 258 this._enableWaitIcon(false); 259 260 if (!error) { 261 this._currentResourceStates = {}; 262 this._currentResourceStates["auto"] = resourceState; 263 this._currentResourceStates[resourceState.id] = resourceState; 264 265 this._debugInfoElement.textContent = "Replay time: " + Number(replayTime).toFixed() + "ms"; 266 this._onReplayContextChanged(); 267 } 268 269 if (index !== this._selectedCallIndex()) 270 this._replayTraceLog(); 271 } 272 this._enableWaitIcon(true); 273 CanvasAgent.replayTraceLog(this._traceLogId, index, didReplayTraceLog.bind(this)); 274 }, 275 276 /** 277 * @param {?Protocol.Error} error 278 * @param {CanvasAgent.TraceLog} traceLog 279 */ 280 _didReceiveTraceLog: function(error, traceLog) 281 { 282 this._enableWaitIcon(false); 283 if (error || !traceLog) 284 return; 285 var callNodes = []; 286 var calls = traceLog.calls; 287 var index = traceLog.startOffset; 288 for (var i = 0, n = calls.length; i < n; ++i) 289 callNodes.push(this._createCallNode(index++, calls[i])); 290 var contexts = traceLog.contexts; 291 for (var i = 0, n = contexts.length; i < n; ++i) { 292 var contextId = contexts[i].resourceId || ""; 293 var description = contexts[i].description || ""; 294 if (this._replayContexts[contextId]) 295 continue; 296 this._replayContexts[contextId] = true; 297 this._replayContextSelector.createOption(description, WebInspector.UIString("Show screenshot of this context's canvas."), contextId); 298 } 299 this._appendCallNodes(callNodes); 300 if (traceLog.alive) 301 setTimeout(this._requestTraceLog.bind(this, index), WebInspector.CanvasProfileView.TraceLogPollingInterval); 302 else 303 this._flattenSingleFrameNode(); 304 this._profile._updateCapturingStatus(traceLog); 305 this._onReplayLastStepClick(); // Automatically replay the last step. 306 }, 307 308 /** 309 * @param {number} offset 310 */ 311 _requestTraceLog: function(offset) 312 { 313 this._enableWaitIcon(true); 314 CanvasAgent.getTraceLog(this._traceLogId, offset, undefined, this._didReceiveTraceLog.bind(this)); 315 }, 316 317 /** 318 * @return {number} 319 */ 320 _selectedCallIndex: function() 321 { 322 var node = this._logGrid.selectedNode; 323 return node ? this._peekLastRecursively(node).index : -1; 324 }, 325 326 /** 327 * @param {!WebInspector.DataGridNode} node 328 * @return {!WebInspector.DataGridNode} 329 */ 330 _peekLastRecursively: function(node) 331 { 332 var lastChild; 333 while ((lastChild = node.children.peekLast())) 334 node = lastChild; 335 return node; 336 }, 337 338 /** 339 * @param {!Array.<!WebInspector.DataGridNode>} callNodes 340 */ 341 _appendCallNodes: function(callNodes) 342 { 343 var rootNode = this._logGrid.rootNode(); 344 var frameNode = rootNode.children.peekLast(); 345 if (frameNode && this._peekLastRecursively(frameNode).call.isFrameEndCall) 346 frameNode = null; 347 for (var i = 0, n = callNodes.length; i < n; ++i) { 348 if (!frameNode) { 349 var index = rootNode.children.length; 350 var data = {}; 351 data[0] = ""; 352 data[1] = "Frame #" + (index + 1); 353 data[2] = ""; 354 frameNode = new WebInspector.DataGridNode(data); 355 frameNode.selectable = true; 356 rootNode.appendChild(frameNode); 357 } 358 var nextFrameCallIndex = i + 1; 359 while (nextFrameCallIndex < n && !callNodes[nextFrameCallIndex - 1].call.isFrameEndCall) 360 ++nextFrameCallIndex; 361 this._appendCallNodesToFrameNode(frameNode, callNodes, i, nextFrameCallIndex); 362 i = nextFrameCallIndex - 1; 363 frameNode = null; 364 } 365 }, 366 367 /** 368 * @param {!WebInspector.DataGridNode} frameNode 369 * @param {!Array.<!WebInspector.DataGridNode>} callNodes 370 * @param {number} fromIndex 371 * @param {number} toIndex not inclusive 372 */ 373 _appendCallNodesToFrameNode: function(frameNode, callNodes, fromIndex, toIndex) 374 { 375 var self = this; 376 function appendDrawCallGroup() 377 { 378 var index = self._drawCallGroupsCount || 0; 379 var data = {}; 380 data[0] = ""; 381 data[1] = "Draw call group #" + (index + 1); 382 data[2] = ""; 383 var node = new WebInspector.DataGridNode(data); 384 node.selectable = true; 385 self._drawCallGroupsCount = index + 1; 386 frameNode.appendChild(node); 387 return node; 388 } 389 390 function splitDrawCallGroup(drawCallGroup) 391 { 392 var splitIndex = 0; 393 var splitNode; 394 while ((splitNode = drawCallGroup.children[splitIndex])) { 395 if (splitNode.call.isDrawingCall) 396 break; 397 ++splitIndex; 398 } 399 var newDrawCallGroup = appendDrawCallGroup(); 400 var lastNode; 401 while ((lastNode = drawCallGroup.children[splitIndex + 1])) 402 newDrawCallGroup.appendChild(lastNode); 403 return newDrawCallGroup; 404 } 405 406 var drawCallGroup = frameNode.children.peekLast(); 407 var groupHasDrawCall = false; 408 if (drawCallGroup) { 409 for (var i = 0, n = drawCallGroup.children.length; i < n; ++i) { 410 if (drawCallGroup.children[i].call.isDrawingCall) { 411 groupHasDrawCall = true; 412 break; 413 } 414 } 415 } else 416 drawCallGroup = appendDrawCallGroup(); 417 418 for (var i = fromIndex; i < toIndex; ++i) { 419 var node = callNodes[i]; 420 drawCallGroup.appendChild(node); 421 if (node.call.isDrawingCall) { 422 if (groupHasDrawCall) 423 drawCallGroup = splitDrawCallGroup(drawCallGroup); 424 else 425 groupHasDrawCall = true; 426 } 427 } 428 }, 429 430 /** 431 * @param {number} index 432 * @param {CanvasAgent.Call} call 433 * @return {!WebInspector.DataGridNode} 434 */ 435 _createCallNode: function(index, call) 436 { 437 var callViewElement = document.createElement("div"); 438 439 var data = {}; 440 data[0] = index + 1; 441 data[1] = callViewElement; 442 data[2] = ""; 443 if (call.sourceURL) { 444 // FIXME(62725): stack trace line/column numbers are one-based. 445 var lineNumber = Math.max(0, call.lineNumber - 1) || 0; 446 var columnNumber = Math.max(0, call.columnNumber - 1) || 0; 447 data[2] = this._linkifier.linkifyLocation(call.sourceURL, lineNumber, columnNumber); 448 } 449 450 callViewElement.createChild("span", "canvas-function-name").textContent = call.functionName || "context." + call.property; 451 452 if (call.arguments) { 453 callViewElement.createTextChild("("); 454 for (var i = 0, n = call.arguments.length; i < n; ++i) { 455 var argument = call.arguments[i]; 456 if (i) 457 callViewElement.createTextChild(", "); 458 this._createCallArgumentChild(callViewElement, argument)._argumentIndex = i; 459 } 460 callViewElement.createTextChild(")"); 461 } else if (typeof call.value !== "undefined") { 462 callViewElement.createTextChild(" = "); 463 this._createCallArgumentChild(callViewElement, call.value); 464 } 465 466 if (typeof call.result !== "undefined") { 467 callViewElement.createTextChild(" => "); 468 this._createCallArgumentChild(callViewElement, call.result); 469 } 470 471 var node = new WebInspector.DataGridNode(data); 472 node.index = index; 473 node.selectable = true; 474 node.call = call; 475 return node; 476 }, 477 478 /** 479 * @param {!Element} parentElement 480 * @param {CanvasAgent.CallArgument} callArgument 481 * @return {!Element} 482 */ 483 _createCallArgumentChild: function(parentElement, callArgument) 484 { 485 var element = parentElement.createChild("span", "canvas-call-argument"); 486 element._argumentIndex = -1; 487 if (callArgument.type === "string") { 488 const maxStringLength = 150; 489 element.createTextChild("\""); 490 element.createChild("span", "canvas-formatted-string").textContent = callArgument.description.trimMiddle(maxStringLength); 491 element.createTextChild("\""); 492 element._suppressPopover = (callArgument.description.length <= maxStringLength && !/[\r\n]/.test(callArgument.description)); 493 } else { 494 var type = callArgument.subtype || callArgument.type; 495 if (type) { 496 element.addStyleClass("canvas-formatted-" + type); 497 switch (type) { 498 case "null": 499 case "undefined": 500 case "boolean": 501 element._suppressPopover = true; 502 break; 503 case "number": 504 element._suppressPopover = !isNaN(callArgument.description); 505 break; 506 } 507 } 508 element.textContent = callArgument.description; 509 } 510 if (callArgument.resourceId) { 511 element.addStyleClass("canvas-formatted-resource"); 512 element.resourceId = callArgument.resourceId; 513 } 514 return element; 515 }, 516 517 _popoverAnchor: function(element, event) 518 { 519 var argumentElement = element.enclosingNodeOrSelfWithClass("canvas-call-argument"); 520 if (!argumentElement || argumentElement._suppressPopover) 521 return null; 522 return argumentElement; 523 }, 524 525 _resolveObjectForPopover: function(argumentElement, showCallback, objectGroupName) 526 { 527 var dataGridNode = this._logGrid.dataGridNodeFromNode(argumentElement); 528 if (!dataGridNode) { 529 this._popoverHelper.hidePopover(); 530 return; 531 } 532 var callIndex = dataGridNode.index; 533 var argumentIndex = argumentElement._argumentIndex; 534 535 /** 536 * @param {?Protocol.Error} error 537 * @param {RuntimeAgent.RemoteObject=} result 538 * @param {CanvasAgent.ResourceState=} resourceState 539 */ 540 function showObjectPopover(error, result, resourceState) 541 { 542 if (error) 543 return; 544 545 // FIXME: handle resourceState also 546 if (!result) 547 return; 548 549 if (result && result.type === "number") { 550 // Show numbers in hex with min length of 4 (e.g. 0x0012). 551 var str = "0000" + Number(result.description).toString(16).toUpperCase(); 552 str = str.replace(/^0+(.{4,})$/, "$1"); 553 result.description = "0x" + str; 554 } 555 556 this._popoverAnchorElement = argumentElement.cloneNode(true); 557 this._popoverAnchorElement.addStyleClass("canvas-popover-anchor"); 558 this._popoverAnchorElement.addStyleClass("source-frame-eval-expression"); 559 argumentElement.parentElement.appendChild(this._popoverAnchorElement); 560 561 var diffLeft = this._popoverAnchorElement.boxInWindow().x - argumentElement.boxInWindow().x; 562 this._popoverAnchorElement.style.left = this._popoverAnchorElement.offsetLeft - diffLeft + "px"; 563 564 showCallback(WebInspector.RemoteObject.fromPayload(result), false, this._popoverAnchorElement); 565 } 566 CanvasAgent.evaluateTraceLogCallArgument(this._traceLogId, callIndex, argumentIndex, objectGroupName, showObjectPopover.bind(this)); 567 }, 568 569 _onHidePopover: function() 570 { 571 if (this._popoverAnchorElement) { 572 this._popoverAnchorElement.remove() 573 delete this._popoverAnchorElement; 574 } 575 }, 576 577 _flattenSingleFrameNode: function() 578 { 579 var rootNode = this._logGrid.rootNode(); 580 if (rootNode.children.length !== 1) 581 return; 582 var frameNode = rootNode.children[0]; 583 while (frameNode.children[0]) 584 rootNode.appendChild(frameNode.children[0]); 585 rootNode.removeChild(frameNode); 586 }, 587 588 __proto__: WebInspector.View.prototype 589 } 590 591 /** 592 * @constructor 593 * @extends {WebInspector.ProfileType} 594 */ 595 WebInspector.CanvasProfileType = function() 596 { 597 WebInspector.ProfileType.call(this, WebInspector.CanvasProfileType.TypeId, WebInspector.UIString("Capture Canvas Frame")); 598 this._nextProfileUid = 1; 599 this._recording = false; 600 this._lastProfileHeader = null; 601 602 this._capturingModeSelector = new WebInspector.StatusBarComboBox(this._dispatchViewUpdatedEvent.bind(this)); 603 this._capturingModeSelector.element.title = WebInspector.UIString("Canvas capture mode."); 604 this._capturingModeSelector.createOption(WebInspector.UIString("Single Frame"), WebInspector.UIString("Capture a single canvas frame."), ""); 605 this._capturingModeSelector.createOption(WebInspector.UIString("Consecutive Frames"), WebInspector.UIString("Capture consecutive canvas frames."), "1"); 606 607 /** @type {!Object.<string, Element>} */ 608 this._frameOptions = {}; 609 610 /** @type {!Object.<string, boolean>} */ 611 this._framesWithCanvases = {}; 612 613 this._frameSelector = new WebInspector.StatusBarComboBox(this._dispatchViewUpdatedEvent.bind(this)); 614 this._frameSelector.element.title = WebInspector.UIString("Frame containing the canvases to capture."); 615 this._frameSelector.element.addStyleClass("hidden"); 616 WebInspector.runtimeModel.contextLists().forEach(this._addFrame, this); 617 WebInspector.runtimeModel.addEventListener(WebInspector.RuntimeModel.Events.FrameExecutionContextListAdded, this._frameAdded, this); 618 WebInspector.runtimeModel.addEventListener(WebInspector.RuntimeModel.Events.FrameExecutionContextListRemoved, this._frameRemoved, this); 619 620 this._dispatcher = new WebInspector.CanvasDispatcher(this); 621 this._canvasAgentEnabled = false; 622 623 this._decorationElement = document.createElement("div"); 624 this._decorationElement.className = "profile-canvas-decoration"; 625 this._updateDecorationElement(); 626 } 627 628 WebInspector.CanvasProfileType.TypeId = "CANVAS_PROFILE"; 629 630 WebInspector.CanvasProfileType.prototype = { 631 get statusBarItems() 632 { 633 return [this._capturingModeSelector.element, this._frameSelector.element]; 634 }, 635 636 get buttonTooltip() 637 { 638 if (this._isSingleFrameMode()) 639 return WebInspector.UIString("Capture next canvas frame."); 640 else 641 return this._recording ? WebInspector.UIString("Stop capturing canvas frames.") : WebInspector.UIString("Start capturing canvas frames."); 642 }, 643 644 /** 645 * @override 646 * @return {boolean} 647 */ 648 buttonClicked: function() 649 { 650 if (!this._canvasAgentEnabled) 651 return false; 652 if (this._recording) { 653 this._recording = false; 654 this._stopFrameCapturing(); 655 } else if (this._isSingleFrameMode()) { 656 this._recording = false; 657 this._runSingleFrameCapturing(); 658 } else { 659 this._recording = true; 660 this._startFrameCapturing(); 661 } 662 return this._recording; 663 }, 664 665 _runSingleFrameCapturing: function() 666 { 667 var frameId = this._selectedFrameId(); 668 CanvasAgent.captureFrame(frameId, this._didStartCapturingFrame.bind(this, frameId)); 669 }, 670 671 _startFrameCapturing: function() 672 { 673 var frameId = this._selectedFrameId(); 674 CanvasAgent.startCapturing(frameId, this._didStartCapturingFrame.bind(this, frameId)); 675 }, 676 677 _stopFrameCapturing: function() 678 { 679 if (!this._lastProfileHeader) 680 return; 681 var profileHeader = this._lastProfileHeader; 682 var traceLogId = profileHeader.traceLogId(); 683 this._lastProfileHeader = null; 684 function didStopCapturing() 685 { 686 profileHeader._updateCapturingStatus(); 687 } 688 CanvasAgent.stopCapturing(traceLogId, didStopCapturing.bind(this)); 689 }, 690 691 /** 692 * @param {string|undefined} frameId 693 * @param {?Protocol.Error} error 694 * @param {CanvasAgent.TraceLogId} traceLogId 695 */ 696 _didStartCapturingFrame: function(frameId, error, traceLogId) 697 { 698 if (error || this._lastProfileHeader && this._lastProfileHeader.traceLogId() === traceLogId) 699 return; 700 var profileHeader = new WebInspector.CanvasProfileHeader(this, WebInspector.UIString("Trace Log %d", this._nextProfileUid), this._nextProfileUid, traceLogId, frameId); 701 ++this._nextProfileUid; 702 this._lastProfileHeader = profileHeader; 703 this.addProfile(profileHeader); 704 profileHeader._updateCapturingStatus(); 705 }, 706 707 get treeItemTitle() 708 { 709 return WebInspector.UIString("CANVAS PROFILE"); 710 }, 711 712 get description() 713 { 714 return WebInspector.UIString("Canvas calls instrumentation"); 715 }, 716 717 /** 718 * @override 719 * @return {Element} 720 */ 721 decorationElement: function() 722 { 723 return this._decorationElement; 724 }, 725 726 /** 727 * @override 728 */ 729 _reset: function() 730 { 731 WebInspector.ProfileType.prototype._reset.call(this); 732 this._nextProfileUid = 1; 733 }, 734 735 /** 736 * @override 737 * @param {!WebInspector.ProfileHeader} profile 738 */ 739 removeProfile: function(profile) 740 { 741 WebInspector.ProfileType.prototype.removeProfile.call(this, profile); 742 if (this._recording && profile === this._lastProfileHeader) 743 this._recording = false; 744 }, 745 746 setRecordingProfile: function(isProfiling) 747 { 748 this._recording = isProfiling; 749 }, 750 751 /** 752 * @override 753 * @param {string=} title 754 * @return {!WebInspector.ProfileHeader} 755 */ 756 createTemporaryProfile: function(title) 757 { 758 title = title || WebInspector.UIString("Capturing\u2026"); 759 return new WebInspector.CanvasProfileHeader(this, title); 760 }, 761 762 /** 763 * @override 764 * @param {ProfilerAgent.ProfileHeader} profile 765 * @return {!WebInspector.ProfileHeader} 766 */ 767 createProfile: function(profile) 768 { 769 return new WebInspector.CanvasProfileHeader(this, profile.title, -1); 770 }, 771 772 /** 773 * @param {boolean=} forcePageReload 774 */ 775 _updateDecorationElement: function(forcePageReload) 776 { 777 this._decorationElement.removeChildren(); 778 this._decorationElement.createChild("div", "warning-icon-small"); 779 this._decorationElement.appendChild(document.createTextNode(this._canvasAgentEnabled ? WebInspector.UIString("Canvas Profiler is enabled.") : WebInspector.UIString("Canvas Profiler is disabled."))); 780 var button = this._decorationElement.createChild("button"); 781 button.type = "button"; 782 button.textContent = this._canvasAgentEnabled ? WebInspector.UIString("Disable") : WebInspector.UIString("Enable"); 783 button.addEventListener("click", this._onProfilerEnableButtonClick.bind(this, !this._canvasAgentEnabled), false); 784 785 if (forcePageReload) { 786 if (this._canvasAgentEnabled) { 787 /** 788 * @param {?Protocol.Error} error 789 * @param {boolean} result 790 */ 791 function hasUninstrumentedCanvasesCallback(error, result) 792 { 793 if (error || result) 794 PageAgent.reload(); 795 } 796 CanvasAgent.hasUninstrumentedCanvases(hasUninstrumentedCanvasesCallback.bind(this)); 797 } else { 798 for (var frameId in this._framesWithCanvases) { 799 if (this._framesWithCanvases.hasOwnProperty(frameId)) { 800 PageAgent.reload(); 801 break; 802 } 803 } 804 } 805 } 806 }, 807 808 /** 809 * @param {boolean} enable 810 */ 811 _onProfilerEnableButtonClick: function(enable) 812 { 813 if (this._canvasAgentEnabled === enable) 814 return; 815 /** 816 * @param {?Protocol.Error} error 817 */ 818 function callback(error) 819 { 820 if (error) 821 return; 822 this._canvasAgentEnabled = enable; 823 this._updateDecorationElement(true); 824 this._dispatchViewUpdatedEvent(); 825 } 826 if (enable) 827 CanvasAgent.enable(callback.bind(this)); 828 else 829 CanvasAgent.disable(callback.bind(this)); 830 }, 831 832 /** 833 * @return {boolean} 834 */ 835 _isSingleFrameMode: function() 836 { 837 return !this._capturingModeSelector.selectedOption().value; 838 }, 839 840 /** 841 * @param {WebInspector.Event} event 842 */ 843 _frameAdded: function(event) 844 { 845 var contextList = /** @type {WebInspector.FrameExecutionContextList} */ (event.data); 846 this._addFrame(contextList); 847 }, 848 849 /** 850 * @param {WebInspector.FrameExecutionContextList} contextList 851 */ 852 _addFrame: function(contextList) 853 { 854 var frameId = contextList.frameId; 855 var option = document.createElement("option"); 856 option.text = contextList.displayName; 857 option.title = contextList.url; 858 option.value = frameId; 859 860 this._frameOptions[frameId] = option; 861 862 if (this._framesWithCanvases[frameId]) { 863 this._frameSelector.addOption(option); 864 this._dispatchViewUpdatedEvent(); 865 } 866 }, 867 868 /** 869 * @param {WebInspector.Event} event 870 */ 871 _frameRemoved: function(event) 872 { 873 var contextList = /** @type {WebInspector.FrameExecutionContextList} */ (event.data); 874 var frameId = contextList.frameId; 875 var option = this._frameOptions[frameId]; 876 if (option && this._framesWithCanvases[frameId]) { 877 this._frameSelector.removeOption(option); 878 this._dispatchViewUpdatedEvent(); 879 } 880 delete this._frameOptions[frameId]; 881 delete this._framesWithCanvases[frameId]; 882 }, 883 884 /** 885 * @param {string} frameId 886 */ 887 _contextCreated: function(frameId) 888 { 889 if (this._framesWithCanvases[frameId]) 890 return; 891 this._framesWithCanvases[frameId] = true; 892 var option = this._frameOptions[frameId]; 893 if (option) { 894 this._frameSelector.addOption(option); 895 this._dispatchViewUpdatedEvent(); 896 } 897 }, 898 899 /** 900 * @param {NetworkAgent.FrameId=} frameId 901 * @param {CanvasAgent.TraceLogId=} traceLogId 902 */ 903 _traceLogsRemoved: function(frameId, traceLogId) 904 { 905 var sidebarElementsToDelete = []; 906 var sidebarElements = /** @type {!Array.<WebInspector.ProfileSidebarTreeElement>} */ ((this.treeElement && this.treeElement.children) || []); 907 for (var i = 0, n = sidebarElements.length; i < n; ++i) { 908 var header = /** @type {WebInspector.CanvasProfileHeader} */ (sidebarElements[i].profile); 909 if (!header) 910 continue; 911 if (frameId && frameId !== header.frameId()) 912 continue; 913 if (traceLogId && traceLogId !== header.traceLogId()) 914 continue; 915 sidebarElementsToDelete.push(sidebarElements[i]); 916 } 917 for (var i = 0, n = sidebarElementsToDelete.length; i < n; ++i) 918 sidebarElementsToDelete[i].ondelete(); 919 }, 920 921 /** 922 * @return {string|undefined} 923 */ 924 _selectedFrameId: function() 925 { 926 var option = this._frameSelector.selectedOption(); 927 return option ? option.value : undefined; 928 }, 929 930 _dispatchViewUpdatedEvent: function() 931 { 932 this._frameSelector.element.enableStyleClass("hidden", this._frameSelector.size() <= 1); 933 this.dispatchEventToListeners(WebInspector.ProfileType.Events.ViewUpdated); 934 }, 935 936 /** 937 * @override 938 * @return {boolean} 939 */ 940 isInstantProfile: function() 941 { 942 return this._isSingleFrameMode(); 943 }, 944 945 /** 946 * @override 947 * @return {boolean} 948 */ 949 isEnabled: function() 950 { 951 return this._canvasAgentEnabled; 952 }, 953 954 __proto__: WebInspector.ProfileType.prototype 955 } 956 957 /** 958 * @constructor 959 * @implements {CanvasAgent.Dispatcher} 960 * @param {WebInspector.CanvasProfileType} profileType 961 */ 962 WebInspector.CanvasDispatcher = function(profileType) 963 { 964 this._profileType = profileType; 965 InspectorBackend.registerCanvasDispatcher(this); 966 } 967 968 WebInspector.CanvasDispatcher.prototype = { 969 /** 970 * @param {string} frameId 971 */ 972 contextCreated: function(frameId) 973 { 974 this._profileType._contextCreated(frameId); 975 }, 976 977 /** 978 * @param {NetworkAgent.FrameId=} frameId 979 * @param {CanvasAgent.TraceLogId=} traceLogId 980 */ 981 traceLogsRemoved: function(frameId, traceLogId) 982 { 983 this._profileType._traceLogsRemoved(frameId, traceLogId); 984 } 985 } 986 987 /** 988 * @constructor 989 * @extends {WebInspector.ProfileHeader} 990 * @param {!WebInspector.CanvasProfileType} type 991 * @param {string} title 992 * @param {number=} uid 993 * @param {CanvasAgent.TraceLogId=} traceLogId 994 * @param {NetworkAgent.FrameId=} frameId 995 */ 996 WebInspector.CanvasProfileHeader = function(type, title, uid, traceLogId, frameId) 997 { 998 WebInspector.ProfileHeader.call(this, type, title, uid); 999 /** @type {CanvasAgent.TraceLogId} */ 1000 this._traceLogId = traceLogId || ""; 1001 this._frameId = frameId; 1002 this._alive = true; 1003 this._traceLogSize = 0; 1004 } 1005 1006 WebInspector.CanvasProfileHeader.prototype = { 1007 /** 1008 * @return {CanvasAgent.TraceLogId} 1009 */ 1010 traceLogId: function() 1011 { 1012 return this._traceLogId; 1013 }, 1014 1015 /** 1016 * @return {NetworkAgent.FrameId|undefined} 1017 */ 1018 frameId: function() 1019 { 1020 return this._frameId; 1021 }, 1022 1023 /** 1024 * @override 1025 * @return {WebInspector.ProfileSidebarTreeElement} 1026 */ 1027 createSidebarTreeElement: function() 1028 { 1029 return new WebInspector.ProfileSidebarTreeElement(this, WebInspector.UIString("Trace Log %d"), "profile-sidebar-tree-item"); 1030 }, 1031 1032 /** 1033 * @override 1034 * @param {WebInspector.ProfilesPanel} profilesPanel 1035 */ 1036 createView: function(profilesPanel) 1037 { 1038 return new WebInspector.CanvasProfileView(this); 1039 }, 1040 1041 /** 1042 * @override 1043 */ 1044 dispose: function() 1045 { 1046 if (this._traceLogId) { 1047 CanvasAgent.dropTraceLog(this._traceLogId); 1048 clearTimeout(this._requestStatusTimer); 1049 this._alive = false; 1050 } 1051 }, 1052 1053 /** 1054 * @param {CanvasAgent.TraceLog=} traceLog 1055 */ 1056 _updateCapturingStatus: function(traceLog) 1057 { 1058 if (!this.sidebarElement || !this._traceLogId) 1059 return; 1060 1061 if (traceLog) { 1062 this._alive = traceLog.alive; 1063 this._traceLogSize = traceLog.totalAvailableCalls; 1064 } 1065 1066 this.sidebarElement.subtitle = this._alive ? WebInspector.UIString("Capturing\u2026 %d calls", this._traceLogSize) : WebInspector.UIString("Captured %d calls", this._traceLogSize); 1067 this.sidebarElement.wait = this._alive; 1068 1069 if (this._alive) { 1070 clearTimeout(this._requestStatusTimer); 1071 this._requestStatusTimer = setTimeout(this._requestCapturingStatus.bind(this), WebInspector.CanvasProfileView.TraceLogPollingInterval); 1072 } 1073 }, 1074 1075 _requestCapturingStatus: function() 1076 { 1077 /** 1078 * @param {?Protocol.Error} error 1079 * @param {CanvasAgent.TraceLog} traceLog 1080 */ 1081 function didReceiveTraceLog(error, traceLog) 1082 { 1083 if (error) 1084 return; 1085 this._alive = traceLog.alive; 1086 this._traceLogSize = traceLog.totalAvailableCalls; 1087 this._updateCapturingStatus(); 1088 } 1089 CanvasAgent.getTraceLog(this._traceLogId, 0, 0, didReceiveTraceLog.bind(this)); 1090 }, 1091 1092 __proto__: WebInspector.ProfileHeader.prototype 1093 } 1094