<!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>