4029804155
- Avoid private fields (using _xyz instead of #xyz) - Avoid static fields on classes These are temporary changes that eventually will be reverted once FireFox and Safari support it. Bug: v8:10644 Change-Id: I3d757251eaedef92751970d866882c3d912c7e3e Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2464924 Commit-Queue: Camillo Bruni <cbruni@chromium.org> Reviewed-by: Sathya Gunasekaran <gsathya@chromium.org> Cr-Commit-Position: refs/heads/master@{#70607}
221 lines
6.8 KiB
JavaScript
221 lines
6.8 KiB
JavaScript
// 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 { Group } from './ic-model.mjs';
|
|
import { MapLogEntry } from "./log/map.mjs";
|
|
import { FocusEvent, SelectTimeEvent, SelectionEvent } from './events.mjs';
|
|
import { defineCustomElement, V8CustomElement } from './helper.mjs';
|
|
import { IcLogEntry } from './log/ic.mjs';
|
|
|
|
defineCustomElement('ic-panel', (templateText) =>
|
|
class ICPanel extends V8CustomElement {
|
|
_selectedLogEntries;
|
|
_timeline;
|
|
constructor() {
|
|
super(templateText);
|
|
this.initGroupKeySelect();
|
|
this.groupKey.addEventListener(
|
|
'change', e => this.updateTable(e));
|
|
this.$('#filterICTimeBtn').addEventListener(
|
|
'click', e => this.handleICTimeFilter(e));
|
|
}
|
|
set timeline(value) {
|
|
console.assert(value !== undefined, "timeline undefined!");
|
|
this._timeline = value;
|
|
this.selectedLogEntries = this._timeline.all;
|
|
this.updateCount();
|
|
}
|
|
get groupKey() {
|
|
return this.$('#group-key');
|
|
}
|
|
|
|
get table() {
|
|
return this.$('#table');
|
|
}
|
|
|
|
get tableBody() {
|
|
return this.$('#table-body');
|
|
}
|
|
|
|
get count() {
|
|
return this.$('#count');
|
|
}
|
|
|
|
get spanSelectAll() {
|
|
return this.querySelectorAll("span");
|
|
}
|
|
|
|
set selectedLogEntries(value) {
|
|
this._selectedLogEntries = value;
|
|
this.updateCount();
|
|
this.updateTable();
|
|
}
|
|
|
|
updateCount() {
|
|
this.count.innerHTML = this._selectedLogEntries.length;
|
|
}
|
|
|
|
updateTable(event) {
|
|
let select = this.groupKey;
|
|
let key = select.options[select.selectedIndex].text;
|
|
let tableBody = this.tableBody;
|
|
this.removeAllChildren(tableBody);
|
|
let groups = Group.groupBy(this._selectedLogEntries, key, true);
|
|
this.render(groups, tableBody);
|
|
}
|
|
|
|
escapeHtml(unsafe) {
|
|
if (!unsafe) return "";
|
|
return unsafe.toString()
|
|
.replace(/&/g, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, """)
|
|
.replace(/'/g, "'");
|
|
}
|
|
processValue(unsafe) {
|
|
if (!unsafe) return "";
|
|
if (!unsafe.startsWith("http")) return this.escapeHtml(unsafe);
|
|
let a = document.createElement("a");
|
|
a.href = unsafe;
|
|
a.textContent = unsafe;
|
|
return a;
|
|
}
|
|
|
|
td(tr, content, className) {
|
|
let node = document.createElement("td");
|
|
if (typeof content == "object") {
|
|
node.appendChild(content);
|
|
} else {
|
|
node.innerHTML = content;
|
|
}
|
|
node.className = className;
|
|
tr.appendChild(node);
|
|
return node
|
|
}
|
|
|
|
handleMapClick(e) {
|
|
const entry = e.target.parentNode.entry;
|
|
const id = entry.key;
|
|
const selectedMapLogEntries =
|
|
this.searchIcLogEntryToMapLogEntry(id, entry.entries);
|
|
this.dispatchEvent(new SelectionEvent(selectedMapLogEntries));
|
|
}
|
|
|
|
searchIcLogEntryToMapLogEntry(id, icLogEntries) {
|
|
// searches for mapLogEntries using the id, time
|
|
const selectedMapLogEntriesSet = new Set();
|
|
for (const icLogEntry of icLogEntries) {
|
|
const time = icLogEntry.time;
|
|
const selectedMap = MapLogEntry.get(id, time);
|
|
selectedMapLogEntriesSet.add(selectedMap);
|
|
}
|
|
return Array.from(selectedMapLogEntriesSet);
|
|
}
|
|
|
|
//TODO(zcankara) Handle in the processor for events with source positions.
|
|
handleFilePositionClick(e) {
|
|
const entry = e.target.parentNode.entry;
|
|
this.dispatchEvent(new FocusEvent(entry.filePosition));
|
|
}
|
|
|
|
render(entries, parent) {
|
|
let fragment = document.createDocumentFragment();
|
|
let max = Math.min(1000, entries.length)
|
|
for (let i = 0; i < max; i++) {
|
|
let entry = entries[i];
|
|
let tr = document.createElement("tr");
|
|
tr.entry = entry;
|
|
//TODO(zcankara) Create one bound method and use it everywhere
|
|
if (entry.property === "map") {
|
|
tr.addEventListener('click', e => this.handleMapClick(e));
|
|
tr.classList.add('clickable');
|
|
} else if (entry.property == "filePosition") {
|
|
tr.classList.add('clickable');
|
|
tr.addEventListener('click',
|
|
e => this.handleFilePositionClick(e));
|
|
}
|
|
let details = this.td(tr, '<span>ℹ</a>', 'details');
|
|
//TODO(zcankara) don't keep the whole function context alive
|
|
details.onclick = _ => this.toggleDetails(details);
|
|
this.td(tr, entry.percentage + "%", 'percentage');
|
|
this.td(tr, entry.count, 'count');
|
|
this.td(tr, this.processValue(entry.key), 'key');
|
|
fragment.appendChild(tr);
|
|
}
|
|
let omitted = entries.length - max;
|
|
if (omitted > 0) {
|
|
let tr = document.createElement("tr");
|
|
let tdNode = this.td(tr, 'Omitted ' + omitted + " entries.");
|
|
tdNode.colSpan = 4;
|
|
fragment.appendChild(tr);
|
|
}
|
|
parent.appendChild(fragment);
|
|
}
|
|
|
|
|
|
renderDrilldown(entry, previousSibling) {
|
|
let tr = document.createElement('tr');
|
|
tr.className = "entry-details";
|
|
tr.style.display = "none";
|
|
// indent by one td.
|
|
tr.appendChild(document.createElement("td"));
|
|
let td = document.createElement("td");
|
|
td.colSpan = 3;
|
|
for (let key in entry.groups) {
|
|
td.appendChild(this.renderDrilldownGroup(entry, key));
|
|
}
|
|
tr.appendChild(td);
|
|
// Append the new TR after previousSibling.
|
|
previousSibling.parentNode.insertBefore(tr, previousSibling.nextSibling)
|
|
}
|
|
|
|
renderDrilldownGroup(entry, key) {
|
|
let max = 20;
|
|
let group = entry.groups[key];
|
|
let div = document.createElement("div")
|
|
div.className = 'drilldown-group-title'
|
|
div.textContent = key + ' [top ' + max + ' out of ' + group.length + ']';
|
|
let table = document.createElement("table");
|
|
this.render(group.slice(0, max), table, false)
|
|
div.appendChild(table);
|
|
return div;
|
|
}
|
|
|
|
toggleDetails(node) {
|
|
let tr = node.parentNode;
|
|
let entry = tr.entry;
|
|
// Create subgroup in-place if the don't exist yet.
|
|
if (entry.groups === undefined) {
|
|
entry.createSubGroups();
|
|
this.renderDrilldown(entry, tr);
|
|
}
|
|
let details = tr.nextSibling;
|
|
let display = details.style.display;
|
|
if (display != "none") {
|
|
display = "none";
|
|
} else {
|
|
display = "table-row"
|
|
};
|
|
details.style.display = display;
|
|
}
|
|
|
|
initGroupKeySelect() {
|
|
let select = this.groupKey;
|
|
select.options.length = 0;
|
|
for (const propertyName of IcLogEntry.propertyNames) {
|
|
let option = document.createElement("option");
|
|
option.text = propertyName;
|
|
select.add(option);
|
|
}
|
|
}
|
|
|
|
handleICTimeFilter(e) {
|
|
this.dispatchEvent(new SelectTimeEvent(
|
|
parseInt(this.$('#filter-time-start').value),
|
|
parseInt(this.$('#filter-time-end').value)));
|
|
}
|
|
|
|
});
|