From d3ee49b092fe464e0e21b101c820533a8d7d6b4f Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sat, 23 Nov 2024 14:48:42 +0100 Subject: [PATCH] LibWeb: Avoid an infinite recursion in replaced element sizing In the case where we had a preferred aspect ratio and a natural height but no natural width, we'd get into ping-ponging infinite recursion by trying to find the width to resolve the height to resolve the width to resolve the height... --- Libraries/LibWeb/Layout/FormattingContext.cpp | 5 +- .../svg-inline-sizing/svg-inline.txt | 172 +++++++ .../replaced-elements/resources/svg-sizing.js | 418 ++++++++++++++++++ .../svg-inline-sizing/svg-inline.html | 29 ++ .../svg-inline-sizing/svg-inline.js | 77 ++++ 5 files changed, 700 insertions(+), 1 deletion(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/html/rendering/replaced-elements/svg-inline-sizing/svg-inline.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/html/rendering/replaced-elements/resources/svg-sizing.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/html/rendering/replaced-elements/svg-inline-sizing/svg-inline.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/html/rendering/replaced-elements/svg-inline-sizing/svg-inline.js diff --git a/Libraries/LibWeb/Layout/FormattingContext.cpp b/Libraries/LibWeb/Layout/FormattingContext.cpp index 72a4452f469..1f08dfea1ac 100644 --- a/Libraries/LibWeb/Layout/FormattingContext.cpp +++ b/Libraries/LibWeb/Layout/FormattingContext.cpp @@ -645,7 +645,10 @@ CSSPixels FormattingContext::compute_height_for_replaced_element(Box const& box, // use the algorithm under 'Minimum and maximum widths' // https://www.w3.org/TR/CSS22/visudet.html#min-max-widths // to find the used width and height. - if (computed_width.is_auto() && computed_height.is_auto() && box.has_preferred_aspect_ratio()) { + if ((computed_width.is_auto() && computed_height.is_auto() && box.has_preferred_aspect_ratio()) + // NOTE: This is a special case where calling tentative_width_for_replaced_element() would call us right back, + // and we'd end up in an infinite loop. So we need to handle this case separately. + && !(!box.has_natural_width() && box.has_natural_height())) { CSSPixels w = tentative_width_for_replaced_element(box, computed_width, available_space); CSSPixels h = used_height; used_height = solve_replaced_size_constraint(w, h, box, available_space).height(); diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/rendering/replaced-elements/svg-inline-sizing/svg-inline.txt b/Tests/LibWeb/Text/expected/wpt-import/html/rendering/replaced-elements/svg-inline-sizing/svg-inline.txt new file mode 100644 index 00000000000..928e523fdcb --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/rendering/replaced-elements/svg-inline-sizing/svg-inline.txt @@ -0,0 +1,172 @@ +Summary + +Harness status: OK + +Rerun + +Found 162 tests + +162 Pass +Details +Result Test Name MessagePass (initial values) +Pass svgViewBoxAttr: '0 0 100 200', +Pass svgWidthStyle: '100px', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '100px', +Pass svgWidthStyle: '50%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '50%', +Pass svgHeightStyle: '100px', +Pass svgViewBoxAttr: '0 0 100 200', svgHeightStyle: '100px', +Pass svgWidthStyle: '100px', svgHeightStyle: '100px', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '100px', svgHeightStyle: '100px', +Pass svgWidthStyle: '50%', svgHeightStyle: '100px', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '50%', svgHeightStyle: '100px', +Pass svgHeightStyle: '50%', +Pass svgViewBoxAttr: '0 0 100 200', svgHeightStyle: '50%', +Pass svgWidthStyle: '100px', svgHeightStyle: '50%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '100px', svgHeightStyle: '50%', +Pass svgWidthStyle: '50%', svgHeightStyle: '50%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '50%', svgHeightStyle: '50%', +Pass svgWidthAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthAttr: '200', +Pass svgWidthStyle: '100px', svgWidthAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '100px', svgWidthAttr: '200', +Pass svgWidthStyle: '50%', svgWidthAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '50%', svgWidthAttr: '200', +Pass svgHeightStyle: '100px', svgWidthAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgHeightStyle: '100px', svgWidthAttr: '200', +Pass svgWidthStyle: '100px', svgHeightStyle: '100px', svgWidthAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '100px', svgHeightStyle: '100px', svgWidthAttr: '200', +Pass svgWidthStyle: '50%', svgHeightStyle: '100px', svgWidthAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '50%', svgHeightStyle: '100px', svgWidthAttr: '200', +Pass svgHeightStyle: '50%', svgWidthAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgHeightStyle: '50%', svgWidthAttr: '200', +Pass svgWidthStyle: '100px', svgHeightStyle: '50%', svgWidthAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '100px', svgHeightStyle: '50%', svgWidthAttr: '200', +Pass svgWidthStyle: '50%', svgHeightStyle: '50%', svgWidthAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '50%', svgHeightStyle: '50%', svgWidthAttr: '200', +Pass svgWidthAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthAttr: '25%', +Pass svgWidthStyle: '100px', svgWidthAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '100px', svgWidthAttr: '25%', +Pass svgWidthStyle: '50%', svgWidthAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '50%', svgWidthAttr: '25%', +Pass svgHeightStyle: '100px', svgWidthAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgHeightStyle: '100px', svgWidthAttr: '25%', +Pass svgWidthStyle: '100px', svgHeightStyle: '100px', svgWidthAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '100px', svgHeightStyle: '100px', svgWidthAttr: '25%', +Pass svgWidthStyle: '50%', svgHeightStyle: '100px', svgWidthAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '50%', svgHeightStyle: '100px', svgWidthAttr: '25%', +Pass svgHeightStyle: '50%', svgWidthAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgHeightStyle: '50%', svgWidthAttr: '25%', +Pass svgWidthStyle: '100px', svgHeightStyle: '50%', svgWidthAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '100px', svgHeightStyle: '50%', svgWidthAttr: '25%', +Pass svgWidthStyle: '50%', svgHeightStyle: '50%', svgWidthAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '50%', svgHeightStyle: '50%', svgWidthAttr: '25%', +Pass svgHeightAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgHeightAttr: '200', +Pass svgWidthStyle: '100px', svgHeightAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '100px', svgHeightAttr: '200', +Pass svgWidthStyle: '50%', svgHeightAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '50%', svgHeightAttr: '200', +Pass svgHeightStyle: '100px', svgHeightAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgHeightStyle: '100px', svgHeightAttr: '200', +Pass svgWidthStyle: '100px', svgHeightStyle: '100px', svgHeightAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '100px', svgHeightStyle: '100px', svgHeightAttr: '200', +Pass svgWidthStyle: '50%', svgHeightStyle: '100px', svgHeightAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '50%', svgHeightStyle: '100px', svgHeightAttr: '200', +Pass svgHeightStyle: '50%', svgHeightAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgHeightStyle: '50%', svgHeightAttr: '200', +Pass svgWidthStyle: '100px', svgHeightStyle: '50%', svgHeightAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '100px', svgHeightStyle: '50%', svgHeightAttr: '200', +Pass svgWidthStyle: '50%', svgHeightStyle: '50%', svgHeightAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '50%', svgHeightStyle: '50%', svgHeightAttr: '200', +Pass svgWidthAttr: '200', svgHeightAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthAttr: '200', svgHeightAttr: '200', +Pass svgWidthStyle: '100px', svgWidthAttr: '200', svgHeightAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '100px', svgWidthAttr: '200', svgHeightAttr: '200', +Pass svgWidthStyle: '50%', svgWidthAttr: '200', svgHeightAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '50%', svgWidthAttr: '200', svgHeightAttr: '200', +Pass svgHeightStyle: '100px', svgWidthAttr: '200', svgHeightAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgHeightStyle: '100px', svgWidthAttr: '200', svgHeightAttr: '200', +Pass svgWidthStyle: '100px', svgHeightStyle: '100px', svgWidthAttr: '200', svgHeightAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '100px', svgHeightStyle: '100px', svgWidthAttr: '200', svgHeightAttr: '200', +Pass svgWidthStyle: '50%', svgHeightStyle: '100px', svgWidthAttr: '200', svgHeightAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '50%', svgHeightStyle: '100px', svgWidthAttr: '200', svgHeightAttr: '200', +Pass svgHeightStyle: '50%', svgWidthAttr: '200', svgHeightAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgHeightStyle: '50%', svgWidthAttr: '200', svgHeightAttr: '200', +Pass svgWidthStyle: '100px', svgHeightStyle: '50%', svgWidthAttr: '200', svgHeightAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '100px', svgHeightStyle: '50%', svgWidthAttr: '200', svgHeightAttr: '200', +Pass svgWidthStyle: '50%', svgHeightStyle: '50%', svgWidthAttr: '200', svgHeightAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '50%', svgHeightStyle: '50%', svgWidthAttr: '200', svgHeightAttr: '200', +Pass svgWidthAttr: '25%', svgHeightAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthAttr: '25%', svgHeightAttr: '200', +Pass svgWidthStyle: '100px', svgWidthAttr: '25%', svgHeightAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '100px', svgWidthAttr: '25%', svgHeightAttr: '200', +Pass svgWidthStyle: '50%', svgWidthAttr: '25%', svgHeightAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '50%', svgWidthAttr: '25%', svgHeightAttr: '200', +Pass svgHeightStyle: '100px', svgWidthAttr: '25%', svgHeightAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgHeightStyle: '100px', svgWidthAttr: '25%', svgHeightAttr: '200', +Pass svgWidthStyle: '100px', svgHeightStyle: '100px', svgWidthAttr: '25%', svgHeightAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '100px', svgHeightStyle: '100px', svgWidthAttr: '25%', svgHeightAttr: '200', +Pass svgWidthStyle: '50%', svgHeightStyle: '100px', svgWidthAttr: '25%', svgHeightAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '50%', svgHeightStyle: '100px', svgWidthAttr: '25%', svgHeightAttr: '200', +Pass svgHeightStyle: '50%', svgWidthAttr: '25%', svgHeightAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgHeightStyle: '50%', svgWidthAttr: '25%', svgHeightAttr: '200', +Pass svgWidthStyle: '100px', svgHeightStyle: '50%', svgWidthAttr: '25%', svgHeightAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '100px', svgHeightStyle: '50%', svgWidthAttr: '25%', svgHeightAttr: '200', +Pass svgWidthStyle: '50%', svgHeightStyle: '50%', svgWidthAttr: '25%', svgHeightAttr: '200', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '50%', svgHeightStyle: '50%', svgWidthAttr: '25%', svgHeightAttr: '200', +Pass svgHeightAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgHeightAttr: '25%', +Pass svgWidthStyle: '100px', svgHeightAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '100px', svgHeightAttr: '25%', +Pass svgWidthStyle: '50%', svgHeightAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '50%', svgHeightAttr: '25%', +Pass svgHeightStyle: '100px', svgHeightAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgHeightStyle: '100px', svgHeightAttr: '25%', +Pass svgWidthStyle: '100px', svgHeightStyle: '100px', svgHeightAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '100px', svgHeightStyle: '100px', svgHeightAttr: '25%', +Pass svgWidthStyle: '50%', svgHeightStyle: '100px', svgHeightAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '50%', svgHeightStyle: '100px', svgHeightAttr: '25%', +Pass svgHeightStyle: '50%', svgHeightAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgHeightStyle: '50%', svgHeightAttr: '25%', +Pass svgWidthStyle: '100px', svgHeightStyle: '50%', svgHeightAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '100px', svgHeightStyle: '50%', svgHeightAttr: '25%', +Pass svgWidthStyle: '50%', svgHeightStyle: '50%', svgHeightAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '50%', svgHeightStyle: '50%', svgHeightAttr: '25%', +Pass svgWidthAttr: '200', svgHeightAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthAttr: '200', svgHeightAttr: '25%', +Pass svgWidthStyle: '100px', svgWidthAttr: '200', svgHeightAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '100px', svgWidthAttr: '200', svgHeightAttr: '25%', +Pass svgWidthStyle: '50%', svgWidthAttr: '200', svgHeightAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '50%', svgWidthAttr: '200', svgHeightAttr: '25%', +Pass svgHeightStyle: '100px', svgWidthAttr: '200', svgHeightAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgHeightStyle: '100px', svgWidthAttr: '200', svgHeightAttr: '25%', +Pass svgWidthStyle: '100px', svgHeightStyle: '100px', svgWidthAttr: '200', svgHeightAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '100px', svgHeightStyle: '100px', svgWidthAttr: '200', svgHeightAttr: '25%', +Pass svgWidthStyle: '50%', svgHeightStyle: '100px', svgWidthAttr: '200', svgHeightAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '50%', svgHeightStyle: '100px', svgWidthAttr: '200', svgHeightAttr: '25%', +Pass svgHeightStyle: '50%', svgWidthAttr: '200', svgHeightAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgHeightStyle: '50%', svgWidthAttr: '200', svgHeightAttr: '25%', +Pass svgWidthStyle: '100px', svgHeightStyle: '50%', svgWidthAttr: '200', svgHeightAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '100px', svgHeightStyle: '50%', svgWidthAttr: '200', svgHeightAttr: '25%', +Pass svgWidthStyle: '50%', svgHeightStyle: '50%', svgWidthAttr: '200', svgHeightAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '50%', svgHeightStyle: '50%', svgWidthAttr: '200', svgHeightAttr: '25%', +Pass svgWidthAttr: '25%', svgHeightAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthAttr: '25%', svgHeightAttr: '25%', +Pass svgWidthStyle: '100px', svgWidthAttr: '25%', svgHeightAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '100px', svgWidthAttr: '25%', svgHeightAttr: '25%', +Pass svgWidthStyle: '50%', svgWidthAttr: '25%', svgHeightAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '50%', svgWidthAttr: '25%', svgHeightAttr: '25%', +Pass svgHeightStyle: '100px', svgWidthAttr: '25%', svgHeightAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgHeightStyle: '100px', svgWidthAttr: '25%', svgHeightAttr: '25%', +Pass svgWidthStyle: '100px', svgHeightStyle: '100px', svgWidthAttr: '25%', svgHeightAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '100px', svgHeightStyle: '100px', svgWidthAttr: '25%', svgHeightAttr: '25%', +Pass svgWidthStyle: '50%', svgHeightStyle: '100px', svgWidthAttr: '25%', svgHeightAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '50%', svgHeightStyle: '100px', svgWidthAttr: '25%', svgHeightAttr: '25%', +Pass svgHeightStyle: '50%', svgWidthAttr: '25%', svgHeightAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgHeightStyle: '50%', svgWidthAttr: '25%', svgHeightAttr: '25%', +Pass svgWidthStyle: '100px', svgHeightStyle: '50%', svgWidthAttr: '25%', svgHeightAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '100px', svgHeightStyle: '50%', svgWidthAttr: '25%', svgHeightAttr: '25%', +Pass svgWidthStyle: '50%', svgHeightStyle: '50%', svgWidthAttr: '25%', svgHeightAttr: '25%', +Pass svgViewBoxAttr: '0 0 100 200', svgWidthStyle: '50%', svgHeightStyle: '50%', svgWidthAttr: '25%', svgHeightAttr: '25%', \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/html/rendering/replaced-elements/resources/svg-sizing.js b/Tests/LibWeb/Text/input/wpt-import/html/rendering/replaced-elements/resources/svg-sizing.js new file mode 100644 index 00000000000..c212c6b2832 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/rendering/replaced-elements/resources/svg-sizing.js @@ -0,0 +1,418 @@ +// Simple implementation of SVG sizing + +setup({explicit_done: true}); + +var SVGSizing = (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 mapPresentationalHintLength(testData, cssProperty, attr) { + if (attr) { + var l = parseLength(attr); + if (l) + testData.style[cssProperty] = l.amount + l.unit; + } + } + + function computedWidthIsAuto(testData) { + return !testData.style["width"] || testData.style["width"] == 'auto'; + } + + function computedHeightIsAuto(testData) { + return !testData.style["height"] || testData.style["height"] == 'auto' || + (parseLength(testData.style["height"]).unit == '%' && + containerComputedHeightIsAuto(testData)); + } + + function containerComputedWidthIsAuto(testData) { + return !testData.config.containerWidthStyle || + testData.config.containerWidthStyle == 'auto'; + } + + function containerComputedHeightIsAuto(testData) { + return !testData.config.containerHeightStyle || + testData.config.containerHeightStyle == 'auto'; + } + + function intrinsicInformation(testData) { + if (testData.config.placeholder == 'iframe') + return {}; + + var w = convertToPx(testData.config.svgWidthAttr) || 0; + var h = convertToPx(testData.config.svgHeightAttr) || 0; + var r = 0; + if (w && h) { + r = w / h; + } else { + var vb = parseViewBox(testData.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 }; + }; + + function contentAttributeForPlaceholder(testData) { + if (testData.config.placeholder == 'object') + return "data"; + else + return "src"; + } + + function TestData(config) { + this.config = config; + this.name = describe(config); + this.style = {}; + if (config.placeholder) { + mapPresentationalHintLength(this, "width", config.placeholderWidthAttr); + mapPresentationalHintLength(this, "height", config.placeholderHeightAttr); + } else { + if (config.svgWidthStyle) + this.style["width"] = config.svgWidthStyle; + else + mapPresentationalHintLength(this, "width", config.svgWidthAttr); + + if (config.svgHeightStyle) + this.style["height"] = config.svgHeightStyle; + else + mapPresentationalHintLength(this, "height", config.svgHeightAttr); + } + } + + TestData.prototype.computeInlineReplacedSize = function(outerWidth, outerHeight) { + var intrinsic = intrinsicInformation(this); + var self = this; + + // http://www.w3.org/TR/CSS2/visudet.html#inline-replaced-height + function calculateUsedHeight() { + if (computedHeightIsAuto(self)) { + if (computedWidthIsAuto(self) && 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, + outerHeight)); + } + + // http://www.w3.org/TR/CSS2/visudet.html#inline-replaced-width + function calculateUsedWidth() { + if (computedWidthIsAuto(self)) { + if (computedHeightIsAuto(self) && intrinsic.width) + return intrinsic.width; + if (!computedHeightIsAuto(self) && intrinsic.ratio) + return calculateUsedHeight() * intrinsic.ratio; + if (computedHeightIsAuto(self) && intrinsic.ratio) { + if (containerComputedWidthIsAuto(self)) { + // Note: While this is actually undefined in CSS + // 2.1, use the suggested value by examining the + // ancestor widths. + return outerWidth; + } else { + return convertToPx(self.config.containerWidthStyle, + outerWidth); + } + } + if (intrinsic.width) + return intrinsic.width; + return 300; + } + + if (containerComputedWidthIsAuto(self)) + return convertToPx(self.style["width"], outerWidth); + else + return convertToPx(self.style["width"], + convertToPx(self.config.containerWidthStyle, + outerWidth)); + } + return { width: calculateUsedWidth(), + height: calculateUsedHeight() }; + }; + + TestData.prototype.buildContainer = function (placeholder, options) { + options = options || {}; + + var container = document.createElement("div"); + + container.id = "container"; + if (this.config.containerWidthStyle) + container.style.width = this.config.containerWidthStyle; + + if (this.config.containerHeightStyle) + container.style.height = this.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; + }; + + TestData.prototype.buildSVGOrPlaceholder = function (options) { + options = options || {}; + var self = this; + + if (this.config.placeholder) { + var generateSVGURI = function(testData, encoder) { + var res = '' + (passed ? 'Pass' : 'Fail') + '');\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 "\n" + root.outerHTML; + } + + function pad(n, width, z) { + z = z || '0'; + n = n + ''; + return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n; + } + + function heightToDescription(height) { + if (!height || height == "auto") + return "auto"; + if (parseLength(height).unit == '%') + return "percentage"; + return "fixed"; + } + + var demoRoot = document.querySelector('#demo'); + if (demoRoot) { + var demo = buildDemoSerialization(); + 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); + demoRoot.appendChild(iframe); + demoRoot.insertAdjacentHTML( + 'beforeEnd', + '

Download

'); + } + }; + + return { + TestData: TestData, + doCombinationTest: function(values, func, testSingleId) { + function computeConfig(id) { + id--; + var multiplier = 1; + var config = {}; + for (var i=0; i= multiplier) + return null; + return config; + } + + function cont(id) { + var config = computeConfig(id); + if (config && (!testSingleId || testSingleId == id)) { + var next = function() {func(config, id, cont)}; + // Make sure we don't blow the stack, without too much slowness + if (id % 20 === 0) { + step_timeout(next, 0); + } else { + next(); + } + } else { + done(); + } + }; + + if (testSingleId) + cont(testSingleId); + else + cont(1); + } + }; +})(); diff --git a/Tests/LibWeb/Text/input/wpt-import/html/rendering/replaced-elements/svg-inline-sizing/svg-inline.html b/Tests/LibWeb/Text/input/wpt-import/html/rendering/replaced-elements/svg-inline-sizing/svg-inline.html new file mode 100644 index 00000000000..4b8c34eb83a --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/rendering/replaced-elements/svg-inline-sizing/svg-inline.html @@ -0,0 +1,29 @@ + + + + SVG sizing: inline + + + + + + + + + + + +
+
+
+ + + diff --git a/Tests/LibWeb/Text/input/wpt-import/html/rendering/replaced-elements/svg-inline-sizing/svg-inline.js b/Tests/LibWeb/Text/input/wpt-import/html/rendering/replaced-elements/svg-inline-sizing/svg-inline.js new file mode 100644 index 00000000000..fb3949f54e5 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/rendering/replaced-elements/svg-inline-sizing/svg-inline.js @@ -0,0 +1,77 @@ +// global async_test, assert_equals +// +// This test generates a couple of scenarios (each a +// SVGSizing.TestData) for sizing inline and uses a simple +// JavaScript sizing implementation for comparison. +// +// The tests loops through different combinations of: +// +// * width and height attributes and style on +// +// * viewBox on (gives intrinsic ratio) +// +// * width and height on containing block of +// +// All these may contribute to the final size of the SVG. The test +// focuses on the size of the CSS box generated by the SVG. Little +// focus is put on variations within an attribute that doesn't affect +// the final size. +// +// To debug a specific test append ? to the URL. An
 element.
+
+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 ""; };
+}
+
+var testContainer = document.querySelector('#testContainer');
+var testContainerWidth = testContainer.getBoundingClientRect().width;
+var testContainerHeight = testContainer.getBoundingClientRect().height;
+
+SVGSizing.doCombinationTest(
+    [["placeholder", [ null ]],
+     ["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, cont) {
+        var testData = new SVGSizing.TestData(config);
+
+        var expectedRect =
+                testData.computeInlineReplacedSize(testContainerWidth,
+                                                   testContainerHeight);
+        var svgElement = testData.buildSVGOrPlaceholder();
+        var container =
+                testData.buildContainer(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);
+                cont(id+1);
+            }
+        };
+
+        testContainer.appendChild(container);
+        test(checkSize, testData.name);
+
+        if (testSingleId == id) {
+            testData.buildDemo(expectedRect, id);
+        }
+    }, testSingleId);