CSSSelectorProfileView.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. /*
  2. * Copyright (C) 2011 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
  6. * are met:
  7. * 1. Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * 2. Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. *
  13. * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
  14. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  15. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  16. * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
  17. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  18. * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  19. * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  20. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
  21. * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  22. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  23. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  24. */
  25. /**
  26. * @constructor
  27. * @extends WebInspector.DataGridNode
  28. * @param {WebInspector.CSSSelectorProfileView} profileView
  29. * @param {CSSAgent.SelectorProfile} data
  30. */
  31. WebInspector.CSSSelectorDataGridNode = function(profileView, data)
  32. {
  33. WebInspector.DataGridNode.call(this, data, false);
  34. this._profileView = profileView;
  35. }
  36. WebInspector.CSSSelectorDataGridNode.prototype = {
  37. get data()
  38. {
  39. var data = {};
  40. data.selector = this._data.selector;
  41. data.matches = this._data.matchCount;
  42. if (this._profileView.showTimeAsPercent.get())
  43. data.time = Number(this._data.timePercent).toFixed(1) + "%";
  44. else
  45. data.time = Number.secondsToString(this._data.time / 1000, true);
  46. return data;
  47. },
  48. get rawData()
  49. {
  50. return this._data;
  51. },
  52. createCell: function(columnIdentifier)
  53. {
  54. var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier);
  55. if (columnIdentifier === "selector" && cell.firstChild) {
  56. cell.firstChild.title = this.rawData.selector;
  57. return cell;
  58. }
  59. if (columnIdentifier !== "source")
  60. return cell;
  61. cell.removeChildren();
  62. if (this.rawData.url) {
  63. var wrapperDiv = cell.createChild("div");
  64. wrapperDiv.appendChild(WebInspector.linkifyResourceAsNode(this.rawData.url, this.rawData.lineNumber));
  65. }
  66. return cell;
  67. },
  68. __proto__: WebInspector.DataGridNode.prototype
  69. }
  70. /**
  71. * @constructor
  72. * @extends WebInspector.View
  73. * @param {CSSAgent.SelectorProfile} profile
  74. */
  75. WebInspector.CSSSelectorProfileView = function(profile)
  76. {
  77. WebInspector.View.call(this);
  78. this.element.addStyleClass("profile-view");
  79. this.showTimeAsPercent = WebInspector.settings.createSetting("selectorProfilerShowTimeAsPercent", true);
  80. var columns = [
  81. {id: "selector", title: WebInspector.UIString("Selector"), width: "550px", sortable: true},
  82. {id: "source", title: WebInspector.UIString("Source"), width: "100px", sortable: true},
  83. {id: "time", title: WebInspector.UIString("Total"), width: "72px", sort: WebInspector.DataGrid.Order.Descending, sortable: true},
  84. {id: "matches", title: WebInspector.UIString("Matches"), width: "72px", sortable: true}
  85. ];
  86. this.dataGrid = new WebInspector.DataGrid(columns);
  87. this.dataGrid.element.addStyleClass("selector-profile-view");
  88. this.dataGrid.addEventListener(WebInspector.DataGrid.Events.SortingChanged, this._sortProfile, this);
  89. this.dataGrid.element.addEventListener("mousedown", this._mouseDownInDataGrid.bind(this), true);
  90. this.dataGrid.show(this.element);
  91. this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item");
  92. this.percentButton.addEventListener("click", this._percentClicked, this);
  93. this.profile = profile;
  94. this._createProfileNodes();
  95. this._sortProfile();
  96. this._updatePercentButton();
  97. }
  98. WebInspector.CSSSelectorProfileView.prototype = {
  99. statusBarItems: function()
  100. {
  101. return [this.percentButton.element];
  102. },
  103. get profile()
  104. {
  105. return this._profile;
  106. },
  107. set profile(profile)
  108. {
  109. this._profile = profile;
  110. },
  111. _createProfileNodes: function()
  112. {
  113. var data = this.profile.data;
  114. if (!data) {
  115. // The profiler may have been terminated with the "Clear all profiles." button.
  116. return;
  117. }
  118. this.profile.children = [];
  119. for (var i = 0; i < data.length; ++i) {
  120. data[i].timePercent = data[i].time * 100 / this.profile.totalTime;
  121. var node = new WebInspector.CSSSelectorDataGridNode(this, data[i]);
  122. this.profile.children.push(node);
  123. }
  124. },
  125. rebuildGridItems: function()
  126. {
  127. this.dataGrid.rootNode().removeChildren();
  128. var children = this.profile.children;
  129. var count = children.length;
  130. for (var index = 0; index < count; ++index)
  131. this.dataGrid.rootNode().appendChild(children[index]);
  132. },
  133. refreshData: function()
  134. {
  135. var child = this.dataGrid.rootNode().children[0];
  136. while (child) {
  137. child.refresh();
  138. child = child.traverseNextNode(false, null, true);
  139. }
  140. },
  141. refreshShowAsPercents: function()
  142. {
  143. this._updatePercentButton();
  144. this.refreshData();
  145. },
  146. _percentClicked: function(event)
  147. {
  148. this.showTimeAsPercent.set(!this.showTimeAsPercent.get());
  149. this.refreshShowAsPercents();
  150. },
  151. _updatePercentButton: function()
  152. {
  153. if (this.showTimeAsPercent.get()) {
  154. this.percentButton.title = WebInspector.UIString("Show absolute times.");
  155. this.percentButton.toggled = true;
  156. } else {
  157. this.percentButton.title = WebInspector.UIString("Show times as percentages.");
  158. this.percentButton.toggled = false;
  159. }
  160. },
  161. _sortProfile: function()
  162. {
  163. var sortAscending = this.dataGrid.isSortOrderAscending();
  164. var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier();
  165. function selectorComparator(a, b)
  166. {
  167. var result = b.rawData.selector.compareTo(a.rawData.selector);
  168. return sortAscending ? -result : result;
  169. }
  170. function sourceComparator(a, b)
  171. {
  172. var aRawData = a.rawData;
  173. var bRawData = b.rawData;
  174. var result = bRawData.url.compareTo(aRawData.url);
  175. if (!result)
  176. result = bRawData.lineNumber - aRawData.lineNumber;
  177. return sortAscending ? -result : result;
  178. }
  179. function timeComparator(a, b)
  180. {
  181. const result = b.rawData.time - a.rawData.time;
  182. return sortAscending ? -result : result;
  183. }
  184. function matchesComparator(a, b)
  185. {
  186. const result = b.rawData.matchCount - a.rawData.matchCount;
  187. return sortAscending ? -result : result;
  188. }
  189. var comparator;
  190. switch (sortColumnIdentifier) {
  191. case "time":
  192. comparator = timeComparator;
  193. break;
  194. case "matches":
  195. comparator = matchesComparator;
  196. break;
  197. case "selector":
  198. comparator = selectorComparator;
  199. break;
  200. case "source":
  201. comparator = sourceComparator;
  202. break;
  203. }
  204. this.profile.children.sort(comparator);
  205. this.rebuildGridItems();
  206. },
  207. _mouseDownInDataGrid: function(event)
  208. {
  209. if (event.detail < 2)
  210. return;
  211. var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
  212. if (!cell)
  213. return;
  214. if (cell.hasStyleClass("time-column"))
  215. this.showTimeAsPercent.set(!this.showTimeAsPercent.get());
  216. else
  217. return;
  218. this.refreshShowAsPercents();
  219. event.consume(true);
  220. },
  221. __proto__: WebInspector.View.prototype
  222. }
  223. /**
  224. * @constructor
  225. * @extends {WebInspector.ProfileType}
  226. */
  227. WebInspector.CSSSelectorProfileType = function()
  228. {
  229. WebInspector.ProfileType.call(this, WebInspector.CSSSelectorProfileType.TypeId, WebInspector.UIString("Collect CSS Selector Profile"));
  230. this._recording = false;
  231. this._profileUid = 1;
  232. WebInspector.CSSSelectorProfileType.instance = this;
  233. }
  234. WebInspector.CSSSelectorProfileType.TypeId = "SELECTOR";
  235. WebInspector.CSSSelectorProfileType.prototype = {
  236. get buttonTooltip()
  237. {
  238. return this._recording ? WebInspector.UIString("Stop CSS selector profiling.") : WebInspector.UIString("Start CSS selector profiling.");
  239. },
  240. /**
  241. * @override
  242. * @return {boolean}
  243. */
  244. buttonClicked: function()
  245. {
  246. if (this._recording) {
  247. this._stopRecordingProfile();
  248. return false;
  249. } else {
  250. this._startRecordingProfile();
  251. return true;
  252. }
  253. },
  254. get treeItemTitle()
  255. {
  256. return WebInspector.UIString("CSS SELECTOR PROFILES");
  257. },
  258. get description()
  259. {
  260. return WebInspector.UIString("CSS selector profiles show how long the selector matching has taken in total and how many times a certain selector has matched DOM elements (the results are approximate due to matching algorithm optimizations.)");
  261. },
  262. reset: function()
  263. {
  264. this._profileUid = 1;
  265. },
  266. setRecordingProfile: function(isProfiling)
  267. {
  268. this._recording = isProfiling;
  269. },
  270. _startRecordingProfile: function()
  271. {
  272. this._recording = true;
  273. CSSAgent.startSelectorProfiler();
  274. },
  275. _stopRecordingProfile: function()
  276. {
  277. /**
  278. * @param {?Protocol.Error} error
  279. * @param {CSSAgent.SelectorProfile} profile
  280. */
  281. function callback(error, profile)
  282. {
  283. if (error)
  284. return;
  285. var uid = this._profileUid++;
  286. var title = WebInspector.UIString("Profile %d", uid) + String.sprintf(" (%s)", Number.secondsToString(profile.totalTime / 1000));
  287. this.addProfile(new WebInspector.CSSProfileHeader(this, title, uid, profile));
  288. }
  289. this._recording = false;
  290. CSSAgent.stopSelectorProfiler(callback.bind(this));
  291. },
  292. /**
  293. * @override
  294. * @param {string=} title
  295. * @return {WebInspector.ProfileHeader}
  296. */
  297. createTemporaryProfile: function(title)
  298. {
  299. title = title || WebInspector.UIString("Recording\u2026");
  300. return new WebInspector.CSSProfileHeader(this, title);
  301. },
  302. __proto__: WebInspector.ProfileType.prototype
  303. }
  304. /**
  305. * @constructor
  306. * @extends {WebInspector.ProfileHeader}
  307. * @param {!WebInspector.CSSSelectorProfileType} type
  308. * @param {string} title
  309. * @param {number=} uid
  310. * @param {CSSAgent.SelectorProfile=} protocolData
  311. */
  312. WebInspector.CSSProfileHeader = function(type, title, uid, protocolData)
  313. {
  314. WebInspector.ProfileHeader.call(this, type, title, uid);
  315. this._protocolData = protocolData;
  316. }
  317. WebInspector.CSSProfileHeader.prototype = {
  318. /**
  319. * @override
  320. */
  321. createSidebarTreeElement: function()
  322. {
  323. return new WebInspector.ProfileSidebarTreeElement(this, this.title, "profile-sidebar-tree-item");
  324. },
  325. /**
  326. * @override
  327. * @param {WebInspector.ProfilesPanel} profilesPanel
  328. */
  329. createView: function(profilesPanel)
  330. {
  331. var profile = /** @type {CSSAgent.SelectorProfile} */ (this._protocolData);
  332. return new WebInspector.CSSSelectorProfileView(profile);
  333. },
  334. __proto__: WebInspector.ProfileHeader.prototype
  335. }