Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / LayoutTests / svg / in-html / sizing / svg-inline.js
blob1c77373216b29ba82139104759895d9a0fdbf853
1 // global async_test, assert_equals
2 //
3 // This test generates a couple of scenarios (each a TestData) for
4 // sizing inline <svg> and has a simple JavaScript sizing
5 // implementation that handles the generated scenarios. It generates a
6 // DOM corresponding to the scenario and compares the laid out size to
7 // the calculated size.
8 //
9 // The tests loops through different combinations of:
11 // * width and height on <svg>, both through style and attributes
13 // * viewBox on <svg> (gives intrinsic ratio)
15 // * width and height on containing block of <object>
17 // All these contribute to the final size of the SVG in some way.
19 // The test focuses on the size of the CSS box generated by the SVG.
20 // The SVG is always empty by itself so no actual SVG are tested.
22 // Focus is also put on how the different attributes interact, little
23 // focus is put on variations within an attribute that doesn't affect
24 // the relationship to other attributes, i.e only px and % units are
25 // used since that covers the interactions.
27 // To debug a specific test append ?<test-id> to the URL. An <iframe>
28 // is generated with equivalent test and the source of the test is
29 // added to a <pre> element.
31 (function() {
32 function parseLength(l) {
33 var match = /^([-+]?[0-9]+|[-+]?[0-9]*\.[0-9]+)(px|%)?$/.exec(l);
34 if (!match)
35 return null;
36 return new Length(Number(match[1]), match[2] ? match[2] : "px");
39 function parseViewBox(input) {
40 if (!input)
41 return null;
43 var arr = input.split(' ');
44 return arr.map(function(a) { return parseInt(a); });
47 // Only px and % are used
48 function convertToPx(input, percentRef) {
49 if (input == null)
50 return null;
51 var length = parseLength(input);
52 if (length.amount == 0)
53 return 0;
54 if (!length.unit)
55 length.unit = "px";
56 if (length.unit == "%" && percentRef === undefined)
57 return null;
58 return length.amount * { px: 1,
59 "%": percentRef/100}[length.unit];
62 function Length(amount, unit) {
63 this.amount = amount;
64 this.unit = unit;
67 function describe(data) {
68 function dumpObject(obj) {
69 var r = "";
70 for (var property in obj) {
71 if (obj.hasOwnProperty(property)) {
72 var value = obj[property];
73 if (typeof value == 'string')
74 value = "'" + value + "'";
75 else if (value == null)
76 value = "null";
77 else if (typeof value == 'object')
79 if (value instanceof Array)
80 value = "[" + value + "]";
81 else
82 value = "{" + dumpObject(value) + "}";
85 if (value != "null")
86 r += property + ": " + value + ", ";
89 return r;
91 var result = dumpObject(data);
92 if (result == "")
93 return "(initial values)";
94 return result;
97 function TestData(config) {
98 this.config = config;
99 this.name = describe(config);
100 this.style = {};
101 if (config.svgWidthStyle)
102 this.style["width"] = config.svgWidthStyle;
103 else
104 this.mapPresentationalHintLength("width", config.svgWidthAttr);
106 if (config.svgHeightStyle)
107 this.style["height"] = config.svgHeightStyle;
108 else
109 this.mapPresentationalHintLength("height", config.svgHeightAttr);
112 TestData.prototype.mapPresentationalHintLength =
113 function(cssProperty, attr) {
114 if (attr) {
115 var l = parseLength(attr);
116 if (l)
117 this.style[cssProperty] = l.amount + l.unit;
121 TestData.prototype.computedWidthIsAuto = function() {
122 return !this.style["width"] || this.style["width"] == 'auto';
125 TestData.prototype.computedHeightIsAuto = function() {
126 return !this.style["height"] || this.style["height"] == 'auto' ||
127 (parseLength(this.style["height"]).unit == '%' &&
128 this.containerComputedHeightIsAuto());
131 TestData.prototype.containerComputedWidthIsAuto = function() {
132 return !this.config.containerWidthStyle ||
133 this.config.containerWidthStyle == 'auto';
136 TestData.prototype.containerComputedHeightIsAuto = function() {
137 return !this.config.containerHeightStyle ||
138 this.config.containerHeightStyle == 'auto';
141 TestData.prototype.intrinsicInformation = function() {
142 var w = convertToPx(this.config.svgWidthAttr) || 0;
143 var h = convertToPx(this.config.svgHeightAttr) || 0;
144 var r = 0;
145 if (w && h) {
146 r = w / h;
147 } else {
148 var vb = parseViewBox(this.config.svgViewBoxAttr);
149 if (vb) {
150 r = vb[2] / vb[3];
152 if (r) {
153 if (!w && h)
154 w = h * r;
155 else if (!h && w)
156 h = w / r;
159 return { width: w, height: h, ratio: r };
163 TestData.prototype.computeInlineReplacedSize = function() {
164 var intrinsic = this.intrinsicInformation();
165 var self = this;
167 // http://www.w3.org/TR/CSS2/visudet.html#inline-replaced-height
168 function calculateUsedHeight() {
169 if (self.computedHeightIsAuto()) {
170 if (self.computedWidthIsAuto() && intrinsic.height)
171 return intrinsic.height;
172 if (intrinsic.ratio)
173 return calculateUsedWidth() / intrinsic.ratio;
174 if (intrinsic.height)
175 return intrinsic.height;
176 return 150;
179 return convertToPx(self.style["height"],
180 convertToPx(self.config.containerHeightStyle,
181 self.outerHeight));
184 // http://www.w3.org/TR/CSS2/visudet.html#inline-replaced-width
185 function calculateUsedWidth() {
186 if (self.computedWidthIsAuto()) {
187 if (self.computedHeightIsAuto() && intrinsic.width)
188 return intrinsic.width;
189 if (!self.computedHeightIsAuto() && intrinsic.ratio)
190 return calculateUsedHeight() * intrinsic.ratio;
191 if (self.computedHeightIsAuto() && intrinsic.ratio) {
192 if (self.containerComputedWidthIsAuto()) {
193 // Note: While this is actually undefined in CSS
194 // 2.1, use the suggested value by examining the
195 // ancestor widths.
196 return self.outerWidth;
197 } else {
198 return convertToPx(self.config.containerWidthStyle,
199 self.outerWidth);
202 if (intrinsic.width)
203 return intrinsic.width;
204 return 300;
207 if (self.containerComputedWidthIsAuto())
208 return convertToPx(self.style["width"], self.outerWidth);
209 else
210 return convertToPx(self.style["width"],
211 convertToPx(self.config.containerWidthStyle,
212 self.outerWidth));
214 return { width: calculateUsedWidth(),
215 height: calculateUsedHeight() };
218 var testContainer = document.querySelector('#testContainer');
219 TestData.prototype.outerWidth = testContainer.getBoundingClientRect().width;
220 TestData.prototype.outerHeight = testContainer.getBoundingClientRect().height;
222 window.TestData = TestData;
223 })();
225 function setupContainer(testData, svgElement, options) {
226 options = options || {};
228 var container = document.createElement("div");
230 container.id = "container";
231 if (testData.config.containerWidthStyle)
232 container.style.width = testData.config.containerWidthStyle;
234 if (testData.config.containerHeightStyle)
235 container.style.height = testData.config.containerHeightStyle;
237 if (options.pretty)
238 container.appendChild(document.createTextNode("\n\t\t"));
239 container.appendChild(svgElement);
240 if (options.pretty)
241 container.appendChild(document.createTextNode("\n\t"));
243 return container;
246 function setupSVGElement(testData) {
247 var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
248 svgElement.setAttribute("id", "test");
249 if (testData.config.svgWidthStyle)
250 svgElement.style.width = testData.config.svgWidthStyle;
251 if (testData.config.svgHeightStyle)
252 svgElement.style.height = testData.config.svgHeightStyle;
253 if (testData.config.svgWidthAttr)
254 svgElement.setAttribute("width", testData.config.svgWidthAttr);
255 if (testData.config.svgHeightAttr)
256 svgElement.setAttribute("height", testData.config.svgHeightAttr);
257 if (testData.config.svgViewBoxAttr)
258 svgElement.setAttribute("viewBox", testData.config.svgViewBoxAttr);
260 return svgElement;
263 function buildDemo(testData) {
264 // Non-essential debugging tool
266 var options = { pretty: true };
267 var expectedRect =
268 testData.computeInlineReplacedSize();
269 var container =
270 setupContainer(testData, setupSVGElement(testData), options);
272 var root = document.createElement("html");
273 var style = document.createElement("style");
275 style.textContent = "\n" +
276 "\tbody { margin: 0; font-family: sans-serif }\n" +
277 "\t#expected {\n" +
278 "\t\twidth: " + (expectedRect.width) + "px; height: "
279 + (expectedRect.height) + "px;\n" +
280 "\t\tborder: 10px solid lime; position: absolute;\n" +
281 "\t\tbackground-color: red }\n" +
282 "\t#testContainer { position: absolute;\n" +
283 "\t\ttop: 10px; left: 10px; width: 800px; height: 600px }\n" +
284 "\tsvg { background-color: green }\n" +
285 "\t.result { position: absolute; top: 0; right: 0;\n" +
286 "\t\tbackground-color: hsla(0,0%, 0%, 0.85); border-radius: 0.5em;\n" +
287 "\t\tpadding: 0.5em; border: 0.25em solid black }\n" +
288 "\t.pass { color: lime }\n" +
289 "\t.fail { color: red }\n";
291 root.appendChild(document.createTextNode("\n"));
292 root.appendChild(style);
293 root.appendChild(document.createTextNode("\n"));
295 var script = document.createElement("script");
296 script.textContent = "\n" +
297 "onload = function() {\n" +
298 "\tvar svgRect = \n" +
299 "\t\tdocument.querySelector('#test').getBoundingClientRect();\n" +
300 "\tpassed = (svgRect.width == " + expectedRect.width + " && " +
301 "svgRect.height == " + expectedRect.height + ");\n" +
302 "\tdocument.body.insertAdjacentHTML('beforeEnd',\n" +
303 "\t\t'<span class=\"result '+ (passed ? 'pass' : 'fail') " +
304 "+ '\">' + (passed ? 'Pass' : 'Fail') + '</span>');\n" +
305 "};\n";
307 root.appendChild(script);
308 root.appendChild(document.createTextNode("\n"));
310 var expectedElement = document.createElement("div");
311 expectedElement.id = "expected";
312 root.appendChild(expectedElement);
313 root.appendChild(document.createTextNode("\n"));
315 var testContainer = document.createElement("div");
316 testContainer.id = "testContainer";
317 testContainer.appendChild(document.createTextNode("\n\t"));
318 testContainer.appendChild(container);
319 testContainer.appendChild(document.createTextNode("\n"));
320 root.appendChild(testContainer);
321 root.appendChild(document.createTextNode("\n"));
323 return "<!DOCTYPE html>\n" + root.outerHTML;
326 function doCombinationTest(values, func)
328 // Recursively construct all possible combinations of values and
329 // send them to |func|. Example:
331 // values: [["X", ["a", "b"]],
332 // ["Y", ["c", "d"]]]
334 // generates the objects:
336 // 1: { "X": "a", "Y": "c" }
337 // 2: { "X": "a", "Y": "d" }
338 // 3: { "X": "b", "Y": "c" }
339 // 4: { "X": "b", "Y": "d" }
341 // and each will be sent to |func| with the corresponding prefixed
342 // id (modulo order).
344 var combinationId = 1;
345 function doCombinationTestRecursive(slicedValues, config) {
346 if (slicedValues.length > 0) {
347 var configKey = slicedValues[0][0];
348 slicedValues[0][1].forEach(function(configValue) {
349 var new_config = {};
350 for (k in config)
351 new_config[k] = config[k];
352 new_config[configKey] = configValue;
353 doCombinationTestRecursive(slicedValues.slice(1), new_config);
355 } else {
356 func(config, combinationId++);
359 doCombinationTestRecursive(values, {});
362 var debugHint = function(id) { return "(append ?"+id+" to debug) " };
363 var testSingleId;
364 if (window.location.search) {
365 testSingleId = window.location.search.substring(1);
366 debugHint = function(id) { return ""; };
369 doCombinationTest(
370 [["containerWidthStyle", [null, "400px"]],
371 ["containerHeightStyle", [null, "400px"]],
372 ["svgViewBoxAttr", [ null, "0 0 100 200" ]],
373 ["svgWidthStyle", [ null, "100px", "50%" ]],
374 ["svgHeightStyle", [ null, "100px", "50%" ]],
375 ["svgWidthAttr", [ null, "200", "25%" ]],
376 ["svgHeightAttr", [ null, "200", "25%" ]]],
377 function(config, id) {
378 if (!testSingleId || testSingleId == id) {
379 var testData = new TestData(config);
381 var expectedRect =
382 testData.computeInlineReplacedSize();
383 var svgElement = setupSVGElement(testData);
384 var container =
385 setupContainer(testData, svgElement);
387 var checkSize = function() {
388 var svgRect =
389 svgElement.getBoundingClientRect();
391 try {
392 assert_equals(svgRect.width,
393 expectedRect.width,
394 debugHint(id) + "Wrong width");
395 assert_equals(svgRect.height,
396 expectedRect.height,
397 debugHint(id) + "Wrong height");
398 } finally {
399 testContainer.removeChild(container);
400 if (testSingleId)
401 document.body.removeChild(testContainer);
405 testContainer.appendChild(container);
406 test(checkSize, testData.name);
409 if (testSingleId == id) {
410 var pad = function(n, width, z) {
411 z = z || '0';
412 n = n + '';
413 return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
416 var demo = buildDemo(testData);
417 var iframe = document.createElement('iframe');
418 iframe.style.width = (Math.max(900, expectedRect.width)) + "px";
419 iframe.style.height = (Math.max(400, expectedRect.height)) + "px";
420 iframe.src = "data:text/html;charset=utf-8," + encodeURIComponent(demo);
421 document.body.appendChild(iframe);
423 document.body.insertAdjacentHTML(
424 'beforeEnd',
425 '<p><a href="data:application/octet-stream;charset=utf-8;base64,' +
426 btoa(demo) + '" download="svg-in-object-test-' + pad(id, 3) + '.html">Download</a></p>');