diff options
Diffstat (limited to 'toolkit/components/aboutcheckerboard')
5 files changed, 398 insertions, 0 deletions
diff --git a/toolkit/components/aboutcheckerboard/content/aboutCheckerboard.css b/toolkit/components/aboutcheckerboard/content/aboutCheckerboard.css new file mode 100644 index 000000000..7f88612db --- /dev/null +++ b/toolkit/components/aboutcheckerboard/content/aboutCheckerboard.css @@ -0,0 +1,49 @@ +/* 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: 0px; + width: 50%; + vertical-align: top; +} + +hr { + clear: both; + margin: 10px; +} + +iframe { + width: 100%; + height: 900px; +} + +#player, #raw { + width: 800px; + margin-left: auto; + margin-right: auto; +} + +#controls { + text-align: center; +} + +#canvas { + border: solid 1px black; +} + +#active { + width: 100%; + border: solid 1px black; + margin-top: 0; +} + +#trace { + width: 100%; +} diff --git a/toolkit/components/aboutcheckerboard/content/aboutCheckerboard.js b/toolkit/components/aboutcheckerboard/content/aboutCheckerboard.js new file mode 100644 index 000000000..c64a80a05 --- /dev/null +++ b/toolkit/components/aboutcheckerboard/content/aboutCheckerboard.js @@ -0,0 +1,276 @@ +/* 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 (var 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 = 'javascript:showReport(' + i + ')'; + 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'); + if (service.isRecordingEnabled()) { + enabled.textContent = 'enabled'; + enabled.style.color = 'green'; + } else { + enabled.textContent = 'disabled'; + enabled.style.color = 'red'; + } +} + +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 = new Array(); +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 = new Array(); + 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 + 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: 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(); +} diff --git a/toolkit/components/aboutcheckerboard/content/aboutCheckerboard.xhtml b/toolkit/components/aboutcheckerboard/content/aboutCheckerboard.xhtml new file mode 100644 index 000000000..6a8a61896 --- /dev/null +++ b/toolkit/components/aboutcheckerboard/content/aboutCheckerboard.xhtml @@ -0,0 +1,55 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta name="viewport" content="width=device-width"/> + <link rel="stylesheet" href="chrome://global/content/aboutCheckerboard.css" type="text/css"/> + <script type="text/javascript;version=1.8" src="chrome://global/content/aboutCheckerboard.js"></script> + </head> + + <body onload="onLoad()"> + <p>Checkerboard recording is <span id="enabled" style="color: red">undetermined</span>. + <button onclick="toggleEnabled()">Toggle it!</button>.</p> + <p>If there are active reports in progress, you can stop and flush them by clicking here: + <button onclick="flushReports()">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 onclick="reset(true)">«</button><!-- rewind button --> + <button onclick="step(true)"><</button><!-- step back button --> + <button onclick="togglePlay()">|| ▶</button><!-- pause button --> + <button onclick="stopPlay()">☐</button><!-- stop button --> + <button onclick="step(false)">></button><!-- step forward button --> + <button onclick="reset(false)">»</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" onclick="loadData()"/><label for="excludePageFromZoom">Exclude page coordinates from zoom calculations</label><br/> + </div> + </div> + </body> +</html> diff --git a/toolkit/components/aboutcheckerboard/jar.mn b/toolkit/components/aboutcheckerboard/jar.mn new file mode 100644 index 000000000..64d5bfc8e --- /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.xhtml (content/aboutCheckerboard.xhtml) + 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 000000000..91d6e9662 --- /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') |