11cb0ef41Sopenharmony_ci// Copyright 2020 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_ciimport {App} from '../index.mjs'
51cb0ef41Sopenharmony_ci
61cb0ef41Sopenharmony_ciimport {SelectionEvent, SelectRelatedEvent, ToolTipEvent} from './events.mjs';
71cb0ef41Sopenharmony_ciimport {arrayEquals, CollapsableElement, CSSColor, defer, delay, DOM, formatBytes, gradientStopsFromGroups, groupBy, LazyTable} from './helper.mjs';
81cb0ef41Sopenharmony_ci
91cb0ef41Sopenharmony_ci// A source mapping proxy for source maps that don't have CORS headers.
101cb0ef41Sopenharmony_ci// TODO(leszeks): Make this configurable.
111cb0ef41Sopenharmony_ciconst sourceMapFetchPrefix = 'http://localhost:8080/';
121cb0ef41Sopenharmony_ci
131cb0ef41Sopenharmony_ciDOM.defineCustomElement('view/script-panel',
141cb0ef41Sopenharmony_ci                        (templateText) =>
151cb0ef41Sopenharmony_ci                            class SourcePanel extends CollapsableElement {
161cb0ef41Sopenharmony_ci  _selectedSourcePositions = [];
171cb0ef41Sopenharmony_ci  _sourcePositionsToMarkNodesPromise = defer();
181cb0ef41Sopenharmony_ci  _scripts = [];
191cb0ef41Sopenharmony_ci  _script;
201cb0ef41Sopenharmony_ci
211cb0ef41Sopenharmony_ci  showToolTipEntriesHandler = this.handleShowToolTipEntries.bind(this);
221cb0ef41Sopenharmony_ci
231cb0ef41Sopenharmony_ci  constructor() {
241cb0ef41Sopenharmony_ci    super(templateText);
251cb0ef41Sopenharmony_ci    this.scriptDropdown.addEventListener(
261cb0ef41Sopenharmony_ci        'change', e => this._handleSelectScript(e));
271cb0ef41Sopenharmony_ci    this.$('#selectedRelatedButton').onclick =
281cb0ef41Sopenharmony_ci        this._handleSelectRelated.bind(this);
291cb0ef41Sopenharmony_ci  }
301cb0ef41Sopenharmony_ci
311cb0ef41Sopenharmony_ci  get script() {
321cb0ef41Sopenharmony_ci    return this.$('#script');
331cb0ef41Sopenharmony_ci  }
341cb0ef41Sopenharmony_ci
351cb0ef41Sopenharmony_ci  get scriptNode() {
361cb0ef41Sopenharmony_ci    return this.$('.scriptNode');
371cb0ef41Sopenharmony_ci  }
381cb0ef41Sopenharmony_ci
391cb0ef41Sopenharmony_ci  set script(script) {
401cb0ef41Sopenharmony_ci    if (this._script === script) return;
411cb0ef41Sopenharmony_ci    this._script = script;
421cb0ef41Sopenharmony_ci    script.ensureSourceMapCalculated(sourceMapFetchPrefix);
431cb0ef41Sopenharmony_ci    this._sourcePositionsToMarkNodesPromise = defer();
441cb0ef41Sopenharmony_ci    this._selectedSourcePositions =
451cb0ef41Sopenharmony_ci        this._selectedSourcePositions.filter(each => each.script === script);
461cb0ef41Sopenharmony_ci    this.requestUpdate();
471cb0ef41Sopenharmony_ci  }
481cb0ef41Sopenharmony_ci
491cb0ef41Sopenharmony_ci  set focusedSourcePositions(sourcePositions) {
501cb0ef41Sopenharmony_ci    this.selectedSourcePositions = sourcePositions;
511cb0ef41Sopenharmony_ci  }
521cb0ef41Sopenharmony_ci
531cb0ef41Sopenharmony_ci  set selectedSourcePositions(sourcePositions) {
541cb0ef41Sopenharmony_ci    if (arrayEquals(this._selectedSourcePositions, sourcePositions)) {
551cb0ef41Sopenharmony_ci      this._focusSelectedMarkers(0);
561cb0ef41Sopenharmony_ci    } else {
571cb0ef41Sopenharmony_ci      this._selectedSourcePositions = sourcePositions;
581cb0ef41Sopenharmony_ci      // TODO: highlight multiple scripts
591cb0ef41Sopenharmony_ci      this.script = sourcePositions[0]?.script;
601cb0ef41Sopenharmony_ci      this._focusSelectedMarkers(100);
611cb0ef41Sopenharmony_ci    }
621cb0ef41Sopenharmony_ci  }
631cb0ef41Sopenharmony_ci
641cb0ef41Sopenharmony_ci  set scripts(scripts) {
651cb0ef41Sopenharmony_ci    this._scripts = scripts;
661cb0ef41Sopenharmony_ci    this._initializeScriptDropdown();
671cb0ef41Sopenharmony_ci  }
681cb0ef41Sopenharmony_ci
691cb0ef41Sopenharmony_ci  get scriptDropdown() {
701cb0ef41Sopenharmony_ci    return this.$('#script-dropdown');
711cb0ef41Sopenharmony_ci  }
721cb0ef41Sopenharmony_ci
731cb0ef41Sopenharmony_ci  _update() {
741cb0ef41Sopenharmony_ci    this._renderSourcePanel();
751cb0ef41Sopenharmony_ci    this._updateScriptDropdownSelection();
761cb0ef41Sopenharmony_ci  }
771cb0ef41Sopenharmony_ci
781cb0ef41Sopenharmony_ci  _initializeScriptDropdown() {
791cb0ef41Sopenharmony_ci    this._scripts.sort((a, b) => a.name?.localeCompare(b.name) ?? 0);
801cb0ef41Sopenharmony_ci    let select = this.scriptDropdown;
811cb0ef41Sopenharmony_ci    select.options.length = 0;
821cb0ef41Sopenharmony_ci    for (const script of this._scripts) {
831cb0ef41Sopenharmony_ci      const option = document.createElement('option');
841cb0ef41Sopenharmony_ci      const size = formatBytes(script.source.length);
851cb0ef41Sopenharmony_ci      option.text = `${script.name} (id=${script.id} size=${size})`;
861cb0ef41Sopenharmony_ci      option.script = script;
871cb0ef41Sopenharmony_ci      select.add(option);
881cb0ef41Sopenharmony_ci    }
891cb0ef41Sopenharmony_ci  }
901cb0ef41Sopenharmony_ci
911cb0ef41Sopenharmony_ci  _updateScriptDropdownSelection() {
921cb0ef41Sopenharmony_ci    this.scriptDropdown.selectedIndex =
931cb0ef41Sopenharmony_ci        this._script ? this._scripts.indexOf(this._script) : -1;
941cb0ef41Sopenharmony_ci  }
951cb0ef41Sopenharmony_ci
961cb0ef41Sopenharmony_ci  async _renderSourcePanel() {
971cb0ef41Sopenharmony_ci    let scriptNode;
981cb0ef41Sopenharmony_ci    const script = this._script;
991cb0ef41Sopenharmony_ci    if (script) {
1001cb0ef41Sopenharmony_ci      await delay(1);
1011cb0ef41Sopenharmony_ci      if (script != this._script) return;
1021cb0ef41Sopenharmony_ci      const builder = new LineBuilder(this, this._script);
1031cb0ef41Sopenharmony_ci      scriptNode = await builder.createScriptNode(this._script.startLine);
1041cb0ef41Sopenharmony_ci      if (script != this._script) return;
1051cb0ef41Sopenharmony_ci      this._sourcePositionsToMarkNodesPromise.resolve(
1061cb0ef41Sopenharmony_ci          builder.sourcePositionToMarkers);
1071cb0ef41Sopenharmony_ci    } else {
1081cb0ef41Sopenharmony_ci      scriptNode = DOM.div();
1091cb0ef41Sopenharmony_ci      this._selectedMarkNodes = undefined;
1101cb0ef41Sopenharmony_ci      this._sourcePositionsToMarkNodesPromise.resolve(new Map());
1111cb0ef41Sopenharmony_ci    }
1121cb0ef41Sopenharmony_ci    const oldScriptNode = this.script.childNodes[1];
1131cb0ef41Sopenharmony_ci    this.script.replaceChild(scriptNode, oldScriptNode);
1141cb0ef41Sopenharmony_ci  }
1151cb0ef41Sopenharmony_ci
1161cb0ef41Sopenharmony_ci  async _focusSelectedMarkers(delay_ms) {
1171cb0ef41Sopenharmony_ci    if (delay_ms) await delay(delay_ms);
1181cb0ef41Sopenharmony_ci    const sourcePositionsToMarkNodes =
1191cb0ef41Sopenharmony_ci        await this._sourcePositionsToMarkNodesPromise;
1201cb0ef41Sopenharmony_ci    // Remove all marked nodes.
1211cb0ef41Sopenharmony_ci    for (let markNode of sourcePositionsToMarkNodes.values()) {
1221cb0ef41Sopenharmony_ci      markNode.className = '';
1231cb0ef41Sopenharmony_ci    }
1241cb0ef41Sopenharmony_ci    for (let sourcePosition of this._selectedSourcePositions) {
1251cb0ef41Sopenharmony_ci      if (sourcePosition.script !== this._script) continue;
1261cb0ef41Sopenharmony_ci      sourcePositionsToMarkNodes.get(sourcePosition).className = 'marked';
1271cb0ef41Sopenharmony_ci    }
1281cb0ef41Sopenharmony_ci    this._scrollToFirstSourcePosition(sourcePositionsToMarkNodes)
1291cb0ef41Sopenharmony_ci  }
1301cb0ef41Sopenharmony_ci
1311cb0ef41Sopenharmony_ci  _scrollToFirstSourcePosition(sourcePositionsToMarkNodes) {
1321cb0ef41Sopenharmony_ci    const sourcePosition = this._selectedSourcePositions.find(
1331cb0ef41Sopenharmony_ci        each => each.script === this._script);
1341cb0ef41Sopenharmony_ci    if (!sourcePosition) return;
1351cb0ef41Sopenharmony_ci    const markNode = sourcePositionsToMarkNodes.get(sourcePosition);
1361cb0ef41Sopenharmony_ci    markNode.scrollIntoView(
1371cb0ef41Sopenharmony_ci        {behavior: 'smooth', block: 'center', inline: 'center'});
1381cb0ef41Sopenharmony_ci  }
1391cb0ef41Sopenharmony_ci
1401cb0ef41Sopenharmony_ci  _handleSelectScript(e) {
1411cb0ef41Sopenharmony_ci    const option =
1421cb0ef41Sopenharmony_ci        this.scriptDropdown.options[this.scriptDropdown.selectedIndex];
1431cb0ef41Sopenharmony_ci    this.script = option.script;
1441cb0ef41Sopenharmony_ci  }
1451cb0ef41Sopenharmony_ci
1461cb0ef41Sopenharmony_ci  _handleSelectRelated(e) {
1471cb0ef41Sopenharmony_ci    if (!this._script) return;
1481cb0ef41Sopenharmony_ci    this.dispatchEvent(new SelectRelatedEvent(this._script));
1491cb0ef41Sopenharmony_ci  }
1501cb0ef41Sopenharmony_ci
1511cb0ef41Sopenharmony_ci  setSelectedSourcePositionInternal(sourcePosition) {
1521cb0ef41Sopenharmony_ci    this._selectedSourcePositions = [sourcePosition];
1531cb0ef41Sopenharmony_ci    console.assert(sourcePosition.script === this._script);
1541cb0ef41Sopenharmony_ci  }
1551cb0ef41Sopenharmony_ci
1561cb0ef41Sopenharmony_ci  handleSourcePositionClick(e) {
1571cb0ef41Sopenharmony_ci    const sourcePosition = e.target.sourcePosition;
1581cb0ef41Sopenharmony_ci    this.setSelectedSourcePositionInternal(sourcePosition);
1591cb0ef41Sopenharmony_ci    this.dispatchEvent(new SelectRelatedEvent(sourcePosition));
1601cb0ef41Sopenharmony_ci  }
1611cb0ef41Sopenharmony_ci
1621cb0ef41Sopenharmony_ci  handleSourcePositionMouseOver(e) {
1631cb0ef41Sopenharmony_ci    const sourcePosition = e.target.sourcePosition;
1641cb0ef41Sopenharmony_ci    const entries = sourcePosition.entries;
1651cb0ef41Sopenharmony_ci    const toolTipContent = DOM.div();
1661cb0ef41Sopenharmony_ci    toolTipContent.appendChild(
1671cb0ef41Sopenharmony_ci        new ToolTipTableBuilder(this, entries).tableNode);
1681cb0ef41Sopenharmony_ci
1691cb0ef41Sopenharmony_ci    let sourceMapContent;
1701cb0ef41Sopenharmony_ci    switch (this._script.sourceMapState) {
1711cb0ef41Sopenharmony_ci      case 'loaded': {
1721cb0ef41Sopenharmony_ci        const originalPosition = sourcePosition.originalPosition;
1731cb0ef41Sopenharmony_ci        if (originalPosition.source === null) {
1741cb0ef41Sopenharmony_ci          sourceMapContent =
1751cb0ef41Sopenharmony_ci              DOM.element('i', {textContent: 'no source mapping for location'});
1761cb0ef41Sopenharmony_ci        } else {
1771cb0ef41Sopenharmony_ci          sourceMapContent = DOM.element('a', {
1781cb0ef41Sopenharmony_ci            href: `${originalPosition.source}`,
1791cb0ef41Sopenharmony_ci            target: '_blank',
1801cb0ef41Sopenharmony_ci            textContent: `${originalPosition.source}:${originalPosition.line}:${
1811cb0ef41Sopenharmony_ci                originalPosition.column}`
1821cb0ef41Sopenharmony_ci          });
1831cb0ef41Sopenharmony_ci        }
1841cb0ef41Sopenharmony_ci        break;
1851cb0ef41Sopenharmony_ci      }
1861cb0ef41Sopenharmony_ci      case 'loading':
1871cb0ef41Sopenharmony_ci        sourceMapContent =
1881cb0ef41Sopenharmony_ci            DOM.element('i', {textContent: 'source map still loading...'});
1891cb0ef41Sopenharmony_ci        break;
1901cb0ef41Sopenharmony_ci      case 'failed':
1911cb0ef41Sopenharmony_ci        sourceMapContent =
1921cb0ef41Sopenharmony_ci            DOM.element('i', {textContent: 'source map failed to load'});
1931cb0ef41Sopenharmony_ci        break;
1941cb0ef41Sopenharmony_ci      case 'none':
1951cb0ef41Sopenharmony_ci        sourceMapContent = DOM.element('i', {textContent: 'no source map'});
1961cb0ef41Sopenharmony_ci        break;
1971cb0ef41Sopenharmony_ci      default:
1981cb0ef41Sopenharmony_ci        break;
1991cb0ef41Sopenharmony_ci    }
2001cb0ef41Sopenharmony_ci    toolTipContent.appendChild(sourceMapContent);
2011cb0ef41Sopenharmony_ci    this.dispatchEvent(new ToolTipEvent(toolTipContent, e.target));
2021cb0ef41Sopenharmony_ci  }
2031cb0ef41Sopenharmony_ci
2041cb0ef41Sopenharmony_ci  handleShowToolTipEntries(event) {
2051cb0ef41Sopenharmony_ci    let entries = event.currentTarget.data;
2061cb0ef41Sopenharmony_ci    const sourcePosition = entries[0].sourcePosition;
2071cb0ef41Sopenharmony_ci    // Add a source position entry so the current position stays focused.
2081cb0ef41Sopenharmony_ci    this.setSelectedSourcePositionInternal(sourcePosition);
2091cb0ef41Sopenharmony_ci    entries = entries.concat(this._selectedSourcePositions);
2101cb0ef41Sopenharmony_ci    this.dispatchEvent(new SelectionEvent(entries));
2111cb0ef41Sopenharmony_ci  }
2121cb0ef41Sopenharmony_ci});
2131cb0ef41Sopenharmony_ci
2141cb0ef41Sopenharmony_ciclass ToolTipTableBuilder {
2151cb0ef41Sopenharmony_ci  constructor(scriptPanel, entries) {
2161cb0ef41Sopenharmony_ci    this._scriptPanel = scriptPanel;
2171cb0ef41Sopenharmony_ci    this.tableNode = DOM.table();
2181cb0ef41Sopenharmony_ci    const tr = DOM.tr();
2191cb0ef41Sopenharmony_ci    tr.appendChild(DOM.td('Type'));
2201cb0ef41Sopenharmony_ci    tr.appendChild(DOM.td('Subtype'));
2211cb0ef41Sopenharmony_ci    tr.appendChild(DOM.td('Count'));
2221cb0ef41Sopenharmony_ci    this.tableNode.appendChild(document.createElement('thead')).appendChild(tr);
2231cb0ef41Sopenharmony_ci    groupBy(entries, each => each.constructor, true).forEach(group => {
2241cb0ef41Sopenharmony_ci      this.addRow(group.key.name, 'all', entries, false)
2251cb0ef41Sopenharmony_ci      groupBy(group.entries, each => each.type, true).forEach(group => {
2261cb0ef41Sopenharmony_ci        this.addRow('', group.key, group.entries, false)
2271cb0ef41Sopenharmony_ci      })
2281cb0ef41Sopenharmony_ci    })
2291cb0ef41Sopenharmony_ci  }
2301cb0ef41Sopenharmony_ci
2311cb0ef41Sopenharmony_ci  addRow(name, subtypeName, entries) {
2321cb0ef41Sopenharmony_ci    const tr = DOM.tr();
2331cb0ef41Sopenharmony_ci    tr.appendChild(DOM.td(name));
2341cb0ef41Sopenharmony_ci    tr.appendChild(DOM.td(subtypeName));
2351cb0ef41Sopenharmony_ci    tr.appendChild(DOM.td(entries.length));
2361cb0ef41Sopenharmony_ci    const button =
2371cb0ef41Sopenharmony_ci        DOM.button('Show', this._scriptPanel.showToolTipEntriesHandler);
2381cb0ef41Sopenharmony_ci    button.data = entries;
2391cb0ef41Sopenharmony_ci    tr.appendChild(DOM.td(button));
2401cb0ef41Sopenharmony_ci    this.tableNode.appendChild(tr);
2411cb0ef41Sopenharmony_ci  }
2421cb0ef41Sopenharmony_ci}
2431cb0ef41Sopenharmony_ci
2441cb0ef41Sopenharmony_ciclass SourcePositionIterator {
2451cb0ef41Sopenharmony_ci  _entries;
2461cb0ef41Sopenharmony_ci  _index = 0;
2471cb0ef41Sopenharmony_ci  constructor(sourcePositions) {
2481cb0ef41Sopenharmony_ci    this._entries = sourcePositions;
2491cb0ef41Sopenharmony_ci  }
2501cb0ef41Sopenharmony_ci
2511cb0ef41Sopenharmony_ci  * forLine(lineIndex) {
2521cb0ef41Sopenharmony_ci    this._findStart(lineIndex);
2531cb0ef41Sopenharmony_ci    while (!this._done() && this._current().line === lineIndex) {
2541cb0ef41Sopenharmony_ci      yield this._current();
2551cb0ef41Sopenharmony_ci      this._next();
2561cb0ef41Sopenharmony_ci    }
2571cb0ef41Sopenharmony_ci  }
2581cb0ef41Sopenharmony_ci
2591cb0ef41Sopenharmony_ci  _findStart(lineIndex) {
2601cb0ef41Sopenharmony_ci    while (!this._done() && this._current().line < lineIndex) {
2611cb0ef41Sopenharmony_ci      this._next();
2621cb0ef41Sopenharmony_ci    }
2631cb0ef41Sopenharmony_ci  }
2641cb0ef41Sopenharmony_ci
2651cb0ef41Sopenharmony_ci  _current() {
2661cb0ef41Sopenharmony_ci    return this._entries[this._index];
2671cb0ef41Sopenharmony_ci  }
2681cb0ef41Sopenharmony_ci
2691cb0ef41Sopenharmony_ci  _done() {
2701cb0ef41Sopenharmony_ci    return this._index >= this._entries.length;
2711cb0ef41Sopenharmony_ci  }
2721cb0ef41Sopenharmony_ci
2731cb0ef41Sopenharmony_ci  _next() {
2741cb0ef41Sopenharmony_ci    this._index++;
2751cb0ef41Sopenharmony_ci  }
2761cb0ef41Sopenharmony_ci}
2771cb0ef41Sopenharmony_ci
2781cb0ef41Sopenharmony_cifunction* lineIterator(source, startLine) {
2791cb0ef41Sopenharmony_ci  let current = 0;
2801cb0ef41Sopenharmony_ci  let line = startLine;
2811cb0ef41Sopenharmony_ci  while (current < source.length) {
2821cb0ef41Sopenharmony_ci    const next = source.indexOf('\n', current);
2831cb0ef41Sopenharmony_ci    if (next === -1) break;
2841cb0ef41Sopenharmony_ci    yield [line, source.substring(current, next)];
2851cb0ef41Sopenharmony_ci    line++;
2861cb0ef41Sopenharmony_ci    current = next + 1;
2871cb0ef41Sopenharmony_ci  }
2881cb0ef41Sopenharmony_ci  if (current < source.length) yield [line, source.substring(current)];
2891cb0ef41Sopenharmony_ci}
2901cb0ef41Sopenharmony_ci
2911cb0ef41Sopenharmony_ciclass LineBuilder {
2921cb0ef41Sopenharmony_ci  static _colorMap = (function() {
2931cb0ef41Sopenharmony_ci    const map = new Map();
2941cb0ef41Sopenharmony_ci    let i = 0;
2951cb0ef41Sopenharmony_ci    for (let type of App.allEventTypes) {
2961cb0ef41Sopenharmony_ci      map.set(type, CSSColor.at(i++));
2971cb0ef41Sopenharmony_ci    }
2981cb0ef41Sopenharmony_ci    return map;
2991cb0ef41Sopenharmony_ci  })();
3001cb0ef41Sopenharmony_ci  static get colorMap() {
3011cb0ef41Sopenharmony_ci    return this._colorMap;
3021cb0ef41Sopenharmony_ci  }
3031cb0ef41Sopenharmony_ci
3041cb0ef41Sopenharmony_ci  _script;
3051cb0ef41Sopenharmony_ci  _clickHandler;
3061cb0ef41Sopenharmony_ci  _mouseoverHandler;
3071cb0ef41Sopenharmony_ci  _sourcePositionToMarkers = new Map();
3081cb0ef41Sopenharmony_ci
3091cb0ef41Sopenharmony_ci  constructor(panel, script) {
3101cb0ef41Sopenharmony_ci    this._script = script;
3111cb0ef41Sopenharmony_ci    this._clickHandler = panel.handleSourcePositionClick.bind(panel);
3121cb0ef41Sopenharmony_ci    this._mouseoverHandler = panel.handleSourcePositionMouseOver.bind(panel);
3131cb0ef41Sopenharmony_ci  }
3141cb0ef41Sopenharmony_ci
3151cb0ef41Sopenharmony_ci  get sourcePositionToMarkers() {
3161cb0ef41Sopenharmony_ci    return this._sourcePositionToMarkers;
3171cb0ef41Sopenharmony_ci  }
3181cb0ef41Sopenharmony_ci
3191cb0ef41Sopenharmony_ci  async createScriptNode(startLine) {
3201cb0ef41Sopenharmony_ci    const scriptNode = DOM.div('scriptNode');
3211cb0ef41Sopenharmony_ci
3221cb0ef41Sopenharmony_ci    // TODO: sort on script finalization.
3231cb0ef41Sopenharmony_ci    this._script.sourcePositions.sort((a, b) => {
3241cb0ef41Sopenharmony_ci      if (a.line === b.line) return a.column - b.column;
3251cb0ef41Sopenharmony_ci      return a.line - b.line;
3261cb0ef41Sopenharmony_ci    });
3271cb0ef41Sopenharmony_ci
3281cb0ef41Sopenharmony_ci    const sourcePositionsIterator =
3291cb0ef41Sopenharmony_ci        new SourcePositionIterator(this._script.sourcePositions);
3301cb0ef41Sopenharmony_ci    scriptNode.style.counterReset = `sourceLineCounter ${startLine - 1}`;
3311cb0ef41Sopenharmony_ci    for (let [lineIndex, line] of lineIterator(
3321cb0ef41Sopenharmony_ci             this._script.source, startLine)) {
3331cb0ef41Sopenharmony_ci      scriptNode.appendChild(
3341cb0ef41Sopenharmony_ci          this._createLineNode(sourcePositionsIterator, lineIndex, line));
3351cb0ef41Sopenharmony_ci    }
3361cb0ef41Sopenharmony_ci    if (this._script.sourcePositions.length !=
3371cb0ef41Sopenharmony_ci        this._sourcePositionToMarkers.size) {
3381cb0ef41Sopenharmony_ci      console.error('Not all SourcePositions were processed.');
3391cb0ef41Sopenharmony_ci    }
3401cb0ef41Sopenharmony_ci    return scriptNode;
3411cb0ef41Sopenharmony_ci  }
3421cb0ef41Sopenharmony_ci
3431cb0ef41Sopenharmony_ci  _createLineNode(sourcePositionsIterator, lineIndex, line) {
3441cb0ef41Sopenharmony_ci    const lineNode = DOM.span();
3451cb0ef41Sopenharmony_ci    let columnIndex = 0;
3461cb0ef41Sopenharmony_ci    for (const sourcePosition of sourcePositionsIterator.forLine(lineIndex)) {
3471cb0ef41Sopenharmony_ci      const nextColumnIndex = sourcePosition.column - 1;
3481cb0ef41Sopenharmony_ci      lineNode.appendChild(document.createTextNode(
3491cb0ef41Sopenharmony_ci          line.substring(columnIndex, nextColumnIndex)));
3501cb0ef41Sopenharmony_ci      columnIndex = nextColumnIndex;
3511cb0ef41Sopenharmony_ci
3521cb0ef41Sopenharmony_ci      lineNode.appendChild(
3531cb0ef41Sopenharmony_ci          this._createMarkerNode(line[columnIndex], sourcePosition));
3541cb0ef41Sopenharmony_ci      columnIndex++;
3551cb0ef41Sopenharmony_ci    }
3561cb0ef41Sopenharmony_ci    lineNode.appendChild(
3571cb0ef41Sopenharmony_ci        document.createTextNode(line.substring(columnIndex) + '\n'));
3581cb0ef41Sopenharmony_ci    return lineNode;
3591cb0ef41Sopenharmony_ci  }
3601cb0ef41Sopenharmony_ci
3611cb0ef41Sopenharmony_ci  _createMarkerNode(text, sourcePosition) {
3621cb0ef41Sopenharmony_ci    const marker = document.createElement('mark');
3631cb0ef41Sopenharmony_ci    this._sourcePositionToMarkers.set(sourcePosition, marker);
3641cb0ef41Sopenharmony_ci    marker.textContent = text;
3651cb0ef41Sopenharmony_ci    marker.sourcePosition = sourcePosition;
3661cb0ef41Sopenharmony_ci    marker.onclick = this._clickHandler;
3671cb0ef41Sopenharmony_ci    marker.onmouseover = this._mouseoverHandler;
3681cb0ef41Sopenharmony_ci
3691cb0ef41Sopenharmony_ci    const entries = sourcePosition.entries;
3701cb0ef41Sopenharmony_ci    const groups = groupBy(entries, entry => entry.constructor);
3711cb0ef41Sopenharmony_ci    if (groups.length > 1) {
3721cb0ef41Sopenharmony_ci      const stops = gradientStopsFromGroups(
3731cb0ef41Sopenharmony_ci          entries.length, '%', groups, type => LineBuilder.colorMap.get(type));
3741cb0ef41Sopenharmony_ci      marker.style.backgroundImage = `linear-gradient(0deg,${stops.join(',')})`
3751cb0ef41Sopenharmony_ci    } else {
3761cb0ef41Sopenharmony_ci      marker.style.backgroundColor = LineBuilder.colorMap.get(groups[0].key)
3771cb0ef41Sopenharmony_ci    }
3781cb0ef41Sopenharmony_ci
3791cb0ef41Sopenharmony_ci    return marker;
3801cb0ef41Sopenharmony_ci  }
3811cb0ef41Sopenharmony_ci}
382