summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/highlighters/css-transform.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/actors/highlighters/css-transform.js')
-rw-r--r--devtools/server/actors/highlighters/css-transform.js243
1 files changed, 243 insertions, 0 deletions
diff --git a/devtools/server/actors/highlighters/css-transform.js b/devtools/server/actors/highlighters/css-transform.js
new file mode 100644
index 000000000..83a2c7e59
--- /dev/null
+++ b/devtools/server/actors/highlighters/css-transform.js
@@ -0,0 +1,243 @@
+/* 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";
+
+const { extend } = require("sdk/core/heritage");
+const { AutoRefreshHighlighter } = require("./auto-refresh");
+const {
+ CanvasFrameAnonymousContentHelper, getComputedStyle,
+ createSVGNode, createNode } = require("./utils/markup");
+const { setIgnoreLayoutChanges,
+ getNodeBounds } = require("devtools/shared/layout/utils");
+
+// The minimum distance a line should be before it has an arrow marker-end
+const ARROW_LINE_MIN_DISTANCE = 10;
+
+var MARKER_COUNTER = 1;
+
+/**
+ * The CssTransformHighlighter is the class that draws an outline around a
+ * transformed element and an outline around where it would be if untransformed
+ * as well as arrows connecting the 2 outlines' corners.
+ */
+function CssTransformHighlighter(highlighterEnv) {
+ AutoRefreshHighlighter.call(this, highlighterEnv);
+
+ this.markup = new CanvasFrameAnonymousContentHelper(this.highlighterEnv,
+ this._buildMarkup.bind(this));
+}
+
+CssTransformHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
+ typeName: "CssTransformHighlighter",
+
+ ID_CLASS_PREFIX: "css-transform-",
+
+ _buildMarkup: function () {
+ let container = createNode(this.win, {
+ attributes: {
+ "class": "highlighter-container"
+ }
+ });
+
+ // The root wrapper is used to unzoom the highlighter when needed.
+ let rootWrapper = createNode(this.win, {
+ parent: container,
+ attributes: {
+ "id": "root",
+ "class": "root"
+ },
+ prefix: this.ID_CLASS_PREFIX
+ });
+
+ let svg = createSVGNode(this.win, {
+ nodeType: "svg",
+ parent: rootWrapper,
+ attributes: {
+ "id": "elements",
+ "hidden": "true",
+ "width": "100%",
+ "height": "100%"
+ },
+ prefix: this.ID_CLASS_PREFIX
+ });
+
+ // Add a marker tag to the svg root for the arrow tip
+ this.markerId = "arrow-marker-" + MARKER_COUNTER;
+ MARKER_COUNTER++;
+ let marker = createSVGNode(this.win, {
+ nodeType: "marker",
+ parent: svg,
+ attributes: {
+ "id": this.markerId,
+ "markerWidth": "10",
+ "markerHeight": "5",
+ "orient": "auto",
+ "markerUnits": "strokeWidth",
+ "refX": "10",
+ "refY": "5",
+ "viewBox": "0 0 10 10"
+ },
+ prefix: this.ID_CLASS_PREFIX
+ });
+ createSVGNode(this.win, {
+ nodeType: "path",
+ parent: marker,
+ attributes: {
+ "d": "M 0 0 L 10 5 L 0 10 z",
+ "fill": "#08C"
+ }
+ });
+
+ let shapesGroup = createSVGNode(this.win, {
+ nodeType: "g",
+ parent: svg
+ });
+
+ // Create the 2 polygons (transformed and untransformed)
+ createSVGNode(this.win, {
+ nodeType: "polygon",
+ parent: shapesGroup,
+ attributes: {
+ "id": "untransformed",
+ "class": "untransformed"
+ },
+ prefix: this.ID_CLASS_PREFIX
+ });
+ createSVGNode(this.win, {
+ nodeType: "polygon",
+ parent: shapesGroup,
+ attributes: {
+ "id": "transformed",
+ "class": "transformed"
+ },
+ prefix: this.ID_CLASS_PREFIX
+ });
+
+ // Create the arrows
+ for (let nb of ["1", "2", "3", "4"]) {
+ createSVGNode(this.win, {
+ nodeType: "line",
+ parent: shapesGroup,
+ attributes: {
+ "id": "line" + nb,
+ "class": "line",
+ "marker-end": "url(#" + this.markerId + ")"
+ },
+ prefix: this.ID_CLASS_PREFIX
+ });
+ }
+
+ return container;
+ },
+
+ /**
+ * Destroy the nodes. Remove listeners.
+ */
+ destroy: function () {
+ AutoRefreshHighlighter.prototype.destroy.call(this);
+ this.markup.destroy();
+ },
+
+ getElement: function (id) {
+ return this.markup.getElement(this.ID_CLASS_PREFIX + id);
+ },
+
+ /**
+ * Show the highlighter on a given node
+ */
+ _show: function () {
+ if (!this._isTransformed(this.currentNode)) {
+ this.hide();
+ return false;
+ }
+
+ return this._update();
+ },
+
+ /**
+ * Checks if the supplied node is transformed and not inline
+ */
+ _isTransformed: function (node) {
+ let style = getComputedStyle(node);
+ return style && (style.transform !== "none" && style.display !== "inline");
+ },
+
+ _setPolygonPoints: function (quad, id) {
+ let points = [];
+ for (let point of ["p1", "p2", "p3", "p4"]) {
+ points.push(quad[point].x + "," + quad[point].y);
+ }
+ this.getElement(id).setAttribute("points", points.join(" "));
+ },
+
+ _setLinePoints: function (p1, p2, id) {
+ let line = this.getElement(id);
+ line.setAttribute("x1", p1.x);
+ line.setAttribute("y1", p1.y);
+ line.setAttribute("x2", p2.x);
+ line.setAttribute("y2", p2.y);
+
+ let dist = Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
+ if (dist < ARROW_LINE_MIN_DISTANCE) {
+ line.removeAttribute("marker-end");
+ } else {
+ line.setAttribute("marker-end", "url(#" + this.markerId + ")");
+ }
+ },
+
+ /**
+ * 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 quads = this.currentQuads.border;
+ if (!quads.length ||
+ quads[0].bounds.width <= 0 || quads[0].bounds.height <= 0) {
+ this._hideShapes();
+ return false;
+ }
+
+ let [quad] = quads;
+
+ // Getting the points for the untransformed shape
+ let untransformedQuad = getNodeBounds(this.win, 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);
+ }
+
+ // Adapt to the current zoom
+ this.markup.scaleRootElement(this.currentNode, this.ID_CLASS_PREFIX + "root");
+
+ this._showShapes();
+
+ setIgnoreLayoutChanges(false, this.highlighterEnv.window.document.documentElement);
+ return true;
+ },
+
+ /**
+ * Hide the highlighter, the outline and the infobar.
+ */
+ _hide: function () {
+ setIgnoreLayoutChanges(true);
+ this._hideShapes();
+ setIgnoreLayoutChanges(false, this.highlighterEnv.window.document.documentElement);
+ },
+
+ _hideShapes: function () {
+ this.getElement("elements").setAttribute("hidden", "true");
+ },
+
+ _showShapes: function () {
+ this.getElement("elements").removeAttribute("hidden");
+ }
+});
+exports.CssTransformHighlighter = CssTransformHighlighter;