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_ci 51cb0ef41Sopenharmony_ciimport {SelectRelatedEvent} from './events.mjs'; 61cb0ef41Sopenharmony_ciimport {CollapsableElement, DOM, formatBytes, formatMicroSeconds} from './helper.mjs'; 71cb0ef41Sopenharmony_ci 81cb0ef41Sopenharmony_ciDOM.defineCustomElement('view/code-panel', 91cb0ef41Sopenharmony_ci (templateText) => 101cb0ef41Sopenharmony_ci class CodePanel extends CollapsableElement { 111cb0ef41Sopenharmony_ci _timeline; 121cb0ef41Sopenharmony_ci _selectedEntries; 131cb0ef41Sopenharmony_ci _entry; 141cb0ef41Sopenharmony_ci 151cb0ef41Sopenharmony_ci constructor() { 161cb0ef41Sopenharmony_ci super(templateText); 171cb0ef41Sopenharmony_ci this._propertiesNode = this.$('#properties'); 181cb0ef41Sopenharmony_ci this._codeSelectNode = this.$('#codeSelect'); 191cb0ef41Sopenharmony_ci this._disassemblyNode = this.$('#disassembly'); 201cb0ef41Sopenharmony_ci this._feedbackVectorNode = this.$('#feedbackVector'); 211cb0ef41Sopenharmony_ci this._selectionHandler = new SelectionHandler(this._disassemblyNode); 221cb0ef41Sopenharmony_ci 231cb0ef41Sopenharmony_ci this._codeSelectNode.onchange = this._handleSelectCode.bind(this); 241cb0ef41Sopenharmony_ci this.$('#selectedRelatedButton').onclick = 251cb0ef41Sopenharmony_ci this._handleSelectRelated.bind(this) 261cb0ef41Sopenharmony_ci } 271cb0ef41Sopenharmony_ci 281cb0ef41Sopenharmony_ci set timeline(timeline) { 291cb0ef41Sopenharmony_ci this._timeline = timeline; 301cb0ef41Sopenharmony_ci this.$('.panel').style.display = timeline.isEmpty() ? 'none' : 'inherit'; 311cb0ef41Sopenharmony_ci this.requestUpdate(); 321cb0ef41Sopenharmony_ci } 331cb0ef41Sopenharmony_ci 341cb0ef41Sopenharmony_ci set selectedEntries(entries) { 351cb0ef41Sopenharmony_ci this._selectedEntries = entries; 361cb0ef41Sopenharmony_ci this.entry = entries.first(); 371cb0ef41Sopenharmony_ci } 381cb0ef41Sopenharmony_ci 391cb0ef41Sopenharmony_ci set entry(entry) { 401cb0ef41Sopenharmony_ci this._entry = entry; 411cb0ef41Sopenharmony_ci if (!entry) { 421cb0ef41Sopenharmony_ci this._propertiesNode.propertyDict = {}; 431cb0ef41Sopenharmony_ci } else { 441cb0ef41Sopenharmony_ci this._propertiesNode.propertyDict = { 451cb0ef41Sopenharmony_ci '__this__': entry, 461cb0ef41Sopenharmony_ci functionName: entry.functionName, 471cb0ef41Sopenharmony_ci size: formatBytes(entry.size), 481cb0ef41Sopenharmony_ci creationTime: formatMicroSeconds(entry.time / 1000), 491cb0ef41Sopenharmony_ci sourcePosition: entry.sourcePosition, 501cb0ef41Sopenharmony_ci script: entry.script, 511cb0ef41Sopenharmony_ci type: entry.type, 521cb0ef41Sopenharmony_ci kind: entry.kindName, 531cb0ef41Sopenharmony_ci variants: entry.variants.length > 1 ? [undefined, ...entry.variants] : 541cb0ef41Sopenharmony_ci undefined, 551cb0ef41Sopenharmony_ci }; 561cb0ef41Sopenharmony_ci } 571cb0ef41Sopenharmony_ci this.requestUpdate(); 581cb0ef41Sopenharmony_ci } 591cb0ef41Sopenharmony_ci 601cb0ef41Sopenharmony_ci _update() { 611cb0ef41Sopenharmony_ci this._updateSelect(); 621cb0ef41Sopenharmony_ci this._updateDisassembly(); 631cb0ef41Sopenharmony_ci this._updateFeedbackVector(); 641cb0ef41Sopenharmony_ci } 651cb0ef41Sopenharmony_ci 661cb0ef41Sopenharmony_ci _updateFeedbackVector() { 671cb0ef41Sopenharmony_ci if (!this._entry?.feedbackVector) { 681cb0ef41Sopenharmony_ci this._feedbackVectorNode.propertyDict = {}; 691cb0ef41Sopenharmony_ci } else { 701cb0ef41Sopenharmony_ci const dict = this._entry.feedbackVector.toolTipDict; 711cb0ef41Sopenharmony_ci delete dict.title; 721cb0ef41Sopenharmony_ci delete dict.code; 731cb0ef41Sopenharmony_ci this._feedbackVectorNode.propertyDict = dict; 741cb0ef41Sopenharmony_ci } 751cb0ef41Sopenharmony_ci } 761cb0ef41Sopenharmony_ci 771cb0ef41Sopenharmony_ci _updateDisassembly() { 781cb0ef41Sopenharmony_ci this._disassemblyNode.innerText = ''; 791cb0ef41Sopenharmony_ci if (!this._entry?.code) return; 801cb0ef41Sopenharmony_ci try { 811cb0ef41Sopenharmony_ci this._disassemblyNode.appendChild( 821cb0ef41Sopenharmony_ci new AssemblyFormatter(this._entry).fragment); 831cb0ef41Sopenharmony_ci } catch (e) { 841cb0ef41Sopenharmony_ci console.error(e); 851cb0ef41Sopenharmony_ci this._disassemblyNode.innerText = this._entry.code; 861cb0ef41Sopenharmony_ci } 871cb0ef41Sopenharmony_ci } 881cb0ef41Sopenharmony_ci 891cb0ef41Sopenharmony_ci _updateSelect() { 901cb0ef41Sopenharmony_ci const select = this._codeSelectNode; 911cb0ef41Sopenharmony_ci if (select.data === this._selectedEntries) return; 921cb0ef41Sopenharmony_ci select.data = this._selectedEntries; 931cb0ef41Sopenharmony_ci select.options.length = 0; 941cb0ef41Sopenharmony_ci const sorted = 951cb0ef41Sopenharmony_ci this._selectedEntries.slice().sort((a, b) => a.time - b.time); 961cb0ef41Sopenharmony_ci for (const code of this._selectedEntries) { 971cb0ef41Sopenharmony_ci const option = DOM.element('option'); 981cb0ef41Sopenharmony_ci option.text = this._entrySummary(code); 991cb0ef41Sopenharmony_ci option.data = code; 1001cb0ef41Sopenharmony_ci select.add(option); 1011cb0ef41Sopenharmony_ci } 1021cb0ef41Sopenharmony_ci } 1031cb0ef41Sopenharmony_ci _entrySummary(code) { 1041cb0ef41Sopenharmony_ci if (code.isBuiltinKind) { 1051cb0ef41Sopenharmony_ci return `${code.functionName}(...) t=${ 1061cb0ef41Sopenharmony_ci formatMicroSeconds(code.time)} size=${formatBytes(code.size)}`; 1071cb0ef41Sopenharmony_ci } 1081cb0ef41Sopenharmony_ci return `${code.functionName}(...) t=${formatMicroSeconds(code.time)} size=${ 1091cb0ef41Sopenharmony_ci formatBytes(code.size)} script=${code.script?.toString()}`; 1101cb0ef41Sopenharmony_ci } 1111cb0ef41Sopenharmony_ci 1121cb0ef41Sopenharmony_ci _handleSelectCode() { 1131cb0ef41Sopenharmony_ci this.entry = this._codeSelectNode.selectedOptions[0].data; 1141cb0ef41Sopenharmony_ci } 1151cb0ef41Sopenharmony_ci 1161cb0ef41Sopenharmony_ci _handleSelectRelated(e) { 1171cb0ef41Sopenharmony_ci if (!this._entry) return; 1181cb0ef41Sopenharmony_ci this.dispatchEvent(new SelectRelatedEvent(this._entry)); 1191cb0ef41Sopenharmony_ci } 1201cb0ef41Sopenharmony_ci}); 1211cb0ef41Sopenharmony_ci 1221cb0ef41Sopenharmony_ciconst kRegisters = ['rsp', 'rbp', 'rax', 'rbx', 'rcx', 'rdx', 'rsi', 'rdi']; 1231cb0ef41Sopenharmony_ci// Make sure we dont match register on bytecode: Star1 or Star2 1241cb0ef41Sopenharmony_ciconst kAvoidBytecodeOpsRegexpSource = '(.*?[^a-zA-Z])' 1251cb0ef41Sopenharmony_ci// Look for registers in strings like: movl rbx,[rcx-0x30] 1261cb0ef41Sopenharmony_ciconst kRegisterRegexpSource = `(?<register>${kRegisters.join('|')}|r[0-9]+)` 1271cb0ef41Sopenharmony_ciconst kRegisterSplitRegexp = 1281cb0ef41Sopenharmony_ci new RegExp(`${kAvoidBytecodeOpsRegexpSource}${kRegisterRegexpSource}`) 1291cb0ef41Sopenharmony_ciconst kIsRegisterRegexp = new RegExp(`^${kRegisterRegexpSource}$`); 1301cb0ef41Sopenharmony_ci 1311cb0ef41Sopenharmony_ciconst kFullAddressRegexp = /(0x[0-9a-f]{8,})/; 1321cb0ef41Sopenharmony_ciconst kRelativeAddressRegexp = /([+-]0x[0-9a-f]+)/; 1331cb0ef41Sopenharmony_ciconst kAnyAddressRegexp = /(?<address>[+-]?0x[0-9a-f]+)/; 1341cb0ef41Sopenharmony_ci 1351cb0ef41Sopenharmony_ciconst kJmpRegexp = new RegExp(`jmp ${kRegisterRegexpSource}`); 1361cb0ef41Sopenharmony_ciconst kMovRegexp = 1371cb0ef41Sopenharmony_ci new RegExp(`mov. ${kRegisterRegexpSource},${kAnyAddressRegexp.source}`); 1381cb0ef41Sopenharmony_ci 1391cb0ef41Sopenharmony_ciclass AssemblyFormatter { 1401cb0ef41Sopenharmony_ci constructor(codeLogEntry) { 1411cb0ef41Sopenharmony_ci this._fragment = new DocumentFragment(); 1421cb0ef41Sopenharmony_ci this._entry = codeLogEntry; 1431cb0ef41Sopenharmony_ci this._lines = new Map(); 1441cb0ef41Sopenharmony_ci this._previousLine = undefined; 1451cb0ef41Sopenharmony_ci this._parseLines(); 1461cb0ef41Sopenharmony_ci this._format(); 1471cb0ef41Sopenharmony_ci } 1481cb0ef41Sopenharmony_ci 1491cb0ef41Sopenharmony_ci get fragment() { 1501cb0ef41Sopenharmony_ci return this._fragment; 1511cb0ef41Sopenharmony_ci } 1521cb0ef41Sopenharmony_ci 1531cb0ef41Sopenharmony_ci _format() { 1541cb0ef41Sopenharmony_ci let block = DOM.div(['basicBlock', 'header']); 1551cb0ef41Sopenharmony_ci this._lines.forEach(line => { 1561cb0ef41Sopenharmony_ci if (!block || line.isBlockStart) { 1571cb0ef41Sopenharmony_ci this._fragment.appendChild(block); 1581cb0ef41Sopenharmony_ci block = DOM.div('basicBlock'); 1591cb0ef41Sopenharmony_ci } 1601cb0ef41Sopenharmony_ci block.appendChild(line.format()) 1611cb0ef41Sopenharmony_ci }); 1621cb0ef41Sopenharmony_ci this._fragment.appendChild(block); 1631cb0ef41Sopenharmony_ci } 1641cb0ef41Sopenharmony_ci 1651cb0ef41Sopenharmony_ci _parseLines() { 1661cb0ef41Sopenharmony_ci this._entry.code.split('\n').forEach(each => this._parseLine(each)); 1671cb0ef41Sopenharmony_ci this._findBasicBlocks(); 1681cb0ef41Sopenharmony_ci } 1691cb0ef41Sopenharmony_ci 1701cb0ef41Sopenharmony_ci _parseLine(line) { 1711cb0ef41Sopenharmony_ci const parts = line.split(' '); 1721cb0ef41Sopenharmony_ci // Use unique placeholder for address: 1731cb0ef41Sopenharmony_ci let lineAddress = -this._lines.size; 1741cb0ef41Sopenharmony_ci for (let part of parts) { 1751cb0ef41Sopenharmony_ci if (kFullAddressRegexp.test(part)) { 1761cb0ef41Sopenharmony_ci lineAddress = parseInt(part); 1771cb0ef41Sopenharmony_ci break; 1781cb0ef41Sopenharmony_ci } 1791cb0ef41Sopenharmony_ci } 1801cb0ef41Sopenharmony_ci const newLine = new AssemblyLine(lineAddress, parts); 1811cb0ef41Sopenharmony_ci // special hack for: mov reg 0x...; jmp reg; 1821cb0ef41Sopenharmony_ci if (lineAddress <= 0 && this._previousLine) { 1831cb0ef41Sopenharmony_ci const jmpMatch = line.match(kJmpRegexp); 1841cb0ef41Sopenharmony_ci if (jmpMatch) { 1851cb0ef41Sopenharmony_ci const register = jmpMatch.groups.register; 1861cb0ef41Sopenharmony_ci const movMatch = this._previousLine.line.match(kMovRegexp); 1871cb0ef41Sopenharmony_ci if (movMatch.groups.register === register) { 1881cb0ef41Sopenharmony_ci newLine.outgoing.push(movMatch.groups.address); 1891cb0ef41Sopenharmony_ci } 1901cb0ef41Sopenharmony_ci } 1911cb0ef41Sopenharmony_ci } 1921cb0ef41Sopenharmony_ci this._lines.set(lineAddress, newLine); 1931cb0ef41Sopenharmony_ci this._previousLine = newLine; 1941cb0ef41Sopenharmony_ci } 1951cb0ef41Sopenharmony_ci 1961cb0ef41Sopenharmony_ci _findBasicBlocks() { 1971cb0ef41Sopenharmony_ci const lines = Array.from(this._lines.values()); 1981cb0ef41Sopenharmony_ci for (let i = 0; i < lines.length; i++) { 1991cb0ef41Sopenharmony_ci const line = lines[i]; 2001cb0ef41Sopenharmony_ci let forceBasicBlock = i == 0; 2011cb0ef41Sopenharmony_ci if (i > 0 && i < lines.length - 1) { 2021cb0ef41Sopenharmony_ci const prevHasAddress = lines[i - 1].address > 0; 2031cb0ef41Sopenharmony_ci const currentHasAddress = lines[i].address > 0; 2041cb0ef41Sopenharmony_ci const nextHasAddress = lines[i + 1].address > 0; 2051cb0ef41Sopenharmony_ci if (prevHasAddress !== currentHasAddress && 2061cb0ef41Sopenharmony_ci currentHasAddress == nextHasAddress) { 2071cb0ef41Sopenharmony_ci forceBasicBlock = true; 2081cb0ef41Sopenharmony_ci } 2091cb0ef41Sopenharmony_ci } 2101cb0ef41Sopenharmony_ci if (forceBasicBlock) { 2111cb0ef41Sopenharmony_ci // Add fake-incoming address to mark a block start. 2121cb0ef41Sopenharmony_ci line.addIncoming(0); 2131cb0ef41Sopenharmony_ci } 2141cb0ef41Sopenharmony_ci line.outgoing.forEach(address => { 2151cb0ef41Sopenharmony_ci const outgoing = this._lines.get(address); 2161cb0ef41Sopenharmony_ci if (outgoing) outgoing.addIncoming(line.address); 2171cb0ef41Sopenharmony_ci }) 2181cb0ef41Sopenharmony_ci } 2191cb0ef41Sopenharmony_ci } 2201cb0ef41Sopenharmony_ci} 2211cb0ef41Sopenharmony_ci 2221cb0ef41Sopenharmony_ciclass AssemblyLine { 2231cb0ef41Sopenharmony_ci constructor(address, parts) { 2241cb0ef41Sopenharmony_ci this.address = address; 2251cb0ef41Sopenharmony_ci this.outgoing = []; 2261cb0ef41Sopenharmony_ci this.incoming = []; 2271cb0ef41Sopenharmony_ci parts.forEach(part => { 2281cb0ef41Sopenharmony_ci const fullMatch = part.match(kFullAddressRegexp); 2291cb0ef41Sopenharmony_ci if (fullMatch) { 2301cb0ef41Sopenharmony_ci let inlineAddress = parseInt(fullMatch[0]); 2311cb0ef41Sopenharmony_ci if (inlineAddress != this.address) this.outgoing.push(inlineAddress); 2321cb0ef41Sopenharmony_ci if (Number.isNaN(inlineAddress)) throw 'invalid address'; 2331cb0ef41Sopenharmony_ci } else if (kRelativeAddressRegexp.test(part)) { 2341cb0ef41Sopenharmony_ci this.outgoing.push(this._toAbsoluteAddress(part)); 2351cb0ef41Sopenharmony_ci } 2361cb0ef41Sopenharmony_ci }); 2371cb0ef41Sopenharmony_ci this.line = parts.join(' '); 2381cb0ef41Sopenharmony_ci } 2391cb0ef41Sopenharmony_ci 2401cb0ef41Sopenharmony_ci get isBlockStart() { 2411cb0ef41Sopenharmony_ci return this.incoming.length > 0; 2421cb0ef41Sopenharmony_ci } 2431cb0ef41Sopenharmony_ci 2441cb0ef41Sopenharmony_ci addIncoming(address) { 2451cb0ef41Sopenharmony_ci this.incoming.push(address); 2461cb0ef41Sopenharmony_ci } 2471cb0ef41Sopenharmony_ci 2481cb0ef41Sopenharmony_ci format() { 2491cb0ef41Sopenharmony_ci const content = DOM.span({textContent: this.line + '\n'}); 2501cb0ef41Sopenharmony_ci let formattedCode = content.innerHTML.split(kRegisterSplitRegexp) 2511cb0ef41Sopenharmony_ci .map(part => this._formatRegisterPart(part)) 2521cb0ef41Sopenharmony_ci .join(''); 2531cb0ef41Sopenharmony_ci formattedCode = 2541cb0ef41Sopenharmony_ci formattedCode.split(kAnyAddressRegexp) 2551cb0ef41Sopenharmony_ci .map((part, index) => this._formatAddressPart(part, index)) 2561cb0ef41Sopenharmony_ci .join(''); 2571cb0ef41Sopenharmony_ci // Let's replace the base-address since it doesn't add any value. 2581cb0ef41Sopenharmony_ci // TODO 2591cb0ef41Sopenharmony_ci content.innerHTML = formattedCode; 2601cb0ef41Sopenharmony_ci return content; 2611cb0ef41Sopenharmony_ci } 2621cb0ef41Sopenharmony_ci 2631cb0ef41Sopenharmony_ci _formatRegisterPart(part) { 2641cb0ef41Sopenharmony_ci if (!kIsRegisterRegexp.test(part)) return part; 2651cb0ef41Sopenharmony_ci return `<span class="reg ${part}">${part}</span>` 2661cb0ef41Sopenharmony_ci } 2671cb0ef41Sopenharmony_ci 2681cb0ef41Sopenharmony_ci _formatAddressPart(part, index) { 2691cb0ef41Sopenharmony_ci if (kFullAddressRegexp.test(part)) { 2701cb0ef41Sopenharmony_ci // The first or second address must be the line address 2711cb0ef41Sopenharmony_ci if (index <= 1) { 2721cb0ef41Sopenharmony_ci return `<span class="addr line" data-addr="${part}">${part}</span>`; 2731cb0ef41Sopenharmony_ci } 2741cb0ef41Sopenharmony_ci return `<span class=addr data-addr="${part}">${part}</span>`; 2751cb0ef41Sopenharmony_ci } else if (kRelativeAddressRegexp.test(part)) { 2761cb0ef41Sopenharmony_ci return `<span class=addr data-addr="0x${ 2771cb0ef41Sopenharmony_ci this._toAbsoluteAddress(part).toString(16)}">${part}</span>`; 2781cb0ef41Sopenharmony_ci } else { 2791cb0ef41Sopenharmony_ci return part; 2801cb0ef41Sopenharmony_ci } 2811cb0ef41Sopenharmony_ci } 2821cb0ef41Sopenharmony_ci 2831cb0ef41Sopenharmony_ci _toAbsoluteAddress(part) { 2841cb0ef41Sopenharmony_ci return this.address + parseInt(part); 2851cb0ef41Sopenharmony_ci } 2861cb0ef41Sopenharmony_ci} 2871cb0ef41Sopenharmony_ci 2881cb0ef41Sopenharmony_ciclass SelectionHandler { 2891cb0ef41Sopenharmony_ci _currentRegisterHovered; 2901cb0ef41Sopenharmony_ci _currentRegisterClicked; 2911cb0ef41Sopenharmony_ci 2921cb0ef41Sopenharmony_ci constructor(node) { 2931cb0ef41Sopenharmony_ci this._node = node; 2941cb0ef41Sopenharmony_ci this._node.onmousemove = this._handleMouseMove.bind(this); 2951cb0ef41Sopenharmony_ci this._node.onclick = this._handleClick.bind(this); 2961cb0ef41Sopenharmony_ci } 2971cb0ef41Sopenharmony_ci 2981cb0ef41Sopenharmony_ci $(query) { 2991cb0ef41Sopenharmony_ci return this._node.querySelectorAll(query); 3001cb0ef41Sopenharmony_ci } 3011cb0ef41Sopenharmony_ci 3021cb0ef41Sopenharmony_ci _handleClick(event) { 3031cb0ef41Sopenharmony_ci const target = event.target; 3041cb0ef41Sopenharmony_ci if (target.classList.contains('addr')) { 3051cb0ef41Sopenharmony_ci return this._handleClickAddress(target); 3061cb0ef41Sopenharmony_ci } else if (target.classList.contains('reg')) { 3071cb0ef41Sopenharmony_ci this._handleClickRegister(target); 3081cb0ef41Sopenharmony_ci } else { 3091cb0ef41Sopenharmony_ci this._clearRegisterSelection(); 3101cb0ef41Sopenharmony_ci } 3111cb0ef41Sopenharmony_ci } 3121cb0ef41Sopenharmony_ci 3131cb0ef41Sopenharmony_ci _handleClickAddress(target) { 3141cb0ef41Sopenharmony_ci let targetAddress = target.getAttribute('data-addr') ?? target.innerText; 3151cb0ef41Sopenharmony_ci // Clear any selection 3161cb0ef41Sopenharmony_ci for (let addrNode of this.$('.addr.selected')) { 3171cb0ef41Sopenharmony_ci addrNode.classList.remove('selected'); 3181cb0ef41Sopenharmony_ci } 3191cb0ef41Sopenharmony_ci // Highlight all matching addresses 3201cb0ef41Sopenharmony_ci let lineAddrNode; 3211cb0ef41Sopenharmony_ci for (let addrNode of this.$(`.addr[data-addr="${targetAddress}"]`)) { 3221cb0ef41Sopenharmony_ci addrNode.classList.add('selected'); 3231cb0ef41Sopenharmony_ci if (addrNode.classList.contains('line') && lineAddrNode == undefined) { 3241cb0ef41Sopenharmony_ci lineAddrNode = addrNode; 3251cb0ef41Sopenharmony_ci } 3261cb0ef41Sopenharmony_ci } 3271cb0ef41Sopenharmony_ci // Jump to potential target address. 3281cb0ef41Sopenharmony_ci if (lineAddrNode) { 3291cb0ef41Sopenharmony_ci lineAddrNode.scrollIntoView({behavior: 'smooth', block: 'nearest'}); 3301cb0ef41Sopenharmony_ci } 3311cb0ef41Sopenharmony_ci } 3321cb0ef41Sopenharmony_ci 3331cb0ef41Sopenharmony_ci _handleClickRegister(target) { 3341cb0ef41Sopenharmony_ci this._setRegisterSelection(target.innerText); 3351cb0ef41Sopenharmony_ci this._currentRegisterClicked = this._currentRegisterHovered; 3361cb0ef41Sopenharmony_ci } 3371cb0ef41Sopenharmony_ci 3381cb0ef41Sopenharmony_ci _handleMouseMove(event) { 3391cb0ef41Sopenharmony_ci if (this._currentRegisterClicked) return; 3401cb0ef41Sopenharmony_ci const target = event.target; 3411cb0ef41Sopenharmony_ci if (!target.classList.contains('reg')) { 3421cb0ef41Sopenharmony_ci this._clearRegisterSelection(); 3431cb0ef41Sopenharmony_ci } else { 3441cb0ef41Sopenharmony_ci this._setRegisterSelection(target.innerText); 3451cb0ef41Sopenharmony_ci } 3461cb0ef41Sopenharmony_ci } 3471cb0ef41Sopenharmony_ci 3481cb0ef41Sopenharmony_ci _clearRegisterSelection() { 3491cb0ef41Sopenharmony_ci if (!this._currentRegisterHovered) return; 3501cb0ef41Sopenharmony_ci for (let node of this.$('.reg.selected')) { 3511cb0ef41Sopenharmony_ci node.classList.remove('selected'); 3521cb0ef41Sopenharmony_ci } 3531cb0ef41Sopenharmony_ci this._currentRegisterClicked = undefined; 3541cb0ef41Sopenharmony_ci this._currentRegisterHovered = undefined; 3551cb0ef41Sopenharmony_ci } 3561cb0ef41Sopenharmony_ci 3571cb0ef41Sopenharmony_ci _setRegisterSelection(register) { 3581cb0ef41Sopenharmony_ci if (register == this._currentRegisterHovered) return; 3591cb0ef41Sopenharmony_ci this._clearRegisterSelection(); 3601cb0ef41Sopenharmony_ci this._currentRegisterHovered = register; 3611cb0ef41Sopenharmony_ci for (let node of this.$(`.reg.${register}`)) { 3621cb0ef41Sopenharmony_ci node.classList.add('selected'); 3631cb0ef41Sopenharmony_ci } 3641cb0ef41Sopenharmony_ci } 3651cb0ef41Sopenharmony_ci} 366