1 // Copyright 2017 the V8 project authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 import * as C from "./constants.js" 6 import {SourceResolver} from "./source-resolver.js" 7 import {SelectionBroker} from "./selection-broker.js" 8 import {DisassemblyView} from "./disassembly-view.js" 9 import {GraphMultiView} from "./graphmultiview.js" 10 import {CodeMode, CodeView} from "./code-view.js" 11 import * as d3 from "d3" 12 13 class Snapper { 14 resizer: Resizer; 15 sourceExpand: HTMLElement; 16 sourceCollapse: HTMLElement; 17 disassemblyExpand: HTMLElement; 18 disassemblyCollapse: HTMLElement; 19 20 constructor(resizer: Resizer) { 21 const snapper = this; 22 snapper.resizer = resizer; 23 snapper.sourceExpand = document.getElementById(C.SOURCE_EXPAND_ID); 24 snapper.sourceCollapse = document.getElementById(C.SOURCE_COLLAPSE_ID); 25 snapper.disassemblyExpand = document.getElementById(C.DISASSEMBLY_EXPAND_ID); 26 snapper.disassemblyCollapse = document.getElementById(C.DISASSEMBLY_COLLAPSE_ID); 27 28 document.getElementById("source-collapse").addEventListener("click", function () { 29 resizer.snapper.toggleSourceExpanded(); 30 }); 31 document.getElementById("disassembly-collapse").addEventListener("click", function () { 32 resizer.snapper.toggleDisassemblyExpanded(); 33 }); 34 } 35 36 getLastExpandedState(type, default_state) { 37 var state = window.sessionStorage.getItem("expandedState-" + type); 38 if (state === null) return default_state; 39 return state === 'true'; 40 } 41 42 setLastExpandedState(type, state) { 43 window.sessionStorage.setItem("expandedState-" + type, state); 44 } 45 46 toggleSourceExpanded(): void { 47 this.setSourceExpanded(!this.sourceExpand.classList.contains("invisible")); 48 } 49 50 sourceExpandUpdate(newState: boolean) { 51 this.setLastExpandedState("source", newState); 52 this.sourceExpand.classList.toggle("invisible", newState); 53 this.sourceCollapse.classList.toggle("invisible", !newState); 54 } 55 56 setSourceExpanded(newState) { 57 if (this.sourceExpand.classList.contains("invisible") === newState) return; 58 this.sourceExpandUpdate(newState); 59 let resizer = this.resizer; 60 if (newState) { 61 resizer.sep_left = resizer.sep_left_snap; 62 resizer.sep_left_snap = 0; 63 } else { 64 resizer.sep_left_snap = resizer.sep_left; 65 resizer.sep_left = 0; 66 } 67 resizer.updatePanes(); 68 } 69 70 toggleDisassemblyExpanded() { 71 this.setDisassemblyExpanded(!this.disassemblyExpand.classList.contains("invisible")); 72 } 73 74 disassemblyExpandUpdate(newState) { 75 this.setLastExpandedState("disassembly", newState); 76 this.disassemblyExpand.classList.toggle("invisible", newState); 77 this.disassemblyCollapse.classList.toggle("invisible", !newState); 78 } 79 80 setDisassemblyExpanded(newState) { 81 if (this.disassemblyExpand.classList.contains("invisible") === newState) return; 82 this.disassemblyExpandUpdate(newState); 83 let resizer = this.resizer; 84 if (newState) { 85 resizer.sep_right = resizer.sep_right_snap; 86 resizer.sep_right_snap = resizer.client_width; 87 } else { 88 resizer.sep_right_snap = resizer.sep_right; 89 resizer.sep_right = resizer.client_width; 90 } 91 resizer.updatePanes(); 92 } 93 94 panesUpated() { 95 this.sourceExpandUpdate(this.resizer.sep_left > this.resizer.dead_width); 96 this.disassemblyExpandUpdate(this.resizer.sep_right < 97 (this.resizer.client_width - this.resizer.dead_width)); 98 } 99 } 100 101 class Resizer { 102 snapper: Snapper; 103 dead_width: number; 104 client_width: number; 105 left: HTMLElement; 106 right: HTMLElement; 107 middle: HTMLElement; 108 sep_left: number; 109 sep_right: number; 110 sep_left_snap: number; 111 sep_right_snap: number; 112 sep_width_offset: number; 113 panes_updated_callback: () => void; 114 resizer_right: d3.Selection<HTMLDivElement, any, any, any>; 115 resizer_left: d3.Selection<HTMLDivElement, any, any, any>; 116 117 constructor(panes_updated_callback: () => void, dead_width: number) { 118 let resizer = this; 119 resizer.snapper = new Snapper(resizer) 120 resizer.panes_updated_callback = panes_updated_callback; 121 resizer.dead_width = dead_width 122 resizer.client_width = document.body.getBoundingClientRect().width; 123 resizer.left = document.getElementById(C.SOURCE_PANE_ID); 124 resizer.middle = document.getElementById(C.INTERMEDIATE_PANE_ID); 125 resizer.right = document.getElementById(C.GENERATED_PANE_ID); 126 resizer.resizer_left = d3.select('.resizer-left'); 127 resizer.resizer_right = d3.select('.resizer-right'); 128 resizer.sep_left = resizer.client_width / 3; 129 resizer.sep_right = resizer.client_width / 3 * 2; 130 resizer.sep_left_snap = 0; 131 resizer.sep_right_snap = 0; 132 // Offset to prevent resizers from sliding slightly over one another. 133 resizer.sep_width_offset = 7; 134 135 let dragResizeLeft = d3.drag() 136 .on('drag', function () { 137 let x = d3.mouse(this.parentElement)[0]; 138 resizer.sep_left = Math.min(Math.max(0, x), resizer.sep_right - resizer.sep_width_offset); 139 resizer.updatePanes(); 140 }) 141 .on('start', function () { 142 resizer.resizer_left.classed("dragged", true); 143 let x = d3.mouse(this.parentElement)[0]; 144 if (x > dead_width) { 145 resizer.sep_left_snap = resizer.sep_left; 146 } 147 }) 148 .on('end', function () { 149 resizer.resizer_left.classed("dragged", false); 150 }); 151 resizer.resizer_left.call(dragResizeLeft); 152 153 let dragResizeRight = d3.drag() 154 .on('drag', function () { 155 let x = d3.mouse(this.parentElement)[0]; 156 resizer.sep_right = Math.max(resizer.sep_left + resizer.sep_width_offset, Math.min(x, resizer.client_width)); 157 resizer.updatePanes(); 158 }) 159 .on('start', function () { 160 resizer.resizer_right.classed("dragged", true); 161 let x = d3.mouse(this.parentElement)[0]; 162 if (x < (resizer.client_width - dead_width)) { 163 resizer.sep_right_snap = resizer.sep_right; 164 } 165 }) 166 .on('end', function () { 167 resizer.resizer_right.classed("dragged", false); 168 });; 169 resizer.resizer_right.call(dragResizeRight); 170 window.onresize = function () { 171 resizer.updateWidths(); 172 resizer.updatePanes(); 173 }; 174 } 175 176 updatePanes() { 177 let left_snapped = this.sep_left === 0; 178 let right_snapped = this.sep_right >= this.client_width - 1; 179 this.resizer_left.classed("snapped", left_snapped); 180 this.resizer_right.classed("snapped", right_snapped); 181 this.left.style.width = this.sep_left + 'px'; 182 this.middle.style.width = (this.sep_right - this.sep_left) + 'px'; 183 this.right.style.width = (this.client_width - this.sep_right) + 'px'; 184 this.resizer_left.style('left', this.sep_left + 'px'); 185 this.resizer_right.style('right', (this.client_width - this.sep_right - 1) + 'px'); 186 187 this.snapper.panesUpated(); 188 this.panes_updated_callback(); 189 } 190 191 updateWidths() { 192 this.client_width = document.body.getBoundingClientRect().width; 193 this.sep_right = Math.min(this.sep_right, this.client_width); 194 this.sep_left = Math.min(Math.max(0, this.sep_left), this.sep_right); 195 } 196 } 197 198 window.onload = function () { 199 var svg = null; 200 var multiview = null; 201 var disassemblyView = null; 202 var sourceViews = []; 203 var selectionBroker = null; 204 var sourceResolver = null; 205 let resizer = new Resizer(panesUpdatedCallback, 100); 206 207 function panesUpdatedCallback() { 208 if (multiview) multiview.onresize(); 209 } 210 211 function loadFile(txtRes) { 212 // If the JSON isn't properly terminated, assume compiler crashed and 213 // add best-guess empty termination 214 if (txtRes[txtRes.length - 2] == ',') { 215 txtRes += '{"name":"disassembly","type":"disassembly","data":""}]}'; 216 } 217 try { 218 sourceViews.forEach((sv) => sv.hide()); 219 if (multiview) multiview.hide(); 220 multiview = null; 221 if (disassemblyView) disassemblyView.hide(); 222 sourceViews = []; 223 sourceResolver = new SourceResolver(); 224 selectionBroker = new SelectionBroker(sourceResolver); 225 226 const jsonObj = JSON.parse(txtRes); 227 228 let fnc = jsonObj.function; 229 // Backwards compatibility. 230 if (typeof fnc == 'string') { 231 fnc = { 232 functionName: fnc, 233 sourceId: -1, 234 startPosition: jsonObj.sourcePosition, 235 endPosition: jsonObj.sourcePosition + jsonObj.source.length, 236 sourceText: jsonObj.source, 237 backwardsCompatibility: true 238 }; 239 } 240 241 sourceResolver.setInlinings(jsonObj.inlinings); 242 sourceResolver.setSourceLineToBytecodePosition(jsonObj.sourceLineToBytecodePosition); 243 sourceResolver.setSources(jsonObj.sources, fnc) 244 sourceResolver.setNodePositionMap(jsonObj.nodePositions); 245 sourceResolver.parsePhases(jsonObj.phases); 246 247 let sourceView = new CodeView(C.SOURCE_PANE_ID, selectionBroker, sourceResolver, fnc, CodeMode.MAIN_SOURCE); 248 sourceView.show(null, null); 249 sourceViews.push(sourceView); 250 251 sourceResolver.forEachSource((source) => { 252 let sourceView = new CodeView(C.SOURCE_PANE_ID, selectionBroker, sourceResolver, source, CodeMode.INLINED_SOURCE); 253 sourceView.show(null, null); 254 sourceViews.push(sourceView); 255 }); 256 257 disassemblyView = new DisassemblyView(C.GENERATED_PANE_ID, selectionBroker); 258 disassemblyView.initializeCode(fnc.sourceText); 259 if (sourceResolver.disassemblyPhase) { 260 disassemblyView.initializePerfProfile(jsonObj.eventCounts); 261 disassemblyView.show(sourceResolver.disassemblyPhase.data, null); 262 } 263 264 multiview = new GraphMultiView(C.INTERMEDIATE_PANE_ID, selectionBroker, sourceResolver); 265 multiview.show(jsonObj); 266 } catch (err) { 267 if (window.confirm("Error: Exception during load of TurboFan JSON file:\n" + 268 "error: " + err.message + "\nDo you want to clear session storage?")) { 269 window.sessionStorage.clear(); 270 } 271 return; 272 } 273 } 274 275 function initializeUploadHandlers() { 276 // The <input> form #upload-helper with type file can't be a picture. 277 // We hence keep it hidden, and forward the click from the picture 278 // button #upload. 279 d3.select("#upload").on("click", 280 () => document.getElementById("upload-helper").click()); 281 d3.select("#upload-helper").on("change", function (this: HTMLInputElement) { 282 var uploadFile = this.files && this.files[0]; 283 var filereader = new FileReader(); 284 filereader.onload = function (e) { 285 var txtRes = e.target.result; 286 loadFile(txtRes); 287 }; 288 if (uploadFile) 289 filereader.readAsText(uploadFile); 290 }); 291 } 292 293 initializeUploadHandlers(); 294 295 296 resizer.snapper.setSourceExpanded(resizer.snapper.getLastExpandedState("source", true)); 297 resizer.snapper.setDisassemblyExpanded(resizer.snapper.getLastExpandedState("disassembly", false)); 298 299 resizer.updatePanes(); 300 301 }; 302