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
5import * as C from "./constants.js"
6import {SourceResolver} from "./source-resolver.js"
7import {SelectionBroker} from "./selection-broker.js"
8import {DisassemblyView} from "./disassembly-view.js"
9import {GraphMultiView} from "./graphmultiview.js"
10import {CodeMode, CodeView} from "./code-view.js"
11import * as d3 from "d3"
12
13class 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
101class 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
198window.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