1// Copyright 2021 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 {Timeline} from '../../timeline.mjs'; 6import {SelectTimeEvent} from '../events.mjs'; 7import {CSSColor, delay, SVG} from '../helper.mjs'; 8 9import {TimelineTrackBase} from './timeline-track-base.mjs' 10 11const kItemHeight = 8; 12 13export class TimelineTrackStackedBase extends TimelineTrackBase { 14 _originalContentWidth = 0; 15 _drawableItems = new Timeline(); 16 17 _updateChunks() { 18 // We don't need to update the chunks here. 19 this._updateDimensions(); 20 this.requestUpdate(); 21 } 22 23 set data(timeline) { 24 super.data = timeline; 25 this._contentWidth = 0; 26 if (timeline.values.length > 0) this._prepareDrawableItems(); 27 } 28 29 _handleDoubleClick(event) { 30 if (event.button !== 0) return; 31 this._selectionHandler.clearSelection(); 32 const item = this._getDrawableItemForEvent(event); 33 if (item === undefined) return; 34 event.stopImmediatePropagation(); 35 this.dispatchEvent(new SelectTimeEvent(item.startTime, item.endTime)); 36 return false; 37 } 38 39 _getStackDepthForEvent(event) { 40 return Math.floor(event.layerY / kItemHeight) - 1; 41 } 42 43 _getDrawableItemForEvent(event) { 44 const depth = this._getStackDepthForEvent(event); 45 const time = this.positionToTime(event.pageX); 46 const index = this._drawableItems.find(time); 47 for (let i = index - 1; i > 0; i--) { 48 const item = this._drawableItems.at(i); 49 if (item.depth != depth) continue; 50 if (item.endTime < time) continue; 51 return item; 52 } 53 return undefined; 54 } 55 56 _drawableItemToLogEntry(item) { 57 return item; 58 } 59 60 _getEntryForEvent(event) { 61 const item = this._getDrawableItemForEvent(event); 62 const logEntry = this._drawableItemToLogEntry(item); 63 if (item === undefined) return undefined; 64 const style = this.toolTipTargetNode.style; 65 style.left = `${event.layerX}px`; 66 style.top = `${(item.depth + 1) * kItemHeight}px`; 67 style.height = `${kItemHeight}px` 68 return logEntry; 69 } 70 71 _prepareDrawableItems() { 72 // Subclass responsibility. 73 } 74 75 _adjustStackDepth(maxDepth) { 76 // Account for empty top line 77 maxDepth++; 78 this._adjustHeight(maxDepth * kItemHeight); 79 } 80 81 _scaleContent(currentWidth) { 82 if (this._originalContentWidth == 0) return; 83 // Instead of repainting just scale the content. 84 const ratio = currentWidth / this._originalContentWidth; 85 this._scalableContentNode.style.transform = `scale(${ratio}, 1)`; 86 this.style.setProperty('--txt-scale', `scale(${1 / ratio}, 1)`); 87 } 88 89 async _drawContent() { 90 if (this._originalContentWidth > 0) return; 91 this._originalContentWidth = parseInt(this.timelineMarkersNode.style.width); 92 this._scalableContentNode.innerHTML = ''; 93 let buffer = ''; 94 const add = async () => { 95 const svg = SVG.svg(); 96 svg.innerHTML = buffer; 97 this._scalableContentNode.appendChild(svg); 98 buffer = ''; 99 await delay(50); 100 }; 101 const items = this._drawableItems.values; 102 for (let i = 0; i < items.length; i++) { 103 if ((i % 3000) == 0) await add(); 104 buffer += this._drawItem(items[i], i); 105 } 106 add(); 107 } 108 109 _drawItem(item, i, outline = false) { 110 const x = roundTo3Digits(this.timeToPosition(item.time)); 111 const y = (item.depth + 1) * kItemHeight; 112 let width = roundTo3Digits(item.duration * this._timeToPixel); 113 if (outline) { 114 return `<rect x=${x} y=${y} width=${width} height=${ 115 kItemHeight - 1} class=fs />`; 116 } 117 let color = this._legend.colorForType(item.type); 118 if (i % 2 == 1) { 119 color = CSSColor.darken(color, 20); 120 } 121 return `<rect x=${x} y=${y} width=${width} height=${kItemHeight - 1} fill=${ 122 color} class=f />`; 123 } 124 125 _drawItemText(item) { 126 const type = item.type; 127 const kHeight = 9; 128 const x = this.timeToPosition(item.time); 129 const y = item.depth * (kHeight + 1); 130 let width = item.duration * this._timeToPixel; 131 width -= width * 0.1; 132 133 let buffer = ''; 134 if (width < 15 || type == 'Other') return buffer; 135 const rawName = item.entry.getName(); 136 if (rawName.length == 0) return buffer; 137 const kChartWidth = 5; 138 const maxChars = Math.floor(width / kChartWidth) 139 const text = rawName.substr(0, maxChars); 140 buffer += `<text x=${x + 1} y=${y - 3} class=txt>${text}</text>` 141 return buffer; 142 } 143} 144 145function roundTo3Digits(value) { 146 return ((value * 1000) | 0) / 1000; 147} 148