bug 971662 - part 0 - The BoxModelHighlighter has options for drawing regions and guides; r=miker

This commit is contained in:
Patrick Brosset 2014-06-24 10:09:30 +02:00
parent 1870de7e72
commit b9052af8a9
6 changed files with 412 additions and 64 deletions

View File

@ -185,13 +185,12 @@ MarkupView.prototype = {
/**
* Show the box model highlighter on a given node front
* @param {NodeFront} nodeFront The node to show the highlighter for
* @param {Object} options Options for the highlighter
* @return a promise that resolves when the highlighter for this nodeFront is
* shown, taking into account that there could already be highlighter requests
* queued up
*/
_showBoxModel: function(nodeFront, options={}) {
return this._inspector.toolbox.highlighterUtils.highlightNodeFront(nodeFront, options);
_showBoxModel: function(nodeFront) {
return this._inspector.toolbox.highlighterUtils.highlightNodeFront(nodeFront);
},
/**
@ -207,7 +206,7 @@ MarkupView.prototype = {
},
_briefBoxModelTimer: null,
_brieflyShowBoxModel: function(nodeFront, options) {
_brieflyShowBoxModel: function(nodeFront) {
let win = this._frame.contentWindow;
if (this._briefBoxModelTimer) {
@ -215,7 +214,7 @@ MarkupView.prototype = {
this._briefBoxModelTimer = null;
}
this._showBoxModel(nodeFront, options);
this._showBoxModel(nodeFront);
this._briefBoxModelTimer = this._frame.contentWindow.setTimeout(() => {
this._hideBoxModel();
@ -320,7 +319,7 @@ MarkupView.prototype = {
let done = this._inspector.updating("markup-view");
if (selection.isNode()) {
if (this._shouldNewSelectionBeHighlighted()) {
this._brieflyShowBoxModel(selection.nodeFront, {});
this._brieflyShowBoxModel(selection.nodeFront);
}
this.showNode(selection.nodeFront, true).then(() => {

View File

@ -39,6 +39,7 @@ svg|line.box-model-guide-bottom,
svg|line.box-model-guide-left {
stroke: #08C;
stroke-dasharray: 5 3;
shape-rendering: crispEdges;
}
/* Highlighter - Node Infobar */

View File

@ -295,16 +295,18 @@ let CustomHighlighterActor = exports.CustomHighlighterActor = protocol.ActorClas
/**
* Display the highlighter on a given NodeActor.
* @param NodeActor The node to be highlighted
* @param Object Options for the custom highlighter
*/
show: method(function(node) {
show: method(function(node, options) {
if (!node || !isNodeValid(node.rawNode) || !this._highlighter) {
return;
}
this._highlighter.show(node.rawNode);
this._highlighter.show(node.rawNode, options);
}, {
request: {
node: Arg(0, "domnode")
node: Arg(0, "domnode"),
options: Arg(1, "nullable:json")
}
}),
@ -352,12 +354,15 @@ XULBasedHighlighter.prototype = {
/**
* Show the highlighter on a given node
* @param {DOMNode} node
* @param {Object} options Depend on sub-classes
*/
show: function(node) {
show: function(node, options={}) {
if (!isNodeValid(node) || node === this.currentNode) {
return;
}
this.options = options;
this._detachPageListeners();
this.currentNode = node;
this._attachPageListeners();
@ -442,10 +447,23 @@ XULBasedHighlighter.prototype = {
* Usage example:
*
* let h = new BoxModelHighlighter(browser);
* h.show(node);
* h.show(node, options);
* h.hide();
* h.destroy();
*
* Available options:
* - region {String}
* "content", "padding", "border" or "margin"
* This specifies the region that the guides should outline.
* Defaults to "content"
* - hideGuides {Boolean}
* Defaults to false
* - hideInfoBar {Boolean}
* Defaults to false
* - showOnly {String}
* "content", "padding", "border" or "margin"
* If set, only this region will be highlighted
*
* Structure:
* <stack class="highlighter-container">
* <svg class="box-model-root" hidden="true">
@ -632,10 +650,8 @@ BoxModelHighlighter.prototype = Heritage.extend(XULBasedHighlighter.prototype, {
/**
* Show the highlighter on a given node
* @param {Object} options
* Object used for passing options
*/
_show: function(options={}) {
_show: function() {
this._update();
this._trackMutations();
this.emit("ready");
@ -664,14 +680,12 @@ BoxModelHighlighter.prototype = Heritage.extend(XULBasedHighlighter.prototype, {
* 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
* @param {Object} options
* Object used for passing options. Valid options are:
* - box: "content", "padding", "border" or "margin." This specifies
* the box that the guides should outline. Default is content.
*/
_update: function(options={}) {
if (this._updateBoxModel(options)) {
this._showInfobar();
_update: function() {
if (this._updateBoxModel()) {
if (!this.options.hideInfoBar) {
this._showInfobar();
}
this._showBoxModel();
} else {
// Nothing to highlight (0px rectangle like a <script> tag for instance)
@ -720,15 +734,11 @@ BoxModelHighlighter.prototype = Heritage.extend(XULBasedHighlighter.prototype, {
/**
* Update the box model as per the current node.
*
* @param {Object} options
* Object used for passing options. Valid options are:
* - region: "content", "padding", "border" or "margin." This specifies
* the region that the guides should outline. Default is content.
* @return {boolean}
* True if the current node has a box model to be highlighted
*/
_updateBoxModel: function(options) {
options.region = options.region || "content";
_updateBoxModel: function() {
this.options.region = this.options.region || "content";
if (this._nodeNeedsHighlighting()) {
for (let boxType in this._boxModelNodes) {
@ -736,14 +746,20 @@ BoxModelHighlighter.prototype = Heritage.extend(XULBasedHighlighter.prototype, {
this.layoutHelpers.getAdjustedQuads(this.currentNode, boxType);
let boxNode = this._boxModelNodes[boxType];
boxNode.setAttribute("points",
p1.x + "," + p1.y + " " +
p2.x + "," + p2.y + " " +
p3.x + "," + p3.y + " " +
p4.x + "," + p4.y);
if (!this.options.showOnly || this.options.showOnly === boxType) {
boxNode.setAttribute("points",
p1.x + "," + p1.y + " " +
p2.x + "," + p2.y + " " +
p3.x + "," + p3.y + " " +
p4.x + "," + p4.y);
} else {
boxNode.setAttribute("points", "");
}
if (boxType === options.region) {
if (boxType === this.options.region && !this.options.hideGuides) {
this._showGuides(p1, p2, p3, p4);
} else if (this.options.hideGuides) {
this._hideGuides();
}
}
@ -805,17 +821,13 @@ BoxModelHighlighter.prototype = Heritage.extend(XULBasedHighlighter.prototype, {
* to line them up. This method finds these edges and displays a guide there.
*
* @param {DOMPoint} p1
* Point 1
* @param {DOMPoint} p2
* Point 2
* @param {DOMPoint} p3 [description]
* Point 3
* @param {DOMPoint} p4 [description]
* Point 4
* @param {DOMPoint} p3
* @param {DOMPoint} p4
*/
_showGuides: function(p1, p2, p3, p4) {
let allX = [p1.x, p2.x, p3.x, p4.x].sort();
let allY = [p1.y, p2.y, p3.y, p4.y].sort();
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 = [];
@ -841,6 +853,12 @@ BoxModelHighlighter.prototype = Heritage.extend(XULBasedHighlighter.prototype, {
this._updateGuide(this._guideNodes.left, toShowX[0]);
},
_hideGuides: function() {
for (let side in this._guideNodes) {
this._guideNodes[side].setAttribute("hidden", "true");
}
},
/**
* Move a guide to the appropriate position and display it. If no point is
* passed then the guide is hidden.
@ -851,32 +869,33 @@ BoxModelHighlighter.prototype = Heritage.extend(XULBasedHighlighter.prototype, {
* x or y co-ordinate. If this is undefined we hide the guide.
*/
_updateGuide: function(guide, point=-1) {
if (point > 0) {
let offset = GUIDE_STROKE_WIDTH / 2;
if (guide === this._guideNodes.top || guide === this._guideNodes.left) {
point -= offset;
} else {
point += offset;
}
if (guide === this._guideNodes.top || guide === this._guideNodes.bottom) {
guide.setAttribute("x1", 0);
guide.setAttribute("y1", point);
guide.setAttribute("x2", "100%");
guide.setAttribute("y2", point);
} else {
guide.setAttribute("x1", point);
guide.setAttribute("y1", 0);
guide.setAttribute("x2", point);
guide.setAttribute("y2", "100%");
}
guide.removeAttribute("hidden");
return true;
} else {
if (point <= 0) {
guide.setAttribute("hidden", "true");
return false;
}
let offset = GUIDE_STROKE_WIDTH / 2;
if (guide === this._guideNodes.top || guide === this._guideNodes.left) {
point -= offset;
} else {
point += offset;
}
if (guide === this._guideNodes.top || guide === this._guideNodes.bottom) {
guide.setAttribute("x1", 0);
guide.setAttribute("y1", point);
guide.setAttribute("x2", "100%");
guide.setAttribute("y2", point);
} else {
guide.setAttribute("x1", point);
guide.setAttribute("y1", 0);
guide.setAttribute("x2", point);
guide.setAttribute("y2", "100%");
}
guide.removeAttribute("hidden");
return true;
},
/**

View File

@ -23,6 +23,8 @@ support-files =
[test_framerate_02.html]
[test_framerate_03.html]
[test_framerate_04.html]
[test_highlighter-boxmodel_01.html]
[test_highlighter-boxmodel_02.html]
[test_highlighter-csstransform_01.html]
[test_highlighter-csstransform_02.html]
[test_highlighter-csstransform_03.html]

View File

@ -0,0 +1,69 @@
<!DOCTYPE HTML>
<html>
<!--
Box Model Highlighter
Test the creation of the SVG highlighter elements in the browser
-->
<head>
<meta charset="utf-8">
<title>box model highlighter actor test</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script type="application/javascript;version=1.8">
window.onload = function() {
var Cu = Components.utils;
var Cc = Components.classes;
var Ci = Components.interfaces;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/devtools/Loader.jsm");
Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
Cu.import("resource://gre/modules/Task.jsm");
const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
SimpleTest.waitForExplicitFinish();
var {InspectorFront} = devtools.require("devtools/server/actors/inspector");
DebuggerServer.init(() => true);
DebuggerServer.addBrowserActors();
var client = new DebuggerClient(DebuggerServer.connectPipe());
client.connect(() => {
client.listTabs(response => {
var form = response.tabs[response.selected];
var front = InspectorFront(client, form);
Task.spawn(function*() {
let walkerFront = yield front.getWalker();
let highlighterFront = yield front.getHighlighterByType(
"BoxModelHighlighter");
let gBrowser = Services.wm.getMostRecentWindow("navigator:browser").gBrowser;
let container =
gBrowser.selectedBrowser.parentNode.querySelector(".highlighter-container");
ok(container, "The highlighter container was found");
is(container.querySelectorAll("polygon").length, 4, "Found 4 polygons");
is(container.querySelectorAll("line").length, 4, "Found 4 guides");
ok(container.querySelector(".highlighter-nodeinfobar-container"), "Found the infobar");
yield highlighterFront.finalize();
}).then(null, ok.bind(null, false)).then(() => {
client.close(() => {
DebuggerServer.destroy();
SimpleTest.finish();
});
});
});
});
}
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,258 @@
<!DOCTYPE HTML>
<html>
<!--
Box Model Highlighter
Test available configuration options of the highlighter
-->
<head>
<meta charset="utf-8">
<title>box model highlighter actor test</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<style>
body {
margin: 0;
padding: 0;
}
#test-element {
width: 100px;
height: 100px;
padding: 10px;
margin: 5px;
border: 20px solid red;
background: yellow;
}
</style>
</head>
<body style="margin">
<div id="test-element"></div>
<pre id="test">
<script type="application/javascript;version=1.8">
window.onload = function() {
var Cu = Components.utils;
var Cc = Components.classes;
var Ci = Components.interfaces;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/devtools/Loader.jsm");
Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
Cu.import("resource://gre/modules/Task.jsm");
const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
SimpleTest.waitForExplicitFinish();
var {InspectorFront} = devtools.require("devtools/server/actors/inspector");
DebuggerServer.init(() => true);
DebuggerServer.addBrowserActors();
var client = new DebuggerClient(DebuggerServer.connectPipe());
client.connect(() => {
client.listTabs(response => {
var form = response.tabs[response.selected];
var front = InspectorFront(client, form);
Task.spawn(function*() {
let walkerFront = yield front.getWalker();
let highlighterFront = yield front.getHighlighterByType(
"BoxModelHighlighter");
let gBrowser = Services.wm.getMostRecentWindow("navigator:browser").gBrowser;
let container =
gBrowser.selectedBrowser.parentNode.querySelector(".highlighter-container");
yield guidesAndInforBarShownByDefault(highlighterFront, walkerFront, container);
yield allRegionsAreShownByDefault(highlighterFront, walkerFront, container);
yield guidesCanBeHidden(highlighterFront, walkerFront, container);
yield infobarCanBeHidden(highlighterFront, walkerFront, container);
yield onlyOneRegionCanBeShown(highlighterFront, walkerFront, container);
yield guidesCanBeDrawnOnAGivenRegion(highlighterFront, walkerFront, container);
yield highlighterFront.finalize();
}).then(null, ok.bind(null, false)).then(() => {
client.close(() => {
DebuggerServer.destroy();
SimpleTest.finish();
});
});
});
});
function getTestNode(walkerFront) {
let rawNode = document.getElementById("test-element");
return walkerFront.frontForRawNode(rawNode);
}
function* guidesAndInforBarShownByDefault(highlighterFront, walkerFront, container) {
info("Checking that by default, guides and infobar are shown");
let node = getTestNode(walkerFront);
let infobar = container.querySelector(".highlighter-nodeinfobar-positioner");
let svg = container.querySelector("svg");
let lines = svg.querySelectorAll("line");
info("Showing the highlighter, with no options");
yield highlighterFront.show(node);
ok(!infobar.hasAttribute("hidden"), "Infobar shown");
ok(!svg.hasAttribute("hidden"), "SVG markup shown");
for (let line of lines) {
ok(!line.hasAttribute("hidden"), "Guide shown");
}
yield highlighterFront.hide();
}
function* allRegionsAreShownByDefault(highlighterFront, walkerFront, container) {
info("Checking that by default, all regions are shown");
let node = getTestNode(walkerFront);
info("Showing the highlighter, with no options");
yield highlighterFront.show(node);
let content = container.querySelector(".box-model-content");
let padding = container.querySelector(".box-model-padding");
let border = container.querySelector(".box-model-border");
let margin = container.querySelector(".box-model-margin");
ok(content.getAttribute("points"), "The points of the content region are set");
ok(padding.getAttribute("points"), "The points of the padding region are set");
ok(border.getAttribute("points"), "The points of the border region are set");
ok(margin.getAttribute("points"), "The points of the margin region are set");
yield highlighterFront.hide();
}
function* guidesCanBeHidden(highlighterFront, walkerFront, container) {
info("Checking that guides can be hidden");
let node = getTestNode(walkerFront);
let svg = container.querySelector("svg");
let lines = svg.querySelectorAll("line");
info("Showing the highlighter, with the hideGuides option");
yield highlighterFront.show(node, {
hideGuides: true
});
for (let line of lines) {
ok(line.hasAttribute("hidden"), "Guide hidden");
}
yield highlighterFront.hide();
}
function* infobarCanBeHidden(highlighterFront, walkerFront, container) {
info("Checking that the infobar can be hidden");
let node = getTestNode(walkerFront);
let svg = container.querySelector("svg");
let lines = svg.querySelectorAll("line");
info("Showing the highlighter, with the hideInfoBar option");
yield highlighterFront.show(node, {
hideInfoBar: true
});
let infobar = container.querySelector(".highlighter-nodeinfobar-positioner");
ok(infobar.hasAttribute("hidden"), "Infobar hidden");
yield highlighterFront.hide();
}
function* onlyOneRegionCanBeShown(highlighterFront, walkerFront, container) {
info("Checking that it's possible to select which region to show");
let node = getTestNode(walkerFront);
let svg = container.querySelector("svg");
let lines = svg.querySelectorAll("line");
let content = container.querySelector(".box-model-content");
let padding = container.querySelector(".box-model-padding");
let border = container.querySelector(".box-model-border");
let margin = container.querySelector(".box-model-margin");
info("Showing the highlighter, with the showOnly option set to content");
yield highlighterFront.show(node, {
showOnly: "content"
});
ok(content.getAttribute("points"), "The points of the content region are set");
ok(!padding.getAttribute("points"), "The points of the padding region are not set");
ok(!border.getAttribute("points"), "The points of the border region are not set");
ok(!margin.getAttribute("points"), "The points of the margin region are not set");
info("Hiding the highlighter and showing it again with the showOnly option " +
"set to margin");
yield highlighterFront.hide();
yield highlighterFront.show(node, {
showOnly: "margin"
});
ok(!content.getAttribute("points"), "The points of the content region are not set");
ok(!padding.getAttribute("points"), "The points of the padding region are not set");
ok(!border.getAttribute("points"), "The points of the border region are not set");
ok(margin.getAttribute("points"), "The points of the margin region are set");
yield highlighterFront.hide();
}
function* guidesCanBeDrawnOnAGivenRegion(highlighterFront, walkerFront, container) {
info("Checking that it's possible to choose which region the guides surround");
let node = getTestNode(walkerFront);
info("Showing the highlighter, with the region option set to padding");
yield highlighterFront.show(node, {
region: "padding"
});
info("Getting the guides and padding region element to compare coordinates");
let guideTop = container.querySelector(".box-model-guide-top");
let guideRight = container.querySelector(".box-model-guide-right");
let guideBottom = container.querySelector(".box-model-guide-bottom");
let guideLeft = container.querySelector(".box-model-guide-left");
let padding = container.querySelector(".box-model-padding");
let points = padding.getAttribute("points").split(" ").map(xy => xy.split(","));
is(Math.ceil(guideTop.getAttribute("y1")), points[0][1],
"Top guide's y1 is correct");
is(Math.floor(guideBottom.getAttribute("y1")), points[2][1],
"Bottom guide's y1 is correct");
is(Math.floor(guideRight.getAttribute("x1")), points[1][0],
"Right guide's x1 is correct");
is(Math.ceil(guideLeft.getAttribute("x1")), points[3][0],
"Left guide's x1 is correct");
info("Hiding the highlighter and showing it again with the margin region");
yield highlighterFront.hide();
yield highlighterFront.show(node, {
region: "margin"
});
let margin = container.querySelector(".box-model-margin");
let points = margin.getAttribute("points").split(" ").map(xy => xy.split(","));
is(Math.ceil(guideTop.getAttribute("y1")), points[0][1],
"Top guide's y1 is correct");
is(Math.floor(guideBottom.getAttribute("y1")), points[2][1],
"Bottom guide's y1 is correct");
is(Math.floor(guideRight.getAttribute("x1")), points[1][0],
"Right guide's x1 is correct");
is(Math.ceil(guideLeft.getAttribute("x1")), points[3][0],
"Left guide's x1 is correct");
}
}
</script>
</pre>
</body>
</html>