Chromium Code Reviews| Index: LayoutTests/svg/in-html/sizing/svg-inline.js |
| diff --git a/LayoutTests/svg/in-html/sizing/svg-inline.js b/LayoutTests/svg/in-html/sizing/svg-inline.js |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..1c77373216b29ba82139104759895d9a0fdbf853 |
| --- /dev/null |
| +++ b/LayoutTests/svg/in-html/sizing/svg-inline.js |
| @@ -0,0 +1,428 @@ |
| +// global async_test, assert_equals |
| +// |
| +// This test generates a couple of scenarios (each a TestData) for |
| +// sizing inline <svg> 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 <svg>, both through style and attributes |
| +// |
| +// * 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. |
| + |
| +(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 = {}; |
| + if (config.svgWidthStyle) |
| + this.style["width"] = config.svgWidthStyle; |
| + else |
| + this.mapPresentationalHintLength("width", config.svgWidthAttr); |
| + |
| + if (config.svgHeightStyle) |
| + this.style["height"] = config.svgHeightStyle; |
| + else |
| + this.mapPresentationalHintLength("height", config.svgHeightAttr); |
| + } |
| + |
| + 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, svgElement, 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(svgElement); |
| + if (options.pretty) |
| + container.appendChild(document.createTextNode("\n\t")); |
| + |
| + return container; |
| +} |
| + |
| +function setupSVGElement(testData) { |
| + var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg"); |
| + svgElement.setAttribute("id", "test"); |
| + if (testData.config.svgWidthStyle) |
| + svgElement.style.width = testData.config.svgWidthStyle; |
| + if (testData.config.svgHeightStyle) |
| + svgElement.style.height = testData.config.svgHeightStyle; |
| + if (testData.config.svgWidthAttr) |
| + svgElement.setAttribute("width", testData.config.svgWidthAttr); |
| + if (testData.config.svgHeightAttr) |
| + svgElement.setAttribute("height", testData.config.svgHeightAttr); |
| + if (testData.config.svgViewBoxAttr) |
| + svgElement.setAttribute("viewBox", testData.config.svgViewBoxAttr); |
| + |
| + return svgElement; |
| +} |
| + |
| +function buildDemo(testData) { |
| + // Non-essential debugging tool |
| + |
| + var options = { pretty: true }; |
| + var expectedRect = |
| + testData.computeInlineReplacedSize(); |
| + var container = |
| + setupContainer(testData, setupSVGElement(testData), 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" + |
| + "\tsvg { 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 svgRect = \n" + |
| + "\t\tdocument.querySelector('#test').getBoundingClientRect();\n" + |
| + "\tpassed = (svgRect.width == " + expectedRect.width + " && " + |
| + "svgRect.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 ""; }; |
| +} |
| + |
| +doCombinationTest( |
|
Erik Dahlström (inactive)
2014/04/15 09:37:43
shouldn't there be tests for explicit 'auto' and '
davve
2014/04/15 15:55:23
For 'auto', I'd rather only add combinations that
|
| + [["containerWidthStyle", [null, "400px"]], |
| + ["containerHeightStyle", [null, "400px"]], |
| + ["svgViewBoxAttr", [ null, "0 0 100 200" ]], |
| + ["svgWidthStyle", [ null, "100px", "50%" ]], |
| + ["svgHeightStyle", [ null, "100px", "50%" ]], |
| + ["svgWidthAttr", [ null, "200", "25%" ]], |
| + ["svgHeightAttr", [ null, "200", "25%" ]]], |
| + function(config, id) { |
| + if (!testSingleId || testSingleId == id) { |
| + var testData = new TestData(config); |
| + |
| + var expectedRect = |
| + testData.computeInlineReplacedSize(); |
| + var svgElement = setupSVGElement(testData); |
| + var container = |
| + setupContainer(testData, svgElement); |
| + |
| + var checkSize = function() { |
| + var svgRect = |
| + svgElement.getBoundingClientRect(); |
| + |
| + try { |
| + assert_equals(svgRect.width, |
| + expectedRect.width, |
| + debugHint(id) + "Wrong width"); |
| + assert_equals(svgRect.height, |
| + expectedRect.height, |
| + debugHint(id) + "Wrong height"); |
| + } finally { |
| + testContainer.removeChild(container); |
| + if (testSingleId) |
| + document.body.removeChild(testContainer); |
| + } |
| + }; |
| + |
| + testContainer.appendChild(container); |
| + test(checkSize, testData.name); |
| + } |
| + |
| + 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>'); |
| + } |
| + }); |