11cb0ef41Sopenharmony_ci// Copyright 2021 the V8 project authors. All rights reserved.
21cb0ef41Sopenharmony_ci// Use of this source code is governed by a BSD-style license that can be
31cb0ef41Sopenharmony_ci// found in the LICENSE file.
41cb0ef41Sopenharmony_ci
51cb0ef41Sopenharmony_ciimport {kChunkVisualWidth, MapLogEntry} from '../../log/map.mjs';
61cb0ef41Sopenharmony_ciimport {FocusEvent} from '../events.mjs';
71cb0ef41Sopenharmony_ciimport {CSSColor, DOM} from '../helper.mjs';
81cb0ef41Sopenharmony_ci
91cb0ef41Sopenharmony_ciimport {TimelineTrackBase} from './timeline-track-base.mjs'
101cb0ef41Sopenharmony_ci
111cb0ef41Sopenharmony_ciDOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-map',
121cb0ef41Sopenharmony_ci                        (templateText) =>
131cb0ef41Sopenharmony_ci                            class TimelineTrackMap extends TimelineTrackBase {
141cb0ef41Sopenharmony_ci  constructor() {
151cb0ef41Sopenharmony_ci    super(templateText);
161cb0ef41Sopenharmony_ci    this.navigation = new Navigation(this)
171cb0ef41Sopenharmony_ci  }
181cb0ef41Sopenharmony_ci
191cb0ef41Sopenharmony_ci  _handleKeyDown(event) {}
201cb0ef41Sopenharmony_ci
211cb0ef41Sopenharmony_ci  getMapStyle(map) {
221cb0ef41Sopenharmony_ci    return map.edge && map.edge.from ? CSSColor.onBackgroundColor :
231cb0ef41Sopenharmony_ci                                       CSSColor.onPrimaryColor;
241cb0ef41Sopenharmony_ci  }
251cb0ef41Sopenharmony_ci
261cb0ef41Sopenharmony_ci  markMap(map) {
271cb0ef41Sopenharmony_ci    const [x, y] = map.position(this.chunks);
281cb0ef41Sopenharmony_ci    const strokeColor = this.getMapStyle(map);
291cb0ef41Sopenharmony_ci    return `<circle cx=${x} cy=${y} r=${2} stroke=${
301cb0ef41Sopenharmony_ci        strokeColor} class=annotationPoint />`
311cb0ef41Sopenharmony_ci  }
321cb0ef41Sopenharmony_ci
331cb0ef41Sopenharmony_ci  markSelectedMap(map) {
341cb0ef41Sopenharmony_ci    const [x, y] = map.position(this.chunks);
351cb0ef41Sopenharmony_ci    const strokeColor = this.getMapStyle(map);
361cb0ef41Sopenharmony_ci    return `<circle cx=${x} cy=${y} r=${3} stroke=${
371cb0ef41Sopenharmony_ci        strokeColor} class=annotationPoint />`
381cb0ef41Sopenharmony_ci  }
391cb0ef41Sopenharmony_ci
401cb0ef41Sopenharmony_ci  _drawAnnotations(logEntry, time) {
411cb0ef41Sopenharmony_ci    if (!(logEntry instanceof MapLogEntry)) return;
421cb0ef41Sopenharmony_ci    if (!logEntry.edge) {
431cb0ef41Sopenharmony_ci      this.timelineAnnotationsNode.innerHTML = '';
441cb0ef41Sopenharmony_ci      return;
451cb0ef41Sopenharmony_ci    }
461cb0ef41Sopenharmony_ci    // Draw the trace of maps in reverse order to make sure the outgoing
471cb0ef41Sopenharmony_ci    // transitions of previous maps aren't drawn over.
481cb0ef41Sopenharmony_ci    const kOpaque = 1.0;
491cb0ef41Sopenharmony_ci    let stack = [];
501cb0ef41Sopenharmony_ci    let current = logEntry;
511cb0ef41Sopenharmony_ci    while (current !== undefined) {
521cb0ef41Sopenharmony_ci      stack.push(current);
531cb0ef41Sopenharmony_ci      current = current.parent;
541cb0ef41Sopenharmony_ci    }
551cb0ef41Sopenharmony_ci
561cb0ef41Sopenharmony_ci    // Draw outgoing refs as fuzzy background. Skip the last map entry.
571cb0ef41Sopenharmony_ci    let buffer = '';
581cb0ef41Sopenharmony_ci    let nofEdges = 0;
591cb0ef41Sopenharmony_ci    const kMaxOutgoingEdges = 100;
601cb0ef41Sopenharmony_ci    for (let i = stack.length - 2; i >= 0; i--) {
611cb0ef41Sopenharmony_ci      const map = stack[i].parent;
621cb0ef41Sopenharmony_ci      nofEdges += map.children.length;
631cb0ef41Sopenharmony_ci      if (nofEdges > kMaxOutgoingEdges) break;
641cb0ef41Sopenharmony_ci      buffer += this.drawOutgoingEdges(map, 0.4, 1);
651cb0ef41Sopenharmony_ci    }
661cb0ef41Sopenharmony_ci
671cb0ef41Sopenharmony_ci    // Draw main connection.
681cb0ef41Sopenharmony_ci    let labelOffset = 15;
691cb0ef41Sopenharmony_ci    let xPrev = 0;
701cb0ef41Sopenharmony_ci    for (let i = stack.length - 1; i >= 0; i--) {
711cb0ef41Sopenharmony_ci      let map = stack[i];
721cb0ef41Sopenharmony_ci      if (map.edge) {
731cb0ef41Sopenharmony_ci        const [xTo, data] = this.drawEdge(map.edge, kOpaque, labelOffset);
741cb0ef41Sopenharmony_ci        buffer += data;
751cb0ef41Sopenharmony_ci        if (xTo == xPrev) {
761cb0ef41Sopenharmony_ci          labelOffset += 10;
771cb0ef41Sopenharmony_ci        } else {
781cb0ef41Sopenharmony_ci          labelOffset = 15
791cb0ef41Sopenharmony_ci        }
801cb0ef41Sopenharmony_ci        xPrev = xTo;
811cb0ef41Sopenharmony_ci      }
821cb0ef41Sopenharmony_ci      buffer += this.markMap(map);
831cb0ef41Sopenharmony_ci    }
841cb0ef41Sopenharmony_ci
851cb0ef41Sopenharmony_ci    buffer += this.drawOutgoingEdges(logEntry, 0.9, 3);
861cb0ef41Sopenharmony_ci    // Mark selected map
871cb0ef41Sopenharmony_ci    buffer += this.markSelectedMap(logEntry);
881cb0ef41Sopenharmony_ci    this.timelineAnnotationsNode.innerHTML = buffer;
891cb0ef41Sopenharmony_ci  }
901cb0ef41Sopenharmony_ci
911cb0ef41Sopenharmony_ci  drawEdge(edge, opacity, labelOffset = 20) {
921cb0ef41Sopenharmony_ci    let buffer = '';
931cb0ef41Sopenharmony_ci    if (!edge.from || !edge.to) return [-1, buffer];
941cb0ef41Sopenharmony_ci    const [xFrom, yFrom] = edge.from.position(this.chunks);
951cb0ef41Sopenharmony_ci    const [xTo, yTo] = edge.to.position(this.chunks);
961cb0ef41Sopenharmony_ci    const sameChunk = xTo == xFrom;
971cb0ef41Sopenharmony_ci    if (sameChunk) labelOffset += 10;
981cb0ef41Sopenharmony_ci    const color = this._legend.colorForType(edge.type);
991cb0ef41Sopenharmony_ci    const offsetX = 20;
1001cb0ef41Sopenharmony_ci    const midX = xFrom + (xTo - xFrom) / 2;
1011cb0ef41Sopenharmony_ci    const midY = (yFrom + yTo) / 2 - 100;
1021cb0ef41Sopenharmony_ci    if (!sameChunk) {
1031cb0ef41Sopenharmony_ci      if (opacity == 1.0) {
1041cb0ef41Sopenharmony_ci        buffer += `<path d="M ${xFrom} ${yFrom} Q ${midX} ${midY}, ${xTo} ${
1051cb0ef41Sopenharmony_ci            yTo}" class=strokeBG />`
1061cb0ef41Sopenharmony_ci      }
1071cb0ef41Sopenharmony_ci      buffer += `<path d="M ${xFrom} ${yFrom} Q ${midX} ${midY}, ${xTo} ${
1081cb0ef41Sopenharmony_ci          yTo}" stroke=${color} fill=none opacity=${opacity} />`
1091cb0ef41Sopenharmony_ci    } else {
1101cb0ef41Sopenharmony_ci      if (opacity == 1.0) {
1111cb0ef41Sopenharmony_ci        buffer += `<line x1=${xFrom} x2=${xTo} y1=${yFrom} y2=${
1121cb0ef41Sopenharmony_ci            yTo} class=strokeBG />`;
1131cb0ef41Sopenharmony_ci      }
1141cb0ef41Sopenharmony_ci      buffer += `<line x1=${xFrom} x2=${xTo} y1=${yFrom} y2=${yTo} stroke=${
1151cb0ef41Sopenharmony_ci          color} fill=none opacity=${opacity} />`;
1161cb0ef41Sopenharmony_ci    }
1171cb0ef41Sopenharmony_ci    if (opacity == 1.0) {
1181cb0ef41Sopenharmony_ci      const centerX = sameChunk ? xTo : ((xFrom / 2 + midX + xTo / 2) / 2) | 0;
1191cb0ef41Sopenharmony_ci      const centerY = sameChunk ? yTo : ((yFrom / 2 + midY + yTo / 2) / 2) | 0;
1201cb0ef41Sopenharmony_ci      const centerYTo = centerY - labelOffset;
1211cb0ef41Sopenharmony_ci      buffer += `<line x1=${centerX} x2=${centerX + offsetX} y1=${centerY} y2=${
1221cb0ef41Sopenharmony_ci          centerYTo} stroke=${color} fill=none opacity=${opacity} />`;
1231cb0ef41Sopenharmony_ci      buffer += `<text x=${centerX + offsetX + 2} y=${
1241cb0ef41Sopenharmony_ci          centerYTo} class=annotationLabel opacity=${opacity} >${
1251cb0ef41Sopenharmony_ci          edge.toString()}</text>`;
1261cb0ef41Sopenharmony_ci    }
1271cb0ef41Sopenharmony_ci    return [xTo, buffer];
1281cb0ef41Sopenharmony_ci  }
1291cb0ef41Sopenharmony_ci
1301cb0ef41Sopenharmony_ci  drawOutgoingEdges(map, opacity = 1.0, max = 10, depth = 0) {
1311cb0ef41Sopenharmony_ci    let buffer = '';
1321cb0ef41Sopenharmony_ci    if (!map || depth >= max) return buffer;
1331cb0ef41Sopenharmony_ci    const limit = Math.min(map.children.length, 100)
1341cb0ef41Sopenharmony_ci    for (let i = 0; i < limit; i++) {
1351cb0ef41Sopenharmony_ci      const edge = map.children[i];
1361cb0ef41Sopenharmony_ci      const [xTo, data] = this.drawEdge(edge, opacity);
1371cb0ef41Sopenharmony_ci      buffer += data;
1381cb0ef41Sopenharmony_ci      buffer += this.drawOutgoingEdges(edge.to, opacity * 0.5, max, depth + 1);
1391cb0ef41Sopenharmony_ci    }
1401cb0ef41Sopenharmony_ci    return buffer;
1411cb0ef41Sopenharmony_ci  }
1421cb0ef41Sopenharmony_ci})
1431cb0ef41Sopenharmony_ci
1441cb0ef41Sopenharmony_ciclass Navigation {
1451cb0ef41Sopenharmony_ci  constructor(track) {
1461cb0ef41Sopenharmony_ci    this._track = track;
1471cb0ef41Sopenharmony_ci    this._track.addEventListener('keydown', this._handleKeyDown.bind(this));
1481cb0ef41Sopenharmony_ci    this._map = undefined;
1491cb0ef41Sopenharmony_ci  }
1501cb0ef41Sopenharmony_ci
1511cb0ef41Sopenharmony_ci  _handleKeyDown(event) {
1521cb0ef41Sopenharmony_ci    if (!this._track.isFocused) return;
1531cb0ef41Sopenharmony_ci    let handled = false;
1541cb0ef41Sopenharmony_ci    switch (event.key) {
1551cb0ef41Sopenharmony_ci      case 'ArrowDown':
1561cb0ef41Sopenharmony_ci        handled = true;
1571cb0ef41Sopenharmony_ci        if (event.shiftKey) {
1581cb0ef41Sopenharmony_ci          this.selectPrevEdge();
1591cb0ef41Sopenharmony_ci        } else {
1601cb0ef41Sopenharmony_ci          this.moveInChunk(-1);
1611cb0ef41Sopenharmony_ci        }
1621cb0ef41Sopenharmony_ci        break;
1631cb0ef41Sopenharmony_ci      case 'ArrowUp':
1641cb0ef41Sopenharmony_ci        handled = true;
1651cb0ef41Sopenharmony_ci        if (event.shiftKey) {
1661cb0ef41Sopenharmony_ci          this.selectNextEdge();
1671cb0ef41Sopenharmony_ci        } else {
1681cb0ef41Sopenharmony_ci          this.moveInChunk(1);
1691cb0ef41Sopenharmony_ci        }
1701cb0ef41Sopenharmony_ci        break;
1711cb0ef41Sopenharmony_ci      case 'ArrowLeft':
1721cb0ef41Sopenharmony_ci        handled = true;
1731cb0ef41Sopenharmony_ci        this.moveInChunks(false);
1741cb0ef41Sopenharmony_ci        break;
1751cb0ef41Sopenharmony_ci      case 'ArrowRight':
1761cb0ef41Sopenharmony_ci        handled = true;
1771cb0ef41Sopenharmony_ci        this.moveInChunks(true);
1781cb0ef41Sopenharmony_ci        break;
1791cb0ef41Sopenharmony_ci      case 'Enter':
1801cb0ef41Sopenharmony_ci        handled = true;
1811cb0ef41Sopenharmony_ci        this.selectMap();
1821cb0ef41Sopenharmony_ci        break
1831cb0ef41Sopenharmony_ci    }
1841cb0ef41Sopenharmony_ci    if (handled) {
1851cb0ef41Sopenharmony_ci      event.stopPropagation();
1861cb0ef41Sopenharmony_ci      event.preventDefault();
1871cb0ef41Sopenharmony_ci      return false;
1881cb0ef41Sopenharmony_ci    }
1891cb0ef41Sopenharmony_ci  }
1901cb0ef41Sopenharmony_ci
1911cb0ef41Sopenharmony_ci  get map() {
1921cb0ef41Sopenharmony_ci    return this._track.focusedEntry;
1931cb0ef41Sopenharmony_ci  }
1941cb0ef41Sopenharmony_ci
1951cb0ef41Sopenharmony_ci  set map(map) {
1961cb0ef41Sopenharmony_ci    this._track.focusedEntry = map;
1971cb0ef41Sopenharmony_ci  }
1981cb0ef41Sopenharmony_ci
1991cb0ef41Sopenharmony_ci  get chunks() {
2001cb0ef41Sopenharmony_ci    return this._track.chunks;
2011cb0ef41Sopenharmony_ci  }
2021cb0ef41Sopenharmony_ci
2031cb0ef41Sopenharmony_ci  selectMap() {
2041cb0ef41Sopenharmony_ci    if (!this.map) return;
2051cb0ef41Sopenharmony_ci    this._track.dispatchEvent(new FocusEvent(this.map))
2061cb0ef41Sopenharmony_ci  }
2071cb0ef41Sopenharmony_ci
2081cb0ef41Sopenharmony_ci  selectNextEdge() {
2091cb0ef41Sopenharmony_ci    if (!this.map) return;
2101cb0ef41Sopenharmony_ci    if (this.map.children.length != 1) return;
2111cb0ef41Sopenharmony_ci    this.show(this.map.children[0].to);
2121cb0ef41Sopenharmony_ci  }
2131cb0ef41Sopenharmony_ci
2141cb0ef41Sopenharmony_ci  selectPrevEdge() {
2151cb0ef41Sopenharmony_ci    if (!this.map) return;
2161cb0ef41Sopenharmony_ci    if (!this.map.parent) return;
2171cb0ef41Sopenharmony_ci    this.map = this.map.parent;
2181cb0ef41Sopenharmony_ci  }
2191cb0ef41Sopenharmony_ci
2201cb0ef41Sopenharmony_ci  selectDefaultMap() {
2211cb0ef41Sopenharmony_ci    this.map = this.chunks[0].at(0);
2221cb0ef41Sopenharmony_ci  }
2231cb0ef41Sopenharmony_ci
2241cb0ef41Sopenharmony_ci  moveInChunks(next) {
2251cb0ef41Sopenharmony_ci    if (!this.map) return this.selectDefaultMap();
2261cb0ef41Sopenharmony_ci    let chunkIndex = this.map.chunkIndex(this.chunks);
2271cb0ef41Sopenharmony_ci    let currentChunk = this.chunks[chunkIndex];
2281cb0ef41Sopenharmony_ci    let currentIndex = currentChunk.indexOf(this.map);
2291cb0ef41Sopenharmony_ci    let newChunk;
2301cb0ef41Sopenharmony_ci    if (next) {
2311cb0ef41Sopenharmony_ci      newChunk = chunk.next(this.chunks);
2321cb0ef41Sopenharmony_ci    } else {
2331cb0ef41Sopenharmony_ci      newChunk = chunk.prev(this.chunks);
2341cb0ef41Sopenharmony_ci    }
2351cb0ef41Sopenharmony_ci    if (!newChunk) return;
2361cb0ef41Sopenharmony_ci    let newIndex = Math.min(currentIndex, newChunk.size() - 1);
2371cb0ef41Sopenharmony_ci    this.map = newChunk.at(newIndex);
2381cb0ef41Sopenharmony_ci  }
2391cb0ef41Sopenharmony_ci
2401cb0ef41Sopenharmony_ci  moveInChunk(delta) {
2411cb0ef41Sopenharmony_ci    if (!this.map) return this.selectDefaultMap();
2421cb0ef41Sopenharmony_ci    let chunkIndex = this.map.chunkIndex(this.chunks)
2431cb0ef41Sopenharmony_ci    let chunk = this.chunks[chunkIndex];
2441cb0ef41Sopenharmony_ci    let index = chunk.indexOf(this.map) + delta;
2451cb0ef41Sopenharmony_ci    let map;
2461cb0ef41Sopenharmony_ci    if (index < 0) {
2471cb0ef41Sopenharmony_ci      map = chunk.prev(this.chunks).last();
2481cb0ef41Sopenharmony_ci    } else if (index >= chunk.size()) {
2491cb0ef41Sopenharmony_ci      map = chunk.next(this.chunks).first()
2501cb0ef41Sopenharmony_ci    } else {
2511cb0ef41Sopenharmony_ci      map = chunk.at(index);
2521cb0ef41Sopenharmony_ci    }
2531cb0ef41Sopenharmony_ci    this.map = map;
2541cb0ef41Sopenharmony_ci  }
2551cb0ef41Sopenharmony_ci}
256