Commit c84213ef authored by Camillo Bruni's avatar Camillo Bruni Committed by Commit Bot

[tools] System-analyzer improvements

- Fix landing page
- Introduce and use SelectRelatedEvent for centralising the logic of
  finding and showing related LogEntries. It also clears the selection
  of all list panels if there are no related entries.
- Add "select related" button to the script-panel to show events only
  from the currently selected script
- Add selection type tabs for the map-panel
- Fix transition colors for map-transitions view
- Introduce separate map-transition view for the currently selected Map

Bug: v8:10644
Change-Id: I4199a8332bab2518d98078712ed5ce9a8f1dc19e
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2599555
Commit-Queue: Camillo Bruni <cbruni@chromium.org>
Reviewed-by: 's avatarSathya Gunasekaran  <gsathya@chromium.org>
Cr-Commit-Position: refs/heads/master@{#71849}
parent d5651560
......@@ -132,12 +132,38 @@ dd {
font-weight: 400;
}
.panel > select{
width: calc(100% + 20px);
margin: 0 -10px 10px -10px;
}
.panel > .selection {
display: flex;
margin: 0 -10px 0 -10px;
}
.panel > .selection input {
display: none;
}
.panel > .selection label {
flex: 1;
padding: 5px;
cursor: pointer;
background-color: var( --surface-color);
font-weight: normal;
text-align: center;
}
.panel > .selection label ~ label {
border-left: 2px var(--border-color) solid;
}
.panel > .selection label:hover {
background-color: var(--primary-color);
}
.panel > .selection [type=radio]:checked + label {
background-color: var(--border-color);
}
button {
cursor: pointer;
}
......@@ -206,6 +232,8 @@ button:hover {
background-color: var(--primary-color);
color: var(--on-primary-color);
}
button:hover,
.clickable:hover,
.mark:hover,
.clickable:active,
......
......@@ -24,16 +24,12 @@ found in the LICENSE file. -->
<link rel="stylesheet" type="text/css" href="./index.css">
<style>
.theme-switch-wrapper {
display: inline-block;
align-items: center;
}
.theme-switch {
display: inline-block;
height: 16px;
position: relative;
width: 38px;
vertical-align: middle;
}
.theme-switch input {
......@@ -140,10 +136,10 @@ found in the LICENSE file. -->
<div class="panels">
<section id="settings" class="panel">
<h2>Settings</h2>
<span>Theme:</span>
<div class="theme-switch-wrapper">
<div class="panelBody">
<span>Theme:</span>
<label class="theme-switch" for="theme-switch-input">
<input type="checkbox" id="theme-switch-input" />
<input type="checkbox" id="theme-switch-input" >
<div class="slider"></div>
</label>
</div>
......@@ -151,94 +147,96 @@ found in the LICENSE file. -->
<section id="instructions" class="panel">
<h2>Instructions</h2>
<p>
Unified web interface to analyse runtime information stored in the v8 log.
</p>
For generating a v8.log file from <a href="https://v8.dev/docs/build">d8</a>:
<ul>
<li>
<code>/path/do/d8 $LOG_FLAGS $FILE</code>
</li>
</ul>
For generating a v8.log file from Chrome:
<ul>
<li>
<code>/path/to/chrome --user-data-dir=/var/tmp/chr$RANDOM --no-sandbox
--js-flags='$LOG_FLAGS’
$WEBSITE_URL</code>
</li>
</ul>
<h3><code>LOG_FLAGS</code>:</h3>
<dl class="d8-options">
<dt>
<a href="https://source.chromium.org/search?q=FLAG_log_all">
<code>--log-all</code>
</a>
</dt>
<dd>Enable all V8 logging options.</dd>
<dt>
<a href="https://source.chromium.org/search?q=FLAG_trace_maps">
<code>--trace-maps</code>
</a>
</dt>
<dd>
Log<a href="https://v8.dev/blog/fast-properties">Maps</a>
</dd>
<dt>
<a href="https://source.chromium.org/search?q=FLAG_trace_ic">
<code>--trace-ic</code>
</a>
</dt>
<dd>
Log <a href="https://mathiasbynens.be/notes/shapes-ics">ICs</a>
</dd>
<dt>
<a href="https://source.chromium.org/search?q=FLAG_log_source_code">
<code>--log-source-code</code>
</a>
</dt>
<dd>Log source code</dd>
<dt>
<a href="https://source.chromium.org/search?q=FLAG_log_code_disassemble">
<code>--log-code-disassemble</code>
</a>
</dt>
<dd>Log detailed generated generated code</dd>
<dt>
<a href="https://source.chromium.org/search?q=FLAG_log_api">
<code>--log-api</code>
</a>
</dt>
<dd>Log various API uses.</dd>
</dl>
<h3>Keyboard Shortcuts for Navigation</h3>
<dl>
<dt><kbd>SHIFT</kbd> + <kbd>Arrow Up</kbd></dt>
<dd>Follow Map transition forward (first child)</dd>
<dt><kbd>SHIFT</kbd> + <kbd>Arrow Down</kbd></dt>
<dd>Follow Map transition backwards</dd>
<dt><kbd>Arrow Up</kbd></dt>
<dd>Go to previous Map chunk</dd>
<dt><kbd>Arrow Down</kbd></dt>
<dd>Go to next Map in chunk</dd>
<dt><kbd>Arrow Left</kbd></dt>
<dd>Go to previous chunk</dd>
<dt><kbd>Arrow Right</kbd></dt>
<dd>Go to next chunk</dd>
<dt><kbd>+</kbd></dt>
<dd>Timeline zoom in</dd>
<dt><kbd>-</kbd></dt>
<dd>Timeline zoom out</dd>
</dl>
<div class="panelBody">
<p>
Unified web interface to analyse runtime information stored in the v8 log.
</p>
For generating a v8.log file from <a href="https://v8.dev/docs/build">d8</a>:
<ul>
<li>
<code>/path/do/d8 $LOG_FLAGS $FILE</code>
</li>
</ul>
For generating a v8.log file from Chrome:
<ul>
<li>
<code>/path/to/chrome --user-data-dir=/var/tmp/chr$RANDOM --no-sandbox
--js-flags='$LOG_FLAGS’
$WEBSITE_URL</code>
</li>
</ul>
<h3><code>LOG_FLAGS</code>:</h3>
<dl class="d8-options">
<dt>
<a href="https://source.chromium.org/search?q=FLAG_log_all">
<code>--log-all</code>
</a>
</dt>
<dd>Enable all V8 logging options.</dd>
<dt>
<a href="https://source.chromium.org/search?q=FLAG_trace_maps">
<code>--trace-maps</code>
</a>
</dt>
<dd>
Log<a href="https://v8.dev/blog/fast-properties">Maps</a>
</dd>
<dt>
<a href="https://source.chromium.org/search?q=FLAG_trace_ic">
<code>--trace-ic</code>
</a>
</dt>
<dd>
Log <a href="https://mathiasbynens.be/notes/shapes-ics">ICs</a>
</dd>
<dt>
<a href="https://source.chromium.org/search?q=FLAG_log_source_code">
<code>--log-source-code</code>
</a>
</dt>
<dd>Log source code</dd>
<dt>
<a href="https://source.chromium.org/search?q=FLAG_log_code_disassemble">
<code>--log-code-disassemble</code>
</a>
</dt>
<dd>Log detailed generated generated code</dd>
<dt>
<a href="https://source.chromium.org/search?q=FLAG_log_api">
<code>--log-api</code>
</a>
</dt>
<dd>Log various API uses.</dd>
</dl>
<h3>Keyboard Shortcuts for Navigation</h3>
<dl>
<dt><kbd>SHIFT</kbd> + <kbd>Arrow Up</kbd></dt>
<dd>Follow Map transition forward (first child)</dd>
<dt><kbd>SHIFT</kbd> + <kbd>Arrow Down</kbd></dt>
<dd>Follow Map transition backwards</dd>
<dt><kbd>Arrow Up</kbd></dt>
<dd>Go to previous Map chunk</dd>
<dt><kbd>Arrow Down</kbd></dt>
<dd>Go to next Map in chunk</dd>
<dt><kbd>Arrow Left</kbd></dt>
<dd>Go to previous chunk</dd>
<dt><kbd>Arrow Right</kbd></dt>
<dd>Go to next chunk</dd>
<dt><kbd>+</kbd></dt>
<dd>Timeline zoom in</dd>
<dt><kbd>-</kbd></dt>
<dd>Timeline zoom out</dd>
</dl>
</div>
</section>
</div>
</body>
......
......@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {SourcePosition} from '../profile.mjs';
import {Script, SourcePosition} from '../profile.mjs';
import {State} from './app-model.mjs';
import {ApiLogEntry} from './log/api.mjs';
......@@ -11,7 +11,7 @@ import {CodeLogEntry} from './log/code.mjs';
import {IcLogEntry} from './log/ic.mjs';
import {MapLogEntry} from './log/map.mjs';
import {Processor} from './processor.mjs';
import {FocusEvent, SelectionEvent, SelectTimeEvent, ToolTipEvent,} from './view/events.mjs';
import {FocusEvent, SelectionEvent, SelectRelatedEvent, SelectTimeEvent, ToolTipEvent,} from './view/events.mjs';
import {$, CSSColor, groupBy} from './view/helper.mjs';
class App {
......@@ -64,7 +64,9 @@ class App {
document.addEventListener(
'keydown', e => this._navigation?.handleKeyDown(e));
document.addEventListener(
SelectionEvent.name, e => this.handleShowEntries(e));
SelectRelatedEvent.name, e => this.handleSelectRelatedEntries(e));
document.addEventListener(
SelectionEvent.name, e => this.handleSelectEntries(e))
document.addEventListener(
FocusEvent.name, e => this.handleFocusLogEntryl(e));
document.addEventListener(
......@@ -72,18 +74,63 @@ class App {
document.addEventListener(ToolTipEvent.name, e => this.handleToolTip(e));
}
handleShowEntries(e) {
e.stopPropagation();
handleSelectRelatedEntries(e) {
e.stopImmediatePropagation();
this.selectRelatedEntries(e.entry);
}
selectRelatedEntries(entry) {
let entries = [entry];
switch (entry.constructor) {
case SourcePosition:
entries = entries.concat(entry.entries);
break;
case MapLogEntry:
entries = this._state.icTimeline.filter(each => each.map === entry);
break;
case IcLogEntry:
if (entry.map) entries.push(entry.map);
break;
case ApiLogEntry:
break;
case CodeLogEntry:
break;
case DeoptLogEntry:
// TODO select map + code entries
if (entry.fileSourcePosition) entries.push(entry.fileSourcePosition);
break;
case Script:
entries = entry.entries.concat(entry.sourcePositions);
break;
default:
throw new Error('Unknown selection type!');
}
if (entry.sourcePosition) {
entries.push(entry.sourcePosition);
// TODO: find the matching Code log entries.
}
this.selectEntries(entries);
}
handleSelectEntries(e) {
e.stopImmediatePropagation();
this.showEntries(e.entries);
}
showEntries(entries) {
groupBy(entries, each => each.constructor, true)
.forEach(group => this.showEntriesOfSingleType(group.entries));
selectEntries(entries) {
const missingTypes = new Set([
SourcePosition, MapLogEntry, IcLogEntry, ApiLogEntry, CodeLogEntry,
DeoptLogEntry
]);
groupBy(entries, each => each.constructor, true).forEach(group => {
this.selectEntriesOfSingleType(group.entries);
missingTypes.delete(group.key);
});
missingTypes.forEach(type => this.selectEntriesOfSingleType([], type));
}
showEntriesOfSingleType(entries) {
switch (entries[0].constructor) {
selectEntriesOfSingleType(entries, type) {
switch (entries[0]?.constructor ?? type) {
case SourcePosition:
return this.showSourcePositions(entries);
case MapLogEntry:
......@@ -133,8 +180,8 @@ class App {
}
handleTimeRangeSelect(e) {
e.stopImmediatePropagation();
this.selectTimeRange(e.start, e.end);
e.stopPropagation();
}
selectTimeRange(start, end) {
......@@ -148,7 +195,7 @@ class App {
}
handleFocusLogEntryl(e) {
e.stopPropagation();
e.stopImmediatePropagation();
this.focusLogEntry(e.entry);
}
......@@ -195,9 +242,9 @@ class App {
this._view.apiTrack.focusedEntry = entry;
}
focusSourcePosition(sourcePositions) {
if (!sourcePositions.script) return;
this._view.sourcePanel.focusedSourcePositions = [sourcePositions];
focusSourcePosition(sourcePosition) {
if (!sourcePosition) return;
this._view.sourcePanel.focusedSourcePositions = [sourcePosition];
}
handleToolTip(event) {
......@@ -233,7 +280,7 @@ class App {
this._view.deoptList.timeline = deoptTimeline;
this._view.codeList.timeline = codeTimeline;
this._view.apiList.timeline = apiTimeline;
this._view.sourcePanel.data = processor.scripts;
this._view.sourcePanel.scripts = processor.scripts;
this._view.codePanel.timeline = codeTimeline;
this.refreshTimelineTrackView();
} catch (e) {
......
......@@ -2,13 +2,20 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
export class SelectionEvent extends CustomEvent {
class AppEvent extends CustomEvent {
constructor(name) {
super(name, {bubbles: true, composed: true});
}
}
export class SelectionEvent extends AppEvent {
// TODO: turn into static class fields once Safari supports it.
static get name() {
return 'showentries';
return 'select';
}
constructor(entries) {
super(SelectionEvent.name, {bubbles: true, composed: true});
super(SelectionEvent.name);
if (!Array.isArray(entries) || entries.length == 0) {
throw new Error('No valid entries selected!');
}
......@@ -16,45 +23,59 @@ export class SelectionEvent extends CustomEvent {
}
}
export class FocusEvent extends CustomEvent {
export class SelectRelatedEvent extends AppEvent {
static get name() {
return 'selectrelated';
}
constructor(entry) {
super(SelectRelatedEvent.name);
this.entry = entry;
}
}
export class FocusEvent extends AppEvent {
static get name() {
return 'showentrydetail';
}
constructor(entry) {
super(FocusEvent.name, {bubbles: true, composed: true});
super(FocusEvent.name);
this.entry = entry;
}
}
export class SelectTimeEvent extends CustomEvent {
export class SelectTimeEvent extends AppEvent {
static get name() {
return 'timerangeselect';
}
constructor(start = 0, end = Infinity) {
super(SelectTimeEvent.name, {bubbles: true, composed: true});
super(SelectTimeEvent.name);
this.start = start;
this.end = end;
}
}
export class SynchronizeSelectionEvent extends CustomEvent {
export class SynchronizeSelectionEvent extends AppEvent {
static get name() {
return 'syncselection';
}
constructor(start, end) {
super(SynchronizeSelectionEvent.name, {bubbles: true, composed: true});
super(SynchronizeSelectionEvent.name);
this.start = start;
this.end = end;
}
}
export class ToolTipEvent extends CustomEvent {
export class ToolTipEvent extends AppEvent {
static get name() {
return 'showtooltip';
}
constructor(content, positionOrTargetNode) {
super(ToolTipEvent.name, {bubbles: true, composed: true});
super(ToolTipEvent.name);
this._content = content;
if (!positionOrTargetNode && !node) {
throw Error('Either provide a valid position or targetNode');
......
......@@ -52,33 +52,6 @@ found in the LICENSE file. -->
overflow-y: scroll;
}
.selection {
display: flex;
margin: 0 -10px 0 -10px;
}
.selection input {
display: none;
}
.selection label {
flex: 1;
padding: 5px;
cursor: pointer;
background-color: var( --surface-color);
font-weight: normal;
text-align: center;
}
.selection label ~ label {
border-left: 2px var(--border-color) solid;
}
.selection label:hover {
background-color: var(--primary-color);
}
.selection [type=radio]:checked + label {
background-color: var(--border-color);
}
.legend {
top: 40px;
}
......
......@@ -2,18 +2,18 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {SourcePosition} from '../../profile.mjs';
import {Script, SourcePosition} from '../../profile.mjs';
import {LogEntry} from '../log/log.mjs';
import {FocusEvent, SelectionEvent, SelectTimeEvent} from './events.mjs';
import {FocusEvent} from './events.mjs';
import {groupBy, LazyTable} from './helper.mjs';
import {DOM, V8CustomElement} from './helper.mjs';
DOM.defineCustomElement('view/list-panel',
(templateText) =>
class ListPanel extends V8CustomElement {
_selectedLogEntries;
_displayedLogEntries;
_selectedLogEntries = [];
_displayedLogEntries = [];
_timeline;
_detailsClickHandler = this._handleDetailsClick.bind(this);
......@@ -22,9 +22,11 @@ DOM.defineCustomElement('view/list-panel',
constructor() {
super(templateText);
this.groupKey.addEventListener('change', e => this.update());
this.showAllRadio.onclick = _ => this._show(this._timeline);
this.showTimerangeRadio.onclick = _ => this._show(this._timeline.selection);
this.showSelectionRadio.onclick = _ => this._show(this._selectedLogEntries);
this.showAllRadio.onclick = _ => this._showEntries(this._timeline);
this.showTimerangeRadio.onclick = _ =>
this._showEntries(this._timeline.selectionOrSelf);
this.showSelectionRadio.onclick = _ =>
this._showEntries(this._selectedLogEntries);
}
static get observedAttributes() {
......@@ -45,10 +47,10 @@ DOM.defineCustomElement('view/list-panel',
}
set selectedLogEntries(entries) {
if (entries === this._timeline.selection) {
this.showTimerangeRadio.click();
} else if (entries == this._timeline) {
if (entries === this._timeline) {
this.showAllRadio.click();
} else if (entries === this._timeline.selection) {
this.showTimerangeRadio.click();
} else {
this._selectedLogEntries = entries;
this.showSelectionRadio.click();
......@@ -91,7 +93,7 @@ DOM.defineCustomElement('view/list-panel',
}
}
_show(entries) {
_showEntries(entries) {
this._displayedLogEntries = entries;
this.update();
}
......@@ -189,6 +191,8 @@ DOM.defineCustomElement('view/list-panel',
_isClickable(object) {
if (typeof object !== 'object') return false;
if (object instanceof LogEntry) return true;
return object instanceof SourcePosition;
if (object instanceof SourcePosition) return true;
if (object instanceof Script) return true;
return false;
}
});
......@@ -11,11 +11,26 @@ found in the LICENSE file. -->
}
</style>
<div class="panel">
<input type="checkbox" id="closer" class="panelCloserInput">
<label class="panelCloserLabel" for="closer"></label>
<h2>Map Panel</h2>
<div class="selection">
<input type="radio" id="show-all" name="selectionType" value="all">
<label for="show-all">All</label>
<input type="radio" id="show-timerange" name="selectionType" value="timerange">
<label for="show-timerange">Time Range</label>
<input type="radio" id="show-selection" name="selectionType" value="selection">
<label for="show-selection">Last Selection</label>
</div>
<h3>All Transitions</h3>
<map-transitions id="map-transitions"></map-transitions>
<h3>Search Map by Address</h3>
<section id="searchBar"></section>
<input type="search" id="searchBarInput"></input>
<button id="searchBarBtn">Search</button>
<h3>Details Transitions</h3>
<map-transitions id="map-details-transitions"></map-transitions>
<h3>Details</h3>
<map-details id="map-details"></map-details>
</div>
......@@ -9,69 +9,98 @@ import {MapLogEntry} from '../log/map.mjs';
import {FocusEvent} from './events.mjs';
import {DOM, V8CustomElement} from './helper.mjs';
DOM.defineCustomElement('view/map-panel',
(templateText) =>
class MapPanel extends V8CustomElement {
_map;
constructor() {
super(templateText);
this.searchBarBtn.addEventListener('click', e => this.handleSearchBar(e));
this.addEventListener(FocusEvent.name, e => this.handleUpdateMapDetails(e));
}
handleUpdateMapDetails(e) {
if (e.entry instanceof MapLogEntry) {
this.mapDetailsPanel.map = e.entry;
}
}
get mapTransitionsPanel() {
return this.$('#map-transitions');
}
get mapDetailsPanel() {
return this.$('#map-details');
}
get searchBarBtn() {
return this.$('#searchBarBtn');
}
get searchBar() {
return this.$('#searchBar');
}
set timeline(timeline) {
this._timeline = timeline;
this.$('.panel').style.display = timeline.isEmpty() ? 'none' : 'inherit';
this.mapTransitionsPanel.timeline = timeline;
}
set map(map) {
this._map = map;
this.mapTransitionsPanel.map = map;
this.mapDetailsPanel.map = map;
}
handleSearchBar(e) {
let searchBar = this.$('#searchBarInput');
let searchBarInput = searchBar.value;
// access the map from model cache
let selectedMap = MapLogEntry.get(searchBarInput);
if (selectedMap) {
searchBar.className = 'success';
this.dispatchEvent(new FocusEvent(selectedMap));
} else {
searchBar.className = 'failure';
}
}
set selectedLogEntries(list) {
this.mapTransitionsPanel.selectedLogEntries = list;
if (list.length === 1) this.mapDetailsPanel.map = list.first();
}
get selectedLogEntries() {
return this.mapTransitionsPanel.selectedLogEntries;
}
});
DOM.defineCustomElement(
'view/map-panel', (templateText) => class MapPanel extends V8CustomElement {
_map;
_timeline;
_selectedLogEntries = [];
_displayedLogEntries = [];
constructor() {
super(templateText);
this.searchBarBtn.addEventListener('click', e => this._handleSearch(e));
this.showAllRadio.onclick = _ => this._showEntries(this._timeline);
this.showTimerangeRadio.onclick = _ =>
this._showEntries(this._timeline.selectionOrSelf);
this.showSelectionRadio.onclick = _ =>
this._showEntries(this._selectedLogEntries);
}
get showAllRadio() {
return this.$('#show-all');
}
get showTimerangeRadio() {
return this.$('#show-timerange');
}
get showSelectionRadio() {
return this.$('#show-selection');
}
get mapTransitionsPanel() {
return this.$('#map-transitions');
}
get mapDetailsTransitionsPanel() {
return this.$('#map-details-transitions');
}
get mapDetailsPanel() {
return this.$('#map-details');
}
get searchBarBtn() {
return this.$('#searchBarBtn');
}
get searchBar() {
return this.$('#searchBar');
}
set timeline(timeline) {
console.assert(timeline !== undefined, 'timeline undefined!');
this._timeline = timeline;
this.$('.panel').style.display =
timeline.isEmpty() ? 'none' : 'inherit';
this.mapTransitionsPanel.timeline = timeline;
this.mapDetailsTransitionsPanel.timeline = timeline;
}
set selectedLogEntries(entries) {
if (entries === this._timeline.selection) {
this.showTimerangeRadio.click();
} else if (entries == this._timeline) {
this.showAllRadio.click();
} else {
this._selectedLogEntries = entries;
this.showSelectionRadio.click();
}
}
set map(map) {
this._map = map;
this.mapDetailsTransitionsPanel.selectedLogEntries = [map];
this.mapDetailsPanel.map = map;
}
_showEntries(entries) {
this._displayedLogEntries = entries;
this.mapTransitionsPanel.selectedLogEntries = entries;
}
update() {
// nothing to do
}
_handleSearch(e) {
let searchBar = this.$('#searchBarInput');
let searchBarInput = searchBar.value;
// access the map from model cache
let selectedMap = MapLogEntry.get(searchBarInput);
if (selectedMap) {
searchBar.className = 'success';
this.dispatchEvent(new FocusEvent(selectedMap));
} else {
searchBar.className = 'failure';
}
}
});
......@@ -8,16 +8,12 @@ found in the LICENSE file. -->
<style>
#mapDetails,
#filePositionNode {
overflow-x: scroll;
overflow: scroll;
}
#mapDetails::-webkit-scrollbar {
width: 0;
background-color: transparent;
#mapDetails {
font-family: monospace;
white-space: pre;
}
</style>
<div class="panel">
<h4>Map Details</h4>
<section id="filePositionNode"></section>
<section id="mapDetails"></section>
</div>
<section id="filePositionNode"></section>
<section id="mapDetails"></section>
......@@ -137,12 +137,4 @@ found in the LICENSE file. -->
padding-bottom: 10px;
}
</style>
<div class="panel">
<div id="title">
<h4>Transitions</h4>
</div>
<section id="transitionView"></section>
<div id="tooltip">
<div id="tooltipContents"></div>
</div>
</div>
<section id="transitionView"></section>
// 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 {FocusEvent, ToolTipEvent} from '../events.mjs';
import {FocusEvent, SelectRelatedEvent, ToolTipEvent} from '../events.mjs';
import {CSSColor} from '../helper.mjs';
import {DOM, V8CustomElement} from '../helper.mjs';
......@@ -14,13 +14,12 @@ DOM.defineCustomElement(
_selectedLogEntries;
_displayedMapsInTree;
_toggleSubtreeHandler = this._handleToggleSubtree.bind(this);
_selectMapHandler = this._handleSelectMap.bind(this);
_mapClickHandler = this._handleMapClick.bind(this);
_mapDoubleClickHandler = this._handleMapDoubleClick.bind(this);
_mouseoverMapHandler = this._handleMouseoverMap.bind(this);
constructor() {
super(templateText);
this.transitionView.addEventListener(
'mousemove', (e) => this._handleTransitionViewChange(e));
this.currentNode = this.transitionView;
}
......@@ -28,24 +27,11 @@ DOM.defineCustomElement(
return this.$('#transitionView');
}
get tooltip() {
return this.$('#tooltip');
}
get tooltipContents() {
return this.$('#tooltipContents');
}
set map(map) {
this._map = map;
this._showMap();
}
set timeline(timeline) {
this._timeline = timeline;
this._edgeToColor.clear();
timeline.getBreakdown().forEach(breakdown => {
this._edgeToColor.set(breakdown.type, CSSColor.at(breakdown.id));
this._edgeToColor.set(breakdown.key, CSSColor.at(breakdown.id));
});
}
......@@ -54,33 +40,13 @@ DOM.defineCustomElement(
this.update();
}
get selectedLogEntries() {
return this._selectedLogEntries;
}
_handleTransitionViewChange(e) {
this.tooltip.style.left = e.pageX + 'px';
this.tooltip.style.top = e.pageY + 'px';
const map = e.target.map;
if (map) {
this.tooltipContents.innerText = map.description;
}
}
_selectMap(map) {
this.dispatchEvent(new FocusEvent(map));
}
_showMap() {
// TODO: highlight current map
}
_update() {
this.transitionView.style.display = 'none';
DOM.removeAllChildren(this.transitionView);
if (this._selectedLogEntries.length == 0) return;
this._displayedMapsInTree = new Set();
// Limit view to 200 maps for performance reasons.
this.selectedLogEntries.slice(0, 200).forEach(
this._selectedLogEntries.slice(0, 200).forEach(
(map) => this._addMapAndParentTransitions(map));
this._displayedMapsInTree = undefined;
this.transitionView.style.display = '';
......@@ -156,7 +122,8 @@ DOM.defineCustomElement(
if (map.edge)
node.style.backgroundColor = this._edgeToColor.get(map.edge.type);
node.map = map;
node.onclick = this._selectMapHandler
node.onclick = this._mapClickHandler
node.ondblclick = this._mapDoubleClickHandler
node.onmouseover = this._mouseoverMapHandler
if (map.children.length > 1) {
node.innerText = map.children.length;
......@@ -171,8 +138,13 @@ DOM.defineCustomElement(
return node;
}
_handleSelectMap(event) {
this._selectMap(event.currentTarget.map)
_handleMapClick(event) {
const map = event.currentTarget.map;
this.dispatchEvent(new FocusEvent(map));
}
_handleMapDoubleClick(event) {
this.dispatchEvent(new SelectRelatedEvent(event.currentTarget.map));
}
_handleMouseoverMap(event) {
......
......@@ -59,10 +59,17 @@ found in the LICENSE file. -->
box-shadow: 0px 0px 0px 0px var(--secondary-color);
}
}
.selection select {
flex: 1;
width:50%;
}
</style>
<div class="panel">
<h2>Source Panel</h2>
<select id="script-dropdown"></select>
<div class="selection">
<select id="script-dropdown"></select>
<button id="selectedRelatedButton">Select Related Events</button>
</div>
<div id="script" class="panelBody">
<div class="scriptNode"></div>
</div>
......
......@@ -2,17 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {groupBy} from '../helper.mjs';
import {IcLogEntry} from '../log/ic.mjs';
import {MapLogEntry} from '../log/map.mjs';
import {FocusEvent, SelectionEvent, ToolTipEvent} from './events.mjs';
import {SelectRelatedEvent, ToolTipEvent} from './events.mjs';
import {delay, DOM, formatBytes, V8CustomElement} from './helper.mjs';
DOM.defineCustomElement('view/source-panel',
(templateText) =>
class SourcePanel extends V8CustomElement {
_selectedSourcePositions = [];
_sourcePositionsToMarkNodes;
_sourcePositionsToMarkNodes = [];
_scripts = [];
_script;
......@@ -20,6 +18,11 @@ DOM.defineCustomElement('view/source-panel',
super(templateText);
this.scriptDropdown.addEventListener(
'change', e => this._handleSelectScript(e));
this.$('#selectedRelatedButton').onclick = (e) => {
if (this._script) {
this.dispatchEvent(new SelectRelatedEvent(this._script));
}
}
}
get script() {
......@@ -44,7 +47,11 @@ DOM.defineCustomElement('view/source-panel',
this._focusSelectedMarkers();
}
set data(scripts) {
set focusedSourcePositions(sourcePositions) {
this.selectedSourcePositions = sourcePositions;
}
set scripts(scripts) {
this._scripts = scripts;
this._initializeScriptDropdown();
}
......@@ -74,13 +81,13 @@ DOM.defineCustomElement('view/source-panel',
let scriptNode;
if (this._script) {
await delay(1);
const builder =
new LineBuilder(this, this._script, this._selectedSourcePositions);
const builder = new LineBuilder(this, this._script);
scriptNode = builder.createScriptNode();
this._sourcePositionsToMarkNodes = builder.sourcePositionToMarkers;
} else {
scriptNode = DOM.div();
this._selectedMarkNodes = undefined;
this._sourcePositionsToMarkNodes = new Map();
}
const oldScriptNode = this.script.childNodes[1];
this.script.replaceChild(scriptNode, oldScriptNode);
......@@ -93,9 +100,15 @@ DOM.defineCustomElement('view/source-panel',
markNode.className = '';
}
for (let sourcePosition of this._selectedSourcePositions) {
if (sourcePosition.script !== this._script) continue;
this._sourcePositionsToMarkNodes.get(sourcePosition).className = 'marked';
}
const sourcePosition = this._selectedSourcePositions[0];
this._scrollToFirstSourcePosition()
}
_scrollToFirstSourcePosition() {
const sourcePosition = this._selectedSourcePositions.find(
each => each.script === this._script);
if (!sourcePosition) return;
const markNode = this._sourcePositionsToMarkNodes.get(sourcePosition);
markNode.scrollIntoView(
......@@ -106,13 +119,11 @@ DOM.defineCustomElement('view/source-panel',
const option =
this.scriptDropdown.options[this.scriptDropdown.selectedIndex];
this.script = option.script;
this.selectLogEntries(this._script.entries);
}
handleSourcePositionClick(e) {
const sourcePosition = e.target.sourcePosition;
this.selectLogEntries(sourcePosition.entries);
this.dispatchEvent(new SelectionEvent([sourcePosition]));
this.dispatchEvent(new SelectRelatedEvent(sourcePosition));
}
handleSourcePositionMouseOver(e) {
......@@ -130,10 +141,6 @@ DOM.defineCustomElement('view/source-panel',
.join('\n');
this.dispatchEvent(new ToolTipEvent(text, e.target));
}
selectLogEntries(logEntries) {
this.dispatchEvent(new SelectionEvent(logEntries));
}
});
class SourcePositionIterator {
......@@ -188,12 +195,10 @@ class LineBuilder {
_clickHandler;
_mouseoverHandler;
_sourcePositions;
_selection;
_sourcePositionToMarkers = new Map();
constructor(panel, script, highlightPositions) {
constructor(panel, script) {
this._script = script;
this._selection = new Set(highlightPositions);
this._clickHandler = panel.handleSourcePositionClick.bind(panel);
this._mouseoverHandler = panel.handleSourcePositionMouseOver.bind(panel);
// TODO: sort on script finalization.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment