Bug 1139925 - Make the BoxModelHighlighter highlight all quads and draw guides around the outer-most rect. r=miker, a=sledru
authorPatrick Brosset <pbrosset@mozilla.com>
Fri, 13 Mar 2015 15:54:10 +0100
changeset 258421 e2f81a3ca1e5
parent 258420 7f7236a5b6dd
child 258422 d2bade84e15e
push id4665
push userryanvm@gmail.com
push date2015-04-10 17:13 +0000
treeherdermozilla-beta@6d9fdd280e65 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmiker, sledru
bugs1139925
milestone38.0
Bug 1139925 - Make the BoxModelHighlighter highlight all quads and draw guides around the outer-most rect. r=miker, a=sledru LayoutHelpers.getAdjustedBoxQuads now returns all quads that el.getBoxQuads returns. The BoxModelHighlighter calculates an outer rect based on these to draw the guides. And if the element has more than 1 quad (inline element that spans line breaks), then all quads are highlighted. Also all related tests were modified and a couple of new tests were added.
browser/devtools/inspector/test/browser.ini
browser/devtools/inspector/test/browser_inspector_highlighter-02.js
browser/devtools/inspector/test/browser_inspector_highlighter-03.js
browser/devtools/inspector/test/browser_inspector_highlighter-csstransform_02.js
browser/devtools/inspector/test/browser_inspector_highlighter-inline.js
browser/devtools/inspector/test/browser_inspector_highlighter-options.js
browser/devtools/inspector/test/browser_inspector_highlighter-zoom.js
browser/devtools/inspector/test/doc_frame_script.js
browser/devtools/inspector/test/doc_inspector_highlighter.html
browser/devtools/inspector/test/doc_inspector_highlighter_inline.html
browser/devtools/inspector/test/head.js
browser/devtools/shared/test/browser_layoutHelpers-getBoxQuads.html
browser/devtools/shared/test/browser_layoutHelpers-getBoxQuads.js
toolkit/devtools/LayoutHelpers.jsm
toolkit/devtools/server/actors/highlighter.js
--- a/browser/devtools/inspector/test/browser.ini
+++ b/browser/devtools/inspector/test/browser.ini
@@ -5,16 +5,17 @@ support-files =
   doc_inspector_breadcrumbs.html
   doc_inspector_delete-selected-node-01.html
   doc_inspector_delete-selected-node-02.html
   doc_inspector_gcli-inspect-command.html
   doc_inspector_highlight_after_transition.html
   doc_inspector_highlighter-comments.html
   doc_inspector_highlighter_csstransform.html
   doc_inspector_highlighter.html
+  doc_inspector_highlighter_inline.html
   doc_inspector_highlighter_rect.html
   doc_inspector_highlighter_rect_iframe.html
   doc_inspector_infobar_01.html
   doc_inspector_infobar_02.html
   doc_inspector_menu.html
   doc_inspector_remove-iframe-during-load.html
   doc_inspector_search.html
   doc_inspector_search-suggestions.html
@@ -38,16 +39,17 @@ skip-if = e10s # GCLI isn't e10s compati
 [browser_inspector_highlighter-by-type.js]
 [browser_inspector_highlighter-comments.js]
 [browser_inspector_highlighter-csstransform_01.js]
 [browser_inspector_highlighter-csstransform_02.js]
 [browser_inspector_highlighter-hover_01.js]
 [browser_inspector_highlighter-hover_02.js]
 [browser_inspector_highlighter-hover_03.js]
 [browser_inspector_highlighter-iframes.js]
+[browser_inspector_highlighter-inline.js]
 [browser_inspector_highlighter-options.js]
 [browser_inspector_highlighter-rect_01.js]
 [browser_inspector_highlighter-rect_02.js]
 [browser_inspector_highlighter-selector_01.js]
 [browser_inspector_highlighter-selector_02.js]
 [browser_inspector_highlighter-zoom.js]
 [browser_inspector_iframe-navigation.js]
 [browser_inspector_infobar_01.js]
--- a/browser/devtools/inspector/test/browser_inspector_highlighter-02.js
+++ b/browser/devtools/inspector/test/browser_inspector_highlighter-02.js
@@ -16,27 +16,27 @@ add_task(function*() {
   info("Selecting the simple, non-transformed DIV");
   yield selectAndHighlightNode("#simple-div", inspector);
 
   let isVisible = yield isHighlighting(toolbox);
   ok(isVisible, "The highlighter is shown");
   let highlightedNode = yield getHighlitNode(toolbox);
   is(highlightedNode, getNode("#simple-div"),
     "The highlighter's outline corresponds to the simple div");
-  yield isNodeCorrectlyHighlighted(getNode("#simple-div"), toolbox,
+  yield isNodeCorrectlyHighlighted("#simple-div", toolbox,
     "non-zoomed");
 
   info("Selecting the rotated DIV");
   yield selectAndHighlightNode("#rotated-div", inspector);
 
   isVisible = yield isHighlighting(toolbox);
   ok(isVisible, "The highlighter is shown");
-  yield isNodeCorrectlyHighlighted(getNode("#rotated-div"), toolbox,
+  yield isNodeCorrectlyHighlighted("#rotated-div", toolbox,
     "rotated");
 
   info("Selecting the zero width height DIV");
   yield selectAndHighlightNode("#widthHeightZero-div", inspector);
 
   isVisible = yield isHighlighting(toolbox);
   ok(isVisible, "The highlighter is shown");
-  yield isNodeCorrectlyHighlighted(getNode("#widthHeightZero-div"), toolbox,
+  yield isNodeCorrectlyHighlighted("#widthHeightZero-div", toolbox,
     "zero width height");
 });
--- a/browser/devtools/inspector/test/browser_inspector_highlighter-03.js
+++ b/browser/devtools/inspector/test/browser_inspector_highlighter-03.js
@@ -40,30 +40,30 @@ add_task(function* () {
 
   info("Waiting for element picker to become active.");
   yield toolbox.highlighterUtils.startPicker();
 
   info("Moving mouse over iframe padding.");
   yield moveMouseOver(iframeNode, 1, 1);
 
   info("Performing checks");
-  yield isNodeCorrectlyHighlighted(iframeNode, toolbox);
+  yield isNodeCorrectlyHighlighted("iframe", toolbox);
 
   info("Scrolling the document");
   iframeNode.style.marginBottom = content.innerHeight + "px";
   content.scrollBy(0, 40);
 
   let iframeBodyNode = iframeNode.contentDocument.body;
 
   info("Moving mouse over iframe body");
   yield moveMouseOver(iframeNode, 40, 40);
 
   let highlightedNode = yield getHighlitNode(toolbox);
   is(highlightedNode, iframeBodyNode, "highlighter shows the right node");
-  yield isNodeCorrectlyHighlighted(iframeBodyNode, toolbox);
+  yield isNodeCorrectlyHighlighted("iframe || body", toolbox);
 
   info("Waiting for the element picker to deactivate.");
   yield inspector.toolbox.highlighterUtils.stopPicker();
 
   function moveMouseOver(node, x, y) {
     info("Waiting for element " + node + " to be highlighted");
     executeInContent("Test:SynthesizeMouse", {x, y, options: {type: "mousemove"}},
                      {node}, false);
--- a/browser/devtools/inspector/test/browser_inspector_highlighter-csstransform_02.js
+++ b/browser/devtools/inspector/test/browser_inspector_highlighter-csstransform_02.js
@@ -21,24 +21,25 @@ transform highlighter applies those valu
 const TEST_URL = TEST_URL_ROOT + "doc_inspector_highlighter_csstransform.html";
 
 add_task(function*() {
   let {inspector, toolbox} = yield openInspectorForURL(TEST_URL);
   let front = inspector.inspector;
 
   let highlighter = yield front.getHighlighterByType("CssTransformHighlighter");
 
-  let node = getNode("#test-node");
   let nodeFront = yield getNodeFront("#test-node", inspector);
 
   info("Displaying the transform highlighter on test node");
   yield highlighter.show(nodeFront);
 
-  let {data} = yield executeInContent("Test:GetAllAdjustedQuads", null, {node});
-  let expected = data.border;
+  let {data} = yield executeInContent("Test:GetAllAdjustedQuads", {
+    selector: "#test-node"
+  });
+  let [expected] = data.border;
 
   let points = yield getHighlighterNodeAttribute(highlighter,
     "css-transform-transformed", "points");
   let polygonPoints = points.split(" ").map(p => {
     return {
       x: +p.substring(0, p.indexOf(",")),
       y: +p.substring(p.indexOf(",")+1)
     };
new file mode 100644
--- /dev/null
+++ b/browser/devtools/inspector/test/browser_inspector_highlighter-inline.js
@@ -0,0 +1,72 @@
+/* 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";
+
+// Test that highlighting various inline boxes displays the right number of
+// polygons in the page.
+
+const TEST_URL = TEST_URL_ROOT + "doc_inspector_highlighter_inline.html";
+const TEST_DATA = [
+  "body",
+  "h1",
+  "h2",
+  "h2 em",
+  "p",
+  "p span",
+  // The following test case used to fail. See bug 1139925.
+  "[dir=rtl] > span"
+];
+
+add_task(function*() {
+  info("Loading the test document and opening the inspector");
+  let {toolbox, inspector} = yield openInspectorForURL(TEST_URL);
+
+  for (let selector of TEST_DATA) {
+    info("Selecting and highlighting node " + selector);
+    yield selectAndHighlightNode(selector, inspector);
+
+    info("Get all quads for this node");
+    let {data} = yield executeInContent("Test:GetAllAdjustedQuads", {selector});
+
+    info("Iterate over the box-model regions and verify that the highlighter is correct");
+    for (let region of ["margin", "border", "padding", "content"]) {
+      let {points} = yield getHighlighterRegionPath(region, toolbox.highlighter);
+      is(points.length, data[region].length,
+        "The highlighter's " + region + " path defines the correct number of boxes");
+    }
+
+    info("Verify that the guides define a rectangle that contains all content boxes");
+
+    let expectedContentRect = {
+      p1: {x: Infinity, y: Infinity},
+      p2: {x: -Infinity, y: Infinity},
+      p3: {x: -Infinity, y: -Infinity},
+      p4: {x: Infinity, y: -Infinity}
+    };
+    for (let {p1, p2, p3, p4} of data.content) {
+      expectedContentRect.p1.x = Math.min(expectedContentRect.p1.x, p1.x);
+      expectedContentRect.p1.y = Math.min(expectedContentRect.p1.y, p1.y);
+      expectedContentRect.p2.x = Math.max(expectedContentRect.p2.x, p2.x);
+      expectedContentRect.p2.y = Math.min(expectedContentRect.p2.y, p2.y);
+      expectedContentRect.p3.x = Math.max(expectedContentRect.p3.x, p3.x);
+      expectedContentRect.p3.y = Math.max(expectedContentRect.p3.y, p3.y);
+      expectedContentRect.p4.x = Math.min(expectedContentRect.p4.x, p4.x);
+      expectedContentRect.p4.y = Math.max(expectedContentRect.p4.y, p4.y);
+    }
+
+    let contentRect = yield getGuidesRectangle(toolbox);
+
+    for (let point of ["p1", "p2", "p3", "p4"]) {
+      is((contentRect[point].x),
+         (expectedContentRect[point].x),
+         "x coordinate of point " + point +
+         " of the content rectangle defined by the outer guides is correct");
+      is((contentRect[point].y),
+         (expectedContentRect[point].y),
+         "y coordinate of point " + point +
+         " of the content rectangle defined by the outer guides is correct");
+    }
+  }
+});
--- a/browser/devtools/inspector/test/browser_inspector_highlighter-options.js
+++ b/browser/devtools/inspector/test/browser_inspector_highlighter-options.js
@@ -36,19 +36,18 @@ const TEST_DATA = [
       }
     }
   },
   {
     desc: "All regions should be shown by default",
     options: {},
     checkHighlighter: function*(toolbox) {
       for (let region of ["margin", "border", "padding", "content"]) {
-        let points = yield getHighlighterNodeAttribute(toolbox.highlighter,
-          "box-model-" + region, "points");
-        ok(points, "Region " + region + " has set coordinates");
+        let {d} = yield getHighlighterRegionPath(region, toolbox.highlighter);
+        ok(d, "Region " + region + " has set coordinates");
       }
     }
   },
   {
     desc: "Guides can be hidden",
     options: {hideGuides: true},
     checkHighlighter: function*(toolbox) {
       for (let side of ["top", "right", "bottom", "left"]) {
@@ -66,70 +65,61 @@ const TEST_DATA = [
         "box-model-nodeinfobar-container", "hidden");
       is(hidden, "true", "nodeinfobar has been hidden");
     }
   },
   {
     desc: "One region only can be shown (1)",
     options: {showOnly: "content"},
     checkHighlighter: function*(toolbox) {
-      let points = yield getHighlighterNodeAttribute(toolbox.highlighter,
-        "box-model-margin", "points");
-      ok(!points, "margin region is hidden");
+      let {d} = yield getHighlighterRegionPath("margin", toolbox.highlighter);
+      ok(!d, "margin region is hidden");
 
-      points = yield getHighlighterNodeAttribute(toolbox.highlighter,
-        "box-model-border", "points");
-      ok(!points, "border region is hidden");
+      ({d}) = yield getHighlighterRegionPath("border", toolbox.highlighter);
+      ok(!d, "border region is hidden");
 
-      points = yield getHighlighterNodeAttribute(toolbox.highlighter,
-        "box-model-padding", "points");
-      ok(!points, "padding region is hidden");
+      ({d}) = yield getHighlighterRegionPath("padding", toolbox.highlighter);
+      ok(!d, "padding region is hidden");
 
-      points = yield getHighlighterNodeAttribute(toolbox.highlighter,
-        "box-model-content", "points");
-      ok(points, "content region is shown");
+      ({d}) = yield getHighlighterRegionPath("content", toolbox.highlighter);
+      ok(d, "content region is shown");
     }
   },
   {
     desc: "One region only can be shown (2)",
     options: {showOnly: "margin"},
     checkHighlighter: function*(toolbox) {
-      let points = yield getHighlighterNodeAttribute(toolbox.highlighter,
-        "box-model-margin", "points");
-      ok(points, "margin region is shown");
+      let {d} = yield getHighlighterRegionPath("margin", toolbox.highlighter);
+      ok(d, "margin region is shown");
 
-      points = yield getHighlighterNodeAttribute(toolbox.highlighter,
-        "box-model-border", "points");
-      ok(!points, "border region is hidden");
+      ({d}) = yield getHighlighterRegionPath("border", toolbox.highlighter);
+      ok(!d, "border region is hidden");
 
-      points = yield getHighlighterNodeAttribute(toolbox.highlighter,
-        "box-model-padding", "points");
-      ok(!points, "padding region is hidden");
+      ({d}) = yield getHighlighterRegionPath("padding", toolbox.highlighter);
+      ok(!d, "padding region is hidden");
 
-      points = yield getHighlighterNodeAttribute(toolbox.highlighter,
-        "box-model-content", "points");
-      ok(!points, "content region is hidden");
+      ({d}) = yield getHighlighterRegionPath("content", toolbox.highlighter);
+      ok(!d, "content region is hidden");
     }
   },
   {
     desc: "Guides can be drawn around a given region (1)",
     options: {region: "padding"},
     checkHighlighter: function*(toolbox) {
       let topY1 = yield getHighlighterNodeAttribute(toolbox.highlighter,
         "box-model-guide-top", "y1");
       let rightX1 = yield getHighlighterNodeAttribute(toolbox.highlighter,
         "box-model-guide-right", "x1");
       let bottomY1 = yield getHighlighterNodeAttribute(toolbox.highlighter,
         "box-model-guide-bottom", "y1");
       let leftX1 = yield getHighlighterNodeAttribute(toolbox.highlighter,
         "box-model-guide-left", "x1");
 
-      let points = yield getHighlighterNodeAttribute(toolbox.highlighter,
-        "box-model-padding", "points");
-      points = points.split(" ").map(xy => xy.split(","));
+      let {points} = yield getHighlighterRegionPath("padding", 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");
     }
   },
   {
@@ -140,20 +130,19 @@ const TEST_DATA = [
         "box-model-guide-top", "y1");
       let rightX1 = yield getHighlighterNodeAttribute(toolbox.highlighter,
         "box-model-guide-right", "x1");
       let bottomY1 = yield getHighlighterNodeAttribute(toolbox.highlighter,
         "box-model-guide-bottom", "y1");
       let leftX1 = yield getHighlighterNodeAttribute(toolbox.highlighter,
         "box-model-guide-left", "x1");
 
-      let points = yield getHighlighterNodeAttribute(toolbox.highlighter,
-        "box-model-margin", "points");
+      let {points} = yield getHighlighterRegionPath("margin", toolbox.highlighter);
+      points = points[0];
 
-      points = points.split(" ").map(xy => xy.split(","));
       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");
     }
   }
 ];
 
--- a/browser/devtools/inspector/test/browser_inspector_highlighter-zoom.js
+++ b/browser/devtools/inspector/test/browser_inspector_highlighter-zoom.js
@@ -36,17 +36,17 @@ add_task(function*() {
   for (let {level, expected} of TEST_LEVELS) {
     info("Zoom to level " + level + " and check that the highlighter is correct");
 
     let {actorID, connPrefix} = getHighlighterActorID(toolbox.highlighter);
     yield zoomPageTo(level, actorID, connPrefix);
     isVisible = yield isHighlighting(toolbox);
     ok(isVisible, "The highlighter is still visible at zoom level " + level);
 
-    yield isNodeCorrectlyHighlighted(getNode("div"), toolbox);
+    yield isNodeCorrectlyHighlighted("div", toolbox);
 
     info("Check that the highlighter root wrapper node was scaled down");
 
     let style = yield getRootNodeStyle(toolbox);
     is(style, expected, "The style attribute of the root element is correct");
   }
 });
 
--- a/browser/devtools/inspector/test/doc_frame_script.js
+++ b/browser/devtools/inspector/test/doc_frame_script.js
@@ -209,22 +209,24 @@ addMessageListener("Test:ElementFromPoin
 
   let helper = new LayoutHelpers(content);
   let element = helper.getElementFromPoint(content.document, x, y);
   sendAsyncMessage("Test:ElementFromPoint", null, {element});
 });
 
 /**
  * Get all box-model regions' adjusted boxquads for the given element
- * @param {Object} msg The msg.objects part should be the element
+ * @param {Object} msg The msg.data part should contain the node selector.
  * @return {Object} An object with each property being a box-model region, each
- * of them being an object with the p1/p2/p3/p4 properties
+ * of them being an array of objects with the p1/p2/p3/p4 properties.
  */
 addMessageListener("Test:GetAllAdjustedQuads", function(msg) {
-  let {node} = msg.objects;
+  let {selector} = msg.data;
+  let node = superQuerySelector(selector);
+
   let regions = {};
 
   let helper = new LayoutHelpers(content);
   for (let boxType of ["content", "padding", "border", "margin"]) {
     regions[boxType] = helper.getAdjustedQuads(node, boxType);
   }
 
   sendAsyncMessage("Test:GetAllAdjustedQuads", regions);
@@ -240,17 +242,21 @@ addMessageListener("Test:GetAllAdjustedQ
  * - {Boolean} center If set to true, x/y will be ignored and
  *             synthesizeMouseAtCenter will be used instead
  * - {Object} options Other event options
  * The msg.objects part should be the element.
  * @param {Object} data Event detail properties:
  */
 addMessageListener("Test:SynthesizeMouse", function(msg) {
   let {node} = msg.objects;
-  let {x, y, center, options} = msg.data;
+  let {x, y, center, options, selector} = msg.data;
+
+  if (!node && selector) {
+    node = superQuerySelector(selector);
+  }
 
   if (center) {
     EventUtils.synthesizeMouseAtCenter(node, options, node.ownerDocument.defaultView);
   } else {
     EventUtils.synthesizeMouse(node, x, y, options, node.ownerDocument.defaultView);
   }
 
   // Most consumers won't need to listen to this message, unless they want to
@@ -268,9 +274,35 @@ addMessageListener("Test:SynthesizeMouse
  * @return {Boolean}
  */
 addMessageListener("Test:HasPseudoClassLock", function(msg) {
   let {node} = msg.objects;
   let {pseudo} = msg.data
   sendAsyncMessage("Test:HasPseudoClassLock", DOMUtils.hasPseudoClassLock(node, pseudo));
 });
 
+/**
+ * Like document.querySelector but can go into iframes too.
+ * ".container iframe || .sub-container div" will first try to find the node
+ * matched by ".container iframe" in the root document, then try to get the
+ * content document inside it, and then try to match ".sub-container div" inside
+ * this document.
+ * Any selector coming before the || separator *MUST* match a frame node.
+ * @param {String} superSelector.
+ * @return {DOMNode} The node, or null if not found.
+ */
+function superQuerySelector(superSelector, root=content.document) {
+  let frameIndex = superSelector.indexOf("||");
+  if (frameIndex === -1) {
+    return root.querySelector(superSelector);
+  } else {
+    let rootSelector = superSelector.substring(0, frameIndex).trim();
+    let childSelector = superSelector.substring(frameIndex+2).trim();
+    root = root.querySelector(rootSelector);
+    if (!root || !root.contentWindow) {
+      return null;
+    }
+
+    return superQuerySelector(childSelector, root.contentWindow.document);
+  }
+}
+
 let dumpn = msg => dump(msg + "\n");
--- a/browser/devtools/inspector/test/doc_inspector_highlighter.html
+++ b/browser/devtools/inspector/test/doc_inspector_highlighter.html
@@ -1,11 +1,12 @@
 <!DOCTYPE html>
 <html>
   <head>
+    <meta charset="utf-8">
     <style>
     div {
       position:absolute;
     }
 
     #simple-div {
       padding: 5px;
       border: 7px solid red;
new file mode 100644
--- /dev/null
+++ b/browser/devtools/inspector/test/doc_inspector_highlighter_inline.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <style>
+      html {
+        height: 100%;
+        background: #eee;
+      }
+      body {
+        margin: 0 auto;
+        padding: 1em;
+        box-sizing: border-box;
+        width: 500px;
+        height: 100%;
+        background: white;
+        font-family: Arial;
+        font-size: 15px;
+        line-height: 40px;
+      }
+      p span {
+        padding: 5px 0;
+        margin: 0 5px;
+        border: 5px solid #eee;
+      }
+    </style>
+  </head>
+  <body>
+    <h1>Lorem Ipsum</h1>
+    <h2>Lorem ipsum <em>dolor sit amet</em></h2>
+    <p><span>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed varius, nisl eget semper maximus, dui tellus tempor leo, at pharetra eros tortor sed odio. Nullam sagittis ex nec mi sagittis pulvinar. Pellentesque dapibus feugiat fermentum. Curabitur lacinia quis enim et tristique. Aliquam in semper massa. In ac vulputate nunc, at rutrum neque. Fusce condimentum, tellus quis placerat imperdiet, dolor tortor mattis erat, nec luctus magna diam pharetra mauris.</span></p>
+    <div dir="rtl">
+      <span><span></span>some ltr text in an rtl container</span>
+    </div>
+  </body>
+</html>
--- a/browser/devtools/inspector/test/head.js
+++ b/browser/devtools/inspector/test/head.js
@@ -301,65 +301,105 @@ let getBoxModelStatus = Task.async(funct
   ret.guides = {};
   for (let guide of ["top", "right", "bottom", "left"]) {
     ret.guides[guide] = yield getGuideStatus(guide, toolbox);
   }
 
   return ret;
 });
 
-let getGuideStatus = Task.async(function*(location, toolbox) {
+/**
+ * Get data about one of the toolbox box-model highlighter's guides.
+ * @param {String} location One of top, right, bottom, left.
+ * @param {Toolbox} toolbox The toolbox instance, used to retrieve the highlighter.
+ * @return {Object} The returned object has the following form:
+ * - visible {Boolean} Whether that guide is visible.
+ * - x1/y1/x2/y2 {String} The <line>'s coordinates.
+ */
+let getGuideStatus = Task.async(function*(location, {highlighter}) {
   let id = "box-model-guide-" + location;
 
-  let hidden = yield getHighlighterNodeAttribute(toolbox.highlighter, id, "hidden");
-  let x1 = yield getHighlighterNodeAttribute(toolbox.highlighter, id, "x1");
-  let y1 = yield getHighlighterNodeAttribute(toolbox.highlighter, id, "y1");
-  let x2 = yield getHighlighterNodeAttribute(toolbox.highlighter, id, "x2");
-  let y2 = yield getHighlighterNodeAttribute(toolbox.highlighter, id, "y2");
+  let hidden = yield getHighlighterNodeAttribute(highlighter, id, "hidden");
+  let x1 = yield getHighlighterNodeAttribute(highlighter, id, "x1");
+  let y1 = yield getHighlighterNodeAttribute(highlighter, id, "y1");
+  let x2 = yield getHighlighterNodeAttribute(highlighter, id, "x2");
+  let y2 = yield getHighlighterNodeAttribute(highlighter, id, "y2");
 
   return {
     visible: !hidden,
     x1: x1,
     y1: y1,
     x2: x2,
     y2: y2
   };
 });
 
 /**
- * Get the coordinate (points attribute) from one of the polygon elements in the
- * box model highlighter.
+ * Get the coordinates of the rectangle that is defined by the 4 guides displayed
+ * in the toolbox box-model highlighter.
+ * @param {Toolbox} toolbox The toolbox instance, used to retrieve the highlighter.
+ * @return {Object} Null if at least one guide is hidden. Otherwise an object
+ * with p1, p2, p3, p4 properties being {x, y} objects.
+ */
+let getGuidesRectangle = Task.async(function*(toolbox) {
+  let tGuide = yield getGuideStatus("top", toolbox);
+  let rGuide = yield getGuideStatus("right", toolbox);
+  let bGuide = yield getGuideStatus("bottom", toolbox);
+  let lGuide = yield getGuideStatus("left", toolbox);
+
+  if (!tGuide.visible || !rGuide.visible || !bGuide.visible || !lGuide.visible) {
+    return null;
+  }
+
+  return {
+    p1: {x: lGuide.x1, y: tGuide.y1},
+    p2: {x: rGuide.x1, y: tGuide. y1},
+    p3: {x: rGuide.x1, y: bGuide.y1},
+    p4: {x: lGuide.x1, y: bGuide.y1}
+  };
+});
+
+/**
+ * Get the coordinate (points defined by the d attribute) from one of the path
+ * elements in the box model highlighter.
  */
 let getPointsForRegion = Task.async(function*(region, toolbox) {
-  let points = yield getHighlighterNodeAttribute(toolbox.highlighter,
-                                                 "box-model-" + region, "points");
-  points = points.split(/[, ]/);
+  let d = yield getHighlighterNodeAttribute(toolbox.highlighter,
+                                            "box-model-" + region, "d");
+  let polygons = d.match(/M[^M]+/g);
+  if (!polygons) {
+    return null;
+  }
+
+  let points = polygons[0].trim().split(" ").map(i => {
+    return i.replace(/M|L/, "").split(",")
+  });
 
   return {
     p1: {
-      x: parseFloat(points[0]),
-      y: parseFloat(points[1])
+      x: parseFloat(points[0][0]),
+      y: parseFloat(points[0][1])
     },
     p2: {
-      x: parseFloat(points[2]),
-      y: parseFloat(points[3])
+      x: parseFloat(points[1][0]),
+      y: parseFloat(points[1][1])
     },
     p3: {
-      x: parseFloat(points[4]),
-      y: parseFloat(points[5])
+      x: parseFloat(points[2][0]),
+      y: parseFloat(points[2][1])
     },
     p4: {
-      x: parseFloat(points[6]),
-      y: parseFloat(points[7])
+      x: parseFloat(points[3][0]),
+      y: parseFloat(points[3][1])
     }
   };
 });
 
 /**
- * Is a given region polygon element of the box-model highlighter currently
+ * Is a given region path element of the box-model highlighter currently
  * hidden?
  */
 let isRegionHidden = Task.async(function*(region, toolbox) {
   let value = yield getHighlighterNodeAttribute(toolbox.highlighter,
                                                 "box-model-" + region, "hidden");
   return value !== null;
 });
 
@@ -382,37 +422,34 @@ let getHighlitNode = Task.async(function
     let {objects} = yield executeInContent("Test:ElementFromPoint", {x, y});
     return objects.element;
   }
 });
 
 /**
  * Assert that the box-model highlighter's current position corresponds to the
  * given node boxquads.
- * @param {DOMNode|CPOW} node The node to get the boxQuads from
+ * @param {String} selector The selector for the node to get the boxQuads from
  * @param {String} prefix An optional prefix for logging information to the
  * console.
  */
-let isNodeCorrectlyHighlighted = Task.async(function*(node, toolbox, prefix="") {
-  prefix += (prefix ? " " : "") + node.nodeName;
-  prefix += (node.id ? "#" + node.id : "");
-  prefix += (node.classList.length ? "." + [...node.classList].join(".") : "");
-  prefix += " ";
-
+let isNodeCorrectlyHighlighted = Task.async(function*(selector, toolbox, prefix="") {
   let boxModel = yield getBoxModelStatus(toolbox);
-  let {data: regions} = yield executeInContent("Test:GetAllAdjustedQuads", null,
-                                               {node});
+  let {data: regions} = yield executeInContent("Test:GetAllAdjustedQuads",
+                                               {selector});
 
   for (let boxType of ["content", "padding", "border", "margin"]) {
-    let quads = regions[boxType];
+    let [quad] = regions[boxType];
     for (let point in boxModel[boxType].points) {
-      is(boxModel[boxType].points[point].x, quads[point].x,
-        prefix + boxType + " point " + point + " x coordinate is correct");
-      is(boxModel[boxType].points[point].y, quads[point].y,
-        prefix + boxType + " point " + point + " y coordinate is correct");
+      is(boxModel[boxType].points[point].x, quad[point].x,
+        "Node " + selector + " " + boxType + " point " + point +
+        " x coordinate is correct");
+      is(boxModel[boxType].points[point].y, quad[point].y,
+        "Node " + selector + " " + boxType + " point " + point +
+        " y coordinate is correct");
     }
   }
 });
 
 function synthesizeKeyFromKeyTag(aKeyId, aDocument = null) {
   let document = aDocument || document;
   let key = document.getElementById(aKeyId);
   isnot(key, null, "Successfully retrieved the <key> node");
@@ -548,16 +585,47 @@ let zoomPageTo = Task.async(function*(le
 let getHighlighterNodeAttribute = Task.async(function*(highlighter, nodeID, name) {
   let {actorID, connPrefix} = getHighlighterActorID(highlighter);
   let {data: value} = yield executeInContent("Test:GetHighlighterAttribute",
                                              {nodeID, name, actorID, connPrefix});
   return value;
 });
 
 /**
+ * Get the "d" attribute value for one of the box-model highlighter's region
+ * <path> elements, and parse it to a list of points.
+ * @param {String} region The box model region name.
+ * @param {Front} highlighter The front of the highlighter.
+ * @return {Object} The object returned has the following form:
+ * - d {String} the d attribute value
+ * - points {Array} an array of all the polygons defined by the path. Each box
+ *   is itself an Array of points, themselves being [x,y] coordinates arrays.
+ */
+let getHighlighterRegionPath = Task.async(function*(region, highlighter) {
+  let d = yield getHighlighterNodeAttribute(highlighter, "box-model-" + region, "d");
+  if (!d) {
+    return {d: null};
+  }
+
+  let polygons = d.match(/M[^M]+/g);
+  if (!polygons) {
+    return {d};
+  }
+
+  let points = [];
+  for (let polygon of polygons) {
+    points.push(polygon.trim().split(" ").map(i => {
+      return i.replace(/M|L/, "").split(",")
+    }));
+  }
+
+  return {d, points};
+});
+
+/**
  * Get the textContent value of one of the highlighter's node.
  * @param {Front} highlighter The front of the highlighter.
  * @param {String} nodeID The Id of the node in the highlighter.
  * @return {String} value
  */
 let getHighlighterNodeTextContent = Task.async(function*(highlighter, nodeID) {
   let {actorID, connPrefix} = getHighlighterActorID(highlighter);
   let {data: value} = yield executeInContent("Test:GetHighlighterTextContent",
--- a/browser/devtools/shared/test/browser_layoutHelpers-getBoxQuads.html
+++ b/browser/devtools/shared/test/browser_layoutHelpers-getBoxQuads.html
@@ -54,9 +54,12 @@
   </iframe>
 </iframe>
  -->
 <iframe src="data:text/html,%3Cstyle%3Ebody%7Bmargin:0;padding:0;%7D%3C/style%3E%3Ciframe%20src=%22data:text/html,%253Cstyle%253Ebody%257Bmargin:0;padding:0;%257D%253C/style%253E%253Cdiv%2520id='inner-node'%2520style='width:100px;height:100px;border:10px%2520solid%2520red;margin:10px;padding:10px;'%253E%253C/div%253E%22%20style=%22margin:10px;border:0;width:200px;height:200px;%22%3E%3C/iframe%3E" style="margin:10px;border:0;width:300px;height:300px;"></iframe>
 <div id="scrolled-node">
   <div id="sub-scrolled-node">
     <div id="inner-scrolled-node"></div>
   </div>
-</div>
\ No newline at end of file
+</div>
+<span id="inline">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus porttitor luctus sem id scelerisque. Cras quis velit sed risus euismod lacinia. Donec viverra enim eu ligula efficitur, quis vulputate metus cursus. Duis sed interdum risus. Ut blandit velit vitae faucibus efficitur. Lorem ipsum dolor sit amet, consectetur adipiscing elit.<br/ >
+Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed vitae dolor metus. Aliquam sed velit sit amet libero vestibulum aliquam vel a lorem. Integer eget ex eget justo auctor ullamcorper.<br/ >
+Praesent tristique maximus lacus, nec ultricies neque ultrices non. Phasellus vel lobortis justo. </span>
\ No newline at end of file
--- a/browser/devtools/shared/test/browser_layoutHelpers-getBoxQuads.js
+++ b/browser/devtools/shared/test/browser_layoutHelpers-getBoxQuads.js
@@ -16,34 +16,35 @@ function test() {
 
     info("Creating a new LayoutHelpers instance for the test window");
     let helper = new LayoutHelpers(win);
     ok(helper.getAdjustedQuads, "getAdjustedQuads is defined");
 
     info("Running tests");
 
     returnsTheRightDataStructure(doc, helper);
-    returnsNullForMissingNode(doc, helper);
-    returnsNullForHiddenNodes(doc, helper);
+    isEmptyForMissingNode(doc, helper);
+    isEmptyForHiddenNodes(doc, helper);
     defaultsToBorderBoxIfNoneProvided(doc, helper);
     returnsLikeGetBoxQuadsInSimpleCase(doc, helper);
     takesIframesOffsetsIntoAccount(doc, helper);
     takesScrollingIntoAccount(doc, helper);
     takesZoomIntoAccount(doc, helper);
+    returnsMultipleItemsForWrappingInlineElements(doc, helper);
 
     gBrowser.removeCurrentTab();
     finish();
   });
 }
 
 function returnsTheRightDataStructure(doc, helper) {
   info("Checks that the returned data contains bounds and 4 points");
 
   let node = doc.querySelector("body");
-  let res = helper.getAdjustedQuads(node, "content");
+  let [res] = helper.getAdjustedQuads(node, "content");
 
   ok("bounds" in res, "The returned data has a bounds property");
   ok("p1" in res, "The returned data has a p1 property");
   ok("p2" in res, "The returned data has a p2 property");
   ok("p3" in res, "The returned data has a p3 property");
   ok("p4" in res, "The returned data has a p4 property");
 
   for (let boundProp of
@@ -53,43 +54,43 @@ function returnsTheRightDataStructure(do
 
   for (let point of ["p1", "p2", "p3", "p4"]) {
     for (let pointProp of ["x", "y", "z", "w"]) {
       ok(pointProp in res[point], point + " has a " + pointProp + " property");
     }
   }
 }
 
-function returnsNullForMissingNode(doc, helper) {
+function isEmptyForMissingNode(doc, helper) {
   info("Checks that null is returned for invalid nodes");
 
   for (let input of [null, undefined, "", 0]) {
-    ok(helper.getAdjustedQuads(input) === null, "null is returned for input " +
-      input);
+    is(helper.getAdjustedQuads(input).length, 0, "A 0-length array is returned" +
+      "for input " + input);
   }
 }
 
-function returnsNullForHiddenNodes(doc, helper) {
+function isEmptyForHiddenNodes(doc, helper) {
   info("Checks that null is returned for nodes that aren't rendered");
 
   let style = doc.querySelector("#styles");
-  ok(helper.getAdjustedQuads(style) === null,
+  is(helper.getAdjustedQuads(style).length, 0,
     "null is returned for a <style> node");
 
   let hidden = doc.querySelector("#hidden-node");
-  ok(helper.getAdjustedQuads(hidden) === null,
+  is(helper.getAdjustedQuads(hidden).length, 0,
     "null is returned for a hidden node");
 }
 
 function defaultsToBorderBoxIfNoneProvided(doc, helper) {
   info("Checks that if no boxtype is passed, then border is the default one");
 
   let node = doc.querySelector("#simple-node-with-margin-padding-border");
-  let withBoxType = helper.getAdjustedQuads(node, "border");
-  let withoutBoxType = helper.getAdjustedQuads(node);
+  let [withBoxType] = helper.getAdjustedQuads(node, "border");
+  let [withoutBoxType] = helper.getAdjustedQuads(node);
 
   for (let boundProp of
     ["bottom", "top", "right", "left", "width", "height", "x", "y"]) {
     is(withBoxType.bounds[boundProp], withoutBoxType.bounds[boundProp],
       boundProp + " bound is equal with or without the border box type");
   }
 
   for (let point of ["p1", "p2", "p3", "p4"]) {
@@ -106,17 +107,17 @@ function returnsLikeGetBoxQuadsInSimpleC
     "that the returned value is similar to the returned value of getBoxQuads");
 
   let node = doc.querySelector("#simple-node-with-margin-padding-border");
 
   for (let region of ["content", "padding", "border", "margin"]) {
     let expected = node.getBoxQuads({
       box: region
     })[0];
-    let actual = helper.getAdjustedQuads(node, region);
+    let [actual] = helper.getAdjustedQuads(node, region);
 
     for (let boundProp of
       ["bottom", "top", "right", "left", "width", "height", "x", "y"]) {
       is(actual.bounds[boundProp], expected.bounds[boundProp],
         boundProp + " bound is equal to the one returned by getBoxQuads for " +
         region + " box");
     }
 
@@ -133,17 +134,17 @@ function returnsLikeGetBoxQuadsInSimpleC
 function takesIframesOffsetsIntoAccount(doc, helper) {
   info("Checks that the quad returned for a node inside iframes that have " +
     "margins takes those offsets into account");
 
   let rootIframe = doc.querySelector("iframe");
   let subIframe = rootIframe.contentDocument.querySelector("iframe");
   let innerNode = subIframe.contentDocument.querySelector("#inner-node");
 
-  let quad = helper.getAdjustedQuads(innerNode, "content");
+  let [quad] = helper.getAdjustedQuads(innerNode, "content");
 
   //rootIframe margin + subIframe margin + node margin + node border + node padding
   let p1x = 10 + 10 + 10 + 10 + 10;
   is(quad.p1.x, p1x, "The inner node's p1 x position is correct");
 
   // Same as p1x + the inner node width
   let p2x = p1x + 100;
   is(quad.p2.x, p2x, "The inner node's p2 x position is correct");
@@ -158,52 +159,64 @@ function takesScrollingIntoAccount(doc, 
 
   info("Scroll the container nodes down");
   let scrolledNode = doc.querySelector("#scrolled-node");
   scrolledNode.scrollTop = 100;
   let subScrolledNode = doc.querySelector("#sub-scrolled-node");
   subScrolledNode.scrollTop = 200;
   let innerNode = doc.querySelector("#inner-scrolled-node");
 
-  let quad = helper.getAdjustedQuads(innerNode, "content");
+  let [quad] = helper.getAdjustedQuads(innerNode, "content");
   is(quad.p1.x, 0, "p1.x of the scrolled node is correct after scrolling down");
   is(quad.p1.y, -300, "p1.y of the scrolled node is correct after scrolling down");
 
   info("Scrolling back up");
   scrolledNode.scrollTop = 0;
   subScrolledNode.scrollTop = 0;
 
-  quad = helper.getAdjustedQuads(innerNode, "content");
+  [quad] = helper.getAdjustedQuads(innerNode, "content");
   is(quad.p1.x, 0, "p1.x of the scrolled node is correct after scrolling up");
   is(quad.p1.y, 0, "p1.y of the scrolled node is correct after scrolling up");
 }
 
 function takesZoomIntoAccount(doc, helper) {
   info("Checks that if the page is zoomed in/out, the quad returned is correct");
 
   // Hard-coding coordinates in this zoom test is a bad idea as it can vary
   // depending on the platform, so we simply test that zooming in produces a
   // bigger quad and zooming out produces a smaller quad
 
   let node = doc.querySelector("#simple-node-with-margin-padding-border");
-  let defaultQuad = helper.getAdjustedQuads(node);
+  let [defaultQuad] = helper.getAdjustedQuads(node);
 
   info("Zoom in");
   window.FullZoom.enlarge();
-  let zoomedInQuad = helper.getAdjustedQuads(node);
+  let [zoomedInQuad] = helper.getAdjustedQuads(node);
 
   ok(zoomedInQuad.bounds.width > defaultQuad.bounds.width,
     "The zoomed in quad is bigger than the default one");
   ok(zoomedInQuad.bounds.height > defaultQuad.bounds.height,
     "The zoomed in quad is bigger than the default one");
 
   info("Zoom out");
   window.FullZoom.reset();
   window.FullZoom.reduce();
-  let zoomedOutQuad = helper.getAdjustedQuads(node);
+  let [zoomedOutQuad] = helper.getAdjustedQuads(node);
 
   ok(zoomedOutQuad.bounds.width < defaultQuad.bounds.width,
     "The zoomed out quad is smaller than the default one");
   ok(zoomedOutQuad.bounds.height < defaultQuad.bounds.height,
     "The zoomed out quad is smaller than the default one");
 
   window.FullZoom.reset();
 }
+
+function returnsMultipleItemsForWrappingInlineElements(doc, helper) {
+  info("Checks that several quads are returned for inline elements that span line-breaks");
+
+  let node = doc.querySelector("#inline");
+  let quads = helper.getAdjustedQuads(node, "content");
+  // At least 3 because of the 2 <br />, maybe more depending on the window size.
+  ok(quads.length >= 3, "Multiple quads were returned");
+
+  is(quads.length, node.getBoxQuads().length,
+    "The same number of boxes as getBoxQuads was returned");
+}
--- a/toolkit/devtools/LayoutHelpers.jsm
+++ b/toolkit/devtools/LayoutHelpers.jsm
@@ -22,77 +22,81 @@ let LayoutHelpers = function(aTopLevelWi
 };
 
 this.LayoutHelpers = LayoutHelpers;
 
 LayoutHelpers.prototype = {
 
   /**
    * Get box quads adjusted for iframes and zoom level.
-
-   * @param {DOMNode} node
-   *        The node for which we are to get the box model region quads
-   * @param  {String} region
-   *         The box model region to return:
-   *         "content", "padding", "border" or "margin"
-   * @return {Object} An object that has the same structure as one quad returned
-   *         by getBoxQuads
+   * @param {DOMNode} node The node for which we are to get the box model region
+   * quads.
+   * @param {String} region The box model region to return: "content",
+   * "padding", "border" or "margin".
+   * @return {Array} An array of objects that have the same structure as quads
+   * returned by getBoxQuads. An empty array if the node has no quads or is
+   * invalid.
    */
   getAdjustedQuads: function(node, region) {
     if (!node || !node.getBoxQuads) {
-      return null;
+      return [];
     }
 
-    let [quads] = node.getBoxQuads({
+    let quads = node.getBoxQuads({
       box: region
     });
 
-    if (!quads) {
-      return null;
+    if (!quads.length) {
+      return [];
     }
 
     let [xOffset, yOffset] = this.getFrameOffsets(node);
     let scale = LayoutHelpers.getCurrentZoom(node);
 
-    return {
-      p1: {
-        w: quads.p1.w * scale,
-        x: quads.p1.x * scale + xOffset,
-        y: quads.p1.y * scale + yOffset,
-        z: quads.p1.z * scale
-      },
-      p2: {
-        w: quads.p2.w * scale,
-        x: quads.p2.x * scale + xOffset,
-        y: quads.p2.y * scale + yOffset,
-        z: quads.p2.z * scale
-      },
-      p3: {
-        w: quads.p3.w * scale,
-        x: quads.p3.x * scale + xOffset,
-        y: quads.p3.y * scale + yOffset,
-        z: quads.p3.z * scale
-      },
-      p4: {
-        w: quads.p4.w * scale,
-        x: quads.p4.x * scale + xOffset,
-        y: quads.p4.y * scale + yOffset,
-        z: quads.p4.z * scale
-      },
-      bounds: {
-        bottom: quads.bounds.bottom * scale + yOffset,
-        height: quads.bounds.height * scale,
-        left: quads.bounds.left * scale + xOffset,
-        right: quads.bounds.right * scale + xOffset,
-        top: quads.bounds.top * scale + yOffset,
-        width: quads.bounds.width * scale,
-        x: quads.bounds.x * scale + xOffset,
-        y: quads.bounds.y * scale + yOffset
-      }
-    };
+    let adjustedQuads = [];
+    for (let quad of quads) {
+      adjustedQuads.push({
+        p1: {
+          w: quad.p1.w * scale,
+          x: quad.p1.x * scale + xOffset,
+          y: quad.p1.y * scale + yOffset,
+          z: quad.p1.z * scale
+        },
+        p2: {
+          w: quad.p2.w * scale,
+          x: quad.p2.x * scale + xOffset,
+          y: quad.p2.y * scale + yOffset,
+          z: quad.p2.z * scale
+        },
+        p3: {
+          w: quad.p3.w * scale,
+          x: quad.p3.x * scale + xOffset,
+          y: quad.p3.y * scale + yOffset,
+          z: quad.p3.z * scale
+        },
+        p4: {
+          w: quad.p4.w * scale,
+          x: quad.p4.x * scale + xOffset,
+          y: quad.p4.y * scale + yOffset,
+          z: quad.p4.z * scale
+        },
+        bounds: {
+          bottom: quad.bounds.bottom * scale + yOffset,
+          height: quad.bounds.height * scale,
+          left: quad.bounds.left * scale + xOffset,
+          right: quad.bounds.right * scale + xOffset,
+          top: quad.bounds.top * scale + yOffset,
+          width: quad.bounds.width * scale,
+          x: quad.bounds.x * scale + xOffset,
+          y: quad.bounds.y * scale + yOffset
+        }
+      });
+    }
+
+    return adjustedQuads;
   },
 
   /**
    * Compute the absolute position and the dimensions of a node, relativalely
    * to the root window.
    *
    * @param {DOMNode} aNode
    *        a DOM element to get the bounds for
--- a/toolkit/devtools/server/actors/highlighter.js
+++ b/toolkit/devtools/server/actors/highlighter.js
@@ -761,19 +761,20 @@ AutoRefreshHighlighter.prototype = {
     this.win = null;
     this.browser = null;
     this.currentNode = null;
     this.layoutHelpers = null;
   }
 };
 
 /**
- * The BoxModelHighlighter is the class that actually draws the the box model
- * regions on top of a node.
- * It is used by the HighlighterActor.
+ * The BoxModelHighlighter draws the box model regions on top of a node.
+ * If the node is a block box, then each region will be displayed as 1 polygon.
+ * If the node is an inline box though, each region may be represented by 1 or
+ * more polygons, depending on how many line boxes the inline element has.
  *
  * Usage example:
  *
  * let h = new BoxModelHighlighter(browser);
  * h.show(node, options);
  * h.hide();
  * h.destroy();
  *
@@ -790,20 +791,20 @@ AutoRefreshHighlighter.prototype = {
  *   "content", "padding", "border" or "margin"
  *    If set, only this region will be highlighted
  *
  * Structure:
  * <div class="highlighter-container">
  *   <div class="box-model-root">
  *     <svg class="box-model-elements" hidden="true">
  *       <g class="box-model-regions">
- *         <polygon class="box-model-margin" points="..." />
- *         <polygon class="box-model-border" points="..." />
- *         <polygon class="box-model-padding" points="..." />
- *         <polygon class="box-model-content" points="..." />
+ *         <path class="box-model-margin" points="..." />
+ *         <path class="box-model-border" points="..." />
+ *         <path class="box-model-padding" points="..." />
+ *         <path class="box-model-content" points="..." />
  *       </g>
  *       <line class="box-model-guide-top" x1="..." y1="..." x2="..." y2="..." />
  *       <line class="box-model-guide-right" x1="..." y1="..." x2="..." y2="..." />
  *       <line class="box-model-guide-bottom" x1="..." y1="..." x2="..." y2="..." />
  *       <line class="box-model-guide-left" x1="..." y1="..." x2="..." y2="..." />
  *     </svg>
  *     <div class="box-model-nodeinfobar-container">
  *       <div class="box-model-nodeinfobar-arrow highlighter-nodeinfobar-arrow-top" />
@@ -891,17 +892,17 @@ BoxModelHighlighter.prototype = Heritage
       attributes: {
         "class": "regions"
       },
       prefix: this.ID_CLASS_PREFIX
     });
 
     for (let region of BOX_MODEL_REGIONS) {
       createSVGNode(this.win, {
-        nodeType: "polygon",
+        nodeType: "path",
         parent: regions,
         attributes: {
           "class": region,
           "id": region
         },
         prefix: this.ID_CLASS_PREFIX
       });
     }
@@ -1104,48 +1105,108 @@ BoxModelHighlighter.prototype = Heritage
    * Show the box model
    */
   _showBoxModel: function() {
     this.markup.removeAttributeForElement(this.ID_CLASS_PREFIX + "elements",
       "hidden");
   },
 
   /**
+   * Calculate an outer quad based on the quads returned by getAdjustedQuads.
+   * The BoxModelHighlighter may highlight more than one boxes, so in this case
+   * create a new quad that "contains" all of these quads.
+   * This is useful to position the guides and nodeinfobar.
+   * This may happen if the BoxModelHighlighter is used to highlight an inline
+   * element that spans line breaks.
+   * @param {String} region The box-model region to get the outer quad for.
+   * @return {Object} A quad-like object {p1,p2,p3,p4,bounds}
+   */
+  _getOuterQuad: function(region) {
+    let quads = this.currentQuads[region];
+    if (!quads.length) {
+      return null;
+    }
+
+    let quad = {
+      p1: {x: Infinity, y: Infinity},
+      p2: {x: -Infinity, y: Infinity},
+      p3: {x: -Infinity, y: -Infinity},
+      p4: {x: Infinity, y: -Infinity},
+      bounds: {
+        bottom: -Infinity,
+        height: 0,
+        left: Infinity,
+        right: -Infinity,
+        top: Infinity,
+        width: 0,
+        x: 0,
+        y: 0,
+      }
+    };
+
+    for (let q of quads) {
+      quad.p1.x = Math.min(quad.p1.x, q.p1.x);
+      quad.p1.y = Math.min(quad.p1.y, q.p1.y);
+      quad.p2.x = Math.max(quad.p2.x, q.p2.x);
+      quad.p2.y = Math.min(quad.p2.y, q.p2.y);
+      quad.p3.x = Math.max(quad.p3.x, q.p3.x);
+      quad.p3.y = Math.max(quad.p3.y, q.p3.y);
+      quad.p4.x = Math.min(quad.p4.x, q.p4.x);
+      quad.p4.y = Math.max(quad.p4.y, q.p4.y);
+
+      quad.bounds.bottom = Math.max(quad.bounds.bottom, q.bounds.bottom);
+      quad.bounds.top = Math.min(quad.bounds.top, q.bounds.top);
+      quad.bounds.left = Math.min(quad.bounds.left, q.bounds.left);
+      quad.bounds.right = Math.max(quad.bounds.right, q.bounds.right);
+    }
+    quad.bounds.x = quad.bounds.left;
+    quad.bounds.y = quad.bounds.top;
+    quad.bounds.width = quad.bounds.right - quad.bounds.left;
+    quad.bounds.height = quad.bounds.bottom - quad.bounds.top;
+
+    return quad;
+  },
+
+  /**
    * 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 {p1, p2, p3, p4} = this.currentQuads[boxType];
-
         if (this.regionFill[boxType]) {
           this.markup.setAttributeForElement(this.ID_CLASS_PREFIX + boxType,
             "style", "fill:" + this.regionFill[boxType]);
         } else {
           this.markup.setAttributeForElement(this.ID_CLASS_PREFIX + boxType,
             "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);
+          }
+
           this.markup.setAttributeForElement(this.ID_CLASS_PREFIX + boxType,
-            "points", p1.x + "," + p1.y + " " +
-                      p2.x + "," + p2.y + " " +
-                      p3.x + "," + p3.y + " " +
-                      p4.x + "," + p4.y);
+            "d", path.join(" "));
         } else {
-          this.markup.removeAttributeForElement(this.ID_CLASS_PREFIX + boxType, "points");
+          this.markup.removeAttributeForElement(this.ID_CLASS_PREFIX + boxType, "d");
         }
 
         if (boxType === this.options.region && !this.options.hideGuides) {
-          this._showGuides(p1, p2, p3, p4);
+          this._showGuides(boxType);
         } else if (this.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);
@@ -1153,20 +1214,20 @@ BoxModelHighlighter.prototype = Heritage
       return true;
     }
 
     this._hideBoxModel();
     return false;
   },
 
   _nodeNeedsHighlighting: function() {
-    let hasNoQuads = !this.currentQuads.margin &&
-                     !this.currentQuads.border &&
-                     !this.currentQuads.padding &&
-                     !this.currentQuads.content;
+    let hasNoQuads = !this.currentQuads.margin.length &&
+                     !this.currentQuads.border.length &&
+                     !this.currentQuads.padding.length &&
+                     !this.currentQuads.content.length;
     if (!this.currentNode ||
         Cu.isDeadWrapper(this.currentNode) ||
         this.currentNode.nodeType !== Ci.nsIDOMNode.ELEMENT_NODE ||
         !this.currentNode.ownerDocument ||
         !this.currentNode.ownerDocument.defaultView ||
         hasNoQuads) {
       return false;
     }
@@ -1175,24 +1236,24 @@ BoxModelHighlighter.prototype = Heritage
       this._computedStyle = CssLogic.getComputedStyle(this.currentNode);
     }
 
     return this._computedStyle.getPropertyValue("display") !== "none";
   },
 
   _getOuterBounds: function() {
     for (let region of ["margin", "border", "padding", "content"]) {
-      let quads = this.currentQuads[region];
+      let quad = this._getOuterQuad(region);
 
-      if (!quads) {
+      if (!quad) {
         // Invisible element such as a script tag.
         break;
       }
 
-      let {bottom, height, left, right, top, width, x, y} = quads.bounds;
+      let {bottom, height, left, right, top, width, x, y} = quad.bounds;
 
       if (width > 0 || height > 0) {
         return {bottom, height, left, right, top, width, x, y};
       }
     }
 
     return {
       bottom: 0,
@@ -1204,23 +1265,21 @@ BoxModelHighlighter.prototype = Heritage
       x: 0,
       y: 0
     };
   },
 
   /**
    * We only want to show guides for horizontal and vertical edges as this helps
    * to line them up. This method finds these edges and displays a guide there.
-   *
-   * @param  {DOMPoint} p1
-   * @param  {DOMPoint} p2
-   * @param  {DOMPoint} p3
-   * @param  {DOMPoint} p4
+   * @param {String} region The region around which the guides should be shown.
    */
-  _showGuides: function(p1, p2, p3, p4) {
+  _showGuides: function(region) {
+    let {p1, p2, p3, p4} = this._getOuterQuad(region);
+
     let allX = [p1.x, p2.x, p3.x, p4.x].sort((a, b) => a - b);
     let allY = [p1.y, p2.y, p3.y, p4.y].sort((a, b) => a - b);
     let toShowX = [];
     let toShowY = [];
 
     for (let arr of [allX, allY]) {
       for (let i = 0; i < arr.length; i++) {
         let val = arr[i];
@@ -1262,24 +1321,16 @@ BoxModelHighlighter.prototype = Heritage
   _updateGuide: function(side, point=-1) {
     let guideId = this.ID_CLASS_PREFIX + "guide-" + side;
 
     if (point <= 0) {
       this.markup.setAttributeForElement(guideId, "hidden", "true");
       return false;
     }
 
-    let offset = GUIDE_STROKE_WIDTH / 2;
-
-    if (side === "top" || side === "left") {
-      point -= offset;
-    } else {
-      point += offset;
-    }
-
     if (side === "top" || side === "bottom") {
       this.markup.setAttributeForElement(guideId, "x1", "0");
       this.markup.setAttributeForElement(guideId, "y1", point + "");
       this.markup.setAttributeForElement(guideId, "x2", "100%");
       this.markup.setAttributeForElement(guideId, "y2", point + "");
     } else {
       this.markup.setAttributeForElement(guideId, "x1", point + "");
       this.markup.setAttributeForElement(guideId, "y1", "0");
@@ -1313,17 +1364,17 @@ BoxModelHighlighter.prototype = Heritage
     let pseudos = PSEUDO_CLASSES.filter(pseudo => {
       return DOMUtils.hasPseudoClassLock(node, pseudo);
     }, this).join("");
     if (pseudo) {
       // Display :after as ::after
       pseudos += ":" + pseudo;
     }
 
-    let rect = this.currentQuads.border.bounds;
+    let rect = this._getOuterQuad("border").bounds;
     let dim = Math.ceil(rect.width) + " \u00D7 " + Math.ceil(rect.height);
 
     let elementId = this.ID_CLASS_PREFIX + "nodeinfobar-";
     this.markup.setTextContentForElement(elementId + "tagname", tagName);
     this.markup.setTextContentForElement(elementId + "id", id);
     this.markup.setTextContentForElement(elementId + "classes", classList);
     this.markup.setTextContentForElement(elementId + "pseudo-classes", pseudos);
     this.markup.setTextContentForElement(elementId + "dimensions", dim);
@@ -1562,22 +1613,25 @@ CssTransformHighlighter.prototype = Heri
    * Update the highlighter on the current highlighted node (the one that was
    * passed as an argument to show(node)).
    * Should be called whenever node size or attributes change
    */
   _update: function() {
     setIgnoreLayoutChanges(true);
 
     // Getting the points for the transformed shape
-    let quad = this.currentQuads.border;
-    if (!quad || quad.bounds.width <= 0 || quad.bounds.height <= 0) {
+    let quads = this.currentQuads.border;
+    if (!quads.length ||
+        quads[0].bounds.width <= 0 || quads[0].bounds.height <= 0) {
       this._hideShapes();
       return null;
     }
 
+    let [quad] = quads;
+
     // Getting the points for the untransformed shape
     let untransformedQuad = this.layoutHelpers.getNodeBounds(this.currentNode);
 
     this._setPolygonPoints(quad, "transformed");
     this._setPolygonPoints(untransformedQuad, "untransformed");
     for (let nb of ["1", "2", "3", "4"]) {
       this._setLinePoints(untransformedQuad["p" + nb], quad["p" + nb], "line" + nb);
     }
@@ -1735,17 +1789,23 @@ RectHighlighter.prototype = {
     if (!this._hasValidOptions(options) || !node || !node.ownerDocument) {
       this.hide();
       return;
     }
 
     let contextNode = node.ownerDocument.documentElement;
 
     // Caculate the absolute rect based on the context node's adjusted quads.
-    let {bounds} = this.layoutHelpers.getAdjustedQuads(contextNode);
+    let quads = this.layoutHelpers.getAdjustedQuads(contextNode);
+    if (!quads.length) {
+      this.hide();
+      return;
+    }
+
+    let {bounds} = quads[0];
     let x = "left:" + (bounds.x + options.rect.x) + "px;";
     let y = "top:" + (bounds.y + options.rect.y) + "px;";
     let width = "width:" + options.rect.width + "px;";
     let height = "height:" + options.rect.height + "px;";
 
     let style = x + y + width + height;
     if (options.fill) {
       style += "background:" + options.fill + ";";