зеркало из https://github.com/mozilla/gecko-dev.git
Backed out changeset 57c170af6a23 (bug 726427) for Mac 10.6 mochitest Failure in Bug 947126
This commit is contained in:
Родитель
af5af6d661
Коммит
43a1495d3d
|
@ -31,4 +31,3 @@ support-files =
|
|||
[browser_toolbar_tooltip.js]
|
||||
[browser_toolbar_webconsole_errors_count.js]
|
||||
[browser_spectrum.js]
|
||||
[browser_csstransformpreview.js]
|
||||
|
|
|
@ -1,139 +0,0 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Tests that the spectrum color picker works correctly
|
||||
|
||||
const TEST_URI = "data:text/html;charset=utf-8,<div></div>";
|
||||
const {CSSTransformPreviewer} = devtools.require("devtools/shared/widgets/CSSTransformPreviewer");
|
||||
|
||||
let doc, root;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
addTab(TEST_URI, () => {
|
||||
doc = content.document;
|
||||
root = doc.querySelector("div");
|
||||
startTests();
|
||||
});
|
||||
}
|
||||
|
||||
function endTests() {
|
||||
doc = root = null;
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
}
|
||||
|
||||
function startTests() {
|
||||
testCreateAndDestroyShouldAppendAndRemoveElements();
|
||||
}
|
||||
|
||||
function testCreateAndDestroyShouldAppendAndRemoveElements() {
|
||||
ok(root, "We have the root node to append the preview to");
|
||||
is(root.childElementCount, 0, "Root node is empty");
|
||||
|
||||
let p = new CSSTransformPreviewer(root);
|
||||
p.preview("matrix(1, -0.2, 0, 1, 0, 0)");
|
||||
ok(root.childElementCount > 0, "Preview has appended elements");
|
||||
ok(root.querySelector("canvas"), "Canvas preview element is here");
|
||||
|
||||
p.destroy();
|
||||
is(root.childElementCount, 0, "Destroying preview removed all nodes");
|
||||
|
||||
testCanvasDimensionIsConstrainedByMaxDim();
|
||||
}
|
||||
|
||||
function testCanvasDimensionIsConstrainedByMaxDim() {
|
||||
let p = new CSSTransformPreviewer(root);
|
||||
p.MAX_DIM = 500;
|
||||
p.preview("scale(1)", "center", 1000, 1000);
|
||||
|
||||
let canvas = root.querySelector("canvas");
|
||||
is(canvas.width, 500, "Canvas width is correct");
|
||||
is(canvas.height, 500, "Canvas height is correct");
|
||||
|
||||
p.destroy();
|
||||
|
||||
testCallingPreviewSeveralTimesReusesTheSameCanvas();
|
||||
}
|
||||
|
||||
function testCallingPreviewSeveralTimesReusesTheSameCanvas() {
|
||||
let p = new CSSTransformPreviewer(root);
|
||||
|
||||
p.preview("scale(1)", "center", 1000, 1000);
|
||||
let canvas = root.querySelector("canvas");
|
||||
|
||||
p.preview("rotate(90deg)");
|
||||
let canvases = root.querySelectorAll("canvas");
|
||||
is(canvases.length, 1, "Still one canvas element");
|
||||
is(canvases[0], canvas, "Still the same canvas element");
|
||||
p.destroy();
|
||||
|
||||
testCanvasDimensionAreCorrect();
|
||||
}
|
||||
|
||||
function testCanvasDimensionAreCorrect() {
|
||||
// Only test a few simple transformations
|
||||
let p = new CSSTransformPreviewer(root);
|
||||
|
||||
// Make sure we have a square
|
||||
let w = 200, h = w;
|
||||
p.MAX_DIM = w;
|
||||
|
||||
// We can't test the content of the canvas here, just that, given a max width
|
||||
// the aspect ratio of the canvas seems correct.
|
||||
|
||||
// Translate a square by its width, should be a rectangle
|
||||
p.preview("translateX(200px)", "center", w, h);
|
||||
let canvas = root.querySelector("canvas");
|
||||
is(canvas.width, w, "width is correct");
|
||||
is(canvas.height, h/2, "height is half of the width");
|
||||
|
||||
// Rotate on the top right corner, should be a rectangle
|
||||
p.preview("rotate(-90deg)", "top right", w, h);
|
||||
is(canvas.width, w, "width is correct");
|
||||
is(canvas.height, h/2, "height is half of the width");
|
||||
|
||||
// Rotate on the bottom left corner, should be a rectangle
|
||||
p.preview("rotate(90deg)", "top right", w, h);
|
||||
is(canvas.width, w/2, "width is half of the height");
|
||||
is(canvas.height, h, "height is correct");
|
||||
|
||||
// Scale from center, should still be a square
|
||||
p.preview("scale(2)", "center", w, h);
|
||||
is(canvas.width, w, "width is correct");
|
||||
is(canvas.height, h, "height is correct");
|
||||
|
||||
// Skew from center, 45deg, should be a rectangle
|
||||
p.preview("skew(45deg)", "center", w, h);
|
||||
is(canvas.width, w, "width is correct");
|
||||
is(canvas.height, h/2, "height is half of the height");
|
||||
|
||||
p.destroy();
|
||||
|
||||
testPreviewingInvalidTransformReturnsFalse();
|
||||
}
|
||||
|
||||
function testPreviewingInvalidTransformReturnsFalse() {
|
||||
let p = new CSSTransformPreviewer(root);
|
||||
ok(!p.preview("veryWow(muchPx) suchTransform(soDeg)"), "Returned false for invalid transform");
|
||||
ok(!p.preview("rotae(3deg)"), "Returned false for invalid transform");
|
||||
|
||||
// Verify the canvas is empty by checking the image data
|
||||
let canvas = root.querySelector("canvas"), ctx = canvas.getContext("2d");
|
||||
let data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
|
||||
for (let i = 0, n = data.length; i < n; i += 4) {
|
||||
// Let's not log 250*250*4 asserts! Instead, just log when it fails
|
||||
let red = data[i];
|
||||
let green = data[i + 1];
|
||||
let blue = data[i + 2];
|
||||
let alpha = data[i + 3];
|
||||
if (red !== 0 || green !== 0 || blue !== 0 || alpha !== 0) {
|
||||
ok(false, "Image data is not empty after an invalid transformed was previewed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
is(p.preview("translateX(30px)"), true, "Returned true for a valid transform");
|
||||
endTests();
|
||||
}
|
|
@ -1,389 +0,0 @@
|
|||
/* 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";
|
||||
|
||||
/**
|
||||
* The CSSTransformPreview module displays, using a <canvas> a rectangle, with
|
||||
* a given width and height and its transformed version, given a css transform
|
||||
* property and origin. It also displays arrows from/to each corner.
|
||||
*
|
||||
* It is useful to visualize how a css transform affected an element. It can
|
||||
* help debug tricky transformations. It is used today in a tooltip, and this
|
||||
* tooltip is shown when hovering over a css transform declaration in the rule
|
||||
* and computed view panels.
|
||||
*
|
||||
* TODO: For now, it multiplies matrices itself to calculate the coordinates of
|
||||
* the transformed box, but that should be removed as soon as we can get access
|
||||
* to getQuads().
|
||||
*/
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
/**
|
||||
* The TransformPreview needs an element to output a canvas tag.
|
||||
*
|
||||
* Usage example:
|
||||
*
|
||||
* let t = new CSSTransformPreviewer(myRootElement);
|
||||
* t.preview("rotate(45deg)", "top left", 200, 400);
|
||||
* t.preview("skew(19deg)", "center", 100, 500);
|
||||
* t.preview("matrix(1, -0.2, 0, 1, 0, 0)");
|
||||
* t.destroy();
|
||||
*
|
||||
* @param {nsIDOMElement} parentEl
|
||||
* Where the canvas will go
|
||||
*/
|
||||
function CSSTransformPreviewer(parentEl) {
|
||||
this.parentEl = parentEl;
|
||||
this.doc = this.parentEl.ownerDocument;
|
||||
this.canvas = null;
|
||||
this.ctx = null;
|
||||
}
|
||||
|
||||
module.exports.CSSTransformPreviewer = CSSTransformPreviewer;
|
||||
|
||||
CSSTransformPreviewer.prototype = {
|
||||
/**
|
||||
* The preview look-and-feel can be changed using these properties
|
||||
*/
|
||||
MAX_DIM: 250,
|
||||
PAD: 5,
|
||||
ORIGINAL_FILL: "#1F303F",
|
||||
ORIGINAL_STROKE: "#B2D8FF",
|
||||
TRANSFORMED_FILL: "rgba(200, 200, 200, .5)",
|
||||
TRANSFORMED_STROKE: "#B2D8FF",
|
||||
ARROW_STROKE: "#329AFF",
|
||||
ORIGIN_STROKE: "#329AFF",
|
||||
ARROW_TIP_HEIGHT: 10,
|
||||
ARROW_TIP_WIDTH: 8,
|
||||
CORNER_SIZE_RATIO: 6,
|
||||
|
||||
/**
|
||||
* Destroy removes the canvas from the parentelement passed in the constructor
|
||||
*/
|
||||
destroy: function() {
|
||||
if (this.canvas) {
|
||||
this.parentEl.removeChild(this.canvas);
|
||||
}
|
||||
if (this._hiddenDiv) {
|
||||
this.parentEl.removeChild(this._hiddenDiv);
|
||||
}
|
||||
this.parentEl = this.canvas = this.ctx = this.doc = null;
|
||||
},
|
||||
|
||||
_createMarkup: function() {
|
||||
this.canvas = this.doc.createElementNS(HTML_NS, "canvas");
|
||||
|
||||
this.canvas.setAttribute("id", "canvas");
|
||||
this.canvas.setAttribute("width", this.MAX_DIM);
|
||||
this.canvas.setAttribute("height", this.MAX_DIM);
|
||||
this.canvas.style.position = "relative";
|
||||
this.parentEl.appendChild(this.canvas);
|
||||
|
||||
this.ctx = this.canvas.getContext("2d");
|
||||
},
|
||||
|
||||
_getComputed: function(name, value, width, height) {
|
||||
if (!this._hiddenDiv) {
|
||||
// Create a hidden element to apply the style to
|
||||
this._hiddenDiv = this.doc.createElementNS(HTML_NS, "div");
|
||||
this._hiddenDiv.style.visibility = "hidden";
|
||||
this._hiddenDiv.style.position = "absolute";
|
||||
this.parentEl.appendChild(this._hiddenDiv);
|
||||
}
|
||||
|
||||
// Camelcase the name
|
||||
name = name.replace(/-([a-z]{1})/g, (m, letter) => letter.toUpperCase());
|
||||
|
||||
// Apply width and height to make sure computation is made correctly
|
||||
this._hiddenDiv.style.width = width + "px";
|
||||
this._hiddenDiv.style.height = height + "px";
|
||||
|
||||
// Show the hidden div, apply the style, read the computed style, hide the
|
||||
// hidden div again
|
||||
this._hiddenDiv.style.display = "block";
|
||||
this._hiddenDiv.style[name] = value;
|
||||
let computed = this.doc.defaultView.getComputedStyle(this._hiddenDiv);
|
||||
let computedValue = computed[name];
|
||||
this._hiddenDiv.style.display = "none";
|
||||
|
||||
return computedValue;
|
||||
},
|
||||
|
||||
_getMatrixFromTransformString: function(transformStr) {
|
||||
let matrix = transformStr.substring(0, transformStr.length - 1).
|
||||
substring(transformStr.indexOf("(") + 1).split(",");
|
||||
|
||||
matrix.forEach(function(value, index) {
|
||||
matrix[index] = parseFloat(value, 10);
|
||||
});
|
||||
|
||||
let transformMatrix = null;
|
||||
|
||||
if (matrix.length === 6) {
|
||||
// 2d transform
|
||||
transformMatrix = [
|
||||
[matrix[0], matrix[2], matrix[4], 0],
|
||||
[matrix[1], matrix[3], matrix[5], 0],
|
||||
[0, 0, 1, 0],
|
||||
[0, 0, 0, 1]
|
||||
];
|
||||
} else {
|
||||
// 3d transform
|
||||
transformMatrix = [
|
||||
[matrix[0], matrix[4], matrix[8], matrix[12]],
|
||||
[matrix[1], matrix[5], matrix[9], matrix[13]],
|
||||
[matrix[2], matrix[6], matrix[10], matrix[14]],
|
||||
[matrix[3], matrix[7], matrix[11], matrix[15]]
|
||||
];
|
||||
}
|
||||
|
||||
return transformMatrix;
|
||||
},
|
||||
|
||||
_getOriginFromOriginString: function(originStr) {
|
||||
let offsets = originStr.split(" ");
|
||||
offsets.forEach(function(item, index) {
|
||||
offsets[index] = parseInt(item, 10);
|
||||
});
|
||||
|
||||
return offsets;
|
||||
},
|
||||
|
||||
_multiply: function(m1, m2) {
|
||||
let m = [];
|
||||
for (let m1Line = 0; m1Line < m1.length; m1Line++) {
|
||||
m[m1Line] = 0;
|
||||
for (let m2Col = 0; m2Col < m2.length; m2Col++) {
|
||||
m[m1Line] += m1[m1Line][m2Col] * m2[m2Col];
|
||||
}
|
||||
}
|
||||
return [m[0], m[1]];
|
||||
},
|
||||
|
||||
_getTransformedPoint: function(matrix, point, origin) {
|
||||
let pointMatrix = [point[0] - origin[0], point[1] - origin[1], 1, 1];
|
||||
return this._multiply(matrix, pointMatrix);
|
||||
},
|
||||
|
||||
_getTransformedPoints: function(matrix, rect, origin) {
|
||||
return rect.map(point => {
|
||||
let tPoint = this._getTransformedPoint(matrix, [point[0], point[1]], origin);
|
||||
return [tPoint[0] + origin[0], tPoint[1] + origin[1]];
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* For canvas to avoid anti-aliasing
|
||||
*/
|
||||
_round: x => Math.round(x) + .5,
|
||||
|
||||
_drawShape: function(points, fillStyle, strokeStyle) {
|
||||
this.ctx.save();
|
||||
|
||||
this.ctx.lineWidth = 1;
|
||||
this.ctx.strokeStyle = strokeStyle;
|
||||
this.ctx.fillStyle = fillStyle;
|
||||
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(this._round(points[0][0]), this._round(points[0][1]));
|
||||
for (var i = 1; i < points.length; i++) {
|
||||
this.ctx.lineTo(this._round(points[i][0]), this._round(points[i][1]));
|
||||
}
|
||||
this.ctx.lineTo(this._round(points[0][0]), this._round(points[0][1]));
|
||||
this.ctx.fill();
|
||||
this.ctx.stroke();
|
||||
|
||||
this.ctx.restore();
|
||||
},
|
||||
|
||||
_drawArrow: function(x1, y1, x2, y2) {
|
||||
// do not draw if the line is too small
|
||||
if (Math.abs(x2-x1) < 20 && Math.abs(y2-y1) < 20) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.ctx.save();
|
||||
|
||||
this.ctx.strokeStyle = this.ARROW_STROKE;
|
||||
this.ctx.fillStyle = this.ARROW_STROKE;
|
||||
this.ctx.lineWidth = 1;
|
||||
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(this._round(x1), this._round(y1));
|
||||
this.ctx.lineTo(this._round(x2), this._round(y2));
|
||||
this.ctx.stroke();
|
||||
|
||||
this.ctx.beginPath();
|
||||
this.ctx.translate(x2, y2);
|
||||
let radians = Math.atan((y1 - y2) / (x1 - x2));
|
||||
radians += ((x1 >= x2) ? -90 : 90) * Math.PI / 180;
|
||||
this.ctx.rotate(radians);
|
||||
this.ctx.moveTo(0, 0);
|
||||
this.ctx.lineTo(this.ARROW_TIP_WIDTH / 2, this.ARROW_TIP_HEIGHT);
|
||||
this.ctx.lineTo(-this.ARROW_TIP_WIDTH / 2, this.ARROW_TIP_HEIGHT);
|
||||
this.ctx.closePath();
|
||||
this.ctx.fill();
|
||||
|
||||
this.ctx.restore();
|
||||
},
|
||||
|
||||
_drawOrigin: function(x, y) {
|
||||
this.ctx.save();
|
||||
|
||||
this.ctx.strokeStyle = this.ORIGIN_STROKE;
|
||||
this.ctx.fillStyle = this.ORIGIN_STROKE;
|
||||
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(x, y, 4, 0, 2 * Math.PI, false);
|
||||
this.ctx.stroke();
|
||||
this.ctx.fill();
|
||||
|
||||
this.ctx.restore();
|
||||
},
|
||||
|
||||
/**
|
||||
* Computes the largest width and height of all the given shapes and changes
|
||||
* all of the shapes' points (by reference) so they fit into the configured
|
||||
* MAX_DIM - 2*PAD area.
|
||||
* @return {Object} A {w, h} giving the size the canvas should be
|
||||
*/
|
||||
_fitAllShapes: function(allShapes) {
|
||||
let allXs = [], allYs = [];
|
||||
for (let shape of allShapes) {
|
||||
for (let point of shape) {
|
||||
allXs.push(point[0]);
|
||||
allYs.push(point[1]);
|
||||
}
|
||||
}
|
||||
let minX = Math.min.apply(Math, allXs);
|
||||
let maxX = Math.max.apply(Math, allXs);
|
||||
let minY = Math.min.apply(Math, allYs);
|
||||
let maxY = Math.max.apply(Math, allYs);
|
||||
|
||||
let spanX = maxX - minX;
|
||||
let spanY = maxY - minY;
|
||||
let isWide = spanX > spanY;
|
||||
|
||||
let cw = isWide ? this.MAX_DIM :
|
||||
this.MAX_DIM * Math.min(spanX, spanY) / Math.max(spanX, spanY);
|
||||
let ch = !isWide ? this.MAX_DIM :
|
||||
this.MAX_DIM * Math.min(spanX, spanY) / Math.max(spanX, spanY);
|
||||
|
||||
let mapX = x => this.PAD + ((cw - 2 * this.PAD) / (maxX - minX)) * (x - minX);
|
||||
let mapY = y => this.PAD + ((ch - 2 * this.PAD) / (maxY - minY)) * (y - minY);
|
||||
|
||||
for (let shape of allShapes) {
|
||||
for (let point of shape) {
|
||||
point[0] = mapX(point[0]);
|
||||
point[1] = mapY(point[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return {w: cw, h: ch};
|
||||
},
|
||||
|
||||
_drawShapes: function(shape, corner, transformed, transformedCorner) {
|
||||
this._drawOriginal(shape);
|
||||
this._drawOriginalCorner(corner);
|
||||
this._drawTransformed(transformed);
|
||||
this._drawTransformedCorner(transformedCorner);
|
||||
},
|
||||
|
||||
_drawOriginal: function(points) {
|
||||
this._drawShape(points, this.ORIGINAL_FILL, this.ORIGINAL_STROKE);
|
||||
},
|
||||
|
||||
_drawTransformed: function(points) {
|
||||
this._drawShape(points, this.TRANSFORMED_FILL, this.TRANSFORMED_STROKE);
|
||||
},
|
||||
|
||||
_drawOriginalCorner: function(points) {
|
||||
this._drawShape(points, this.ORIGINAL_STROKE, this.ORIGINAL_STROKE);
|
||||
},
|
||||
|
||||
_drawTransformedCorner: function(points) {
|
||||
this._drawShape(points, this.TRANSFORMED_STROKE, this.TRANSFORMED_STROKE);
|
||||
},
|
||||
|
||||
_drawArrows: function(shape, transformed) {
|
||||
this._drawArrow(shape[0][0], shape[0][1], transformed[0][0], transformed[0][1]);
|
||||
this._drawArrow(shape[1][0], shape[1][1], transformed[1][0], transformed[1][1]);
|
||||
this._drawArrow(shape[2][0], shape[2][1], transformed[2][0], transformed[2][1]);
|
||||
this._drawArrow(shape[3][0], shape[3][1], transformed[3][0], transformed[3][1]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Draw a transform preview
|
||||
*
|
||||
* @param {String} transform
|
||||
* The css transform value as a string, as typed by the user, as long
|
||||
* as it can be computed by the browser
|
||||
* @param {String} origin
|
||||
* Same as above for the transform-origin value. Defaults to "center"
|
||||
* @param {Number} width
|
||||
* The width of the container. Defaults to 200
|
||||
* @param {Number} height
|
||||
* The height of the container. Defaults to 200
|
||||
* @return {Boolean} Whether or not the preview could be created. Will return
|
||||
* false for instance if the transform is invalid
|
||||
*/
|
||||
preview: function(transform, origin="center", width=200, height=200) {
|
||||
// Create/clear the canvas
|
||||
if (!this.canvas) {
|
||||
this._createMarkup();
|
||||
}
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
// Get computed versions of transform and origin
|
||||
transform = this._getComputed("transform", transform, width, height);
|
||||
if (transform && transform !== "none") {
|
||||
origin = this._getComputed("transform-origin", origin, width, height);
|
||||
|
||||
// Get the matrix, origin and width height data for the previewed element
|
||||
let originData = this._getOriginFromOriginString(origin);
|
||||
let matrixData = this._getMatrixFromTransformString(transform);
|
||||
|
||||
// Compute the original box rect and transformed box rect
|
||||
let shapePoints = [
|
||||
[0, 0],
|
||||
[width, 0],
|
||||
[width, height],
|
||||
[0, height]
|
||||
];
|
||||
let transformedPoints = this._getTransformedPoints(matrixData, shapePoints, originData);
|
||||
|
||||
// Do the same for the corner triangle shape
|
||||
let cornerSize = Math.min(shapePoints[2][1] - shapePoints[1][1],
|
||||
shapePoints[1][0] - shapePoints[0][0]) / this.CORNER_SIZE_RATIO;
|
||||
let cornerPoints = [
|
||||
[shapePoints[1][0], shapePoints[1][1]],
|
||||
[shapePoints[1][0], shapePoints[1][1] + cornerSize],
|
||||
[shapePoints[1][0] - cornerSize, shapePoints[1][1]]
|
||||
];
|
||||
let transformedCornerPoints = this._getTransformedPoints(matrixData, cornerPoints, originData);
|
||||
|
||||
// Resize points to fit everything in the canvas
|
||||
let {w, h} = this._fitAllShapes([
|
||||
shapePoints,
|
||||
transformedPoints,
|
||||
cornerPoints,
|
||||
transformedCornerPoints,
|
||||
[originData]
|
||||
]);
|
||||
|
||||
this.canvas.setAttribute("width", w);
|
||||
this.canvas.setAttribute("height", h);
|
||||
|
||||
this._drawShapes(shapePoints, cornerPoints, transformedPoints, transformedCornerPoints)
|
||||
this._drawArrows(shapePoints, transformedPoints);
|
||||
this._drawOrigin(originData[0], originData[1]);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
|
@ -12,7 +12,6 @@ const {Spectrum} = require("devtools/shared/widgets/Spectrum");
|
|||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const {colorUtils} = require("devtools/css-color");
|
||||
const Heritage = require("sdk/core/heritage");
|
||||
const {CSSTransformPreviewer} = require("devtools/shared/widgets/CSSTransformPreviewer");
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
@ -101,6 +100,7 @@ let PanelFactory = {
|
|||
panel.setAttribute("hidden", true);
|
||||
panel.setAttribute("ignorekeys", true);
|
||||
|
||||
// Prevent the click used to close the panel from being consumed
|
||||
panel.setAttribute("consumeoutsideclicks", options.get("consumeOutsideClick"));
|
||||
panel.setAttribute("noautofocus", options.get("noAutoFocus"));
|
||||
panel.setAttribute("type", "arrow");
|
||||
|
@ -229,10 +229,6 @@ Tooltip.prototype = {
|
|||
return this.panel.state !== "closed" && this.panel.state !== "hiding";
|
||||
},
|
||||
|
||||
setSize: function(width, height) {
|
||||
this.panel.sizeTo(width, height);
|
||||
},
|
||||
|
||||
/**
|
||||
* Empty the tooltip's content
|
||||
*/
|
||||
|
@ -308,8 +304,7 @@ Tooltip.prototype = {
|
|||
* The container for all target nodes
|
||||
* @param {Function} targetNodeCb
|
||||
* A function that accepts a node argument and returns true or false
|
||||
* (or a promise that resolves or rejects) to signify if the tooltip
|
||||
* should be shown on that node or not.
|
||||
* to signify if the tooltip should be shown on that node or not.
|
||||
* Additionally, the function receives a second argument which is the
|
||||
* tooltip instance itself, to be used to add/modify the content of the
|
||||
* tooltip if needed. If omitted, the tooltip will be shown everytime.
|
||||
|
@ -317,7 +312,7 @@ Tooltip.prototype = {
|
|||
* An optional delay that will be observed before showing the tooltip.
|
||||
* Defaults to this.defaultShowDelay.
|
||||
*/
|
||||
startTogglingOnHover: function(baseNode, targetNodeCb, showDelay=this.defaultShowDelay) {
|
||||
startTogglingOnHover: function(baseNode, targetNodeCb, showDelay = this.defaultShowDelay) {
|
||||
if (this._basedNode) {
|
||||
this.stopTogglingOnHover();
|
||||
}
|
||||
|
@ -362,12 +357,7 @@ Tooltip.prototype = {
|
|||
},
|
||||
|
||||
_showOnHover: function(target) {
|
||||
let res = this._targetNodeCb(target, this);
|
||||
if (res && res.then) {
|
||||
res.then(() => {
|
||||
this.show(target);
|
||||
});
|
||||
} else if (res) {
|
||||
if (this._targetNodeCb(target, this)) {
|
||||
this.show(target);
|
||||
}
|
||||
},
|
||||
|
@ -537,8 +527,6 @@ Tooltip.prototype = {
|
|||
let w = options.naturalWidth || imgObj.naturalWidth;
|
||||
let h = options.naturalHeight || imgObj.naturalHeight;
|
||||
label.textContent = w + " x " + h;
|
||||
|
||||
this.setSize(vbox.width, vbox.height);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -595,54 +583,6 @@ Tooltip.prototype = {
|
|||
// Put the iframe in the tooltip
|
||||
this.content = iframe;
|
||||
|
||||
return def.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the content of the tooltip to be the result of CSSTransformPreviewer.
|
||||
* Meaning a canvas previewing a css transformation.
|
||||
*
|
||||
* @param {String} transform
|
||||
* The CSS transform value (e.g. "rotate(45deg) translateX(50px)")
|
||||
* @param {PageStyleActor} pageStyle
|
||||
* An instance of the PageStyleActor that will be used to retrieve
|
||||
* computed styles
|
||||
* @param {NodeActor} node
|
||||
* The NodeActor for the currently selected node
|
||||
* @return A promise that resolves when the tooltip content is ready, or
|
||||
* rejects if no transform is provided or is invalid
|
||||
*/
|
||||
setCssTransformContent: function(transform, pageStyle, node) {
|
||||
let def = promise.defer();
|
||||
|
||||
if (transform) {
|
||||
// Look into the computed styles to find the width and height and possibly
|
||||
// the origin if it hadn't been provided
|
||||
pageStyle.getComputed(node, {
|
||||
filter: "user",
|
||||
markMatched: false,
|
||||
onlyMatched: false
|
||||
}).then(styles => {
|
||||
let origin = styles["transform-origin"].value;
|
||||
let width = parseInt(styles["width"].value);
|
||||
let height = parseInt(styles["height"].value);
|
||||
|
||||
let root = this.doc.createElementNS(XHTML_NS, "div");
|
||||
let previewer = new CSSTransformPreviewer(root);
|
||||
this.content = root;
|
||||
if (!previewer.preview(transform, origin, width, height)) {
|
||||
// If the preview didn't work, reject the promise
|
||||
def.reject();
|
||||
} else {
|
||||
// Else, make sure the tooltip has the right size and resolve
|
||||
this.setSize(previewer.canvas.width, previewer.canvas.height);
|
||||
def.resolve();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
def.reject();
|
||||
}
|
||||
|
||||
return def.promise;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -505,27 +505,21 @@ CssHtmlTree.prototype = {
|
|||
*/
|
||||
_buildTooltipContent: function(target)
|
||||
{
|
||||
// Test for image url
|
||||
if (target.classList.contains("theme-link")) {
|
||||
let propValue = target.parentNode;
|
||||
let propName = propValue.parentNode.querySelector(".property-name");
|
||||
if (propName.textContent === "background-image") {
|
||||
this.tooltip.setCssBackgroundImageContent(propValue.textContent);
|
||||
return true;
|
||||
}
|
||||
// If the hovered element is not a property view and is not a background
|
||||
// image, then don't show a tooltip
|
||||
let isPropertyValue = target.classList.contains("property-value");
|
||||
if (!isPropertyValue) {
|
||||
return false;
|
||||
}
|
||||
let propName = target.parentNode.querySelector(".property-name");
|
||||
let isBackgroundImage = propName.textContent === "background-image";
|
||||
if (!isBackgroundImage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Test for css transform
|
||||
if (target.classList.contains("property-value")) {
|
||||
let def = promise.defer();
|
||||
let propValue = target;
|
||||
let propName = target.parentNode.querySelector(".property-name");
|
||||
if (propName.textContent === "transform") {
|
||||
this.tooltip.setCssTransformContent(propValue.textContent,
|
||||
this.pageStyle, this.viewedElement).then(def.resolve);
|
||||
return def.promise;
|
||||
}
|
||||
}
|
||||
// Fill some content
|
||||
this.tooltip.setCssBackgroundImageContent(target.textContent);
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -1125,32 +1125,27 @@ CssRuleView.prototype = {
|
|||
* prepare some content for the tooltip
|
||||
*/
|
||||
_buildTooltipContent: function(target) {
|
||||
let property = target.textProperty, def = promise.defer(), hasTooltip = false;
|
||||
|
||||
// Test for css transform
|
||||
if (property && property.name === "transform") {
|
||||
this.previewTooltip.setCssTransformContent(property.value, this.pageStyle,
|
||||
this._viewedElement).then(def.resolve);
|
||||
hasTooltip = true;
|
||||
}
|
||||
|
||||
// Test for image
|
||||
let isImageHref = target.classList.contains("theme-link") &&
|
||||
target.parentNode.classList.contains("ruleview-propertyvalue");
|
||||
if (isImageHref) {
|
||||
property = target.parentNode.textProperty;
|
||||
this.previewTooltip.setCssBackgroundImageContent(property.value,
|
||||
property.rule.domRule.href);
|
||||
def.resolve();
|
||||
hasTooltip = true;
|
||||
|
||||
// If the inplace-editor is visible or if this is not a background image
|
||||
// don't show the tooltip
|
||||
if (!isImageHref) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasTooltip) {
|
||||
this.colorPicker.revert();
|
||||
this.colorPicker.hide();
|
||||
}
|
||||
// Retrieve the TextProperty for the hovered element
|
||||
let property = target.parentNode.textProperty;
|
||||
let href = property.rule.domRule.href;
|
||||
|
||||
return def.promise;
|
||||
// Fill some content
|
||||
this.previewTooltip.setCssBackgroundImageContent(property.value, href);
|
||||
|
||||
// Hide the color picker tooltip if shown and revert changes
|
||||
this.colorPicker.revert();
|
||||
this.colorPicker.hide();
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1306,7 +1301,7 @@ CssRuleView.prototype = {
|
|||
/**
|
||||
* Update the highlighted element.
|
||||
*
|
||||
* @param {NodeActor} aElement
|
||||
* @param {nsIDOMElement} aElement
|
||||
* The node whose style rules we'll inspect.
|
||||
*/
|
||||
highlight: function CssRuleView_highlight(aElement)
|
||||
|
|
|
@ -53,6 +53,4 @@ support-files = browser_ruleview_pseudoelement.html
|
|||
[browser_bug913014_matched_expand.js]
|
||||
[browser_bug765105_background_image_tooltip.js]
|
||||
[browser_bug889638_rule_view_color_picker.js]
|
||||
[browser_bug726427_csstransform_tooltip.js]
|
||||
|
||||
[browser_bug940500_rule_view_pick_gradient_color.js]
|
||||
|
|
|
@ -1,224 +0,0 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
let contentDoc;
|
||||
let inspector;
|
||||
let ruleView;
|
||||
let computedView;
|
||||
|
||||
const PAGE_CONTENT = [
|
||||
'<style type="text/css">',
|
||||
' #testElement {',
|
||||
' width: 500px;',
|
||||
' height: 300px;',
|
||||
' background: red;',
|
||||
' transform: skew(16deg);',
|
||||
' }',
|
||||
' .test-element {',
|
||||
' transform-origin: top left;',
|
||||
' transform: rotate(45deg);',
|
||||
' }',
|
||||
' div {',
|
||||
' transform: scaleX(1.5);',
|
||||
' transform-origin: bottom right;',
|
||||
' }',
|
||||
' [attr] {',
|
||||
' }',
|
||||
'</style>',
|
||||
'<div id="testElement" class="test-element" attr="value">transformed element</div>'
|
||||
].join("\n");
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function(evt) {
|
||||
gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
|
||||
contentDoc = content.document;
|
||||
waitForFocus(createDocument, content);
|
||||
}, true);
|
||||
|
||||
content.location = "data:text/html,rule view css transform tooltip test";
|
||||
}
|
||||
|
||||
function createDocument() {
|
||||
contentDoc.body.innerHTML = PAGE_CONTENT;
|
||||
|
||||
openRuleView((aInspector, aRuleView) => {
|
||||
inspector = aInspector;
|
||||
ruleView = aRuleView;
|
||||
startTests();
|
||||
});
|
||||
}
|
||||
|
||||
function startTests() {
|
||||
inspector.selection.setNode(contentDoc.querySelector("#testElement"));
|
||||
inspector.once("inspector-updated", testTransformTooltipOnIDSelector);
|
||||
}
|
||||
|
||||
function endTests() {
|
||||
contentDoc = inspector = ruleView = computedView = null;
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
}
|
||||
|
||||
function testTransformTooltipOnIDSelector() {
|
||||
info("Testing that a transform tooltip appears on the #ID rule");
|
||||
|
||||
let panel = ruleView.previewTooltip.panel;
|
||||
ok(panel, "The XUL panel exists for the rule-view preview tooltips");
|
||||
|
||||
let {valueSpan} = getRuleViewProperty("#testElement", "transform");
|
||||
assertTooltipShownOn(ruleView.previewTooltip, valueSpan, () => {
|
||||
// The transform preview is canvas, so there's not much we can test, so for
|
||||
// now, let's just be happy with the fact that the tooltips is shown!
|
||||
ok(true, "Tooltip shown on the transform property of the #ID rule");
|
||||
ruleView.previewTooltip.hide();
|
||||
executeSoon(testTransformTooltipOnClassSelector);
|
||||
});
|
||||
}
|
||||
|
||||
function testTransformTooltipOnClassSelector() {
|
||||
info("Testing that a transform tooltip appears on the .class rule");
|
||||
|
||||
let {valueSpan} = getRuleViewProperty(".test-element", "transform");
|
||||
assertTooltipShownOn(ruleView.previewTooltip, valueSpan, () => {
|
||||
// The transform preview is canvas, so there's not much we can test, so for
|
||||
// now, let's just be happy with the fact that the tooltips is shown!
|
||||
ok(true, "Tooltip shown on the transform property of the .class rule");
|
||||
ruleView.previewTooltip.hide();
|
||||
executeSoon(testTransformTooltipOnTagSelector);
|
||||
});
|
||||
}
|
||||
|
||||
function testTransformTooltipOnTagSelector() {
|
||||
info("Testing that a transform tooltip appears on the tag rule");
|
||||
|
||||
let {valueSpan} = getRuleViewProperty("div", "transform");
|
||||
assertTooltipShownOn(ruleView.previewTooltip, valueSpan, () => {
|
||||
// The transform preview is canvas, so there's not much we can test, so for
|
||||
// now, let's just be happy with the fact that the tooltips is shown!
|
||||
ok(true, "Tooltip shown on the transform property of the tag rule");
|
||||
ruleView.previewTooltip.hide();
|
||||
executeSoon(testTransformTooltipNotShownOnInvalidTransform);
|
||||
});
|
||||
}
|
||||
|
||||
function testTransformTooltipNotShownOnInvalidTransform() {
|
||||
info("Testing that a transform tooltip does not appear for invalid values");
|
||||
|
||||
// This is the list of keys to type in the inplace-editor
|
||||
let keyData = "transform".split("");
|
||||
keyData.push("VK_TAB");
|
||||
keyData = keyData.concat("muchTransform(suchAngle)".split(""));
|
||||
keyData.push("VK_RETURN");
|
||||
|
||||
// Focus the inplace editor
|
||||
let rule = getRule("[attr]");
|
||||
let brace = rule.querySelector(".ruleview-ruleclose");
|
||||
waitForEditorFocus(brace.parentNode, editor => {
|
||||
// Enter an invalid value
|
||||
typeKeySequence(keyData, () => {
|
||||
let {valueSpan} = getRuleViewProperty("[attr]", "transform");
|
||||
assertTooltipNotShownOn(ruleView.previewTooltip, valueSpan, () => {
|
||||
executeSoon(testTransformTooltipOnComputedView);
|
||||
});
|
||||
});
|
||||
});
|
||||
brace.click();
|
||||
}
|
||||
|
||||
function testTransformTooltipOnComputedView() {
|
||||
info("Testing that a transform tooltip appears in the computed view too");
|
||||
|
||||
inspector.sidebar.select("computedview");
|
||||
computedView = inspector.sidebar.getWindowForTab("computedview").computedview.view;
|
||||
let doc = computedView.styleDocument;
|
||||
|
||||
let panel = computedView.tooltip.panel;
|
||||
let {valueSpan} = getComputedViewProperty("transform");
|
||||
|
||||
assertTooltipShownOn(computedView.tooltip, valueSpan, () => {
|
||||
// The transform preview is canvas, so there's not much we can test, so for
|
||||
// now, let's just be happy with the fact that the tooltips is shown!
|
||||
ok(true, "Tooltip shown on the computed transform property");
|
||||
computedView.tooltip.hide();
|
||||
executeSoon(endTests);
|
||||
});
|
||||
}
|
||||
|
||||
function assertTooltipShownOn(tooltip, element, cb) {
|
||||
// If there is indeed a show-on-hover on element, the xul panel will be shown
|
||||
tooltip.panel.addEventListener("popupshown", function shown() {
|
||||
tooltip.panel.removeEventListener("popupshown", shown, true);
|
||||
cb();
|
||||
}, true);
|
||||
tooltip._showOnHover(element);
|
||||
}
|
||||
|
||||
function assertTooltipNotShownOn(tooltip, element, cb) {
|
||||
// The only way to make sure the tooltip is not shown is try and show it, wait
|
||||
// for a given amount of time, and then check if it's shown or not
|
||||
tooltip._showOnHover(element);
|
||||
setTimeout(() => {
|
||||
ok(!tooltip.isShown(), "The tooltip did not appear on hover of the element");
|
||||
cb();
|
||||
}, tooltip.defaultShowDelay + 100);
|
||||
}
|
||||
|
||||
function typeKeySequence(sequence, cb, index=0) {
|
||||
if (index === sequence.length) {
|
||||
return cb();
|
||||
}
|
||||
|
||||
EventUtils.synthesizeKey(sequence[index], {}, ruleView.doc.defaultView);
|
||||
executeSoon(() => {
|
||||
typeKeySequence(sequence, cb, index + 1);
|
||||
});
|
||||
}
|
||||
|
||||
function getRule(selectorText) {
|
||||
let rule;
|
||||
|
||||
[].forEach.call(ruleView.doc.querySelectorAll(".ruleview-rule"), aRule => {
|
||||
let selector = aRule.querySelector(".ruleview-selector-matched");
|
||||
if (selector && selector.textContent === selectorText) {
|
||||
rule = aRule;
|
||||
}
|
||||
});
|
||||
|
||||
return rule;
|
||||
}
|
||||
|
||||
function getRuleViewProperty(selectorText, propertyName) {
|
||||
let prop;
|
||||
|
||||
let rule = getRule(selectorText);
|
||||
if (rule) {
|
||||
// Look for the propertyName in that rule element
|
||||
[].forEach.call(rule.querySelectorAll(".ruleview-property"), property => {
|
||||
let nameSpan = property.querySelector(".ruleview-propertyname");
|
||||
let valueSpan = property.querySelector(".ruleview-propertyvalue");
|
||||
|
||||
if (nameSpan.textContent === propertyName) {
|
||||
prop = {nameSpan: nameSpan, valueSpan: valueSpan};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return prop;
|
||||
}
|
||||
|
||||
function getComputedViewProperty(name) {
|
||||
let prop;
|
||||
[].forEach.call(computedView.styleDocument.querySelectorAll(".property-view"), property => {
|
||||
let nameSpan = property.querySelector(".property-name");
|
||||
let valueSpan = property.querySelector(".property-value");
|
||||
|
||||
if (nameSpan.textContent === name) {
|
||||
prop = {nameSpan: nameSpan, valueSpan: valueSpan};
|
||||
}
|
||||
});
|
||||
return prop;
|
||||
}
|
|
@ -142,9 +142,8 @@ function testComputedView() {
|
|||
|
||||
let panel = computedView.tooltip.panel;
|
||||
let {valueSpan} = getComputedViewProperty("background-image");
|
||||
let uriSpan = valueSpan.querySelector(".theme-link");
|
||||
|
||||
assertTooltipShownOn(computedView.tooltip, uriSpan, () => {
|
||||
assertTooltipShownOn(computedView.tooltip, valueSpan, () => {
|
||||
let images = panel.getElementsByTagName("image");
|
||||
is(images.length, 1, "Tooltip contains an image");
|
||||
ok(images[0].src === "chrome://global/skin/icons/warning-64.png");
|
||||
|
|
Загрузка…
Ссылка в новой задаче