Açıklama Yok

webhtml.go 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763
  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. // webTemplate defines a collection of related templates:
  17. // css
  18. // header
  19. // script
  20. // graph
  21. // top
  22. var webTemplate = template.Must(template.New("web").Parse(`
  23. {{define "css"}}
  24. <style type="text/css">
  25. html, body {
  26. height: 100%;
  27. min-height: 100%;
  28. margin: 0px;
  29. }
  30. body {
  31. width: 100%;
  32. height: 100%;
  33. min-height: 100%;
  34. overflow: hidden;
  35. }
  36. #graphcontainer {
  37. display: flex;
  38. flex-direction: column;
  39. height: 100%;
  40. min-height: 100%;
  41. width: 100%;
  42. min-width: 100%;
  43. margin: 0px;
  44. }
  45. #graph {
  46. flex: 1 1 auto;
  47. overflow: hidden;
  48. }
  49. svg {
  50. width: 100%;
  51. height: auto;
  52. }
  53. button {
  54. margin-top: 5px;
  55. margin-bottom: 5px;
  56. }
  57. #detailtext {
  58. display: none;
  59. position: fixed;
  60. top: 20px;
  61. right: 10px;
  62. background-color: #ffffff;
  63. min-width: 160px;
  64. border: 1px solid #888;
  65. box-shadow: 4px 4px 4px 0px rgba(0,0,0,0.2);
  66. z-index: 1;
  67. }
  68. #closedetails {
  69. float: right;
  70. margin: 2px;
  71. }
  72. #home {
  73. font-size: 14pt;
  74. padding-left: 0.5em;
  75. padding-right: 0.5em;
  76. float: right;
  77. }
  78. .menubar {
  79. display: inline-block;
  80. background-color: #f8f8f8;
  81. border: 1px solid #ccc;
  82. width: 100%;
  83. }
  84. .menu-header {
  85. position: relative;
  86. display: inline-block;
  87. padding: 2px 2px;
  88. cursor: default;
  89. font-size: 14pt;
  90. }
  91. .menu {
  92. display: none;
  93. position: absolute;
  94. background-color: #f8f8f8;
  95. border: 1px solid #888;
  96. box-shadow: 4px 4px 4px 0px rgba(0,0,0,0.2);
  97. z-index: 1;
  98. margin-top: 2px;
  99. left: 0px;
  100. min-width: 5em;
  101. }
  102. .menu hr {
  103. background-color: #fff;
  104. margin-top: 0px;
  105. margin-bottom: 0px;
  106. }
  107. .menu button {
  108. display: block;
  109. width: 100%;
  110. margin: 0px;
  111. text-align: left;
  112. padding-left: 2px;
  113. background-color: #fff;
  114. font-size: 12pt;
  115. border: none;
  116. }
  117. .menu-header:hover {
  118. background-color: #ccc;
  119. }
  120. .menu-header:hover .menu {
  121. display: block;
  122. }
  123. .menu button:hover {
  124. background-color: #ccc;
  125. }
  126. #searchbox {
  127. margin-left: 10pt;
  128. }
  129. #topcontainer {
  130. width: 100%;
  131. height: 100%;
  132. max-height: 100%;
  133. overflow: scroll;
  134. }
  135. #toptable {
  136. border-spacing: 0px;
  137. width: 100%;
  138. }
  139. #toptable tr th {
  140. border-bottom: 1px solid black;
  141. text-align: right;
  142. padding-left: 1em;
  143. }
  144. #toptable tr th:nth-child(6) { text-align: left; }
  145. #toptable tr th:nth-child(7) { text-align: left; }
  146. #toptable tr td {
  147. padding-left: 1em;
  148. font: monospace;
  149. text-align: right;
  150. white-space: nowrap;
  151. cursor: default;
  152. }
  153. #toptable tr td:nth-child(6) {
  154. text-align: left;
  155. max-width: 30em; // Truncate very long names
  156. overflow: hidden;
  157. }
  158. #toptable tr td:nth-child(7) { text-align: left; }
  159. .hilite {
  160. background-color: #ccf;
  161. }
  162. </style>
  163. {{end}}
  164. {{define "header"}}
  165. <div id="detailtext">
  166. <button id="closedetails">Close</button>
  167. {{range .Legend}}<div>{{.}}</div>{{end}}
  168. </div>
  169. <div class="menubar">
  170. <div class="menu-header">
  171. View
  172. <div class="menu">
  173. {{if (ne .Type "top")}}
  174. <button title="{{.Help.top}}" id="topbtn">Top</button>
  175. {{end}}
  176. {{if (ne .Type "dot")}}
  177. <button title="{{.Help.graph}}" id="graphbtn">Graph</button>
  178. {{end}}
  179. <hr>
  180. <button title="{{.Help.details}}" id="details">Details</button>
  181. </div>
  182. </div>
  183. <div class="menu-header">
  184. Functions
  185. <div class="menu">
  186. <button title="{{.Help.peek}}" id="peek">Peek</button>
  187. <button title="{{.Help.list}}" id="list">List</button>
  188. <button title="{{.Help.disasm}}" id="disasm">Disassemble</button>
  189. </div>
  190. </div>
  191. <div class="menu-header">
  192. Refine
  193. <div class="menu">
  194. <button title="{{.Help.focus}}" id="focus">Focus</button>
  195. <button title="{{.Help.ignore}}" id="ignore">Ignore</button>
  196. <button title="{{.Help.hide}}" id="hide">Hide</button>
  197. <button title="{{.Help.show}}" id="show">Show</button>
  198. <hr>
  199. <button title="{{.Help.reset}}" id="reset">Reset</button>
  200. </div>
  201. </div>
  202. <input id="searchbox" type="text" placeholder="Search regexp" autocomplete="off" autocapitalize="none" size=40>
  203. <span id="home">{{.Title}}</span>
  204. </div> <!-- menubar -->
  205. <div id="errors">{{range .Errors}}<div>{{.}}</div>{{end}}</div>
  206. {{end}}
  207. {{define "graph" -}}
  208. <!DOCTYPE html>
  209. <html>
  210. <head>
  211. <meta charset="utf-8">
  212. <title>{{.Title}}</title>
  213. {{template "css" .}}
  214. </head>
  215. <body>
  216. {{template "header" .}}
  217. <div id="graphcontainer">
  218. <div id="graph">
  219. {{.Svg}}
  220. </div>
  221. </div>
  222. {{template "script" .}}
  223. <script>viewer({{.BaseURL}}, {{.Nodes}})</script>
  224. </body>
  225. </html>
  226. {{end}}
  227. {{define "script"}}
  228. <script>
  229. // Make svg pannable and zoomable.
  230. // Call clickHandler(t) if a click event is caught by the pan event handlers.
  231. function initPanAndZoom(svg, clickHandler) {
  232. 'use strict';
  233. // Current mouse/touch handling mode
  234. const IDLE = 0
  235. const MOUSEPAN = 1
  236. const TOUCHPAN = 2
  237. const TOUCHZOOM = 3
  238. let mode = IDLE
  239. // State needed to implement zooming.
  240. let currentScale = 1.0
  241. const initWidth = svg.viewBox.baseVal.width
  242. const initHeight = svg.viewBox.baseVal.height
  243. // State needed to implement panning.
  244. let panLastX = 0 // Last event X coordinate
  245. let panLastY = 0 // Last event Y coordinate
  246. let moved = false // Have we seen significant movement
  247. let touchid = null // Current touch identifier
  248. // State needed for pinch zooming
  249. let touchid2 = null // Second id for pinch zooming
  250. let initGap = 1.0 // Starting gap between two touches
  251. let initScale = 1.0 // currentScale when pinch zoom started
  252. let centerPoint = null // Center point for scaling
  253. // Convert event coordinates to svg coordinates.
  254. function toSvg(x, y) {
  255. const p = svg.createSVGPoint()
  256. p.x = x
  257. p.y = y
  258. let m = svg.getCTM()
  259. if (m == null) m = svg.getScreenCTM() // Firefox workaround.
  260. return p.matrixTransform(m.inverse())
  261. }
  262. // Change the scaling for the svg to s, keeping the point denoted
  263. // by u (in svg coordinates]) fixed at the same screen location.
  264. function rescale(s, u) {
  265. // Limit to a good range.
  266. if (s < 0.2) s = 0.2
  267. if (s > 10.0) s = 10.0
  268. currentScale = s
  269. // svg.viewBox defines the visible portion of the user coordinate
  270. // system. So to magnify by s, divide the visible portion by s,
  271. // which will then be stretched to fit the viewport.
  272. const vb = svg.viewBox
  273. const w1 = vb.baseVal.width
  274. const w2 = initWidth / s
  275. const h1 = vb.baseVal.height
  276. const h2 = initHeight / s
  277. vb.baseVal.width = w2
  278. vb.baseVal.height = h2
  279. // We also want to adjust vb.baseVal.x so that u.x remains at same
  280. // screen X coordinate. In other words, want to change it from x1 to x2
  281. // so that:
  282. // (u.x - x1) / w1 = (u.x - x2) / w2
  283. // Simplifying that, we get
  284. // (u.x - x1) * (w2 / w1) = u.x - x2
  285. // x2 = u.x - (u.x - x1) * (w2 / w1)
  286. vb.baseVal.x = u.x - (u.x - vb.baseVal.x) * (w2 / w1)
  287. vb.baseVal.y = u.y - (u.y - vb.baseVal.y) * (h2 / h1)
  288. }
  289. function handleWheel(e) {
  290. if (e.deltaY == 0) return
  291. // Change scale factor by 1.1 or 1/1.1
  292. rescale(currentScale * (e.deltaY < 0 ? 1.1 : (1/1.1)),
  293. toSvg(e.offsetX, e.offsetY))
  294. }
  295. function setMode(m) {
  296. mode = m
  297. touchid = null
  298. touchid2 = null
  299. }
  300. function panStart(x, y) {
  301. moved = false
  302. panLastX = x
  303. panLastY = y
  304. }
  305. function panMove(x, y) {
  306. let dx = x - panLastX
  307. let dy = y - panLastY
  308. if (Math.abs(dx) <= 2 && Math.abs(dy) <= 2) return // Ignore tiny moves
  309. moved = true
  310. panLastX = x
  311. panLastY = y
  312. // Firefox workaround: get dimensions from parentNode.
  313. const swidth = svg.clientWidth || svg.parentNode.clientWidth
  314. const sheight = svg.clientHeight || svg.parentNode.clientHeight
  315. // Convert deltas from screen space to svg space.
  316. dx *= (svg.viewBox.baseVal.width / swidth)
  317. dy *= (svg.viewBox.baseVal.height / sheight)
  318. svg.viewBox.baseVal.x -= dx
  319. svg.viewBox.baseVal.y -= dy
  320. }
  321. function handleScanStart(e) {
  322. if (e.button != 0) return // Do not catch right-clicks etc.
  323. setMode(MOUSEPAN)
  324. panStart(e.clientX, e.clientY)
  325. e.preventDefault()
  326. svg.addEventListener("mousemove", handleScanMove)
  327. }
  328. function handleScanMove(e) {
  329. if (e.buttons == 0) {
  330. // Missed an end event, perhaps because mouse moved outside window.
  331. setMode(IDLE)
  332. svg.removeEventListener("mousemove", handleScanMove)
  333. return
  334. }
  335. if (mode == MOUSEPAN) panMove(e.clientX, e.clientY)
  336. }
  337. function handleScanEnd(e) {
  338. if (mode == MOUSEPAN) panMove(e.clientX, e.clientY)
  339. setMode(IDLE)
  340. svg.removeEventListener("mousemove", handleScanMove)
  341. if (!moved) clickHandler(e.target)
  342. }
  343. // Find touch object with specified identifier.
  344. function findTouch(tlist, id) {
  345. for (const t of tlist) {
  346. if (t.identifier == id) return t
  347. }
  348. return null
  349. }
  350. // Return distance between two touch points
  351. function touchGap(t1, t2) {
  352. const dx = t1.clientX - t2.clientX
  353. const dy = t1.clientY - t2.clientY
  354. return Math.hypot(dx, dy)
  355. }
  356. function handleTouchStart(e) {
  357. if (mode == IDLE && e.changedTouches.length == 1) {
  358. // Start touch based panning
  359. const t = e.changedTouches[0]
  360. setMode(TOUCHPAN)
  361. touchid = t.identifier
  362. panStart(t.clientX, t.clientY)
  363. e.preventDefault()
  364. } else if (mode == TOUCHPAN && e.touches.length == 2) {
  365. // Start pinch zooming
  366. setMode(TOUCHZOOM)
  367. const t1 = e.touches[0]
  368. const t2 = e.touches[1]
  369. touchid = t1.identifier
  370. touchid2 = t2.identifier
  371. initScale = currentScale
  372. initGap = touchGap(t1, t2)
  373. centerPoint = toSvg((t1.clientX + t2.clientX) / 2,
  374. (t1.clientY + t2.clientY) / 2)
  375. e.preventDefault()
  376. }
  377. }
  378. function handleTouchMove(e) {
  379. if (mode == TOUCHPAN) {
  380. const t = findTouch(e.changedTouches, touchid)
  381. if (t == null) return
  382. if (e.touches.length != 1) {
  383. setMode(IDLE)
  384. return
  385. }
  386. panMove(t.clientX, t.clientY)
  387. e.preventDefault()
  388. } else if (mode == TOUCHZOOM) {
  389. // Get two touches; new gap; rescale to ratio.
  390. const t1 = findTouch(e.touches, touchid)
  391. const t2 = findTouch(e.touches, touchid2)
  392. if (t1 == null || t2 == null) return
  393. const gap = touchGap(t1, t2)
  394. rescale(initScale * gap / initGap, centerPoint)
  395. e.preventDefault()
  396. }
  397. }
  398. function handleTouchEnd(e) {
  399. if (mode == TOUCHPAN) {
  400. const t = findTouch(e.changedTouches, touchid)
  401. if (t == null) return
  402. panMove(t.clientX, t.clientY)
  403. setMode(IDLE)
  404. e.preventDefault()
  405. if (!moved) clickHandler(t.target)
  406. } else if (mode == TOUCHZOOM) {
  407. setMode(IDLE)
  408. e.preventDefault()
  409. }
  410. }
  411. svg.addEventListener("mousedown", handleScanStart)
  412. svg.addEventListener("mouseup", handleScanEnd)
  413. svg.addEventListener("touchstart", handleTouchStart)
  414. svg.addEventListener("touchmove", handleTouchMove)
  415. svg.addEventListener("touchend", handleTouchEnd)
  416. svg.addEventListener("wheel", handleWheel, true)
  417. }
  418. function viewer(baseUrl, nodes) {
  419. 'use strict';
  420. // Elements
  421. const search = document.getElementById("searchbox")
  422. const graph0 = document.getElementById("graph0")
  423. const svg = (graph0 == null ? null : graph0.parentElement)
  424. const toptable = document.getElementById("toptable")
  425. let regexpActive = false
  426. let selected = new Map()
  427. let origFill = new Map()
  428. let searchAlarm = null
  429. let buttonsEnabled = true
  430. function handleDetails() {
  431. const detailsText = document.getElementById("detailtext")
  432. if (detailsText != null) detailsText.style.display = "block"
  433. }
  434. function handleCloseDetails() {
  435. const detailsText = document.getElementById("detailtext")
  436. if (detailsText != null) detailsText.style.display = "none"
  437. }
  438. function handleReset() { window.location.href = baseUrl }
  439. function handleTop() { navigate("/top", "f", false) }
  440. function handleGraph() { navigate("/", "f", false) }
  441. function handleList() { navigate("/weblist", "f", true) }
  442. function handleDisasm() { navigate("/disasm", "f", true) }
  443. function handlePeek() { navigate("/peek", "f", true) }
  444. function handleFocus() { navigate(baseUrl, "f", false) }
  445. function handleShow() { navigate(baseUrl, "s", false) }
  446. function handleIgnore() { navigate(baseUrl, "i", false) }
  447. function handleHide() { navigate(baseUrl, "h", false) }
  448. function handleKey(e) {
  449. if (e.keyCode != 13) return
  450. handleFocus()
  451. e.preventDefault()
  452. }
  453. function handleSearch() {
  454. // Delay expensive processing so a flurry of key strokes is handled once.
  455. if (searchAlarm != null) {
  456. clearTimeout(searchAlarm)
  457. }
  458. searchAlarm = setTimeout(selectMatching, 300)
  459. regexpActive = true
  460. updateButtons()
  461. }
  462. function selectMatching() {
  463. searchAlarm = null
  464. let re = null
  465. if (search.value != "") {
  466. try {
  467. re = new RegExp(search.value)
  468. } catch (e) {
  469. // TODO: Display error state in search box
  470. return
  471. }
  472. }
  473. function match(text) {
  474. return re != null && re.test(text)
  475. }
  476. // drop currently selected items that do not match re.
  477. selected.forEach(function(v, n) {
  478. if (!match(nodes[n])) {
  479. unselect(n, document.getElementById("node" + n))
  480. }
  481. })
  482. // add matching items that are not currently selected.
  483. for (let n = 0; n < nodes.length; n++) {
  484. if (!selected.has(n) && match(nodes[n])) {
  485. select(n, document.getElementById("node" + n))
  486. }
  487. }
  488. updateButtons()
  489. }
  490. function toggleSvgSelect(elem) {
  491. // Walk up to immediate child of graph0
  492. while (elem != null && elem.parentElement != graph0) {
  493. elem = elem.parentElement
  494. }
  495. if (!elem) return
  496. // Disable regexp mode.
  497. regexpActive = false
  498. const n = nodeId(elem)
  499. if (n < 0) return
  500. if (selected.has(n)) {
  501. unselect(n, elem)
  502. } else {
  503. select(n, elem)
  504. }
  505. updateButtons()
  506. }
  507. function unselect(n, elem) {
  508. if (elem == null) return
  509. selected.delete(n)
  510. setBackground(elem, false)
  511. }
  512. function select(n, elem) {
  513. if (elem == null) return
  514. selected.set(n, true)
  515. setBackground(elem, true)
  516. }
  517. function nodeId(elem) {
  518. const id = elem.id
  519. if (!id) return -1
  520. if (!id.startsWith("node")) return -1
  521. const n = parseInt(id.slice(4), 10)
  522. if (isNaN(n)) return -1
  523. if (n < 0 || n >= nodes.length) return -1
  524. return n
  525. }
  526. function setBackground(elem, set) {
  527. // Handle table row highlighting.
  528. if (elem.nodeName == "TR") {
  529. elem.classList.toggle("hilite", set)
  530. return
  531. }
  532. // Handle svg element highlighting.
  533. const p = findPolygon(elem)
  534. if (p != null) {
  535. if (set) {
  536. origFill.set(p, p.style.fill)
  537. p.style.fill = "#ccccff"
  538. } else if (origFill.has(p)) {
  539. p.style.fill = origFill.get(p)
  540. }
  541. }
  542. }
  543. function findPolygon(elem) {
  544. if (elem.localName == "polygon") return elem
  545. for (const c of elem.children) {
  546. const p = findPolygon(c)
  547. if (p != null) return p
  548. }
  549. return null
  550. }
  551. // convert a string to a regexp that matches that string.
  552. function quotemeta(str) {
  553. return str.replace(/([\\\.?+*\[\](){}|^$])/g, '\\$1')
  554. }
  555. // Navigate to specified path with current selection reflected
  556. // in the named parameter.
  557. function navigate(path, param, newWindow) {
  558. // The selection can be in one of two modes: regexp-based or
  559. // list-based. Construct regular expression depending on mode.
  560. let re = regexpActive ? search.value : ""
  561. if (!regexpActive) {
  562. selected.forEach(function(v, key) {
  563. if (re != "") re += "|"
  564. re += quotemeta(nodes[key])
  565. })
  566. }
  567. const url = new URL(window.location.href)
  568. url.pathname = path
  569. url.hash = ""
  570. if (re != "") {
  571. // For focus/show, forget old parameter. For others, add to re.
  572. const params = url.searchParams
  573. if (param != "f" && param != "s" && params.has(param)) {
  574. const old = params.get(param)
  575. if (old != "") {
  576. re += "|" + old
  577. }
  578. }
  579. params.set(param, re)
  580. }
  581. if (newWindow) {
  582. window.open(url.toString(), "_blank")
  583. } else {
  584. window.location.href = url.toString()
  585. }
  586. }
  587. function handleTopClick(e) {
  588. // Walk back until we find TR and then get the Name column (index 5)
  589. let elem = e.target
  590. while (elem != null && elem.nodeName != "TR") {
  591. elem = elem.parentElement
  592. }
  593. if (elem == null || elem.children.length < 6) return
  594. e.preventDefault()
  595. const tr = elem
  596. const td = elem.children[5]
  597. if (td.nodeName != "TD") return
  598. const name = td.innerText
  599. const index = nodes.indexOf(name)
  600. if (index < 0) return
  601. // Disable regexp mode.
  602. regexpActive = false
  603. if (selected.has(index)) {
  604. unselect(index, elem)
  605. } else {
  606. select(index, elem)
  607. }
  608. updateButtons()
  609. }
  610. function updateButtons() {
  611. const enable = (search.value != "" || selected.size != 0)
  612. if (buttonsEnabled == enable) return
  613. buttonsEnabled = enable
  614. for (const id of ["peek", "list", "disasm", "focus", "ignore", "hide", "show"]) {
  615. const btn = document.getElementById(id)
  616. if (btn != null) {
  617. btn.disabled = !enable
  618. }
  619. }
  620. }
  621. // Initialize button states
  622. updateButtons()
  623. // Setup event handlers
  624. if (svg != null) {
  625. initPanAndZoom(svg, toggleSvgSelect)
  626. }
  627. if (toptable != null) {
  628. toptable.addEventListener("mousedown", handleTopClick)
  629. toptable.addEventListener("touchstart", handleTopClick)
  630. }
  631. // Bind action to button with specified id.
  632. function addAction(id, action) {
  633. const btn = document.getElementById(id)
  634. if (btn != null) {
  635. btn.addEventListener("click", action)
  636. btn.addEventListener("touchstart", action)
  637. }
  638. }
  639. addAction("details", handleDetails)
  640. addAction("closedetails", handleCloseDetails)
  641. addAction("topbtn", handleTop)
  642. addAction("graphbtn", handleGraph)
  643. addAction("reset", handleReset)
  644. addAction("peek", handlePeek)
  645. addAction("list", handleList)
  646. addAction("disasm", handleDisasm)
  647. addAction("focus", handleFocus)
  648. addAction("ignore", handleIgnore)
  649. addAction("hide", handleHide)
  650. addAction("show", handleShow)
  651. search.addEventListener("input", handleSearch)
  652. search.addEventListener("keydown", handleKey)
  653. }
  654. </script>
  655. {{end}}
  656. {{define "top" -}}
  657. <!DOCTYPE html>
  658. <html>
  659. <head>
  660. <meta charset="utf-8">
  661. <title>{{.Title}}</title>
  662. {{template "css" .}}
  663. </head>
  664. <body>
  665. {{template "header" .}}
  666. <div id="topcontainer">
  667. <table id="toptable">
  668. <tr><th>Flat<th>Flat%<th>Sum%<th>Cum<th>Cum%<th>Name<th>Inlined?</tr>
  669. {{range $i,$e := .Top}}
  670. <tr id="node{{$i}}"><td>{{$e.Flat}}<td>{{$e.FlatPercent}}<td>{{$e.SumPercent}}<td>{{$e.Cum}}<td>{{$e.CumPercent}}<td>{{$e.Name}}<td>{{$e.InlineLabel}}</tr>
  671. {{end}}
  672. </table>
  673. </div>
  674. {{template "script" .}}
  675. <script>viewer({{.BaseURL}}, {{.Nodes}})</script>
  676. </body>
  677. </html>
  678. {{end}}
  679. `))