Bug 1139925 - Make the BoxModelHighlighter highlight all quads and draw guides around the outer-most rect

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.
This commit is contained in:
Patrick Brosset 2015-03-13 15:54:10 +01:00
parent 60a2beb02f
commit 60c76b4463
15 changed files with 474 additions and 197 deletions

View File

@ -12,6 +12,7 @@ support-files =
doc_inspector_highlighter-geometry_02.html doc_inspector_highlighter-geometry_02.html
doc_inspector_highlighter_csstransform.html doc_inspector_highlighter_csstransform.html
doc_inspector_highlighter_dom.html doc_inspector_highlighter_dom.html
doc_inspector_highlighter_inline.html
doc_inspector_highlighter.html doc_inspector_highlighter.html
doc_inspector_highlighter_rect.html doc_inspector_highlighter_rect.html
doc_inspector_highlighter_rect_iframe.html doc_inspector_highlighter_rect_iframe.html
@ -51,6 +52,7 @@ skip-if = e10s # GCLI isn't e10s compatible. See bug 1128988.
[browser_inspector_highlighter-hover_02.js] [browser_inspector_highlighter-hover_02.js]
[browser_inspector_highlighter-hover_03.js] [browser_inspector_highlighter-hover_03.js]
[browser_inspector_highlighter-iframes.js] [browser_inspector_highlighter-iframes.js]
[browser_inspector_highlighter-inline.js]
[browser_inspector_highlighter-keybinding_01.js] [browser_inspector_highlighter-keybinding_01.js]
[browser_inspector_highlighter-keybinding_02.js] [browser_inspector_highlighter-keybinding_02.js]
[browser_inspector_highlighter-keybinding_03.js] [browser_inspector_highlighter-keybinding_03.js]

View File

@ -21,7 +21,7 @@ add_task(function*() {
let highlightedNode = yield getHighlitNode(toolbox); let highlightedNode = yield getHighlitNode(toolbox);
is(highlightedNode, getNode("#simple-div"), is(highlightedNode, getNode("#simple-div"),
"The highlighter's outline corresponds to the simple div"); "The highlighter's outline corresponds to the simple div");
yield isNodeCorrectlyHighlighted(getNode("#simple-div"), toolbox, yield isNodeCorrectlyHighlighted("#simple-div", toolbox,
"non-zoomed"); "non-zoomed");
info("Selecting the rotated DIV"); info("Selecting the rotated DIV");
@ -29,7 +29,7 @@ add_task(function*() {
isVisible = yield isHighlighting(toolbox); isVisible = yield isHighlighting(toolbox);
ok(isVisible, "The highlighter is shown"); ok(isVisible, "The highlighter is shown");
yield isNodeCorrectlyHighlighted(getNode("#rotated-div"), toolbox, yield isNodeCorrectlyHighlighted("#rotated-div", toolbox,
"rotated"); "rotated");
info("Selecting the zero width height DIV"); info("Selecting the zero width height DIV");
@ -37,6 +37,6 @@ add_task(function*() {
isVisible = yield isHighlighting(toolbox); isVisible = yield isHighlighting(toolbox);
ok(isVisible, "The highlighter is shown"); ok(isVisible, "The highlighter is shown");
yield isNodeCorrectlyHighlighted(getNode("#widthHeightZero-div"), toolbox, yield isNodeCorrectlyHighlighted("#widthHeightZero-div", toolbox,
"zero width height"); "zero width height");
}); });

View File

@ -45,7 +45,7 @@ add_task(function* () {
yield moveMouseOver(iframeNode, 1, 1); yield moveMouseOver(iframeNode, 1, 1);
info("Performing checks"); info("Performing checks");
yield isNodeCorrectlyHighlighted(iframeNode, toolbox); yield isNodeCorrectlyHighlighted("iframe", toolbox);
info("Scrolling the document"); info("Scrolling the document");
iframeNode.style.marginBottom = content.innerHeight + "px"; iframeNode.style.marginBottom = content.innerHeight + "px";
@ -58,7 +58,7 @@ add_task(function* () {
let highlightedNode = yield getHighlitNode(toolbox); let highlightedNode = yield getHighlitNode(toolbox);
is(highlightedNode, iframeBodyNode, "highlighter shows the right node"); 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."); info("Waiting for the element picker to deactivate.");
yield inspector.toolbox.highlighterUtils.stopPicker(); yield inspector.toolbox.highlighterUtils.stopPicker();

View File

@ -26,14 +26,15 @@ add_task(function*() {
let highlighter = yield front.getHighlighterByType("CssTransformHighlighter"); let highlighter = yield front.getHighlighterByType("CssTransformHighlighter");
let node = getNode("#test-node");
let nodeFront = yield getNodeFront("#test-node", inspector); let nodeFront = yield getNodeFront("#test-node", inspector);
info("Displaying the transform highlighter on test node"); info("Displaying the transform highlighter on test node");
yield highlighter.show(nodeFront); yield highlighter.show(nodeFront);
let {data} = yield executeInContent("Test:GetAllAdjustedQuads", null, {node}); let {data} = yield executeInContent("Test:GetAllAdjustedQuads", {
let expected = data.border; selector: "#test-node"
});
let [expected] = data.border;
let points = yield getHighlighterNodeAttribute(highlighter, let points = yield getHighlighterNodeAttribute(highlighter,
"css-transform-transformed", "points"); "css-transform-transformed", "points");

View File

@ -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");
}
}
});

View File

@ -41,9 +41,8 @@ const TEST_DATA = [
options: {}, options: {},
checkHighlighter: function*(toolbox) { checkHighlighter: function*(toolbox) {
for (let region of ["margin", "border", "padding", "content"]) { for (let region of ["margin", "border", "padding", "content"]) {
let points = yield getHighlighterNodeAttribute(toolbox.highlighter, let {d} = yield getHighlighterRegionPath(region, toolbox.highlighter);
"box-model-" + region, "points"); ok(d, "Region " + region + " has set coordinates");
ok(points, "Region " + region + " has set coordinates");
} }
} }
}, },
@ -71,42 +70,34 @@ const TEST_DATA = [
desc: "One region only can be shown (1)", desc: "One region only can be shown (1)",
options: {showOnly: "content"}, options: {showOnly: "content"},
checkHighlighter: function*(toolbox) { checkHighlighter: function*(toolbox) {
let points = yield getHighlighterNodeAttribute(toolbox.highlighter, let {d} = yield getHighlighterRegionPath("margin", toolbox.highlighter);
"box-model-margin", "points"); ok(!d, "margin region is hidden");
ok(!points, "margin region is hidden");
points = yield getHighlighterNodeAttribute(toolbox.highlighter, ({d}) = yield getHighlighterRegionPath("border", toolbox.highlighter);
"box-model-border", "points"); ok(!d, "border region is hidden");
ok(!points, "border region is hidden");
points = yield getHighlighterNodeAttribute(toolbox.highlighter, ({d}) = yield getHighlighterRegionPath("padding", toolbox.highlighter);
"box-model-padding", "points"); ok(!d, "padding region is hidden");
ok(!points, "padding region is hidden");
points = yield getHighlighterNodeAttribute(toolbox.highlighter, ({d}) = yield getHighlighterRegionPath("content", toolbox.highlighter);
"box-model-content", "points"); ok(d, "content region is shown");
ok(points, "content region is shown");
} }
}, },
{ {
desc: "One region only can be shown (2)", desc: "One region only can be shown (2)",
options: {showOnly: "margin"}, options: {showOnly: "margin"},
checkHighlighter: function*(toolbox) { checkHighlighter: function*(toolbox) {
let points = yield getHighlighterNodeAttribute(toolbox.highlighter, let {d} = yield getHighlighterRegionPath("margin", toolbox.highlighter);
"box-model-margin", "points"); ok(d, "margin region is shown");
ok(points, "margin region is shown");
points = yield getHighlighterNodeAttribute(toolbox.highlighter, ({d}) = yield getHighlighterRegionPath("border", toolbox.highlighter);
"box-model-border", "points"); ok(!d, "border region is hidden");
ok(!points, "border region is hidden");
points = yield getHighlighterNodeAttribute(toolbox.highlighter, ({d}) = yield getHighlighterRegionPath("padding", toolbox.highlighter);
"box-model-padding", "points"); ok(!d, "padding region is hidden");
ok(!points, "padding region is hidden");
points = yield getHighlighterNodeAttribute(toolbox.highlighter, ({d}) = yield getHighlighterRegionPath("content", toolbox.highlighter);
"box-model-content", "points"); ok(!d, "content region is hidden");
ok(!points, "content region is hidden");
} }
}, },
{ {
@ -122,9 +113,8 @@ const TEST_DATA = [
let leftX1 = yield getHighlighterNodeAttribute(toolbox.highlighter, let leftX1 = yield getHighlighterNodeAttribute(toolbox.highlighter,
"box-model-guide-left", "x1"); "box-model-guide-left", "x1");
let points = yield getHighlighterNodeAttribute(toolbox.highlighter, let {points} = yield getHighlighterRegionPath("padding", toolbox.highlighter);
"box-model-padding", "points"); 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.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(rightX1), points[1][0], "Right guide's x1 is correct");
@ -145,10 +135,9 @@ const TEST_DATA = [
let leftX1 = yield getHighlighterNodeAttribute(toolbox.highlighter, let leftX1 = yield getHighlighterNodeAttribute(toolbox.highlighter,
"box-model-guide-left", "x1"); "box-model-guide-left", "x1");
let points = yield getHighlighterNodeAttribute(toolbox.highlighter, let {points} = yield getHighlighterRegionPath("margin", toolbox.highlighter);
"box-model-margin", "points"); 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.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(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.floor(bottomY1), points[2][1], "Bottom guide's y1 is correct");

View File

@ -41,7 +41,7 @@ add_task(function*() {
isVisible = yield isHighlighting(toolbox); isVisible = yield isHighlighting(toolbox);
ok(isVisible, "The highlighter is still visible at zoom level " + level); 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"); info("Check that the highlighter root wrapper node was scaled down");

View File

@ -214,12 +214,14 @@ addMessageListener("Test:ElementFromPoint", function(msg) {
/** /**
* Get all box-model regions' adjusted boxquads for the given 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 * @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) { addMessageListener("Test:GetAllAdjustedQuads", function(msg) {
let {node} = msg.objects; let {selector} = msg.data;
let node = superQuerySelector(selector);
let regions = {}; let regions = {};
let helper = new LayoutHelpers(content); let helper = new LayoutHelpers(content);
@ -250,7 +252,7 @@ addMessageListener("Test:SynthesizeMouse", function(msg) {
let {node} = msg.objects; let {node} = msg.objects;
if (!node && selector) { if (!node && selector) {
node = content.document.querySelector(selector); node = superQuerySelector(selector);
} }
if (center) { if (center) {
@ -293,4 +295,30 @@ addMessageListener("Test:HasPseudoClassLock", function(msg) {
sendAsyncMessage("Test:HasPseudoClassLock", DOMUtils.hasPseudoClassLock(node, pseudo)); 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"); let dumpn = msg => dump(msg + "\n");

View File

@ -1,6 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8">
<style> <style>
div { div {
position:absolute; position:absolute;

View File

@ -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>

View File

@ -306,14 +306,22 @@ let getBoxModelStatus = Task.async(function*(toolbox) {
return ret; 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 id = "box-model-guide-" + location;
let hidden = yield getHighlighterNodeAttribute(toolbox.highlighter, id, "hidden"); let hidden = yield getHighlighterNodeAttribute(highlighter, id, "hidden");
let x1 = yield getHighlighterNodeAttribute(toolbox.highlighter, id, "x1"); let x1 = yield getHighlighterNodeAttribute(highlighter, id, "x1");
let y1 = yield getHighlighterNodeAttribute(toolbox.highlighter, id, "y1"); let y1 = yield getHighlighterNodeAttribute(highlighter, id, "y1");
let x2 = yield getHighlighterNodeAttribute(toolbox.highlighter, id, "x2"); let x2 = yield getHighlighterNodeAttribute(highlighter, id, "x2");
let y2 = yield getHighlighterNodeAttribute(toolbox.highlighter, id, "y2"); let y2 = yield getHighlighterNodeAttribute(highlighter, id, "y2");
return { return {
visible: !hidden, visible: !hidden,
@ -325,36 +333,68 @@ let getGuideStatus = Task.async(function*(location, toolbox) {
}); });
/** /**
* Get the coordinate (points attribute) from one of the polygon elements in the * Get the coordinates of the rectangle that is defined by the 4 guides displayed
* box model highlighter. * 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 getPointsForRegion = Task.async(function*(region, toolbox) {
let points = yield getHighlighterNodeAttribute(toolbox.highlighter, let d = yield getHighlighterNodeAttribute(toolbox.highlighter,
"box-model-" + region, "points"); "box-model-" + region, "d");
points = points.split(/[, ]/); 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 { return {
p1: { p1: {
x: parseFloat(points[0]), x: parseFloat(points[0][0]),
y: parseFloat(points[1]) y: parseFloat(points[0][1])
}, },
p2: { p2: {
x: parseFloat(points[2]), x: parseFloat(points[1][0]),
y: parseFloat(points[3]) y: parseFloat(points[1][1])
}, },
p3: { p3: {
x: parseFloat(points[4]), x: parseFloat(points[2][0]),
y: parseFloat(points[5]) y: parseFloat(points[2][1])
}, },
p4: { p4: {
x: parseFloat(points[6]), x: parseFloat(points[3][0]),
y: parseFloat(points[7]) 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? * hidden?
*/ */
let isRegionHidden = Task.async(function*(region, toolbox) { let isRegionHidden = Task.async(function*(region, toolbox) {
@ -387,27 +427,24 @@ let getHighlitNode = Task.async(function*(toolbox) {
/** /**
* Assert that the box-model highlighter's current position corresponds to the * Assert that the box-model highlighter's current position corresponds to the
* given node boxquads. * 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 * @param {String} prefix An optional prefix for logging information to the
* console. * console.
*/ */
let isNodeCorrectlyHighlighted = Task.async(function*(node, toolbox, prefix="") { let isNodeCorrectlyHighlighted = Task.async(function*(selector, toolbox, prefix="") {
prefix += (prefix ? " " : "") + node.nodeName;
prefix += (node.id ? "#" + node.id : "");
prefix += (node.classList.length ? "." + [...node.classList].join(".") : "");
prefix += " ";
let boxModel = yield getBoxModelStatus(toolbox); let boxModel = yield getBoxModelStatus(toolbox);
let {data: regions} = yield executeInContent("Test:GetAllAdjustedQuads", null, let {data: regions} = yield executeInContent("Test:GetAllAdjustedQuads",
{node}); {selector});
for (let boxType of ["content", "padding", "border", "margin"]) { for (let boxType of ["content", "padding", "border", "margin"]) {
let quads = regions[boxType]; let [quad] = regions[boxType];
for (let point in boxModel[boxType].points) { for (let point in boxModel[boxType].points) {
is(boxModel[boxType].points[point].x, quads[point].x, is(boxModel[boxType].points[point].x, quad[point].x,
prefix + boxType + " point " + point + " x coordinate is correct"); "Node " + selector + " " + boxType + " point " + point +
is(boxModel[boxType].points[point].y, quads[point].y, " x coordinate is correct");
prefix + boxType + " point " + point + " y coordinate is correct"); is(boxModel[boxType].points[point].y, quad[point].y,
"Node " + selector + " " + boxType + " point " + point +
" y coordinate is correct");
} }
} }
}); });
@ -552,6 +589,37 @@ let getHighlighterNodeAttribute = Task.async(function*(highlighter, nodeID, name
return value; 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. * Get the textContent value of one of the highlighter's node.
* @param {Front} highlighter The front of the highlighter. * @param {Front} highlighter The front of the highlighter.

View File

@ -59,4 +59,7 @@
<div id="sub-scrolled-node"> <div id="sub-scrolled-node">
<div id="inner-scrolled-node"></div> <div id="inner-scrolled-node"></div>
</div> </div>
</div> </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>

View File

@ -21,13 +21,14 @@ function test() {
info("Running tests"); info("Running tests");
returnsTheRightDataStructure(doc, helper); returnsTheRightDataStructure(doc, helper);
returnsNullForMissingNode(doc, helper); isEmptyForMissingNode(doc, helper);
returnsNullForHiddenNodes(doc, helper); isEmptyForHiddenNodes(doc, helper);
defaultsToBorderBoxIfNoneProvided(doc, helper); defaultsToBorderBoxIfNoneProvided(doc, helper);
returnsLikeGetBoxQuadsInSimpleCase(doc, helper); returnsLikeGetBoxQuadsInSimpleCase(doc, helper);
takesIframesOffsetsIntoAccount(doc, helper); takesIframesOffsetsIntoAccount(doc, helper);
takesScrollingIntoAccount(doc, helper); takesScrollingIntoAccount(doc, helper);
takesZoomIntoAccount(doc, helper); takesZoomIntoAccount(doc, helper);
returnsMultipleItemsForWrappingInlineElements(doc, helper);
gBrowser.removeCurrentTab(); gBrowser.removeCurrentTab();
finish(); finish();
@ -38,7 +39,7 @@ function returnsTheRightDataStructure(doc, helper) {
info("Checks that the returned data contains bounds and 4 points"); info("Checks that the returned data contains bounds and 4 points");
let node = doc.querySelector("body"); 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("bounds" in res, "The returned data has a bounds property");
ok("p1" in res, "The returned data has a p1 property"); ok("p1" in res, "The returned data has a p1 property");
@ -58,24 +59,24 @@ function returnsTheRightDataStructure(doc, helper) {
} }
} }
function returnsNullForMissingNode(doc, helper) { function isEmptyForMissingNode(doc, helper) {
info("Checks that null is returned for invalid nodes"); info("Checks that null is returned for invalid nodes");
for (let input of [null, undefined, "", 0]) { for (let input of [null, undefined, "", 0]) {
ok(helper.getAdjustedQuads(input) === null, "null is returned for input " + is(helper.getAdjustedQuads(input).length, 0, "A 0-length array is returned" +
input); "for input " + input);
} }
} }
function returnsNullForHiddenNodes(doc, helper) { function isEmptyForHiddenNodes(doc, helper) {
info("Checks that null is returned for nodes that aren't rendered"); info("Checks that null is returned for nodes that aren't rendered");
let style = doc.querySelector("#styles"); let style = doc.querySelector("#styles");
ok(helper.getAdjustedQuads(style) === null, is(helper.getAdjustedQuads(style).length, 0,
"null is returned for a <style> node"); "null is returned for a <style> node");
let hidden = doc.querySelector("#hidden-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"); "null is returned for a hidden node");
} }
@ -83,8 +84,8 @@ function defaultsToBorderBoxIfNoneProvided(doc, helper) {
info("Checks that if no boxtype is passed, then border is the default one"); 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 node = doc.querySelector("#simple-node-with-margin-padding-border");
let withBoxType = helper.getAdjustedQuads(node, "border"); let [withBoxType] = helper.getAdjustedQuads(node, "border");
let withoutBoxType = helper.getAdjustedQuads(node); let [withoutBoxType] = helper.getAdjustedQuads(node);
for (let boundProp of for (let boundProp of
["bottom", "top", "right", "left", "width", "height", "x", "y"]) { ["bottom", "top", "right", "left", "width", "height", "x", "y"]) {
@ -111,7 +112,7 @@ function returnsLikeGetBoxQuadsInSimpleCase(doc, helper) {
let expected = node.getBoxQuads({ let expected = node.getBoxQuads({
box: region box: region
})[0]; })[0];
let actual = helper.getAdjustedQuads(node, region); let [actual] = helper.getAdjustedQuads(node, region);
for (let boundProp of for (let boundProp of
["bottom", "top", "right", "left", "width", "height", "x", "y"]) { ["bottom", "top", "right", "left", "width", "height", "x", "y"]) {
@ -138,7 +139,7 @@ function takesIframesOffsetsIntoAccount(doc, helper) {
let subIframe = rootIframe.contentDocument.querySelector("iframe"); let subIframe = rootIframe.contentDocument.querySelector("iframe");
let innerNode = subIframe.contentDocument.querySelector("#inner-node"); 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 //rootIframe margin + subIframe margin + node margin + node border + node padding
let p1x = 10 + 10 + 10 + 10 + 10; let p1x = 10 + 10 + 10 + 10 + 10;
@ -163,7 +164,7 @@ function takesScrollingIntoAccount(doc, helper) {
subScrolledNode.scrollTop = 200; subScrolledNode.scrollTop = 200;
let innerNode = doc.querySelector("#inner-scrolled-node"); 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.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"); is(quad.p1.y, -300, "p1.y of the scrolled node is correct after scrolling down");
@ -171,7 +172,7 @@ function takesScrollingIntoAccount(doc, helper) {
scrolledNode.scrollTop = 0; scrolledNode.scrollTop = 0;
subScrolledNode.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.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"); is(quad.p1.y, 0, "p1.y of the scrolled node is correct after scrolling up");
} }
@ -184,11 +185,11 @@ function takesZoomIntoAccount(doc, helper) {
// bigger quad and zooming out produces a smaller quad // bigger quad and zooming out produces a smaller quad
let node = doc.querySelector("#simple-node-with-margin-padding-border"); let node = doc.querySelector("#simple-node-with-margin-padding-border");
let defaultQuad = helper.getAdjustedQuads(node); let [defaultQuad] = helper.getAdjustedQuads(node);
info("Zoom in"); info("Zoom in");
window.FullZoom.enlarge(); window.FullZoom.enlarge();
let zoomedInQuad = helper.getAdjustedQuads(node); let [zoomedInQuad] = helper.getAdjustedQuads(node);
ok(zoomedInQuad.bounds.width > defaultQuad.bounds.width, ok(zoomedInQuad.bounds.width > defaultQuad.bounds.width,
"The zoomed in quad is bigger than the default one"); "The zoomed in quad is bigger than the default one");
@ -198,7 +199,7 @@ function takesZoomIntoAccount(doc, helper) {
info("Zoom out"); info("Zoom out");
window.FullZoom.reset(); window.FullZoom.reset();
window.FullZoom.reduce(); window.FullZoom.reduce();
let zoomedOutQuad = helper.getAdjustedQuads(node); let [zoomedOutQuad] = helper.getAdjustedQuads(node);
ok(zoomedOutQuad.bounds.width < defaultQuad.bounds.width, ok(zoomedOutQuad.bounds.width < defaultQuad.bounds.width,
"The zoomed out quad is smaller than the default one"); "The zoomed out quad is smaller than the default one");
@ -207,3 +208,15 @@ function takesZoomIntoAccount(doc, helper) {
window.FullZoom.reset(); 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");
}

View File

@ -27,67 +27,71 @@ LayoutHelpers.prototype = {
/** /**
* Get box quads adjusted for iframes and zoom level. * Get box quads adjusted for iframes and zoom level.
* @param {DOMNode} node The node for which we are to get the box model region
* @param {DOMNode} node * quads.
* The node for which we are to get the box model region quads * @param {String} region The box model region to return: "content",
* @param {String} region * "padding", "border" or "margin".
* The box model region to return: * @return {Array} An array of objects that have the same structure as quads
* "content", "padding", "border" or "margin" * returned by getBoxQuads. An empty array if the node has no quads or is
* @return {Object} An object that has the same structure as one quad returned * invalid.
* by getBoxQuads
*/ */
getAdjustedQuads: function(node, region) { getAdjustedQuads: function(node, region) {
if (!node || !node.getBoxQuads) { if (!node || !node.getBoxQuads) {
return null; return [];
} }
let [quads] = node.getBoxQuads({ let quads = node.getBoxQuads({
box: region box: region
}); });
if (!quads) { if (!quads.length) {
return null; return [];
} }
let [xOffset, yOffset] = this.getFrameOffsets(node); let [xOffset, yOffset] = this.getFrameOffsets(node);
let scale = LayoutHelpers.getCurrentZoom(node); let scale = LayoutHelpers.getCurrentZoom(node);
return { let adjustedQuads = [];
p1: { for (let quad of quads) {
w: quads.p1.w * scale, adjustedQuads.push({
x: quads.p1.x * scale + xOffset, p1: {
y: quads.p1.y * scale + yOffset, w: quad.p1.w * scale,
z: quads.p1.z * scale x: quad.p1.x * scale + xOffset,
}, y: quad.p1.y * scale + yOffset,
p2: { z: quad.p1.z * scale
w: quads.p2.w * scale, },
x: quads.p2.x * scale + xOffset, p2: {
y: quads.p2.y * scale + yOffset, w: quad.p2.w * scale,
z: quads.p2.z * scale x: quad.p2.x * scale + xOffset,
}, y: quad.p2.y * scale + yOffset,
p3: { z: quad.p2.z * scale
w: quads.p3.w * scale, },
x: quads.p3.x * scale + xOffset, p3: {
y: quads.p3.y * scale + yOffset, w: quad.p3.w * scale,
z: quads.p3.z * scale x: quad.p3.x * scale + xOffset,
}, y: quad.p3.y * scale + yOffset,
p4: { z: quad.p3.z * scale
w: quads.p4.w * scale, },
x: quads.p4.x * scale + xOffset, p4: {
y: quads.p4.y * scale + yOffset, w: quad.p4.w * scale,
z: quads.p4.z * scale x: quad.p4.x * scale + xOffset,
}, y: quad.p4.y * scale + yOffset,
bounds: { z: quad.p4.z * scale
bottom: quads.bounds.bottom * scale + yOffset, },
height: quads.bounds.height * scale, bounds: {
left: quads.bounds.left * scale + xOffset, bottom: quad.bounds.bottom * scale + yOffset,
right: quads.bounds.right * scale + xOffset, height: quad.bounds.height * scale,
top: quads.bounds.top * scale + yOffset, left: quad.bounds.left * scale + xOffset,
width: quads.bounds.width * scale, right: quad.bounds.right * scale + xOffset,
x: quads.bounds.x * scale + xOffset, top: quad.bounds.top * scale + yOffset,
y: quads.bounds.y * scale + yOffset width: quad.bounds.width * scale,
} x: quad.bounds.x * scale + xOffset,
}; y: quad.bounds.y * scale + yOffset
}
});
}
return adjustedQuads;
}, },
/** /**

View File

@ -829,9 +829,10 @@ AutoRefreshHighlighter.prototype = {
}; };
/** /**
* The BoxModelHighlighter is the class that actually draws the the box model * The BoxModelHighlighter draws the box model regions on top of a node.
* regions on top of a node. * If the node is a block box, then each region will be displayed as 1 polygon.
* It is used by the HighlighterActor. * 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: * Usage example:
* *
@ -858,10 +859,10 @@ AutoRefreshHighlighter.prototype = {
* <div class="box-model-root"> * <div class="box-model-root">
* <svg class="box-model-elements" hidden="true"> * <svg class="box-model-elements" hidden="true">
* <g class="box-model-regions"> * <g class="box-model-regions">
* <polygon class="box-model-margin" points="..." /> * <path class="box-model-margin" points="..." />
* <polygon class="box-model-border" points="..." /> * <path class="box-model-border" points="..." />
* <polygon class="box-model-padding" points="..." /> * <path class="box-model-padding" points="..." />
* <polygon class="box-model-content" points="..." /> * <path class="box-model-content" points="..." />
* </g> * </g>
* <line class="box-model-guide-top" x1="..." y1="..." x2="..." y2="..." /> * <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-right" x1="..." y1="..." x2="..." y2="..." />
@ -959,7 +960,7 @@ BoxModelHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.prototype
for (let region of BOX_MODEL_REGIONS) { for (let region of BOX_MODEL_REGIONS) {
createSVGNode(this.win, { createSVGNode(this.win, {
nodeType: "polygon", nodeType: "path",
parent: regions, parent: regions,
attributes: { attributes: {
"class": region, "class": region,
@ -1171,6 +1172,62 @@ BoxModelHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.prototype
"hidden"); "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. * Update the box model as per the current node.
* *
@ -1182,8 +1239,6 @@ BoxModelHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.prototype
if (this._nodeNeedsHighlighting()) { if (this._nodeNeedsHighlighting()) {
for (let boxType of BOX_MODEL_REGIONS) { for (let boxType of BOX_MODEL_REGIONS) {
let {p1, p2, p3, p4} = this.currentQuads[boxType];
if (this.regionFill[boxType]) { if (this.regionFill[boxType]) {
this.markup.setAttributeForElement(this.ID_CLASS_PREFIX + boxType, this.markup.setAttributeForElement(this.ID_CLASS_PREFIX + boxType,
"style", "fill:" + this.regionFill[boxType]); "style", "fill:" + this.regionFill[boxType]);
@ -1193,17 +1248,23 @@ BoxModelHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.prototype
} }
if (!this.options.showOnly || this.options.showOnly === boxType) { 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, this.markup.setAttributeForElement(this.ID_CLASS_PREFIX + boxType,
"points", p1.x + "," + p1.y + " " + "d", path.join(" "));
p2.x + "," + p2.y + " " +
p3.x + "," + p3.y + " " +
p4.x + "," + p4.y);
} else { } 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) { if (boxType === this.options.region && !this.options.hideGuides) {
this._showGuides(p1, p2, p3, p4); this._showGuides(boxType);
} else if (this.options.hideGuides) { } else if (this.options.hideGuides) {
this._hideGuides(); this._hideGuides();
} }
@ -1221,10 +1282,10 @@ BoxModelHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.prototype
}, },
_nodeNeedsHighlighting: function() { _nodeNeedsHighlighting: function() {
let hasNoQuads = !this.currentQuads.margin && let hasNoQuads = !this.currentQuads.margin.length &&
!this.currentQuads.border && !this.currentQuads.border.length &&
!this.currentQuads.padding && !this.currentQuads.padding.length &&
!this.currentQuads.content; !this.currentQuads.content.length;
if (!this.currentNode || if (!this.currentNode ||
Cu.isDeadWrapper(this.currentNode) || Cu.isDeadWrapper(this.currentNode) ||
this.currentNode.nodeType !== Ci.nsIDOMNode.ELEMENT_NODE || this.currentNode.nodeType !== Ci.nsIDOMNode.ELEMENT_NODE ||
@ -1243,14 +1304,14 @@ BoxModelHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.prototype
_getOuterBounds: function() { _getOuterBounds: function() {
for (let region of ["margin", "border", "padding", "content"]) { 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. // Invisible element such as a script tag.
break; 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) { if (width > 0 || height > 0) {
return {bottom, height, left, right, top, width, x, y}; return {bottom, height, left, right, top, width, x, y};
@ -1272,13 +1333,11 @@ BoxModelHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.prototype
/** /**
* We only want to show guides for horizontal and vertical edges as this helps * 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. * to line them up. This method finds these edges and displays a guide there.
* * @param {String} region The region around which the guides should be shown.
* @param {DOMPoint} p1
* @param {DOMPoint} p2
* @param {DOMPoint} p3
* @param {DOMPoint} p4
*/ */
_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 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 allY = [p1.y, p2.y, p3.y, p4.y].sort((a, b) => a - b);
let toShowX = []; let toShowX = [];
@ -1330,14 +1389,6 @@ BoxModelHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.prototype
return false; return false;
} }
let offset = GUIDE_STROKE_WIDTH / 2;
if (side === "top" || side === "left") {
point -= offset;
} else {
point += offset;
}
if (side === "top" || side === "bottom") { if (side === "top" || side === "bottom") {
this.markup.setAttributeForElement(guideId, "x1", "0"); this.markup.setAttributeForElement(guideId, "x1", "0");
this.markup.setAttributeForElement(guideId, "y1", point + ""); this.markup.setAttributeForElement(guideId, "y1", point + "");
@ -1381,7 +1432,7 @@ BoxModelHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.prototype
pseudos += ":" + pseudo; pseudos += ":" + pseudo;
} }
let rect = this.currentQuads.border.bounds; let rect = this._getOuterQuad("border").bounds;
let dim = parseFloat(rect.width.toPrecision(6)) + " \u00D7 " + parseFloat(rect.height.toPrecision(6)); let dim = parseFloat(rect.width.toPrecision(6)) + " \u00D7 " + parseFloat(rect.height.toPrecision(6));
let elementId = this.ID_CLASS_PREFIX + "nodeinfobar-"; let elementId = this.ID_CLASS_PREFIX + "nodeinfobar-";
@ -1630,12 +1681,15 @@ CssTransformHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.proto
setIgnoreLayoutChanges(true); setIgnoreLayoutChanges(true);
// Getting the points for the transformed shape // Getting the points for the transformed shape
let quad = this.currentQuads.border; let quads = this.currentQuads.border;
if (!quad || quad.bounds.width <= 0 || quad.bounds.height <= 0) { if (!quads.length ||
quads[0].bounds.width <= 0 || quads[0].bounds.height <= 0) {
this._hideShapes(); this._hideShapes();
return null; return null;
} }
let [quad] = quads;
// Getting the points for the untransformed shape // Getting the points for the untransformed shape
let untransformedQuad = this.layoutHelpers.getNodeBounds(this.currentNode); let untransformedQuad = this.layoutHelpers.getNodeBounds(this.currentNode);
@ -1803,7 +1857,13 @@ RectHighlighter.prototype = {
let contextNode = node.ownerDocument.documentElement; let contextNode = node.ownerDocument.documentElement;
// Caculate the absolute rect based on the context node's adjusted quads. // 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 x = "left:" + (bounds.x + options.rect.x) + "px;";
let y = "top:" + (bounds.y + options.rect.y) + "px;"; let y = "top:" + (bounds.y + options.rect.y) + "px;";
let width = "width:" + options.rect.width + "px;"; let width = "width:" + options.rect.width + "px;";
@ -2209,7 +2269,7 @@ GeometryEditorHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.pro
this.offsetParent = getOffsetParent(this.currentNode); this.offsetParent = getOffsetParent(this.currentNode);
// And the offsetParent quads. // And the offsetParent quads.
this.parentQuads = this.layoutHelpers this.parentQuads = this.layoutHelpers
.getAdjustedQuads(this.offsetParent.element, "padding"); .getAdjustedQuads(this.offsetParent.element, "padding");
let el = this.markup.getElement(this.ID_CLASS_PREFIX + "offset-parent"); let el = this.markup.getElement(this.ID_CLASS_PREFIX + "offset-parent");
@ -2219,7 +2279,7 @@ GeometryEditorHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.pro
let isHighlighted = false; let isHighlighted = false;
if (this.offsetParent.element && isPositioned) { if (this.offsetParent.element && isPositioned) {
let {p1, p2, p3, p4} = this.parentQuads; let {p1, p2, p3, p4} = this.parentQuads[0];
let points = p1.x + "," + p1.y + " " + let points = p1.x + "," + p1.y + " " +
p2.x + "," + p2.y + " " + p2.x + "," + p2.y + " " +
p3.x + "," + p3.y + " " + p3.x + "," + p3.y + " " +
@ -2230,7 +2290,7 @@ GeometryEditorHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.pro
let xDelta = parseFloat(this.computedStyle.left); let xDelta = parseFloat(this.computedStyle.left);
let yDelta = parseFloat(this.computedStyle.top); let yDelta = parseFloat(this.computedStyle.top);
if (xDelta || yDelta) { if (xDelta || yDelta) {
let {p1, p2, p3, p4} = this.currentQuads.margin; let {p1, p2, p3, p4} = this.currentQuads.margin[0];
let points = (p1.x - xDelta) + "," + (p1.y - yDelta) + " " + let points = (p1.x - xDelta) + "," + (p1.y - yDelta) + " " +
(p2.x - xDelta) + "," + (p2.y - yDelta) + " " + (p2.x - xDelta) + "," + (p2.y - yDelta) + " " +
(p3.x - xDelta) + "," + (p3.y - yDelta) + " " + (p3.x - xDelta) + "," + (p3.y - yDelta) + " " +
@ -2249,7 +2309,7 @@ GeometryEditorHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.pro
updateCurrentNode: function() { updateCurrentNode: function() {
let box = this.markup.getElement(this.ID_CLASS_PREFIX + "current-node"); let box = this.markup.getElement(this.ID_CLASS_PREFIX + "current-node");
let {p1, p2, p3, p4} = this.currentQuads.margin; let {p1, p2, p3, p4} = this.currentQuads.margin[0];
let attr = p1.x + "," + p1.y + " " + let attr = p1.x + "," + p1.y + " " +
p2.x + "," + p2.y + " " + p2.x + "," + p2.y + " " +
p3.x + "," + p3.y + " " + p3.x + "," + p3.y + " " +
@ -2304,7 +2364,7 @@ GeometryEditorHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.pro
let labelEl = this.markup.getElement(id + "label-size"); let labelEl = this.markup.getElement(id + "label-size");
let labelTextEl = this.markup.getElement(id + "label-text-size"); let labelTextEl = this.markup.getElement(id + "label-text-size");
let {bounds} = this.currentQuads.margin; let {bounds} = this.currentQuads.margin[0];
labelEl.setAttribute("transform", "translate(" + labelEl.setAttribute("transform", "translate(" +
(bounds.left + bounds.width/2) + " " + (bounds.left + bounds.width/2) + " " +
@ -2318,11 +2378,11 @@ GeometryEditorHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.pro
this.hideArrows(); this.hideArrows();
// Position arrows always end at the node's margin box. // Position arrows always end at the node's margin box.
let marginBox = this.currentQuads.margin.bounds; let marginBox = this.currentQuads.margin[0].bounds;
// But size arrows are displayed in the box that corresponds to the current // But size arrows are displayed in the box that corresponds to the current
// box-sizing. // box-sizing.
let boxSizing = this.computedStyle.boxSizing.split("-")[0]; let boxSizing = this.computedStyle.boxSizing.split("-")[0];
let box = this.currentQuads[boxSizing].bounds; let box = this.currentQuads[boxSizing][0].bounds;
// Position the side arrows which need to be visible. // Position the side arrows which need to be visible.
// Arrows always start at the offsetParent edge, and end at the middle // Arrows always start at the offsetParent edge, and end at the middle
@ -2341,8 +2401,8 @@ GeometryEditorHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.pro
// +------------------+----------------+ // +------------------+----------------+
let getSideArrowStartPos = side => { let getSideArrowStartPos = side => {
// In case an offsetParent exists and is highlighted. // In case an offsetParent exists and is highlighted.
if (this.parentQuads) { if (this.parentQuads && this.parentQuads.length) {
return this.parentQuads.bounds[side]; return this.parentQuads[0].bounds[side];
} }
// In case of relative positioning. // In case of relative positioning.