| Index: LayoutTests/svg/as-object/sizing/svg-in-object.js
|
| diff --git a/LayoutTests/svg/as-object/sizing/svg-in-object.js b/LayoutTests/svg/as-object/sizing/svg-in-object.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..b704e5e27afcd619d455a2dfb7ec0be9d42fa86f
|
| --- /dev/null
|
| +++ b/LayoutTests/svg/as-object/sizing/svg-in-object.js
|
| @@ -0,0 +1,461 @@
|
| +// global async_test, assert_equals
|
| +//
|
| +// This test generates a couple of scenarios (each a TestData) for
|
| +// sizing SVG inside an inline <object> and has a simple JavaScript
|
| +// sizing implementation that handles the generated scenarios. It
|
| +// generates a DOM corresponding to the scenario and compares the laid
|
| +// out size to the calculated size.
|
| +//
|
| +// The tests loops through different combinations of:
|
| +//
|
| +// * width and height on <object>
|
| +//
|
| +// * width and height on <svg>
|
| +//
|
| +// * viewBox on <svg> (gives intrinsic ratio)
|
| +//
|
| +// * width and height on containing block of <object>
|
| +//
|
| +// All these contribute to the final size of the SVG in some way.
|
| +//
|
| +// The test focuses on the size of the CSS box generated by the SVG.
|
| +// The SVG is always empty by itself so no actual SVG are tested.
|
| +//
|
| +// Focus is also put on how the different attributes interact, little
|
| +// focus is put on variations within an attribute that doesn't affect
|
| +// the relationship to other attributes, i.e only px and % units are
|
| +// used since that covers the interactions.
|
| +//
|
| +// To debug a specific test append ?<test-id> to the URL. An <iframe>
|
| +// is generated with equivalent test and the source of the test is
|
| +// added to a <pre> element.
|
| +//
|
| +// Note: placeholder is an alternative name for the tested <object>
|
| +// element; 'object' becomes such an ambigious name when placed in
|
| +// code.
|
| +
|
| +(function() {
|
| + function parseLength(l) {
|
| + var match = /^([-+]?[0-9]+|[-+]?[0-9]*\.[0-9]+)(px|%)?$/.exec(l);
|
| + if (!match)
|
| + return null;
|
| + return new Length(Number(match[1]), match[2] ? match[2] : "px");
|
| + }
|
| +
|
| + function parseViewBox(input) {
|
| + if (!input)
|
| + return null;
|
| +
|
| + var arr = input.split(' ');
|
| + return arr.map(function(a) { return parseInt(a); });
|
| + }
|
| +
|
| + // Only px and % are used
|
| + function convertToPx(input, percentRef) {
|
| + if (input == null)
|
| + return null;
|
| + var length = parseLength(input);
|
| + if (length.amount == 0)
|
| + return 0;
|
| + if (!length.unit)
|
| + length.unit = "px";
|
| + if (length.unit == "%" && percentRef === undefined)
|
| + return null;
|
| + return length.amount * { px: 1,
|
| + "%": percentRef/100}[length.unit];
|
| + }
|
| +
|
| + function Length(amount, unit) {
|
| + this.amount = amount;
|
| + this.unit = unit;
|
| + }
|
| +
|
| + function describe(data) {
|
| + function dumpObject(obj) {
|
| + var r = "";
|
| + for (var property in obj) {
|
| + if (obj.hasOwnProperty(property)) {
|
| + var value = obj[property];
|
| + if (typeof value == 'string')
|
| + value = "'" + value + "'";
|
| + else if (value == null)
|
| + value = "null";
|
| + else if (typeof value == 'object')
|
| + {
|
| + if (value instanceof Array)
|
| + value = "[" + value + "]";
|
| + else
|
| + value = "{" + dumpObject(value) + "}";
|
| + }
|
| +
|
| + if (value != "null")
|
| + r += property + ": " + value + ", ";
|
| + }
|
| + }
|
| + return r;
|
| + }
|
| + var result = dumpObject(data);
|
| + if (result == "")
|
| + return "(initial values)";
|
| + return result;
|
| + }
|
| +
|
| + function TestData(config) {
|
| + this.config = config;
|
| + this.name = describe(config);
|
| + this.style = {};
|
| + this.mapPresentationalHintLength("width", config.placeholderWidthAttr);
|
| + this.mapPresentationalHintLength("height", config.placeholderHeightAttr);
|
| + }
|
| +
|
| + TestData.prototype.mapPresentationalHintLength =
|
| + function(cssProperty, attr) {
|
| + if (attr) {
|
| + var l = parseLength(attr);
|
| + if (l)
|
| + this.style[cssProperty] = l.amount + l.unit;
|
| + }
|
| + };
|
| +
|
| + TestData.prototype.computedWidthIsAuto = function() {
|
| + return !this.style["width"] || this.style["width"] == 'auto';
|
| + };
|
| +
|
| + TestData.prototype.computedHeightIsAuto = function() {
|
| + return !this.style["height"] || this.style["height"] == 'auto' ||
|
| + (parseLength(this.style["height"]).unit == '%' &&
|
| + this.containerComputedHeightIsAuto());
|
| + };
|
| +
|
| + TestData.prototype.containerComputedWidthIsAuto = function() {
|
| + return !this.config.containerWidthStyle ||
|
| + this.config.containerWidthStyle == 'auto';
|
| + };
|
| +
|
| + TestData.prototype.containerComputedHeightIsAuto = function() {
|
| + return !this.config.containerHeightStyle ||
|
| + this.config.containerHeightStyle == 'auto';
|
| + };
|
| +
|
| + TestData.prototype.intrinsicInformation = function() {
|
| + var w = convertToPx(this.config.svgWidthAttr) || 0;
|
| + var h = convertToPx(this.config.svgHeightAttr) || 0;
|
| + var r = 0;
|
| + if (w && h) {
|
| + r = w / h;
|
| + } else {
|
| + var vb = parseViewBox(this.config.svgViewBoxAttr);
|
| + if (vb) {
|
| + r = vb[2] / vb[3];
|
| + }
|
| + if (r) {
|
| + if (!w && h)
|
| + w = h * r;
|
| + else if (!h && w)
|
| + h = w / r;
|
| + }
|
| + }
|
| + return { width: w, height: h, ratio: r };
|
| + };
|
| +
|
| +
|
| + TestData.prototype.computeInlineReplacedSize = function() {
|
| + var intrinsic = this.intrinsicInformation();
|
| + var self = this;
|
| +
|
| + // http://www.w3.org/TR/CSS2/visudet.html#inline-replaced-height
|
| + function calculateUsedHeight() {
|
| + if (self.computedHeightIsAuto()) {
|
| + if (self.computedWidthIsAuto() && intrinsic.height)
|
| + return intrinsic.height;
|
| + if (intrinsic.ratio)
|
| + return calculateUsedWidth() / intrinsic.ratio;
|
| + if (intrinsic.height)
|
| + return intrinsic.height;
|
| + return 150;
|
| + }
|
| +
|
| + return convertToPx(self.style["height"],
|
| + convertToPx(self.config.containerHeightStyle,
|
| + self.outerHeight));
|
| + }
|
| +
|
| + // http://www.w3.org/TR/CSS2/visudet.html#inline-replaced-width
|
| + function calculateUsedWidth() {
|
| + if (self.computedWidthIsAuto()) {
|
| + if (self.computedHeightIsAuto() && intrinsic.width)
|
| + return intrinsic.width;
|
| + if (!self.computedHeightIsAuto() && intrinsic.ratio)
|
| + return calculateUsedHeight() * intrinsic.ratio;
|
| + if (self.computedHeightIsAuto() && intrinsic.ratio) {
|
| + if (self.containerComputedWidthIsAuto()) {
|
| + // Note: While this is actually undefined in CSS
|
| + // 2.1, use the suggested value by examining the
|
| + // ancestor widths.
|
| + return self.outerWidth;
|
| + } else {
|
| + return convertToPx(self.config.containerWidthStyle,
|
| + self.outerWidth);
|
| + }
|
| + }
|
| + if (intrinsic.width)
|
| + return intrinsic.width;
|
| + return 300;
|
| + }
|
| +
|
| + if (self.containerComputedWidthIsAuto())
|
| + return convertToPx(self.style["width"], self.outerWidth);
|
| + else
|
| + return convertToPx(self.style["width"],
|
| + convertToPx(self.config.containerWidthStyle,
|
| + self.outerWidth));
|
| + }
|
| + return { width: calculateUsedWidth(),
|
| + height: calculateUsedHeight() };
|
| + };
|
| +
|
| + var testContainer = document.querySelector('#testContainer');
|
| + TestData.prototype.outerWidth = testContainer.getBoundingClientRect().width;
|
| + TestData.prototype.outerHeight = testContainer.getBoundingClientRect().height;
|
| +
|
| + window.TestData = TestData;
|
| +})();
|
| +
|
| +function setupContainer(testData, placeholder, options) {
|
| + options = options || {};
|
| +
|
| + var container = document.createElement("div");
|
| +
|
| + container.id = "container";
|
| + if (testData.config.containerWidthStyle)
|
| + container.style.width = testData.config.containerWidthStyle;
|
| +
|
| + if (testData.config.containerHeightStyle)
|
| + container.style.height = testData.config.containerHeightStyle;
|
| +
|
| + if (options.pretty)
|
| + container.appendChild(document.createTextNode("\n\t\t"));
|
| + container.appendChild(placeholder);
|
| + if (options.pretty)
|
| + container.appendChild(document.createTextNode("\n\t"));
|
| +
|
| + return container;
|
| +}
|
| +
|
| +function setupPlaceholder(testData, options) {
|
| + options = options || {};
|
| +
|
| + function generateSVGURI(testData, encoder) {
|
| + var res = '<svg xmlns="http://www.w3.org/2000/svg"';
|
| + function addAttr(attr, prop) {
|
| + if (testData.config[prop])
|
| + res += ' ' + attr + '="' + testData.config[prop] + '"';
|
| + }
|
| + addAttr("width", "svgWidthAttr");
|
| + addAttr("height", "svgHeightAttr");
|
| + addAttr("viewBox", "svgViewBoxAttr");
|
| + res += '></svg>';
|
| + return 'data:image/svg+xml' + encoder(res);
|
| + }
|
| +
|
| + var placeholder = document.createElement("object");
|
| +
|
| + if (options.pretty) {
|
| + placeholder.appendChild(document.createTextNode("\n\t\t\t"));
|
| + placeholder.appendChild(
|
| + document.createComment(
|
| + generateSVGURI(testData, function(x) { return "," + x; })));
|
| + placeholder.appendChild(document.createTextNode("\n\t\t"));
|
| + }
|
| +
|
| + placeholder.setAttribute("id", "test");
|
| + if (testData.config.placeholderWidthAttr)
|
| + placeholder.setAttribute("width", testData.config.placeholderWidthAttr);
|
| + if (testData.config.placeholderHeightAttr)
|
| + placeholder.setAttribute("height", testData.config.placeholderHeightAttr);
|
| + placeholder.setAttribute("data",
|
| + generateSVGURI(testData, function(x) {
|
| + return ";base64," + btoa(x);
|
| + }));
|
| + return placeholder;
|
| +}
|
| +
|
| +function buildDemo(testData) {
|
| + // Non-essential debugging tool
|
| +
|
| + var options = { pretty: true };
|
| + var expectedRect =
|
| + testData.computeInlineReplacedSize();
|
| + var container =
|
| + setupContainer(testData, setupPlaceholder(testData, options), options);
|
| +
|
| + var root = document.createElement("html");
|
| + var style = document.createElement("style");
|
| +
|
| + style.textContent = "\n" +
|
| + "\tbody { margin: 0; font-family: sans-serif }\n" +
|
| + "\t#expected {\n" +
|
| + "\t\twidth: " + (expectedRect.width) + "px; height: "
|
| + + (expectedRect.height) + "px;\n" +
|
| + "\t\tborder: 10px solid lime; position: absolute;\n" +
|
| + "\t\tbackground-color: red }\n" +
|
| + "\t#testContainer { position: absolute;\n" +
|
| + "\t\ttop: 10px; left: 10px; width: 800px; height: 600px }\n" +
|
| + "\tobject { background-color: green }\n" +
|
| + "\t.result { position: absolute; top: 0; right: 0;\n" +
|
| + "\t\tbackground-color: hsla(0,0%, 0%, 0.85); border-radius: 0.5em;\n" +
|
| + "\t\tpadding: 0.5em; border: 0.25em solid black }\n" +
|
| + "\t.pass { color: lime }\n" +
|
| + "\t.fail { color: red }\n";
|
| +
|
| + root.appendChild(document.createTextNode("\n"));
|
| + root.appendChild(style);
|
| + root.appendChild(document.createTextNode("\n"));
|
| +
|
| + var script = document.createElement("script");
|
| + script.textContent = "\n" +
|
| + "onload = function() {\n" +
|
| + "\tvar objectRect = \n" +
|
| + "\t\tdocument.querySelector('#test').getBoundingClientRect();\n" +
|
| + "\tpassed = (objectRect.width == " + expectedRect.width + " && " +
|
| + "objectRect.height == " + expectedRect.height + ");\n" +
|
| + "\tdocument.body.insertAdjacentHTML('beforeEnd',\n" +
|
| + "\t\t'<span class=\"result '+ (passed ? 'pass' : 'fail') " +
|
| + "+ '\">' + (passed ? 'Pass' : 'Fail') + '</span>');\n" +
|
| + "};\n";
|
| +
|
| + root.appendChild(script);
|
| + root.appendChild(document.createTextNode("\n"));
|
| +
|
| + var expectedElement = document.createElement("div");
|
| + expectedElement.id = "expected";
|
| + root.appendChild(expectedElement);
|
| + root.appendChild(document.createTextNode("\n"));
|
| +
|
| + var testContainer = document.createElement("div");
|
| + testContainer.id = "testContainer";
|
| + testContainer.appendChild(document.createTextNode("\n\t"));
|
| + testContainer.appendChild(container);
|
| + testContainer.appendChild(document.createTextNode("\n"));
|
| + root.appendChild(testContainer);
|
| + root.appendChild(document.createTextNode("\n"));
|
| +
|
| + return "<!DOCTYPE html>\n" + root.outerHTML;
|
| +}
|
| +
|
| +function doCombinationTest(values, func)
|
| +{
|
| + // Recursively construct all possible combinations of values and
|
| + // send them to |func|. Example:
|
| + //
|
| + // values: [["X", ["a", "b"]],
|
| + // ["Y", ["c", "d"]]]
|
| + //
|
| + // generates the objects:
|
| + //
|
| + // 1: { "X": "a", "Y": "c" }
|
| + // 2: { "X": "a", "Y": "d" }
|
| + // 3: { "X": "b", "Y": "c" }
|
| + // 4: { "X": "b", "Y": "d" }
|
| + //
|
| + // and each will be sent to |func| with the corresponding prefixed
|
| + // id (modulo order).
|
| +
|
| + var combinationId = 1;
|
| + function doCombinationTestRecursive(slicedValues, config) {
|
| + if (slicedValues.length > 0) {
|
| + var configKey = slicedValues[0][0];
|
| + slicedValues[0][1].forEach(function(configValue) {
|
| + var new_config = {};
|
| + for (k in config)
|
| + new_config[k] = config[k];
|
| + new_config[configKey] = configValue;
|
| + doCombinationTestRecursive(slicedValues.slice(1), new_config);
|
| + });
|
| + } else {
|
| + func(config, combinationId++);
|
| + }
|
| + }
|
| + doCombinationTestRecursive(values, {});
|
| +}
|
| +
|
| +var debugHint = function(id) { return "(append ?"+id+" to debug) " };
|
| +var testSingleId;
|
| +if (window.location.search) {
|
| + testSingleId = window.location.search.substring(1);
|
| + debugHint = function(id) { return ""; };
|
| +}
|
| +
|
| +function testSVGInObjectWithPlaceholderHeightAttr(placeholderHeightAttr) {
|
| + // Separated over placeholderHeightAttr so that the test count is around ~200
|
| +
|
| + doCombinationTest(
|
| + [["containerWidthStyle", [null, "400px"]],
|
| + ["containerHeightStyle", [null, "400px"]],
|
| + ["placeholderWidthAttr", [null, "100", "50%"]],
|
| + ["placeholderHeightAttr", [placeholderHeightAttr]],
|
| + ["svgViewBoxAttr", [ null, "0 0 100 200" ]],
|
| + ["svgWidthAttr", [ null, "200", "25%" ]],
|
| + ["svgHeightAttr", [ null, "200", "25%" ]]],
|
| + function(config, id) {
|
| + if (!testSingleId || testSingleId == id) {
|
| + var testData = new TestData(config);
|
| + var t = async_test(testData.name);
|
| +
|
| + var expectedRect =
|
| + testData.computeInlineReplacedSize();
|
| + var placeholder = setupPlaceholder(testData);
|
| + var container =
|
| + setupContainer(testData, placeholder);
|
| +
|
| + var checkSize = function() {
|
| + var placeholderRect =
|
| + placeholder.getBoundingClientRect();
|
| +
|
| + try {
|
| + assert_equals(placeholderRect.width,
|
| + expectedRect.width,
|
| + debugHint(id) + "Wrong width");
|
| + assert_equals(placeholderRect.height,
|
| + expectedRect.height,
|
| + debugHint(id) + "Wrong height");
|
| + } finally {
|
| + testContainer.removeChild(container);
|
| + if (testSingleId)
|
| + document.body.removeChild(testContainer);
|
| + }
|
| + t.done();
|
| + };
|
| +
|
| + t.step(function() {
|
| + placeholder.addEventListener('load', function() {
|
| + // setTimeout is a work-around to let engines
|
| + // finish layout of child browsing contexts even
|
| + // after the load event
|
| + setTimeout(t.step_func(checkSize), 0);
|
| + });
|
| + testContainer.appendChild(container);
|
| + });
|
| + }
|
| +
|
| + if (testSingleId == id) {
|
| + var pad = function(n, width, z) {
|
| + z = z || '0';
|
| + n = n + '';
|
| + return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
|
| + };
|
| +
|
| + var demo = buildDemo(testData);
|
| + var iframe = document.createElement('iframe');
|
| + iframe.style.width = (Math.max(900, expectedRect.width)) + "px";
|
| + iframe.style.height = (Math.max(400, expectedRect.height)) + "px";
|
| + iframe.src = "data:text/html;charset=utf-8," + encodeURIComponent(demo);
|
| + document.body.appendChild(iframe);
|
| +
|
| + document.body.insertAdjacentHTML(
|
| + 'beforeEnd',
|
| + '<p><a href="data:application/octet-stream;charset=utf-8;base64,' +
|
| + btoa(demo) + '" download="svg-in-object-test-' + pad(id, 3) + '.html">Download</a></p>');
|
| + }
|
| + });
|
| +}
|
|
|