DOMCountersGraph.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. /*
  2. * Copyright (C) 2013 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.MemoryStatistics}
  33. * @param {WebInspector.TimelinePanel} timelinePanel
  34. * @param {WebInspector.TimelineModel} model
  35. * @param {number} sidebarWidth
  36. */
  37. WebInspector.DOMCountersGraph = function(timelinePanel, model, sidebarWidth)
  38. {
  39. WebInspector.MemoryStatistics.call(this, timelinePanel, model, sidebarWidth);
  40. }
  41. /**
  42. * @constructor
  43. * @extends {WebInspector.CounterUIBase}
  44. * @param {WebInspector.DOMCountersGraph} memoryCountersPane
  45. * @param {string} title
  46. * @param {string} currentValueLabel
  47. * @param {Array.<number>} rgb
  48. * @param {function(WebInspector.DOMCountersGraph.Counter):number} valueGetter
  49. */
  50. WebInspector.DOMCounterUI = function(memoryCountersPane, title, currentValueLabel, rgb, valueGetter)
  51. {
  52. var swatchColor = "rgb(" + rgb.join(",") + ")";
  53. WebInspector.CounterUIBase.call(this, memoryCountersPane, title, swatchColor, valueGetter)
  54. this._range = this._swatch.element.createChild("span");
  55. this._value = memoryCountersPane._currentValuesBar.createChild("span", "memory-counter-value");
  56. this._value.style.color = swatchColor;
  57. this._currentValueLabel = currentValueLabel;
  58. this.graphColor = "rgba(" + rgb.join(",") + ",0.8)";
  59. this.graphYValues = [];
  60. }
  61. /**
  62. * @constructor
  63. * @extends {WebInspector.MemoryStatistics.Counter}
  64. * @param {number} time
  65. * @param {number} documentCount
  66. * @param {number} nodeCount
  67. * @param {number} listenerCount
  68. */
  69. WebInspector.DOMCountersGraph.Counter = function(time, documentCount, nodeCount, listenerCount)
  70. {
  71. WebInspector.MemoryStatistics.Counter.call(this, time);
  72. this.documentCount = documentCount;
  73. this.nodeCount = nodeCount;
  74. this.listenerCount = listenerCount;
  75. }
  76. WebInspector.DOMCounterUI.prototype = {
  77. /**
  78. * @param {number} minValue
  79. * @param {number} maxValue
  80. */
  81. setRange: function(minValue, maxValue)
  82. {
  83. this._range.textContent = WebInspector.UIString("[ %d - %d ]", minValue, maxValue);
  84. },
  85. updateCurrentValue: function(countersEntry)
  86. {
  87. this._value.textContent = WebInspector.UIString(this._currentValueLabel, this.valueGetter(countersEntry));
  88. },
  89. clearCurrentValueAndMarker: function(ctx)
  90. {
  91. this._value.textContent = "";
  92. this.restoreImageUnderMarker(ctx);
  93. },
  94. /**
  95. * @param {CanvasRenderingContext2D} ctx
  96. * @param {number} x
  97. * @param {number} y
  98. * @param {number} radius
  99. */
  100. saveImageUnderMarker: function(ctx, x, y, radius)
  101. {
  102. const w = radius + 1;
  103. var imageData = ctx.getImageData(x - w, y - w, 2 * w, 2 * w);
  104. this._imageUnderMarker = {
  105. x: x - w,
  106. y: y - w,
  107. imageData: imageData
  108. };
  109. },
  110. /**
  111. * @param {CanvasRenderingContext2D} ctx
  112. */
  113. restoreImageUnderMarker: function(ctx)
  114. {
  115. if (!this.visible)
  116. return;
  117. if (this._imageUnderMarker)
  118. ctx.putImageData(this._imageUnderMarker.imageData, this._imageUnderMarker.x, this._imageUnderMarker.y);
  119. this.discardImageUnderMarker();
  120. },
  121. discardImageUnderMarker: function()
  122. {
  123. delete this._imageUnderMarker;
  124. },
  125. __proto__: WebInspector.CounterUIBase.prototype
  126. }
  127. WebInspector.DOMCountersGraph.prototype = {
  128. _createCurrentValuesBar: function()
  129. {
  130. this._currentValuesBar = this._canvasContainer.createChild("div");
  131. this._currentValuesBar.id = "counter-values-bar";
  132. this._canvasContainer.addStyleClass("dom-counters");
  133. },
  134. /**
  135. * @return {Array.<WebInspector.DOMCounterUI>}
  136. */
  137. _createCounterUIList: function()
  138. {
  139. function getDocumentCount(entry)
  140. {
  141. return entry.documentCount;
  142. }
  143. function getNodeCount(entry)
  144. {
  145. return entry.nodeCount;
  146. }
  147. function getListenerCount(entry)
  148. {
  149. return entry.listenerCount;
  150. }
  151. return [
  152. new WebInspector.DOMCounterUI(this, "Document Count", "Documents: %d", [100, 0, 0], getDocumentCount),
  153. new WebInspector.DOMCounterUI(this, "DOM Node Count", "Nodes: %d", [0, 100, 0], getNodeCount),
  154. new WebInspector.DOMCounterUI(this, "Event Listener Count", "Listeners: %d", [0, 0, 100], getListenerCount)
  155. ];
  156. },
  157. _canvasHeight: function()
  158. {
  159. return this._canvasContainer.offsetHeight - this._currentValuesBar.offsetHeight;
  160. },
  161. /**
  162. * @param {WebInspector.Event} event
  163. */
  164. _onRecordAdded: function(event)
  165. {
  166. function addStatistics(record)
  167. {
  168. var counters = record["counters"];
  169. if (!counters)
  170. return;
  171. this._counters.push(new WebInspector.DOMCountersGraph.Counter(
  172. record.endTime || record.startTime,
  173. counters["documents"],
  174. counters["nodes"],
  175. counters["jsEventListeners"]
  176. ));
  177. }
  178. WebInspector.TimelinePresentationModel.forAllRecords([event.data], null, addStatistics.bind(this));
  179. },
  180. _draw: function()
  181. {
  182. WebInspector.MemoryStatistics.prototype._draw.call(this);
  183. for (var i = 0; i < this._counterUI.length; i++)
  184. this._drawGraph(this._counterUI[i]);
  185. },
  186. /**
  187. * @param {CanvasRenderingContext2D} ctx
  188. */
  189. _restoreImageUnderMarker: function(ctx)
  190. {
  191. for (var i = 0; i < this._counterUI.length; i++) {
  192. var counterUI = this._counterUI[i];
  193. if (!counterUI.visible)
  194. continue;
  195. counterUI.restoreImageUnderMarker(ctx);
  196. }
  197. },
  198. /**
  199. * @param {CanvasRenderingContext2D} ctx
  200. * @param {number} x
  201. * @param {number} index
  202. */
  203. _saveImageUnderMarker: function(ctx, x, index)
  204. {
  205. const radius = 2;
  206. for (var i = 0; i < this._counterUI.length; i++) {
  207. var counterUI = this._counterUI[i];
  208. if (!counterUI.visible)
  209. continue;
  210. var y = counterUI.graphYValues[index];
  211. counterUI.saveImageUnderMarker(ctx, x, y, radius);
  212. }
  213. },
  214. /**
  215. * @param {CanvasRenderingContext2D} ctx
  216. * @param {number} x
  217. * @param {number} index
  218. */
  219. _drawMarker: function(ctx, x, index)
  220. {
  221. this._saveImageUnderMarker(ctx, x, index);
  222. const radius = 2;
  223. for (var i = 0; i < this._counterUI.length; i++) {
  224. var counterUI = this._counterUI[i];
  225. if (!counterUI.visible)
  226. continue;
  227. var y = counterUI.graphYValues[index];
  228. ctx.beginPath();
  229. ctx.arc(x, y, radius, 0, Math.PI * 2, true);
  230. ctx.lineWidth = 1;
  231. ctx.fillStyle = counterUI.graphColor;
  232. ctx.strokeStyle = counterUI.graphColor;
  233. ctx.fill();
  234. ctx.stroke();
  235. ctx.closePath();
  236. }
  237. },
  238. /**
  239. * @param {WebInspector.CounterUIBase} counterUI
  240. */
  241. _drawGraph: function(counterUI)
  242. {
  243. var canvas = this._canvas;
  244. var ctx = canvas.getContext("2d");
  245. var width = canvas.width;
  246. var height = this._clippedHeight;
  247. var originY = this._originY;
  248. var valueGetter = counterUI.valueGetter;
  249. if (!this._counters.length)
  250. return;
  251. var maxValue;
  252. var minValue;
  253. for (var i = this._minimumIndex; i <= this._maximumIndex; i++) {
  254. var value = valueGetter(this._counters[i]);
  255. if (minValue === undefined || value < minValue)
  256. minValue = value;
  257. if (maxValue === undefined || value > maxValue)
  258. maxValue = value;
  259. }
  260. counterUI.setRange(minValue, maxValue);
  261. if (!counterUI.visible)
  262. return;
  263. var yValues = counterUI.graphYValues;
  264. yValues.length = this._counters.length;
  265. var maxYRange = maxValue - minValue;
  266. var yFactor = maxYRange ? height / (maxYRange) : 1;
  267. ctx.beginPath();
  268. var currentY = originY + (height - (valueGetter(this._counters[this._minimumIndex]) - minValue) * yFactor);
  269. ctx.moveTo(0, currentY);
  270. for (var i = this._minimumIndex; i <= this._maximumIndex; i++) {
  271. var x = this._counters[i].x;
  272. ctx.lineTo(x, currentY);
  273. currentY = originY + (height - (valueGetter(this._counters[i]) - minValue) * yFactor);
  274. ctx.lineTo(x, currentY);
  275. yValues[i] = currentY;
  276. }
  277. ctx.lineTo(width, currentY);
  278. ctx.lineWidth = 1;
  279. ctx.strokeStyle = counterUI.graphColor;
  280. ctx.stroke();
  281. ctx.closePath();
  282. },
  283. _discardImageUnderMarker: function()
  284. {
  285. for (var i = 0; i < this._counterUI.length; i++)
  286. this._counterUI[i].discardImageUnderMarker();
  287. },
  288. __proto__: WebInspector.MemoryStatistics.prototype
  289. }