1 // global async_test, assert_equals
3 // This test generates a couple of scenarios (each a TestData) for
4 // sizing SVG inside an inline <object> and has a simple JavaScript
5 // sizing implementation that handles the generated scenarios. It
6 // generates a DOM corresponding to the scenario and compares the laid
7 // out size to the calculated size.
9 // The tests loops through different combinations of:
11 // * width and height on <object>
13 // * width and height on <svg>
15 // * viewBox on <svg> (gives intrinsic ratio)
17 // * width and height on containing block of <object>
19 // All these contribute to the final size of the SVG in some way.
21 // The test focuses on the size of the CSS box generated by the SVG.
22 // The SVG is always empty by itself so no actual SVG are tested.
24 // Focus is also put on how the different attributes interact, little
25 // focus is put on variations within an attribute that doesn't affect
26 // the relationship to other attributes, i.e only px and % units are
27 // used since that covers the interactions.
29 // To debug a specific test append ?<test-id> to the URL. An <iframe>
30 // is generated with equivalent test and the source of the test is
31 // added to a <pre> element.
33 // Note: placeholder is an alternative name for the tested <object>
34 // element; 'object' becomes such an ambigious name when placed in
38 function parseLength(l) {
39 var match = /^([-+]?[0-9]+|[-+]?[0-9]*\.[0-9]+)(px|%)?$/.exec(l);
42 return new Length(Number(match[1]), match[2] ? match[2] : "px");
45 function parseViewBox(input) {
49 var arr = input.split(' ');
50 return arr.map(function(a) { return parseInt(a); });
53 // Only px and % are used
54 function convertToPx(input, percentRef) {
57 var length = parseLength(input);
58 if (length.amount == 0)
62 if (length.unit == "%" && percentRef === undefined)
64 return length.amount * { px: 1,
65 "%": percentRef/100}[length.unit];
68 function Length(amount, unit) {
73 function describe(data) {
74 function dumpObject(obj) {
76 for (var property in obj) {
77 if (obj.hasOwnProperty(property)) {
78 var value = obj[property];
79 if (typeof value == 'string')
80 value = "'" + value + "'";
81 else if (value == null)
83 else if (typeof value == 'object')
85 if (value instanceof Array)
86 value = "[" + value + "]";
88 value = "{" + dumpObject(value) + "}";
92 r += property + ": " + value + ", ";
97 var result = dumpObject(data);
99 return "(initial values)";
103 function TestData(config) {
104 this.config = config;
105 this.name = describe(config);
107 this.mapPresentationalHintLength("width", config.placeholderWidthAttr);
108 this.mapPresentationalHintLength("height", config.placeholderHeightAttr);
111 TestData.prototype.mapPresentationalHintLength =
112 function(cssProperty, attr) {
114 var l = parseLength(attr);
116 this.style[cssProperty] = l.amount + l.unit;
120 TestData.prototype.computedWidthIsAuto = function() {
121 return !this.style["width"] || this.style["width"] == 'auto';
124 TestData.prototype.computedHeightIsAuto = function() {
125 return !this.style["height"] || this.style["height"] == 'auto' ||
126 (parseLength(this.style["height"]).unit == '%' &&
127 this.containerComputedHeightIsAuto());
130 TestData.prototype.containerComputedWidthIsAuto = function() {
131 return !this.config.containerWidthStyle ||
132 this.config.containerWidthStyle == 'auto';
135 TestData.prototype.containerComputedHeightIsAuto = function() {
136 return !this.config.containerHeightStyle ||
137 this.config.containerHeightStyle == 'auto';
140 TestData.prototype.intrinsicInformation = function() {
141 var w = convertToPx(this.config.svgWidthAttr) || 0;
142 var h = convertToPx(this.config.svgHeightAttr) || 0;
147 var vb = parseViewBox(this.config.svgViewBoxAttr);
158 return { width: w, height: h, ratio: r };
162 TestData.prototype.computeInlineReplacedSize = function() {
163 var intrinsic = this.intrinsicInformation();
166 // http://www.w3.org/TR/CSS2/visudet.html#inline-replaced-height
167 function calculateUsedHeight() {
168 if (self.computedHeightIsAuto()) {
169 if (self.computedWidthIsAuto() && intrinsic.height)
170 return intrinsic.height;
172 return calculateUsedWidth() / intrinsic.ratio;
173 if (intrinsic.height)
174 return intrinsic.height;
178 return convertToPx(self.style["height"],
179 convertToPx(self.config.containerHeightStyle,
183 // http://www.w3.org/TR/CSS2/visudet.html#inline-replaced-width
184 function calculateUsedWidth() {
185 if (self.computedWidthIsAuto()) {
186 if (self.computedHeightIsAuto() && intrinsic.width)
187 return intrinsic.width;
188 if (!self.computedHeightIsAuto() && intrinsic.ratio)
189 return calculateUsedHeight() * intrinsic.ratio;
190 if (self.computedHeightIsAuto() && intrinsic.ratio) {
191 if (self.containerComputedWidthIsAuto()) {
192 // Note: While this is actually undefined in CSS
193 // 2.1, use the suggested value by examining the
195 return self.outerWidth;
197 return convertToPx(self.config.containerWidthStyle,
202 return intrinsic.width;
206 if (self.containerComputedWidthIsAuto())
207 return convertToPx(self.style["width"], self.outerWidth);
209 return convertToPx(self.style["width"],
210 convertToPx(self.config.containerWidthStyle,
213 return { width: calculateUsedWidth(),
214 height: calculateUsedHeight() };
217 var testContainer = document.querySelector('#testContainer');
218 TestData.prototype.outerWidth = testContainer.getBoundingClientRect().width;
219 TestData.prototype.outerHeight = testContainer.getBoundingClientRect().height;
221 window.TestData = TestData;
224 function setupContainer(testData, placeholder, options) {
225 options = options || {};
227 var container = document.createElement("div");
229 container.id = "container";
230 if (testData.config.containerWidthStyle)
231 container.style.width = testData.config.containerWidthStyle;
233 if (testData.config.containerHeightStyle)
234 container.style.height = testData.config.containerHeightStyle;
237 container.appendChild(document.createTextNode("\n\t\t"));
238 container.appendChild(placeholder);
240 container.appendChild(document.createTextNode("\n\t"));
245 function setupPlaceholder(testData, options) {
246 options = options || {};
248 function generateSVGURI(testData, encoder) {
249 var res = '<svg xmlns="http://www.w3.org/2000/svg"';
250 function addAttr(attr, prop) {
251 if (testData.config[prop])
252 res += ' ' + attr + '="' + testData.config[prop] + '"';
254 addAttr("width", "svgWidthAttr");
255 addAttr("height", "svgHeightAttr");
256 addAttr("viewBox", "svgViewBoxAttr");
258 return 'data:image/svg+xml' + encoder(res);
261 var placeholder = document.createElement("object");
263 if (options.pretty) {
264 placeholder.appendChild(document.createTextNode("\n\t\t\t"));
265 placeholder.appendChild(
266 document.createComment(
267 generateSVGURI(testData, function(x) { return "," + x; })));
268 placeholder.appendChild(document.createTextNode("\n\t\t"));
271 placeholder.setAttribute("id", "test");
272 if (testData.config.placeholderWidthAttr)
273 placeholder.setAttribute("width", testData.config.placeholderWidthAttr);
274 if (testData.config.placeholderHeightAttr)
275 placeholder.setAttribute("height", testData.config.placeholderHeightAttr);
276 placeholder.setAttribute("data",
277 generateSVGURI(testData, function(x) {
278 return ";base64," + btoa(x);
283 function buildDemo(testData) {
284 // Non-essential debugging tool
286 var options = { pretty: true };
288 testData.computeInlineReplacedSize();
290 setupContainer(testData, setupPlaceholder(testData, options), options);
292 var root = document.createElement("html");
293 var style = document.createElement("style");
295 style.textContent = "\n" +
296 "\tbody { margin: 0; font-family: sans-serif }\n" +
298 "\t\twidth: " + (expectedRect.width) + "px; height: "
299 + (expectedRect.height) + "px;\n" +
300 "\t\tborder: 10px solid lime; position: absolute;\n" +
301 "\t\tbackground-color: red }\n" +
302 "\t#testContainer { position: absolute;\n" +
303 "\t\ttop: 10px; left: 10px; width: 800px; height: 600px }\n" +
304 "\tobject { background-color: green }\n" +
305 "\t.result { position: absolute; top: 0; right: 0;\n" +
306 "\t\tbackground-color: hsla(0,0%, 0%, 0.85); border-radius: 0.5em;\n" +
307 "\t\tpadding: 0.5em; border: 0.25em solid black }\n" +
308 "\t.pass { color: lime }\n" +
309 "\t.fail { color: red }\n";
311 root.appendChild(document.createTextNode("\n"));
312 root.appendChild(style);
313 root.appendChild(document.createTextNode("\n"));
315 var script = document.createElement("script");
316 script.textContent = "\n" +
317 "onload = function() {\n" +
318 "\tvar objectRect = \n" +
319 "\t\tdocument.querySelector('#test').getBoundingClientRect();\n" +
320 "\tpassed = (objectRect.width == " + expectedRect.width + " && " +
321 "objectRect.height == " + expectedRect.height + ");\n" +
322 "\tdocument.body.insertAdjacentHTML('beforeEnd',\n" +
323 "\t\t'<span class=\"result '+ (passed ? 'pass' : 'fail') " +
324 "+ '\">' + (passed ? 'Pass' : 'Fail') + '</span>');\n" +
327 root.appendChild(script);
328 root.appendChild(document.createTextNode("\n"));
330 var expectedElement = document.createElement("div");
331 expectedElement.id = "expected";
332 root.appendChild(expectedElement);
333 root.appendChild(document.createTextNode("\n"));
335 var testContainer = document.createElement("div");
336 testContainer.id = "testContainer";
337 testContainer.appendChild(document.createTextNode("\n\t"));
338 testContainer.appendChild(container);
339 testContainer.appendChild(document.createTextNode("\n"));
340 root.appendChild(testContainer);
341 root.appendChild(document.createTextNode("\n"));
343 return "<!DOCTYPE html>\n" + root.outerHTML;
346 function doCombinationTest(values, func)
348 // Recursively construct all possible combinations of values and
349 // send them to |func|. Example:
351 // values: [["X", ["a", "b"]],
352 // ["Y", ["c", "d"]]]
354 // generates the objects:
356 // 1: { "X": "a", "Y": "c" }
357 // 2: { "X": "a", "Y": "d" }
358 // 3: { "X": "b", "Y": "c" }
359 // 4: { "X": "b", "Y": "d" }
361 // and each will be sent to |func| with the corresponding prefixed
362 // id (modulo order).
364 var combinationId = 1;
365 function doCombinationTestRecursive(slicedValues, config) {
366 if (slicedValues.length > 0) {
367 var configKey = slicedValues[0][0];
368 slicedValues[0][1].forEach(function(configValue) {
371 new_config[k] = config[k];
372 new_config[configKey] = configValue;
373 doCombinationTestRecursive(slicedValues.slice(1), new_config);
376 func(config, combinationId++);
379 doCombinationTestRecursive(values, {});
382 var debugHint = function(id) { return "(append ?"+id+" to debug) " };
384 if (window.location.search) {
385 testSingleId = window.location.search.substring(1);
386 debugHint = function(id) { return ""; };
389 function testSVGInObjectWithPlaceholderHeightAttr(placeholderHeightAttr) {
390 // Separated over placeholderHeightAttr so that the test count is around ~200
393 [["containerWidthStyle", [null, "400px"]],
394 ["containerHeightStyle", [null, "400px"]],
395 ["placeholderWidthAttr", [null, "100", "50%"]],
396 ["placeholderHeightAttr", [placeholderHeightAttr]],
397 ["svgViewBoxAttr", [ null, "0 0 100 200" ]],
398 ["svgWidthAttr", [ null, "200", "25%" ]],
399 ["svgHeightAttr", [ null, "200", "25%" ]]],
400 function(config, id) {
401 if (!testSingleId || testSingleId == id) {
402 var testData = new TestData(config);
403 var t = async_test(testData.name);
406 testData.computeInlineReplacedSize();
407 var placeholder = setupPlaceholder(testData);
409 setupContainer(testData, placeholder);
411 var checkSize = function() {
412 var placeholderRect =
413 placeholder.getBoundingClientRect();
416 assert_equals(placeholderRect.width,
418 debugHint(id) + "Wrong width");
419 assert_equals(placeholderRect.height,
421 debugHint(id) + "Wrong height");
423 testContainer.removeChild(container);
425 document.body.removeChild(testContainer);
431 placeholder.addEventListener('load', function() {
432 // setTimeout is a work-around to let engines
433 // finish layout of child browsing contexts even
434 // after the load event
435 setTimeout(t.step_func(checkSize), 0);
437 testContainer.appendChild(container);
441 if (testSingleId == id) {
442 var pad = function(n, width, z) {
445 return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
448 var demo = buildDemo(testData);
449 var iframe = document.createElement('iframe');
450 iframe.style.width = (Math.max(900, expectedRect.width)) + "px";
451 iframe.style.height = (Math.max(400, expectedRect.height)) + "px";
452 iframe.src = "data:text/html;charset=utf-8," + encodeURIComponent(demo);
453 document.body.appendChild(iframe);
455 document.body.insertAdjacentHTML(
457 '<p><a href="data:application/octet-stream;charset=utf-8;base64,' +
458 btoa(demo) + '" download="svg-in-object-test-' + pad(id, 3) + '.html">Download</a></p>');