Commit 5e29241d authored by Camillo Bruni's avatar Camillo Bruni Committed by V8 LUCI CQ

[tools][system-analyzer] Postpone updating collapsed panels

- Add CollapsableElement helper
- Collapse all panels by default
- Only update panels if they are opened
- Only update CodePanel Select element if the data has changed
- Fix focusing SourcePosition selection

Bug: v8:10644
Change-Id: Id9b5159e86623c7ca0f437dd9db6b0faff3ec573
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2859952
Auto-Submit: Camillo Bruni <cbruni@chromium.org>
Reviewed-by: 's avatarPatrick Thier <pthier@chromium.org>
Commit-Queue: Patrick Thier <pthier@chromium.org>
Cr-Commit-Position: refs/heads/master@{#74329}
parent c42a0c95
......@@ -11,7 +11,9 @@ found in the LICENSE file. -->
}
</style>
<div class="panel">
<h2>Code Panel</h2>
<input type="checkbox" id="closer" class="panelCloserInput" checked>
<label class="panelCloserLabel" for="closer"></label>
<h2 class="title">Code Panel</h2>
<div class="selection">
<select id="codeSelect"></select>
<button id="selectedRelatedButton">Select Related Events</button>
......
......@@ -5,11 +5,11 @@ import {IcLogEntry} from '../log/ic.mjs';
import {MapLogEntry} from '../log/map.mjs';
import {FocusEvent, SelectionEvent, ToolTipEvent} from './events.mjs';
import {delay, DOM, formatBytes, formatMicroSeconds, V8CustomElement} from './helper.mjs';
import {CollapsableElement, delay, DOM, formatBytes, formatMicroSeconds} from './helper.mjs';
DOM.defineCustomElement('view/code-panel',
(templateText) =>
class CodePanel extends V8CustomElement {
class CodePanel extends CollapsableElement {
_timeline;
_selectedEntries;
_entry;
......@@ -24,19 +24,17 @@ DOM.defineCustomElement('view/code-panel',
set timeline(timeline) {
this._timeline = timeline;
this.$('.panel').style.display = timeline.isEmpty() ? 'none' : 'inherit';
this.update();
this.requestUpdate();
}
set selectedEntries(entries) {
this._selectedEntries = entries;
// TODO: add code selection dropdown
this._updateSelect();
this.entry = entries.first();
}
set entry(entry) {
this._entry = entry;
this.update();
this.requestUpdate();
}
get _disassemblyNode() {
......@@ -52,12 +50,15 @@ DOM.defineCustomElement('view/code-panel',
}
_update() {
this._updateSelect();
this._disassemblyNode.innerText = this._entry?.disassemble ?? '';
this._sourceNode.innerText = this._entry?.source ?? '';
}
_updateSelect() {
const select = this._codeSelectNode;
if (select.data === this._selectedEntries) return;
select.data = this._selectedEntries;
select.options.length = 0;
const sorted =
this._selectedEntries.slice().sort((a, b) => a.time - b.time);
......
......@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
class CSSColor {
export class CSSColor {
static _cache = new Map();
static get(name) {
......@@ -121,7 +121,7 @@ class CSSColor {
}
}
class DOM {
export class DOM {
static element(type, classes) {
const node = document.createElement(type);
if (classes === undefined) return node;
......@@ -185,19 +185,18 @@ class DOM {
}
}
function $(id) {
export function $(id) {
return document.querySelector(id)
}
class V8CustomElement extends HTMLElement {
export class V8CustomElement extends HTMLElement {
_updateTimeoutId;
_updateCallback = this._update.bind(this);
_updateCallback = this.forceUpdate.bind(this);
constructor(templateText) {
super();
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = templateText;
this._updateCallback = this._update.bind(this);
}
$(id) {
......@@ -208,7 +207,7 @@ class V8CustomElement extends HTMLElement {
return this.shadowRoot.querySelectorAll(query);
}
update(useAnimation = false) {
requestUpdate(useAnimation = false) {
if (useAnimation) {
window.cancelAnimationFrame(this._updateTimeoutId);
this._updateTimeoutId =
......@@ -221,12 +220,54 @@ class V8CustomElement extends HTMLElement {
}
}
forceUpdate() {
this._update();
}
_update() {
throw Error('Subclass responsibility');
}
}
class Chunked {
export class CollapsableElement extends V8CustomElement {
constructor(templateText) {
super(templateText);
this._hasPendingUpdate = false;
this._closer.onclick = _ => this.tryUpdateOnVisibilityChange();
}
get _closer() {
return this.$('#closer');
}
_contentIsVisible() {
return !this._closer.checked;
}
requestUpdate(useAnimation = false) {
// A pending update will be resolved later, no need to try again.
if (this._hasPendingUpdate) return;
this._hasPendingUpdate = true;
this.requestUpdateIfVisible(useAnimation);
}
tryUpdateOnVisibilityChange() {
if (!this._hasPendingUpdate) return;
this.requestUpdateIfVisible(true);
}
requestUpdateIfVisible(useAnimation) {
if (!this._contentIsVisible()) return;
return super.requestUpdate(useAnimation);
}
forceUpdate() {
this._hasPendingUpdate = false;
super.forceUpdate();
}
}
export class Chunked {
constructor(iterable, limit) {
this._iterator = iterable[Symbol.iterator]();
this._limit = limit;
......@@ -248,7 +289,7 @@ class Chunked {
}
}
class LazyTable {
export class LazyTable {
constructor(table, rowData, rowElementCreator, limit = 100) {
this._table = table;
this._chunkedRowData = new Chunked(rowData, limit);
......@@ -304,11 +345,4 @@ export function gradientStopsFromGroups(
return stops;
}
export * from '../helper.mjs';
export {
DOM,
$,
V8CustomElement,
CSSColor,
LazyTable,
};
export * from '../helper.mjs';
\ No newline at end of file
......@@ -58,7 +58,7 @@ found in the LICENSE file. -->
</style>
<div class="panel">
<input type="checkbox" id="closer" class="panelCloserInput">
<input type="checkbox" id="closer" class="panelCloserInput" checked>
<label class="panelCloserLabel" for="closer"></label>
<h2 id="title"></h2>
<div class="selection">
......
......@@ -7,11 +7,11 @@ import {LogEntry} from '../log/log.mjs';
import {FocusEvent} from './events.mjs';
import {groupBy, LazyTable} from './helper.mjs';
import {DOM, V8CustomElement} from './helper.mjs';
import {CollapsableElement, DOM} from './helper.mjs';
DOM.defineCustomElement('view/list-panel',
(templateText) =>
class ListPanel extends V8CustomElement {
class ListPanel extends CollapsableElement {
_selectedLogEntries = [];
_displayedLogEntries = [];
_timeline;
......@@ -21,7 +21,7 @@ DOM.defineCustomElement('view/list-panel',
constructor() {
super(templateText);
this.groupKey.addEventListener('change', e => this.update());
this.groupKey.addEventListener('change', e => this.requestUpdate());
this.showAllRadio.onclick = _ => this._showEntries(this._timeline);
this.showTimerangeRadio.onclick = _ =>
this._showEntries(this._timeline.selectionOrSelf);
......@@ -72,9 +72,11 @@ DOM.defineCustomElement('view/list-panel',
get showAllRadio() {
return this.$('#show-all');
}
get showTimerangeRadio() {
return this.$('#show-timerange');
}
get showSelectionRadio() {
return this.$('#show-selection');
}
......@@ -95,7 +97,7 @@ DOM.defineCustomElement('view/list-panel',
_showEntries(entries) {
this._displayedLogEntries = entries;
this.update();
this.requestUpdate();
}
_update() {
......
......@@ -11,7 +11,7 @@ found in the LICENSE file. -->
}
</style>
<div class="panel">
<input type="checkbox" id="closer" class="panelCloserInput">
<input type="checkbox" id="closer" class="panelCloserInput" checked>
<label class="panelCloserLabel" for="closer"></label>
<h2>Map Panel</h2>
<div class="selection">
......
......@@ -7,100 +7,103 @@ import './map-panel/map-transitions.mjs';
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;
_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';
}
}
});
import {CollapsableElement, DOM} from './helper.mjs';
DOM.defineCustomElement('view/map-panel',
(templateText) =>
class MapPanel extends CollapsableElement {
_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.requestUpdate();
}
_showEntries(entries) {
this._displayedLogEntries = entries;
this.requestUpdate();
}
_update() {
this.mapDetailsTransitionsPanel.selectedLogEntries = [this._map];
this.mapDetailsPanel.map = this._map;
this.mapTransitionsPanel.selectedLogEntries = this._displayedLogEntries;
}
_handleSearch(e) {
const searchBar = this.$('#searchBarInput');
const searchBarInput = searchBar.value;
// access the map from model cache
const selectedMap = MapLogEntry.get(searchBarInput);
if (selectedMap) {
searchBar.className = 'success';
this.dispatchEvent(new FocusEvent(selectedMap));
} else {
searchBar.className = 'failure';
}
}
});
......@@ -25,7 +25,7 @@ DOM.defineCustomElement(
set map(map) {
if (this._map === map) return;
this._map = map;
this.update();
this.requestUpdate();
}
_update() {
......
......@@ -37,7 +37,7 @@ DOM.defineCustomElement(
set selectedLogEntries(list) {
this._selectedLogEntries = list;
this.update();
this.requestUpdate();
}
_update() {
......
......@@ -40,11 +40,15 @@ found in the LICENSE file. -->
.marked {
background-color: var(--secondary-color);
box-shadow: 0px 0px 2px 3px var(--secondary-color);
animation-name: pulse;
animation-duration: 3s;
animation-delay: 500ms;
}
@keyframes pulse {
0% {
box-shadow: 0px 0px 0px 0px var(--secondary-color);
box-shadow: 0px 0px 0px 3px var(--secondary-color);
}
5% {
box-shadow: 0px 0px 0px 10px var(--secondary-color);
......@@ -56,11 +60,13 @@ found in the LICENSE file. -->
box-shadow: 0px 0px 0px 10px var(--secondary-color);
}
20% {
box-shadow: 0px 0px 0px 0px var(--secondary-color);
box-shadow: 0px 0px 2px 3px var(--secondary-color);
}
}
</style>
<div class="panel">
<input type="checkbox" id="closer" class="panelCloserInput" checked>
<label class="panelCloserLabel" for="closer"></label>
<h2>Source Panel</h2>
<div class="selection">
<select id="script-dropdown"></select>
......
......@@ -5,11 +5,11 @@ import {groupBy} from '../helper.mjs';
import {App} from '../index.mjs'
import {SelectRelatedEvent, ToolTipEvent} from './events.mjs';
import {CSSColor, delay, DOM, formatBytes, gradientStopsFromGroups, V8CustomElement} from './helper.mjs';
import {CollapsableElement, CSSColor, delay, DOM, formatBytes, gradientStopsFromGroups} from './helper.mjs';
DOM.defineCustomElement('view/script-panel',
(templateText) =>
class SourcePanel extends V8CustomElement {
class SourcePanel extends CollapsableElement {
_selectedSourcePositions = [];
_sourcePositionsToMarkNodes = [];
_scripts = [];
......@@ -110,7 +110,7 @@ DOM.defineCustomElement('view/script-panel',
if (!sourcePosition) return;
const markNode = this._sourcePositionsToMarkNodes.get(sourcePosition);
markNode.scrollIntoView(
{behavior: 'smooth', block: 'nearest', inline: 'center'});
{behavior: 'auto', block: 'center', inline: 'center'});
}
_handleSelectScript(e) {
......
......@@ -112,7 +112,7 @@ DOM.defineCustomElement('view/timeline/timeline-track',
_updateChunks() {
this._chunks =
this._timeline.chunks(this.nofChunks, this._legend.filterPredicate);
this.update();
this.requestUpdate();
}
get chunks() {
......
......@@ -16,7 +16,7 @@ DOM.defineCustomElement(
this.hide();
} else {
this.show();
this.update(true);
this.requestUpdate(true);
}
});
}
......@@ -31,7 +31,7 @@ DOM.defineCustomElement(
rect.y += rect.height;
}
this._setPosition(rect, atRight, atBottom);
this.update(true);
this.requestUpdate(true);
}
set positionOrTargetNode(positionOrTargetNode) {
......@@ -47,7 +47,7 @@ DOM.defineCustomElement(
this._targetNode = targetNode;
if (targetNode) {
this._intersectionObserver.observe(targetNode);
this.update(true);
this.requestUpdate(true);
}
}
......
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