暫無描述

webhtml.go 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020
  1. // Copyright 2017 Google Inc. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package driver
  15. import "html/template"
  16. // addTemplates adds a set of template definitions to templates.
  17. func addTemplates(templates *template.Template) {
  18. template.Must(templates.Parse(`
  19. {{define "css"}}
  20. <style type="text/css">
  21. * {
  22. margin: 0;
  23. padding: 0;
  24. box-sizing: border-box;
  25. }
  26. html, body {
  27. height: 100%;
  28. }
  29. body {
  30. font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
  31. font-size: 13px;
  32. line-height: 1.4;
  33. display: flex;
  34. flex-direction: column;
  35. }
  36. a {
  37. color: #2a66d9;
  38. }
  39. .header {
  40. display: flex;
  41. align-items: center;
  42. height: 44px;
  43. min-height: 44px;
  44. background-color: #eee;
  45. color: #212121;
  46. padding: 0 1rem;
  47. }
  48. .header > div {
  49. margin: 0 0.125em;
  50. }
  51. .header .title h1 {
  52. font-size: 1.75em;
  53. margin-right: 1rem;
  54. }
  55. .header .title a {
  56. color: #212121;
  57. text-decoration: none;
  58. }
  59. .header .title a:hover {
  60. text-decoration: underline;
  61. }
  62. .header .description {
  63. width: 100%;
  64. text-align: right;
  65. white-space: nowrap;
  66. }
  67. @media screen and (max-width: 799px) {
  68. .header input {
  69. display: none;
  70. }
  71. }
  72. #detailsbox {
  73. display: none;
  74. z-index: 1;
  75. position: fixed;
  76. top: 40px;
  77. right: 20px;
  78. background-color: #ffffff;
  79. box-shadow: 0 1px 5px rgba(0,0,0,.3);
  80. line-height: 24px;
  81. padding: 1em;
  82. text-align: left;
  83. }
  84. .header input {
  85. background: white url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' style='pointer-events:none;display:block;width:100%25;height:100%25;fill:#757575'%3E%3Cpath d='M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61.0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3C/svg%3E") no-repeat 4px center/20px 20px;
  86. border: 1px solid #d1d2d3;
  87. border-radius: 2px 0 0 2px;
  88. padding: 0.25em;
  89. padding-left: 28px;
  90. margin-left: 1em;
  91. font-family: 'Roboto', 'Noto', sans-serif;
  92. font-size: 1em;
  93. line-height: 24px;
  94. color: #212121;
  95. }
  96. .downArrow {
  97. border-top: .36em solid #ccc;
  98. border-left: .36em solid transparent;
  99. border-right: .36em solid transparent;
  100. margin-bottom: .05em;
  101. margin-left: .5em;
  102. transition: border-top-color 200ms;
  103. }
  104. .menu-item {
  105. height: 100%;
  106. text-transform: uppercase;
  107. font-family: 'Roboto Medium', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
  108. position: relative;
  109. }
  110. .menu-item.disabled {
  111. opacity: 0.5;
  112. }
  113. .menu-item .menu-name:hover {
  114. opacity: 0.75;
  115. }
  116. .menu-item .menu-name:hover .downArrow {
  117. border-top-color: #666;
  118. }
  119. .menu-item.disabled .menu-name:hover {
  120. opacity: 1;
  121. }
  122. .menu-item.disabled .menu-name:hover .downArrow {
  123. border-top-color: #ccc;
  124. }
  125. .menu-name {
  126. height: 100%;
  127. padding: 0 0.5em;
  128. display: flex;
  129. align-items: center;
  130. justify-content: center;
  131. }
  132. .submenu {
  133. display: none;
  134. z-index: 1;
  135. margin-top: -4px;
  136. min-width: 10em;
  137. position: absolute;
  138. left: 0px;
  139. background-color: white;
  140. box-shadow: 0 1px 5px rgba(0,0,0,.3);
  141. font-size: 100%;
  142. text-transform: none;
  143. }
  144. .menu-item, .submenu {
  145. user-select: none;
  146. -moz-user-select: none;
  147. -ms-user-select: none;
  148. -webkit-user-select: none;
  149. }
  150. .submenu hr {
  151. border: 0;
  152. border-top: 2px solid #eee;
  153. }
  154. .submenu a {
  155. display: block;
  156. padding: .5em 1em;
  157. text-decoration: none;
  158. }
  159. .submenu a:hover, .submenu a.active {
  160. color: white;
  161. background-color: #6b82d6;
  162. }
  163. .submenu a.disabled {
  164. color: gray;
  165. pointer-events: none;
  166. }
  167. #content {
  168. overflow-y: scroll;
  169. padding: 1em;
  170. }
  171. #top {
  172. overflow-y: scroll;
  173. }
  174. #graph {
  175. overflow: hidden;
  176. }
  177. #graph svg {
  178. width: 100%;
  179. height: auto;
  180. padding: 10px;
  181. }
  182. #content.source .filename {
  183. margin-top: 0;
  184. margin-bottom: 1em;
  185. font-size: 120%;
  186. }
  187. #content.source pre {
  188. margin-bottom: 3em;
  189. }
  190. table {
  191. border-spacing: 0px;
  192. width: 100%;
  193. padding-bottom: 1em;
  194. white-space: nowrap;
  195. }
  196. table thead {
  197. font-family: 'Roboto Medium', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
  198. }
  199. table tr th {
  200. background-color: #ddd;
  201. text-align: right;
  202. padding: .3em .5em;
  203. }
  204. table tr td {
  205. padding: .3em .5em;
  206. text-align: right;
  207. }
  208. #top table tr th:nth-child(6),
  209. #top table tr th:nth-child(7),
  210. #top table tr td:nth-child(6),
  211. #top table tr td:nth-child(7) {
  212. text-align: left;
  213. }
  214. #top table tr td:nth-child(6) {
  215. width: 100%;
  216. text-overflow: ellipsis;
  217. overflow: hidden;
  218. white-space: nowrap;
  219. }
  220. #flathdr1, #flathdr2, #cumhdr1, #cumhdr2, #namehdr {
  221. cursor: ns-resize;
  222. }
  223. .hilite {
  224. background-color: #ebf5fb;
  225. font-weight: bold;
  226. }
  227. </style>
  228. {{end}}
  229. {{define "header"}}
  230. <div class="header">
  231. <div class="title">
  232. <h1><a href="/">pprof</a></h1>
  233. </div>
  234. <div id="view" class="menu-item">
  235. <div class="menu-name">
  236. View
  237. <i class="downArrow"></i>
  238. </div>
  239. <div class="submenu">
  240. <a title="{{.Help.top}}" href="/top" id="topbtn">Top</a>
  241. <a title="{{.Help.graph}}" href="/" id="graphbtn">Graph</a>
  242. <a title="{{.Help.peek}}" href="/peek" id="peek">Peek</a>
  243. <a title="{{.Help.list}}" href="/source" id="list">Source</a>
  244. <a title="{{.Help.disasm}}" href="/disasm" id="disasm">Disassemble</a>
  245. </div>
  246. </div>
  247. <div id="refine" class="menu-item disabled">
  248. <div class="menu-name">
  249. Refine
  250. <i class="downArrow"></i>
  251. </div>
  252. <div class="submenu">
  253. <a title="{{.Help.focus}}" href="{{.BaseURL}}" id="focus">Focus</a>
  254. <a title="{{.Help.ignore}}" href="{{.BaseURL}}" id="ignore">Ignore</a>
  255. <a title="{{.Help.hide}}" href="{{.BaseURL}}" id="hide">Hide</a>
  256. <a title="{{.Help.show}}" href="{{.BaseURL}}" id="show">Show</a>
  257. <hr>
  258. <a title="{{.Help.reset}}" href="{{.BaseURL}}">Reset</a>
  259. </div>
  260. </div>
  261. <div>
  262. <input id="search" type="text" placeholder="Search regexp" autocomplete="off" autocapitalize="none" size=40>
  263. </div>
  264. <div class="description">
  265. <a title="{{.Help.details}}" href="#" id="details">{{.Title}}</a>
  266. <div id="detailsbox">
  267. {{range .Legend}}<div>{{.}}</div>{{end}}
  268. </div>
  269. </div>
  270. </div>
  271. <div id="errors">{{range .Errors}}<div>{{.}}</div>{{end}}</div>
  272. {{end}}
  273. {{define "graph" -}}
  274. <!DOCTYPE html>
  275. <html>
  276. <head>
  277. <meta charset="utf-8">
  278. <title>{{.Title}}</title>
  279. {{template "css" .}}
  280. </head>
  281. <body>
  282. {{template "header" .}}
  283. <div id="graph">
  284. {{.HTMLBody}}
  285. </div>
  286. {{template "script" .}}
  287. <script>viewer({{.BaseURL}}, {{.Nodes}})</script>
  288. </body>
  289. </html>
  290. {{end}}
  291. {{define "script"}}
  292. <script>
  293. // Make svg pannable and zoomable.
  294. // Call clickHandler(t) if a click event is caught by the pan event handlers.
  295. function initPanAndZoom(svg, clickHandler) {
  296. 'use strict';
  297. // Current mouse/touch handling mode
  298. const IDLE = 0
  299. const MOUSEPAN = 1
  300. const TOUCHPAN = 2
  301. const TOUCHZOOM = 3
  302. let mode = IDLE
  303. // State needed to implement zooming.
  304. let currentScale = 1.0
  305. const initWidth = svg.viewBox.baseVal.width
  306. const initHeight = svg.viewBox.baseVal.height
  307. // State needed to implement panning.
  308. let panLastX = 0 // Last event X coordinate
  309. let panLastY = 0 // Last event Y coordinate
  310. let moved = false // Have we seen significant movement
  311. let touchid = null // Current touch identifier
  312. // State needed for pinch zooming
  313. let touchid2 = null // Second id for pinch zooming
  314. let initGap = 1.0 // Starting gap between two touches
  315. let initScale = 1.0 // currentScale when pinch zoom started
  316. let centerPoint = null // Center point for scaling
  317. // Convert event coordinates to svg coordinates.
  318. function toSvg(x, y) {
  319. const p = svg.createSVGPoint()
  320. p.x = x
  321. p.y = y
  322. let m = svg.getCTM()
  323. if (m == null) m = svg.getScreenCTM() // Firefox workaround.
  324. return p.matrixTransform(m.inverse())
  325. }
  326. // Change the scaling for the svg to s, keeping the point denoted
  327. // by u (in svg coordinates]) fixed at the same screen location.
  328. function rescale(s, u) {
  329. // Limit to a good range.
  330. if (s < 0.2) s = 0.2
  331. if (s > 10.0) s = 10.0
  332. currentScale = s
  333. // svg.viewBox defines the visible portion of the user coordinate
  334. // system. So to magnify by s, divide the visible portion by s,
  335. // which will then be stretched to fit the viewport.
  336. const vb = svg.viewBox
  337. const w1 = vb.baseVal.width
  338. const w2 = initWidth / s
  339. const h1 = vb.baseVal.height
  340. const h2 = initHeight / s
  341. vb.baseVal.width = w2
  342. vb.baseVal.height = h2
  343. // We also want to adjust vb.baseVal.x so that u.x remains at same
  344. // screen X coordinate. In other words, want to change it from x1 to x2
  345. // so that:
  346. // (u.x - x1) / w1 = (u.x - x2) / w2
  347. // Simplifying that, we get
  348. // (u.x - x1) * (w2 / w1) = u.x - x2
  349. // x2 = u.x - (u.x - x1) * (w2 / w1)
  350. vb.baseVal.x = u.x - (u.x - vb.baseVal.x) * (w2 / w1)
  351. vb.baseVal.y = u.y - (u.y - vb.baseVal.y) * (h2 / h1)
  352. }
  353. function handleWheel(e) {
  354. if (e.deltaY == 0) return
  355. // Change scale factor by 1.1 or 1/1.1
  356. rescale(currentScale * (e.deltaY < 0 ? 1.1 : (1/1.1)),
  357. toSvg(e.offsetX, e.offsetY))
  358. }
  359. function setMode(m) {
  360. mode = m
  361. touchid = null
  362. touchid2 = null
  363. }
  364. function panStart(x, y) {
  365. moved = false
  366. panLastX = x
  367. panLastY = y
  368. }
  369. function panMove(x, y) {
  370. let dx = x - panLastX
  371. let dy = y - panLastY
  372. if (Math.abs(dx) <= 2 && Math.abs(dy) <= 2) return // Ignore tiny moves
  373. moved = true
  374. panLastX = x
  375. panLastY = y
  376. // Firefox workaround: get dimensions from parentNode.
  377. const swidth = svg.clientWidth || svg.parentNode.clientWidth
  378. const sheight = svg.clientHeight || svg.parentNode.clientHeight
  379. // Convert deltas from screen space to svg space.
  380. dx *= (svg.viewBox.baseVal.width / swidth)
  381. dy *= (svg.viewBox.baseVal.height / sheight)
  382. svg.viewBox.baseVal.x -= dx
  383. svg.viewBox.baseVal.y -= dy
  384. }
  385. function handleScanStart(e) {
  386. if (e.button != 0) return // Do not catch right-clicks etc.
  387. setMode(MOUSEPAN)
  388. panStart(e.clientX, e.clientY)
  389. e.preventDefault()
  390. svg.addEventListener("mousemove", handleScanMove)
  391. }
  392. function handleScanMove(e) {
  393. if (e.buttons == 0) {
  394. // Missed an end event, perhaps because mouse moved outside window.
  395. setMode(IDLE)
  396. svg.removeEventListener("mousemove", handleScanMove)
  397. return
  398. }
  399. if (mode == MOUSEPAN) panMove(e.clientX, e.clientY)
  400. }
  401. function handleScanEnd(e) {
  402. if (mode == MOUSEPAN) panMove(e.clientX, e.clientY)
  403. setMode(IDLE)
  404. svg.removeEventListener("mousemove", handleScanMove)
  405. if (!moved) clickHandler(e.target)
  406. }
  407. // Find touch object with specified identifier.
  408. function findTouch(tlist, id) {
  409. for (const t of tlist) {
  410. if (t.identifier == id) return t
  411. }
  412. return null
  413. }
  414. // Return distance between two touch points
  415. function touchGap(t1, t2) {
  416. const dx = t1.clientX - t2.clientX
  417. const dy = t1.clientY - t2.clientY
  418. return Math.hypot(dx, dy)
  419. }
  420. function handleTouchStart(e) {
  421. if (mode == IDLE && e.changedTouches.length == 1) {
  422. // Start touch based panning
  423. const t = e.changedTouches[0]
  424. setMode(TOUCHPAN)
  425. touchid = t.identifier
  426. panStart(t.clientX, t.clientY)
  427. e.preventDefault()
  428. } else if (mode == TOUCHPAN && e.touches.length == 2) {
  429. // Start pinch zooming
  430. setMode(TOUCHZOOM)
  431. const t1 = e.touches[0]
  432. const t2 = e.touches[1]
  433. touchid = t1.identifier
  434. touchid2 = t2.identifier
  435. initScale = currentScale
  436. initGap = touchGap(t1, t2)
  437. centerPoint = toSvg((t1.clientX + t2.clientX) / 2,
  438. (t1.clientY + t2.clientY) / 2)
  439. e.preventDefault()
  440. }
  441. }
  442. function handleTouchMove(e) {
  443. if (mode == TOUCHPAN) {
  444. const t = findTouch(e.changedTouches, touchid)
  445. if (t == null) return
  446. if (e.touches.length != 1) {
  447. setMode(IDLE)
  448. return
  449. }
  450. panMove(t.clientX, t.clientY)
  451. e.preventDefault()
  452. } else if (mode == TOUCHZOOM) {
  453. // Get two touches; new gap; rescale to ratio.
  454. const t1 = findTouch(e.touches, touchid)
  455. const t2 = findTouch(e.touches, touchid2)
  456. if (t1 == null || t2 == null) return
  457. const gap = touchGap(t1, t2)
  458. rescale(initScale * gap / initGap, centerPoint)
  459. e.preventDefault()
  460. }
  461. }
  462. function handleTouchEnd(e) {
  463. if (mode == TOUCHPAN) {
  464. const t = findTouch(e.changedTouches, touchid)
  465. if (t == null) return
  466. panMove(t.clientX, t.clientY)
  467. setMode(IDLE)
  468. e.preventDefault()
  469. if (!moved) clickHandler(t.target)
  470. } else if (mode == TOUCHZOOM) {
  471. setMode(IDLE)
  472. e.preventDefault()
  473. }
  474. }
  475. svg.addEventListener("mousedown", handleScanStart)
  476. svg.addEventListener("mouseup", handleScanEnd)
  477. svg.addEventListener("touchstart", handleTouchStart)
  478. svg.addEventListener("touchmove", handleTouchMove)
  479. svg.addEventListener("touchend", handleTouchEnd)
  480. svg.addEventListener("wheel", handleWheel, true)
  481. }
  482. function initMenus() {
  483. 'use strict';
  484. let activeMenu = null;
  485. let activeMenuHdr = null;
  486. function cancelActiveMenu() {
  487. if (activeMenu == null) return;
  488. activeMenu.style.display = "none";
  489. activeMenu = null;
  490. activeMenuHdr = null;
  491. }
  492. // Set click handlers on every menu header.
  493. for (const menu of document.getElementsByClassName("submenu")) {
  494. const hdr = menu.parentElement;
  495. if (hdr == null) return;
  496. if (hdr.classList.contains("disabled")) return;
  497. function showMenu(e) {
  498. // menu is a child of hdr, so this event can fire for clicks
  499. // inside menu. Ignore such clicks.
  500. if (e.target.parentElement != hdr) return;
  501. activeMenu = menu;
  502. activeMenuHdr = hdr;
  503. menu.style.display = "block";
  504. }
  505. hdr.addEventListener("mousedown", showMenu);
  506. hdr.addEventListener("touchstart", showMenu);
  507. }
  508. // If there is an active menu and a down event outside, retract the menu.
  509. for (const t of ["mousedown", "touchstart"]) {
  510. document.addEventListener(t, (e) => {
  511. // Note: to avoid unnecessary flicker, if the down event is inside
  512. // the active menu header, do not retract the menu.
  513. if (activeMenuHdr != e.target.closest(".menu-item")) {
  514. cancelActiveMenu();
  515. }
  516. }, { passive: true, capture: true });
  517. }
  518. // If there is an active menu and an up event inside, retract the menu.
  519. document.addEventListener("mouseup", (e) => {
  520. if (activeMenu == e.target.closest(".submenu")) {
  521. cancelActiveMenu();
  522. }
  523. }, { passive: true, capture: true });
  524. }
  525. function viewer(baseUrl, nodes) {
  526. 'use strict';
  527. // Elements
  528. const search = document.getElementById("search")
  529. const graph0 = document.getElementById("graph0")
  530. const svg = (graph0 == null ? null : graph0.parentElement)
  531. const toptable = document.getElementById("toptable")
  532. let regexpActive = false
  533. let selected = new Map()
  534. let origFill = new Map()
  535. let searchAlarm = null
  536. let buttonsEnabled = true
  537. function handleDetails(e) {
  538. e.preventDefault()
  539. const detailsText = document.getElementById("detailsbox")
  540. if (detailsText != null) {
  541. if (detailsText.style.display === "block") {
  542. detailsText.style.display = "none"
  543. } else {
  544. detailsText.style.display = "block"
  545. }
  546. }
  547. }
  548. function handleKey(e) {
  549. if (e.keyCode != 13) return
  550. window.location.href =
  551. updateUrl(new URL({{.BaseURL}}, window.location.href), "f")
  552. e.preventDefault()
  553. }
  554. function handleSearch() {
  555. // Delay expensive processing so a flurry of key strokes is handled once.
  556. if (searchAlarm != null) {
  557. clearTimeout(searchAlarm)
  558. }
  559. searchAlarm = setTimeout(selectMatching, 300)
  560. regexpActive = true
  561. updateButtons()
  562. }
  563. function selectMatching() {
  564. searchAlarm = null
  565. let re = null
  566. if (search.value != "") {
  567. try {
  568. re = new RegExp(search.value)
  569. } catch (e) {
  570. // TODO: Display error state in search box
  571. return
  572. }
  573. }
  574. function match(text) {
  575. return re != null && re.test(text)
  576. }
  577. // drop currently selected items that do not match re.
  578. selected.forEach(function(v, n) {
  579. if (!match(nodes[n])) {
  580. unselect(n, document.getElementById("node" + n))
  581. }
  582. })
  583. // add matching items that are not currently selected.
  584. for (let n = 0; n < nodes.length; n++) {
  585. if (!selected.has(n) && match(nodes[n])) {
  586. select(n, document.getElementById("node" + n))
  587. }
  588. }
  589. updateButtons()
  590. }
  591. function toggleSvgSelect(elem) {
  592. // Walk up to immediate child of graph0
  593. while (elem != null && elem.parentElement != graph0) {
  594. elem = elem.parentElement
  595. }
  596. if (!elem) return
  597. // Disable regexp mode.
  598. regexpActive = false
  599. const n = nodeId(elem)
  600. if (n < 0) return
  601. if (selected.has(n)) {
  602. unselect(n, elem)
  603. } else {
  604. select(n, elem)
  605. }
  606. updateButtons()
  607. }
  608. function unselect(n, elem) {
  609. if (elem == null) return
  610. selected.delete(n)
  611. setBackground(elem, false)
  612. }
  613. function select(n, elem) {
  614. if (elem == null) return
  615. selected.set(n, true)
  616. setBackground(elem, true)
  617. }
  618. function nodeId(elem) {
  619. const id = elem.id
  620. if (!id) return -1
  621. if (!id.startsWith("node")) return -1
  622. const n = parseInt(id.slice(4), 10)
  623. if (isNaN(n)) return -1
  624. if (n < 0 || n >= nodes.length) return -1
  625. return n
  626. }
  627. function setBackground(elem, set) {
  628. // Handle table row highlighting.
  629. if (elem.nodeName == "TR") {
  630. elem.classList.toggle("hilite", set)
  631. return
  632. }
  633. // Handle svg element highlighting.
  634. const p = findPolygon(elem)
  635. if (p != null) {
  636. if (set) {
  637. origFill.set(p, p.style.fill)
  638. p.style.fill = "#ccccff"
  639. } else if (origFill.has(p)) {
  640. p.style.fill = origFill.get(p)
  641. }
  642. }
  643. }
  644. function findPolygon(elem) {
  645. if (elem.localName == "polygon") return elem
  646. for (const c of elem.children) {
  647. const p = findPolygon(c)
  648. if (p != null) return p
  649. }
  650. return null
  651. }
  652. // convert a string to a regexp that matches that string.
  653. function quotemeta(str) {
  654. return str.replace(/([\\\.?+*\[\](){}|^$])/g, '\\$1')
  655. }
  656. // Update id's href to reflect current selection whenever it is
  657. // liable to be followed.
  658. function makeLinkDynamic(id) {
  659. const elem = document.getElementById(id)
  660. if (elem == null) return
  661. // Most links copy current selection into the "f" parameter,
  662. // but Refine menu links are different.
  663. let param = "f"
  664. if (id == "ignore") param = "i"
  665. if (id == "hide") param = "h"
  666. if (id == "show") param = "s"
  667. // We update on mouseenter so middle-click/right-click work properly.
  668. elem.addEventListener("mouseenter", updater)
  669. elem.addEventListener("touchstart", updater)
  670. function updater() {
  671. elem.href = updateUrl(new URL(elem.href), param)
  672. }
  673. }
  674. // Update URL to reflect current selection.
  675. function updateUrl(url, param) {
  676. url.hash = ""
  677. // The selection can be in one of two modes: regexp-based or
  678. // list-based. Construct regular expression depending on mode.
  679. let re = regexpActive
  680. ? search.value
  681. : Array.from(selected.keys()).map(key => quotemeta(nodes[key])).join("|")
  682. // Copy params from this page's URL.
  683. const params = url.searchParams
  684. for (const p of new URLSearchParams(window.location.search)) {
  685. params.set(p[0], p[1])
  686. }
  687. if (re != "") {
  688. // For focus/show, forget old parameter. For others, add to re.
  689. if (param != "f" && param != "s" && params.has(param)) {
  690. const old = params.get(param)
  691. if (old != "") {
  692. re += "|" + old
  693. }
  694. }
  695. params.set(param, re)
  696. } else {
  697. params.delete(param)
  698. }
  699. return url.toString()
  700. }
  701. function handleTopClick(e) {
  702. // Walk back until we find TR and then get the Name column (index 5)
  703. let elem = e.target
  704. while (elem != null && elem.nodeName != "TR") {
  705. elem = elem.parentElement
  706. }
  707. if (elem == null || elem.children.length < 6) return
  708. e.preventDefault()
  709. const tr = elem
  710. const td = elem.children[5]
  711. if (td.nodeName != "TD") return
  712. const name = td.innerText
  713. const index = nodes.indexOf(name)
  714. if (index < 0) return
  715. // Disable regexp mode.
  716. regexpActive = false
  717. if (selected.has(index)) {
  718. unselect(index, elem)
  719. } else {
  720. select(index, elem)
  721. }
  722. updateButtons()
  723. }
  724. function updateButtons() {
  725. const enable = (search.value != "" || selected.size != 0)
  726. if (buttonsEnabled == enable) return
  727. buttonsEnabled = enable
  728. for (const id of ["focus", "ignore", "hide", "show"]) {
  729. const link = document.getElementById(id)
  730. if (link != null) {
  731. link.classList.toggle("disabled", !enable)
  732. }
  733. }
  734. if (document.getElementById("graph") !== null) {
  735. document.getElementById("refine").classList.remove("disabled")
  736. }
  737. }
  738. // Initialize button states
  739. updateButtons()
  740. // Setup event handlers
  741. initMenus()
  742. if (svg != null) {
  743. initPanAndZoom(svg, toggleSvgSelect)
  744. }
  745. if (toptable != null) {
  746. toptable.addEventListener("mousedown", handleTopClick)
  747. toptable.addEventListener("touchstart", handleTopClick)
  748. }
  749. const ids = ["topbtn", "graphbtn", "peek", "list", "disasm",
  750. "focus", "ignore", "hide", "show"]
  751. ids.forEach(makeLinkDynamic)
  752. // Bind action to button with specified id.
  753. function addAction(id, action) {
  754. const btn = document.getElementById(id)
  755. if (btn != null) {
  756. btn.addEventListener("click", action)
  757. btn.addEventListener("touchstart", action)
  758. }
  759. }
  760. addAction("details", handleDetails)
  761. search.addEventListener("input", handleSearch)
  762. search.addEventListener("keydown", handleKey)
  763. // Give initial focus to main container so it can be scrolled using keys.
  764. const main = document.getElementById("bodycontainer")
  765. if (main) {
  766. main.focus()
  767. }
  768. }
  769. </script>
  770. {{end}}
  771. {{define "top" -}}
  772. <!DOCTYPE html>
  773. <html>
  774. <head>
  775. <meta charset="utf-8">
  776. <title>{{.Title}}</title>
  777. {{template "css" .}}
  778. <style type="text/css">
  779. </style>
  780. </head>
  781. <body>
  782. {{template "header" .}}
  783. <div id="top">
  784. <table id="toptable">
  785. <thead>
  786. <tr>
  787. <th id="flathdr1">Flat</th>
  788. <th id="flathdr2">Flat%</th>
  789. <th>Sum%</th>
  790. <th id="cumhdr1">Cum</th>
  791. <th id="cumhdr2">Cum%</th>
  792. <th id="namehdr">Name</th>
  793. <th>Inlined?</th>
  794. </tr>
  795. </thead>
  796. <tbody id="rows"></tbody>
  797. </table>
  798. </div>
  799. {{template "script" .}}
  800. <script>
  801. function makeTopTable(total, entries) {
  802. const rows = document.getElementById("rows")
  803. if (rows == null) return
  804. // Store initial index in each entry so we have stable node ids for selection.
  805. for (let i = 0; i < entries.length; i++) {
  806. entries[i].Id = "node" + i
  807. }
  808. // Which column are we currently sorted by and in what order?
  809. let currentColumn = ""
  810. let descending = false
  811. sortBy("Flat")
  812. function sortBy(column) {
  813. // Update sort criteria
  814. if (column == currentColumn) {
  815. descending = !descending // Reverse order
  816. } else {
  817. currentColumn = column
  818. descending = (column != "Name")
  819. }
  820. // Sort according to current criteria.
  821. function cmp(a, b) {
  822. const av = a[currentColumn]
  823. const bv = b[currentColumn]
  824. if (av < bv) return -1
  825. if (av > bv) return +1
  826. return 0
  827. }
  828. entries.sort(cmp)
  829. if (descending) entries.reverse()
  830. function addCell(tr, val) {
  831. const td = document.createElement('td')
  832. td.textContent = val
  833. tr.appendChild(td)
  834. }
  835. function percent(v) {
  836. return (v * 100.0 / total).toFixed(2) + "%"
  837. }
  838. // Generate rows
  839. const fragment = document.createDocumentFragment()
  840. let sum = 0
  841. for (const row of entries) {
  842. const tr = document.createElement('tr')
  843. tr.id = row.Id
  844. sum += row.Flat
  845. addCell(tr, row.FlatFormat)
  846. addCell(tr, percent(row.Flat))
  847. addCell(tr, percent(sum))
  848. addCell(tr, row.CumFormat)
  849. addCell(tr, percent(row.Cum))
  850. addCell(tr, row.Name)
  851. addCell(tr, row.InlineLabel)
  852. fragment.appendChild(tr)
  853. }
  854. rows.textContent = '' // Remove old rows
  855. rows.appendChild(fragment)
  856. }
  857. // Make different column headers trigger sorting.
  858. function bindSort(id, column) {
  859. const hdr = document.getElementById(id)
  860. if (hdr == null) return
  861. const fn = function() { sortBy(column) }
  862. hdr.addEventListener("click", fn)
  863. hdr.addEventListener("touch", fn)
  864. }
  865. bindSort("flathdr1", "Flat")
  866. bindSort("flathdr2", "Flat")
  867. bindSort("cumhdr1", "Cum")
  868. bindSort("cumhdr2", "Cum")
  869. bindSort("namehdr", "Name")
  870. }
  871. viewer({{.BaseURL}}, {{.Nodes}})
  872. makeTopTable({{.Total}}, {{.Top}})
  873. </script>
  874. </body>
  875. </html>
  876. {{end}}
  877. {{define "sourcelisting" -}}
  878. <!DOCTYPE html>
  879. <html>
  880. <head>
  881. <meta charset="utf-8">
  882. <title>{{.Title}}</title>
  883. {{template "css" .}}
  884. {{template "weblistcss" .}}
  885. {{template "weblistjs" .}}
  886. </head>
  887. <body>
  888. {{template "header" .}}
  889. <div id="content" class="source">
  890. {{.HTMLBody}}
  891. </div>
  892. {{template "script" .}}
  893. <script>viewer({{.BaseURL}}, null)</script>
  894. </body>
  895. </html>
  896. {{end}}
  897. {{define "plaintext" -}}
  898. <!DOCTYPE html>
  899. <html>
  900. <head>
  901. <meta charset="utf-8">
  902. <title>{{.Title}}</title>
  903. {{template "css" .}}
  904. </head>
  905. <body>
  906. {{template "header" .}}
  907. <div id="content">
  908. <pre>
  909. {{.TextBody}}
  910. </pre>
  911. </div>
  912. {{template "script" .}}
  913. <script>viewer({{.BaseURL}}, null)</script>
  914. </body>
  915. </html>
  916. {{end}}
  917. `))
  918. }