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