CanvasProfileView.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946
  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. * @constructor
  32. * @extends {WebInspector.View}
  33. * @param {!WebInspector.CanvasProfileHeader} profile
  34. */
  35. WebInspector.CanvasProfileView = function(profile)
  36. {
  37. WebInspector.View.call(this);
  38. this.registerRequiredCSS("canvasProfiler.css");
  39. this._profile = profile;
  40. this._traceLogId = profile.traceLogId();
  41. this.element.addStyleClass("canvas-profile-view");
  42. this._linkifier = new WebInspector.Linkifier();
  43. this._splitView = new WebInspector.SplitView(false, "canvasProfileViewSplitLocation", 300);
  44. var replayImageContainer = this._splitView.firstElement();
  45. replayImageContainer.id = "canvas-replay-image-container";
  46. this._replayImageElement = replayImageContainer.createChild("image", "canvas-replay-image");
  47. this._debugInfoElement = replayImageContainer.createChild("div", "canvas-debug-info hidden");
  48. this._spinnerIcon = replayImageContainer.createChild("img", "canvas-spinner-icon hidden");
  49. var replayInfoContainer = this._splitView.secondElement();
  50. var controlsContainer = replayInfoContainer.createChild("div", "status-bar");
  51. var logGridContainer = replayInfoContainer.createChild("div", "canvas-replay-log");
  52. this._createControlButton(controlsContainer, "canvas-replay-first-step", WebInspector.UIString("First call."), this._onReplayFirstStepClick.bind(this));
  53. this._createControlButton(controlsContainer, "canvas-replay-prev-step", WebInspector.UIString("Previous call."), this._onReplayStepClick.bind(this, false));
  54. this._createControlButton(controlsContainer, "canvas-replay-next-step", WebInspector.UIString("Next call."), this._onReplayStepClick.bind(this, true));
  55. this._createControlButton(controlsContainer, "canvas-replay-prev-draw", WebInspector.UIString("Previous drawing call."), this._onReplayDrawingCallClick.bind(this, false));
  56. this._createControlButton(controlsContainer, "canvas-replay-next-draw", WebInspector.UIString("Next drawing call."), this._onReplayDrawingCallClick.bind(this, true));
  57. this._createControlButton(controlsContainer, "canvas-replay-last-step", WebInspector.UIString("Last call."), this._onReplayLastStepClick.bind(this));
  58. this._replayContextSelector = new WebInspector.StatusBarComboBox(this._onReplayContextChanged.bind(this));
  59. this._replayContextSelector.createOption("<screenshot auto>", WebInspector.UIString("Show screenshot of the last replayed resource."), "");
  60. controlsContainer.appendChild(this._replayContextSelector.element);
  61. /** @type {!Object.<string, boolean>} */
  62. this._replayContexts = {};
  63. /** @type {!Object.<string, CanvasAgent.ResourceState>} */
  64. this._currentResourceStates = {};
  65. var columns = [
  66. {title: "#", sortable: true, width: "5%"},
  67. {title: WebInspector.UIString("Call"), sortable: true, width: "75%", disclosure: true},
  68. {title: WebInspector.UIString("Location"), sortable: true, width: "20%"}
  69. ];
  70. this._logGrid = new WebInspector.DataGrid(columns);
  71. this._logGrid.element.addStyleClass("fill");
  72. this._logGrid.show(logGridContainer);
  73. this._logGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._replayTraceLog.bind(this));
  74. this._splitView.show(this.element);
  75. this._requestTraceLog(0);
  76. }
  77. /**
  78. * @const
  79. * @type {number}
  80. */
  81. WebInspector.CanvasProfileView.TraceLogPollingInterval = 500;
  82. WebInspector.CanvasProfileView.prototype = {
  83. dispose: function()
  84. {
  85. this._linkifier.reset();
  86. },
  87. statusBarItems: function()
  88. {
  89. return [];
  90. },
  91. get profile()
  92. {
  93. return this._profile;
  94. },
  95. /**
  96. * @override
  97. * @return {Array.<Element>}
  98. */
  99. elementsToRestoreScrollPositionsFor: function()
  100. {
  101. return [this._logGrid.scrollContainer];
  102. },
  103. /**
  104. * @param {Element} parent
  105. * @param {string} className
  106. * @param {string} title
  107. * @param {function(this:WebInspector.CanvasProfileView)} clickCallback
  108. */
  109. _createControlButton: function(parent, className, title, clickCallback)
  110. {
  111. var button = parent.createChild("button", "status-bar-item");
  112. button.addStyleClass(className);
  113. button.title = title;
  114. button.createChild("img");
  115. button.addEventListener("click", clickCallback, false);
  116. },
  117. _onReplayContextChanged: function()
  118. {
  119. /**
  120. * @param {?Protocol.Error} error
  121. * @param {CanvasAgent.ResourceState} resourceState
  122. */
  123. function didReceiveResourceState(error, resourceState)
  124. {
  125. this._enableWaitIcon(false);
  126. if (error)
  127. return;
  128. this._currentResourceStates[resourceState.id] = resourceState;
  129. var selectedContextId = this._replayContextSelector.selectedOption().value;
  130. if (selectedContextId === resourceState.id)
  131. this._replayImageElement.src = resourceState.imageURL;
  132. }
  133. var selectedContextId = this._replayContextSelector.selectedOption().value || "auto";
  134. var resourceState = this._currentResourceStates[selectedContextId];
  135. if (resourceState)
  136. this._replayImageElement.src = resourceState.imageURL;
  137. else {
  138. this._enableWaitIcon(true);
  139. this._replayImageElement.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; // Empty transparent image.
  140. CanvasAgent.getResourceState(this._traceLogId, selectedContextId, didReceiveResourceState.bind(this));
  141. }
  142. },
  143. /**
  144. * @param {boolean} forward
  145. */
  146. _onReplayStepClick: function(forward)
  147. {
  148. var selectedNode = this._logGrid.selectedNode;
  149. if (!selectedNode)
  150. return;
  151. var nextNode = forward ? selectedNode.traverseNextNode(false) : selectedNode.traversePreviousNode(false);
  152. (nextNode || selectedNode).revealAndSelect();
  153. },
  154. /**
  155. * @param {boolean} forward
  156. */
  157. _onReplayDrawingCallClick: function(forward)
  158. {
  159. var selectedNode = this._logGrid.selectedNode;
  160. if (!selectedNode)
  161. return;
  162. var nextNode = selectedNode;
  163. while (nextNode) {
  164. var sibling = forward ? nextNode.nextSibling : nextNode.previousSibling;
  165. if (sibling) {
  166. nextNode = sibling;
  167. if (nextNode.hasChildren || nextNode.call.isDrawingCall)
  168. break;
  169. } else {
  170. nextNode = nextNode.parent;
  171. if (!forward)
  172. break;
  173. }
  174. }
  175. if (!nextNode && forward)
  176. this._onReplayLastStepClick();
  177. else
  178. (nextNode || selectedNode).revealAndSelect();
  179. },
  180. _onReplayFirstStepClick: function()
  181. {
  182. var firstNode = this._logGrid.rootNode().children[0];
  183. if (firstNode)
  184. firstNode.revealAndSelect();
  185. },
  186. _onReplayLastStepClick: function()
  187. {
  188. var lastNode = this._logGrid.rootNode().children.peekLast();
  189. if (!lastNode)
  190. return;
  191. while (lastNode.expanded) {
  192. var lastChild = lastNode.children.peekLast();
  193. if (!lastChild)
  194. break;
  195. lastNode = lastChild;
  196. }
  197. lastNode.revealAndSelect();
  198. },
  199. /**
  200. * @param {boolean} enable
  201. */
  202. _enableWaitIcon: function(enable)
  203. {
  204. this._spinnerIcon.enableStyleClass("hidden", !enable);
  205. this._debugInfoElement.enableStyleClass("hidden", enable);
  206. },
  207. _replayTraceLog: function()
  208. {
  209. if (this._pendingReplayTraceLogEvent)
  210. return;
  211. var index = this._selectedCallIndex();
  212. if (index === -1 || index === this._lastReplayCallIndex)
  213. return;
  214. this._lastReplayCallIndex = index;
  215. this._pendingReplayTraceLogEvent = true;
  216. var time = Date.now();
  217. /**
  218. * @param {?Protocol.Error} error
  219. * @param {CanvasAgent.ResourceState} resourceState
  220. */
  221. function didReplayTraceLog(error, resourceState)
  222. {
  223. delete this._pendingReplayTraceLogEvent;
  224. if (index !== this._selectedCallIndex()) {
  225. this._replayTraceLog();
  226. return;
  227. }
  228. this._enableWaitIcon(false);
  229. if (error)
  230. return;
  231. this._currentResourceStates = {};
  232. this._currentResourceStates["auto"] = resourceState;
  233. this._currentResourceStates[resourceState.id] = resourceState;
  234. this._debugInfoElement.textContent = "Replay time: " + (Date.now() - time) + "ms";
  235. this._onReplayContextChanged();
  236. }
  237. this._enableWaitIcon(true);
  238. CanvasAgent.replayTraceLog(this._traceLogId, index, didReplayTraceLog.bind(this));
  239. },
  240. /**
  241. * @param {?Protocol.Error} error
  242. * @param {CanvasAgent.TraceLog} traceLog
  243. */
  244. _didReceiveTraceLog: function(error, traceLog)
  245. {
  246. this._enableWaitIcon(false);
  247. if (error || !traceLog)
  248. return;
  249. var callNodes = [];
  250. var calls = traceLog.calls;
  251. var index = traceLog.startOffset;
  252. for (var i = 0, n = calls.length; i < n; ++i) {
  253. var call = calls[i];
  254. this._requestReplayContextInfo(call.contextId);
  255. var gridNode = this._createCallNode(index++, call);
  256. callNodes.push(gridNode);
  257. }
  258. this._appendCallNodes(callNodes);
  259. if (traceLog.alive)
  260. setTimeout(this._requestTraceLog.bind(this, index), WebInspector.CanvasProfileView.TraceLogPollingInterval);
  261. else
  262. this._flattenSingleFrameNode();
  263. this._profile._updateCapturingStatus(traceLog);
  264. this._onReplayLastStepClick(); // Automatically replay the last step.
  265. },
  266. /**
  267. * @param {number} offset
  268. */
  269. _requestTraceLog: function(offset)
  270. {
  271. this._enableWaitIcon(true);
  272. CanvasAgent.getTraceLog(this._traceLogId, offset, undefined, this._didReceiveTraceLog.bind(this));
  273. },
  274. /**
  275. * @param {string} contextId
  276. */
  277. _requestReplayContextInfo: function(contextId)
  278. {
  279. if (this._replayContexts[contextId])
  280. return;
  281. this._replayContexts[contextId] = true;
  282. /**
  283. * @param {?Protocol.Error} error
  284. * @param {CanvasAgent.ResourceInfo} resourceInfo
  285. */
  286. function didReceiveResourceInfo(error, resourceInfo)
  287. {
  288. if (error) {
  289. delete this._replayContexts[contextId];
  290. return;
  291. }
  292. this._replayContextSelector.createOption(resourceInfo.description, WebInspector.UIString("Show screenshot of this context's canvas."), contextId);
  293. }
  294. CanvasAgent.getResourceInfo(contextId, didReceiveResourceInfo.bind(this));
  295. },
  296. /**
  297. * @return {number}
  298. */
  299. _selectedCallIndex: function()
  300. {
  301. var node = this._logGrid.selectedNode;
  302. return node ? this._peekLastRecursively(node).index : -1;
  303. },
  304. /**
  305. * @param {!WebInspector.DataGridNode} node
  306. * @return {!WebInspector.DataGridNode}
  307. */
  308. _peekLastRecursively: function(node)
  309. {
  310. var lastChild;
  311. while ((lastChild = node.children.peekLast()))
  312. node = /** @type {!WebInspector.DataGridNode} */ (lastChild);
  313. return node;
  314. },
  315. /**
  316. * @param {!Array.<!WebInspector.DataGridNode>} callNodes
  317. */
  318. _appendCallNodes: function(callNodes)
  319. {
  320. var rootNode = this._logGrid.rootNode();
  321. var frameNode = /** @type {WebInspector.DataGridNode} */ (rootNode.children.peekLast());
  322. if (frameNode && this._peekLastRecursively(frameNode).call.isFrameEndCall)
  323. frameNode = null;
  324. for (var i = 0, n = callNodes.length; i < n; ++i) {
  325. if (!frameNode) {
  326. var index = rootNode.children.length;
  327. var data = {};
  328. data[0] = "";
  329. data[1] = "Frame #" + (index + 1);
  330. data[2] = "";
  331. frameNode = new WebInspector.DataGridNode(data);
  332. frameNode.selectable = true;
  333. rootNode.appendChild(frameNode);
  334. }
  335. var nextFrameCallIndex = i + 1;
  336. while (nextFrameCallIndex < n && !callNodes[nextFrameCallIndex - 1].call.isFrameEndCall)
  337. ++nextFrameCallIndex;
  338. this._appendCallNodesToFrameNode(frameNode, callNodes, i, nextFrameCallIndex);
  339. i = nextFrameCallIndex - 1;
  340. frameNode = null;
  341. }
  342. },
  343. /**
  344. * @param {!WebInspector.DataGridNode} frameNode
  345. * @param {!Array.<!WebInspector.DataGridNode>} callNodes
  346. * @param {number} fromIndex
  347. * @param {number} toIndex not inclusive
  348. */
  349. _appendCallNodesToFrameNode: function(frameNode, callNodes, fromIndex, toIndex)
  350. {
  351. var self = this;
  352. function appendDrawCallGroup()
  353. {
  354. var index = self._drawCallGroupsCount || 0;
  355. var data = {};
  356. data[0] = "";
  357. data[1] = "Draw call group #" + (index + 1);
  358. data[2] = "";
  359. var node = new WebInspector.DataGridNode(data);
  360. node.selectable = true;
  361. self._drawCallGroupsCount = index + 1;
  362. frameNode.appendChild(node);
  363. return node;
  364. }
  365. function splitDrawCallGroup(drawCallGroup)
  366. {
  367. var splitIndex = 0;
  368. var splitNode;
  369. while ((splitNode = drawCallGroup.children[splitIndex])) {
  370. if (splitNode.call.isDrawingCall)
  371. break;
  372. ++splitIndex;
  373. }
  374. var newDrawCallGroup = appendDrawCallGroup();
  375. var lastNode;
  376. while ((lastNode = drawCallGroup.children[splitIndex + 1]))
  377. newDrawCallGroup.appendChild(lastNode);
  378. return newDrawCallGroup;
  379. }
  380. var drawCallGroup = frameNode.children.peekLast();
  381. var groupHasDrawCall = false;
  382. if (drawCallGroup) {
  383. for (var i = 0, n = drawCallGroup.children.length; i < n; ++i) {
  384. if (drawCallGroup.children[i].call.isDrawingCall) {
  385. groupHasDrawCall = true;
  386. break;
  387. }
  388. }
  389. } else
  390. drawCallGroup = appendDrawCallGroup();
  391. for (var i = fromIndex; i < toIndex; ++i) {
  392. var node = callNodes[i];
  393. drawCallGroup.appendChild(node);
  394. if (node.call.isDrawingCall) {
  395. if (groupHasDrawCall)
  396. drawCallGroup = splitDrawCallGroup(drawCallGroup);
  397. else
  398. groupHasDrawCall = true;
  399. }
  400. }
  401. },
  402. /**
  403. * @param {number} index
  404. * @param {CanvasAgent.Call} call
  405. * @return {!WebInspector.DataGridNode}
  406. */
  407. _createCallNode: function(index, call)
  408. {
  409. var data = {};
  410. data[0] = index + 1;
  411. data[1] = call.functionName || "context." + call.property;
  412. data[2] = "";
  413. if (call.sourceURL) {
  414. // FIXME(62725): stack trace line/column numbers are one-based.
  415. var lineNumber = Math.max(0, call.lineNumber - 1) || 0;
  416. var columnNumber = Math.max(0, call.columnNumber - 1) || 0;
  417. data[2] = this._linkifier.linkifyLocation(call.sourceURL, lineNumber, columnNumber);
  418. }
  419. if (call.arguments) {
  420. var args = call.arguments.map(function(argument) {
  421. return argument.description;
  422. });
  423. data[1] += "(" + args.join(", ") + ")";
  424. } else
  425. data[1] += " = " + call.value.description;
  426. if (typeof call.result !== "undefined")
  427. data[1] += " => " + call.result.description;
  428. var node = new WebInspector.DataGridNode(data);
  429. node.index = index;
  430. node.selectable = true;
  431. node.call = call;
  432. return node;
  433. },
  434. _flattenSingleFrameNode: function()
  435. {
  436. var rootNode = this._logGrid.rootNode();
  437. if (rootNode.children.length !== 1)
  438. return;
  439. var frameNode = rootNode.children[0];
  440. while (frameNode.children[0])
  441. rootNode.appendChild(frameNode.children[0]);
  442. rootNode.removeChild(frameNode);
  443. },
  444. __proto__: WebInspector.View.prototype
  445. }
  446. /**
  447. * @constructor
  448. * @extends {WebInspector.ProfileType}
  449. */
  450. WebInspector.CanvasProfileType = function()
  451. {
  452. WebInspector.ProfileType.call(this, WebInspector.CanvasProfileType.TypeId, WebInspector.UIString("Capture Canvas Frame"));
  453. this._nextProfileUid = 1;
  454. this._recording = false;
  455. this._lastProfileHeader = null;
  456. this._capturingModeSelector = new WebInspector.StatusBarComboBox(this._dispatchViewUpdatedEvent.bind(this));
  457. this._capturingModeSelector.element.title = WebInspector.UIString("Canvas capture mode.");
  458. this._capturingModeSelector.createOption(WebInspector.UIString("Single Frame"), WebInspector.UIString("Capture a single canvas frame."), "");
  459. this._capturingModeSelector.createOption(WebInspector.UIString("Consecutive Frames"), WebInspector.UIString("Capture consecutive canvas frames."), "1");
  460. /** @type {!Object.<string, Element>} */
  461. this._frameOptions = {};
  462. /** @type {!Object.<string, boolean>} */
  463. this._framesWithCanvases = {};
  464. this._frameSelector = new WebInspector.StatusBarComboBox(this._dispatchViewUpdatedEvent.bind(this));
  465. this._frameSelector.element.title = WebInspector.UIString("Frame containing the canvases to capture.");
  466. this._frameSelector.element.addStyleClass("hidden");
  467. WebInspector.runtimeModel.contextLists().forEach(this._addFrame, this);
  468. WebInspector.runtimeModel.addEventListener(WebInspector.RuntimeModel.Events.FrameExecutionContextListAdded, this._frameAdded, this);
  469. WebInspector.runtimeModel.addEventListener(WebInspector.RuntimeModel.Events.FrameExecutionContextListRemoved, this._frameRemoved, this);
  470. this._decorationElement = document.createElement("div");
  471. this._decorationElement.addStyleClass("profile-canvas-decoration");
  472. this._decorationElement.addStyleClass("hidden");
  473. this._decorationElement.textContent = WebInspector.UIString("There is an uninstrumented canvas on the page. Reload the page to instrument it.");
  474. var reloadPageButton = this._decorationElement.createChild("button");
  475. reloadPageButton.type = "button";
  476. reloadPageButton.textContent = WebInspector.UIString("Reload");
  477. reloadPageButton.addEventListener("click", this._onReloadPageButtonClick.bind(this), false);
  478. this._dispatcher = new WebInspector.CanvasDispatcher(this);
  479. // FIXME: enable/disable by a UI action?
  480. CanvasAgent.enable(this._updateDecorationElement.bind(this));
  481. WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._updateDecorationElement, this);
  482. }
  483. WebInspector.CanvasProfileType.TypeId = "CANVAS_PROFILE";
  484. WebInspector.CanvasProfileType.prototype = {
  485. statusBarItems: function()
  486. {
  487. return [this._capturingModeSelector.element, this._frameSelector.element];
  488. },
  489. get buttonTooltip()
  490. {
  491. if (this._isSingleFrameMode())
  492. return WebInspector.UIString("Capture next canvas frame.");
  493. else
  494. return this._recording ? WebInspector.UIString("Stop capturing canvas frames.") : WebInspector.UIString("Start capturing canvas frames.");
  495. },
  496. /**
  497. * @override
  498. * @return {boolean}
  499. */
  500. buttonClicked: function()
  501. {
  502. if (this._recording) {
  503. this._recording = false;
  504. this._stopFrameCapturing();
  505. } else if (this._isSingleFrameMode()) {
  506. this._recording = false;
  507. this._runSingleFrameCapturing();
  508. } else {
  509. this._recording = true;
  510. this._startFrameCapturing();
  511. }
  512. return this._recording;
  513. },
  514. _runSingleFrameCapturing: function()
  515. {
  516. var frameId = this._selectedFrameId();
  517. CanvasAgent.captureFrame(frameId, this._didStartCapturingFrame.bind(this, frameId));
  518. },
  519. _startFrameCapturing: function()
  520. {
  521. var frameId = this._selectedFrameId();
  522. CanvasAgent.startCapturing(frameId, this._didStartCapturingFrame.bind(this, frameId));
  523. },
  524. _stopFrameCapturing: function()
  525. {
  526. if (!this._lastProfileHeader)
  527. return;
  528. var profileHeader = this._lastProfileHeader;
  529. var traceLogId = profileHeader.traceLogId();
  530. this._lastProfileHeader = null;
  531. function didStopCapturing()
  532. {
  533. profileHeader._updateCapturingStatus();
  534. }
  535. CanvasAgent.stopCapturing(traceLogId, didStopCapturing.bind(this));
  536. },
  537. /**
  538. * @param {string|undefined} frameId
  539. * @param {?Protocol.Error} error
  540. * @param {CanvasAgent.TraceLogId} traceLogId
  541. */
  542. _didStartCapturingFrame: function(frameId, error, traceLogId)
  543. {
  544. if (error || this._lastProfileHeader && this._lastProfileHeader.traceLogId() === traceLogId)
  545. return;
  546. var profileHeader = new WebInspector.CanvasProfileHeader(this, WebInspector.UIString("Trace Log %d", this._nextProfileUid), this._nextProfileUid, traceLogId, frameId);
  547. ++this._nextProfileUid;
  548. this._lastProfileHeader = profileHeader;
  549. this.addProfile(profileHeader);
  550. profileHeader._updateCapturingStatus();
  551. },
  552. get treeItemTitle()
  553. {
  554. return WebInspector.UIString("CANVAS PROFILE");
  555. },
  556. get description()
  557. {
  558. return WebInspector.UIString("Canvas calls instrumentation");
  559. },
  560. /**
  561. * @override
  562. * @return {Element}
  563. */
  564. decorationElement: function()
  565. {
  566. return this._decorationElement;
  567. },
  568. /**
  569. * @override
  570. */
  571. _reset: function()
  572. {
  573. WebInspector.ProfileType.prototype._reset.call(this);
  574. this._nextProfileUid = 1;
  575. },
  576. /**
  577. * @override
  578. * @param {!WebInspector.ProfileHeader} profile
  579. */
  580. removeProfile: function(profile)
  581. {
  582. WebInspector.ProfileType.prototype.removeProfile.call(this, profile);
  583. if (this._recording && profile === this._lastProfileHeader)
  584. this._recording = false;
  585. },
  586. setRecordingProfile: function(isProfiling)
  587. {
  588. this._recording = isProfiling;
  589. },
  590. /**
  591. * @override
  592. * @param {string=} title
  593. * @return {!WebInspector.ProfileHeader}
  594. */
  595. createTemporaryProfile: function(title)
  596. {
  597. title = title || WebInspector.UIString("Capturing\u2026");
  598. return new WebInspector.CanvasProfileHeader(this, title);
  599. },
  600. /**
  601. * @override
  602. * @param {ProfilerAgent.ProfileHeader} profile
  603. * @return {!WebInspector.ProfileHeader}
  604. */
  605. createProfile: function(profile)
  606. {
  607. return new WebInspector.CanvasProfileHeader(this, profile.title, -1);
  608. },
  609. _updateDecorationElement: function()
  610. {
  611. /**
  612. * @param {?Protocol.Error} error
  613. * @param {boolean} result
  614. */
  615. function callback(error, result)
  616. {
  617. var hideWarning = (error || !result);
  618. this._decorationElement.enableStyleClass("hidden", hideWarning);
  619. }
  620. CanvasAgent.hasUninstrumentedCanvases(callback.bind(this));
  621. },
  622. /**
  623. * @param {MouseEvent} event
  624. */
  625. _onReloadPageButtonClick: function(event)
  626. {
  627. PageAgent.reload(event.shiftKey);
  628. },
  629. /**
  630. * @return {boolean}
  631. */
  632. _isSingleFrameMode: function()
  633. {
  634. return !this._capturingModeSelector.selectedOption().value;
  635. },
  636. /**
  637. * @param {WebInspector.Event} event
  638. */
  639. _frameAdded: function(event)
  640. {
  641. var contextList = /** @type {WebInspector.FrameExecutionContextList} */ (event.data);
  642. this._addFrame(contextList);
  643. },
  644. /**
  645. * @param {WebInspector.FrameExecutionContextList} contextList
  646. */
  647. _addFrame: function(contextList)
  648. {
  649. var frameId = contextList.frameId;
  650. var option = document.createElement("option");
  651. option.text = contextList.displayName;
  652. option.title = contextList.url;
  653. option.value = frameId;
  654. this._frameOptions[frameId] = option;
  655. if (this._framesWithCanvases[frameId]) {
  656. this._frameSelector.addOption(option);
  657. this._dispatchViewUpdatedEvent();
  658. }
  659. },
  660. /**
  661. * @param {WebInspector.Event} event
  662. */
  663. _frameRemoved: function(event)
  664. {
  665. var contextList = /** @type {WebInspector.FrameExecutionContextList} */ (event.data);
  666. var frameId = contextList.frameId;
  667. var option = this._frameOptions[frameId];
  668. if (option && this._framesWithCanvases[frameId]) {
  669. this._frameSelector.removeOption(option);
  670. this._dispatchViewUpdatedEvent();
  671. }
  672. delete this._frameOptions[frameId];
  673. delete this._framesWithCanvases[frameId];
  674. },
  675. /**
  676. * @param {string} frameId
  677. */
  678. _contextCreated: function(frameId)
  679. {
  680. if (this._framesWithCanvases[frameId])
  681. return;
  682. this._framesWithCanvases[frameId] = true;
  683. var option = this._frameOptions[frameId];
  684. if (option) {
  685. this._frameSelector.addOption(option);
  686. this._dispatchViewUpdatedEvent();
  687. }
  688. },
  689. /**
  690. * @param {NetworkAgent.FrameId=} frameId
  691. * @param {CanvasAgent.TraceLogId=} traceLogId
  692. */
  693. _traceLogsRemoved: function(frameId, traceLogId)
  694. {
  695. var sidebarElementsToDelete = [];
  696. var sidebarElements = /** @type {!Array.<WebInspector.ProfileSidebarTreeElement>} */ ((this.treeElement && this.treeElement.children) || []);
  697. for (var i = 0, n = sidebarElements.length; i < n; ++i) {
  698. var header = /** @type {WebInspector.CanvasProfileHeader} */ (sidebarElements[i].profile);
  699. if (!header)
  700. continue;
  701. if (frameId && frameId !== header.frameId())
  702. continue;
  703. if (traceLogId && traceLogId !== header.traceLogId())
  704. continue;
  705. sidebarElementsToDelete.push(sidebarElements[i]);
  706. }
  707. for (var i = 0, n = sidebarElementsToDelete.length; i < n; ++i)
  708. sidebarElementsToDelete[i].ondelete();
  709. },
  710. /**
  711. * @return {string|undefined}
  712. */
  713. _selectedFrameId: function()
  714. {
  715. var option = this._frameSelector.selectedOption();
  716. return option ? option.value : undefined;
  717. },
  718. _dispatchViewUpdatedEvent: function()
  719. {
  720. this._frameSelector.element.enableStyleClass("hidden", this._frameSelector.size() <= 1);
  721. this.dispatchEventToListeners(WebInspector.ProfileType.Events.ViewUpdated);
  722. },
  723. __proto__: WebInspector.ProfileType.prototype
  724. }
  725. /**
  726. * @constructor
  727. * @implements {CanvasAgent.Dispatcher}
  728. * @param {WebInspector.CanvasProfileType} profileType
  729. */
  730. WebInspector.CanvasDispatcher = function(profileType)
  731. {
  732. this._profileType = profileType;
  733. InspectorBackend.registerCanvasDispatcher(this);
  734. }
  735. WebInspector.CanvasDispatcher.prototype = {
  736. /**
  737. * @param {string} frameId
  738. */
  739. contextCreated: function(frameId)
  740. {
  741. this._profileType._contextCreated(frameId);
  742. },
  743. /**
  744. * @param {NetworkAgent.FrameId=} frameId
  745. * @param {CanvasAgent.TraceLogId=} traceLogId
  746. */
  747. traceLogsRemoved: function(frameId, traceLogId)
  748. {
  749. this._profileType._traceLogsRemoved(frameId, traceLogId);
  750. }
  751. }
  752. /**
  753. * @constructor
  754. * @extends {WebInspector.ProfileHeader}
  755. * @param {!WebInspector.CanvasProfileType} type
  756. * @param {string} title
  757. * @param {number=} uid
  758. * @param {CanvasAgent.TraceLogId=} traceLogId
  759. * @param {NetworkAgent.FrameId=} frameId
  760. */
  761. WebInspector.CanvasProfileHeader = function(type, title, uid, traceLogId, frameId)
  762. {
  763. WebInspector.ProfileHeader.call(this, type, title, uid);
  764. /** @type {CanvasAgent.TraceLogId} */
  765. this._traceLogId = traceLogId || "";
  766. this._frameId = frameId;
  767. this._alive = true;
  768. this._traceLogSize = 0;
  769. }
  770. WebInspector.CanvasProfileHeader.prototype = {
  771. /**
  772. * @return {CanvasAgent.TraceLogId}
  773. */
  774. traceLogId: function()
  775. {
  776. return this._traceLogId;
  777. },
  778. /**
  779. * @return {NetworkAgent.FrameId|undefined}
  780. */
  781. frameId: function()
  782. {
  783. return this._frameId;
  784. },
  785. /**
  786. * @override
  787. * @return {WebInspector.ProfileSidebarTreeElement}
  788. */
  789. createSidebarTreeElement: function()
  790. {
  791. return new WebInspector.ProfileSidebarTreeElement(this, WebInspector.UIString("Trace Log %d"), "profile-sidebar-tree-item");
  792. },
  793. /**
  794. * @override
  795. * @param {WebInspector.ProfilesPanel} profilesPanel
  796. */
  797. createView: function(profilesPanel)
  798. {
  799. return new WebInspector.CanvasProfileView(this);
  800. },
  801. /**
  802. * @override
  803. */
  804. dispose: function()
  805. {
  806. if (this._traceLogId) {
  807. CanvasAgent.dropTraceLog(this._traceLogId);
  808. clearTimeout(this._requestStatusTimer);
  809. this._alive = false;
  810. }
  811. },
  812. /**
  813. * @param {CanvasAgent.TraceLog=} traceLog
  814. */
  815. _updateCapturingStatus: function(traceLog)
  816. {
  817. if (!this.sidebarElement || !this._traceLogId)
  818. return;
  819. if (traceLog) {
  820. this._alive = traceLog.alive;
  821. this._traceLogSize = traceLog.totalAvailableCalls;
  822. }
  823. this.sidebarElement.subtitle = this._alive ? WebInspector.UIString("Capturing\u2026 %d calls", this._traceLogSize) : WebInspector.UIString("Captured %d calls", this._traceLogSize);
  824. this.sidebarElement.wait = this._alive;
  825. if (this._alive) {
  826. clearTimeout(this._requestStatusTimer);
  827. this._requestStatusTimer = setTimeout(this._requestCapturingStatus.bind(this), WebInspector.CanvasProfileView.TraceLogPollingInterval);
  828. }
  829. },
  830. _requestCapturingStatus: function()
  831. {
  832. /**
  833. * @param {?Protocol.Error} error
  834. * @param {CanvasAgent.TraceLog} traceLog
  835. */
  836. function didReceiveTraceLog(error, traceLog)
  837. {
  838. if (error)
  839. return;
  840. this._alive = traceLog.alive;
  841. this._traceLogSize = traceLog.totalAvailableCalls;
  842. this._updateCapturingStatus();
  843. }
  844. CanvasAgent.getTraceLog(this._traceLogId, 0, 0, didReceiveTraceLog.bind(this));
  845. },
  846. __proto__: WebInspector.ProfileHeader.prototype
  847. }