Нема описа

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