Bug 1338582 - New devtools highlighter for signaling paused state; r=jlast draft
authorPatrick Brosset <pbrosset@mozilla.com>
Tue, 04 Apr 2017 12:03:03 +0200
changeset 555424 2006068b82f808c284aebe9f1f364970bda428c5
parent 555045 f44becc9e8bb088b027c3673cf117a88ed5291c1
child 622610 39c45c8c5af28c6a8d985bb0b8ecf5303de77102
push id52241
push userbmo:pbrosset@mozilla.com
push dateTue, 04 Apr 2017 10:05:16 +0000
reviewersjlast
bugs1338582
milestone55.0a1
Bug 1338582 - New devtools highlighter for signaling paused state; r=jlast This adds a new highlighter to our collection of highlighters. This one is a simple overlay on top of the page and a message at the top. It will be used by the debugger to signal to users that script execution is paused. In later versions, the message at the top will also contain stepping and resuming buttons. MozReview-Commit-ID: JNGWrVjMzkm
devtools/server/actors/highlighters.css
devtools/server/actors/highlighters.js
devtools/server/actors/highlighters/moz.build
devtools/server/actors/highlighters/paused-debugger.js
devtools/server/tests/mochitest/chrome.ini
devtools/server/tests/mochitest/test_highlighter_paused_debugger.html
--- a/devtools/server/actors/highlighters.css
+++ b/devtools/server/actors/highlighters.css
@@ -29,16 +29,18 @@
 
 :-moz-native-anonymous .highlighter-container {
   --highlighter-guide-color: #08c;
   --highlighter-content-color: #87ceeb;
   --highlighter-bubble-text-color: hsl(216, 33%, 97%);
   --highlighter-bubble-background-color: hsl(214, 13%, 24%);
   --highlighter-bubble-border-color: rgba(255, 255, 255, 0.2);
   --highlighter-bubble-arrow-size: 8px;
+  --highlighter-font-family: message-box;
+  --highlighter-font-size: 11px;
 }
 
 /**
  * Highlighters are asbolute positioned in the page by default.
  * A single highlighter can have fixed position in its css class if needed (see below the
  * eye dropper or rulers highlighter, for example); but if it has to handle the
  * document's scrolling (as rulers does), it would lag a bit behind due the APZ (Async
  * Pan/Zoom module), that performs asynchronously panning and zooming on the compositor
@@ -115,18 +117,18 @@
 }
 
 /* Highlighter - Infobar */
 
 :-moz-native-anonymous [class$=infobar-container] {
   position: absolute;
   max-width: 95%;
 
-  font: message-box;
-  font-size: 11px;
+  font: var(--highlighter-font-family);
+  font-size: var(--highlighter-font-size);
 }
 
 :-moz-native-anonymous [class$=infobar] {
   position: relative;
 
   /* Centering the infobar in the container */
   left: -50%;
 
@@ -325,17 +327,17 @@
 
 :-moz-native-anonymous .geometry-editor-label-bubble {
   fill: var(--highlighter-bubble-background-color);
   shape-rendering: crispEdges;
 }
 
 :-moz-native-anonymous .geometry-editor-label-text {
   fill: var(--highlighter-bubble-text-color);
-  font: message-box;
+  font: var(--highlighter-font-family);
   font-size: 10px;
   text-anchor: middle;
   dominant-baseline: middle;
 }
 
 /* Rulers Highlighter */
 
 :-moz-native-anonymous .rulers-highlighter-elements {
@@ -361,17 +363,17 @@
 :-moz-native-anonymous .rulers-highlighter-ruler-markers {
   stroke: #202020;
 }
 
 :-moz-native-anonymous .rulers-highlighter-horizontal-labels > text,
 :-moz-native-anonymous .rulers-highlighter-vertical-labels > text {
   stroke: none;
   fill: #202020;
-  font: message-box;
+  font: var(--highlighter-font-family);
   font-size: 9px;
   dominant-baseline: hanging;
 }
 
 :-moz-native-anonymous .rulers-highlighter-horizontal-labels > text {
   text-anchor: start;
 }
 
@@ -411,17 +413,17 @@
 :-moz-native-anonymous .measuring-tool-highlighter-label-position {
   position: absolute;
   top: 0;
   left: 0;
   display: inline-block;
   border-radius: 4px;
   padding: 4px;
   white-space: pre-line;
-  font: message-box;
+  font: var(--highlighter-font-family);
   font-size: 10px;
   pointer-events: none;
   -moz-user-select: none;
   box-sizing: border-box;
 }
 
 :-moz-native-anonymous .measuring-tool-highlighter-label-position {
   color: #fff;
@@ -535,13 +537,57 @@
   offset-inline-start: 3px;
   offset-block-start: 3px;
   box-shadow: 0px 0px 0px black;
   border: solid 1px #fff;
 }
 
 :-moz-native-anonymous .eye-dropper-color-value {
   text-shadow: 1px 1px 1px #fff;
-  font: message-box;
-  font-size: 11px;
+  font: var(--highlighter-font-family);
+  font-size: var(--highlighter-font-size);
   text-align: center;
   padding: 4px 0;
 }
+
+/* Paused Debugger Overlay */
+
+:-moz-native-anonymous .paused-dbg-root {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+
+  display: flex;
+  align-items: center;
+  flex-direction: column;
+
+  /* We don't have access to DevTools themes here, but some of these colors come from the
+     themes. Theme variable names are given in comments. */
+  --text-color: #585959; /* --theme-body-color-alt */
+  --toolbar-background: #fcfcfc; /* --theme-toolbar-background */;
+  --toolbar-border: #dde1e4; /* --theme-splitter-color */
+  --toolbar-box-shadow: 0 4px 4px 0 rgba(155, 155, 155, 0.26); /* --rdm-box-shadow */
+  --overlay-background: #dde1e4a8;
+}
+
+:-moz-native-anonymous .paused-dbg-root[overlay] {
+  background-color: var(--overlay-background);
+  pointer-events: auto;
+}
+
+:-moz-native-anonymous .paused-dbg-toolbar {
+  margin-top: 15px;
+  padding: 4px 5px;
+  display: inline-flex;
+  -moz-user-select: none;
+  pointer-events: auto;
+
+  color: var(--text-color);
+  border-radius: 2px;
+  box-shadow: var(--toolbar-box-shadow);
+  background-color: var(--toolbar-background);
+  border: 1px solid var(--toolbar-border);
+
+  font: var(--highlighter-font-family);
+  font-size: var(--highlighter-font-size);
+}
--- a/devtools/server/actors/highlighters.js
+++ b/devtools/server/actors/highlighters.js
@@ -720,8 +720,12 @@ exports.RulersHighlighter = RulersHighli
 
 const { MeasuringToolHighlighter } = require("./highlighters/measuring-tool");
 register(MeasuringToolHighlighter);
 exports.MeasuringToolHighlighter = MeasuringToolHighlighter;
 
 const { EyeDropper } = require("./highlighters/eye-dropper");
 register(EyeDropper);
 exports.EyeDropper = EyeDropper;
+
+const { PausedDebuggerOverlay } = require("./highlighters/paused-debugger");
+register(PausedDebuggerOverlay);
+exports.PausedDebuggerOverlay = PausedDebuggerOverlay;
--- a/devtools/server/actors/highlighters/moz.build
+++ b/devtools/server/actors/highlighters/moz.build
@@ -11,12 +11,13 @@ DIRS += [
 DevToolsModules(
     'auto-refresh.js',
     'box-model.js',
     'css-grid.js',
     'css-transform.js',
     'eye-dropper.js',
     'geometry-editor.js',
     'measuring-tool.js',
+    'paused-debugger.js',
     'rulers.js',
     'selector.js',
     'simple-outline.js'
 )
new file mode 100644
--- /dev/null
+++ b/devtools/server/actors/highlighters/paused-debugger.js
@@ -0,0 +1,116 @@
+/* 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";
+
+const { CanvasFrameAnonymousContentHelper, createNode } = require("./utils/markup");
+
+/**
+ * The PausedDebuggerOverlay is a class that displays a semi-transparent mask on top of
+ * the whole page and a toolbar at the top of the page.
+ * This is used to signal to users that script execution is current paused.
+ * The toolbar is used to display the reason for the pause in script execution as well as
+ * buttons to resume or step through the program.
+ */
+function PausedDebuggerOverlay(highlighterEnv) {
+  this.env = highlighterEnv;
+  this.markup = new CanvasFrameAnonymousContentHelper(highlighterEnv,
+    this._buildMarkup.bind(this));
+}
+
+PausedDebuggerOverlay.prototype = {
+  typeName: "PausedDebuggerOverlay",
+
+  ID_CLASS_PREFIX: "paused-dbg-",
+
+  _buildMarkup() {
+    let { window } = this.env;
+    let prefix = this.ID_CLASS_PREFIX;
+
+    let container = createNode(window, {
+      attributes: {"class": "highlighter-container"}
+    });
+
+    // Wrapper element.
+    let wrapper = createNode(window, {
+      parent: container,
+      attributes: {
+        "id": "root",
+        "class": "root",
+        "hidden": "true",
+        "overlay": "true"
+      },
+      prefix
+    });
+
+    let toolbar = createNode(window, {
+      parent: wrapper,
+      attributes: {
+        "id": "toolbar",
+        "class": "toolbar"
+      },
+      prefix
+    });
+
+    createNode(window, {
+      nodeType: "span",
+      parent: toolbar,
+      attributes: {
+        "id": "reason",
+        "class": "reason"
+      },
+      prefix
+    });
+
+    return container;
+  },
+
+  destroy() {
+    this.hide();
+    this.markup.destroy();
+    this.env = null;
+  },
+
+  getElement(id) {
+    return this.markup.getElement(this.ID_CLASS_PREFIX + id);
+  },
+
+  show(node, options = {}) {
+    if (this.env.isXUL) {
+      return false;
+    }
+
+    // Show the highlighter's root element.
+    let root = this.getElement("root");
+    root.removeAttribute("hidden");
+
+    // The page overlay is only shown upon request. Sometimes we just want the toolbar.
+    if (options.onlyToolbar) {
+      root.removeAttribute("overlay");
+    } else {
+      root.setAttribute("overlay", "true");
+    }
+
+    // Set the text to appear in the toolbar.
+    let toolbar = this.getElement("toolbar");
+    if (options.reason) {
+      this.getElement("reason").setTextContent(options.reason);
+      toolbar.removeAttribute("hidden");
+    } else {
+      toolbar.setAttribute("hidden", "true");
+    }
+
+    return true;
+  },
+
+  hide() {
+    if (this.env.isXUL) {
+      return;
+    }
+
+    // Hide the overlay.
+    this.getElement("root").setAttribute("hidden", "true");
+  }
+};
+exports.PausedDebuggerOverlay = PausedDebuggerOverlay;
--- a/devtools/server/tests/mochitest/chrome.ini
+++ b/devtools/server/tests/mochitest/chrome.ini
@@ -42,16 +42,17 @@ support-files =
 [test_executeInGlobal-outerized_this.html]
 [test_framerate_01.html]
 [test_framerate_02.html]
 [test_framerate_03.html]
 [test_framerate_04.html]
 [test_framerate_05.html]
 [test_framerate_06.html]
 [test_getProcess.html]
+[test_highlighter_paused_debugger.html]
 [test_inspector-anonymous.html]
 [test_inspector-changeattrs.html]
 [test_inspector-changevalue.html]
 [test_inspector-dead-nodes.html]
 [test_inspector-duplicate-node.html]
 [test_inspector_getImageData.html]
 [test_inspector_getImageDataFromURL.html]
 [test_inspector_getImageData-wait-for-load.html]
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_highlighter_paused_debugger.html
@@ -0,0 +1,92 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test the PausedDebuggerOverlay highlighter.
+-->
+<head>
+  <meta charset="utf-8">
+  <title>PausedDebuggerOverlay test</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+"use strict";
+
+window.onload = function () {
+  SimpleTest.waitForExplicitFinish();
+
+  const {utils: Cu} = Components;
+  const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+  require("devtools/server/actors/inspector");
+  const {HighlighterEnvironment} = require("devtools/server/actors/highlighters");
+  const {PausedDebuggerOverlay} = require("devtools/server/actors/highlighters/paused-debugger");
+
+  const env = new HighlighterEnvironment();
+  env.initFromWindow(window);
+
+  const highlighter = new PausedDebuggerOverlay(env);
+  const anonymousContent = highlighter.markup.content;
+
+  const id = elementID => `${highlighter.ID_CLASS_PREFIX}${elementID}`;
+
+  function isHidden(elementID) {
+    let attr = anonymousContent.getAttributeForElement(id(elementID), "hidden");
+    return typeof attr === "string" && attr == "true";
+  }
+
+  function getReason() {
+    return anonymousContent.getTextContentForElement(id("reason"));
+  }
+
+  function isOverlayShown() {
+    let attr = anonymousContent.getAttributeForElement(id("root"), "overlay");
+    return typeof attr === "string" && attr == "true";
+  }
+
+  info("Test that the various elements with IDs exist");
+  ok(highlighter.getElement("root"), "The root wrapper element exists");
+  ok(highlighter.getElement("toolbar"), "The toolbar element exists");
+  ok(highlighter.getElement("reason"), "The reason label element exists");
+
+  info("Test that the highlighter is hidden by default");
+  ok(isHidden("root"), "The highlighter is hidden");
+
+  info("Show the highlighter with overlay and toolbar");
+  let didShow = highlighter.show(null, {"reason": "Paused in debugger"});
+  ok(didShow, "Calling show returned true");
+  ok(!isHidden("root"), "The highlighter is shown");
+  ok(isOverlayShown(), "The overlay is shown");
+  is(getReason(), "Paused in debugger", "The reason displayed in the toolbar is correct");
+
+  info("Call show again with another reason");
+  didShow = highlighter.show(null, {"reason": "Paused for another reason"});
+  ok(didShow, "Calling show returned true too");
+  ok(!isHidden("root"), "The highlighter is still shown");
+  is(getReason(), "Paused for another reason",
+     "The reason displayed in the toolbar is correct again");
+  ok(isOverlayShown(), "The overlay is still shown too");
+
+  info("Call show again but with no reason");
+  highlighter.show();
+  ok(isHidden("toolbar"), "The toolbar is hidden");
+  ok(isOverlayShown(), "The overlay is shown however");
+
+  info("Call show again with a reason but no overlay");
+  highlighter.show(null, {reason: "no overlay this time", onlyToolbar: true});
+  ok(!isHidden("toolbar"), "The toolbar is shown this time");
+  is(getReason(), "no overlay this time",
+     "The reason displayed in the toolbar is correct again");
+  ok(!isOverlayShown(), "The overlay is hidden");
+
+  info("Hide the highlighter");
+  highlighter.hide();
+  ok(isHidden("root"), "The highlighter is now hidden");
+
+  SimpleTest.finish();
+};
+</script>
+</pre>
+</body>
+</html>