Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / LayoutTests / svg / as-object / sizing / svg-in-object.js
blobb704e5e27afcd619d455a2dfb7ec0be9d42fa86f
1 // global async_test, assert_equals
2 //
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.
8 //
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
35 // code.
37 (function() {
38     function parseLength(l) {
39         var match = /^([-+]?[0-9]+|[-+]?[0-9]*\.[0-9]+)(px|%)?$/.exec(l);
40         if (!match)
41             return null;
42         return new Length(Number(match[1]), match[2] ? match[2] : "px");
43     }
45     function parseViewBox(input) {
46         if (!input)
47             return null;
49         var arr = input.split(' ');
50         return arr.map(function(a) { return parseInt(a); });
51     }
53     // Only px and % are used
54     function convertToPx(input, percentRef) {
55         if (input == null)
56             return null;
57         var length = parseLength(input);
58         if (length.amount == 0)
59             return 0;
60         if (!length.unit)
61             length.unit = "px";
62         if (length.unit == "%" && percentRef === undefined)
63             return null;
64         return length.amount * { px: 1,
65                                  "%": percentRef/100}[length.unit];
66     }
68     function Length(amount, unit) {
69         this.amount = amount;
70         this.unit = unit;
71     }
73     function describe(data) {
74         function dumpObject(obj) {
75             var r = "";
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)
82                         value = "null";
83                     else if (typeof value == 'object')
84                     {
85                         if (value instanceof Array)
86                             value = "[" + value + "]";
87                         else
88                             value = "{" + dumpObject(value) + "}";
89                     }
91                     if (value != "null")
92                         r += property + ": " + value + ", ";
93                 }
94             }
95             return r;
96         }
97         var result = dumpObject(data);
98         if (result == "")
99             return "(initial values)";
100         return result;
101     }
103     function TestData(config) {
104         this.config = config;
105         this.name = describe(config);
106         this.style = {};
107         this.mapPresentationalHintLength("width", config.placeholderWidthAttr);
108         this.mapPresentationalHintLength("height", config.placeholderHeightAttr);
109     }
111     TestData.prototype.mapPresentationalHintLength =
112         function(cssProperty, attr) {
113             if (attr) {
114                 var l = parseLength(attr);
115                 if (l)
116                     this.style[cssProperty] = l.amount + l.unit;
117             }
118         };
120     TestData.prototype.computedWidthIsAuto = function() {
121         return !this.style["width"] || this.style["width"] == 'auto';
122     };
124     TestData.prototype.computedHeightIsAuto = function() {
125         return !this.style["height"] || this.style["height"] == 'auto' ||
126             (parseLength(this.style["height"]).unit == '%' &&
127              this.containerComputedHeightIsAuto());
128     };
130     TestData.prototype.containerComputedWidthIsAuto = function() {
131         return !this.config.containerWidthStyle ||
132             this.config.containerWidthStyle == 'auto';
133     };
135     TestData.prototype.containerComputedHeightIsAuto = function() {
136         return !this.config.containerHeightStyle ||
137             this.config.containerHeightStyle == 'auto';
138     };
140     TestData.prototype.intrinsicInformation = function() {
141         var w = convertToPx(this.config.svgWidthAttr) || 0;
142         var h = convertToPx(this.config.svgHeightAttr) || 0;
143         var r = 0;
144         if (w && h) {
145             r =  w / h;
146         } else {
147             var vb = parseViewBox(this.config.svgViewBoxAttr);
148             if (vb) {
149                 r = vb[2] / vb[3];
150             }
151             if (r) {
152                 if (!w && h)
153                     w = h * r;
154                 else if (!h && w)
155                     h = w / r;
156             }
157         }
158         return { width: w, height: h, ratio: r };
159     };
162     TestData.prototype.computeInlineReplacedSize = function() {
163         var intrinsic = this.intrinsicInformation();
164         var self = this;
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;
171                 if (intrinsic.ratio)
172                     return calculateUsedWidth() / intrinsic.ratio;
173                 if (intrinsic.height)
174                     return intrinsic.height;
175                 return 150;
176             }
178             return convertToPx(self.style["height"],
179                                convertToPx(self.config.containerHeightStyle,
180                                            self.outerHeight));
181         }
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
194                         // ancestor widths.
195                         return self.outerWidth;
196                     } else {
197                         return convertToPx(self.config.containerWidthStyle,
198                                            self.outerWidth);
199                     }
200                 }
201                 if (intrinsic.width)
202                     return intrinsic.width;
203                 return 300;
204             }
206             if (self.containerComputedWidthIsAuto())
207                 return convertToPx(self.style["width"], self.outerWidth);
208             else
209                 return convertToPx(self.style["width"],
210                                    convertToPx(self.config.containerWidthStyle,
211                                                self.outerWidth));
212         }
213         return { width: calculateUsedWidth(),
214                  height: calculateUsedHeight() };
215     };
217     var testContainer = document.querySelector('#testContainer');
218     TestData.prototype.outerWidth = testContainer.getBoundingClientRect().width;
219     TestData.prototype.outerHeight = testContainer.getBoundingClientRect().height;
221     window.TestData = TestData;
222 })();
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;
236     if (options.pretty)
237         container.appendChild(document.createTextNode("\n\t\t"));
238     container.appendChild(placeholder);
239     if (options.pretty)
240         container.appendChild(document.createTextNode("\n\t"));
242     return container;
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] + '"';
253         }
254         addAttr("width", "svgWidthAttr");
255         addAttr("height", "svgHeightAttr");
256         addAttr("viewBox", "svgViewBoxAttr");
257         res += '></svg>';
258         return 'data:image/svg+xml' + encoder(res);
259     }
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"));
269     }
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);
279                              }));
280     return placeholder;
283 function buildDemo(testData) {
284     // Non-essential debugging tool
286     var options = { pretty: true };
287     var expectedRect =
288             testData.computeInlineReplacedSize();
289     var container =
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" +
297         "\t#expected {\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" +
325         "};\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:
350     //
351     // values: [["X", ["a", "b"]],
352     //          ["Y", ["c", "d"]]]
353     //
354     // generates the objects:
355     //
356     // 1: { "X": "a", "Y": "c" }
357     // 2: { "X": "a", "Y": "d" }
358     // 3: { "X": "b", "Y": "c" }
359     // 4: { "X": "b", "Y": "d" }
360     //
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) {
369                 var new_config = {};
370                 for (k in config)
371                     new_config[k] = config[k];
372                 new_config[configKey] = configValue;
373                 doCombinationTestRecursive(slicedValues.slice(1), new_config);
374             });
375         } else {
376             func(config, combinationId++);
377         }
378     }
379     doCombinationTestRecursive(values, {});
382 var debugHint = function(id) { return "(append ?"+id+" to debug) " };
383 var testSingleId;
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
392     doCombinationTest(
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);
405                 var expectedRect =
406                         testData.computeInlineReplacedSize();
407                 var placeholder = setupPlaceholder(testData);
408                 var container =
409                         setupContainer(testData, placeholder);
411                 var checkSize = function() {
412                     var placeholderRect =
413                             placeholder.getBoundingClientRect();
415                     try {
416                         assert_equals(placeholderRect.width,
417                                       expectedRect.width,
418                                       debugHint(id) + "Wrong width");
419                         assert_equals(placeholderRect.height,
420                                       expectedRect.height,
421                                       debugHint(id) + "Wrong height");
422                     } finally {
423                         testContainer.removeChild(container);
424                         if (testSingleId)
425                             document.body.removeChild(testContainer);
426                     }
427                     t.done();
428                 };
430                 t.step(function() {
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);
436                     });
437                     testContainer.appendChild(container);
438                 });
439             }
441             if (testSingleId == id) {
442                 var pad = function(n, width, z) {
443                     z = z || '0';
444                     n = n + '';
445                     return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
446                 };
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(
456                     'beforeEnd',
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>');
459             }
460         });