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