<!DOCTYPE HTML> <html> <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1142686 --> <head> <meta charset="utf-8"> <title>Test for Bug 1142686</title> <script src="/tests/SimpleTest/SimpleTest.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> <style> .flex { display: flex; } #outerFlex { border: 1px solid black; } </style> </head> <body> <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1142686">Mozilla Bug 1142686</a> <div id="display"> <div id="content"> <div class="flex" id="outerFlex"> <div class="flex" id="midFlex"> <div id="innerBlock"> </div> </div> </div> </div> </div> <pre id="test"> <script type="application/javascript"> "use strict"; /** Test for Bug 1142686 **/ /** * This test checks how many reflows are required, when we make a change inside * a set of two nested flex containers, with various styles applied to * the containers & innermost child. Some flex layout operations require two * passes (which can cause exponential blowup). This test is intended to verify * that certain configurations do *not* require two-pass layout, by comparing * the reflow-count for a more-complex scenario against a less-complex scenario. * * We have two nested flex containers around an initially-empty block. For each * measurement, we put some text in the block, and we see how many frame-reflow * operations occur as a result. */ const gUtils = SpecialPowers.getDOMWindowUtils(window); // The elements: const gOuterFlex = document.getElementById("outerFlex"); const gMidFlex = document.getElementById("midFlex"); const gInnerBlock = document.getElementById("innerBlock"); // This cleanup helper-function removes all children from 'parent' // except for 'childToPreserve' (if specified) function removeChildrenExcept(parent, childToPreserve) { if (childToPreserve && childToPreserve.parentNode != parent) { // This is just a sanity/integrity-check -- if this fails, it's probably a // bug in this test rather than in the code. I'm not checking this via // e.g. "is(childToPreserve.parentNode, parent)", because this *should* // always pass, and each "pass" is not interesting here since it's a // sanity-check. It's only interesting/noteworthy if it fails. So, to // avoid bloating this test's passed-subtest-count & output, we only bother // reporting on this in the case where something's wrong. ok(false, "bug in test; 'childToPreserve' should be child of 'parent'"); } // For simplicity, we just remove *all children* and then reappend // childToPreserve as the sole child. while (parent.firstChild) { parent.removeChild(parent.firstChild); } if (childToPreserve) { parent.appendChild(childToPreserve); } } // Appends 'childCount' new children to 'parent' function addNChildren(parent, childCount) { for (let i = 0; i < childCount; i++) { let newChild = document.createElement("div"); // Give the new child some text so it's got a nonzero content-size: newChild.append("a"); parent.appendChild(newChild); } } // Undoes whatever styling customizations and DOM insertions that a given // testcase has done, to prepare for running the next testcase. function cleanup() { gOuterFlex.style = gMidFlex.style = gInnerBlock.style = ""; removeChildrenExcept(gInnerBlock); removeChildrenExcept(gMidFlex, gInnerBlock); removeChildrenExcept(gOuterFlex, gMidFlex); } // Each testcase here has a label (used in test output), a function to set up // the testcase, and (optionally) a function to set up the reference case. let gTestcases = [ { label : "border on flex items", addTestStyle : function() { gMidFlex.style.border = gInnerBlock.style.border = "3px solid black"; }, }, { label : "padding on flex items", addTestStyle : function() { gMidFlex.style.padding = gInnerBlock.style.padding = "5px"; }, }, { label : "margin on flex items", addTestStyle : function() { gMidFlex.style.margin = gInnerBlock.style.margin = "2px"; }, }, { // When we make a change in one flex item, the number of reflows should not // scale with its number of siblings (as long as those siblings' sizes // aren't impacted by the change): label : "additional flex items in outer flex container", // Compare 5 bonus flex items vs. 1 bonus flex item: addTestStyle : function() { addNChildren(gOuterFlex, 5); }, addReferenceStyle : function() { addNChildren(gOuterFlex, 1); }, }, { // (As above, but now the bonus flex items are one step deeper in the tree, // on the nested flex container rather than the outer one) label : "additional flex items in nested flex container", addTestStyle : function() { addNChildren(gMidFlex, 5); }, addReferenceStyle : function() { addNChildren(gMidFlex, 1); }, }, ]; // Flush layout & return the global frame-reflow-count function getReflowCount() { let unusedVal = gOuterFlex.offsetHeight; // flush layout return gUtils.framesReflowed; } // This function adds some text inside of gInnerBlock, and returns the number // of frames that need to be reflowed as a result. function makeTweakAndCountReflows() { let beforeCount = getReflowCount(); gInnerBlock.appendChild(document.createTextNode("hello")); let afterCount = getReflowCount(); let numReflows = afterCount - beforeCount; if (numReflows <= 0) { ok(false, "something's wrong -- we should've reflowed *something*"); } return numReflows; } // Given a testcase (from gTestcases), this function verifies that the // testcase scenario requires the same number of reflows as the reference // scenario. function runOneTest(aTestcase) { aTestcase.addTestStyle(); let numTestcaseReflows = makeTweakAndCountReflows(); cleanup(); if (aTestcase.addReferenceStyle) { aTestcase.addReferenceStyle(); } let numReferenceReflows = makeTweakAndCountReflows(); cleanup(); is(numTestcaseReflows, numReferenceReflows, "Testcase & reference case should require same number of reflows" + " (testcase label: '" + aTestcase.label + "')"); } gTestcases.forEach(runOneTest); </script> </pre> </body> </html>