// Copyright 2017 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import * as C from "./constants.js" import {SourceResolver} from "./source-resolver.js" import {SelectionBroker} from "./selection-broker.js" import {DisassemblyView} from "./disassembly-view.js" import {GraphMultiView} from "./graphmultiview.js" import {CodeMode, CodeView} from "./code-view.js" import * as d3 from "d3" class Snapper { resizer: Resizer; sourceExpand: HTMLElement; sourceCollapse: HTMLElement; disassemblyExpand: HTMLElement; disassemblyCollapse: HTMLElement; constructor(resizer: Resizer) { const snapper = this; snapper.resizer = resizer; snapper.sourceExpand = document.getElementById(C.SOURCE_EXPAND_ID); snapper.sourceCollapse = document.getElementById(C.SOURCE_COLLAPSE_ID); snapper.disassemblyExpand = document.getElementById(C.DISASSEMBLY_EXPAND_ID); snapper.disassemblyCollapse = document.getElementById(C.DISASSEMBLY_COLLAPSE_ID); document.getElementById("source-collapse").addEventListener("click", function () { resizer.snapper.toggleSourceExpanded(); }); document.getElementById("disassembly-collapse").addEventListener("click", function () { resizer.snapper.toggleDisassemblyExpanded(); }); } getLastExpandedState(type, default_state) { var state = window.sessionStorage.getItem("expandedState-" + type); if (state === null) return default_state; return state === 'true'; } setLastExpandedState(type, state) { window.sessionStorage.setItem("expandedState-" + type, state); } toggleSourceExpanded(): void { this.setSourceExpanded(!this.sourceExpand.classList.contains("invisible")); } sourceExpandUpdate(newState: boolean) { this.setLastExpandedState("source", newState); this.sourceExpand.classList.toggle("invisible", newState); this.sourceCollapse.classList.toggle("invisible", !newState); } setSourceExpanded(newState) { if (this.sourceExpand.classList.contains("invisible") === newState) return; this.sourceExpandUpdate(newState); let resizer = this.resizer; if (newState) { resizer.sep_left = resizer.sep_left_snap; resizer.sep_left_snap = 0; } else { resizer.sep_left_snap = resizer.sep_left; resizer.sep_left = 0; } resizer.updatePanes(); } toggleDisassemblyExpanded() { this.setDisassemblyExpanded(!this.disassemblyExpand.classList.contains("invisible")); } disassemblyExpandUpdate(newState) { this.setLastExpandedState("disassembly", newState); this.disassemblyExpand.classList.toggle("invisible", newState); this.disassemblyCollapse.classList.toggle("invisible", !newState); } setDisassemblyExpanded(newState) { if (this.disassemblyExpand.classList.contains("invisible") === newState) return; this.disassemblyExpandUpdate(newState); let resizer = this.resizer; if (newState) { resizer.sep_right = resizer.sep_right_snap; resizer.sep_right_snap = resizer.client_width; } else { resizer.sep_right_snap = resizer.sep_right; resizer.sep_right = resizer.client_width; } resizer.updatePanes(); } panesUpated() { this.sourceExpandUpdate(this.resizer.sep_left > this.resizer.dead_width); this.disassemblyExpandUpdate(this.resizer.sep_right < (this.resizer.client_width - this.resizer.dead_width)); } } class Resizer { snapper: Snapper; dead_width: number; client_width: number; left: HTMLElement; right: HTMLElement; middle: HTMLElement; sep_left: number; sep_right: number; sep_left_snap: number; sep_right_snap: number; sep_width_offset: number; panes_updated_callback: () => void; resizer_right: d3.Selection<HTMLDivElement, any, any, any>; resizer_left: d3.Selection<HTMLDivElement, any, any, any>; constructor(panes_updated_callback: () => void, dead_width: number) { let resizer = this; resizer.snapper = new Snapper(resizer) resizer.panes_updated_callback = panes_updated_callback; resizer.dead_width = dead_width resizer.client_width = document.body.getBoundingClientRect().width; resizer.left = document.getElementById(C.SOURCE_PANE_ID); resizer.middle = document.getElementById(C.INTERMEDIATE_PANE_ID); resizer.right = document.getElementById(C.GENERATED_PANE_ID); resizer.resizer_left = d3.select('.resizer-left'); resizer.resizer_right = d3.select('.resizer-right'); resizer.sep_left = resizer.client_width / 3; resizer.sep_right = resizer.client_width / 3 * 2; resizer.sep_left_snap = 0; resizer.sep_right_snap = 0; // Offset to prevent resizers from sliding slightly over one another. resizer.sep_width_offset = 7; let dragResizeLeft = d3.drag() .on('drag', function () { let x = d3.mouse(this.parentElement)[0]; resizer.sep_left = Math.min(Math.max(0, x), resizer.sep_right - resizer.sep_width_offset); resizer.updatePanes(); }) .on('start', function () { resizer.resizer_left.classed("dragged", true); let x = d3.mouse(this.parentElement)[0]; if (x > dead_width) { resizer.sep_left_snap = resizer.sep_left; } }) .on('end', function () { resizer.resizer_left.classed("dragged", false); }); resizer.resizer_left.call(dragResizeLeft); let dragResizeRight = d3.drag() .on('drag', function () { let x = d3.mouse(this.parentElement)[0]; resizer.sep_right = Math.max(resizer.sep_left + resizer.sep_width_offset, Math.min(x, resizer.client_width)); resizer.updatePanes(); }) .on('start', function () { resizer.resizer_right.classed("dragged", true); let x = d3.mouse(this.parentElement)[0]; if (x < (resizer.client_width - dead_width)) { resizer.sep_right_snap = resizer.sep_right; } }) .on('end', function () { resizer.resizer_right.classed("dragged", false); });; resizer.resizer_right.call(dragResizeRight); window.onresize = function () { resizer.updateWidths(); resizer.updatePanes(); }; } updatePanes() { let left_snapped = this.sep_left === 0; let right_snapped = this.sep_right >= this.client_width - 1; this.resizer_left.classed("snapped", left_snapped); this.resizer_right.classed("snapped", right_snapped); this.left.style.width = this.sep_left + 'px'; this.middle.style.width = (this.sep_right - this.sep_left) + 'px'; this.right.style.width = (this.client_width - this.sep_right) + 'px'; this.resizer_left.style('left', this.sep_left + 'px'); this.resizer_right.style('right', (this.client_width - this.sep_right - 1) + 'px'); this.snapper.panesUpated(); this.panes_updated_callback(); } updateWidths() { this.client_width = document.body.getBoundingClientRect().width; this.sep_right = Math.min(this.sep_right, this.client_width); this.sep_left = Math.min(Math.max(0, this.sep_left), this.sep_right); } } window.onload = function () { var svg = null; var multiview = null; var disassemblyView = null; var sourceViews = []; var selectionBroker = null; var sourceResolver = null; let resizer = new Resizer(panesUpdatedCallback, 100); function panesUpdatedCallback() { if (multiview) multiview.onresize(); } function loadFile(txtRes) { // If the JSON isn't properly terminated, assume compiler crashed and // add best-guess empty termination if (txtRes[txtRes.length - 2] == ',') { txtRes += '{"name":"disassembly","type":"disassembly","data":""}]}'; } try { sourceViews.forEach((sv) => sv.hide()); if (multiview) multiview.hide(); multiview = null; if (disassemblyView) disassemblyView.hide(); sourceViews = []; sourceResolver = new SourceResolver(); selectionBroker = new SelectionBroker(sourceResolver); const jsonObj = JSON.parse(txtRes); let fnc = jsonObj.function; // Backwards compatibility. if (typeof fnc == 'string') { fnc = { functionName: fnc, sourceId: -1, startPosition: jsonObj.sourcePosition, endPosition: jsonObj.sourcePosition + jsonObj.source.length, sourceText: jsonObj.source, backwardsCompatibility: true }; } sourceResolver.setInlinings(jsonObj.inlinings); sourceResolver.setSourceLineToBytecodePosition(jsonObj.sourceLineToBytecodePosition); sourceResolver.setSources(jsonObj.sources, fnc) sourceResolver.setNodePositionMap(jsonObj.nodePositions); sourceResolver.parsePhases(jsonObj.phases); let sourceView = new CodeView(C.SOURCE_PANE_ID, selectionBroker, sourceResolver, fnc, CodeMode.MAIN_SOURCE); sourceView.show(null, null); sourceViews.push(sourceView); sourceResolver.forEachSource((source) => { let sourceView = new CodeView(C.SOURCE_PANE_ID, selectionBroker, sourceResolver, source, CodeMode.INLINED_SOURCE); sourceView.show(null, null); sourceViews.push(sourceView); }); disassemblyView = new DisassemblyView(C.GENERATED_PANE_ID, selectionBroker); disassemblyView.initializeCode(fnc.sourceText); if (sourceResolver.disassemblyPhase) { disassemblyView.initializePerfProfile(jsonObj.eventCounts); disassemblyView.show(sourceResolver.disassemblyPhase.data, null); } multiview = new GraphMultiView(C.INTERMEDIATE_PANE_ID, selectionBroker, sourceResolver); multiview.show(jsonObj); } catch (err) { if (window.confirm("Error: Exception during load of TurboFan JSON file:\n" + "error: " + err.message + "\nDo you want to clear session storage?")) { window.sessionStorage.clear(); } return; } } function initializeUploadHandlers() { // The <input> form #upload-helper with type file can't be a picture. // We hence keep it hidden, and forward the click from the picture // button #upload. d3.select("#upload").on("click", () => document.getElementById("upload-helper").click()); d3.select("#upload-helper").on("change", function (this: HTMLInputElement) { var uploadFile = this.files && this.files[0]; var filereader = new FileReader(); filereader.onload = function (e) { var txtRes = e.target.result; loadFile(txtRes); }; if (uploadFile) filereader.readAsText(uploadFile); }); } initializeUploadHandlers(); resizer.snapper.setSourceExpanded(resizer.snapper.getLastExpandedState("source", true)); resizer.snapper.setDisassemblyExpanded(resizer.snapper.getLastExpandedState("disassembly", false)); resizer.updatePanes(); };