説明なし

webhtml.go 19KB

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