summaryrefslogtreecommitdiffstats
path: root/layout/style/test/test_flexbox_reflow_counts.html
blob: a8f4913f7d8d7e43f8d00bb06da0f97761a0d573 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
<!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>