--- a/devtools/server/actors/highlighters/shapes.js
+++ b/devtools/server/actors/highlighters/shapes.js
@@ -115,16 +115,106 @@ class ShapesHighlighter extends AutoRefr
"id": "shape-container",
"class": "shape-container",
"viewBox": "0 0 100 100",
"preserveAspectRatio": "none"
},
prefix: this.ID_CLASS_PREFIX
});
+ // This clipPath and its children make sure the element quad outline
+ // is only shown when the shape extends past the element quads.
+ let clipSvg = createSVGNode(this.win, {
+ nodeType: "clipPath",
+ parent: mainSvg,
+ attributes: {
+ "id": "clip-path",
+ "class": "clip-path",
+ },
+ prefix: this.ID_CLASS_PREFIX
+ });
+
+ createSVGNode(this.win, {
+ nodeType: "polygon",
+ parent: clipSvg,
+ attributes: {
+ "id": "clip-polygon",
+ "class": "clip-polygon",
+ "hidden": "true"
+ },
+ prefix: this.ID_CLASS_PREFIX
+ });
+
+ createSVGNode(this.win, {
+ nodeType: "ellipse",
+ parent: clipSvg,
+ attributes: {
+ "id": "clip-ellipse",
+ "class": "clip-ellipse",
+ "hidden": true
+ },
+ prefix: this.ID_CLASS_PREFIX
+ });
+
+ createSVGNode(this.win, {
+ nodeType: "rect",
+ parent: clipSvg,
+ attributes: {
+ "id": "clip-rect",
+ "class": "clip-rect",
+ "hidden": true
+ },
+ prefix: this.ID_CLASS_PREFIX
+ });
+
+ // Rectangle that displays the element quads. Only shown for shape-outside.
+ // Only the parts of the rectangle's outline that overlap with the shape is shown.
+ createSVGNode(this.win, {
+ nodeType: "rect",
+ parent: mainSvg,
+ attributes: {
+ "id": "quad",
+ "class": "quad",
+ "hidden": "true",
+ "clip-path": "url(#shapes-clip-path)",
+ "x": 0,
+ "y": 0,
+ "width": 100,
+ "height": 100
+ },
+ prefix: this.ID_CLASS_PREFIX
+ });
+
+ // clipPath that corresponds to the element's quads. Only applied for shape-outside.
+ // This ensures only the parts of the shape that are within the element's quads are
+ // outlined by a solid line.
+ let shapeClipSvg = createSVGNode(this.win, {
+ nodeType: "clipPath",
+ parent: mainSvg,
+ attributes: {
+ "id": "quad-clip-path",
+ "class": "quad-clip-path",
+ },
+ prefix: this.ID_CLASS_PREFIX
+ });
+
+ createSVGNode(this.win, {
+ nodeType: "rect",
+ parent: shapeClipSvg,
+ attributes: {
+ "id": "quad-clip",
+ "class": "quad-clip",
+ "x": -1,
+ "y": -1,
+ "width": 102,
+ "height": 102
+ },
+ prefix: this.ID_CLASS_PREFIX
+ });
+
let mainGroup = createSVGNode(this.win, {
nodeType: "g",
parent: mainSvg,
attributes: {
"id": "group",
},
prefix: this.ID_CLASS_PREFIX
});
@@ -160,16 +250,54 @@ class ShapesHighlighter extends AutoRefr
attributes: {
"id": "rect",
"class": "rect",
"hidden": true
},
prefix: this.ID_CLASS_PREFIX
});
+ // Dashed versions of each shape. Only shown for the parts of the shape
+ // that extends past the element's quads.
+ createSVGNode(this.win, {
+ nodeType: "polygon",
+ parent: mainGroup,
+ attributes: {
+ "id": "dashed-polygon",
+ "class": "polygon",
+ "hidden": "true",
+ "stroke-dasharray": "5, 5",
+ },
+ prefix: this.ID_CLASS_PREFIX
+ });
+
+ createSVGNode(this.win, {
+ nodeType: "ellipse",
+ parent: mainGroup,
+ attributes: {
+ "id": "dashed-ellipse",
+ "class": "ellipse",
+ "hidden": "true",
+ "stroke-dasharray": "5, 5",
+ },
+ prefix: this.ID_CLASS_PREFIX
+ });
+
+ createSVGNode(this.win, {
+ nodeType: "rect",
+ parent: mainGroup,
+ attributes: {
+ "id": "dashed-rect",
+ "class": "rect",
+ "hidden": "true",
+ "stroke-dasharray": "5, 5",
+ },
+ prefix: this.ID_CLASS_PREFIX
+ });
+
createSVGNode(this.win, {
nodeType: "path",
parent: mainGroup,
attributes: {
"id": "bounding-box",
"class": "bounding-box",
"stroke-dasharray": "5, 5",
"hidden": true
@@ -314,24 +442,25 @@ class ShapesHighlighter extends AutoRefr
let { target, type, pageX, pageY } = event;
// For events on highlighted nodes in an iframe, when the event takes place
// outside the iframe. Check if event target belongs to the iframe. If it doesn't,
// adjust pageX/pageY to be relative to the iframe rather than the parent.
let nodeDocument = this.currentNode.ownerDocument;
if (target !== nodeDocument && target.ownerDocument !== nodeDocument) {
let [xOffset, yOffset] = getFrameOffsets(target.ownerGlobal, this.currentNode);
+ let zoom = getCurrentZoom(this.win);
// xOffset/yOffset are relative to the viewport, so first find the top/left
// edges of the viewport relative to the page.
let viewportLeft = pageX - event.clientX;
let viewportTop = pageY - event.clientY;
// Also adjust for scrolling in the iframe.
let { scrollTop, scrollLeft } = nodeDocument.documentElement;
- pageX -= viewportLeft + xOffset - scrollLeft;
- pageY -= viewportTop + yOffset - scrollTop;
+ pageX -= viewportLeft + xOffset / zoom - scrollLeft;
+ pageY -= viewportTop + yOffset / zoom - scrollTop;
}
switch (type) {
case "pagehide":
// If a page hide event is triggered for current window's highlighter, hide the
// highlighter.
if (target.defaultView === this.win) {
this.destroy();
@@ -2007,16 +2136,23 @@ class ShapesHighlighter extends AutoRefr
_hideShapes() {
this.getElement("ellipse").setAttribute("hidden", true);
this.getElement("polygon").setAttribute("hidden", true);
this.getElement("rect").setAttribute("hidden", true);
this.getElement("bounding-box").setAttribute("hidden", true);
this.getElement("markers").setAttribute("d", "");
this.getElement("markers-outline").setAttribute("d", "");
this.getElement("rotate-line").setAttribute("d", "");
+ this.getElement("quad").setAttribute("hidden", true);
+ this.getElement("clip-ellipse").setAttribute("hidden", true);
+ this.getElement("clip-polygon").setAttribute("hidden", true);
+ this.getElement("clip-rect").setAttribute("hidden", true);
+ this.getElement("dashed-polygon").setAttribute("hidden", true);
+ this.getElement("dashed-ellipse").setAttribute("hidden", true);
+ this.getElement("dashed-rect").setAttribute("hidden", true);
}
/**
* Update the highlighter for the current node. Called whenever the element's quads
* or CSS shape has changed.
* @returns {Boolean} whether the highlighter was successfully updated
*/
_update() {
@@ -2041,16 +2177,32 @@ class ShapesHighlighter extends AutoRefr
} else if (this.shapeType === "circle") {
this._updateCircleShape(width, height, zoom);
} else if (this.shapeType === "ellipse") {
this._updateEllipseShape(width, height, zoom);
} else if (this.shapeType === "inset") {
this._updateInsetShape(width, height, zoom);
}
+ if (this.property === "shape-outside") {
+ // For shape-outside, the element's quads are displayed for the parts that overlap
+ // with the shape, and the parts of the shape that extend past the element's quads
+ // are shown with a dashed line.
+ let quadRect = this.getElement("quad");
+ quadRect.removeAttribute("hidden");
+
+ this.getElement("polygon").setAttribute("clip-path", "url(#shapes-quad-clip-path)");
+ this.getElement("ellipse").setAttribute("clip-path", "url(#shapes-quad-clip-path)");
+ this.getElement("rect").setAttribute("clip-path", "url(#shapes-quad-clip-path)");
+ } else {
+ this.getElement("polygon").removeAttribute("clip-path");
+ this.getElement("ellipse").removeAttribute("clip-path");
+ this.getElement("rect").removeAttribute("clip-path");
+ }
+
let { width: winWidth, height: winHeight } = this._winDimensions;
root.removeAttribute("hidden");
root.setAttribute("style",
`position:absolute; width:${winWidth}px;height:${winHeight}px; overflow:hidden;`);
this._handleMarkerHover(this.hoveredPoint);
setIgnoreLayoutChanges(false, this.highlighterEnv.window.document.documentElement);
@@ -2079,27 +2231,49 @@ class ShapesHighlighter extends AutoRefr
if (this.shapeType === "polygon") {
let points = this.coordinates.map(point => point.join(",")).join(" ");
let polygonEl = this.getElement("polygon");
polygonEl.setAttribute("points", points);
polygonEl.removeAttribute("hidden");
+ let clipPolygon = this.getElement("clip-polygon");
+ clipPolygon.setAttribute("points", points);
+ clipPolygon.removeAttribute("hidden");
+
+ let dashedPolygon = this.getElement("dashed-polygon");
+ dashedPolygon.setAttribute("points", points);
+ dashedPolygon.removeAttribute("hidden");
+
markerPoints.push(rotatePoint);
let rotateLine = `M ${center.join(" ")} L ${rotatePoint.join(" ")}`;
this.getElement("rotate-line").setAttribute("d", rotateLine);
} else if (this.shapeType === "circle" || this.shapeType === "ellipse") {
let { rx, ry, cx, cy } = this.coordinates;
let ellipseEl = this.getElement("ellipse");
ellipseEl.setAttribute("rx", rx);
ellipseEl.setAttribute("ry", ry);
ellipseEl.setAttribute("cx", cx);
ellipseEl.setAttribute("cy", cy);
ellipseEl.removeAttribute("hidden");
+
+ let clipEllipse = this.getElement("clip-ellipse");
+ clipEllipse.setAttribute("rx", rx);
+ clipEllipse.setAttribute("ry", ry);
+ clipEllipse.setAttribute("cx", cx);
+ clipEllipse.setAttribute("cy", cy);
+ clipEllipse.removeAttribute("hidden");
+
+ let dashedEllipse = this.getElement("dashed-ellipse");
+ dashedEllipse.setAttribute("rx", rx);
+ dashedEllipse.setAttribute("ry", ry);
+ dashedEllipse.setAttribute("cx", cx);
+ dashedEllipse.setAttribute("cy", cy);
+ dashedEllipse.removeAttribute("hidden");
}
this._drawMarkers(markerPoints, width, height, zoom);
}
/**
* Update the SVG polygon to fit the CSS polygon.
* @param {Number} width the width of the element quads
* @param {Number} height the height of the element quads
@@ -2108,16 +2282,23 @@ class ShapesHighlighter extends AutoRefr
_updatePolygonShape(width, height, zoom) {
// Draw and show the polygon.
let points = this.coordinates.map(point => point.join(",")).join(" ");
let polygonEl = this.getElement("polygon");
polygonEl.setAttribute("points", points);
polygonEl.removeAttribute("hidden");
+ let clipPolygon = this.getElement("clip-polygon");
+ clipPolygon.setAttribute("points", points);
+ clipPolygon.removeAttribute("hidden");
+
+ let dashedPolygon = this.getElement("dashed-polygon");
+ dashedPolygon.setAttribute("points", points);
+ dashedPolygon.removeAttribute("hidden");
this._drawMarkers(this.coordinates, width, height, zoom);
}
/**
* Update the SVG ellipse to fit the CSS circle.
* @param {Number} width the width of the element quads
* @param {Number} height the height of the element quads
* @param {Number} zoom the zoom level of the window
@@ -2126,16 +2307,30 @@ class ShapesHighlighter extends AutoRefr
let { rx, ry, cx, cy } = this.coordinates;
let ellipseEl = this.getElement("ellipse");
ellipseEl.setAttribute("rx", rx);
ellipseEl.setAttribute("ry", ry);
ellipseEl.setAttribute("cx", cx);
ellipseEl.setAttribute("cy", cy);
ellipseEl.removeAttribute("hidden");
+ let clipEllipse = this.getElement("clip-ellipse");
+ clipEllipse.setAttribute("rx", rx);
+ clipEllipse.setAttribute("ry", ry);
+ clipEllipse.setAttribute("cx", cx);
+ clipEllipse.setAttribute("cy", cy);
+ clipEllipse.removeAttribute("hidden");
+
+ let dashedEllipse = this.getElement("dashed-ellipse");
+ dashedEllipse.setAttribute("rx", rx);
+ dashedEllipse.setAttribute("ry", ry);
+ dashedEllipse.setAttribute("cx", cx);
+ dashedEllipse.setAttribute("cy", cy);
+ dashedEllipse.removeAttribute("hidden");
+
this._drawMarkers([[cx, cy], [cx + rx, cy]], width, height, zoom);
}
/**
* Update the SVG ellipse to fit the CSS ellipse.
* @param {Number} width the width of the element quads
* @param {Number} height the height of the element quads
* @param {Number} zoom the zoom level of the window
@@ -2144,16 +2339,29 @@ class ShapesHighlighter extends AutoRefr
let { rx, ry, cx, cy } = this.coordinates;
let ellipseEl = this.getElement("ellipse");
ellipseEl.setAttribute("rx", rx);
ellipseEl.setAttribute("ry", ry);
ellipseEl.setAttribute("cx", cx);
ellipseEl.setAttribute("cy", cy);
ellipseEl.removeAttribute("hidden");
+ let clipEllipse = this.getElement("clip-ellipse");
+ clipEllipse.setAttribute("rx", rx);
+ clipEllipse.setAttribute("ry", ry);
+ clipEllipse.setAttribute("cx", cx);
+ clipEllipse.setAttribute("cy", cy);
+ clipEllipse.removeAttribute("hidden");
+
+ let dashedEllipse = this.getElement("dashed-ellipse");
+ dashedEllipse.setAttribute("rx", rx);
+ dashedEllipse.setAttribute("ry", ry);
+ dashedEllipse.setAttribute("cx", cx);
+ dashedEllipse.setAttribute("cy", cy);
+ dashedEllipse.removeAttribute("hidden");
let markerCoords = [ [cx, cy], [cx + rx, cy], [cx, cy + ry] ];
this._drawMarkers(markerCoords, width, height, zoom);
}
/**
* Update the SVG rect to fit the CSS inset.
* @param {Number} width the width of the element quads
* @param {Number} height the height of the element quads
@@ -2163,16 +2371,30 @@ class ShapesHighlighter extends AutoRefr
let { top, left, right, bottom } = this.coordinates;
let rectEl = this.getElement("rect");
rectEl.setAttribute("x", left);
rectEl.setAttribute("y", top);
rectEl.setAttribute("width", 100 - left - right);
rectEl.setAttribute("height", 100 - top - bottom);
rectEl.removeAttribute("hidden");
+ let clipRect = this.getElement("clip-rect");
+ clipRect.setAttribute("x", left);
+ clipRect.setAttribute("y", top);
+ clipRect.setAttribute("width", 100 - left - right);
+ clipRect.setAttribute("height", 100 - top - bottom);
+ clipRect.removeAttribute("hidden");
+
+ let dashedRect = this.getElement("dashed-rect");
+ dashedRect.setAttribute("x", left);
+ dashedRect.setAttribute("y", top);
+ dashedRect.setAttribute("width", 100 - left - right);
+ dashedRect.setAttribute("height", 100 - top - bottom);
+ dashedRect.removeAttribute("hidden");
+
let centerX = (left + (100 - right)) / 2;
let centerY = (top + (100 - bottom)) / 2;
let markerCoords = [[centerX, top], [100 - right, centerY],
[centerX, 100 - bottom], [left, centerY]];
this._drawMarkers(markerCoords, width, height, zoom);
}
/**