"use strict";
(function (exports, window) {
const COLORS = exports.COLORS = [
"#0088cc",
"#5b5fff",
"#b82ee5",
"#ed2655",
"#f13c00",
"#d97e00",
"#2cbb0f",
"#0072ab",
];
const State = exports.State = (function () {
return class State {
constructor(rawTraces, windowWidth) {
this.traces = null;
this.minimumTraceTime = 100000;
this.minTime = Infinity;
this.maxTime = 0;
this.startSelection = 0;
this.endSelection = 0;
this.windowWidth = windowWidth;
this.grabbingLeft = false;
this.grabbingRight = false;
this.grabbingSlider = false;
this.colorIndex = 0;
this.categoryToColor = Object.create(null);
this.initialize(rawTraces);
}
initialize(rawTraces) {
this.traces = rawTraces.filter(t => t.endTime - t.startTime >= this.minimumTraceTime);
this.traces.sort((t1, t2) => {
let cmp = t1.startTime - t2.startTime;
if (cmp !== 0) {
return cmp;
}
return t1.endTime - t2.endTime;
});
this.findMinTime();
this.normalizeTimes();
this.removeIdleTime();
this.findMaxTime();
this.startSelection = 3 * this.maxTime / 8;
this.endSelection = 5 * this.maxTime / 8;
}
findMinTime() {
this.minTime = this.traces.reduce((min, t) => Math.min(min, t.startTime),
Infinity);
}
findMaxTime() {
this.maxTime = this.traces.reduce((max, t) => Math.max(max, t.endTime),
0);
}
normalizeTimes() {
for (let i = 0; i < this.traces.length; i++) {
let trace = this.traces[i];
trace.startTime -= this.minTime;
trace.endTime -= this.minTime;
}
this.minTime = 0;
}
removeIdleTime() {
let totalIdleTime = 0;
let lastEndTime = null;
for (let i = 0; i < this.traces.length; i++) {
let trace = this.traces[i];
if (lastEndTime !== null && trace.startTime > lastEndTime) {
totalIdleTime += trace.startTime - lastEndTime;
}
lastEndTime = trace.endTime;
trace.startTime -= totalIdleTime;
trace.endTime -= totalIdleTime;
}
}
getColorForCategory(category) {
let result = this.categoryToColor[category];
if (!result) {
result = COLORS[this.colorIndex++ % COLORS.length];
this.categoryToColor[category] = result;
}
return result;
}
pxToNs(px) {
return px / this.windowWidth * this.maxTime;
}
nsToPx(ns) {
return ns / this.maxTime * this.windowWidth
}
nsToSelectionPx(ns) {
return ns / (this.endSelection - this.startSelection) * this.windowWidth;
}
updateStartSelection(position) {
this.startSelection = clamp(this.pxToNs(position),
this.minTime,
this.endSelection);
}
updateEndSelection(position) {
this.endSelection = clamp(this.pxToNs(position),
this.startSelection,
this.maxTime);
}
moveSelection(movement) {
let delta = clamp(this.pxToNs(movement),
-this.startSelection,
this.maxTime - this.endSelection);
this.startSelection += delta;
this.endSelection += delta;
}
zoomSelection(zoom) {
const increment = this.maxTime / 1000;
this.startSelection = clamp(this.startSelection - zoom * increment,
this.minTime,
this.endSelection);
this.endSelection = clamp(this.endSelection + zoom * increment,
this.startSelection,
this.maxTime);
}
getTracesInSelection() {
const tracesInSelection = [];
for (let i = 0; i < state.traces.length; i++) {
let trace = state.traces[i];
if (trace.endTime < state.startSelection) {
continue;
}
if (trace.startTime > state.endSelection) {
break;
}
tracesInSelection.push(trace);
}
return tracesInSelection;
}
};
}());
const clamp = exports.clamp = (value, min, max) => {
return Math.max(Math.min(value, max), min);
};
const closestPowerOfTen = exports.closestPowerOfTen = n => {
let powerOfTen = 1;
let diff = Math.abs(n - powerOfTen);
while (true) {
let nextPowerOfTen = powerOfTen * 10;
let nextDiff = Math.abs(n - nextPowerOfTen);
if (nextDiff > diff) {
return powerOfTen;
}
diff = nextDiff;
powerOfTen = nextPowerOfTen;
}
};
const selectIncrement = exports.selectIncrement = (range, maxTicks) => {
let increment = closestPowerOfTen(range / 10);
while (range / increment > maxTicks) {
increment *= 2;
}
return increment;
};
if (!window) {
return;
}
const state = exports.state = new State(window.TRACES, window.innerWidth);
window.document.body.innerHTML = "";
const sliderContainer = window.document.createElement("div");
sliderContainer.id = "slider";
window.document.body.appendChild(sliderContainer);
const leftGrabby = window.document.createElement("span");
leftGrabby.className = "grabby";
sliderContainer.appendChild(leftGrabby);
const sliderViewport = window.document.createElement("span");
sliderViewport.id = "slider-viewport";
sliderContainer.appendChild(sliderViewport);
const rightGrabby = window.document.createElement("span");
rightGrabby.className = "grabby";
sliderContainer.appendChild(rightGrabby);
const tracesContainer = window.document.createElement("div");
tracesContainer.id = "traces";
window.document.body.appendChild(tracesContainer);
const withRender = fn => function () {
fn.apply(null, arguments);
render();
};
window.addEventListener("resize", withRender(() => {
state.windowWidth = window.innerWidth;
}));
window.addEventListener("mouseup", () => {
state.grabbingSlider = state.grabbingLeft = state.grabbingRight = false;
});
leftGrabby.addEventListener("mousedown", () => {
state.grabbingLeft = true;
});
rightGrabby.addEventListener("mousedown", () => {
state.grabbingRight = true;
});
sliderViewport.addEventListener("mousedown", () => {
state.grabbingSlider = true;
});
window.addEventListener("mousemove", event => {
if (state.grabbingSlider) {
state.moveSelection(event.movementX);
event.preventDefault();
render();
} else if (state.grabbingLeft) {
state.updateStartSelection(event.clientX);
event.preventDefault();
render();
} else if (state.grabbingRight) {
state.updateEndSelection(event.clientX);
event.preventDefault();
render();
}
});
sliderContainer.addEventListener("wheel", withRender(event => {
state.zoomSelection(event.deltaY);
}));
const oncePerAnimationFrame = fn => {
let animationId = null;
return () => {
if (animationId !== null) {
return;
}
animationId = window.requestAnimationFrame(() => {
fn();
animationId = null;
});
};
};
const oncePerWindowWidth = fn => {
let lastWidth = null;
return () => {
if (state.windowWidth !== lastWidth) {
fn();
lastWidth = state.windowWidth;
}
};
};
const render = oncePerAnimationFrame(() => {
renderSlider();
renderTraces();
});
const renderSlider = () => {
let selectionDelta = state.endSelection - state.startSelection;
sliderViewport.style.width = state.nsToPx(selectionDelta) - 6 + "px";
leftGrabby.style.marginLeft = state.nsToPx(state.startSelection) + "px";
rightGrabby.style.rightMargin = state.nsToPx(state.maxTime - state.endSelection) + "px";
renderSliderTicks();
};
const renderSliderTicks = oncePerWindowWidth(() => {
let oldTicks = Array.from(window.document.querySelectorAll(".slider-tick"));
for (let tick of oldTicks) {
tick.remove();
}
let increment = selectIncrement(state.maxTime, 20);
let px = state.nsToPx(increment);
let ms = 0;
for (let i = 0; i < state.windowWidth; i += px) {
let tick = window.document.createElement("div");
tick.className = "slider-tick";
tick.textContent = ms + " ms";
tick.style.left = i + "px";
window.document.body.appendChild(tick);
ms += increment / 1000000;
}
});
const renderTraces = () => {
renderTracesTicks();
let tracesToRender = state.getTracesInSelection();
let rows = Array.from(tracesContainer.querySelectorAll(".outer"));
while (rows.length > tracesToRender.length) {
rows.pop().remove();
}
while (rows.length < tracesToRender.length) {
let elem = makeTraceTemplate();
tracesContainer.appendChild(elem);
rows.push(elem);
}
for (let i = 0; i < tracesToRender.length; i++) {
renderTrace(tracesToRender[i], rows[i]);
}
};
const renderTracesTicks = () => {
let oldTicks = Array.from(tracesContainer.querySelectorAll(".traces-tick"));
for (let tick of oldTicks) {
tick.remove();
}
let selectionDelta = state.endSelection - state.startSelection;
let increment = selectIncrement(selectionDelta, 10);
let px = state.nsToPx(increment);
let offset = state.startSelection % increment;
let time = state.startSelection - offset + increment;
while (time < state.endSelection) {
let tick = document.createElement("div");
tick.className = "traces-tick";
tick.textContent = Math.round(time / 1000000) + " ms";
tick.style.left = state.nsToSelectionPx(time - state.startSelection) + "px";
tracesContainer.appendChild(tick);
time += increment;
}
};
const makeTraceTemplate = () => {
let outer = window.document.createElement("div");
outer.className = "outer";
let inner = window.document.createElement("div");
inner.className = "inner";
let tooltip = window.document.createElement("div");
tooltip.className = "tooltip";
let header = window.document.createElement("h3");
header.className = "header";
tooltip.appendChild(header);
let duration = window.document.createElement("h4");
duration.className = "duration";
tooltip.appendChild(duration);
let pairs = window.document.createElement("dl");
let timeStartLabel = window.document.createElement("dt");
timeStartLabel.textContent = "Start:"
pairs.appendChild(timeStartLabel);
let timeStartValue = window.document.createElement("dd");
timeStartValue.className = "start";
pairs.appendChild(timeStartValue);
let timeEndLabel = window.document.createElement("dt");
timeEndLabel.textContent = "End:"
pairs.appendChild(timeEndLabel);
let timeEndValue = window.document.createElement("dd");
timeEndValue.className = "end";
pairs.appendChild(timeEndValue);
let urlLabel = window.document.createElement("dt");
urlLabel.textContent = "URL:";
pairs.appendChild(urlLabel);
let urlValue = window.document.createElement("dd");
urlValue.className = "url";
pairs.appendChild(urlValue);
let iframeLabel = window.document.createElement("dt");
iframeLabel.textContent = "iframe?";
pairs.appendChild(iframeLabel);
let iframeValue = window.document.createElement("dd");
iframeValue.className = "iframe";
pairs.appendChild(iframeValue);
let incrementalLabel = window.document.createElement("dt");
incrementalLabel.textContent = "Incremental?";
pairs.appendChild(incrementalLabel);
let incrementalValue = window.document.createElement("dd");
incrementalValue.className = "incremental";
pairs.appendChild(incrementalValue);
tooltip.appendChild(pairs);
outer.appendChild(tooltip);
outer.appendChild(inner);
return outer;
};
const renderTrace = (trace, elem) => {
let inner = elem.querySelector(".inner");
inner.style.width = state.nsToSelectionPx(trace.endTime - trace.startTime) + "px";
inner.style.marginLeft = state.nsToSelectionPx(trace.startTime - state.startSelection) + "px";
let category = trace.category;
inner.textContent = category;
inner.style.backgroundColor = state.getColorForCategory(category);
let header = elem.querySelector(".header");
header.textContent = category;
let duration = elem.querySelector(".duration");
duration.textContent = (trace.endTime - trace.startTime) / 1000000 + " ms";
let timeStartValue = elem.querySelector(".start");
timeStartValue.textContent = trace.startTime / 1000000 + " ms";
let timeEndValue = elem.querySelector(".end");
timeEndValue.textContent = trace.endTime / 1000000 + " ms";
if (trace.metadata) {
let urlValue = elem.querySelector(".url");
urlValue.textContent = trace.metadata.url;
urlValue.removeAttribute("hidden");
let iframeValue = elem.querySelector(".iframe");
iframeValue.textContent = trace.metadata.iframe.RootWindow ? "No" : "Yes";
iframeValue.removeAttribute("hidden");
let incrementalValue = elem.querySelector(".incremental");
incrementalValue.textContent = trace.metadata.incremental.Incremental ? "Yes" : "No";
incrementalValue.removeAttribute("hidden");
} else {
elem.querySelector(".url").setAttribute("hidden", "");
elem.querySelector(".iframe").setAttribute("hidden", "");
elem.querySelector(".incremental").setAttribute("hidden", "");
}
};
render();
}(typeof exports === "object" ? exports : window,
typeof window === "object" ? window : null));