Bug 1499828 Part 2 - Show overlay when the current child is replaying, r=lsmyth.
authorBrian Hackett <bhackett1024@gmail.com>
Wed, 17 Oct 2018 13:32:06 -0600
changeset 500772 dc47411763c560e974c3f6d4842db72f75386ac6
parent 500771 65557267db2a8993cb3318fee762d9dcb4b65954
child 500773 97475fb566eaefc467b4068a0579e779bd5e19d1
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslsmyth
bugs1499828
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1499828 Part 2 - Show overlay when the current child is replaying, r=lsmyth.
devtools/server/actors/replay/graphics.js
--- a/devtools/server/actors/replay/graphics.js
+++ b/devtools/server/actors/replay/graphics.js
@@ -9,25 +9,127 @@
 // which are connected to the compositor in the UI process in the usual way.
 // We need to update the contents of the document to draw the raw graphics data
 // provided by the child process.
 
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
-function updateWindow(window, buffer, width, height, hadFailure) {
+const CC = Components.Constructor;
+
+// Create a sandbox with the resources we need. require() doesn't work here.
+const sandbox = Cu.Sandbox(CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")());
+Cu.evalInSandbox(
+  "Components.utils.import('resource://gre/modules/jsdebugger.jsm');" +
+  "addDebuggerToGlobal(this);",
+  sandbox
+);
+const RecordReplayControl = sandbox.RecordReplayControl;
+
+// State for dragging the overlay.
+let dragx, dragy;
+
+// Windows in the middleman process are initially set up as about:blank pages.
+// This method fills them in with a canvas filling the tab, and an overlay that
+// can be displayed over that canvas.
+function setupContents(window) {
+  // The middlemanOverlay element is shown when the active child is replaying.
+  const overlay = window.middlemanOverlay = window.document.createElement("div");
+  overlay.style.position = "absolute";
+  overlay.style.border = "medium solid #000000";
+  overlay.style.backgroundColor = "#BBCCCC";
+
+  // The middlemanPosition element is contained in the overlay and shows the
+  // current position in the recording.
+  const position = window.middlemanPosition = window.document.createElement("div");
+  position.innerText = "";
+  position.style.textAlign = "center";
+  position.style.padding = "5px 5px 0px 5px";
+  overlay.appendChild(position);
+
+  // The middlemanProgressBar element is contained in the overlay and shows any
+  // progress made on the current operation.
+  const progressBar =
+    window.middlemanProgressBar = window.document.createElement("canvas");
+  progressBar.width = 100;
+  progressBar.height = 5;
+  progressBar.getContext("2d").fillStyle = "white";
+  progressBar.getContext("2d").fillRect(0, 0, 100, 5);
+  progressBar.style.padding = "5px 5px 5px 5px";
+
+  overlay.appendChild(progressBar);
+  window.document.body.prepend(overlay);
+
+  overlay.onmousedown = window.middlemanMouseDown = function(e) {
+    e.preventDefault();
+    dragx = e.clientX;
+    dragy = e.clientY;
+    window.document.onmouseup = window.middlemanMouseUp;
+    window.document.onmousemove = window.middlemanMouseMove;
+  };
+
+  window.middlemanMouseMove = function(e) {
+    // Compute the new position of the overlay per the current drag operation.
+    // Don't allow the overlay to be dragged outside the window.
+    e.preventDefault();
+    const canvas = window.middlemanCanvas;
+    let diffx = e.clientX - dragx;
+    let diffy = e.clientY - dragy;
+    const newTop = () => overlay.offsetTop + diffy;
+    if (newTop() < 0) {
+      diffy -= newTop();
+    }
+    const maxTop = canvas.height / window.devicePixelRatio;
+    if (newTop() + overlay.offsetHeight >= maxTop) {
+      diffy -= newTop() + overlay.offsetHeight - maxTop;
+    }
+    overlay.style.top = newTop() + "px";
+    const newLeft = () => overlay.offsetLeft + diffx;
+    if (newLeft() < 0) {
+      diffx -= newLeft();
+    }
+    const maxLeft = canvas.width / window.devicePixelRatio;
+    if (newLeft() + overlay.offsetWidth >= maxLeft) {
+      diffx -= newLeft() + overlay.offsetWidth - maxLeft;
+    }
+    overlay.style.left = (overlay.offsetLeft + diffx) + "px";
+    dragx += diffx;
+    dragy += diffy;
+  };
+
+  window.middlemanMouseUp = function(e) {
+    window.document.onmouseup = null;
+    window.document.onmousemove = null;
+  };
+
+  // The middlemanCanvas element fills the tab's contents.
+  const canvas = window.middlemanCanvas = window.document.createElement("canvas");
+  canvas.style.position = "absolute";
+  window.document.body.style.margin = "0px";
+  window.document.body.prepend(canvas);
+}
+
+function getOverlay(window) {
+  if (!window.middlemanOverlay) {
+    setupContents(window);
+  }
+  return window.middlemanOverlay;
+}
+
+function getCanvas(window) {
+  if (!window.middlemanCanvas) {
+    setupContents(window);
+  }
+  return window.middlemanCanvas;
+}
+
+function updateWindowCanvas(window, buffer, width, height, hadFailure) {
   // Make sure the window has a canvas filling the screen.
-  let canvas = window.middlemanCanvas;
-  if (!canvas) {
-    canvas = window.document.createElement("canvas");
-    window.document.body.style.margin = "0px";
-    window.document.body.insertBefore(canvas, window.document.body.firstChild);
-    window.middlemanCanvas = canvas;
-  }
+  const canvas = getCanvas(window);
 
   canvas.width = width;
   canvas.height = height;
 
   // If there is a scale for this window, then the graphics will already have
   // been scaled in the child process. To avoid scaling the graphics twice,
   // transform the canvas to undo the scaling.
   const scale = window.devicePixelRatio;
@@ -48,28 +150,56 @@ function updateWindow(window, buffer, wi
   if (hadFailure) {
     cx.fillStyle = "red";
     cx.font = "48px serif";
     cx.fillText("PAINT FAILURE", 10, 50);
   }
 
   // Make recording/replaying tabs easier to differentiate from other tabs.
   window.document.title = "RECORD/REPLAY";
+
+  updateWindowOverlay(window);
 }
 
 // Entry point for when we have some new graphics data from the child process
 // to draw.
 // eslint-disable-next-line no-unused-vars
-function Update(buffer, width, height, hadFailure) {
+function UpdateCanvas(buffer, width, height, hadFailure) {
   try {
     // Paint to all windows we can find. Hopefully there is only one.
     for (const window of Services.ww.getWindowEnumerator()) {
-      updateWindow(window, buffer, width, height, hadFailure);
+      updateWindowCanvas(window, buffer, width, height, hadFailure);
     }
   } catch (e) {
-    dump("Middleman Graphics Update Exception: " + e + "\n");
+    dump("Middleman Graphics UpdateCanvas Exception: " + e + "\n");
+  }
+}
+
+function updateWindowOverlay(window) {
+  const overlay = getOverlay(window);
+
+  const position = RecordReplayControl.recordingPosition();
+  if (position === undefined) {
+    overlay.style.visibility = "hidden";
+  } else {
+    overlay.style.visibility = "visible";
+    window.middlemanPosition.innerText = (Math.round(position * 10000) / 100) + "%";
+  }
+}
+
+// Entry point for when we need to update the overlay's contents or visibility.
+// eslint-disable-next-line no-unused-vars
+function UpdateOverlay() {
+  try {
+    // Paint to all windows we can find. Hopefully there is only one.
+    for (const window of Services.ww.getWindowEnumerator()) {
+      updateWindowOverlay(window);
+    }
+  } catch (e) {
+    dump("Middleman Graphics UpdateOverlay Exception: " + e + "\n");
   }
 }
 
 // eslint-disable-next-line no-unused-vars
 var EXPORTED_SYMBOLS = [
-  "Update",
+  "UpdateCanvas",
+  "UpdateOverlay",
 ];