Source code

Revision control

Copy as Markdown

Other Tools

/* 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);