Status: PASS | Duration: 5ms | Subtests: 1/1 | Open test on wpt.live | Open ref on wpt.live | wpt.fyi
/css/CSS2/floats/floats-wrap-bfc-with-margin-003.tentative.html
<!DOCTYPE html> <html class="reftest-wait"> <meta charset="utf-8"> <title>CSS Test: BFCs with large margin, placed next to a float</title> <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com"> <link rel="help" href="https://www.w3.org/TR/CSS21/visuren.html#floats"> <link rel="help" href="https://www.w3.org/TR/CSS21/visudet.html#blockwidth"> <link rel="match" href="floats-wrap-bfc-with-margin-003-ref.html"> <!-- This test exercises the layout of an auto-width BFC with a large positive margin on one side, positioned alongside a float. (Here, "large" = larger than the space that's left when the float's width and the BFC's border are subtracted away from the available space.) The scenarios here break down into 3 cases, described below as A/B/C with expected outcomes for each (with expectations based on the observed and interoperably-implemented behavior of Gecko, WebKit, and Blink, aside from some known bugs listed further down[1]): (A) If the BFC's margin is on the same side as the float, then that margin can simply overlap the float. In this case, nothing needs to overflow or wrap, and there may even be some space remaining for the BFC's auto-width content box (rendered as aqua). (B) If the BFC's margin is on the "line-start" side and the float is on the "line-end" side, then the BFC's margin pushes it into the float's margin-box-area such that it impermissably "collides". So, the BFC instead gets moved down below the float to avoid this collision. With this moved-down placement, there may be space remaining for the BFC's auto-width content-box -- precisely the same amount of space as in case A. (C) If the BFC's margin is on the "line-end" side and the float is on the "line-start" side, then the BFC is placed adjacent to the float, and its large margin simply runs off the line-end edge of its containing block. The BFC's content-box is 0 width (since there's no free space left over). Note: the expected-outcome in case B and C feels somewhat asymmetrical, and in fact Gecko is the only engine that pushes the BFC down in case B. But Blink/WebKit's alternative behavior for case B involves the BFC overlapping the float, which clearly violates the spec, as discussed in their bugs linked below. So I'm making the test expect Gecko's existing and non-spec-violating behavior for case B, at this point. (I'm guessing the asymmetry comes from how overflowing margins are handled at the line-start vs. line-end edge, which makes some sense.) Here's how this test's groups (black-bordered sections) map to these cases: * This test's 1st, 4th, 5th, and 8th groups are "case A". * This test's 3rd and 6th groups are in "case B". * This test's 2nd and 7th groups are in "case C". [1] Known bugs referenced above, which cause parts of this test to fail in WebKit and Blink: https://bugs.webkit.org/show_bug.cgi?id=240128 https://bugs.chromium.org/p/chromium/issues/detail?id=1322774 https://bugs.chromium.org/p/chromium/issues/detail?id=1323004 --> <script> const MARGIN_VALS = [15, 22, 28]; const HORIZ_SIDES = ["left", "right"]; // Used for 'float:*' and 'margin-*'. const DIRECTION_VALS = ["ltr", "rtl"]; function newDivWithClassAndParent(className, parent) { let elem = document.createElement("div"); if (className) { elem.classList.add(className); } parent.appendChild(elem); return elem; } function generateGroup(directionVal, floatVal, marginPropSuffix) { let group = newDivWithClassAndParent("group", document.body); group.style.direction = directionVal; const marginPropName = "margin-" + marginPropSuffix; for (let v of MARGIN_VALS) { let container = newDivWithClassAndParent("container", group); let float = newDivWithClassAndParent("float", container); float.style.cssFloat = floatVal; let bfc = newDivWithClassAndParent("bfc", container); bfc.style[marginPropName] = v + "px"; } } function go() { for (let directionVal of DIRECTION_VALS) { for (let floatVal of HORIZ_SIDES) { for (let marginPropSuffix of HORIZ_SIDES) { generateGroup(directionVal, floatVal, marginPropSuffix); } } } // Note: the "reftest-wait" usage here isn't strictly necessary; it just // helps ensure that we actually make it through all of the above JS and // populate this document with the content that we want to render. // (Specifically: if we e.g. throw a JS exception somewhere early in both // the testcase and reference case, then the "reftest-wait" class will // never be removed; and that will cause the test run to be classified // as a failure, rather than a trivial "pass" with a visual comparison of // two blank documents.) document.documentElement.removeAttribute("class"); } </script> <style> .group { width: 300px; border: 1px solid black; } .container { /* This is the container that holds our float+bfc. We make it an inline-block so that we can test a bunch of these in a row. */ display: inline-block; vertical-align: top; width: 30px; height: 40px; /* This border and margin are just cosmetic, to avoid overlap between * adjacent containers within a row. */ border: 1px solid gray; margin-left: 30px; } .float { /* We'll set the float property elsewhere (to 'right' or 'left'). */ width: 7px; height: 8px; background: fuchsia; border: 1px solid purple; /* Each .float's margin-box (which the corresponding .bfc's border-box cannot * overlap) is 14px wide: * 7px content + 2px horizontal border + 5px horizontal margin * Note that we're intentionally using a nonzero 'margin' here, to be sure * the UA is using the float's margin-box (and not one of its other * boxes) for this non-overlapping calculation. */ margin: 1px 3px 1px 2px; } .bfc { /* Each .bfc's border-box width is 2px (from the border) plus whatever we * resolve 'width:auto' to, which is influenced by the particular choice of * 'margin' values (and the available space). */ display: flow-root; background: aqua; height: 15px; border: 1px solid blue; } </style> <body onload="go()"> </body> </html>
/css/CSS2/floats/floats-wrap-bfc-with-margin-003-ref.html
<!DOCTYPE html> <html class="reftest-wait"> <meta charset="utf-8"> <title>CSS Reference Case</title> <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com"> <script> const MARGIN_VALS = [15, 22, 28]; const HORIZ_SIDES = ["left", "right"]; // Used for 'float:*' and 'margin-*'. const DIRECTION_VALS = ["ltr", "rtl"]; // This comes from .float width + horizontal border and padding: const FLOAT_MARGIN_BOX_WIDTH = 14; // This comes from 30 (container) - 14 (float mbox width) - 2 (bfc border): const AVAIL_WIDTH_NO_WRAPPING = 14; function newDivWithClassAndParent(className, parent) { let elem = document.createElement("div"); if (className) { elem.classList.add(className); } parent.appendChild(elem); return elem; } function generateGroup(directionVal, floatVal, marginPropSuffix) { let group = newDivWithClassAndParent("group", document.body); group.style.direction = directionVal; const marginPropName = "margin-" + marginPropSuffix; for (let v of MARGIN_VALS) { const isMarginSideFloatSide = (marginPropSuffix == floatVal); // initial-char comparison to match [l]eft/[l]tr and [r]ight/[r]tl: const isMarginSideLineStartSide = (marginPropSuffix[0] == directionVal[0]); const isFloatSideLineStartSide = (floatVal[0] == directionVal[0]); let container = newDivWithClassAndParent("container", group); if (!isFloatSideLineStartSide) { // In the corresponding piece of the testcase, the float is floated to // the inline-end side (for the given writing-mode). We use a // "row-reverse" flex container as our mockup for that here. container.style.flexDirection = "row-reverse"; } let float = newDivWithClassAndParent("float", container); let bfc = newDivWithClassAndParent("bfc", container); // Set the actual margin value that we're testing here, based on which // case this group is in. See comment in testcase for explanation of the // three cases. let marginValToUse; if (isMarginSideFloatSide) { // Case (A): in the testcase, the margin simply overlaps the float. // In our mockup here, they don't actually overlap; so we subtract // the portion that overlaps in the testcase, which is the float's // margin-box width: marginValToUse = v - FLOAT_MARGIN_BOX_WIDTH; } else if (isMarginSideLineStartSide) { // Case (B): we push the BFC down below the float (which we emulate // here with a wrapped flexbox), and we use the full specified margin: container.style.flexWrap = "wrap"; marginValToUse = v; } else { // Case (C): we let the BFC be smooshed against the float, and the // margin effectively behaves as if it were clamped to the available // space (so we just clamp it to that value here). marginValToUse = AVAIL_WIDTH_NO_WRAPPING; } bfc.style[marginPropName] = marginValToUse + "px"; } } function go() { for (let directionVal of DIRECTION_VALS) { for (let floatVal of HORIZ_SIDES) { for (let marginPropSuffix of HORIZ_SIDES) { generateGroup(directionVal, floatVal, marginPropSuffix); } } } // Note: the "reftest-wait" usage here isn't strictly necessary; it just // helps ensure that we actually make it through all of the above JS and // populate this document with the content that we want to render. // (Specifically: if we e.g. throw a JS exception somewhere early in both // the testcase and reference case, then the "reftest-wait" class will // never be removed; and that will cause the test run to be classified // as a failure, rather than a trivial "pass" with a visual comparison of // two blank documents.) document.documentElement.removeAttribute("class"); } </script> <style> .group { width: 300px; border: 1px solid black; } .container { display: inline-flex; align-content: start; vertical-align: top; width: 30px; height: 40px; /* This border and margin are just cosmetic, to avoid overlap between * adjacent containers within a row. */ border: 1px solid gray; margin-left: 30px; } .float { width: 7px; height: 8px; background: fuchsia; border: 1px solid purple; margin: 1px 3px 1px 2px; } .bfc { display: flow-root; background: aqua; height: 15px; border: 1px solid blue; /* We use "flex: 1" (on a flex item) to mock up the fill-available-space * block-layout behavior in the testcase. */ flex: 1 auto; } </style> <body onload="go()"> </body> </html>