test_flexbox_reflow_counts.html (6305B)
1 <!DOCTYPE HTML> 2 <html> 3 <!-- 4 https://bugzilla.mozilla.org/show_bug.cgi?id=1142686 5 --> 6 <head> 7 <meta charset="utf-8"> 8 <title>Test for Bug 1142686</title> 9 <script src="/tests/SimpleTest/SimpleTest.js"></script> 10 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> 11 <style> 12 .flex { 13 display: flex; 14 } 15 #outerFlex { 16 border: 1px solid black; 17 } 18 </style> 19 </head> 20 <body> 21 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1142686">Mozilla Bug 1142686</a> 22 <div id="display"> 23 <div id="content"> 24 <div class="flex" id="outerFlex"> 25 <div class="flex" id="midFlex"> 26 <div id="innerBlock"> 27 </div> 28 </div> 29 </div> 30 </div> 31 </div> 32 <pre id="test"> 33 <script type="application/javascript"> 34 "use strict"; 35 36 /** Test for Bug 1142686 */ 37 38 /** 39 * This test checks how many reflows are required, when we make a change inside 40 * a set of two nested flex containers, with various styles applied to 41 * the containers & innermost child. Some flex layout operations require two 42 * passes (which can cause exponential blowup). This test is intended to verify 43 * that certain configurations do *not* require two-pass layout, by comparing 44 * the reflow-count for a more-complex scenario against a less-complex scenario. 45 * 46 * We have two nested flex containers around an initially-empty block. For each 47 * measurement, we put some text in the block, and we see how many frame-reflow 48 * operations occur as a result. 49 */ 50 51 const gUtils = SpecialPowers.getDOMWindowUtils(window); 52 53 // The elements: 54 const gOuterFlex = document.getElementById("outerFlex"); 55 const gMidFlex = document.getElementById("midFlex"); 56 const gInnerBlock = document.getElementById("innerBlock"); 57 58 // This cleanup helper-function removes all children from 'parent' 59 // except for 'childToPreserve' (if specified) 60 function removeChildrenExcept(parent, childToPreserve) 61 { 62 if (childToPreserve && childToPreserve.parentNode != parent) { 63 // This is just a sanity/integrity-check -- if this fails, it's probably a 64 // bug in this test rather than in the code. I'm not checking this via 65 // e.g. "is(childToPreserve.parentNode, parent)", because this *should* 66 // always pass, and each "pass" is not interesting here since it's a 67 // sanity-check. It's only interesting/noteworthy if it fails. So, to 68 // avoid bloating this test's passed-subtest-count & output, we only bother 69 // reporting on this in the case where something's wrong. 70 ok(false, "bug in test; 'childToPreserve' should be child of 'parent'"); 71 } 72 73 // For simplicity, we just remove *all children* and then reappend 74 // childToPreserve as the sole child. 75 while (parent.firstChild) { 76 parent.removeChild(parent.firstChild); 77 } 78 if (childToPreserve) { 79 parent.appendChild(childToPreserve); 80 } 81 } 82 83 // Appends 'childCount' new children to 'parent' 84 function addNChildren(parent, childCount) 85 { 86 for (let i = 0; i < childCount; i++) { 87 let newChild = document.createElement("div"); 88 // Give the new child some text so it's got a nonzero content-size: 89 newChild.append("a"); 90 parent.appendChild(newChild); 91 } 92 } 93 94 // Undoes whatever styling customizations and DOM insertions that a given 95 // testcase has done, to prepare for running the next testcase. 96 function cleanup() 97 { 98 gOuterFlex.style = gMidFlex.style = gInnerBlock.style = ""; 99 removeChildrenExcept(gInnerBlock); 100 removeChildrenExcept(gMidFlex, gInnerBlock); 101 removeChildrenExcept(gOuterFlex, gMidFlex); 102 } 103 104 // Each testcase here has a label (used in test output), a function to set up 105 // the testcase, and (optionally) a function to set up the reference case. 106 let gTestcases = [ 107 { 108 label : "border on flex items", 109 addTestStyle : function() { 110 gMidFlex.style.border = gInnerBlock.style.border = "3px solid black"; 111 }, 112 }, 113 { 114 label : "padding on flex items", 115 addTestStyle : function() { 116 gMidFlex.style.padding = gInnerBlock.style.padding = "5px"; 117 }, 118 }, 119 { 120 label : "margin on flex items", 121 addTestStyle : function() { 122 gMidFlex.style.margin = gInnerBlock.style.margin = "2px"; 123 }, 124 }, 125 { 126 // When we make a change in one flex item, the number of reflows should not 127 // scale with its number of siblings (as long as those siblings' sizes 128 // aren't impacted by the change): 129 label : "additional flex items in outer flex container", 130 131 // Compare 5 bonus flex items vs. 1 bonus flex item: 132 addTestStyle : function() { 133 addNChildren(gOuterFlex, 5); 134 }, 135 addReferenceStyle : function() { 136 addNChildren(gOuterFlex, 1); 137 }, 138 }, 139 { 140 // (As above, but now the bonus flex items are one step deeper in the tree, 141 // on the nested flex container rather than the outer one) 142 label : "additional flex items in nested flex container", 143 addTestStyle : function() { 144 addNChildren(gMidFlex, 5); 145 }, 146 addReferenceStyle : function() { 147 addNChildren(gMidFlex, 1); 148 }, 149 }, 150 ]; 151 152 // Flush layout & return the global frame-reflow-count 153 function getReflowCount() 154 { 155 let unusedVal = gOuterFlex.offsetHeight; // flush layout 156 return gUtils.framesReflowed; 157 } 158 159 // This function adds some text inside of gInnerBlock, and returns the number 160 // of frames that need to be reflowed as a result. 161 function makeTweakAndCountReflows() 162 { 163 let beforeCount = getReflowCount(); 164 gInnerBlock.appendChild(document.createTextNode("hello")); 165 let afterCount = getReflowCount(); 166 167 let numReflows = afterCount - beforeCount; 168 if (numReflows <= 0) { 169 ok(false, "something's wrong -- we should've reflowed *something*"); 170 } 171 return numReflows; 172 } 173 174 // Given a testcase (from gTestcases), this function verifies that the 175 // testcase scenario requires the same number of reflows as the reference 176 // scenario. 177 function runOneTest(aTestcase) 178 { 179 aTestcase.addTestStyle(); 180 let numTestcaseReflows = makeTweakAndCountReflows(); 181 cleanup(); 182 183 if (aTestcase.addReferenceStyle) { 184 aTestcase.addReferenceStyle(); 185 } 186 let numReferenceReflows = makeTweakAndCountReflows(); 187 cleanup(); 188 189 is(numTestcaseReflows, numReferenceReflows, 190 "Testcase & reference case should require same number of reflows" + 191 " (testcase label: '" + aTestcase.label + "')"); 192 } 193 194 gTestcases.forEach(runOneTest); 195 196 </script> 197 </pre> 198 </body> 199 </html>