[tools][system-analyzer] Add map-details and map-transitions
This CL encapsulates map panel behaviour with custom web components map-details for displaying information and map-transitions to show map transition tree. The web components emit/receive events to interact with the app. Bug: v8:10644 Change-Id: Ic61c2794ed7ab854ee61fa1e7c8fe9b1c3c31b7e Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2305889 Commit-Queue: Zeynep Cankara <zcankara@google.com> Reviewed-by: Camillo Bruni <cbruni@chromium.org> Reviewed-by: Sathya Gunasekaran <gsathya@chromium.org> Cr-Commit-Position: refs/heads/master@{#69004}
This commit is contained in:
parent
09de3e9cc9
commit
e607d54a0e
@ -22,9 +22,10 @@ function formatSeconds(millis) {
|
||||
return (millis * kMillis2Seconds).toFixed(2) + 's';
|
||||
}
|
||||
|
||||
function defineCustomElement(name, generator) {
|
||||
let htmlTemplatePath = name + '-template.html';
|
||||
fetch(htmlTemplatePath)
|
||||
function defineCustomElement(path, generator) {
|
||||
let name = path.substring(path.lastIndexOf("/") + 1, path.length);
|
||||
path = path + '-template.html';
|
||||
fetch(path)
|
||||
.then(stream => stream.text())
|
||||
.then(
|
||||
templateText => customElements.define(name, generator(templateText)));
|
||||
|
@ -29,7 +29,7 @@ body {
|
||||
background-color: var(--background-color);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
h2 {
|
||||
h2, h4 {
|
||||
background-color: var(--primary-color);
|
||||
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
|
||||
transition: 0.3s;
|
||||
|
@ -96,8 +96,7 @@ class App {
|
||||
}
|
||||
|
||||
handleShowMaps(e) {
|
||||
//document.state.view.transitionView.showMaps(e.detail);
|
||||
document.state.mapPanel.showMaps(e.detail);
|
||||
document.state.mapPanel.mapEntries = e.detail;
|
||||
}
|
||||
|
||||
handleSelectIc(e){
|
||||
|
@ -62,7 +62,7 @@ class State {
|
||||
set map(value) {
|
||||
this._map = value;
|
||||
this._navigation.updateUrl();
|
||||
this.mapPanel_.showMap(this.map);
|
||||
this.mapPanel_.map = this._map;
|
||||
this.view.redraw();
|
||||
}
|
||||
updateChunks() {
|
||||
@ -155,12 +155,17 @@ class Navigation {
|
||||
class View {
|
||||
constructor(state, mapPanelId, timelinePanelId) {
|
||||
this.mapPanel_ = $(mapPanelId);
|
||||
this.mapPanel_.addEventListener(
|
||||
'statemapchange', e => this.handleStateMapChange(e));
|
||||
this.mapPanel_.addEventListener(
|
||||
'selectmapdblclick', e => this.handleDblClickSelectMap(e));
|
||||
this.mapPanel_.addEventListener(
|
||||
'sourcepositionsclick', e => this.handleClickSourcePositions(e));
|
||||
|
||||
this.timelinePanel_ = $(timelinePanelId);
|
||||
this.state = state;
|
||||
setInterval(this.timelinePanel_.updateOverviewWindow(), 50);
|
||||
this.timelinePanel_.createBackgroundCanvas();
|
||||
this.mapPanel_.transitionView = state;
|
||||
|
||||
this.isLocked = false;
|
||||
this._filteredEntries = [];
|
||||
}
|
||||
@ -174,6 +179,24 @@ class View {
|
||||
return this.state.map
|
||||
}
|
||||
|
||||
handleClickSourcePositions(e){
|
||||
//TODO(zcankara) Handle source position
|
||||
console.log("source position map detail: ", e.detail);
|
||||
}
|
||||
|
||||
handleDblClickSelectMap(e){
|
||||
//TODO(zcankara) Handle double clicked map
|
||||
console.log("double clicked map: ", e.detail);
|
||||
}
|
||||
|
||||
handleStateMapChange(e){
|
||||
this.state.map = e.detail;
|
||||
}
|
||||
|
||||
handleIsLocked(e){
|
||||
this.state.view.isLocked = e.detail;
|
||||
}
|
||||
|
||||
updateTimeline() {
|
||||
let chunksNode = this.timelinePanel_.timelineChunks;
|
||||
removeAllChildren(chunksNode);
|
||||
@ -247,7 +270,8 @@ class View {
|
||||
this.isLocked = true;
|
||||
let chunk = event.target.chunk;
|
||||
if (!chunk) return;
|
||||
this.mapPanel_.showMaps(chunk.getUniqueTransitions());
|
||||
this.state.view.isLocked = true;
|
||||
this.mapPanel_.mapEntries = chunk.getUniqueTransitions();
|
||||
}
|
||||
|
||||
drawOverview() {
|
||||
|
@ -4,132 +4,6 @@ found in the LICENSE file. -->
|
||||
<style>
|
||||
@import "./index.css";
|
||||
|
||||
#mapDetails {
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
#transitionView {
|
||||
overflow-x: scroll;
|
||||
white-space: nowrap;
|
||||
min-height: 50px;
|
||||
max-height: 200px;
|
||||
padding: 50px 0 0 0;
|
||||
margin-top: -25px;
|
||||
width: 100%;
|
||||
}
|
||||
.map {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
background-color: var(--map-background-color);
|
||||
border: 4px solid var(--on-surface-color);
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
line-height: 18px;
|
||||
color: var(--on-surface-color);
|
||||
vertical-align: top;
|
||||
margin-top: -13px;
|
||||
/* raise z-index */
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.map.selected {
|
||||
border-color: var(--map-background-color);
|
||||
}
|
||||
|
||||
.transitions {
|
||||
display: inline-block;
|
||||
margin-left: -15px;
|
||||
}
|
||||
|
||||
.transition {
|
||||
min-height: 55px;
|
||||
margin: 0 0 -2px 2px;
|
||||
}
|
||||
|
||||
/* gray out deprecated transitions */
|
||||
.deprecated > .transitionEdge,
|
||||
.deprecated > .map {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.deprecated > .transition {
|
||||
border-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
/* Show a border for all but the first transition */
|
||||
.transition:nth-of-type(2),
|
||||
.transition:nth-last-of-type(n+2) {
|
||||
border-left: 2px solid;
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
/* special case for 2 transitions */
|
||||
.transition:nth-last-of-type(1) {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
/* topmost transitions are not related */
|
||||
#transitionView > .transition {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
/* topmost transition edge needs initial offset to be aligned */
|
||||
#transitionView > .transition > .transitionEdge {
|
||||
margin-left: 13px;
|
||||
}
|
||||
|
||||
.transitionEdge {
|
||||
height: 2px;
|
||||
width: 80px;
|
||||
display: inline-block;
|
||||
margin: 0 0 2px 0;
|
||||
background-color: var(--map-background-color);
|
||||
vertical-align: top;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.transitionLabel {
|
||||
color: var(--on-surface-color);
|
||||
transform: rotate(-15deg);
|
||||
transform-origin: top left;
|
||||
margin-top: -10px;
|
||||
font-size: 10px;
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
background-color: var(--surface-color);
|
||||
}
|
||||
|
||||
.showSubtransitions {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-top: 10px solid var(--map-background-color);
|
||||
cursor: zoom-in;
|
||||
margin: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
.showSubtransitions.opened {
|
||||
border-top: none;
|
||||
border-bottom: 10px solid var(--map-background-color);
|
||||
cursor: zoom-out;
|
||||
}
|
||||
|
||||
#tooltip {
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background-color: var(--red);
|
||||
pointer-events: none;
|
||||
z-index: 100;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#searchBarInput {
|
||||
width: 200px;
|
||||
color: black;
|
||||
@ -138,20 +12,12 @@ found in the LICENSE file. -->
|
||||
<stats-panel id="stats-panel" onchange="app.handleShowMaps(event)"></stats-panel>
|
||||
<div class="panel">
|
||||
<h2>Map Panel</h2>
|
||||
<h3>Transitions</h3>
|
||||
<section id="transitionView"></section>
|
||||
<br/>
|
||||
|
||||
<h4>Search Map by Address</h4>
|
||||
<map-transitions id="map-transitions"></map-transitions>
|
||||
<h3>Search Map by Address</h3>
|
||||
<section id="searchBar"></section>
|
||||
<input type="search" id="searchBarInput" placeholder="Search maps by address.."></input>
|
||||
<button id="searchBarBtn">Search</button>
|
||||
|
||||
<h4>Selected Map</h4>
|
||||
<section id="mapDetails"></section>
|
||||
|
||||
<div id="tooltip">
|
||||
<div id="tooltipContents"></div>
|
||||
</div>
|
||||
<map-details id="map-details"></map-details>
|
||||
</div>
|
||||
|
||||
|
@ -2,21 +2,40 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
import "./stats-panel.mjs";
|
||||
import "./map-panel/map-details.mjs";
|
||||
import "./map-panel/map-transitions.mjs";
|
||||
import {V8Map} from "./map-processor.mjs";
|
||||
import {defineCustomElement, V8CustomElement} from './helper.mjs';
|
||||
|
||||
defineCustomElement('map-panel', (templateText) =>
|
||||
class MapPanel extends V8CustomElement {
|
||||
#map;
|
||||
constructor() {
|
||||
super(templateText);
|
||||
this.transitionView.addEventListener(
|
||||
'mousemove', e => this.handleTransitionViewChange(e));
|
||||
this.$('#searchBarBtn').addEventListener(
|
||||
this.searchBarBtn.addEventListener(
|
||||
'click', e => this.handleSearchBar(e));
|
||||
this.addEventListener(
|
||||
'mapdetailsupdate', e => this.handleUpdateMapDetails(e));
|
||||
}
|
||||
|
||||
get transitionView() {
|
||||
return this.$('#transitionView');
|
||||
handleUpdateMapDetails(e){
|
||||
this.mapDetailsPanel.mapDetails = e.detail;
|
||||
}
|
||||
|
||||
get statsPanel() {
|
||||
return this.$('#stats-panel');
|
||||
}
|
||||
|
||||
get mapTransitionsPanel() {
|
||||
return this.$('#map-transitions');
|
||||
}
|
||||
|
||||
get mapDetailsPanel() {
|
||||
return this.$('#map-details');
|
||||
}
|
||||
|
||||
get searchBarBtn() {
|
||||
return this.$('#searchBarBtn');
|
||||
}
|
||||
|
||||
get searchBar() {
|
||||
@ -24,19 +43,7 @@ defineCustomElement('map-panel', (templateText) =>
|
||||
}
|
||||
|
||||
get mapDetails() {
|
||||
return this.$('#mapDetails');
|
||||
}
|
||||
|
||||
get tooltip() {
|
||||
return this.$('#tooltip');
|
||||
}
|
||||
|
||||
get tooltipContents() {
|
||||
return this.$('#tooltipContents');
|
||||
}
|
||||
|
||||
get statsPanel() {
|
||||
return this.$('#stats-panel');
|
||||
return this.mapDetailsPanel.mapDetails;
|
||||
}
|
||||
|
||||
// send a timeline to the stats-panel
|
||||
@ -49,14 +56,9 @@ defineCustomElement('map-panel', (templateText) =>
|
||||
this.statsPanel.update();
|
||||
}
|
||||
|
||||
|
||||
handleTransitionViewChange(e){
|
||||
this.tooltip.style.left = e.pageX + "px";
|
||||
this.tooltip.style.top = e.pageY + "px";
|
||||
let map = e.target.map;
|
||||
if (map) {
|
||||
this.tooltipContents.innerText = map.description;
|
||||
}
|
||||
set map(value) {
|
||||
this.#map = value;
|
||||
this.mapTransitionsPanel.map = this.#map;
|
||||
}
|
||||
|
||||
handleSearchBar(e){
|
||||
@ -77,174 +79,15 @@ defineCustomElement('map-panel', (templateText) =>
|
||||
'click', {bubbles: true, composed: true, detail: dataModel}));
|
||||
}
|
||||
|
||||
//TODO(zcankara) Take view, transitionView logic inside map panel
|
||||
//TODO(zcankara) VIEW RELATED
|
||||
updateStats(timeline) {
|
||||
this.timeline = timeline;
|
||||
}
|
||||
|
||||
updateMapDetails(map) {
|
||||
let details = '';
|
||||
if (map) {
|
||||
details += 'ID: ' + map.id;
|
||||
details += '\nSource location: ' + map.filePosition;
|
||||
details += '\n' + map.description;
|
||||
}
|
||||
this.mapDetails.innerText = details;
|
||||
set mapEntries(list){
|
||||
this.mapTransitionsPanel.mapEntries = list;
|
||||
}
|
||||
|
||||
//TODO(zcankara) DOM RELATED
|
||||
div(classes) {
|
||||
let node = document.createElement('div');
|
||||
if (classes !== void 0) {
|
||||
if (typeof classes === 'string') {
|
||||
node.classList.add(classes);
|
||||
} else {
|
||||
classes.forEach(cls => node.classList.add(cls));
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
removeAllChildren(node) {
|
||||
while (node.lastChild) {
|
||||
node.removeChild(node.lastChild);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO(zcankara) TRANSITIONVIEW RELATED
|
||||
set transitionView(state){
|
||||
this.state = state;
|
||||
this.container = this.transitionView;
|
||||
this.currentNode = this.transitionView;
|
||||
this.currentMap = undefined;
|
||||
}
|
||||
|
||||
selectMap(map) {
|
||||
this.currentMap = map;
|
||||
this.state.map = map;
|
||||
}
|
||||
|
||||
showMap(map) {
|
||||
this.updateMapDetails(map);
|
||||
if (this.currentMap === map) return;
|
||||
this.currentMap = map;
|
||||
this._showMaps([map]);
|
||||
}
|
||||
|
||||
showMaps(list) {
|
||||
this.state.view.isLocked = true;
|
||||
this._showMaps(list);
|
||||
}
|
||||
|
||||
_showMaps(list) {
|
||||
// Hide the container to avoid any layouts.
|
||||
this.container.style.display = 'none';
|
||||
this.removeAllChildren(this.container);
|
||||
list.forEach(map => this.addMapAndParentTransitions(map));
|
||||
this.container.style.display = ''
|
||||
}
|
||||
|
||||
addMapAndParentTransitions(map) {
|
||||
if (map === void 0) return;
|
||||
this.currentNode = this.container;
|
||||
let parents = map.getParents();
|
||||
if (parents.length > 0) {
|
||||
this.addTransitionTo(parents.pop());
|
||||
parents.reverse().forEach(each => this.addTransitionTo(each));
|
||||
}
|
||||
let mapNode = this.addSubtransitions(map);
|
||||
// Mark and show the selected map.
|
||||
mapNode.classList.add('selected');
|
||||
if (this.selectedMap == map) {
|
||||
setTimeout(
|
||||
() => mapNode.scrollIntoView(
|
||||
{behavior: 'smooth', block: 'nearest', inline: 'nearest'}),
|
||||
1);
|
||||
}
|
||||
}
|
||||
|
||||
addMapNode(map) {
|
||||
let node = this.div('map');
|
||||
if (map.edge) node.style.backgroundColor = map.edge.getColor();
|
||||
node.map = map;
|
||||
node.addEventListener('click', () => this.selectMap(map));
|
||||
if (map.children.length > 1) {
|
||||
node.innerText = map.children.length;
|
||||
let showSubtree = this.div('showSubtransitions');
|
||||
showSubtree.addEventListener('click', (e) => this.toggleSubtree(e, node));
|
||||
node.appendChild(showSubtree);
|
||||
} else if (map.children.length == 0) {
|
||||
node.innerHTML = '●'
|
||||
}
|
||||
this.currentNode.appendChild(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
addSubtransitions(map) {
|
||||
let mapNode = this.addTransitionTo(map);
|
||||
// Draw outgoing linear transition line.
|
||||
let current = map;
|
||||
while (current.children.length == 1) {
|
||||
current = current.children[0].to;
|
||||
this.addTransitionTo(current);
|
||||
}
|
||||
return mapNode;
|
||||
}
|
||||
|
||||
addTransitionEdge(map) {
|
||||
let classes = ['transitionEdge'];
|
||||
let edge = this.div(classes);
|
||||
edge.style.backgroundColor = map.edge.getColor();
|
||||
let labelNode = this.div('transitionLabel');
|
||||
labelNode.innerText = map.edge.toString();
|
||||
edge.appendChild(labelNode);
|
||||
return edge;
|
||||
}
|
||||
|
||||
addTransitionTo(map) {
|
||||
// transition[ transitions[ transition[...], transition[...], ...]];
|
||||
|
||||
let transition = this.div('transition');
|
||||
if (map.isDeprecated()) transition.classList.add('deprecated');
|
||||
if (map.edge) {
|
||||
transition.appendChild(this.addTransitionEdge(map));
|
||||
}
|
||||
let mapNode = this.addMapNode(map);
|
||||
transition.appendChild(mapNode);
|
||||
|
||||
let subtree = this.div('transitions');
|
||||
transition.appendChild(subtree);
|
||||
|
||||
this.currentNode.appendChild(transition);
|
||||
this.currentNode = subtree;
|
||||
|
||||
return mapNode;
|
||||
}
|
||||
|
||||
toggleSubtree(event, node) {
|
||||
let map = node.map;
|
||||
event.target.classList.toggle('opened');
|
||||
let transitionsNode = node.parentElement.querySelector('.transitions');
|
||||
let subtransitionNodes = transitionsNode.children;
|
||||
if (subtransitionNodes.length <= 1) {
|
||||
// Add subtransitions excepth the one that's already shown.
|
||||
let visibleTransitionMap = subtransitionNodes.length == 1 ?
|
||||
transitionsNode.querySelector('.map').map :
|
||||
void 0;
|
||||
map.children.forEach(edge => {
|
||||
if (edge.to != visibleTransitionMap) {
|
||||
this.currentNode = transitionsNode;
|
||||
this.addSubtransitions(edge.to);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// remove all but the first (currently selected) subtransition
|
||||
for (let i = subtransitionNodes.length - 1; i > 0; i--) {
|
||||
transitionsNode.removeChild(subtransitionNodes[i]);
|
||||
}
|
||||
}
|
||||
get mapEntries(){
|
||||
return this.mapTransitionsPanel.mapEntries;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
19
tools/system-analyzer/map-panel/map-details-template.html
Normal file
19
tools/system-analyzer/map-panel/map-details-template.html
Normal file
@ -0,0 +1,19 @@
|
||||
<!-- Copyright 2020 the V8 project authors. All rights reserved.
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file. -->
|
||||
<head>
|
||||
<link href="./index.css" rel="stylesheet">
|
||||
</head>
|
||||
<style>
|
||||
#mapDetails {
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
</style>
|
||||
<div id="container">
|
||||
<div class="panel">
|
||||
<h4>Map Details</h4>
|
||||
<section id="mapDetails"></section>
|
||||
</div>
|
||||
</div>
|
37
tools/system-analyzer/map-panel/map-details.mjs
Normal file
37
tools/system-analyzer/map-panel/map-details.mjs
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
import {V8CustomElement, defineCustomElement} from '../helper.mjs';
|
||||
|
||||
defineCustomElement('./map-panel/map-details', (templateText) =>
|
||||
class MapDetails extends V8CustomElement {
|
||||
constructor() {
|
||||
super(templateText);
|
||||
this.mapDetails.addEventListener('click', () => this.handleClickSourcePositions());
|
||||
this.selectedMap = undefined;
|
||||
}
|
||||
get mapDetails() {
|
||||
return this.$('#mapDetails');
|
||||
}
|
||||
|
||||
setSelectedMap(value) {
|
||||
this.selectedMap = value;
|
||||
}
|
||||
|
||||
set mapDetails(map){
|
||||
let details = '';
|
||||
if (map) {
|
||||
details += 'ID: ' + map.id;
|
||||
details += '\nSource location: ' + map.filePosition;
|
||||
details += '\n' + map.description;
|
||||
this.setSelectedMap(map);
|
||||
}
|
||||
this.mapDetails.innerText = details;
|
||||
}
|
||||
|
||||
handleClickSourcePositions(){
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
'sourcepositionsclick', {bubbles: true, composed: true, detail: this.selectedMap.filePosition}));
|
||||
}
|
||||
|
||||
});
|
142
tools/system-analyzer/map-panel/map-transitions-template.html
Normal file
142
tools/system-analyzer/map-panel/map-transitions-template.html
Normal file
@ -0,0 +1,142 @@
|
||||
<!-- Copyright 2020 the V8 project authors. All rights reserved.
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file. -->
|
||||
<head>
|
||||
<link href="./index.css" rel="stylesheet">
|
||||
</head>
|
||||
<style>
|
||||
|
||||
#transitionView {
|
||||
overflow-x: scroll;
|
||||
white-space: nowrap;
|
||||
min-height: 50px;
|
||||
max-height: 200px;
|
||||
padding: 50px 0 0 0;
|
||||
margin-top: -25px;
|
||||
width: 100%;
|
||||
}
|
||||
.map {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
background-color: var(--map-background-color);
|
||||
border: 4px solid var(--on-surface-color);
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
line-height: 18px;
|
||||
color: var(--on-surface-color);
|
||||
vertical-align: top;
|
||||
margin-top: -13px;
|
||||
/* raise z-index */
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.map.selected {
|
||||
border-color: var(--map-background-color);
|
||||
}
|
||||
|
||||
.transitions {
|
||||
display: inline-block;
|
||||
margin-left: -15px;
|
||||
}
|
||||
|
||||
.transition {
|
||||
min-height: 55px;
|
||||
margin: 0 0 -2px 2px;
|
||||
}
|
||||
|
||||
/* gray out deprecated transitions */
|
||||
.deprecated > .transitionEdge,
|
||||
.deprecated > .map {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.deprecated > .transition {
|
||||
border-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
/* Show a border for all but the first transition */
|
||||
.transition:nth-of-type(2),
|
||||
.transition:nth-last-of-type(n+2) {
|
||||
border-left: 2px solid;
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
/* special case for 2 transitions */
|
||||
.transition:nth-last-of-type(1) {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
/* topmost transitions are not related */
|
||||
#transitionView > .transition {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
/* topmost transition edge needs initial offset to be aligned */
|
||||
#transitionView > .transition > .transitionEdge {
|
||||
margin-left: 13px;
|
||||
}
|
||||
|
||||
.transitionEdge {
|
||||
height: 2px;
|
||||
width: 80px;
|
||||
display: inline-block;
|
||||
margin: 0 0 2px 0;
|
||||
background-color: var(--map-background-color);
|
||||
vertical-align: top;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.transitionLabel {
|
||||
color: var(--on-surface-color);
|
||||
transform: rotate(-15deg);
|
||||
transform-origin: top left;
|
||||
margin-top: -10px;
|
||||
font-size: 10px;
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
background-color: var(--surface-color);
|
||||
}
|
||||
|
||||
.showSubtransitions {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-top: 10px solid var(--map-background-color);
|
||||
cursor: zoom-in;
|
||||
margin: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
.showSubtransitions.opened {
|
||||
border-top: none;
|
||||
border-bottom: 10px solid var(--map-background-color);
|
||||
cursor: zoom-out;
|
||||
}
|
||||
|
||||
#tooltip {
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background-color: var(--red);
|
||||
pointer-events: none;
|
||||
z-index: 100;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#title {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
<div id="container">
|
||||
<div class="panel">
|
||||
<div id="title"><h4>Transitions</h4></div>
|
||||
<section id="transitionView"></section>
|
||||
<div id="tooltip">
|
||||
<div id="tooltipContents"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
185
tools/system-analyzer/map-panel/map-transitions.mjs
Normal file
185
tools/system-analyzer/map-panel/map-transitions.mjs
Normal file
@ -0,0 +1,185 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
import {V8CustomElement, defineCustomElement} from '../helper.mjs';
|
||||
|
||||
defineCustomElement('./map-panel/map-transitions', (templateText) =>
|
||||
class MapTransitions extends V8CustomElement {
|
||||
#map;
|
||||
constructor() {
|
||||
super(templateText);
|
||||
this.transitionView.addEventListener(
|
||||
'mousemove', e => this.handleTransitionViewChange(e));
|
||||
this.currentNode = this.transitionView;
|
||||
this.currentMap = undefined;
|
||||
}
|
||||
|
||||
get transitionView() {
|
||||
return this.$('#transitionView');
|
||||
}
|
||||
|
||||
get tooltip() {
|
||||
return this.$('#tooltip');
|
||||
}
|
||||
|
||||
get tooltipContents() {
|
||||
return this.$('#tooltipContents');
|
||||
}
|
||||
|
||||
set map(value) {
|
||||
this.#map = value;
|
||||
this.showMap();
|
||||
}
|
||||
|
||||
handleTransitionViewChange(e){
|
||||
this.tooltip.style.left = e.pageX + "px";
|
||||
this.tooltip.style.top = e.pageY + "px";
|
||||
let map = e.target.map;
|
||||
if (map) {
|
||||
this.tooltipContents.innerText = map.description;
|
||||
}
|
||||
}
|
||||
|
||||
selectMap(map) {
|
||||
this.currentMap = map;
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
'mapdetailsupdate', {bubbles: true, composed: true, detail: map}));
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
'statemapchange', {bubbles: true, composed: true, detail: map}));
|
||||
}
|
||||
|
||||
dblClickSelectMap(map) {
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
'selectmapdblclick', {bubbles: true, composed: true, detail: map}));
|
||||
}
|
||||
|
||||
showMap() {
|
||||
// Called when a map selected
|
||||
let selected = this.#map;
|
||||
if (this.currentMap === this.#map) return;
|
||||
this.currentMap = this.#map;
|
||||
this.mapEntries = [this.#map];
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
'mapdetailsupdate', {bubbles: true, composed: true, detail: selected}));
|
||||
}
|
||||
|
||||
showMaps() {
|
||||
// Timeline dbl click to show map transitions of selected maps
|
||||
this.transitionView.style.display = 'none';
|
||||
this.removeAllChildren(this.transitionView);
|
||||
this.mapEntries.forEach(map => this.addMapAndParentTransitions(map));
|
||||
this.transitionView.style.display = '';
|
||||
}
|
||||
|
||||
set mapEntries(list){
|
||||
this._mapEntries = list;
|
||||
this.showMaps();
|
||||
}
|
||||
|
||||
get mapEntries(){
|
||||
return this._mapEntries;
|
||||
}
|
||||
|
||||
addMapAndParentTransitions(map) {
|
||||
if (map === void 0) return;
|
||||
this.currentNode = this.transitionView;
|
||||
let parents = map.getParents();
|
||||
if (parents.length > 0) {
|
||||
this.addTransitionTo(parents.pop());
|
||||
parents.reverse().forEach(each => this.addTransitionTo(each));
|
||||
}
|
||||
let mapNode = this.addSubtransitions(map);
|
||||
// Mark and show the selected map.
|
||||
mapNode.classList.add('selected');
|
||||
if (this.selectedMap == map) {
|
||||
setTimeout(
|
||||
() => mapNode.scrollIntoView(
|
||||
{behavior: 'smooth', block: 'nearest', inline: 'nearest'}),
|
||||
1);
|
||||
}
|
||||
}
|
||||
|
||||
addMapNode(map) {
|
||||
let node = this.div('map');
|
||||
if (map.edge) node.style.backgroundColor = map.edge.getColor();
|
||||
node.map = map;
|
||||
node.addEventListener('click', () => this.selectMap(map));
|
||||
node.addEventListener('dblclick', () => this.dblClickSelectMap(map));
|
||||
if (map.children.length > 1) {
|
||||
node.innerText = map.children.length;
|
||||
let showSubtree = this.div('showSubtransitions');
|
||||
showSubtree.addEventListener('click', (e) => this.toggleSubtree(e, node));
|
||||
node.appendChild(showSubtree);
|
||||
} else if (map.children.length == 0) {
|
||||
node.innerHTML = '●'
|
||||
}
|
||||
this.currentNode.appendChild(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
addSubtransitions(map) {
|
||||
let mapNode = this.addTransitionTo(map);
|
||||
// Draw outgoing linear transition line.
|
||||
let current = map;
|
||||
while (current.children.length == 1) {
|
||||
current = current.children[0].to;
|
||||
this.addTransitionTo(current);
|
||||
}
|
||||
return mapNode;
|
||||
}
|
||||
|
||||
addTransitionEdge(map) {
|
||||
let classes = ['transitionEdge'];
|
||||
let edge = this.div(classes);
|
||||
edge.style.backgroundColor = map.edge.getColor();
|
||||
let labelNode = this.div('transitionLabel');
|
||||
labelNode.innerText = map.edge.toString();
|
||||
edge.appendChild(labelNode);
|
||||
return edge;
|
||||
}
|
||||
|
||||
addTransitionTo(map) {
|
||||
// transition[ transitions[ transition[...], transition[...], ...]];
|
||||
|
||||
let transition = this.div('transition');
|
||||
if (map.isDeprecated()) transition.classList.add('deprecated');
|
||||
if (map.edge) {
|
||||
transition.appendChild(this.addTransitionEdge(map));
|
||||
}
|
||||
let mapNode = this.addMapNode(map);
|
||||
transition.appendChild(mapNode);
|
||||
|
||||
let subtree = this.div('transitions');
|
||||
transition.appendChild(subtree);
|
||||
|
||||
this.currentNode.appendChild(transition);
|
||||
this.currentNode = subtree;
|
||||
|
||||
return mapNode;
|
||||
}
|
||||
|
||||
toggleSubtree(event, node) {
|
||||
let map = node.map;
|
||||
event.target.classList.toggle('opened');
|
||||
let transitionsNode = node.parentElement.querySelector('.transitions');
|
||||
let subtransitionNodes = transitionsNode.children;
|
||||
if (subtransitionNodes.length <= 1) {
|
||||
// Add subtransitions excepth the one that's already shown.
|
||||
let visibleTransitionMap = subtransitionNodes.length == 1 ?
|
||||
transitionsNode.querySelector('.map').map :
|
||||
void 0;
|
||||
map.children.forEach(edge => {
|
||||
if (edge.to != visibleTransitionMap) {
|
||||
this.currentNode = transitionsNode;
|
||||
this.addSubtransitions(edge.to);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// remove all but the first (currently selected) subtransition
|
||||
for (let i = subtransitionNodes.length - 1; i > 0; i--) {
|
||||
transitionsNode.removeChild(subtransitionNodes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
Loading…
Reference in New Issue
Block a user