324 lines
8 KiB
JavaScript
324 lines
8 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
"use strict";
|
|
|
|
var trace;
|
|
var service;
|
|
var reports;
|
|
|
|
function onLoad() {
|
|
trace = document.getElementById("trace");
|
|
service = new CheckerboardReportService();
|
|
updateEnabled();
|
|
reports = service.getReports();
|
|
for (let i = 0; i < reports.length; i++) {
|
|
let text =
|
|
"Severity " +
|
|
reports[i].severity +
|
|
" at " +
|
|
new Date(reports[i].timestamp).toString();
|
|
let link = document.createElement("a");
|
|
link.href = "#";
|
|
link.addEventListener("click", function () {
|
|
showReport(i);
|
|
return false;
|
|
});
|
|
link.textContent = text;
|
|
let bullet = document.createElement("li");
|
|
bullet.appendChild(link);
|
|
document.getElementById(reports[i].reason).appendChild(bullet);
|
|
}
|
|
}
|
|
|
|
function updateEnabled() {
|
|
let enabled = document.getElementById("enabled");
|
|
let isEnabled = service.isRecordingEnabled();
|
|
if (isEnabled) {
|
|
enabled.textContent = "enabled";
|
|
} else {
|
|
enabled.textContent = "disabled";
|
|
}
|
|
enabled.classList.toggle("enabled", isEnabled);
|
|
}
|
|
|
|
function toggleEnabled() {
|
|
service.setRecordingEnabled(!service.isRecordingEnabled());
|
|
updateEnabled();
|
|
}
|
|
|
|
function flushReports() {
|
|
service.flushActiveReports();
|
|
}
|
|
|
|
function showReport(index) {
|
|
trace.value = reports[index].log;
|
|
loadData();
|
|
}
|
|
|
|
// -- Code to load and render the trace --
|
|
|
|
const CANVAS_USE_RATIO = 0.75;
|
|
const FRAME_INTERVAL_MS = 50;
|
|
const VECTOR_NORMALIZED_MAGNITUDE = 30.0;
|
|
|
|
var renderData = [];
|
|
var currentFrame = 0;
|
|
var playing = false;
|
|
var timerId = 0;
|
|
|
|
var minX = undefined;
|
|
var minY = undefined;
|
|
var maxX = undefined;
|
|
var maxY = undefined;
|
|
|
|
function log(x) {
|
|
if (console) {
|
|
console.log(x);
|
|
}
|
|
}
|
|
|
|
function getFlag(flag) {
|
|
return document.getElementById(flag).checked;
|
|
}
|
|
|
|
// parses the lines in the textarea, ignoring anything that doesn't have RENDERTRACE.
|
|
// for each matching line, tokenizes on whitespace and ignores all tokens prior to
|
|
// RENDERTRACE. Additional info can be included at the end of the line, and will be
|
|
// displayed but not parsed. Allowed syntaxes:
|
|
// <junk> RENDERTRACE <timestamp> rect <color> <x> <y> <width> <height> [extraInfo]
|
|
function loadData() {
|
|
stopPlay();
|
|
renderData = [];
|
|
currentFrame = 0;
|
|
minX = undefined;
|
|
minY = undefined;
|
|
maxX = undefined;
|
|
maxY = undefined;
|
|
|
|
var charPos = 0;
|
|
var lastLineLength = 0;
|
|
var lines = trace.value.split(/\r|\n/);
|
|
for (var i = 0; i < lines.length; i++) {
|
|
charPos += lastLineLength;
|
|
lastLineLength = lines[i].length + 1;
|
|
// skip lines without RENDERTRACE
|
|
if (!/RENDERTRACE/.test(lines[i])) {
|
|
continue;
|
|
}
|
|
|
|
var tokens = lines[i].split(/\s+/);
|
|
var j = 0;
|
|
// skip tokens until RENDERTRACE
|
|
// eslint-disable-next-line no-empty
|
|
while (j < tokens.length && tokens[j++] != "RENDERTRACE") {} // empty loop body
|
|
if (j >= tokens.length - 2) {
|
|
log("Error parsing line: " + lines[i]);
|
|
continue;
|
|
}
|
|
|
|
var timestamp = tokens[j++];
|
|
var destIndex = renderData.length;
|
|
if (destIndex == 0) {
|
|
// create the initial frame
|
|
renderData.push({
|
|
timestamp,
|
|
rects: {},
|
|
});
|
|
} else if (renderData[destIndex - 1].timestamp == timestamp) {
|
|
// timestamp hasn't changed use, so update the previous object
|
|
destIndex--;
|
|
} else {
|
|
// clone a new copy of the last frame and update timestamp
|
|
renderData.push(JSON.parse(JSON.stringify(renderData[destIndex - 1])));
|
|
renderData[destIndex].timestamp = timestamp;
|
|
}
|
|
|
|
switch (tokens[j++]) {
|
|
case "rect":
|
|
if (j > tokens.length - 5) {
|
|
log("Error parsing line: " + lines[i]);
|
|
continue;
|
|
}
|
|
|
|
var rect = {};
|
|
var color = tokens[j++];
|
|
renderData[destIndex].rects[color] = rect;
|
|
rect.x = parseFloat(tokens[j++]);
|
|
rect.y = parseFloat(tokens[j++]);
|
|
rect.width = parseFloat(tokens[j++]);
|
|
rect.height = parseFloat(tokens[j++]);
|
|
rect.dataText = trace.value.substring(
|
|
charPos,
|
|
charPos + lines[i].length
|
|
);
|
|
|
|
if (!getFlag("excludePageFromZoom") || color != "brown") {
|
|
if (typeof minX == "undefined") {
|
|
minX = rect.x;
|
|
minY = rect.y;
|
|
maxX = rect.x + rect.width;
|
|
maxY = rect.y + rect.height;
|
|
} else {
|
|
minX = Math.min(minX, rect.x);
|
|
minY = Math.min(minY, rect.y);
|
|
maxX = Math.max(maxX, rect.x + rect.width);
|
|
maxY = Math.max(maxY, rect.y + rect.height);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
log("Error parsing line " + lines[i]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!renderFrame()) {
|
|
alert("No data found; nothing to render!");
|
|
}
|
|
}
|
|
|
|
// render the current frame (i.e. renderData[currentFrame])
|
|
// returns false if currentFrame is out of bounds, true otherwise
|
|
function renderFrame() {
|
|
var frame = currentFrame;
|
|
if (frame < 0 || frame >= renderData.length) {
|
|
log("Invalid frame index");
|
|
return false;
|
|
}
|
|
|
|
var canvas = document.getElementById("canvas");
|
|
if (!canvas.getContext) {
|
|
log("No canvas context");
|
|
}
|
|
|
|
var context = canvas.getContext("2d");
|
|
|
|
// midpoint of the bounding box
|
|
var midX = (minX + maxX) / 2.0;
|
|
var midY = (minY + maxY) / 2.0;
|
|
|
|
// midpoint of the canvas
|
|
var cmx = canvas.width / 2.0;
|
|
var cmy = canvas.height / 2.0;
|
|
|
|
// scale factor
|
|
var scale =
|
|
CANVAS_USE_RATIO *
|
|
Math.min(canvas.width / (maxX - minX), canvas.height / (maxY - minY));
|
|
|
|
function projectX(value) {
|
|
return cmx + (value - midX) * scale;
|
|
}
|
|
|
|
function projectY(value) {
|
|
return cmy + (value - midY) * scale;
|
|
}
|
|
|
|
function drawRect(color, rect) {
|
|
context.strokeStyle = color;
|
|
context.strokeRect(
|
|
projectX(rect.x),
|
|
projectY(rect.y),
|
|
rect.width * scale,
|
|
rect.height * scale
|
|
);
|
|
}
|
|
|
|
// clear canvas
|
|
context.fillStyle = "white";
|
|
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
var activeData = "";
|
|
// draw rects
|
|
for (var i in renderData[frame].rects) {
|
|
drawRect(i, renderData[frame].rects[i]);
|
|
activeData += "\n" + renderData[frame].rects[i].dataText;
|
|
}
|
|
// draw timestamp and frame counter
|
|
context.fillStyle = "black";
|
|
context.fillText(
|
|
frame + 1 + "/" + renderData.length + ": " + renderData[frame].timestamp,
|
|
5,
|
|
15
|
|
);
|
|
|
|
document.getElementById("active").textContent = activeData;
|
|
|
|
return true;
|
|
}
|
|
|
|
// -- Player controls --
|
|
|
|
function reset(beginning) {
|
|
currentFrame = beginning ? 0 : renderData.length - 1;
|
|
renderFrame();
|
|
}
|
|
|
|
function step(backwards) {
|
|
if (playing) {
|
|
togglePlay();
|
|
}
|
|
currentFrame += backwards ? -1 : 1;
|
|
if (!renderFrame()) {
|
|
currentFrame -= backwards ? -1 : 1;
|
|
}
|
|
}
|
|
|
|
function pause() {
|
|
clearInterval(timerId);
|
|
playing = false;
|
|
}
|
|
|
|
function togglePlay() {
|
|
if (playing) {
|
|
pause();
|
|
} else {
|
|
timerId = setInterval(function () {
|
|
currentFrame++;
|
|
if (!renderFrame()) {
|
|
currentFrame--;
|
|
togglePlay();
|
|
}
|
|
}, FRAME_INTERVAL_MS);
|
|
playing = true;
|
|
}
|
|
}
|
|
|
|
function stopPlay() {
|
|
if (playing) {
|
|
togglePlay();
|
|
}
|
|
currentFrame = 0;
|
|
renderFrame();
|
|
}
|
|
|
|
document.getElementById("pauseButton").addEventListener("click", togglePlay);
|
|
document.getElementById("stopButton").addEventListener("click", stopPlay);
|
|
document
|
|
.getElementById("enableToggleButton")
|
|
.addEventListener("click", toggleEnabled);
|
|
document
|
|
.getElementById("flushReportsButton")
|
|
.addEventListener("click", flushReports);
|
|
document
|
|
.getElementById("excludePageFromZoom")
|
|
.addEventListener("click", loadData);
|
|
document
|
|
.getElementById("stepForwardButton")
|
|
.addEventListener("click", function () {
|
|
step(false);
|
|
});
|
|
document.getElementById("forwardButton").addEventListener("click", function () {
|
|
reset(false);
|
|
});
|
|
document.getElementById("rewindButton").addEventListener("click", function () {
|
|
reset(true);
|
|
});
|
|
document
|
|
.getElementById("stepBackButton")
|
|
.addEventListener("click", function () {
|
|
step(true);
|
|
});
|
|
window.addEventListener("load", onLoad);
|