Bug 938188 - Make highlighter capable of highlighting only one region, fading out others. r=bgrins
authorPatrick Brosset <pbrosset@mozilla.com>
Sat, 20 Jun 2015 14:51:50 +0200
changeset 268083 975d6d156f8e86cceff703e85a17f9c8f15a5ed2
parent 267938 171e623cf40c76eaa1c22bfba9807d8f4f77e8d0
child 268084 b12b24c7f989bc27cc5742702b978bee2dfc311c
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-esr52@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbgrins
bugs938188
milestone41.0a1
Bug 938188 - Make highlighter capable of highlighting only one region, fading out others. r=bgrins Introduced a new highlighter option that makes each region <path> element in the highlighter only cover the actual area of the corresponding region, excluding the area of nested regions. This is useful when used with the existing showOnly region because it lets users see exactly where a given region is. This patch makes the layout-view use this new option, so that when users hover over the various regions in the layout-view, only the corresponding regions are highlighted.
browser/devtools/inspector/test/browser_inspector_highlighter-options.js
browser/devtools/layoutview/view.js
toolkit/devtools/server/actors/highlighter.css
toolkit/devtools/server/actors/highlighter.js
--- a/browser/devtools/inspector/test/browser_inspector_highlighter-options.js
+++ b/browser/devtools/inspector/test/browser_inspector_highlighter-options.js
@@ -138,16 +138,56 @@ const TEST_DATA = [
       let {points} = yield getHighlighterRegionPath("margin", toolbox.highlighter);
       points = points[0];
 
       is(Math.ceil(topY1), points[0][1], "Top guide's y1 is correct");
       is(Math.floor(rightX1), points[1][0], "Right guide's x1 is correct");
       is(Math.floor(bottomY1), points[2][1], "Bottom guide's y1 is correct");
       is(Math.ceil(leftX1), points[3][0], "Left guide's x1 is correct");
     }
+  },
+  {
+    desc: "When showOnly is used, other regions can be faded",
+    options: {showOnly: "margin", onlyRegionArea: true},
+    checkHighlighter: function*(toolbox) {
+      let h = toolbox.highlighter;
+
+      for (let region of ["margin", "border", "padding", "content"]) {
+        let {d} = yield getHighlighterRegionPath(region, h);
+        ok(d, "Region " + region + " is shown (it has a d attribute)");
+
+        let faded = yield getHighlighterNodeAttribute(h,
+                          "box-model-" + region, "faded");
+        if (region === "margin") {
+          ok(!faded, "The margin region is not faded");
+        } else {
+          is(faded, "true", "Region " + region + " is faded");
+        }
+      }
+    }
+  },
+  {
+    desc: "When showOnly is used, other regions can be faded (2)",
+    options: {showOnly: "padding", onlyRegionArea: true},
+    checkHighlighter: function*(toolbox) {
+      let h = toolbox.highlighter;
+
+      for (let region of ["margin", "border", "padding", "content"]) {
+        let {d} = yield getHighlighterRegionPath(region, h);
+        ok(d, "Region " + region + " is shown (it has a d attribute)");
+
+        let faded = yield getHighlighterNodeAttribute(h,
+                          "box-model-" + region, "faded");
+        if (region === "padding") {
+          ok(!faded, "The padding region is not faded");
+        } else {
+          is(faded, "true", "Region " + region + " is faded");
+        }
+      }
+    }
   }
 ];
 
 add_task(function*() {
   let {inspector, toolbox} = yield openInspectorForURL(TEST_URL);
 
   let divFront = yield getNodeFront("div", inspector);
 
--- a/browser/devtools/layoutview/view.js
+++ b/browser/devtools/layoutview/view.js
@@ -548,17 +548,21 @@ LayoutView.prototype = {
 let elts;
 
 let onmouseover = function(e) {
   let region = e.target.getAttribute("data-box");
   if (!region) {
     return false;
   }
 
-  this.layoutview.showBoxModel({region});
+  this.layoutview.showBoxModel({
+    region,
+    showOnly: region,
+    onlyRegionArea: true
+  });
 
   return false;
 }.bind(window);
 
 let onmouseout = function() {
   this.layoutview.hideBoxModel();
   return false;
 }.bind(window);
--- a/toolkit/devtools/server/actors/highlighter.css
+++ b/toolkit/devtools/server/actors/highlighter.css
@@ -30,16 +30,22 @@
 }
 
 /* Box model highlighter */
 
 :-moz-native-anonymous .box-model-regions {
   opacity: 0.6;
 }
 
+/* Box model regions can be faded (see the onlyRegionArea option in
+   highlighter.js) in order to only display certain regions. */
+:-moz-native-anonymous .box-model-regions [faded] {
+  display: none;
+}
+
 :-moz-native-anonymous .box-model-content {
   fill: #87ceeb;
 }
 
 :-moz-native-anonymous .box-model-padding {
   fill: #6a5acd;
 }
 
--- a/toolkit/devtools/server/actors/highlighter.js
+++ b/toolkit/devtools/server/actors/highlighter.js
@@ -229,17 +229,18 @@ let HighlighterActor = exports.Highlight
       this._highlighter.hide();
     }
   }, {
     request: {
       node: Arg(0, "domnode"),
       region: Option(1),
       hideInfoBar: Option(1),
       hideGuides: Option(1),
-      showOnly: Option(1)
+      showOnly: Option(1),
+      onlyRegionArea: Option(1)
     }
   }),
 
   /**
    * Hide the box model highlighting if it was shown before
    */
   hideBoxModel: method(function() {
     this._highlighter.hide();
@@ -1040,17 +1041,22 @@ AutoRefreshHighlighter.prototype = {
  *   This specifies the region that the guides should outline.
  *   Defaults to "content"
  * - hideGuides {Boolean}
  *   Defaults to false
  * - hideInfoBar {Boolean}
  *   Defaults to false
  * - showOnly {String}
  *   "content", "padding", "border" or "margin"
- *    If set, only this region will be highlighted
+ *   If set, only this region will be highlighted. Use with onlyRegionArea to
+ *   only highlight the area of the region.
+ * - onlyRegionArea {Boolean}
+ *   This can be set to true to make each region's box only highlight the area
+ *   of the corresponding region rather than the area of nested regions too.
+ *   This is useful when used with showOnly.
  *
  * Structure:
  * <div class="highlighter-container">
  *   <div class="box-model-root">
  *     <svg class="box-model-elements" hidden="true">
  *       <g class="box-model-regions">
  *         <path class="box-model-margin" points="..." />
  *         <path class="box-model-border" points="..." />
@@ -1422,59 +1428,100 @@ BoxModelHighlighter.prototype = Heritage
 
   /**
    * Update the box model as per the current node.
    *
    * @return {boolean}
    *         True if the current node has a box model to be highlighted
    */
   _updateBoxModel: function() {
-    this.options.region = this.options.region || "content";
-
-    if (this._nodeNeedsHighlighting()) {
-      for (let boxType of BOX_MODEL_REGIONS) {
-        let box = this.getElement(boxType);
-
-        if (this.regionFill[boxType]) {
-          box.setAttribute("style", "fill:" + this.regionFill[boxType]);
-        } else {
-          box.setAttribute("style", "");
-        }
-
-        if (!this.options.showOnly || this.options.showOnly === boxType) {
-          // Highlighting all quads.
-          let path = [];
-          for (let {p1, p2, p3, p4} of this.currentQuads[boxType]) {
-            path.push("M" + p1.x + "," + p1.y + " " +
-                      "L" + p2.x + "," + p2.y + " " +
-                      "L" + p3.x + "," + p3.y + " " +
-                      "L" + p4.x + "," + p4.y);
-          }
-
-          box.setAttribute("d", path.join(" "));
+    let options = this.options;
+    options.region = options.region || "content";
+
+    if (!this._nodeNeedsHighlighting()) {
+      this._hideBoxModel();
+      return false;
+    }
+
+    for (let i = 0; i < BOX_MODEL_REGIONS.length; i++) {
+      let boxType = BOX_MODEL_REGIONS[i];
+      let nextBoxType = BOX_MODEL_REGIONS[i + 1];
+      let box = this.getElement(boxType);
+
+      if (this.regionFill[boxType]) {
+        box.setAttribute("style", "fill:" + this.regionFill[boxType]);
+      } else {
+        box.setAttribute("style", "");
+      }
+
+      // Highlight all quads for this region by setting the "d" attribute of the
+      // corresponding <path>.
+      let path = [];
+      for (let j = 0; j < this.currentQuads[boxType].length; j++) {
+        let boxQuad = this.currentQuads[boxType][j];
+        let nextBoxQuad = this.currentQuads[nextBoxType]
+                          ? this.currentQuads[nextBoxType][j]
+                          : null;
+        path.push(this._getBoxPathCoordinates(boxQuad, nextBoxQuad));
+      }
+
+      box.setAttribute("d", path.join(" "));
+      box.removeAttribute("faded");
+
+      // If showOnly is defined, either hide the other regions, or fade them out
+      // if onlyRegionArea is set too.
+      if (options.showOnly && options.showOnly !== boxType) {
+        if (options.onlyRegionArea) {
+          box.setAttribute("faded", "true");
         } else {
           box.removeAttribute("d");
         }
-
-        if (boxType === this.options.region && !this.options.hideGuides) {
-          this._showGuides(boxType);
-        } else if (this.options.hideGuides) {
-          this._hideGuides();
-        }
+      }
+
+      if (boxType === options.region && !options.hideGuides) {
+        this._showGuides(boxType);
+      } else if (options.hideGuides) {
+        this._hideGuides();
       }
-
-      // Un-zoom the root wrapper if the page was zoomed.
-      let rootId = this.ID_CLASS_PREFIX + "root";
-      this.markup.scaleRootElement(this.currentNode, rootId);
-
-      return true;
     }
 
-    this._hideBoxModel();
-    return false;
+    // Un-zoom the root wrapper if the page was zoomed.
+    let rootId = this.ID_CLASS_PREFIX + "root";
+    this.markup.scaleRootElement(this.currentNode, rootId);
+
+    return true;
+  },
+
+  _getBoxPathCoordinates: function(boxQuad, nextBoxQuad) {
+    let {p1, p2, p3, p4} = boxQuad;
+
+    let path;
+    if (!nextBoxQuad || !this.options.onlyRegionArea) {
+      // If this is the content box (inner-most box) or if we're not being asked
+      // to highlight only region areas, then draw a simple rectangle.
+      path = "M" + p1.x + "," + p1.y + " " +
+             "L" + p2.x + "," + p2.y + " " +
+             "L" + p3.x + "," + p3.y + " " +
+             "L" + p4.x + "," + p4.y;
+    } else {
+      // Otherwise, just draw the region itself, not a filled rectangle.
+      let {p1: np1, p2: np2, p3: np3, p4: np4} = nextBoxQuad;
+      path = "M" + p1.x + "," + p1.y + " " +
+             "L" + p2.x + "," + p2.y + " " +
+             "L" + p3.x + "," + p3.y + " " +
+             "L" + p4.x + "," + p4.y + " " +
+             "L" + p1.x + "," + p1.y + " " +
+             "L" + np1.x + "," + np1.y + " " +
+             "L" + np4.x + "," + np4.y + " " +
+             "L" + np3.x + "," + np3.y + " " +
+             "L" + np2.x + "," + np2.y + " " +
+             "L" + np1.x + "," + np1.y;
+    }
+
+    return path;
   },
 
   _nodeNeedsHighlighting: function() {
     let hasNoQuads = !this.currentQuads.margin.length &&
                      !this.currentQuads.border.length &&
                      !this.currentQuads.padding.length &&
                      !this.currentQuads.content.length;
     if (!this.currentNode ||