Reland "[presubmit] Add JS formatting for tools/system-analyzer"

This is a reland of 1ec8f1da4f

Original change's description:
> [presubmit] Add JS formatting for tools/system-analyzer
>
> Bug: v8:10670
> Change-Id: Ifb653ada003719faff261b6e5b2169db37cffdaf
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2282522
> Reviewed-by: Tamer Tas <tmrts@chromium.org>
> Commit-Queue: Sathya Gunasekaran  <gsathya@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#68909}

Bug: v8:10670
Change-Id: I4903b3eb8ff39a76594324076f0840b06290044e
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2307229
Commit-Queue: Sathya Gunasekaran  <gsathya@chromium.org>
Reviewed-by: Tamer Tas <tmrts@chromium.org>
Reviewed-by: Camillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#70938}
This commit is contained in:
Sathya Gunasekaran 2020-11-03 08:01:33 +00:00 committed by Commit Bot
parent efff3d18ec
commit 91ace57f43
23 changed files with 1401 additions and 1361 deletions

1
.gitignore vendored
View File

@ -29,6 +29,7 @@
.cproject
.gclient_entries
.gdb_history
.jslint-cache
.landmines
.project
.pydevproject

View File

@ -80,6 +80,7 @@ def _V8PresubmitChecks(input_api, output_api):
sys.path.append(input_api.os_path.join(
input_api.PresubmitLocalPath(), 'tools'))
from v8_presubmit import CppLintProcessor
from v8_presubmit import JSLintProcessor
from v8_presubmit import TorqueLintProcessor
from v8_presubmit import SourceProcessor
from v8_presubmit import StatusFilesProcessor
@ -95,6 +96,11 @@ def _V8PresubmitChecks(input_api, output_api):
affected_file,
files_to_check=(r'.+\.tq'))
def FilterJSFile(affected_file):
return input_api.FilterSourceFile(
affected_file,
white_list=(r'.+\.m?js'))
results = []
if not CppLintProcessor().RunOnFiles(
input_api.AffectedFiles(file_filter=FilterFile, include_deletes=False)):
@ -103,6 +109,10 @@ def _V8PresubmitChecks(input_api, output_api):
input_api.AffectedFiles(file_filter=FilterTorqueFile,
include_deletes=False)):
results.append(output_api.PresubmitError("Torque format check failed"))
if not JSLintProcessor().RunOnFiles(
input_api.AffectedFiles(file_filter=FilterJSFile,
include_deletes=False)):
results.append(output_api.PresubmitError("JS format check failed"))
if not SourceProcessor().RunOnFiles(
input_api.AffectedFiles(include_deletes=False)):
results.append(output_api.PresubmitError(

View File

@ -3,7 +3,7 @@
// found in the LICENSE file.
class State {
_timeSelection = { start: 0, end: Infinity };
_timeSelection = {start: 0, end: Infinity};
_map;
_ic;
_selectedMapLogEntries;
@ -60,11 +60,11 @@ class State {
this._deoptTimeline = timeline;
}
set chunks(value) {
//TODO(zcankara) split up between maps and ics, and every timeline track
// TODO(zcankara) split up between maps and ics, and every timeline track
this._chunks = value;
}
get chunks() {
//TODO(zcankara) split up between maps and ics, and every timeline track
// TODO(zcankara) split up between maps and ics, and every timeline track
return this._chunks;
}
get nofChunks() {
@ -74,20 +74,20 @@ class State {
this._nofChunks = count;
}
get map() {
//TODO(zcankara) rename as selectedMapEvents, array of selected events
// TODO(zcankara) rename as selectedMapEvents, array of selected events
return this._map;
}
set map(value) {
//TODO(zcankara) rename as selectedMapEvents, array of selected events
// TODO(zcankara) rename as selectedMapEvents, array of selected events
if (!value) return;
this._map = value;
}
get ic() {
//TODO(zcankara) rename selectedICEvents, array of selected events
// TODO(zcankara) rename selectedICEvents, array of selected events
return this._ic;
}
set ic(value) {
//TODO(zcankara) rename selectedIcEvents, array of selected events
// TODO(zcankara) rename selectedIcEvents, array of selected events
if (!value) return;
this._ic = value;
}
@ -122,4 +122,4 @@ class State {
}
}
export { State };
export {State};

View File

@ -4,44 +4,48 @@
class SelectionEvent extends CustomEvent {
// TODO: turn into static class fields once Safari supports it.
static get name() { return "showentries"; }
static get name() {
return 'showentries';
}
constructor(entries) {
super(SelectionEvent.name, { bubbles: true, composed: true });
super(SelectionEvent.name, {bubbles: true, composed: true});
if (!Array.isArray(entries) || entries.length == 0) {
throw new Error("No valid entries selected!");
throw new Error('No valid entries selected!');
}
this.entries = entries;
}
}
class FocusEvent extends CustomEvent {
static get name() { return "showentrydetail"; }
static get name() {
return 'showentrydetail';
}
constructor(entry) {
super(FocusEvent.name, { bubbles: true, composed: true });
super(FocusEvent.name, {bubbles: true, composed: true});
this.entry = entry;
}
}
class SelectTimeEvent extends CustomEvent {
static get name() { return 'timerangeselect'; }
static get name() {
return 'timerangeselect';
}
constructor(start, end) {
super(SelectTimeEvent.name, { bubbles: true, composed: true });
super(SelectTimeEvent.name, {bubbles: true, composed: true});
this.start = start;
this.end = end;
}
}
class SynchronizeSelectionEvent extends CustomEvent {
static get name() { return 'syncselection'; }
static get name() {
return 'syncselection';
}
constructor(start, end) {
super(SynchronizeSelectionEvent.name, { bubbles: true, composed: true });
super(SynchronizeSelectionEvent.name, {bubbles: true, composed: true});
this.start = start;
this.end = end;
}
}
export {
SelectionEvent, FocusEvent, SelectTimeEvent,
SynchronizeSelectionEvent
};
export {SelectionEvent, FocusEvent, SelectTimeEvent, SynchronizeSelectionEvent};

View File

@ -144,7 +144,7 @@ class DOM {
if (className) node.classList.add(className);
return node;
}
static tr(className) {
const node = document.createElement('tr');
if (className) node.classList.add(className);
@ -162,12 +162,13 @@ class DOM {
}
static defineCustomElement(path, generator) {
let name = path.substring(path.lastIndexOf("/") + 1, path.length);
let name = path.substring(path.lastIndexOf('/') + 1, path.length);
path = path + '-template.html';
fetch(path)
.then(stream => stream.text())
.then(
templateText => customElements.define(name, generator(templateText)));
.then(stream => stream.text())
.then(
templateText =>
customElements.define(name, generator(templateText)));
}
}
@ -178,7 +179,7 @@ function $(id) {
class V8CustomElement extends HTMLElement {
constructor(templateText) {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = templateText;
}
$(id) {
@ -190,7 +191,6 @@ class V8CustomElement extends HTMLElement {
}
}
class LazyTable {
constructor(table, rowData, rowElementCreator) {
this._table = table;
@ -198,7 +198,7 @@ class LazyTable {
this._rowElementCreator = rowElementCreator;
const tbody = table.querySelector('tbody');
table.replaceChild(document.createElement('tbody'), tbody);
table.querySelector("tfoot td").onclick = (e) => this._addMoreRows();
table.querySelector('tfoot td').onclick = (e) => this._addMoreRows();
this._addMoreRows();
}
@ -216,7 +216,6 @@ class LazyTable {
}
}
class LazyTable {
constructor(table, rowData, rowElementCreator) {
this._table = table;
@ -224,7 +223,7 @@ class LazyTable {
this._rowElementCreator = rowElementCreator;
const tbody = table.querySelector('tbody');
table.replaceChild(document.createElement('tbody'), tbody);
table.querySelector("tfoot td").onclick = (e) => this._addMoreRows();
table.querySelector('tfoot td').onclick = (e) => this._addMoreRows();
this._addMoreRows();
}
@ -247,6 +246,12 @@ function delay(time) {
}
export {
DOM, $, V8CustomElement, formatBytes,
typeToColor, CSSColor, delay, LazyTable,
DOM,
$,
V8CustomElement,
formatBytes,
typeToColor,
CSSColor,
delay,
LazyTable,
};

View File

@ -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 { IcLogEntry } from "./log/ic.mjs";
import {IcLogEntry} from './log/ic.mjs';
// For compatibility with console scripts:
print = console.log;
@ -51,8 +51,7 @@ export class Group {
group.percentage = Math.round(group.count / length * 100 * 100) / 100;
result.push(group);
}
result.sort((a, b) => { return b.count - a.count });
result.sort((a, b) => {return b.count - a.count});
return result;
}
}

View File

@ -2,194 +2,194 @@
// 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 { DOM, V8CustomElement, delay } from './helper.mjs';
import { IcLogEntry } from './log/ic.mjs';
import {FocusEvent, SelectionEvent, SelectTimeEvent} from './events.mjs';
import {delay, DOM, V8CustomElement} from './helper.mjs';
import {Group} from './ic-model.mjs';
import {IcLogEntry} from './log/ic.mjs';
import {MapLogEntry} from './log/map.mjs';
DOM.defineCustomElement('ic-panel', (templateText) =>
class ICPanel extends V8CustomElement {
_selectedLogEntries;
_timeline;
constructor() {
super(templateText);
this.initGroupKeySelect();
this.groupKey.addEventListener(
'change', e => this.updateTable(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.update();
}
async update() {
await delay(1);
this.updateCount();
this.updateTable();
}
updateCount() {
this.count.innerHTML = "length=" + this._selectedLogEntries.length;
}
updateTable(event) {
let select = this.groupKey;
let key = select.options[select.selectedIndex].text;
let tableBody = this.tableBody;
DOM.removeAllChildren(tableBody);
let groups = Group.groupBy(this._selectedLogEntries, key, true);
this.render(groups, tableBody);
}
escapeHtml(unsafe) {
if (!unsafe) return "";
return unsafe.toString()
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
handleMapClick(e) {
const group = e.target.parentNode.entry;
const id = group.key;
const selectedMapLogEntries =
this.searchIcLogEntryToMapLogEntry(id, group.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);
DOM.defineCustomElement(
'ic-panel', (templateText) => class ICPanel extends V8CustomElement {
_selectedLogEntries;
_timeline;
constructor() {
super(templateText);
this.initGroupKeySelect();
this.groupKey.addEventListener('change', e => this.updateTable(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');
}
return Array.from(selectedMapLogEntriesSet);
}
//TODO(zcankara) Handle in the processor for events with source positions.
handleFilePositionClick(e) {
const tr = e.target.parentNode;
const sourcePosition = tr.group.entries[0].sourcePosition;
this.dispatchEvent(new FocusEvent(sourcePosition));
}
get table() {
return this.$('#table');
}
render(groups, parent) {
const fragment = document.createDocumentFragment();
const max = Math.min(1000, groups.length)
const detailsClickHandler = this.handleDetailsClick.bind(this);
const mapClickHandler = this.handleMapClick.bind(this);
const fileClickHandler = this.handleFilePositionClick.bind(this);
for (let i = 0; i < max; i++) {
const group = groups[i];
const tr = DOM.tr();
tr.group = group;
const details = tr.appendChild(DOM.td('', 'toggle'));
details.onclick = detailsClickHandler;
tr.appendChild(DOM.td(group.percentage + "%", 'percentage'));
tr.appendChild(DOM.td(group.count, 'count'));
const valueTd = tr.appendChild(DOM.td(group.key, 'key'));
if (group.property === "map") {
valueTd.onclick = mapClickHandler;
valueTd.classList.add('clickable');
} else if (group.property == "filePosition") {
valueTd.classList.add('clickable');
valueTd.onclick = fileClickHandler;
get tableBody() {
return this.$('#table-body');
}
get count() {
return this.$('#count');
}
get spanSelectAll() {
return this.querySelectorAll('span');
}
set selectedLogEntries(value) {
this._selectedLogEntries = value;
this.update();
}
async update() {
await delay(1);
this.updateCount();
this.updateTable();
}
updateCount() {
this.count.innerHTML = 'length=' + this._selectedLogEntries.length;
}
updateTable(event) {
let select = this.groupKey;
let key = select.options[select.selectedIndex].text;
let tableBody = this.tableBody;
DOM.removeAllChildren(tableBody);
let groups = Group.groupBy(this._selectedLogEntries, key, true);
this.render(groups, tableBody);
}
escapeHtml(unsafe) {
if (!unsafe) return '';
return unsafe.toString()
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
handleMapClick(e) {
const group = e.target.parentNode.entry;
const id = group.key;
const selectedMapLogEntries =
this.searchIcLogEntryToMapLogEntry(id, group.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);
}
fragment.appendChild(tr);
return Array.from(selectedMapLogEntriesSet);
}
const omitted = groups.length - max;
if (omitted > 0) {
const tr = DOM.tr();
const tdNode =
tr.appendChild(DOM.td('Omitted ' + omitted + " entries."));
tdNode.colSpan = 4;
fragment.appendChild(tr);
}
parent.appendChild(fragment);
}
handleDetailsClick(event) {
const tr = event.target.parentNode;
const group = tr.group;
// Create subgroup in-place if the don't exist yet.
if (group.groups === undefined) {
group.createSubGroups();
this.renderDrilldown(group, tr);
// TODO(zcankara) Handle in the processor for events with source
// positions.
handleFilePositionClick(e) {
const tr = e.target.parentNode;
const sourcePosition = tr.group.entries[0].sourcePosition;
this.dispatchEvent(new FocusEvent(sourcePosition));
}
let detailsTr = tr.nextSibling;
if (tr.classList.contains("open")) {
tr.classList.remove("open");
detailsTr.style.display = "none";
} else {
tr.classList.add("open");
detailsTr.style.display = "table-row";
}
}
renderDrilldown(group, previousSibling) {
let tr = DOM.tr("entry-details");
tr.style.display = "none";
// indent by one td.
tr.appendChild(DOM.td());
let td = DOM.td();
td.colSpan = 3;
for (let key in group.groups) {
this.renderDrilldownGroup(td, group.groups[key], key);
render(groups, parent) {
const fragment = document.createDocumentFragment();
const max = Math.min(1000, groups.length)
const detailsClickHandler = this.handleDetailsClick.bind(this);
const mapClickHandler = this.handleMapClick.bind(this);
const fileClickHandler = this.handleFilePositionClick.bind(this);
for (let i = 0; i < max; i++) {
const group = groups[i];
const tr = DOM.tr();
tr.group = group;
const details = tr.appendChild(DOM.td('', 'toggle'));
details.onclick = detailsClickHandler;
tr.appendChild(DOM.td(group.percentage + '%', 'percentage'));
tr.appendChild(DOM.td(group.count, 'count'));
const valueTd = tr.appendChild(DOM.td(group.key, 'key'));
if (group.property === 'map') {
valueTd.onclick = mapClickHandler;
valueTd.classList.add('clickable');
} else if (group.property == 'filePosition') {
valueTd.classList.add('clickable');
valueTd.onclick = fileClickHandler;
}
fragment.appendChild(tr);
}
const omitted = groups.length - max;
if (omitted > 0) {
const tr = DOM.tr();
const tdNode =
tr.appendChild(DOM.td('Omitted ' + omitted + ' entries.'));
tdNode.colSpan = 4;
fragment.appendChild(tr);
}
parent.appendChild(fragment);
}
tr.appendChild(td);
// Append the new TR after previousSibling.
previousSibling.parentNode.insertBefore(tr, previousSibling.nextSibling)
}
renderDrilldownGroup(td, children, key) {
const max = 20;
const div = DOM.div('drilldown-group-title');
div.textContent =
`Grouped by ${key} [top ${max} out of ${children.length}]`;
td.appendChild(div);
const table = DOM.table();
this.render(children.slice(0, max), table, false)
td.appendChild(table);
}
initGroupKeySelect() {
const select = this.groupKey;
select.options.length = 0;
for (const propertyName of IcLogEntry.propertyNames) {
const option = document.createElement("option");
option.text = propertyName;
select.add(option);
handleDetailsClick(event) {
const tr = event.target.parentNode;
const group = tr.group;
// Create subgroup in-place if the don't exist yet.
if (group.groups === undefined) {
group.createSubGroups();
this.renderDrilldown(group, tr);
}
let detailsTr = tr.nextSibling;
if (tr.classList.contains('open')) {
tr.classList.remove('open');
detailsTr.style.display = 'none';
} else {
tr.classList.add('open');
detailsTr.style.display = 'table-row';
}
}
}
});
renderDrilldown(group, previousSibling) {
let tr = DOM.tr('entry-details');
tr.style.display = 'none';
// indent by one td.
tr.appendChild(DOM.td());
let td = DOM.td();
td.colSpan = 3;
for (let key in group.groups) {
this.renderDrilldownGroup(td, group.groups[key], key);
}
tr.appendChild(td);
// Append the new TR after previousSibling.
previousSibling.parentNode.insertBefore(tr, previousSibling.nextSibling)
}
renderDrilldownGroup(td, children, key) {
const max = 20;
const div = DOM.div('drilldown-group-title');
div.textContent =
`Grouped by ${key} [top ${max} out of ${children.length}]`;
td.appendChild(div);
const table = DOM.table();
this.render(children.slice(0, max), table, false)
td.appendChild(table);
}
initGroupKeySelect() {
const select = this.groupKey;
select.options.length = 0;
for (const propertyName of IcLogEntry.propertyNames) {
const option = document.createElement('option');
option.text = propertyName;
select.add(option);
}
}
});

View File

@ -2,22 +2,23 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import { SelectionEvent, FocusEvent, SelectTimeEvent } from "./events.mjs";
import { State } from "./app-model.mjs";
import { MapLogEntry } from "./log/map.mjs";
import { IcLogEntry } from "./log/ic.mjs";
import { Processor } from "./processor.mjs";
import { SourcePosition } from "../profile.mjs";
import { $ } from "./helper.mjs";
import {SourcePosition} from '../profile.mjs';
import {State} from './app-model.mjs';
import {FocusEvent, SelectionEvent, SelectTimeEvent} from './events.mjs';
import {$} from './helper.mjs';
import {IcLogEntry} from './log/ic.mjs';
import {MapLogEntry} from './log/map.mjs';
import {Processor} from './processor.mjs';
class App {
_state;
_view;
_navigation;
_startupPromise;
constructor(fileReaderId, mapPanelId, mapStatsPanelId, timelinePanelId,
icPanelId, mapTrackId, icTrackId, deoptTrackId, sourcePanelId) {
constructor(
fileReaderId, mapPanelId, mapStatsPanelId, timelinePanelId, icPanelId,
mapTrackId, icTrackId, deoptTrackId, sourcePanelId) {
this._view = {
__proto__: null,
logFileReader: $(fileReaderId),
@ -31,32 +32,30 @@ class App {
sourcePanel: $(sourcePanelId)
};
this.toggleSwitch = $('.theme-switch input[type="checkbox"]');
this.toggleSwitch.addEventListener("change", (e) => this.switchTheme(e));
this._view.logFileReader.addEventListener("fileuploadstart", (e) =>
this.handleFileUploadStart(e)
);
this._view.logFileReader.addEventListener("fileuploadend", (e) =>
this.handleFileUploadEnd(e)
);
this.toggleSwitch.addEventListener('change', (e) => this.switchTheme(e));
this._view.logFileReader.addEventListener(
'fileuploadstart', (e) => this.handleFileUploadStart(e));
this._view.logFileReader.addEventListener(
'fileuploadend', (e) => this.handleFileUploadEnd(e));
this._startupPromise = this.runAsyncInitialize();
}
async runAsyncInitialize() {
await Promise.all([
import("./ic-panel.mjs"),
import("./timeline-panel.mjs"),
import("./stats-panel.mjs"),
import("./map-panel.mjs"),
import("./source-panel.mjs"),
]);
document.addEventListener('keydown',
e => this._navigation?.handleKeyDown(e));
document.addEventListener(SelectionEvent.name,
e => this.handleShowEntries(e));
document.addEventListener(FocusEvent.name,
e => this.handleShowEntryDetail(e));
document.addEventListener(SelectTimeEvent.name,
e => this.handleTimeRangeSelect(e));
import('./ic-panel.mjs'),
import('./timeline-panel.mjs'),
import('./stats-panel.mjs'),
import('./map-panel.mjs'),
import('./source-panel.mjs'),
]);
document.addEventListener(
'keydown', e => this._navigation?.handleKeyDown(e));
document.addEventListener(
SelectionEvent.name, e => this.handleShowEntries(e));
document.addEventListener(
FocusEvent.name, e => this.handleShowEntryDetail(e));
document.addEventListener(
SelectTimeEvent.name, e => this.handleTimeRangeSelect(e));
}
handleShowEntries(e) {
@ -67,7 +66,7 @@ class App {
} else if (e.entries[0] instanceof SourcePosition) {
this.showSourcePositionEntries(e.entries);
} else {
throw new Error("Unknown selection type!");
throw new Error('Unknown selection type!');
}
e.stopPropagation();
}
@ -84,7 +83,7 @@ class App {
this._state.selectedDeoptLogEntries = entries;
}
showSourcePositionEntries(entries) {
//TODO: Handle multiple source position selection events
// TODO: Handle multiple source position selection events
this._view.sourcePanel.selectedSourcePositions = entries
}
@ -98,7 +97,7 @@ class App {
this.showMapEntries(this._state.mapTimeline.selection);
this.showIcEntries(this._state.icTimeline.selection);
this.showDeoptEntries(this._state.deoptTimeline.selection);
this._view.timelinePanel.timeSelection = {start,end};
this._view.timelinePanel.timeSelection = {start, end};
}
handleShowEntryDetail(e) {
@ -109,7 +108,7 @@ class App {
} else if (e.entry instanceof SourcePosition) {
this.selectSourcePosition(e.entry);
} else {
throw new Error("Unknown selection type!");
throw new Error('Unknown selection type!');
}
e.stopPropagation();
}
@ -129,7 +128,7 @@ class App {
handleFileUploadStart(e) {
this.restartApp();
$("#container").className = "initial";
$('#container').className = 'initial';
}
restartApp() {
@ -150,17 +149,18 @@ class App {
// Transitions must be set before timeline for stats panel.
this._view.mapPanel.timeline = mapTimeline;
this._view.mapTrack.data = mapTimeline;
this._view.mapStatsPanel.transitions = this._state.mapTimeline.transitions;
this._view.mapStatsPanel.transitions =
this._state.mapTimeline.transitions;
this._view.mapStatsPanel.timeline = mapTimeline;
this._view.icPanel.timeline = icTimeline;
this._view.icTrack.data = icTimeline;
this._view.deoptTrack.data = deoptTimeline;
this._view.sourcePanel.data = processor.scripts
} catch(e) {
this._view.logFileReader.error = "Log file contains errors!"
throw(e);
} catch (e) {
this._view.logFileReader.error = 'Log file contains errors!'
throw (e);
} finally {
$("#container").className = "loaded";
$('#container').className = 'loaded';
this.fileLoaded = true;
}
}
@ -172,9 +172,8 @@ class App {
}
switchTheme(event) {
document.documentElement.dataset.theme = event.target.checked
? "light"
: "dark";
document.documentElement.dataset.theme =
event.target.checked ? 'light' : 'dark';
if (this.fileLoaded) {
this.refreshTimelineTrackView();
}
@ -268,7 +267,7 @@ class Navigation {
}
handleKeyDown(event) {
switch (event.key) {
case "ArrowUp":
case 'ArrowUp':
event.preventDefault();
if (event.shiftKey) {
this.selectPrevEdge();
@ -276,7 +275,7 @@ class Navigation {
this.moveInChunk(-1);
}
return false;
case "ArrowDown":
case 'ArrowDown':
event.preventDefault();
if (event.shiftKey) {
this.selectNextEdge();
@ -284,20 +283,20 @@ class Navigation {
this.moveInChunk(1);
}
return false;
case "ArrowLeft":
case 'ArrowLeft':
this.moveInChunks(false);
break;
case "ArrowRight":
case 'ArrowRight':
this.moveInChunks(true);
break;
case "+":
case '+':
this.increaseTimelineResolution();
break;
case "-":
case '-':
this.decreaseTimelineResolution();
break;
}
}
}
export { App };
export {App};

View File

@ -1,82 +1,84 @@
// 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 { DOM, V8CustomElement } from './helper.mjs';
import {DOM, V8CustomElement} from './helper.mjs';
DOM.defineCustomElement('log-file-reader', (templateText) =>
class LogFileReader extends V8CustomElement {
constructor() {
super(templateText);
this.addEventListener('click', e => this.handleClick(e));
this.addEventListener('dragover', e => this.handleDragOver(e));
this.addEventListener('drop', e => this.handleChange(e));
this.$('#file').addEventListener('change', e => this.handleChange(e));
this.$('#fileReader').addEventListener('keydown',
e => this.handleKeyEvent(e));
DOM.defineCustomElement('log-file-reader',
(templateText) =>
class LogFileReader extends V8CustomElement {
constructor() {
super(templateText);
this.addEventListener('click', e => this.handleClick(e));
this.addEventListener('dragover', e => this.handleDragOver(e));
this.addEventListener('drop', e => this.handleChange(e));
this.$('#file').addEventListener('change', e => this.handleChange(e));
this.$('#fileReader')
.addEventListener('keydown', e => this.handleKeyEvent(e));
}
set error(message) {
this.updateLabel(message);
this.root.className = 'fail';
}
updateLabel(text) {
this.$('#label').innerText = text;
}
handleKeyEvent(event) {
if (event.key == 'Enter') this.handleClick(event);
}
handleClick(event) {
this.$('#file').click();
}
handleChange(event) {
// Used for drop and file change.
event.preventDefault();
this.dispatchEvent(
new CustomEvent('fileuploadstart', {bubbles: true, composed: true}));
var host = event.dataTransfer ? event.dataTransfer : event.target;
this.readFile(host.files[0]);
}
handleDragOver(event) {
event.preventDefault();
}
connectedCallback() {
this.fileReader.focus();
}
get fileReader() {
return this.$('#fileReader');
}
get root() {
return this.$('#root');
}
readFile(file) {
if (!file) {
this.error = 'Failed to load file.';
return;
}
this.fileReader.blur();
this.root.className = 'loading';
const reader = new FileReader();
reader.onload = (e) => this.handleFileLoad(e, file);
// Delay the loading a bit to allow for CSS animations to happen.
setTimeout(() => reader.readAsText(file), 0);
}
set error(message) {
this.updateLabel(message);
this.root.className = 'fail';
}
updateLabel(text) {
this.$('#label').innerText = text;
}
handleKeyEvent(event) {
if (event.key == "Enter") this.handleClick(event);
}
handleClick(event) {
this.$('#file').click();
}
handleChange(event) {
// Used for drop and file change.
event.preventDefault();
this.dispatchEvent(new CustomEvent(
'fileuploadstart', { bubbles: true, composed: true }));
var host = event.dataTransfer ? event.dataTransfer : event.target;
this.readFile(host.files[0]);
}
handleDragOver(event) {
event.preventDefault();
}
connectedCallback() {
this.fileReader.focus();
}
get fileReader() {
return this.$('#fileReader');
}
get root() { return this.$("#root"); }
readFile(file) {
if (!file) {
this.error = 'Failed to load file.';
return;
}
this.fileReader.blur();
this.root.className = 'loading';
const reader = new FileReader();
reader.onload = (e) => this.handleFileLoad(e, file);
// Delay the loading a bit to allow for CSS animations to happen.
setTimeout(() => reader.readAsText(file), 0);
}
handleFileLoad(e, file) {
const chunk = e.target.result;
this.updateLabel('Finished loading \'' + file.name + '\'.');
this.dispatchEvent(new CustomEvent(
'fileuploadend', {
bubbles: true,
composed: true,
detail: chunk,
}));
this.root.className = 'done';
}
});
handleFileLoad(e, file) {
const chunk = e.target.result;
this.updateLabel('Finished loading \'' + file.name + '\'.');
this.dispatchEvent(new CustomEvent('fileuploadend', {
bubbles: true,
composed: true,
detail: chunk,
}));
this.root.className = 'done';
}
});

View File

@ -1,7 +1,7 @@
// 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 { LogEntry } from './log.mjs';
import {LogEntry} from './log.mjs';
export class DeoptLogEntry extends LogEntry {
constructor(type, time) {

View File

@ -1,12 +1,12 @@
// 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 { LogEntry } from './log.mjs';
import {LogEntry} from './log.mjs';
export class IcLogEntry extends LogEntry {
constructor(
type, fn_file, time, line, column, key, oldState, newState, map, reason,
script, modifier, additional) {
type, fn_file, time, line, column, key, oldState, newState, map, reason,
script, modifier, additional) {
super(type, time);
this.category = 'other';
if (this.type.indexOf('Store') !== -1) {
@ -55,18 +55,11 @@ export class IcLogEntry extends LogEntry {
this.file = parts[offset];
return offset;
}
static get propertyNames() {
return [
'type',
'category',
'functionName',
'filePosition',
'state',
'key',
'map',
'reason',
'file'
'type', 'category', 'functionName', 'filePosition', 'state', 'key', 'map',
'reason', 'file'
];
}
}

View File

@ -6,7 +6,7 @@ export class LogEntry {
_time;
_type;
constructor(type, time) {
//TODO(zcankara) remove type and add empty getters to override
// TODO(zcankara) remove type and add empty getters to override
this._time = time;
this._type = type;
}
@ -18,6 +18,6 @@ export class LogEntry {
}
// Returns an Array of all possible #type values.
static get allTypes() {
throw new Error("Not implemented.");
throw new Error('Not implemented.');
}
}

View File

@ -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 { LogEntry } from './log.mjs';
import {LogEntry} from './log.mjs';
// ===========================================================================
// Map Log Events
@ -11,10 +11,10 @@ const kChunkHeight = 200;
const kChunkWidth = 10;
function define(prototype, name, fn) {
Object.defineProperty(prototype, name, { value: fn, enumerable: false });
Object.defineProperty(prototype, name, {value: fn, enumerable: false});
}
define(Array.prototype, 'max', function (fn) {
define(Array.prototype, 'max', function(fn) {
if (this.length === 0) return undefined;
if (fn === undefined) fn = (each) => each;
let max = fn(this[0]);
@ -23,10 +23,10 @@ define(Array.prototype, 'max', function (fn) {
}
return max;
})
define(Array.prototype, 'first', function () {
define(Array.prototype, 'first', function() {
return this[0]
});
define(Array.prototype, 'last', function () {
define(Array.prototype, 'last', function() {
return this[this.length - 1]
});
@ -282,9 +282,8 @@ class Edge {
return this.type + ' ' + this.symbol() + this.name;
}
return this.type + ' ' + (this.reason ? this.reason : '') + ' ' +
(this.name ? this.name : '')
(this.name ? this.name : '')
}
}
export { MapLogEntry, Edge, kChunkWidth, kChunkHeight };
export {MapLogEntry, Edge, kChunkWidth, kChunkHeight};

View File

@ -1,77 +1,76 @@
// 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 "./stats-panel.mjs";
import "./map-panel/map-details.mjs";
import "./map-panel/map-transitions.mjs";
import { FocusEvent } from './events.mjs';
import { MapLogEntry } from "./log/map.mjs";
import { DOM, V8CustomElement } from './helper.mjs';
import './stats-panel.mjs';
import './map-panel/map-details.mjs';
import './map-panel/map-transitions.mjs';
DOM.defineCustomElement('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));
}
import {FocusEvent} from './events.mjs';
import {DOM, V8CustomElement} from './helper.mjs';
import {MapLogEntry} from './log/map.mjs';
handleUpdateMapDetails(e) {
if (e.entry instanceof MapLogEntry) {
this.mapDetailsPanel.mapDetails = e.entry;
}
}
DOM.defineCustomElement('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));
}
get mapTransitionsPanel() {
return this.$('#map-transitions');
handleUpdateMapDetails(e) {
if (e.entry instanceof MapLogEntry) {
this.mapDetailsPanel.mapDetails = e.entry;
}
}
get mapDetailsPanel() {
return this.$('#map-details');
}
get mapTransitionsPanel() {
return this.$('#map-transitions');
}
get searchBarBtn() {
return this.$('#searchBarBtn');
}
get mapDetailsPanel() {
return this.$('#map-details');
}
get searchBar() {
return this.$('#searchBar');
}
get searchBarBtn() {
return this.$('#searchBarBtn');
}
get mapDetails() {
return this.mapDetailsPanel.mapDetails;
}
get searchBar() {
return this.$('#searchBar');
}
set timeline(timeline) {
this._timeline = timeline;
}
get mapDetails() {
return this.mapDetailsPanel.mapDetails;
}
set map(value) {
this._map = value;
this.mapTransitionsPanel.map = this._map;
}
set timeline(timeline) {
this._timeline = timeline;
}
handleSearchBar(e) {
let searchBar = this.$('#searchBarInput');
let searchBarInput = searchBar.value;
//access the map from model cache
let selectedMap = MapLogEntry.get(parseInt(searchBarInput));
if (selectedMap) {
searchBar.className = "success";
} else {
searchBar.className = "failure";
}
this.dispatchEvent(new FocusEvent(selectedMap));
}
set map(value) {
this._map = value;
this.mapTransitionsPanel.map = this._map;
}
set selectedMapLogEntries(list) {
this.mapTransitionsPanel.selectedMapLogEntries = list;
}
get selectedMapLogEntries() {
return this.mapTransitionsPanel.selectedMapLogEntries;
handleSearchBar(e) {
let searchBar = this.$('#searchBarInput');
let searchBarInput = searchBar.value;
// access the map from model cache
let selectedMap = MapLogEntry.get(parseInt(searchBarInput));
if (selectedMap) {
searchBar.className = 'success';
} else {
searchBar.className = 'failure';
}
this.dispatchEvent(new FocusEvent(selectedMap));
}
});
set selectedMapLogEntries(list) {
this.mapTransitionsPanel.selectedMapLogEntries = list;
}
get selectedMapLogEntries() {
return this.mapTransitionsPanel.selectedMapLogEntries;
}
});

View File

@ -1,26 +1,24 @@
// 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 { V8CustomElement, DOM} from "../helper.mjs";
import { FocusEvent } from "../events.mjs";
import {FocusEvent} from '../events.mjs';
import {DOM, V8CustomElement} from '../helper.mjs';
DOM.defineCustomElement(
"./map-panel/map-details",
(templateText) =>
class MapDetails extends V8CustomElement {
'./map-panel/map-details',
(templateText) => class MapDetails extends V8CustomElement {
constructor() {
super(templateText);
this._filePositionNode.addEventListener("click", e =>
this.handleFilePositionClick(e)
);
this._filePositionNode.addEventListener(
'click', e => this.handleFilePositionClick(e));
this.selectedMap = undefined;
}
get mapDetails() {
return this.$("#mapDetails");
return this.$('#mapDetails');
}
get _filePositionNode() {
return this.$("#filePositionNode");
return this.$('#filePositionNode');
}
setSelectedMap(value) {
@ -28,21 +26,20 @@ DOM.defineCustomElement(
}
set mapDetails(map) {
let details = "";
let clickableDetails = "";
let details = '';
let clickableDetails = '';
if (map) {
clickableDetails += "ID: " + map.id;
clickableDetails += "\nSource location: " + map.filePosition;
details += "\n" + map.description;
clickableDetails += 'ID: ' + map.id;
clickableDetails += '\nSource location: ' + map.filePosition;
details += '\n' + map.description;
this.setSelectedMap(map);
}
this._filePositionNode.innerText = clickableDetails;
this._filePositionNode.classList.add("clickable");
this._filePositionNode.classList.add('clickable');
this.mapDetails.innerText = details;
}
handleFilePositionClick() {
this.dispatchEvent(new FocusEvent(this.selectedMap.sourcePosition));
}
}
);
});

View File

@ -1,197 +1,188 @@
// 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 { V8CustomElement, DOM, typeToColor } from "../helper.mjs";
import { FocusEvent, SelectionEvent } from "../events.mjs";
import {FocusEvent, SelectionEvent} from '../events.mjs';
import {DOM, typeToColor, V8CustomElement} from '../helper.mjs';
DOM.defineCustomElement(
"./map-panel/map-transitions",
(templateText) =>
class MapTransitions extends V8CustomElement {
_map;
_selectedMapLogEntries;
_displayedMapsInTree;
_showMapsUpdateId;
constructor() {
super(templateText);
this.transitionView.addEventListener("mousemove", (e) =>
this.handleTransitionViewChange(e)
);
this.currentNode = this.transitionView;
this.currentMap = undefined;
}
DOM.defineCustomElement('./map-panel/map-transitions',
(templateText) =>
class MapTransitions extends V8CustomElement {
_map;
_selectedMapLogEntries;
_displayedMapsInTree;
_showMapsUpdateId;
constructor() {
super(templateText);
this.transitionView.addEventListener(
'mousemove', (e) => this.handleTransitionViewChange(e));
this.currentNode = this.transitionView;
this.currentMap = undefined;
}
get transitionView() {
return this.$("#transitionView");
}
get transitionView() {
return this.$('#transitionView');
}
get tooltip() {
return this.$("#tooltip");
}
get tooltip() {
return this.$('#tooltip');
}
get tooltipContents() {
return this.$("#tooltipContents");
}
get tooltipContents() {
return this.$('#tooltipContents');
}
set map(value) {
this._map = value;
this.showMap();
}
set map(value) {
this._map = value;
this.showMap();
}
handleTransitionViewChange(e) {
this.tooltip.style.left = e.pageX + "px";
this.tooltip.style.top = e.pageY + "px";
let map = e.target.map;
if (map) {
this.tooltipContents.innerText = map.description;
}
}
selectMap(map) {
this.dispatchEvent(new SelectionEvent([map]));
}
showMap() {
if (this.currentMap === this._map) return;
this.currentMap = this._map;
this.selectedMapLogEntries = [this._map];
this.showMaps();
}
showMaps() {
clearTimeout(this._showMapsUpdateId);
this._showMapsUpdateId = setTimeout(() => this._showMaps(), 250);
}
_showMaps() {
this.transitionView.style.display = "none";
DOM.removeAllChildren(this.transitionView);
this._displayedMapsInTree = new Set();
// Limit view to 200 maps for performance reasons.
this.selectedMapLogEntries.slice(0, 200).forEach((map) =>
this.addMapAndParentTransitions(map));
this._displayedMapsInTree = undefined;
this.transitionView.style.display = "";
}
set selectedMapLogEntries(list) {
this._selectedMapLogEntries = list;
this.showMaps();
}
get selectedMapLogEntries() {
return this._selectedMapLogEntries;
}
addMapAndParentTransitions(map) {
if (map === void 0) return;
if (this._displayedMapsInTree.has(map)) return;
this._displayedMapsInTree.add(map);
this.currentNode = this.transitionView;
let parents = map.getParents();
if (parents.length > 0) {
this.addTransitionTo(parents.pop());
parents.reverse().forEach((each) => this.addTransitionTo(each));
}
let mapNode = this.addSubtransitions(map);
// Mark and show the selected map.
mapNode.classList.add("selected");
if (this.selectedMap == map) {
setTimeout(
() =>
mapNode.scrollIntoView({
behavior: "smooth",
block: "nearest",
inline: "nearest",
}),
1
);
}
}
addSubtransitions(map) {
let mapNode = this.addTransitionTo(map);
// Draw outgoing linear transition line.
let current = map;
while (current.children.length == 1) {
current = current.children[0].to;
this.addTransitionTo(current);
}
return mapNode;
}
addTransitionEdge(map) {
let classes = ["transitionEdge"];
let edge = DOM.div(classes);
edge.style.backgroundColor = typeToColor(map.edge);
let labelNode = DOM.div("transitionLabel");
labelNode.innerText = map.edge.toString();
edge.appendChild(labelNode);
return edge;
}
addTransitionTo(map) {
// transition[ transitions[ transition[...], transition[...], ...]];
this._displayedMapsInTree?.add(map);
let transition = DOM.div("transition");
if (map.isDeprecated()) transition.classList.add("deprecated");
if (map.edge) {
transition.appendChild(this.addTransitionEdge(map));
}
let mapNode = this.addMapNode(map);
transition.appendChild(mapNode);
let subtree = DOM.div("transitions");
transition.appendChild(subtree);
this.currentNode.appendChild(transition);
this.currentNode = subtree;
return mapNode;
}
addMapNode(map) {
let node = DOM.div("map");
if (map.edge) node.style.backgroundColor = typeToColor(map.edge);
node.map = map;
node.addEventListener("click", () => this.selectMap(map));
if (map.children.length > 1) {
node.innerText = map.children.length;
let showSubtree = DOM.div("showSubtransitions");
showSubtree.addEventListener("click", (e) =>
this.toggleSubtree(e, node)
);
node.appendChild(showSubtree);
} else if (map.children.length == 0) {
node.innerHTML = "&#x25CF;";
}
this.currentNode.appendChild(node);
return node;
}
toggleSubtree(event, node) {
let map = node.map;
event.target.classList.toggle("opened");
let transitionsNode = node.parentElement.querySelector(".transitions");
let subtransitionNodes = transitionsNode.children;
if (subtransitionNodes.length <= 1) {
// Add subtransitions excepth the one that's already shown.
let visibleTransitionMap =
subtransitionNodes.length == 1
? transitionsNode.querySelector(".map").map
: void 0;
map.children.forEach((edge) => {
if (edge.to != visibleTransitionMap) {
this.currentNode = transitionsNode;
this.addSubtransitions(edge.to);
}
});
} else {
// remove all but the first (currently selected) subtransition
for (let i = subtransitionNodes.length - 1; i > 0; i--) {
transitionsNode.removeChild(subtransitionNodes[i]);
}
handleTransitionViewChange(e) {
this.tooltip.style.left = e.pageX + 'px';
this.tooltip.style.top = e.pageY + 'px';
let map = e.target.map;
if (map) {
this.tooltipContents.innerText = map.description;
}
}
selectMap(map) {
this.dispatchEvent(new SelectionEvent([map]));
}
showMap() {
if (this.currentMap === this._map) return;
this.currentMap = this._map;
this.selectedMapLogEntries = [this._map];
this.showMaps();
}
showMaps() {
clearTimeout(this._showMapsUpdateId);
this._showMapsUpdateId = setTimeout(() => this._showMaps(), 250);
}
_showMaps() {
this.transitionView.style.display = 'none';
DOM.removeAllChildren(this.transitionView);
this._displayedMapsInTree = new Set();
// Limit view to 200 maps for performance reasons.
this.selectedMapLogEntries.slice(0, 200).forEach(
(map) => this.addMapAndParentTransitions(map));
this._displayedMapsInTree = undefined;
this.transitionView.style.display = '';
}
set selectedMapLogEntries(list) {
this._selectedMapLogEntries = list;
this.showMaps();
}
get selectedMapLogEntries() {
return this._selectedMapLogEntries;
}
addMapAndParentTransitions(map) {
if (map === void 0) return;
if (this._displayedMapsInTree.has(map)) return;
this._displayedMapsInTree.add(map);
this.currentNode = this.transitionView;
let parents = map.getParents();
if (parents.length > 0) {
this.addTransitionTo(parents.pop());
parents.reverse().forEach((each) => this.addTransitionTo(each));
}
let mapNode = this.addSubtransitions(map);
// Mark and show the selected map.
mapNode.classList.add('selected');
if (this.selectedMap == map) {
setTimeout(
() => mapNode.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'nearest',
}),
1);
}
}
addSubtransitions(map) {
let mapNode = this.addTransitionTo(map);
// Draw outgoing linear transition line.
let current = map;
while (current.children.length == 1) {
current = current.children[0].to;
this.addTransitionTo(current);
}
return mapNode;
}
addTransitionEdge(map) {
let classes = ['transitionEdge'];
let edge = DOM.div(classes);
edge.style.backgroundColor = typeToColor(map.edge);
let labelNode = DOM.div('transitionLabel');
labelNode.innerText = map.edge.toString();
edge.appendChild(labelNode);
return edge;
}
addTransitionTo(map) {
// transition[ transitions[ transition[...], transition[...], ...]];
this._displayedMapsInTree?.add(map);
let transition = DOM.div('transition');
if (map.isDeprecated()) transition.classList.add('deprecated');
if (map.edge) {
transition.appendChild(this.addTransitionEdge(map));
}
let mapNode = this.addMapNode(map);
transition.appendChild(mapNode);
let subtree = DOM.div('transitions');
transition.appendChild(subtree);
this.currentNode.appendChild(transition);
this.currentNode = subtree;
return mapNode;
}
addMapNode(map) {
let node = DOM.div('map');
if (map.edge) node.style.backgroundColor = typeToColor(map.edge);
node.map = map;
node.addEventListener('click', () => this.selectMap(map));
if (map.children.length > 1) {
node.innerText = map.children.length;
let showSubtree = DOM.div('showSubtransitions');
showSubtree.addEventListener('click', (e) => this.toggleSubtree(e, node));
node.appendChild(showSubtree);
} else if (map.children.length == 0) {
node.innerHTML = '&#x25CF;';
}
this.currentNode.appendChild(node);
return node;
}
toggleSubtree(event, node) {
let map = node.map;
event.target.classList.toggle('opened');
let transitionsNode = node.parentElement.querySelector('.transitions');
let subtransitionNodes = transitionsNode.children;
if (subtransitionNodes.length <= 1) {
// Add subtransitions excepth the one that's already shown.
let visibleTransitionMap = subtransitionNodes.length == 1 ?
transitionsNode.querySelector('.map').map :
void 0;
map.children.forEach((edge) => {
if (edge.to != visibleTransitionMap) {
this.currentNode = transitionsNode;
this.addSubtransitions(edge.to);
}
});
} else {
// remove all but the first (currently selected) subtransition
for (let i = subtransitionNodes.length - 1; i > 0; i--) {
transitionsNode.removeChild(subtransitionNodes[i]);
}
}
);
}
});

View File

@ -2,12 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import { MapLogEntry, Edge } from "./log/map.mjs";
import { IcLogEntry } from "./log/ic.mjs";
import { DeoptLogEntry } from "./log/deopt.mjs";
import { Timeline } from "./timeline.mjs";
import { LogReader, parseString, parseVarArgs } from "../logreader.mjs";
import { Profile } from "../profile.mjs";
import {LogReader, parseString, parseVarArgs} from '../logreader.mjs';
import {Profile} from '../profile.mjs';
import {DeoptLogEntry} from './log/deopt.mjs';
import {IcLogEntry} from './log/ic.mjs';
import {Edge, MapLogEntry} from './log/map.mjs';
import {Timeline} from './timeline.mjs';
// ===========================================================================
@ -43,7 +44,8 @@ export class Processor extends LogReader {
},
'v8-version': {
parsers: [
parseInt, parseInt,
parseInt,
parseInt,
],
processor: this.processV8Version
},
@ -52,12 +54,12 @@ export class Processor extends LogReader {
processor: this.processScriptSource
},
'code-move':
{ parsers: [parseInt, parseInt], processor: this.processCodeMove },
'code-delete': { parsers: [parseInt], processor: this.processCodeDelete },
{parsers: [parseInt, parseInt], processor: this.processCodeMove},
'code-delete': {parsers: [parseInt], processor: this.processCodeDelete},
'sfi-move':
{ parsers: [parseInt, parseInt], processor: this.processFunctionMove },
{parsers: [parseInt, parseInt], processor: this.processFunctionMove},
'map-create':
{ parsers: [parseInt, parseString], processor: this.processMapCreate },
{parsers: [parseInt, parseString], processor: this.processMapCreate},
'map': {
parsers: [
parseString, parseInt, parseString, parseString, parseInt, parseInt,
@ -140,8 +142,8 @@ export class Processor extends LogReader {
}
} catch (e) {
console.error(
'Error occurred during parsing line ' + i +
', trying to continue: ' + e);
'Error occurred during parsing line ' + i +
', trying to continue: ' + e);
}
this.finalize();
}
@ -184,24 +186,25 @@ export class Processor extends LogReader {
let funcAddr = parseInt(maybe_func[0]);
let state = this.parseState(maybe_func[1]);
this._profile.addFuncCode(
type, name, timestamp, start, size, funcAddr, state);
type, name, timestamp, start, size, funcAddr, state);
} else {
this._profile.addCode(type, name, timestamp, start, size);
}
}
processCodeDeopt(timestamp, codeSize, instructionStart, inliningId,
scriptOffset, deoptKind, deoptLocation, deoptReason) {
processCodeDeopt(
timestamp, codeSize, instructionStart, inliningId, scriptOffset,
deoptKind, deoptLocation, deoptReason) {
this._deoptTimeline.push(new DeoptLogEntry(deoptKind, timestamp));
}
processV8Version(majorVersion, minorVersion) {
if (
(majorVersion == this.MAJOR_VERSION && minorVersion <= this.MINOR_VERSION)
|| (majorVersion < this.MAJOR_VERSION)) {
if ((majorVersion == this.MAJOR_VERSION &&
minorVersion <= this.MINOR_VERSION) ||
(majorVersion < this.MAJOR_VERSION)) {
window.alert(
`Unsupported version ${majorVersion}.${minorVersion}. \n` +
`Please use the matching tool for given the V8 version.`);
`Unsupported version ${majorVersion}.${minorVersion}. \n` +
`Please use the matching tool for given the V8 version.`);
}
}
@ -231,16 +234,16 @@ export class Processor extends LogReader {
}
processPropertyIC(
type, pc, time, line, column, old_state, new_state, map, key, modifier,
slow_reason) {
type, pc, time, line, column, old_state, new_state, map, key, modifier,
slow_reason) {
let fnName = this.functionName(pc);
let parts = fnName.split(' ');
let fileName = parts[parts.length-1];
let fileName = parts[parts.length - 1];
let script = this.getScript(fileName);
// TODO: Use SourcePosition here directly
let entry = new IcLogEntry(
type, fnName, time, line, column, key, old_state, new_state, map,
slow_reason, script, modifier);
type, fnName, time, line, column, key, old_state, new_state, map,
slow_reason, script, modifier);
if (script) {
entry.sourcePosition = script.addSourcePosition(line, column, entry);
}
@ -254,9 +257,9 @@ export class Processor extends LogReader {
formatPC(pc, line, column) {
let entry = this._profile.findEntry(pc);
if (!entry) return '<unknown>'
if (entry.type === 'Builtin') {
return entry.name;
}
if (entry.type === 'Builtin') {
return entry.name;
}
let name = entry.func.getName();
let array = this._formatPCRegexp.exec(name);
if (array === null) {
@ -272,7 +275,7 @@ export class Processor extends LogReader {
// Try to handle urls with file positions: https://foo.bar.com/:17:330"
filePositionLine = filePositionLine.split(' ');
let parts = filePositionLine[1].split(':');
if (parts[0].length <= 5) return parts[0] + ':' + parts[1]
if (parts[0].length <= 5) return parts[0] + ':' + parts[1];
return parts[1];
}

View File

@ -1,138 +1,133 @@
// 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 { V8CustomElement, DOM, delay, formatBytes} from "./helper.mjs";
import { SelectionEvent, FocusEvent } from "./events.mjs";
import { MapLogEntry } from "./log/map.mjs";
import { IcLogEntry } from "./log/ic.mjs";
import {FocusEvent, SelectionEvent} from './events.mjs';
import {delay, DOM, formatBytes, V8CustomElement} from './helper.mjs';
import {IcLogEntry} from './log/ic.mjs';
import {MapLogEntry} from './log/map.mjs';
DOM.defineCustomElement(
"source-panel",
(templateText) =>
class SourcePanel extends V8CustomElement {
_selectedSourcePositions = [];
_sourcePositionsToMarkNodes;
_scripts = [];
_script;
constructor() {
super(templateText);
this.scriptDropdown.addEventListener(
'change', e => this._handleSelectScript(e));
}
DOM.defineCustomElement('source-panel',
(templateText) =>
class SourcePanel extends V8CustomElement {
_selectedSourcePositions = [];
_sourcePositionsToMarkNodes;
_scripts = [];
_script;
constructor() {
super(templateText);
this.scriptDropdown.addEventListener(
'change', e => this._handleSelectScript(e));
}
get script() {
return this.$('#script');
}
get script() {
return this.$('#script');
}
get scriptNode() {
return this.$('.scriptNode');
}
get scriptNode() {
return this.$('.scriptNode');
}
set script(script) {
if (this._script === script) return;
this._script = script;
this._renderSourcePanel();
this._updateScriptDropdownSelection();
}
set script(script) {
if (this._script === script) return;
this._script = script;
this._renderSourcePanel();
this._updateScriptDropdownSelection();
}
set selectedSourcePositions(sourcePositions) {
this._selectedSourcePositions = sourcePositions;
// TODO: highlight multiple scripts
this.script = sourcePositions[0]?.script;
this._focusSelectedMarkers();
}
set selectedSourcePositions(sourcePositions) {
this._selectedSourcePositions = sourcePositions;
// TODO: highlight multiple scripts
this.script = sourcePositions[0]?.script;
this._focusSelectedMarkers();
}
set data(scripts) {
this._scripts = scripts;
this._initializeScriptDropdown();
}
set data(scripts) {
this._scripts = scripts;
this._initializeScriptDropdown();
}
get scriptDropdown() {
return this.$("#script-dropdown");
}
get scriptDropdown() {
return this.$('#script-dropdown');
}
_initializeScriptDropdown() {
this._scripts.sort((a, b) => a.name.localeCompare(b.name));
let select = this.scriptDropdown;
select.options.length = 0;
for (const script of this._scripts) {
const option = document.createElement("option");
const size = formatBytes(script.source.length);
option.text = `${script.name} (id=${script.id} size=${size})`;
option.script = script;
select.add(option);
}
}
_updateScriptDropdownSelection() {
this.scriptDropdown.selectedIndex =
this._script ? this._scripts.indexOf(this._script) : -1;
}
_initializeScriptDropdown() {
this._scripts.sort((a, b) => a.name.localeCompare(b.name));
let select = this.scriptDropdown;
select.options.length = 0;
for (const script of this._scripts) {
const option = document.createElement('option');
const size = formatBytes(script.source.length);
option.text = `${script.name} (id=${script.id} size=${size})`;
option.script = script;
select.add(option);
}
}
_updateScriptDropdownSelection() {
this.scriptDropdown.selectedIndex =
this._script ? this._scripts.indexOf(this._script) : -1;
}
async _renderSourcePanel() {
let scriptNode;
if (this._script) {
await delay(1);
const builder = new LineBuilder(
this, this._script, this._selectedSourcePositions);
scriptNode = builder.createScriptNode();
this._sourcePositionsToMarkNodes = builder.sourcePositionToMarkers;
} else {
scriptNode = document.createElement("pre");
this._selectedMarkNodes = undefined;
}
const oldScriptNode = this.script.childNodes[1];
this.script.replaceChild(scriptNode, oldScriptNode);
}
async _renderSourcePanel() {
let scriptNode;
if (this._script) {
await delay(1);
const builder =
new LineBuilder(this, this._script, this._selectedSourcePositions);
scriptNode = builder.createScriptNode();
this._sourcePositionsToMarkNodes = builder.sourcePositionToMarkers;
} else {
scriptNode = document.createElement('pre');
this._selectedMarkNodes = undefined;
}
const oldScriptNode = this.script.childNodes[1];
this.script.replaceChild(scriptNode, oldScriptNode);
}
async _focusSelectedMarkers() {
await delay(100);
// Remove all marked nodes.
for (let markNode of this._sourcePositionsToMarkNodes.values()) {
markNode.className = "";
}
for (let sourcePosition of this._selectedSourcePositions) {
this._sourcePositionsToMarkNodes
.get(sourcePosition).className = "marked";
}
const sourcePosition = this._selectedSourcePositions[0];
if (!sourcePosition) return;
const markNode = this._sourcePositionsToMarkNodes.get(sourcePosition);
markNode.scrollIntoView({
behavior: "smooth", block: "nearest", inline: "center"
});
}
async _focusSelectedMarkers() {
await delay(100);
// Remove all marked nodes.
for (let markNode of this._sourcePositionsToMarkNodes.values()) {
markNode.className = '';
}
for (let sourcePosition of this._selectedSourcePositions) {
this._sourcePositionsToMarkNodes.get(sourcePosition).className = 'marked';
}
const sourcePosition = this._selectedSourcePositions[0];
if (!sourcePosition) return;
const markNode = this._sourcePositionsToMarkNodes.get(sourcePosition);
markNode.scrollIntoView(
{behavior: 'smooth', block: 'nearest', inline: 'center'});
}
_handleSelectScript(e) {
const option =
this.scriptDropdown.options[this.scriptDropdown.selectedIndex];
this.script = option.script;
this.selectLogEntries(this._script.entries());
}
_handleSelectScript(e) {
const option =
this.scriptDropdown.options[this.scriptDropdown.selectedIndex];
this.script = option.script;
this.selectLogEntries(this._script.entries());
}
handleSourcePositionClick(e) {
this.selectLogEntries(e.target.sourcePosition.entries)
}
handleSourcePositionClick(e) {
this.selectLogEntries(e.target.sourcePosition.entries)
}
selectLogEntries(logEntries) {
let icLogEntries = [];
let mapLogEntries = [];
for (const entry of logEntries) {
if (entry instanceof MapLogEntry) {
mapLogEntries.push(entry);
} else if (entry instanceof IcLogEntry) {
icLogEntries.push(entry);
}
}
if (icLogEntries.length > 0 ) {
this.dispatchEvent(new SelectionEvent(icLogEntries));
}
if (mapLogEntries.length > 0) {
this.dispatchEvent(new SelectionEvent(mapLogEntries));
}
selectLogEntries(logEntries) {
let icLogEntries = [];
let mapLogEntries = [];
for (const entry of logEntries) {
if (entry instanceof MapLogEntry) {
mapLogEntries.push(entry);
} else if (entry instanceof IcLogEntry) {
icLogEntries.push(entry);
}
}
);
if (icLogEntries.length > 0) {
this.dispatchEvent(new SelectionEvent(icLogEntries));
}
if (mapLogEntries.length > 0) {
this.dispatchEvent(new SelectionEvent(mapLogEntries));
}
}
});
class SourcePositionIterator {
_entries;
@ -141,16 +136,16 @@ class SourcePositionIterator {
this._entries = sourcePositions;
}
*forLine(lineIndex) {
* forLine(lineIndex) {
this._findStart(lineIndex);
while(!this._done() && this._current().line === lineIndex) {
while (!this._done() && this._current().line === lineIndex) {
yield this._current();
this._next();
}
}
_findStart(lineIndex) {
while(!this._done() && this._current().line < lineIndex) {
while (!this._done() && this._current().line < lineIndex) {
this._next();
}
}
@ -168,11 +163,11 @@ class SourcePositionIterator {
}
}
function * lineIterator(source) {
function* lineIterator(source) {
let current = 0;
let line = 1;
while(current < source.length) {
const next = source.indexOf("\n", current);
while (current < source.length) {
const next = source.indexOf('\n', current);
if (next === -1) break;
yield [line, source.substring(current, next)];
line++;
@ -181,7 +176,6 @@ function * lineIterator(source) {
if (current < source.length) yield [line, source.substring(current)];
}
class LineBuilder {
_script;
_clickHandler;
@ -194,12 +188,12 @@ class LineBuilder {
this._selection = new Set(highlightPositions);
this._clickHandler = panel.handleSourcePositionClick.bind(panel);
// TODO: sort on script finalization.
script.sourcePositions.sort((a, b) => {
if (a.line === b.line) return a.column - b.column;
return a.line - b.line;
})
this._sourcePositions
= new SourcePositionIterator(script.sourcePositions);
script.sourcePositions
.sort((a, b) => {
if (a.line === b.line) return a.column - b.column;
return a.line - b.line;
}) this._sourcePositions =
new SourcePositionIterator(script.sourcePositions);
}
get sourcePositionToMarkers() {
@ -207,7 +201,7 @@ class LineBuilder {
}
createScriptNode() {
const scriptNode = document.createElement("pre");
const scriptNode = document.createElement('pre');
scriptNode.classList.add('scriptNode');
for (let [lineIndex, line] of lineIterator(this._script.source)) {
scriptNode.appendChild(this._createLineNode(lineIndex, line));
@ -216,13 +210,12 @@ class LineBuilder {
}
_createLineNode(lineIndex, line) {
const lineNode = document.createElement("span");
let columnIndex = 0;
const lineNode = document.createElement('span');
let columnIndex = 0;
for (const sourcePosition of this._sourcePositions.forLine(lineIndex)) {
const nextColumnIndex = sourcePosition.column - 1;
lineNode.appendChild(
document.createTextNode(
line.substring(columnIndex, nextColumnIndex)));
lineNode.appendChild(document.createTextNode(
line.substring(columnIndex, nextColumnIndex)));
columnIndex = nextColumnIndex;
lineNode.appendChild(
@ -230,12 +223,12 @@ class LineBuilder {
columnIndex++;
}
lineNode.appendChild(
document.createTextNode(line.substring(columnIndex) + "\n"));
document.createTextNode(line.substring(columnIndex) + '\n'));
return lineNode;
}
_createMarkerNode(text, sourcePosition) {
const marker = document.createElement("mark");
const marker = document.createElement('mark');
this._sourcePositionToMarkers.set(sourcePosition, marker);
marker.textContent = text;
marker.sourcePosition = sourcePosition;

View File

@ -1,14 +1,12 @@
// 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 { V8CustomElement, DOM} from "./helper.mjs";
import { SelectionEvent } from "./events.mjs";
import { delay, LazyTable } from "./helper.mjs";
import {SelectionEvent} from './events.mjs';
import {DOM, V8CustomElement} from './helper.mjs';
import {delay, LazyTable} from './helper.mjs';
DOM.defineCustomElement(
"stats-panel",
(templateText) =>
class StatsPanel extends V8CustomElement {
'stats-panel', (templateText) => class StatsPanel extends V8CustomElement {
_timeline;
_transitions;
_selectedLogEntries;
@ -17,7 +15,7 @@ DOM.defineCustomElement(
}
get stats() {
return this.$("#stats");
return this.$('#stats');
}
set timeline(timeline) {
@ -51,40 +49,40 @@ DOM.defineCustomElement(
}
updateGeneralStats() {
console.assert(this._timeline !== undefined, "Timeline not set yet!");
console.assert(this._timeline !== undefined, 'Timeline not set yet!');
let pairs = [
["Transitions", "primary", (e) => e.edge && e.edge.isTransition()],
["Fast to Slow", "violet", (e) => e.edge && e.edge.isFastToSlow()],
["Slow to Fast", "orange", (e) => e.edge && e.edge.isSlowToFast()],
["Initial Map", "yellow", (e) => e.edge && e.edge.isInitial()],
['Transitions', 'primary', (e) => e.edge && e.edge.isTransition()],
['Fast to Slow', 'violet', (e) => e.edge && e.edge.isFastToSlow()],
['Slow to Fast', 'orange', (e) => e.edge && e.edge.isSlowToFast()],
['Initial Map', 'yellow', (e) => e.edge && e.edge.isInitial()],
[
"Replace Descriptors",
"red",
'Replace Descriptors',
'red',
(e) => e.edge && e.edge.isReplaceDescriptors(),
],
[
"Copy as Prototype",
"red",
'Copy as Prototype',
'red',
(e) => e.edge && e.edge.isCopyAsPrototype(),
],
[
"Optimize as Prototype",
'Optimize as Prototype',
null,
(e) => e.edge && e.edge.isOptimizeAsPrototype(),
],
["Deprecated", null, (e) => e.isDeprecated()],
["Bootstrapped", "green", (e) => e.isBootstrapped()],
["Total", null, (e) => true],
['Deprecated', null, (e) => e.isDeprecated()],
['Bootstrapped', 'green', (e) => e.isBootstrapped()],
['Total', null, (e) => true],
];
let tbody = document.createElement("tbody");
let tbody = document.createElement('tbody');
let total = this._selectedLogEntries.length;
pairs.forEach(([name, color, filter]) => {
let row = DOM.tr();
if (color !== null) {
row.appendChild(DOM.td(DOM.div(["colorbox", color])));
row.appendChild(DOM.td(DOM.div(['colorbox', color])));
} else {
row.appendChild(DOM.td(""));
row.appendChild(DOM.td(''));
}
row.classList.add('clickable');
row.onclick = (e) => {
@ -99,10 +97,10 @@ DOM.defineCustomElement(
let count = this.count(filter);
row.appendChild(DOM.td(count));
let percent = Math.round((count / total) * 1000) / 10;
row.appendChild(DOM.td(percent.toFixed(1) + "%"));
row.appendChild(DOM.td(percent.toFixed(1) + '%'));
tbody.appendChild(row);
});
this.$("#typeTable").replaceChild(tbody, this.$("#typeTable tbody"));
this.$('#typeTable').replaceChild(tbody, this.$('#typeTable tbody'));
}
count(filter) {
@ -116,21 +114,17 @@ DOM.defineCustomElement(
updateNamedTransitionsStats() {
let rowData = Array.from(this._transitions.entries());
rowData.sort((a, b) => b[1].length - a[1].length);
new LazyTable(this.$("#nameTable"), rowData, ([name, maps]) => {
let row = DOM.tr();
row.maps = maps;
row.classList.add('clickable');
row.addEventListener("click", (e) =>
this.dispatchEvent(
new SelectionEvent(
e.target.parentNode.maps.map((map) => map.to)
)
)
);
row.appendChild(DOM.td(maps.length));
row.appendChild(DOM.td(name));
return row;
});
new LazyTable(this.$('#nameTable'), rowData, ([name, maps]) => {
let row = DOM.tr();
row.maps = maps;
row.classList.add('clickable');
row.addEventListener(
'click',
(e) => this.dispatchEvent(new SelectionEvent(
e.target.parentNode.maps.map((map) => map.to))));
row.appendChild(DOM.td(maps.length));
row.appendChild(DOM.td(name));
return row;
});
}
}
);
});

View File

@ -2,53 +2,55 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import { DOM, V8CustomElement } from './helper.mjs';
import { SynchronizeSelectionEvent } from './events.mjs';
import './timeline/timeline-track.mjs';
DOM.defineCustomElement('timeline-panel', (templateText) =>
class TimelinePanel extends V8CustomElement {
constructor() {
super(templateText);
this.addEventListener(
'scrolltrack', e => this.handleTrackScroll(e));
this.addEventListener(
SynchronizeSelectionEvent.name,
e => this.handleSelectionSyncronization(e));
}
import {SynchronizeSelectionEvent} from './events.mjs';
import {DOM, V8CustomElement} from './helper.mjs';
set nofChunks(count) {
for (const track of this.timelineTracks) {
track.nofChunks = count;
DOM.defineCustomElement(
'timeline-panel',
(templateText) => class TimelinePanel extends V8CustomElement {
constructor() {
super(templateText);
this.addEventListener('scrolltrack', e => this.handleTrackScroll(e));
this.addEventListener(
SynchronizeSelectionEvent.name,
e => this.handleSelectionSyncronization(e));
}
}
get nofChunks() {
return this.timelineTracks[0].nofChunks;
}
get timelineTracks() {
return this.$("slot").assignedNodes().filter(
node => node.nodeType === Node.ELEMENT_NODE);
}
handleTrackScroll(event) {
//TODO(zcankara) add forEachTrack helper method
for (const track of this.timelineTracks) {
track.scrollLeft = event.detail;
set nofChunks(count) {
for (const track of this.timelineTracks) {
track.nofChunks = count;
}
}
}
handleSelectionSyncronization(event) {
this.timeSelection = {start:event.start, end:event.end};
}
get nofChunks() {
return this.timelineTracks[0].nofChunks;
}
set timeSelection(timeSelection) {
if (timeSelection.start > timeSelection.end) {
throw new Error("Invalid time range");
get timelineTracks() {
return this.$('slot').assignedNodes().filter(
node => node.nodeType === Node.ELEMENT_NODE);
}
for (const track of this.timelineTracks) {
track.timeSelection = timeSelection;;
handleTrackScroll(event) {
// TODO(zcankara) add forEachTrack helper method
for (const track of this.timelineTracks) {
track.scrollLeft = event.detail;
}
}
}
});
handleSelectionSyncronization(event) {
this.timeSelection = {start: event.start, end: event.end};
}
set timeSelection(timeSelection) {
if (timeSelection.start > timeSelection.end) {
throw new Error('Invalid time range');
}
for (const track of this.timelineTracks) {
track.timeSelection = timeSelection;
;
}
}
});

View File

@ -35,17 +35,16 @@ class Timeline {
}
selectTimeRange(start, end) {
this._selection = this.filter(
e => e.time >= start && e.time <= end);
this._selection = this.filter(e => e.time >= start && e.time <= end);
}
getChunks(windowSizeMs) {
//TODO(zcankara) Fill this one
// TODO(zcankara) Fill this one
return this.chunkSizes(windowSizeMs);
}
get values() {
//TODO(zcankara) Not to break something delete later
// TODO(zcankara) Not to break something delete later
return this._values;
}
@ -253,7 +252,7 @@ class Chunk {
if (event_fn === void 0) {
event_fn = each => each;
}
let breakdown = { __proto__: null };
let breakdown = {__proto__: null};
this.items.forEach(each => {
const type = event_fn(each);
const v = breakdown[type];
@ -265,7 +264,6 @@ class Chunk {
filter() {
return this.items.filter(map => !map.parent() || !this.has(map.parent()));
}
}
export { Timeline, Chunk };
export {Timeline, Chunk};

View File

@ -2,12 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import { DOM, V8CustomElement, CSSColor, delay } from '../helper.mjs';
import { kChunkWidth, kChunkHeight } from "../log/map.mjs";
import {
SelectionEvent, FocusEvent, SelectTimeEvent,
SynchronizeSelectionEvent
} from '../events.mjs';
import {FocusEvent, SelectionEvent, SelectTimeEvent, SynchronizeSelectionEvent} from '../events.mjs';
import {CSSColor, delay, DOM, V8CustomElement} from '../helper.mjs';
import {kChunkHeight, kChunkWidth} from '../log/map.mjs';
const kColors = [
CSSColor.green,
@ -21,500 +18,499 @@ const kColors = [
CSSColor.secondaryColor,
];
DOM.defineCustomElement('./timeline/timeline-track', (templateText) =>
class TimelineTrack extends V8CustomElement {
// TODO turn into static field once Safari supports it.
static get SELECTION_OFFSET() { return 10 };
_timeline;
_nofChunks = 400;
_chunks;
_selectedEntry;
_timeToPixel;
_timeSelection = { start: -1, end: Infinity };
_timeStartOffset;
_selectionOriginTime;
_typeToColor;
constructor() {
super(templateText);
this.timeline.addEventListener("scroll",
e => this.handleTimelineScroll(e));
this.timeline.addEventListener("mousedown",
e => this.handleTimeSelectionMouseDown(e));
this.timeline.addEventListener("mouseup",
e => this.handleTimeSelectionMouseUp(e));
this.timeline.addEventListener("mousemove",
e => this.handleTimeSelectionMouseMove(e));
this.backgroundCanvas = document.createElement('canvas');
this.isLocked = false;
}
DOM.defineCustomElement('./timeline/timeline-track',
(templateText) =>
class TimelineTrack extends V8CustomElement {
// TODO turn into static field once Safari supports it.
static get SELECTION_OFFSET() {
return 10
};
_timeline;
_nofChunks = 400;
_chunks;
_selectedEntry;
_timeToPixel;
_timeSelection = {start: -1, end: Infinity};
_timeStartOffset;
_selectionOriginTime;
_typeToColor;
constructor() {
super(templateText);
this.timeline.addEventListener('scroll', e => this.handleTimelineScroll(e));
this.timeline.addEventListener(
'mousedown', e => this.handleTimeSelectionMouseDown(e));
this.timeline.addEventListener(
'mouseup', e => this.handleTimeSelectionMouseUp(e));
this.timeline.addEventListener(
'mousemove', e => this.handleTimeSelectionMouseMove(e));
this.backgroundCanvas = document.createElement('canvas');
this.isLocked = false;
}
handleTimeSelectionMouseDown(e) {
let xPosition = e.clientX
// Update origin time in case we click on a handle.
if (this.isOnLeftHandle(xPosition)) {
xPosition = this.rightHandlePosX;
} else if (this.isOnRightHandle(xPosition)) {
xPosition = this.leftHandlePosX;
}
this._selectionOriginTime = this.positionToTime(xPosition);
handleTimeSelectionMouseDown(e) {
let xPosition = e.clientX
// Update origin time in case we click on a handle.
if (this.isOnLeftHandle(xPosition)) {
xPosition = this.rightHandlePosX;
}
isOnLeftHandle(posX) {
return (Math.abs(this.leftHandlePosX - posX)
<= TimelineTrack.SELECTION_OFFSET);
else if (this.isOnRightHandle(xPosition)) {
xPosition = this.leftHandlePosX;
}
this._selectionOriginTime = this.positionToTime(xPosition);
}
isOnRightHandle(posX) {
return (Math.abs(this.rightHandlePosX - posX)
<= TimelineTrack.SELECTION_OFFSET);
}
isOnLeftHandle(posX) {
return (
Math.abs(this.leftHandlePosX - posX) <= TimelineTrack.SELECTION_OFFSET);
}
handleTimeSelectionMouseMove(e) {
if (!this._isSelecting) return;
const currentTime = this.positionToTime(e.clientX);
this.dispatchEvent(new SynchronizeSelectionEvent(
isOnRightHandle(posX) {
return (
Math.abs(this.rightHandlePosX - posX) <=
TimelineTrack.SELECTION_OFFSET);
}
handleTimeSelectionMouseMove(e) {
if (!this._isSelecting) return;
const currentTime = this.positionToTime(e.clientX);
this.dispatchEvent(new SynchronizeSelectionEvent(
Math.min(this._selectionOriginTime, currentTime),
Math.max(this._selectionOriginTime, currentTime)));
}
}
handleTimeSelectionMouseUp(e) {
this._selectionOriginTime = -1;
const delta = this._timeSelection.end - this._timeSelection.start;
if (delta <= 1 || isNaN(delta)) return;
this.dispatchEvent(new SelectTimeEvent(this._timeSelection.start,
this._timeSelection.end));
}
handleTimeSelectionMouseUp(e) {
this._selectionOriginTime = -1;
const delta = this._timeSelection.end - this._timeSelection.start;
if (delta <= 1 || isNaN(delta)) return;
this.dispatchEvent(new SelectTimeEvent(
this._timeSelection.start, this._timeSelection.end));
}
set timeSelection(selection) {
this._timeSelection.start = selection.start;
this._timeSelection.end= selection.end;
this.updateSelection();
}
set timeSelection(selection) {
this._timeSelection.start = selection.start;
this._timeSelection.end = selection.end;
this.updateSelection();
}
get _isSelecting() {
return this._selectionOriginTime >= 0;
}
get _isSelecting() {
return this._selectionOriginTime >= 0;
}
updateSelection() {
const startPosition = this.timeToPosition(this._timeSelection.start);
const endPosition = this.timeToPosition(this._timeSelection.end);
const delta = endPosition - startPosition;
this.leftHandle.style.left = startPosition + "px";
this.selection.style.left = startPosition + "px";
this.rightHandle.style.left = endPosition + "px";
this.selection.style.width = delta + "px";
}
updateSelection() {
const startPosition = this.timeToPosition(this._timeSelection.start);
const endPosition = this.timeToPosition(this._timeSelection.end);
const delta = endPosition - startPosition;
this.leftHandle.style.left = startPosition + 'px';
this.selection.style.left = startPosition + 'px';
this.rightHandle.style.left = endPosition + 'px';
this.selection.style.width = delta + 'px';
}
get leftHandlePosX() {
return this.leftHandle.getBoundingClientRect().x;
}
get leftHandlePosX() {
return this.leftHandle.getBoundingClientRect().x;
}
get rightHandlePosX() {
return this.rightHandle.getBoundingClientRect().x;
}
get rightHandlePosX() {
return this.rightHandle.getBoundingClientRect().x;
}
// Maps the clicked x position to the x position on timeline canvas
positionOnTimeline(posX) {
let rect = this.timeline.getBoundingClientRect();
let posClickedX = posX - rect.left + this.timeline.scrollLeft;
return posClickedX;
}
// Maps the clicked x position to the x position on timeline canvas
positionOnTimeline(posX) {
let rect = this.timeline.getBoundingClientRect();
let posClickedX = posX - rect.left + this.timeline.scrollLeft;
return posClickedX;
}
positionToTime(posX) {
let posTimelineX = this.positionOnTimeline(posX) + this._timeStartOffset;
return posTimelineX / this._timeToPixel;
}
positionToTime(posX) {
let posTimelineX = this.positionOnTimeline(posX) + this._timeStartOffset;
return posTimelineX / this._timeToPixel;
}
timeToPosition(time) {
let posX = time * this._timeToPixel;
posX -= this._timeStartOffset
return posX;
}
timeToPosition(time) {
let posX = time * this._timeToPixel;
posX -= this._timeStartOffset
return posX;
}
get leftHandle() {
return this.$('.leftHandle');
}
get leftHandle() {
return this.$('.leftHandle');
}
get rightHandle() {
return this.$('.rightHandle');
}
get rightHandle() {
return this.$('.rightHandle');
}
get selection() {
return this.$('.selection');
}
get selection() {
return this.$('.selection');
}
get timelineCanvas() {
return this.$('#timelineCanvas');
}
get timelineCanvas() {
return this.$('#timelineCanvas');
}
get timelineChunks() {
return this.$('#timelineChunks');
}
get timelineChunks() {
return this.$('#timelineChunks');
}
get timeline() {
return this.$('#timeline');
}
get timeline() {
return this.$('#timeline');
}
get timelineLegend() {
return this.$('#legend');
}
get timelineLegend() {
return this.$('#legend');
}
get timelineLegendContent() {
return this.$('#legendContent');
}
get timelineLegendContent() {
return this.$('#legendContent');
}
set data(value) {
this._timeline = value;
this._resetTypeToColorCache();
this.updateChunks();
this.updateTimeline();
this.renderLegend();
}
set data(value) {
this._timeline = value;
this._resetTypeToColorCache();
this.updateChunks();
this.updateTimeline();
this.renderLegend();
}
_resetTypeToColorCache() {
this._typeToColor = new Map();
let lastIndex = 0;
for (const type of this.data.uniqueTypes.keys()) {
this._typeToColor.set(type, kColors[lastIndex++]);
}
}
get data() {
return this._timeline;
}
set nofChunks(count) {
this._nofChunks = count;
this.updateChunks();
this.updateTimeline();
}
get nofChunks() {
return this._nofChunks;
}
updateChunks() {
this._chunks = this.data.chunks(this.nofChunks);
}
get chunks() {
return this._chunks;
}
set selectedEntry(value) {
this._selectedEntry = value;
if (value.edge) this.redraw();
}
get selectedEntry() {
return this._selectedEntry;
}
set scrollLeft(offset) {
this.timeline.scrollLeft = offset;
}
typeToColor(type) {
return this._typeToColor.get(type);
}
renderLegend() {
let timelineLegend = this.timelineLegend;
let timelineLegendContent = this.timelineLegendContent;
DOM.removeAllChildren(timelineLegendContent);
this._timeline.uniqueTypes.forEach((entries, type) => {
let row = DOM.tr("clickable");
row.entries = entries;
row.addEventListener('dblclick', e => this.handleEntryTypeDblClick(e));
let color = this.typeToColor(type);
if (color !== null) {
let div = DOM.div("colorbox");
div.style.backgroundColor = color;
row.appendChild(DOM.td(div));
} else {
row.appendChild(DOM.td());
}
let td = DOM.td(type);
row.appendChild(td);
row.appendChild(DOM.td(entries.length));
let percent = (entries.length / this.data.all.length) * 100;
row.appendChild(DOM.td(percent.toFixed(1) + "%"));
timelineLegendContent.appendChild(row);
});
// Add Total row.
let row = DOM.tr();
row.appendChild(DOM.td(""));
row.appendChild(DOM.td("All"));
row.appendChild(DOM.td(this.data.all.length));
row.appendChild(DOM.td("100%"));
timelineLegendContent.appendChild(row);
timelineLegend.appendChild(timelineLegendContent);
}
handleEntryTypeDblClick(e) {
this.dispatchEvent(new SelectionEvent(e.target.parentNode.entries));
}
timelineIndicatorMove(offset) {
this.timeline.scrollLeft += offset;
}
handleTimelineScroll(e) {
let horizontal = e.currentTarget.scrollLeft;
this.dispatchEvent(new CustomEvent(
'scrolltrack', {
bubbles: true, composed: true,
detail: horizontal
}));
}
async setChunkBackgrounds(backgroundTodo) {
const kMaxDuration = 50;
let lastTime = 0;
for (let [chunk, node] of backgroundTodo) {
const current = performance.now();
if (current - lastTime > kMaxDuration) {
await delay(25);
lastTime = current;
}
this.setChunkBackground(chunk, node);
}
}
setChunkBackground(chunk, node) {
// Render the types of transitions as bar charts
const kHeight = chunk.height;
const kWidth = 1;
this.backgroundCanvas.width = kWidth;
this.backgroundCanvas.height = kHeight;
let ctx = this.backgroundCanvas.getContext('2d');
ctx.clearRect(0, 0, kWidth, kHeight);
let y = 0;
let total = chunk.size();
let type, count;
if (true) {
chunk.getBreakdown(map => map.type).forEach(([type, count]) => {
ctx.fillStyle = this.typeToColor(type);
let height = count / total * kHeight;
ctx.fillRect(0, y, kWidth, y + height);
y += height;
});
} else {
chunk.items.forEach(map => {
ctx.fillStyle = this.typeToColor(map.type);
let y = chunk.yOffset(map);
ctx.fillRect(0, y, kWidth, y + 1);
});
}
let imageData = this.backgroundCanvas.toDataURL('image/webp', 0.2);
node.style.backgroundImage = 'url(' + imageData + ')';
}
updateTimeline() {
let chunksNode = this.timelineChunks;
DOM.removeAllChildren(chunksNode);
let chunks = this.chunks;
let max = chunks.max(each => each.size());
let start = this.data.startTime;
let end = this.data.endTime;
let duration = end - start;
this._timeToPixel = chunks.length * kChunkWidth / duration;
this._timeStartOffset = start * this._timeToPixel;
let addTimestamp = (time, name) => {
let timeNode = DOM.div('timestamp');
timeNode.innerText = name;
timeNode.style.left = ((time - start) * this._timeToPixel) + 'px';
chunksNode.appendChild(timeNode);
};
let backgroundTodo = [];
for (let i = 0; i < chunks.length; i++) {
let chunk = chunks[i];
let height = (chunk.size() / max * kChunkHeight);
chunk.height = height;
if (chunk.isEmpty()) continue;
let node = DOM.div();
node.className = 'chunk';
node.style.left =
((chunks[i].start - start) * this._timeToPixel) + 'px';
node.style.height = height + 'px';
node.chunk = chunk;
node.addEventListener('mousemove', e => this.handleChunkMouseMove(e));
node.addEventListener('click', e => this.handleChunkClick(e));
node.addEventListener('dblclick', e => this.handleChunkDoubleClick(e));
backgroundTodo.push([chunk, node])
chunksNode.appendChild(node);
}
this.setChunkBackgrounds(backgroundTodo);
// Put a time marker roughly every 20 chunks.
let expected = duration / chunks.length * 20;
let interval = (10 ** Math.floor(Math.log10(expected)));
let correction = Math.log10(expected / interval);
correction = (correction < 0.33) ? 1 : (correction < 0.75) ? 2.5 : 5;
interval *= correction;
let time = start;
while (time < end) {
addTimestamp(time, ((time - start) / 1000) + ' ms');
time += interval;
}
this.redraw();
}
handleChunkMouseMove(event) {
if (this.isLocked) return false;
if (this._isSelecting) return false;
let chunk = event.target.chunk;
if (!chunk) return;
// topmost map (at chunk.height) == map #0.
let relativeIndex =
Math.round(event.layerY / event.target.offsetHeight * chunk.size());
let map = chunk.at(relativeIndex);
this.dispatchEvent(new FocusEvent(map));
}
handleChunkClick(event) {
this.isLocked = !this.isLocked;
}
handleChunkDoubleClick(event) {
let chunk = event.target.chunk;
if (!chunk) return;
this.dispatchEvent(new SelectTimeEvent(chunk.start, chunk.end));
}
redraw() {
let canvas = this.timelineCanvas;
canvas.width = (this.chunks.length + 1) * kChunkWidth;
canvas.height = kChunkHeight;
let ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, kChunkHeight);
if (!this.selectedEntry || !this.selectedEntry.edge) return;
this.drawEdges(ctx);
}
setMapStyle(map, ctx) {
ctx.fillStyle = map.edge && map.edge.from ?
CSSColor.onBackgroundColor : CSSColor.onPrimaryColor;
}
setEdgeStyle(edge, ctx) {
let color = this.typeToColor(edge.type);
ctx.strokeStyle = color;
ctx.fillStyle = color;
}
markMap(ctx, map) {
let [x, y] = map.position(this.chunks);
ctx.beginPath();
this.setMapStyle(map, ctx);
ctx.arc(x, y, 3, 0, 2 * Math.PI);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = CSSColor.onBackgroundColor;
ctx.arc(x, y, 2, 0, 2 * Math.PI);
ctx.fill();
}
markSelectedMap(ctx, map) {
let [x, y] = map.position(this.chunks);
ctx.beginPath();
this.setMapStyle(map, ctx);
ctx.arc(x, y, 6, 0, 2 * Math.PI);
ctx.strokeStyle = CSSColor.onBackgroundColor;
ctx.stroke();
}
drawEdges(ctx) {
// Draw the trace of maps in reverse order to make sure the outgoing
// transitions of previous maps aren't drawn over.
const kMaxOutgoingEdges = 100;
let nofEdges = 0;
let stack = [];
let current = this.selectedEntry;
while (current && nofEdges < kMaxOutgoingEdges) {
nofEdges += current.children.length;
stack.push(current);
current = current.parent();
}
ctx.save();
this.drawOutgoingEdges(ctx, this.selectedEntry, 3);
ctx.restore();
let labelOffset = 15;
let xPrev = 0;
while (current = stack.pop()) {
if (current.edge) {
this.setEdgeStyle(current.edge, ctx);
let [xTo, yTo] = this.drawEdge(ctx, current.edge, true, labelOffset);
if (xTo == xPrev) {
labelOffset += 8;
} else {
labelOffset = 15
}
xPrev = xTo;
}
this.markMap(ctx, current);
current = current.parent();
ctx.save();
// this.drawOutgoingEdges(ctx, current, 1);
ctx.restore();
}
// Mark selected map
this.markSelectedMap(ctx, this.selectedEntry);
}
drawEdge(ctx, edge, showLabel = true, labelOffset = 20) {
if (!edge.from || !edge.to) return [-1, -1];
let [xFrom, yFrom] = edge.from.position(this.chunks);
let [xTo, yTo] = edge.to.position(this.chunks);
let sameChunk = xTo == xFrom;
if (sameChunk) labelOffset += 8;
ctx.beginPath();
ctx.moveTo(xFrom, yFrom);
let offsetX = 20;
let offsetY = 20;
let midX = xFrom + (xTo - xFrom) / 2;
let midY = (yFrom + yTo) / 2 - 100;
if (!sameChunk) {
ctx.quadraticCurveTo(midX, midY, xTo, yTo);
} else {
ctx.lineTo(xTo, yTo);
}
if (!showLabel) {
ctx.stroke();
} else {
let centerX, centerY;
if (!sameChunk) {
centerX = (xFrom / 2 + midX + xTo / 2) / 2;
centerY = (yFrom / 2 + midY + yTo / 2) / 2;
} else {
centerX = xTo;
centerY = yTo;
}
ctx.moveTo(centerX, centerY);
ctx.lineTo(centerX + offsetX, centerY - labelOffset);
ctx.stroke();
ctx.textAlign = 'left';
ctx.fillStyle = this.typeToColor(edge.type);
ctx.fillText(
edge.toString(), centerX + offsetX + 2, centerY - labelOffset);
}
return [xTo, yTo];
}
drawOutgoingEdges(ctx, map, max = 10, depth = 0) {
if (!map) return;
if (depth >= max) return;
ctx.globalAlpha = 0.5 - depth * (0.3 / max);
ctx.strokeStyle = CSSColor.timelineBackgroundColor;
const limit = Math.min(map.children.length, 100)
for (let i = 0; i < limit; i++) {
let edge = map.children[i];
this.drawEdge(ctx, edge, true);
this.drawOutgoingEdges(ctx, edge.to, max, depth + 1);
}
_resetTypeToColorCache() {
this._typeToColor = new Map();
let lastIndex = 0;
for (const type of this.data.uniqueTypes.keys()) {
this._typeToColor.set(type, kColors[lastIndex++]);
}
}
);
get data() {
return this._timeline;
}
set nofChunks(count) {
this._nofChunks = count;
this.updateChunks();
this.updateTimeline();
}
get nofChunks() {
return this._nofChunks;
}
updateChunks() {
this._chunks = this.data.chunks(this.nofChunks);
}
get chunks() {
return this._chunks;
}
set selectedEntry(value) {
this._selectedEntry = value;
if (value.edge) this.redraw();
}
get selectedEntry() {
return this._selectedEntry;
}
set scrollLeft(offset) {
this.timeline.scrollLeft = offset;
}
typeToColor(type) {
return this._typeToColor.get(type);
}
renderLegend() {
let timelineLegend = this.timelineLegend;
let timelineLegendContent = this.timelineLegendContent;
DOM.removeAllChildren(timelineLegendContent);
this._timeline.uniqueTypes.forEach((entries, type) => {
let row = DOM.tr('clickable');
row.entries = entries;
row.addEventListener('dblclick', e => this.handleEntryTypeDblClick(e));
let color = this.typeToColor(type);
if (color !== null) {
let div = DOM.div('colorbox');
div.style.backgroundColor = color;
row.appendChild(DOM.td(div));
} else {
row.appendChild(DOM.td());
}
let td = DOM.td(type);
row.appendChild(td);
row.appendChild(DOM.td(entries.length));
let percent = (entries.length / this.data.all.length) * 100;
row.appendChild(DOM.td(percent.toFixed(1) + '%'));
timelineLegendContent.appendChild(row);
});
// Add Total row.
let row = DOM.tr();
row.appendChild(DOM.td(''));
row.appendChild(DOM.td('All'));
row.appendChild(DOM.td(this.data.all.length));
row.appendChild(DOM.td('100%'));
timelineLegendContent.appendChild(row);
timelineLegend.appendChild(timelineLegendContent);
}
handleEntryTypeDblClick(e) {
this.dispatchEvent(new SelectionEvent(e.target.parentNode.entries));
}
timelineIndicatorMove(offset) {
this.timeline.scrollLeft += offset;
}
handleTimelineScroll(e) {
let horizontal = e.currentTarget.scrollLeft;
this.dispatchEvent(new CustomEvent(
'scrolltrack', {bubbles: true, composed: true, detail: horizontal}));
}
async setChunkBackgrounds(backgroundTodo) {
const kMaxDuration = 50;
let lastTime = 0;
for (let [chunk, node] of backgroundTodo) {
const current = performance.now();
if (current - lastTime > kMaxDuration) {
await delay(25);
lastTime = current;
}
this.setChunkBackground(chunk, node);
}
}
setChunkBackground(chunk, node) {
// Render the types of transitions as bar charts
const kHeight = chunk.height;
const kWidth = 1;
this.backgroundCanvas.width = kWidth;
this.backgroundCanvas.height = kHeight;
let ctx = this.backgroundCanvas.getContext('2d');
ctx.clearRect(0, 0, kWidth, kHeight);
let y = 0;
let total = chunk.size();
let type, count;
if (true) {
chunk.getBreakdown(map => map.type).forEach(([type, count]) => {
ctx.fillStyle = this.typeToColor(type);
let height = count / total * kHeight;
ctx.fillRect(0, y, kWidth, y + height);
y += height;
});
} else {
chunk.items.forEach(map => {
ctx.fillStyle = this.typeToColor(map.type);
let y = chunk.yOffset(map);
ctx.fillRect(0, y, kWidth, y + 1);
});
}
let imageData = this.backgroundCanvas.toDataURL('image/webp', 0.2);
node.style.backgroundImage = 'url(' + imageData + ')';
}
updateTimeline() {
let chunksNode = this.timelineChunks;
DOM.removeAllChildren(chunksNode);
let chunks = this.chunks;
let max = chunks.max(each => each.size());
let start = this.data.startTime;
let end = this.data.endTime;
let duration = end - start;
this._timeToPixel = chunks.length * kChunkWidth / duration;
this._timeStartOffset = start * this._timeToPixel;
let addTimestamp = (time, name) => {
let timeNode = DOM.div('timestamp');
timeNode.innerText = name;
timeNode.style.left = ((time - start) * this._timeToPixel) + 'px';
chunksNode.appendChild(timeNode);
};
let backgroundTodo = [];
for (let i = 0; i < chunks.length; i++) {
let chunk = chunks[i];
let height = (chunk.size() / max * kChunkHeight);
chunk.height = height;
if (chunk.isEmpty()) continue;
let node = DOM.div();
node.className = 'chunk';
node.style.left = ((chunks[i].start - start) * this._timeToPixel) + 'px';
node.style.height = height + 'px';
node.chunk = chunk;
node.addEventListener('mousemove', e => this.handleChunkMouseMove(e));
node.addEventListener('click', e => this.handleChunkClick(e));
node.addEventListener('dblclick', e => this.handleChunkDoubleClick(e));
backgroundTodo.push([chunk, node])
chunksNode.appendChild(node);
}
this.setChunkBackgrounds(backgroundTodo);
// Put a time marker roughly every 20 chunks.
let expected = duration / chunks.length * 20;
let interval = (10 ** Math.floor(Math.log10(expected)));
let correction = Math.log10(expected / interval);
correction = (correction < 0.33) ? 1 : (correction < 0.75) ? 2.5 : 5;
interval *= correction;
let time = start;
while (time < end) {
addTimestamp(time, ((time - start) / 1000) + ' ms');
time += interval;
}
this.redraw();
}
handleChunkMouseMove(event) {
if (this.isLocked) return false;
if (this._isSelecting) return false;
let chunk = event.target.chunk;
if (!chunk) return;
// topmost map (at chunk.height) == map #0.
let relativeIndex =
Math.round(event.layerY / event.target.offsetHeight * chunk.size());
let map = chunk.at(relativeIndex);
this.dispatchEvent(new FocusEvent(map));
}
handleChunkClick(event) {
this.isLocked = !this.isLocked;
}
handleChunkDoubleClick(event) {
let chunk = event.target.chunk;
if (!chunk) return;
this.dispatchEvent(new SelectTimeEvent(chunk.start, chunk.end));
}
redraw() {
let canvas = this.timelineCanvas;
canvas.width = (this.chunks.length + 1) * kChunkWidth;
canvas.height = kChunkHeight;
let ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, kChunkHeight);
if (!this.selectedEntry || !this.selectedEntry.edge) return;
this.drawEdges(ctx);
}
setMapStyle(map, ctx) {
ctx.fillStyle = map.edge && map.edge.from ? CSSColor.onBackgroundColor :
CSSColor.onPrimaryColor;
}
setEdgeStyle(edge, ctx) {
let color = this.typeToColor(edge.type);
ctx.strokeStyle = color;
ctx.fillStyle = color;
}
markMap(ctx, map) {
let [x, y] = map.position(this.chunks);
ctx.beginPath();
this.setMapStyle(map, ctx);
ctx.arc(x, y, 3, 0, 2 * Math.PI);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = CSSColor.onBackgroundColor;
ctx.arc(x, y, 2, 0, 2 * Math.PI);
ctx.fill();
}
markSelectedMap(ctx, map) {
let [x, y] = map.position(this.chunks);
ctx.beginPath();
this.setMapStyle(map, ctx);
ctx.arc(x, y, 6, 0, 2 * Math.PI);
ctx.strokeStyle = CSSColor.onBackgroundColor;
ctx.stroke();
}
drawEdges(ctx) {
// Draw the trace of maps in reverse order to make sure the outgoing
// transitions of previous maps aren't drawn over.
const kMaxOutgoingEdges = 100;
let nofEdges = 0;
let stack = [];
let current = this.selectedEntry;
while (current && nofEdges < kMaxOutgoingEdges) {
nofEdges += current.children.length;
stack.push(current);
current = current.parent();
}
ctx.save();
this.drawOutgoingEdges(ctx, this.selectedEntry, 3);
ctx.restore();
let labelOffset = 15;
let xPrev = 0;
while (current = stack.pop()) {
if (current.edge) {
this.setEdgeStyle(current.edge, ctx);
let [xTo, yTo] = this.drawEdge(ctx, current.edge, true, labelOffset);
if (xTo == xPrev) {
labelOffset += 8;
} else {
labelOffset = 15
}
xPrev = xTo;
}
this.markMap(ctx, current);
current = current.parent();
ctx.save();
// this.drawOutgoingEdges(ctx, current, 1);
ctx.restore();
}
// Mark selected map
this.markSelectedMap(ctx, this.selectedEntry);
}
drawEdge(ctx, edge, showLabel = true, labelOffset = 20) {
if (!edge.from || !edge.to) return [-1, -1];
let [xFrom, yFrom] = edge.from.position(this.chunks);
let [xTo, yTo] = edge.to.position(this.chunks);
let sameChunk = xTo == xFrom;
if (sameChunk) labelOffset += 8;
ctx.beginPath();
ctx.moveTo(xFrom, yFrom);
let offsetX = 20;
let offsetY = 20;
let midX = xFrom + (xTo - xFrom) / 2;
let midY = (yFrom + yTo) / 2 - 100;
if (!sameChunk) {
ctx.quadraticCurveTo(midX, midY, xTo, yTo);
} else {
ctx.lineTo(xTo, yTo);
}
if (!showLabel) {
ctx.stroke();
} else {
let centerX, centerY;
if (!sameChunk) {
centerX = (xFrom / 2 + midX + xTo / 2) / 2;
centerY = (yFrom / 2 + midY + yTo / 2) / 2;
} else {
centerX = xTo;
centerY = yTo;
}
ctx.moveTo(centerX, centerY);
ctx.lineTo(centerX + offsetX, centerY - labelOffset);
ctx.stroke();
ctx.textAlign = 'left';
ctx.fillStyle = this.typeToColor(edge.type);
ctx.fillText(
edge.toString(), centerX + offsetX + 2, centerY - labelOffset);
}
return [xTo, yTo];
}
drawOutgoingEdges(ctx, map, max = 10, depth = 0) {
if (!map) return;
if (depth >= max) return;
ctx.globalAlpha = 0.5 - depth * (0.3 / max);
ctx.strokeStyle = CSSColor.timelineBackgroundColor;
const limit = Math.min(map.children.length, 100)
for (let i = 0; i < limit; i++) {
let edge = map.children[i];
this.drawEdge(ctx, edge, true);
this.drawOutgoingEdges(ctx, edge.to, max, depth + 1);
}
}
});

View File

@ -131,6 +131,31 @@ def TorqueLintWorker(command):
print('Error running format-torque.py')
process.kill()
def JSLintWorker(command):
try:
file_name = command[-1]
with open(file_name, "r") as file_handle:
contents = file_handle.read()
process = subprocess.Popen(command, stdout=PIPE, stderr=subprocess.PIPE)
output, err = process.communicate()
rc = process.returncode
if rc != 0:
sys.stdout.write("error code " + str(rc) + " running clang-format.\n")
return rc
if output != contents:
sys.stdout.write(file_name + " requires formatting.\n")
return 1
return 0
except KeyboardInterrupt:
process.kill()
except Exception:
print('Error running clang-format. Please make sure you have depot_tools' +
' in your $PATH. Lint check skipped.')
process.kill()
class FileContentsCache(object):
def __init__(self, sums_file_name):
@ -392,6 +417,33 @@ class TorqueLintProcessor(CacheableSourceFileProcessor):
return None, arguments
class JSLintProcessor(CacheableSourceFileProcessor):
"""
Check .{m}js file to verify they follow the JS Style guide.
"""
def __init__(self, use_cache=True):
super(JSLintProcessor, self).__init__(
use_cache=use_cache, cache_file_path='.jslint-cache',
file_type='JavaScript')
def IsRelevant(self, name):
return name.endswith('.js') or name.endswith('.mjs')
def GetPathsToSearch(self):
return ['tools/system-analyzer']
def GetProcessorWorker(self):
return JSLintWorker
def GetProcessorScript(self):
for path in [TOOLS_PATH] + os.environ["PATH"].split(os.pathsep):
path = path.strip('"')
clang_format = os.path.join(path, 'clang_format.py')
if os.path.isfile(clang_format):
return clang_format, []
return None, []
COPYRIGHT_HEADER_PATTERN = re.compile(
r'Copyright [\d-]*20[0-2][0-9] the V8 project authors. All rights reserved.')
@ -708,6 +760,9 @@ def Main():
print("Running Torque formatting check...")
success &= TorqueLintProcessor(use_cache=use_linter_cache).RunOnPath(
workspace)
print("Running JavaScript formatting check...")
success &= JSLintProcessor(use_cache=use_linter_cache).RunOnPath(
workspace)
print("Running copyright header, trailing whitespaces and " \
"two empty lines between declarations check...")
success &= SourceProcessor().RunOnPath(workspace)