diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /toolkit/components/aboutcheckerboard | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/aboutcheckerboard')
5 files changed, 477 insertions, 0 deletions
diff --git a/toolkit/components/aboutcheckerboard/content/aboutCheckerboard.css b/toolkit/components/aboutcheckerboard/content/aboutCheckerboard.css new file mode 100644 index 0000000000..a0095d8563 --- /dev/null +++ b/toolkit/components/aboutcheckerboard/content/aboutCheckerboard.css @@ -0,0 +1,56 @@ +/* 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/. */ + +table.listing { + width: 100%; +} + +table th, table td { + padding: 5px; + border: inset 2px black; + margin: 0; + width: 50%; + vertical-align: top; +} + +hr { + clear: both; + margin: 10px; +} + +iframe { + width: 100%; + height: 900px; +} + +#player, #raw { + width: 800px; + margin-inline: auto; +} + +#controls { + text-align: center; +} + +#canvas { + border: solid 1px black; +} + +#active { + width: 100%; + border: solid 1px black; + margin-top: 0; +} + +#trace { + width: 100%; +} + +#enabled { + color: red; +} + +#enabled.enabled { + color: green; +} diff --git a/toolkit/components/aboutcheckerboard/content/aboutCheckerboard.html b/toolkit/components/aboutcheckerboard/content/aboutCheckerboard.html new file mode 100644 index 0000000000..8f15425a43 --- /dev/null +++ b/toolkit/components/aboutcheckerboard/content/aboutCheckerboard.html @@ -0,0 +1,79 @@ +<!-- 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/. --> +<!DOCTYPE html> +<html> + <head> + <meta + http-equiv="Content-Security-Policy" + content="default-src chrome:; object-src 'none'" + /> + <meta name="viewport" content="width=device-width" /> + <meta charset="utf-8" /> + <title>Checkerboard Analyzer</title> + <link + rel="stylesheet" + href="chrome://global/content/aboutCheckerboard.css" + type="text/css" + /> + </head> + + <body> + <p> + Checkerboard recording is <span id="enabled">undetermined</span>. + <button id="enableToggleButton">Toggle it!</button>. + </p> + <p> + If there are active reports in progress, you can stop and flush them by + clicking here: + <button id="flushReportsButton">Flush active reports</button> + </p> + <table class="listing" cellspacing="0"> + <tr> + <th>Most severe checkerboarding reports</th> + <th>Most recent checkerboarding reports</th> + </tr> + <tr> + <td><ul id="severe"></ul></td> + <td><ul id="recent"></ul></td> + </tr> + </table> + + <hr /> + + <div id="player"> + <div id="controls"> + <button id="rewindButton">«</button + ><!-- rewind button --> + <button id="stepBackButton"><</button + ><!-- step back button --> + <button id="pauseButton">|| ▶</button + ><!-- pause button --> + <button id="stopButton">☐</button + ><!-- stop button --> + <button id="stepForwardButton">></button + ><!-- step forward button --> + <button id="forwardButton">»</button + ><!-- forward button --> + </div> + <canvas id="canvas" width="800" height="600" + >Canvas not supported!</canvas + > + <pre id="active">(Details for currently visible replay frame)</pre> + </div> + + <hr /> + + <div id="raw"> + Raw log:<br /> + <textarea id="trace" rows="10"></textarea> + <div> + <input type="checkbox" id="excludePageFromZoom" /><label + for="excludePageFromZoom" + >Exclude page coordinates from zoom calculations</label + ><br /> + </div> + </div> + </body> + <script src="chrome://global/content/aboutCheckerboard.js"></script> +</html> diff --git a/toolkit/components/aboutcheckerboard/content/aboutCheckerboard.js b/toolkit/components/aboutcheckerboard/content/aboutCheckerboard.js new file mode 100644 index 0000000000..e985a312ad --- /dev/null +++ b/toolkit/components/aboutcheckerboard/content/aboutCheckerboard.js @@ -0,0 +1,324 @@ +/* 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); diff --git a/toolkit/components/aboutcheckerboard/jar.mn b/toolkit/components/aboutcheckerboard/jar.mn new file mode 100644 index 0000000000..5d8cd6729d --- /dev/null +++ b/toolkit/components/aboutcheckerboard/jar.mn @@ -0,0 +1,8 @@ +# 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/. + +toolkit.jar: + content/global/aboutCheckerboard.js (content/aboutCheckerboard.js) + content/global/aboutCheckerboard.html (content/aboutCheckerboard.html) + content/global/aboutCheckerboard.css (content/aboutCheckerboard.css) diff --git a/toolkit/components/aboutcheckerboard/moz.build b/toolkit/components/aboutcheckerboard/moz.build new file mode 100644 index 0000000000..6d0b2c5000 --- /dev/null +++ b/toolkit/components/aboutcheckerboard/moz.build @@ -0,0 +1,10 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +JAR_MANIFESTS += ["jar.mn"] + +with Files("**"): + BUG_COMPONENT = ("Core", "Panning and Zooming") |