tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_dynamic_reflow_root_disallowal.html (28910B)


      1 <!DOCTYPE HTML>
      2 <html>
      3 <!--
      4 https://bugzilla.mozilla.org/show_bug.cgi?id=1508420
      5 -->
      6 <head>
      7  <meta charset="utf-8">
      8  <title>
      9    Test for Bug 1508420: Cases where a frame isn't allowed to be a dynamic
     10    reflow root
     11  </title>
     12  <script src="/tests/SimpleTest/SimpleTest.js"></script>
     13  <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
     14  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
     15 </head>
     16 <body onload="main()">
     17 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1508420">Mozilla Bug 1508420</a>
     18 <p id="display">
     19  <!-- Here's the iframe that we'll do all of our testing/snapshotting in: -->
     20  <iframe srcdoc="<!DOCTYPE html><body></body>"></iframe>
     21 </p>
     22 <script type="application/javascript">
     23  /** Test for Bug 1508420 */
     24  /**
     25   * This test exercises various cases where we exclude a frame from being
     26   * flagged as a dynamic reflow root. (We prevent this because we know that
     27   * there are cases where we'd produce incorrect layout if we initiated reflow
     28   * from the frame in question.)
     29   *
     30   * Roughly, the idea in each subtest here is to do the following:
     31   *  1) Set up a scenario with some condition that we think should prevent a
     32   *     particular frame from being flagged as a dynamic reflow root.
     33   *  2) Make a dynamic tweak that we expect would result in broken layout, if
     34   *     we had allowed the frame in question to be a dynamic reflow root.
     35   *     Take a snapshot.
     36   *  3) Force a full reconstruct + reflow of the document's frames (by
     37   *     toggling "display:none" on the root element). Take another snapshot.
     38   *  4) Assert that snapshots look the same -- i.e. that our incremental
     39   *     reflow didn't produce the wrong layout.
     40   *
     41   * Ideally, every condition in ReflowInput::InitDynamicReflowRoot()
     42   * should have a corresponding subtest here (and the subtest should fail if
     43   * we remove the condition from InitDynamicReflowRoot).
     44   */
     45 
     46  // Styles that are sufficient to make a typical element into a reflow root.
     47  // We apply these styles to "reflow root candidates" throughout this test
     48  // (and then add other styles that should make the candidate ineligible,
     49  // typically).
     50  const gReflowRootCandidateStyles =
     51    "display: flow-root; will-change: transform; width: 10px; height: 10px;";
     52 
     53  // Some convenience globals for the document inside the iframe:
     54  // (initialized in 'main' after the iframe gets a chance to load)
     55  // --------------------------------------------------------------
     56  let gFWindow;
     57  let gFDoc;
     58  let gFBody;
     59 
     60  // Some utility functions used in each test function:
     61  // --------------------------------------------------
     62  function createStyledDiv(divStyleStr, divInnerText) {
     63    let div = gFDoc.createElement("div");
     64    div.style.cssText = divStyleStr;
     65    if (typeof divInnerText !== "undefined") {
     66      div.innerText = divInnerText;
     67    }
     68    return div;
     69  }
     70 
     71  // This function takes an initial snapshot, then a second snapshot after
     72  // invoking the given tweakFunc, and finally a third after forcing the frame
     73  // tree to be reconstructed from scratch. Then it compares the snapshots to
     74  // validate that the tweak did produce a visible change, & that the
     75  // after-tweak rendering looks the same in the last two snapshots.
     76  function tweakAndCompareSnapshots(tweakFunc, descPrefix) {
     77    let snapPreTweak = snapshotWindow(gFWindow, false);
     78    let descPreTweak = descPrefix + "-initial-rendering";
     79 
     80    // Now we invoke the tweak (changing the size of some content inside the
     81    // reflow root candidate). If this influences the size of the candidate
     82    // itself, and we fail to do any reflow outside of the candidate because
     83    // we made it a reflow root, then we expect to end up with a broken layout
     84    // due to a parent or sibling not having been resized/repositioned.
     85    // We'll discover that when comparing snapIncReflow against snapFullReflow
     86    // below.
     87    tweakFunc();
     88 
     89    let snapIncReflow = snapshotWindow(gFWindow, false);
     90    let descIncReflow = descPrefix + "-after-tweak-inc-reflow";
     91 
     92    // Now we trigger a "full" reflow (not incremental), by forcing
     93    // frame reconstruction all the way from the body element. This should
     94    // force us to reflow from the actual document root, even if we have
     95    // promoted any frames to be dynamic reflow roots.
     96    gFBody.style.display = "none";
     97    gFBody.offsetTop; // flush layout
     98    gFBody.style.display = "";
     99    let snapFullReflow = snapshotWindow(gFWindow, false);
    100    let descFullReflow = descPrefix + "-after-tweak-full-reflow";
    101 
    102    assertSnapshots(snapIncReflow, snapPreTweak, false, null,
    103                    descIncReflow, descPreTweak);
    104    assertSnapshots(snapIncReflow, snapFullReflow, true, null,
    105                    descIncReflow, descFullReflow);
    106  }
    107 
    108  // Test functions (called from "main"), with a subtest array in most cases:
    109  // ------------------------------------------------------------------------
    110 
    111  // Subtests for intrinsic size keywords (and equivalent, e.g. percentages) as
    112  // values for width/height/{min,max}-{width,height} on reflow root candidates:
    113  let intrinsicSizeSubtests = [
    114    { desc: "width-auto",
    115      candStyle: "width:auto",
    116    },
    117    { desc: "width-pct",
    118      candStyle: "width:80%",
    119    },
    120    { desc: "width-calc-pct",
    121      candStyle: "width:calc(10px + 80%)",
    122    },
    123    { desc: "width-min-content",
    124      candStyle: "width:-moz-min-content; width:min-content;",
    125    },
    126    { desc: "width-max-content",
    127      candStyle: "width:-moz-max-content; width:max-content;",
    128    },
    129    { desc: "min-width-min-content",
    130      candStyle: "min-width:-moz-min-content; min-width:min-content;",
    131    },
    132    { desc: "min-width-max-content",
    133      candStyle: "min-width:-moz-max-content; min-width:max-content;",
    134    },
    135    { desc: "max-width-min-content",
    136      // Note: hardcoded 'width' here must be larger than what 'inner'
    137      // gets resized to, so that max-width gets a chance to clamp.
    138      candStyle: "width: 50px; \
    139                  max-width:-moz-min-content; max-width:min-content;",
    140    },
    141    { desc: "max-width-max-content",
    142      candStyle: "width: 50px; \
    143                  max-width:-moz-max-content; max-width:max-content;",
    144    },
    145    { desc: "height-auto",
    146      candStyle: "height:auto",
    147    },
    148    { desc: "height-pct",
    149      candStyle: "height:80%",
    150    },
    151    { desc: "height-calc-pct",
    152      candStyle: "height:calc(10px + 80%)",
    153    },
    154    { desc: "height-min-content",
    155      candStyle: "height:-moz-min-content; height:min-content;",
    156    },
    157    { desc: "height-max-content",
    158      candStyle: "height:-moz-max-content; height:max-content;",
    159    },
    160    { desc: "min-height-min-content",
    161      candStyle: "min-height:-moz-min-content; min-height:min-content;",
    162    },
    163    { desc: "min-height-max-content",
    164      candStyle: "min-height:-moz-max-content; min-height:max-content;",
    165    },
    166    { desc: "max-height-min-content",
    167      // Note: hardcoded 'height' here must be larger than what 'inner'
    168      // gets resized to, so that max-height gets a chance to clamp.
    169      candStyle: "height: 50px; \
    170                  max-height:-moz-min-content; max-height:min-content;",
    171    },
    172    { desc: "max-height-max-content",
    173      candStyle: "height: 50px; \
    174                  max-height:-moz-max-content; max-height:max-content;",
    175    },
    176  ];
    177 
    178  // Intrinsic heights (e.g. 'height:auto') should prevent
    179  // an element from being a reflow root.
    180  function runIntrinsicSizeSubtest(subtest) {
    181    // Run each testcase in horizontal & vertical writing mode:
    182    for (let wmVal of ["horizontal-tb", "vertical-lr"]) {
    183      gFBody.style.writingMode = wmVal;
    184 
    185      // Short version of WM, for use in logging for snapshot comparison below:
    186      let wmDesc = (wmVal == "horizontal-tb" ? "-horizWM" : "-vertWM");
    187 
    188      // This outer div is intrinsically sized, and it needs to be reflowed
    189      // when the size of its child (the reflow root candidate) changes.
    190      let outer = createStyledDiv("border: 2px solid teal; \
    191                                   inline-size: -moz-max-content; \
    192                                   inline-size: max-content");
    193      // The reflow root candidate:
    194      let cand = createStyledDiv(gReflowRootCandidateStyles +
    195                                 subtest.candStyle);
    196 
    197      // Something whose size we can adjust, inside the reflow root candidate:
    198      let inner = createStyledDiv("height:20px; width:20px; \
    199                                   border: 1px solid purple");
    200 
    201      cand.appendChild(inner);
    202      outer.appendChild(cand);
    203      gFBody.appendChild(outer);
    204 
    205      let tweakFunc = function() {
    206        inner.style.width = inner.style.height = "40px";
    207      };
    208 
    209      tweakAndCompareSnapshots(tweakFunc, subtest.desc + wmDesc);
    210 
    211      // clean up
    212      outer.remove();
    213      gFBody.style.writingMode = "";
    214    }
    215  }
    216 
    217  let flexItemSubtests = [
    218    { desc: "flex-basis-content",
    219      candStyle: "flex-basis:content;",
    220    },
    221    { desc: "flex-basis-min-content",
    222      candStyle: "flex-basis:-moz-min-content;flex-basis:min-content;",
    223    },
    224    { desc: "flex-basis-auto-width-auto",
    225      candStyle: "flex-basis:auto;width:auto;",
    226    },
    227    // For percent flex-basis, we're concerned with cases where the percent
    228    // triggers content-based sizing during the flex container's intrinsic
    229    // sizing step.  So we need to get the container to be intrinsically sized;
    230    // hence the use of the (optional) "isContainerIntrinsicallySized" flag.
    231 // FIXME(bug 1548078): the following two tests fail to produce a rendering difference:
    232 //    { desc: "flex-basis-pct",
    233 //      candStyle: "flex-basis:80%;",
    234 //      isContainerIntrinsicallySized: true,
    235 //    },
    236 //    { desc: "flex-basis-calc-pct",
    237 //      candStyle: "flex-basis:calc(10px + 80%);",
    238 //      isContainerIntrinsicallySized: true,
    239 //    },
    240    { desc: "flex-basis-from-pct-isize",
    241      candStyle: "inline-size:80%",
    242      isContainerIntrinsicallySized: true,
    243    },
    244    { desc: "flex-basis-from-calc-pct-isize",
    245      candStyle: "inline-size:calc(10px + 80%);",
    246      isContainerIntrinsicallySized: true,
    247    },
    248    // Testing the magic "min-main-size:auto" keyword
    249    // and other intrinsic min/max sizes
    250    { desc: "flex-min-inline-size-auto",
    251      candStyle: "flex:0 5px; inline-size:auto; min-inline-size:auto",
    252    },
    253    { desc: "flex-min-inline-size-min-content",
    254      candStyle: "flex:0 5px; inline-size:auto; min-inline-size:min-content",
    255    },
    256    { desc: "flex-min-block-size-auto",
    257      candStyle: "flex:0 5px; block-size:auto; min-block-size:auto",
    258      isContainerColumnOriented: true,
    259    },
    260    { desc: "flex-min-block-size-auto",
    261      candStyle: "flex:0 5px; block-size:auto; min-block-size:min-content",
    262      isContainerColumnOriented: true,
    263    },
    264  ];
    265 
    266  // Content-dependent flex-basis values should prevent a flex item
    267  // from being a reflow root.
    268  function runFlexItemSubtest(subtest) {
    269    // We create a flex container with two flex items:
    270    //  - a simple flex item that just absorbs all extra space
    271    //  - the reflow root candidate
    272    let containerSizeVal = subtest.isContainerIntrinsicallySized ?
    273      "max-content" : "100px";
    274    let containerSizeDecl =
    275      "inline-size: " + containerSizeVal + "; " +
    276      "block-size: " + containerSizeVal + ";";
    277    let containerFlexDirectionDecl = "flex-direction: " +
    278      (subtest.isContainerColumnOriented ? "column" : "row") + ";"
    279 
    280    let flexContainer = createStyledDiv("display: flex; \
    281                                         border: 2px solid teal; " +
    282                                         containerSizeDecl +
    283                                         containerFlexDirectionDecl);
    284 
    285    let simpleItem = createStyledDiv("border: 1px solid gray; \
    286                                      background: yellow; \
    287                                      min-inline-size: 10px; \
    288                                      flex: 1");
    289 
    290    // The reflow root candidate
    291    // (Note that we use min-width:0/min-height:0 by default, but subtests
    292    // might override that with other values in 'candStyle'.)
    293    let cand = createStyledDiv(gReflowRootCandidateStyles +
    294                               " min-width: 0; min-height: 0; " +
    295                               subtest.candStyle);
    296 
    297    // Something whose size we can adjust, inside the reflow root candidate:
    298    let inner = createStyledDiv("height:20px; width:20px");
    299 
    300    cand.appendChild(inner);
    301    flexContainer.appendChild(simpleItem);
    302    flexContainer.appendChild(cand);
    303    gFBody.appendChild(flexContainer);
    304 
    305    let tweakFunc = function() {
    306      inner.style.width = inner.style.height = "40px";
    307    };
    308    tweakAndCompareSnapshots(tweakFunc, subtest.desc);
    309 
    310    flexContainer.remove(); // clean up
    311  }
    312 
    313  let gridItemSubtests = [
    314    { desc: "grid-pct-inline-isize",
    315      candStyle: "inline-size:80%",
    316      isContainerIntrinsicallySized: true,
    317    },
    318    { desc: "grid-calc-pct-inline-isize",
    319      candStyle: "inline-size:calc(10px + 80%);",
    320      isContainerIntrinsicallySized: true,
    321    },
    322    { desc: "grid-min-inline-size-min-content",
    323      candStyle: "min-inline-size:min-content",
    324    },
    325  ];
    326 
    327  // 'auto' and intrinsic size keywords on some properties should prevent
    328  // a grid item from becoming a reflow root.
    329  function runGridItemSubtest(subtest) {
    330    // We create a 4x4 grid container with two grid items:
    331    //  - a simple grid item that just absorbs all extra space
    332    //  - the reflow root candidate
    333    let containerSizeVal = subtest.isContainerIntrinsicallySized ?
    334      "max-content" : "100px";
    335    let containerSizeDecl =
    336      "inline-size: " + containerSizeVal + "; " +
    337      "block-size: " + containerSizeVal + ";";
    338    let containerGridDirectionDecl = "grid-auto-flow: " +
    339      (subtest.isContainerColumnOriented ? "column" : "row") + ";"
    340    let gridContainer = createStyledDiv("display: grid; \
    341                                         grid: 1fr auto / 1fr auto; \
    342                                         border: 2px solid teal; " +
    343                                         containerSizeDecl +
    344                                         containerGridDirectionDecl);
    345 
    346    let simpleItem = createStyledDiv("border: 1px solid gray; \
    347                                      background: yellow;");
    348 
    349    // The reflow root candidate
    350    let cand = createStyledDiv(gReflowRootCandidateStyles +
    351                               "background: blue; " +
    352                               "grid-area:2/2; " +
    353                               "min-width: 10px; min-height: 10px; " +
    354                               subtest.candStyle);
    355    // Something whose size we can adjust, inside the reflow root candidate:
    356    let inner = createStyledDiv("height:20px; width:20px;");
    357 
    358    cand.appendChild(inner);
    359    gridContainer.appendChild(simpleItem);
    360    gridContainer.appendChild(cand);
    361    gFBody.appendChild(gridContainer);
    362 
    363    let tweakFunc = function() {
    364      inner.style.width = inner.style.height = "40px";
    365    };
    366    tweakAndCompareSnapshots(tweakFunc, subtest.desc);
    367 
    368    gridContainer.remove(); // clean up
    369  }
    370 
    371  let gridContainerSubtests = [
    372    { desc: "grid-column-start",
    373      candStyle: "grid-column-start:2",
    374    },
    375    { desc: "grid-column-end",
    376      candStyle: "grid-column-end:3",
    377    },
    378    { desc: "grid-row-start",
    379      candStyle: "grid-row-start:2",
    380    },
    381    { desc: "grid-row-end",
    382      candStyle: "grid-row-end:3",
    383    },
    384  ];
    385 
    386  // Test that changes to grid item properties that affect grid container
    387  // layout causes a grid container reflow when the item is a reflow root.
    388  function runGridContainerSubtest(subtest) {
    389    // We create a 4x4 grid container with one grid item:
    390    //  - a reflow root grid item that we'll tweak from
    391    //    the list above. By default it's placed at 1,1
    392    //    but after the tweak it should be placed elsewhere
    393    let gridContainer = createStyledDiv("display: grid; \
    394                                         width: 100px; \
    395                                         height: 100px; \
    396                                         grid: 1fr 10px / 1fr 10px; \
    397                                         border: 2px solid teal");
    398    // The reflow root candidate
    399    let cand = createStyledDiv(gReflowRootCandidateStyles +
    400                               "background: blue; " +
    401                               " min-width: 10px; min-height: 10px; ");
    402 
    403    gridContainer.appendChild(cand);
    404    gFBody.appendChild(gridContainer);
    405 
    406    let tweakFunc = function() {
    407      cand.style.cssText += "; " + subtest.candStyle;
    408    };
    409    tweakAndCompareSnapshots(tweakFunc, subtest.desc);
    410 
    411    gridContainer.remove(); // clean up
    412  }
    413 
    414  let gridSubgridSubtests = [
    415    { desc: "subgrid",
    416      candStyle: "grid: subgrid / subgrid",
    417    },
    418    { desc: "subgrid-rows",
    419      candStyle: "grid: subgrid / 20px",
    420    },
    421    { desc: "subgrid-columns",
    422      candStyle: "grid: 20px / subgrid",
    423    },
    424  ];
    425 
    426  // Test that a subgrid is not a reflow root.
    427  function runGridSubgridSubtest(subtest) {
    428    // We create a 4x4 grid container a with one grid item:
    429    //  - a reflow root display:grid that we'll style as a subgrid from
    430    //    the list above. We place an item inside it that we'll tweak
    431    //    the size of, which should affect the outer grid track sizes.
    432    let gridContainer = createStyledDiv("display: grid; \
    433                                         width: 100px; \
    434                                         height: 100px; \
    435                                         grid: 1fr auto / 1fr auto; \
    436                                         border: 2px solid teal");
    437    // The reflow root candidate
    438    let cand = createStyledDiv(gReflowRootCandidateStyles +
    439                               "display: grid;" +
    440                               "grid-area: 2/2;" +
    441                               "background: blue;" +
    442                               "min-width: 10px; min-height: 10px;" +
    443                               subtest.candStyle);
    444 
    445    // Something whose size we can adjust, inside the subgrid:
    446    let inner = createStyledDiv("height:20px; width:20px;");
    447 
    448    cand.appendChild(inner);
    449    gridContainer.appendChild(cand);
    450    gFBody.appendChild(gridContainer);
    451 
    452    let tweakFunc = function() {
    453      inner.style.width = inner.style.height = "40px";
    454    };
    455    tweakAndCompareSnapshots(tweakFunc, subtest.desc);
    456 
    457    gridContainer.remove(); // clean up
    458  }
    459 
    460  let tableSubtests = [
    461    { desc: "table",
    462      /* Testing the default "display:table" styling that runTableTest uses: */
    463      candStyle: "",
    464    },
    465    { desc: "inline-table",
    466      candStyle: "display:inline-table;",
    467    },
    468    { desc: "table-caption",
    469      candStyle: "display:table-caption;",
    470    },
    471    { desc: "table-cell",
    472      candStyle: "display:table-cell;",
    473    },
    474    { desc: "table-column",
    475      candStyle: "display:table-column;",
    476      isColumn: true,
    477    },
    478    { desc: "table-column-group",
    479      candStyle: "display:table-column-group;",
    480      isColumn: true,
    481    },
    482    { desc: "table-row",
    483      candStyle: "display:table-row;",
    484    },
    485    { desc: "table-row-group",
    486      candStyle: "display:table-row-group;",
    487    },
    488  ];
    489 
    490  function runTableSubtest(subtest) {
    491    let outer = createStyledDiv("");
    492    let shrinkWrapIB = createStyledDiv("display: inline-block; \
    493                                        border: 2px solid teal");
    494    let cand = createStyledDiv("display: table; \
    495                                width: 1px; height: 1px; \
    496                                will-change: transform; \
    497                                border: 1px solid purple;" +
    498                                subtest.candStyle);
    499    let inner = createStyledDiv("display: block; \
    500                                 width: 10px; height: 10px; \
    501                                 background: pink;");
    502    if (subtest.isColumn) {
    503      // The candidate is a table-column / table-column-group, so
    504      // the inner content that we tweak shouldn't be inside of it.
    505      // Create an explicit table, separately, and put the candidate
    506      // (the column/column-group) and the tweakable inner element
    507      // both inside of that explicit table.
    508      let table = createStyledDiv("display: table");
    509      table.appendChild(inner);
    510      table.appendChild(cand);
    511      shrinkWrapIB.appendChild(table);
    512    } else {
    513      // The candidate is a table or some other table part
    514      // that can hold content. Just put the tweakable inner
    515      // element directly inside of it, and let anonymous table parts
    516      // be generated as-needed.
    517      cand.appendChild(inner);
    518      shrinkWrapIB.appendChild(cand);
    519    }
    520 
    521    outer.appendChild(gFDoc.createTextNode("a"));
    522    outer.appendChild(shrinkWrapIB);
    523    gFBody.appendChild(outer);
    524 
    525    let tweakFunc = function() {
    526      inner.style.width = inner.style.height = "40px";
    527    };
    528    tweakAndCompareSnapshots(tweakFunc, subtest.desc);
    529 
    530    outer.remove(); // clean up
    531  }
    532 
    533  let inlineSubtests = [
    534    { desc: "inline",
    535      candStyle: "display:inline",
    536    },
    537  ];
    538  function runInlineSubtest(subtest) {
    539    let outer = createStyledDiv("");
    540    let shrinkWrapIB = createStyledDiv("display: inline-block; \
    541                                        border: 2px solid teal");
    542    let cand = createStyledDiv(gReflowRootCandidateStyles +
    543                               subtest.candStyle);
    544    let inner = createStyledDiv("display: inline-block; \
    545                                 width: 20px; height: 20px; \
    546                                 background: pink;");
    547 
    548    cand.appendChild(inner);
    549    shrinkWrapIB.appendChild(cand);
    550    outer.appendChild(gFDoc.createTextNode("a"));
    551    outer.appendChild(shrinkWrapIB);
    552    gFBody.appendChild(outer);
    553 
    554    let tweakFunc = function() {
    555      inner.style.width = inner.style.height = "40px";
    556    };
    557    tweakAndCompareSnapshots(tweakFunc, subtest.desc);
    558 
    559    outer.remove(); // clean up
    560  }
    561 
    562  let rubySubtests = [
    563    { desc: "ruby",
    564      candStyle: "display:ruby",
    565    },
    566    { desc: "ruby-base",
    567      candStyle: "display:ruby-base",
    568    },
    569    { desc: "ruby-base-container",
    570      candStyle: "display:ruby-base-container",
    571    },
    572    { desc: "ruby-text",
    573      candStyle: "display:ruby-text",
    574    },
    575    { desc: "ruby-text-container",
    576      candStyle: "display:ruby-text-container",
    577    },
    578  ];
    579 
    580  function runRubySubtest(subtest) {
    581    let outer = createStyledDiv("");
    582    let shrinkWrapIB = createStyledDiv("display: inline-block; \
    583                                        border: 2px solid teal");
    584    let cand = createStyledDiv(gReflowRootCandidateStyles +
    585                               subtest.candStyle);
    586    let inner = createStyledDiv("display: inline-block; \
    587                                 width: 20px; height: 20px; \
    588                                 background: pink;");
    589 
    590    cand.appendChild(inner);
    591    shrinkWrapIB.appendChild(cand);
    592    outer.appendChild(gFDoc.createTextNode("a"));
    593    outer.appendChild(shrinkWrapIB);
    594    gFBody.appendChild(outer);
    595 
    596    let tweakFunc = function() {
    597      inner.style.width = inner.style.height = "40px";
    598    };
    599    tweakAndCompareSnapshots(tweakFunc, subtest.desc);
    600 
    601    outer.remove(); // clean up
    602  }
    603 
    604  function runFixedPosTest() {
    605    // We reset the 'will-change' value on the candidate (overriding
    606    // 'will-change:transform'), so that it won't be a fixed-pos CB. We also
    607    // give the candidate some margins to shift it away from the origin, to
    608    // make it visually clearer that its child's fixed-pos offsets are being
    609    // resolved against the viewport rather than against the candidate div.
    610    let cand = createStyledDiv(gReflowRootCandidateStyles +
    611                               "will-change: initial; \
    612                                margin: 20px 0 0 30px; \
    613                                border: 2px solid black;");
    614 
    615    let inner = createStyledDiv("height: 20px; width: 20px; \
    616                                 background: pink;");
    617    let fixedPos = createStyledDiv("position: fixed; \
    618                                    width: 10px; height: 10px; \
    619                                    background: gray;");
    620 
    621    cand.appendChild(inner);
    622    cand.appendChild(fixedPos);
    623    gFBody.appendChild(cand);
    624 
    625    // For our tweak, we'll adjust the size of "inner". This change impacts
    626    // the position of the "fixedPos" placeholder (specifically, its static
    627    // position), so this will require an incremental reflow that is rooted at
    628    // the viewport (the containing block of "fixedPos") in order to produce
    629    // the correct final layout. This is why "cand" isn't allowed to be a
    630    // reflow root.
    631    let tweakFunc = function() {
    632      inner.style.width = inner.style.height = "40px";
    633    };
    634    tweakAndCompareSnapshots(tweakFunc, "fixed-pos");
    635 
    636    cand.remove(); // clean up
    637  }
    638 
    639  function runMarginCollapseTest() {
    640    let outer = createStyledDiv("background: lime");
    641 
    642    // We use 'display:block' on the candidate (overriding 'display:flow-root')
    643    // so that it won't be a block formatting context. (See usage/definition of
    644    // NS_BLOCK_BFC_STATE_BITS in our c++ layout code.)
    645    let cand = createStyledDiv(gReflowRootCandidateStyles +
    646                               "display: block; \
    647                                background: purple;");
    648    // We'll add border to this div in the "tweak" function, which will break
    649    // the stack of margin collapsed divs.
    650    let divWithEventualBorder = createStyledDiv("");
    651    let divWithMargin = createStyledDiv("margin-top: 30px; \
    652                                         width: 10px; height: 10px; \
    653                                         background: pink;");
    654 
    655    divWithEventualBorder.appendChild(divWithMargin);
    656    cand.appendChild(divWithEventualBorder);
    657    outer.appendChild(cand);
    658    gFBody.appendChild(outer);
    659 
    660    // For our tweak, we'll add a border around "divWithEventualBorder", which
    661    // prevents the margin (on "divWithMargin") from collapsing all the way up
    662    // to the outermost div wrapper (which it does, before the tweak).
    663    // So: this tweak effectively moves the y-position towards 0, for all
    664    // div wrappers outside the new border. This includes "outer", the parent
    665    // of our reflow root candidate. So: if we mistakenly allow "cand" to be a
    666    // reflow root, then we probably would neglect to adjust the position of
    667    // "outer" when reacting to this tweak (and we'd catch that & report a
    668    // test failure in our screenshot comparisons below).
    669    let tweakFunc = function() {
    670      divWithEventualBorder.style.border = "2px solid black";
    671    };
    672    tweakAndCompareSnapshots(tweakFunc, "margin-uncollapse");
    673 
    674    outer.remove(); // clean up
    675  }
    676 
    677  function runFloatTest() {
    678    let outer = createStyledDiv("");
    679 
    680    // We use 'display:block' on the candidate (overriding 'display:flow-root')
    681    // so that it won't be a block formatting context. (See usage/definition of
    682    // NS_BLOCK_BFC_STATE_BITS in our c++ layout code.)
    683    // This allows floats inside the candidate to affect the position of
    684    // inline-level content outside of it.
    685    let cand = createStyledDiv(gReflowRootCandidateStyles +
    686                               "display: block; \
    687                                border: 2px solid black;");
    688    let floatChild = createStyledDiv("float: left; \
    689                                     width: 60px; height: 60px; \
    690                                     background: pink;");
    691    let inlineBlock = createStyledDiv("display: inline-block; \
    692                                       width: 80px; height: 80px; \
    693                                       background: teal");
    694    cand.appendChild(floatChild);
    695    outer.appendChild(cand);
    696    outer.appendChild(inlineBlock);
    697    gFBody.appendChild(outer);
    698 
    699    let tweakFunc = function() {
    700      floatChild.style.width = floatChild.style.height = "40px";
    701    };
    702    tweakAndCompareSnapshots(tweakFunc, "float");
    703 
    704    outer.remove(); // clean up
    705  }
    706 
    707  function main() {
    708    SimpleTest.waitForExplicitFinish();
    709 
    710    // Initialize our convenience aliases:
    711    gFWindow = frames[0].window;
    712    gFDoc = frames[0].document;
    713    gFBody = frames[0].document.body;
    714 
    715    for (let subtest of intrinsicSizeSubtests) {
    716      runIntrinsicSizeSubtest(subtest);
    717    }
    718    for (let subtest of flexItemSubtests) {
    719      runFlexItemSubtest(subtest);
    720    }
    721    for (let subtest of gridContainerSubtests) {
    722      runGridContainerSubtest(subtest);
    723    }
    724    for (let subtest of gridSubgridSubtests) {
    725      runGridSubgridSubtest(subtest);
    726    }
    727    for (let subtest of gridItemSubtests) {
    728      runGridItemSubtest(subtest);
    729    }
    730    for (let subtest of tableSubtests) {
    731      runTableSubtest(subtest);
    732    }
    733    for (let subtest of inlineSubtests) {
    734      runInlineSubtest(subtest);
    735    }
    736    for (let subtest of rubySubtests) {
    737      runRubySubtest(subtest);
    738    }
    739    runFixedPosTest();
    740    runMarginCollapseTest();
    741    runFloatTest();
    742 
    743    SimpleTest.finish();
    744  }
    745 </script>
    746 </body>
    747 </html>