tor-browser

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

test_dynamic_change_causing_reflow.html (27966B)


      1 <!DOCTYPE HTML>
      2 <html>
      3 <!--
      4 https://bugzilla.mozilla.org/show_bug.cgi?id=1131371
      5 -->
      6 <head>
      7  <meta charset="utf-8">
      8  <title>Test for Bug 1131371</title>
      9  <script src="/tests/SimpleTest/SimpleTest.js"></script>
     10  <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
     11 </head>
     12 <body>
     13 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1131371">Mozilla Bug 1131371</a>
     14 <style>
     15  #elemWithScrollbars { overflow: scroll }
     16  .flexScrollerWithTransformedItems {
     17    display: flex;
     18    overflow: hidden;
     19    width: 200px;
     20    position: relative;
     21  }
     22  .flexScrollerWithTransformedItems div {
     23    min-width: 50px;
     24    min-height: 50px;
     25    margin: 10px;
     26    will-change: transform;
     27  }
     28  .absposFlexItem {
     29    left: 250px;
     30    top: 0;
     31    position: absolute;
     32  }
     33  #tableCell,
     34  #tableCellWithAbsPosChild {
     35    display: table-cell;
     36  }
     37  .blockmargin {
     38    margin: 25px 0;
     39  }
     40 </style>
     41 <div id="display">
     42  <div id="content">
     43  </div>
     44  <div id="elemWithAbsPosChild"><div style="position:absolute"></div></div>
     45  <div id="elemWithFixedPosChild"><div style="position:fixed"></div></div>
     46  <div id="elemWithScrollbars"></div>
     47  <div id="elemWithoutScrollbars"></div>
     48  <div class="flexScrollerWithTransformedItems">
     49    <div></div>
     50    <div></div>
     51    <div></div>
     52    <div></div>
     53    <div id="flexItemMovementTarget"></div>
     54  </div>
     55  <div class="flexScrollerWithTransformedItems">
     56    <div></div>
     57    <div></div>
     58    <div></div>
     59    <div id="flexItemMovementTarget2"></div>
     60    <div class="absposFlexItem"></div>
     61  </div>
     62  <input id="inputElem" type="image">
     63  <textarea id="textareaElem">
     64    Some
     65    lines
     66    of
     67    text
     68  </textarea>
     69  <select id="selectElem">
     70    <option>A</option>
     71    <option>B</option>
     72    <option>C</option>
     73  </select>
     74  <button id="buttonElem">
     75    Something
     76  </button>
     77  <button id="buttonElemWithAbsPosChild"><div style="position:absolute"></div></button>
     78  <div id="tableCell"></div>
     79  <div id="tableCellWithAbsPosChild">
     80    <div style="position: absolute"></div>
     81  </div>
     82  <div id="containNode">
     83    <div></div>
     84  </div>
     85  <div id="containNodeWithAbsPosChild">
     86    <div style="position: absolute"></div>
     87  </div>
     88  <div id="containNodeWithFixedPosChild">
     89    <div style="position: fixed"></div>
     90  </div>
     91  <div id="containNodeWithFloatingChild">
     92    <div style="float: left"></div>
     93  </div>
     94  <div id="containNodeWithMarginCollapsing" class="blockmargin">
     95    <div class="blockmargin"></div>
     96  </div>
     97  <div id="contentVisibilityNode">
     98    <div></div>
     99  </div>
    100 </div>
    101 <pre id="test">
    102 <script>
    103 "use strict";
    104 
    105 /** Test for Bug 1131371 */
    106 
    107 const elemWithAbsPosChild = document.getElementById("elemWithAbsPosChild");
    108 const elemWithFixedPosChild = document.getElementById("elemWithFixedPosChild");
    109 const elemWithScrollbars = document.getElementById("elemWithScrollbars");
    110 const elemWithoutScrollbars = document.getElementById("elemWithoutScrollbars");
    111 const inputElem = document.getElementById("inputElem");
    112 const textareaElem = document.getElementById("textareaElem");
    113 const selectElem = document.getElementById("selectElem");
    114 const buttonElem = document.getElementById("buttonElem");
    115 const buttonElemWithAbsPosChild = document.getElementById("buttonElemWithAbsPosChild");
    116 const tableCell = document.getElementById("tableCell");
    117 const tableCellWithAbsPosChild = document.getElementById("tableCellWithAbsPosChild");
    118 
    119 for (let scroller of document.querySelectorAll(".flexScrollerWithTransformedItems")) {
    120  scroller.scrollLeft = 10000;
    121 }
    122 
    123 const flexItemMovementTarget = document.getElementById("flexItemMovementTarget");
    124 const flexItemMovementTarget2 = document.getElementById("flexItemMovementTarget2");
    125 
    126 /**
    127 * This test verifies that certain style changes do or don't cause reflow
    128 * and/or frame construction. We do this by checking the framesReflowed &
    129 * framesConstructed counts, before & after a style-change, and verifying
    130 * that any change to these counts is in line with our expectations.
    131 *
    132 * Each entry in gTestcases contains these member-values:
    133 *   - beforeStyle (optional): initial value to use for "style" attribute.
    134 *   - afterStyle: value to change the "style" attribute to.
    135 *
    136 * Testcases may also include two optional member-values to express that reflow
    137 * and/or frame construction *are* in fact expected:
    138 *   - expectConstruction (optional): if set to something truthy, then we expect
    139 *      frame construction to occur when afterStyle is set. Otherwise, we
    140 *      expect that frame construction should *not* occur.
    141 *   - expectReflow (optional): if set to something truthy, then we expect
    142 *      reflow to occur when afterStyle is set. Otherwise, we expect that
    143 *      reflow should *not* occur.
    144 */
    145 const gTestcases = [
    146  // Things that shouldn't cause reflow:
    147  // -----------------------------------
    148  // * Adding an outline (e.g. for focus ring).
    149  {
    150    afterStyle:  "outline: 1px dotted black",
    151  },
    152 
    153  // * Changing between completely different outlines.
    154  {
    155    beforeStyle: "outline: 2px solid black",
    156    afterStyle:  "outline: 6px dashed yellow",
    157  },
    158 
    159  // * Adding a box-shadow.
    160  {
    161    afterStyle: "box-shadow: inset 3px 3px gray",
    162  },
    163  {
    164    afterStyle: "box-shadow: 0px 0px 10px 30px blue"
    165  },
    166 
    167  // * Changing between completely different box-shadow values,
    168  // e.g. from an upper-left shadow to a bottom-right shadow:
    169  {
    170    beforeStyle: "box-shadow: -15px -20px teal",
    171    afterStyle:  "box-shadow:  30px  40px yellow",
    172  },
    173 
    174  // * Adding a text-shadow.
    175  {
    176    afterStyle: "text-shadow: 3px 3px gray",
    177  },
    178  {
    179    afterStyle: "text-shadow: 0px 0px 10px blue"
    180  },
    181 
    182  // * Changing between completely different text-shadow values,
    183  // e.g. from an upper-left shadow to a bottom-right shadow:
    184  {
    185    beforeStyle: "text-shadow: -15px -20px teal",
    186    afterStyle:  "text-shadow:  30px  40px yellow",
    187  },
    188 
    189  // * switching overflow between things that shouldn't create scrollframes.
    190  {
    191    beforeStyle: "overflow: visible",
    192    afterStyle: "overflow: clip",
    193  },
    194 
    195  // Things that *should* cause reflow:
    196  // ----------------------------------
    197  // (e.g. to make sure our counts are actually measuring something)
    198 
    199  // * Changing 'height' should cause reflow, but not frame construction.
    200  {
    201    beforeStyle: "height: 10px",
    202    afterStyle:  "height: 15px",
    203    expectReflow: true,
    204  },
    205 
    206  // * Changing 'shape-outside' on a non-floating box should not cause anything to happen.
    207  {
    208    beforeStyle: "shape-outside: none",
    209    afterStyle:  "shape-outside: circle()",
    210  },
    211 
    212  // * Changing 'shape-outside' should cause reflow, but not frame construction.
    213  {
    214    beforeStyle: "float: left; shape-outside: none",
    215    afterStyle:  "float: left; shape-outside: circle()",
    216    expectReflow: true,
    217  },
    218 
    219  // * Changing 'overflow' on <body> should cause reflow,
    220  // but not frame reconstruction
    221  {
    222    elem: document.body,
    223    /* beforeStyle: implicitly 'overflow:visible' */
    224    afterStyle:  "overflow: hidden",
    225    expectConstruction: false,
    226    expectReflow: true,
    227  },
    228  {
    229    elem: document.body,
    230    /* beforeStyle: implicitly 'overflow:visible' */
    231    afterStyle:  "overflow: scroll",
    232    expectConstruction: false,
    233    expectReflow: true,
    234  },
    235  {
    236    elem: document.body,
    237    beforeStyle: "overflow: hidden",
    238    afterStyle:  "overflow: auto",
    239    expectConstruction: false,
    240    expectReflow: true,
    241  },
    242  {
    243    elem: document.body,
    244    beforeStyle: "overflow: hidden",
    245    afterStyle:  "overflow: scroll",
    246    expectConstruction: false,
    247    expectReflow: true,
    248  },
    249  {
    250    elem: document.body,
    251    beforeStyle: "overflow: hidden",
    252    afterStyle:  "overflow: visible",
    253    expectConstruction: false,
    254    expectReflow: true,
    255  },
    256  {
    257    elem: document.body,
    258    beforeStyle: "overflow: auto",
    259    afterStyle:  "overflow: hidden",
    260    expectConstruction: false,
    261    expectReflow: true,
    262  },
    263  {
    264    elem: document.body,
    265    beforeStyle: "overflow: visible",
    266    afterStyle:  "overflow: hidden",
    267    expectConstruction: false,
    268    expectReflow: true,
    269  },
    270 
    271  // * Changing 'overflow' on <html> should cause reflow,
    272  // but not frame reconstruction
    273  {
    274    elem: document.documentElement,
    275    /* beforeStyle: implicitly 'overflow:visible' */
    276    afterStyle:  "overflow: auto",
    277    expectConstruction: false,
    278    expectReflow: true,
    279  },
    280  {
    281    elem: document.documentElement,
    282    beforeStyle: "overflow: visible",
    283    afterStyle:  "overflow: auto",
    284    expectConstruction: false,
    285    expectReflow: true,
    286  },
    287 
    288  // * Setting 'overflow' on arbitrary node should cause reflow as well as
    289  // frame reconstruction
    290  {
    291    /* beforeStyle: implicitly 'overflow:visible' */
    292    afterStyle:  "overflow: auto",
    293    expectConstruction: true,
    294    expectReflow: true,
    295  },
    296  {
    297    beforeStyle: "overflow: auto",
    298    afterStyle:  "overflow: visible",
    299    expectConstruction: true,
    300    expectReflow: true,
    301  },
    302 
    303  // * but only reflow if we don't need to construct / unconstruct a new frame.
    304  {
    305    beforeStyle: "overflow: scroll",
    306    afterStyle:  "overflow: auto",
    307    expectConstruction: false,
    308    expectReflow: true,
    309  },
    310  {
    311    beforeStyle: "overflow: auto",
    312    afterStyle:  "overflow: scroll",
    313    expectConstruction: false,
    314    expectReflow: true,
    315  },
    316 
    317  {
    318    beforeStyle: "overflow: hidden",
    319    afterStyle:  "overflow: auto",
    320    expectConstruction: true,
    321    expectReflow: true,
    322  },
    323  {
    324    beforeStyle: "overflow: auto",
    325    afterStyle:  "overflow: hidden",
    326    expectConstruction: false,
    327    expectReflow: true,
    328  },
    329  {
    330    beforeStyle: "overflow: hidden",
    331    afterStyle:  "overflow: scroll",
    332    expectConstruction: true,
    333    expectReflow: true,
    334  },
    335  {
    336    beforeStyle: "overflow: scroll",
    337    afterStyle:  "overflow: hidden",
    338    expectConstruction: false,
    339    expectReflow: true,
    340  },
    341 
    342  {
    343    elem: elemWithoutScrollbars,
    344    beforeStyle: "scrollbar-width: auto",
    345    afterStyle:  "scrollbar-width: none",
    346    expectConstruction: false,
    347    expectReflow: false,
    348  },
    349  {
    350    elem: elemWithScrollbars,
    351    beforeStyle: "scrollbar-width: auto",
    352    afterStyle:  "scrollbar-width: none",
    353    expectConstruction: false,
    354    expectReflow: true,
    355  },
    356  {
    357    // Not the scrolling element, so nothing should happen.
    358    elem: document.body,
    359    beforeStyle: "scrollbar-width: none",
    360    afterStyle:  "scrollbar-width: auto",
    361    expectConstruction: false,
    362    expectReflow: false,
    363  },
    364  {
    365    // Not the scrolling element, so nothing should happen.
    366    elem: document.body,
    367    beforeStyle: "scrollbar-width: auto",
    368    afterStyle:  "scrollbar-width: none",
    369    expectConstruction: false,
    370    expectReflow: false,
    371  },
    372  {
    373    elem: document.documentElement,
    374    beforeStyle: "scrollbar-width: none;",
    375    afterStyle:  "scrollbar-width: auto",
    376    expectConstruction: false,
    377    expectReflow: true,
    378  },
    379  {
    380    elem: document.documentElement,
    381    beforeStyle: "overflow: scroll; scrollbar-width: none;",
    382    afterStyle:  "overflow: scroll; scrollbar-width: auto",
    383    expectConstruction: false,
    384    expectReflow: true,
    385  },
    386  {
    387    elem: document.documentElement,
    388    beforeStyle: "overflow: scroll; scrollbar-width: auto;",
    389    afterStyle:  "overflow: scroll; scrollbar-width: none",
    390    expectConstruction: false,
    391    expectReflow: true,
    392  },
    393 
    394  // * Changing 'display' should cause frame construction and reflow.
    395  {
    396    beforeStyle: "display: inline",
    397    afterStyle:  "display: table",
    398    expectConstruction: true,
    399    expectReflow: true,
    400  },
    401 
    402 
    403  // * Position changes trigger a reframe, unless whether we're a containing
    404  // block doesn't change, in which case we just need to reflow.
    405  {
    406    beforeStyle: "position: static",
    407    afterStyle:  "position: absolute",
    408    expectConstruction: true,
    409    expectReflow: true,
    410  },
    411  {
    412    beforeStyle:  "position: absolute",
    413    afterStyle: "position: fixed",
    414    expectConstruction: true,
    415    expectReflow: true,
    416  },
    417  {
    418    beforeStyle:  "position: relative",
    419    afterStyle: "position: fixed",
    420    expectConstruction: true,
    421    expectReflow: true,
    422  },
    423 
    424  // This doesn't change whether we're a containing block because there are no
    425  // abspos descendants.
    426  {
    427    afterStyle: "position: static",
    428    beforeStyle:  "position: relative",
    429    expectReflow: true,
    430  },
    431 
    432  // This doesn't change whether we're a containing block, shouldn't reframe.
    433  {
    434    afterStyle: "position: sticky",
    435    beforeStyle:  "position: relative",
    436    expectReflow: true,
    437  },
    438 
    439  // These don't change whether we're a containing block for our
    440  // absolutely-positioned child, so shouldn't reframe.
    441  {
    442    elem: elemWithAbsPosChild,
    443    afterStyle: "position: sticky",
    444    beforeStyle:  "position: relative",
    445    expectReflow: true,
    446  },
    447  {
    448    elem: elemWithFixedPosChild,
    449    afterStyle: "position: sticky",
    450    beforeStyle:  "position: relative",
    451    expectReflow: true,
    452  },
    453  {
    454    elem: elemWithFixedPosChild,
    455    afterStyle: "position: static",
    456    beforeStyle:  "position: relative",
    457    expectReflow: true,
    458  },
    459  {
    460    elem: elemWithFixedPosChild,
    461    afterStyle: "position: static",
    462    beforeStyle:  "position: sticky",
    463    expectReflow: true,
    464  },
    465  {
    466    // Even if we're a scroll frame.
    467    elem: elemWithFixedPosChild,
    468    afterStyle: "position: static; overflow: auto;",
    469    beforeStyle:  "position: relative; overflow: auto;",
    470    expectReflow: true,
    471  },
    472  {
    473    elem: tableCell,
    474    afterStyle: "position: static;",
    475    beforeStyle:  "position: relative;",
    476    expectReflow: true,
    477  },
    478  {
    479    elem: tableCell,
    480    afterStyle: "filter: none",
    481    beforeStyle:  "filter: saturate(1)",
    482    expectReflow: false,
    483  },
    484 
    485  // These ones do though.
    486  {
    487    elem: elemWithAbsPosChild,
    488    afterStyle: "position: static",
    489    beforeStyle:  "position: relative",
    490    expectConstruction: true,
    491    expectReflow: true,
    492  },
    493  {
    494    elem: elemWithAbsPosChild,
    495    afterStyle: "position: static",
    496    beforeStyle:  "position: sticky",
    497    expectConstruction: true,
    498    expectReflow: true,
    499  },
    500  {
    501    elem: elemWithAbsPosChild,
    502    afterStyle: "position: static; overflow: auto;",
    503    beforeStyle:  "position: relative; overflow: auto;",
    504    expectConstruction: true,
    505    expectReflow: true,
    506  },
    507  {
    508    elem: tableCellWithAbsPosChild,
    509    afterStyle: "position: static;",
    510    beforeStyle:  "position: relative;",
    511    expectConstruction: true,
    512    expectReflow: true,
    513  },
    514 
    515  // Adding transform to a scrollframe without abspos / fixedpos children shouldn't reframe.
    516  {
    517    elem: elemWithScrollbars,
    518    afterStyle: "transform: translateX(1px)",
    519    expectConstruction: false,
    520    expectReflow: false,
    521  },
    522 
    523  // <select> can't contain abspos / floating children so shouldn't reframe
    524  // when changing containing block-ness.
    525  {
    526    elem: selectElem,
    527    afterStyle: "transform: translateX(1px)",
    528    expectConstruction: false,
    529    expectReflow: false,
    530  },
    531  {
    532    elem: selectElem,
    533    afterStyle: "position: relative",
    534    expectConstruction: false,
    535    expectReflow: true,
    536  },
    537 
    538  // <button> shouldn't be reframed either in the absence of positioned descendants.
    539  {
    540    elem: buttonElem,
    541    afterStyle: "transform: translateX(1px)",
    542    expectConstruction: false,
    543    expectReflow: false,
    544  },
    545  {
    546    elem: buttonElem,
    547    afterStyle: "position: relative",
    548    expectConstruction: false,
    549    expectReflow: true,
    550  },
    551  {
    552    elem: buttonElemWithAbsPosChild,
    553    afterStyle: "position: relative",
    554    expectConstruction: true,
    555    expectReflow: true,
    556  },
    557  // changing scroll-behavior should not cause reflow or frame construction
    558  {
    559    elem: document.documentElement,
    560    /* beforeStyle: implicitly 'scroll-behavior: auto' */
    561    afterStyle:  "scroll-behavior: smooth",
    562    expectConstruction: false,
    563    expectReflow: false,
    564  },
    565  {
    566    elem: document.documentElement,
    567    beforeStyle: "scroll-behavior: smooth",
    568    afterStyle:  "scroll-behavior: auto",
    569    expectConstruction: false,
    570    expectReflow: false,
    571  },
    572  {
    573    elem: document.body,
    574    /* beforeStyle: implicitly 'scroll-behavior: auto' */
    575    afterStyle:  "scroll-behavior: smooth",
    576    expectConstruction: false,
    577    expectReflow: false,
    578  },
    579  {
    580    elem: document.body,
    581    beforeStyle: "scroll-behavior: smooth",
    582    afterStyle:  "scroll-behavior: auto",
    583    expectConstruction: false,
    584    expectReflow: false,
    585  },
    586  // changing scroll-snap-type should not cause reflow or frame construction
    587  {
    588    elem: document.documentElement,
    589    /* beforeStyle: implicitly 'scroll-snap-type: none' */
    590    afterStyle:  "scroll-snap-type: y mandatory",
    591    expectConstruction: false,
    592    expectReflow: false,
    593  },
    594  {
    595    elem: document.documentElement,
    596    /* beforeStyle: implicitly 'scroll-snap-type: none' */
    597    afterStyle:  "scroll-snap-type: x proximity",
    598    expectConstruction: false,
    599    expectReflow: false,
    600  },
    601  {
    602    elem: document.documentElement,
    603    beforeStyle: "scroll-snap-type: y mandatory",
    604    afterStyle:  "scroll-snap-type: none",
    605    expectConstruction: false,
    606    expectReflow: false,
    607  },
    608  {
    609    elem: document.body,
    610    /* beforeStyle: implicitly 'scroll-snap-type: none' */
    611    afterStyle:  "scroll-snap-type: y mandatory",
    612    expectConstruction: false,
    613    expectReflow: false,
    614  },
    615  {
    616    elem: document.body,
    617    /* beforeStyle: implicitly 'scroll-snap-type: none' */
    618    afterStyle:  "scroll-snap-type: x proximity",
    619    expectConstruction: false,
    620    expectReflow: false,
    621  },
    622  {
    623    elem: document.body,
    624    beforeStyle: "scroll-snap-type: y mandatory",
    625    afterStyle:  "scroll-snap-type: none",
    626    expectConstruction: false,
    627    expectReflow: false,
    628  },
    629  {
    630    elem: inputElem,
    631    beforeStyle: "overflow: auto",
    632    afterStyle:  "overflow: hidden",
    633    expectConstruction: false,
    634    expectReflow: true,
    635  },
    636  {
    637    elem: textareaElem,
    638    beforeStyle: "overflow: auto",
    639    afterStyle:  "overflow: hidden",
    640    expectConstruction: false,
    641    expectReflow: true,
    642  },
    643  {
    644    elem: flexItemMovementTarget,
    645    beforeStyle: "transform: translateX(0)",
    646    afterStyle: "transform: translateX(-100px)",
    647    expectConstruction: false,
    648    expectReflow: false,
    649  },
    650  {
    651    elem: flexItemMovementTarget2,
    652    beforeStyle: "transform: translateX(0)",
    653    afterStyle: "transform: translateX(-100px)",
    654    expectConstruction: false,
    655    expectReflow: false,
    656  },
    657  // Style containment affects counters so we need to re-frame.
    658  // For other containments, we only need to reflow.
    659  {
    660    elem: containNode,
    661    beforeStyle: "contain: none",
    662    afterStyle: "contain: style",
    663    expectConstruction: true,
    664    expectReflow: true,
    665  },
    666  {
    667    elem: containNode,
    668    beforeStyle: "contain: style",
    669    afterStyle: "contain: none",
    670    expectConstruction: true,
    671    expectReflow: true,
    672  },
    673  {
    674    elem: containNode,
    675    beforeStyle: "contain: none",
    676    afterStyle: "contain: paint",
    677    expectConstruction: false,
    678    expectReflow: true,
    679  },
    680  {
    681    elem: containNode,
    682    beforeStyle: "contain: paint",
    683    afterStyle: "contain: none",
    684    expectConstruction: false,
    685    expectReflow: true,
    686  },
    687  {
    688    elem: containNode,
    689    beforeStyle: "contain: none",
    690    afterStyle: "contain: layout",
    691    expectConstruction: false,
    692    expectReflow: true,
    693  },
    694  {
    695    elem: containNode,
    696    beforeStyle: "contain: layout",
    697    afterStyle: "contain: none",
    698    expectConstruction: false,
    699    expectReflow: true,
    700  },
    701  {
    702    elem: containNode,
    703    beforeStyle: "contain: none",
    704    afterStyle: "contain: size",
    705    expectConstruction: false,
    706    expectReflow: true,
    707  },
    708  {
    709    elem: containNode,
    710    beforeStyle: "contain: size",
    711    afterStyle: "contain: none",
    712    expectConstruction: false,
    713    expectReflow: true,
    714  },
    715  // paint/layout containment boxes establish an absolute positioning
    716  // containing block, so need a re-frame to handle abs/fixed positioned boxes.
    717  {
    718    elem: containNodeWithAbsPosChild,
    719    beforeStyle: "contain: none",
    720    afterStyle: "contain: paint",
    721    expectConstruction: true,
    722    expectReflow: true,
    723  },
    724  {
    725    elem: containNodeWithAbsPosChild,
    726    beforeStyle: "contain: paint",
    727    afterStyle: "contain: none",
    728    expectConstruction: true,
    729    expectReflow: true,
    730  },
    731  {
    732    elem: containNodeWithAbsPosChild,
    733    beforeStyle: "contain: none",
    734    afterStyle: "contain: layout",
    735    expectConstruction: true,
    736    expectReflow: true,
    737  },
    738  {
    739    elem: containNodeWithAbsPosChild,
    740    beforeStyle: "contain: layout",
    741    afterStyle: "contain: none",
    742    expectConstruction: true,
    743    expectReflow: true,
    744  },
    745  {
    746    elem: containNodeWithAbsPosChild,
    747    beforeStyle: "contain: none",
    748    afterStyle: "contain: size",
    749    expectConstruction: false,
    750    expectReflow: true,
    751  },
    752  {
    753    elem: containNodeWithAbsPosChild,
    754    beforeStyle: "contain: size",
    755    afterStyle: "contain: none",
    756    expectConstruction: false,
    757    expectReflow: true,
    758  },
    759  {
    760    elem: containNodeWithFixedPosChild,
    761    beforeStyle: "contain: none",
    762    afterStyle: "contain: paint",
    763    expectConstruction: true,
    764    expectReflow: true,
    765  },
    766  {
    767    elem: containNodeWithFixedPosChild,
    768    beforeStyle: "contain: paint",
    769    afterStyle: "contain: none",
    770    expectConstruction: true,
    771    expectReflow: true,
    772  },
    773  {
    774    elem: containNodeWithFixedPosChild,
    775    beforeStyle: "contain: none",
    776    afterStyle: "contain: layout",
    777    expectConstruction: true,
    778    expectReflow: true,
    779  },
    780  {
    781    elem: containNodeWithFixedPosChild,
    782    beforeStyle: "contain: layout",
    783    afterStyle: "contain: none",
    784    expectConstruction: true,
    785    expectReflow: true,
    786  },
    787  {
    788    elem: containNodeWithFixedPosChild,
    789    beforeStyle: "contain: none",
    790    afterStyle: "contain: size",
    791    expectConstruction: false,
    792    expectReflow: true,
    793  },
    794  {
    795    elem: containNodeWithFixedPosChild,
    796    beforeStyle: "contain: size",
    797    afterStyle: "contain: none",
    798    expectConstruction: false,
    799    expectReflow: true,
    800  },
    801  // paint/layout containment boxes establish an independent formatting context
    802  // but floats and margin collapsing can be handled without reconstruction.
    803  {
    804    elem: containNodeWithFloatingChild,
    805    beforeStyle: "contain: none",
    806    afterStyle: "contain: paint",
    807    expectConstruction: false,
    808    expectReflow: true,
    809  },
    810  {
    811    elem: containNodeWithFloatingChild,
    812    beforeStyle: "contain: paint",
    813    afterStyle: "contain: none",
    814    expectConstruction: false,
    815    expectReflow: true,
    816  },
    817  {
    818    elem: containNodeWithFloatingChild,
    819    beforeStyle: "contain: none",
    820    afterStyle: "contain: layout",
    821    expectConstruction: false,
    822    expectReflow: true,
    823  },
    824  {
    825    elem: containNodeWithFloatingChild,
    826    beforeStyle: "contain: layout",
    827    afterStyle: "contain: none",
    828    expectConstruction: false,
    829    expectReflow: true,
    830  },
    831  {
    832    elem: containNodeWithFloatingChild,
    833    beforeStyle: "contain: none",
    834    afterStyle: "contain: size",
    835    expectConstruction: false,
    836    expectReflow: true,
    837  },
    838  {
    839    elem: containNodeWithFloatingChild,
    840    beforeStyle: "contain: size",
    841    afterStyle: "contain: none",
    842    expectConstruction: false,
    843    expectReflow: true,
    844  },
    845  {
    846    elem: containNodeWithMarginCollapsing,
    847    beforeStyle: "contain: none",
    848    afterStyle: "contain: paint",
    849    expectConstruction: false,
    850    expectReflow: true,
    851  },
    852  {
    853    elem: containNodeWithMarginCollapsing,
    854    beforeStyle: "contain: paint",
    855    afterStyle: "contain: none",
    856    expectConstruction: false,
    857    expectReflow: true,
    858  },
    859  {
    860    elem: containNodeWithMarginCollapsing,
    861    beforeStyle: "contain: none",
    862    afterStyle: "contain: layout",
    863    expectConstruction: false,
    864    expectReflow: true,
    865  },
    866  {
    867    elem: containNodeWithMarginCollapsing,
    868    beforeStyle: "contain: layout",
    869    afterStyle: "contain: none",
    870    expectConstruction: false,
    871    expectReflow: true,
    872  },
    873  // content-visibility: auto/hidden implies style containment contrary to
    874  // content-visibility: visible, so  we generally need a re-frame (see above)
    875  // when going from one case to the other.
    876  {
    877    elem: contentVisibilityNode,
    878    beforeStyle: "content-visibility: visible",
    879    afterStyle: "content-visibility: hidden",
    880    expectConstruction: true,
    881    expectReflow: true,
    882  },
    883  {
    884    elem: contentVisibilityNode,
    885    beforeStyle: "content-visibility: hidden",
    886    afterStyle: "content-visibility: visible",
    887    expectConstruction: true,
    888    expectReflow: true,
    889  },
    890  {
    891    elem: contentVisibilityNode,
    892    beforeStyle: "content-visibility: visible",
    893    afterStyle: "content-visibility: auto",
    894    expectConstruction: true,
    895    expectReflow: true,
    896  },
    897  {
    898    elem: contentVisibilityNode,
    899    beforeStyle: "content-visibility: auto",
    900    afterStyle: "content-visibility: visible",
    901    expectConstruction: true,
    902    expectReflow: true,
    903  },
    904  {
    905    elem: contentVisibilityNode,
    906    beforeStyle: "content-visibility: hidden",
    907    afterStyle: "content-visibility: auto",
    908    expectConstruction: false,
    909    expectReflow: true,
    910  },
    911  {
    912    elem: contentVisibilityNode,
    913    beforeStyle: "content-visibility: auto",
    914    afterStyle: "content-visibility: hidden",
    915    expectConstruction: false,
    916    expectReflow: true,
    917  },
    918  // However that's not the case if we force style containment explicitly.
    919  {
    920    elem: contentVisibilityNode,
    921    beforeStyle: "content-visibility: visible; contain: style",
    922    afterStyle: "content-visibility: hidden; contain: style",
    923    expectConstruction: false,
    924    expectReflow: true,
    925  },
    926  {
    927    elem: contentVisibilityNode,
    928    beforeStyle: "content-visibility: hidden; contain: style",
    929    afterStyle: "content-visibility: visible; contain: style",
    930    expectConstruction: false,
    931    expectReflow: true,
    932  },
    933 ];
    934 
    935 // Helper function to let us call either "is" or "isnot" & assemble
    936 // the failure message, based on the provided parameters.
    937 function checkFinalCount(aFinalCount, aExpectedCount,
    938                         aExpectChange, aMsgPrefix, aCountDescription)
    939 {
    940  let compareFunc;
    941  let msg = aMsgPrefix;
    942  if (aExpectChange) {
    943    compareFunc = isnot;
    944    msg += "should cause " + aCountDescription;
    945  } else {
    946    compareFunc = is;
    947    msg += "should not cause " + aCountDescription;
    948  }
    949 
    950  compareFunc(aFinalCount, aExpectedCount, msg);
    951 }
    952 
    953 // Vars used in runOneTest that we really only have to look up once:
    954 const gUtils = SpecialPowers.getDOMWindowUtils(window);
    955 const gElem = document.getElementById("content");
    956 
    957 function runOneTest(aTestcase)
    958 {
    959  // sanity-check that we have the one main thing we need:
    960  if (!aTestcase.afterStyle) {
    961    ok(false, "testcase is missing an 'afterStyle' to change to");
    962    return;
    963  }
    964 
    965  // Figure out which element we'll be tweaking (defaulting to gElem)
    966  let elem = aTestcase.elem ? aTestcase.elem : gElem;
    967 
    968  // Verify that 'style' attribute is unset (avoid causing ourselves trouble):
    969  const oldStyle = elem.getAttribute("style");
    970 
    971  // Set the "before" style, and compose the first part of the message
    972  // to be used in our "is"/"isnot" invocations:
    973  let msgPrefix = "Changing style ";
    974  if (aTestcase.beforeStyle) {
    975    elem.setAttribute("style", aTestcase.beforeStyle);
    976    msgPrefix += "from '" + aTestcase.beforeStyle + "' ";
    977  }
    978  msgPrefix += "to '" + aTestcase.afterStyle + "' ";
    979  msgPrefix += "on " + elem.nodeName + " ";
    980 
    981  // Establish initial counts:
    982  let unusedVal = elem.offsetHeight; // flush layout
    983  let origFramesConstructed = gUtils.framesConstructed;
    984  let origFramesReflowed = gUtils.framesReflowed;
    985 
    986  // Make the change and flush:
    987  elem.setAttribute("style", aTestcase.afterStyle);
    988  unusedVal = elem.offsetHeight; // flush layout
    989 
    990  // Make our is/isnot assertions about whether things should have changed:
    991  checkFinalCount(gUtils.framesConstructed, origFramesConstructed,
    992                  aTestcase.expectConstruction, msgPrefix,
    993                  "frame construction");
    994  checkFinalCount(gUtils.framesReflowed, origFramesReflowed,
    995                  aTestcase.expectReflow, msgPrefix,
    996                  "reflow");
    997 
    998  // Clean up!
    999  if (oldStyle) {
   1000    elem.setAttribute("style", oldStyle);
   1001  } else {
   1002    elem.removeAttribute("style");
   1003  }
   1004 
   1005  unusedVal = elem.offsetHeight; // flush layout
   1006 }
   1007 
   1008 gTestcases.forEach(runOneTest);
   1009 
   1010 </script>
   1011 </pre>
   1012 </body>
   1013 </html>