summaryrefslogtreecommitdiffstats
path: root/layout/style/test/test_dynamic_change_causing_reflow.html
diff options
context:
space:
mode:
Diffstat (limited to 'layout/style/test/test_dynamic_change_causing_reflow.html')
-rw-r--r--layout/style/test/test_dynamic_change_causing_reflow.html716
1 files changed, 716 insertions, 0 deletions
diff --git a/layout/style/test/test_dynamic_change_causing_reflow.html b/layout/style/test/test_dynamic_change_causing_reflow.html
new file mode 100644
index 0000000000..bf3b7578b8
--- /dev/null
+++ b/layout/style/test/test_dynamic_change_causing_reflow.html
@@ -0,0 +1,716 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1131371
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1131371</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1131371">Mozilla Bug 1131371</a>
+<style>
+ #elemWithScrollbars { overflow: scroll }
+ .flexScrollerWithTransformedItems {
+ display: flex;
+ overflow: hidden;
+ width: 200px;
+ position: relative;
+ }
+ .flexScrollerWithTransformedItems div {
+ min-width: 50px;
+ min-height: 50px;
+ margin: 10px;
+ will-change: transform;
+ }
+ .absposFlexItem {
+ left: 250px;
+ top: 0;
+ position: absolute;
+ }
+ #tableCell,
+ #tableCellWithAbsPosChild {
+ display: table-cell;
+ }
+</style>
+<div id="display">
+ <div id="content">
+ </div>
+ <div id="elemWithAbsPosChild"><div style="position:absolute"></div></div>
+ <div id="elemWithFixedPosChild"><div style="position:fixed"></div></div>
+ <div id="elemWithScrollbars"></div>
+ <div id="elemWithoutScrollbars"></div>
+ <div class="flexScrollerWithTransformedItems">
+ <div></div>
+ <div></div>
+ <div></div>
+ <div></div>
+ <div id="flexItemMovementTarget"></div>
+ </div>
+ <div class="flexScrollerWithTransformedItems">
+ <div></div>
+ <div></div>
+ <div></div>
+ <div id="flexItemMovementTarget2"></div>
+ <div class="absposFlexItem"></div>
+ </div>
+ <input id="inputElem">
+ <textarea id="textareaElem">
+ Some
+ lines
+ of
+ text
+ </textarea>
+ <select id="selectElem">
+ <option>A</option>
+ <option>B</option>
+ <option>C</option>
+ </select>
+ <button id="buttonElem">
+ Something
+ </button>
+ <button id="buttonElemWithAbsPosChild"><div style="position:absolute"></div></button>
+ <div id="tableCell"></div>
+ <div id="tableCellWithAbsPosChild">
+ <div style="position: absolute"></div>
+ </div>
+</div>
+<pre id="test">
+<script>
+"use strict";
+
+/** Test for Bug 1131371 **/
+
+const elemWithAbsPosChild = document.getElementById("elemWithAbsPosChild");
+const elemWithFixedPosChild = document.getElementById("elemWithFixedPosChild");
+const elemWithScrollbars = document.getElementById("elemWithScrollbars");
+const elemWithoutScrollbars = document.getElementById("elemWithoutScrollbars");
+const inputElem = document.getElementById("inputElem");
+const textareaElem = document.getElementById("textareaElem");
+const selectElem = document.getElementById("selectElem");
+const buttonElem = document.getElementById("buttonElem");
+const buttonElemWithAbsPosChild = document.getElementById("buttonElemWithAbsPosChild");
+const tableCell = document.getElementById("tableCell");
+const tableCellWithAbsPosChild = document.getElementById("tableCellWithAbsPosChild");
+
+for (let scroller of document.querySelectorAll(".flexScrollerWithTransformedItems")) {
+ scroller.scrollLeft = 10000;
+}
+
+const flexItemMovementTarget = document.getElementById("flexItemMovementTarget");
+const flexItemMovementTarget2 = document.getElementById("flexItemMovementTarget2");
+
+/**
+ * This test verifies that certain style changes do or don't cause reflow
+ * and/or frame construction. We do this by checking the framesReflowed &
+ * framesConstructed counts, before & after a style-change, and verifying
+ * that any change to these counts is in line with our expectations.
+ *
+ * Each entry in gTestcases contains these member-values:
+ * - beforeStyle (optional): initial value to use for "style" attribute.
+ * - afterStyle: value to change the "style" attribute to.
+ *
+ * Testcases may also include two optional member-values to express that reflow
+ * and/or frame construction *are* in fact expected:
+ * - expectConstruction (optional): if set to something truthy, then we expect
+ * frame construction to occur when afterStyle is set. Otherwise, we
+ * expect that frame construction should *not* occur.
+ * - expectReflow (optional): if set to something truthy, then we expect
+ * reflow to occur when afterStyle is set. Otherwise, we expect that
+ * reflow should *not* occur.
+ */
+const gTestcases = [
+ // Things that shouldn't cause reflow:
+ // -----------------------------------
+ // * Adding an outline (e.g. for focus ring).
+ {
+ afterStyle: "outline: 1px dotted black",
+ },
+
+ // * Changing between completely different outlines.
+ {
+ beforeStyle: "outline: 2px solid black",
+ afterStyle: "outline: 6px dashed yellow",
+ },
+
+ // * Adding a box-shadow.
+ {
+ afterStyle: "box-shadow: inset 3px 3px gray",
+ },
+ {
+ afterStyle: "box-shadow: 0px 0px 10px 30px blue"
+ },
+
+ // * Changing between completely different box-shadow values,
+ // e.g. from an upper-left shadow to a bottom-right shadow:
+ {
+ beforeStyle: "box-shadow: -15px -20px teal",
+ afterStyle: "box-shadow: 30px 40px yellow",
+ },
+
+ // * Adding a text-shadow.
+ {
+ afterStyle: "text-shadow: 3px 3px gray",
+ },
+ {
+ afterStyle: "text-shadow: 0px 0px 10px blue"
+ },
+
+ // * Changing between completely different text-shadow values,
+ // e.g. from an upper-left shadow to a bottom-right shadow:
+ {
+ beforeStyle: "text-shadow: -15px -20px teal",
+ afterStyle: "text-shadow: 30px 40px yellow",
+ },
+
+ // * switching overflow between things that shouldn't create scrollframes.
+ {
+ beforeStyle: "overflow: visible",
+ afterStyle: "overflow: clip",
+ },
+
+ // Things that *should* cause reflow:
+ // ----------------------------------
+ // (e.g. to make sure our counts are actually measuring something)
+
+ // * Changing 'height' should cause reflow, but not frame construction.
+ {
+ beforeStyle: "height: 10px",
+ afterStyle: "height: 15px",
+ expectReflow: true,
+ },
+
+ // * Changing 'shape-outside' on a non-floating box should not cause anything to happen.
+ {
+ beforeStyle: "shape-outside: none",
+ afterStyle: "shape-outside: circle()",
+ },
+
+ // * Changing 'shape-outside' should cause reflow, but not frame construction.
+ {
+ beforeStyle: "float: left; shape-outside: none",
+ afterStyle: "float: left; shape-outside: circle()",
+ expectReflow: true,
+ },
+
+ // * Changing 'overflow' on <body> should cause reflow,
+ // but not frame reconstruction
+ {
+ elem: document.body,
+ /* beforeStyle: implicitly 'overflow:visible' */
+ afterStyle: "overflow: hidden",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.body,
+ /* beforeStyle: implicitly 'overflow:visible' */
+ afterStyle: "overflow: scroll",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.body,
+ beforeStyle: "overflow: hidden",
+ afterStyle: "overflow: auto",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.body,
+ beforeStyle: "overflow: hidden",
+ afterStyle: "overflow: scroll",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.body,
+ beforeStyle: "overflow: hidden",
+ afterStyle: "overflow: visible",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.body,
+ beforeStyle: "overflow: auto",
+ afterStyle: "overflow: hidden",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.body,
+ beforeStyle: "overflow: visible",
+ afterStyle: "overflow: hidden",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+
+ // * Changing 'overflow' on <html> should cause reflow,
+ // but not frame reconstruction
+ {
+ elem: document.documentElement,
+ /* beforeStyle: implicitly 'overflow:visible' */
+ afterStyle: "overflow: auto",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.documentElement,
+ beforeStyle: "overflow: visible",
+ afterStyle: "overflow: auto",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+
+ // * Setting 'overflow' on arbitrary node should cause reflow as well as
+ // frame reconstruction
+ {
+ /* beforeStyle: implicitly 'overflow:visible' */
+ afterStyle: "overflow: auto",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ beforeStyle: "overflow: auto",
+ afterStyle: "overflow: visible",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+
+ // * but only reflow if we don't need to construct / unconstruct a new frame.
+ {
+ beforeStyle: "overflow: scroll",
+ afterStyle: "overflow: auto",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ beforeStyle: "overflow: auto",
+ afterStyle: "overflow: scroll",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+
+ {
+ beforeStyle: "overflow: hidden",
+ afterStyle: "overflow: auto",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ beforeStyle: "overflow: auto",
+ afterStyle: "overflow: hidden",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ beforeStyle: "overflow: hidden",
+ afterStyle: "overflow: scroll",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ beforeStyle: "overflow: scroll",
+ afterStyle: "overflow: hidden",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+
+ {
+ elem: elemWithoutScrollbars,
+ beforeStyle: "scrollbar-width: auto",
+ afterStyle: "scrollbar-width: none",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: elemWithScrollbars,
+ beforeStyle: "scrollbar-width: auto",
+ afterStyle: "scrollbar-width: none",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ // Not the scrolling element, so nothing should happen.
+ elem: document.body,
+ beforeStyle: "scrollbar-width: none",
+ afterStyle: "scrollbar-width: auto",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ // Not the scrolling element, so nothing should happen.
+ elem: document.body,
+ beforeStyle: "scrollbar-width: auto",
+ afterStyle: "scrollbar-width: none",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: document.documentElement,
+ beforeStyle: "scrollbar-width: none;",
+ afterStyle: "scrollbar-width: auto",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.documentElement,
+ beforeStyle: "overflow: scroll; scrollbar-width: none;",
+ afterStyle: "overflow: scroll; scrollbar-width: auto",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.documentElement,
+ beforeStyle: "overflow: scroll; scrollbar-width: auto;",
+ afterStyle: "overflow: scroll; scrollbar-width: none",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+
+ // * Changing 'display' should cause frame construction and reflow.
+ {
+ beforeStyle: "display: inline",
+ afterStyle: "display: table",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+
+
+ // * Position changes trigger a reframe, unless whether we're a containing
+ // block doesn't change, in which case we just need to reflow.
+ {
+ beforeStyle: "position: static",
+ afterStyle: "position: absolute",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ beforeStyle: "position: absolute",
+ afterStyle: "position: fixed",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ beforeStyle: "position: relative",
+ afterStyle: "position: fixed",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+
+ // This doesn't change whether we're a containing block because there are no
+ // abspos descendants.
+ {
+ afterStyle: "position: static",
+ beforeStyle: "position: relative",
+ expectReflow: true,
+ },
+
+ // This doesn't change whether we're a containing block, shouldn't reframe.
+ {
+ afterStyle: "position: sticky",
+ beforeStyle: "position: relative",
+ expectReflow: true,
+ },
+
+ // These don't change whether we're a containing block for our
+ // absolutely-positioned child, so shouldn't reframe.
+ {
+ elem: elemWithAbsPosChild,
+ afterStyle: "position: sticky",
+ beforeStyle: "position: relative",
+ expectReflow: true,
+ },
+ {
+ elem: elemWithFixedPosChild,
+ afterStyle: "position: sticky",
+ beforeStyle: "position: relative",
+ expectReflow: true,
+ },
+ {
+ elem: elemWithFixedPosChild,
+ afterStyle: "position: static",
+ beforeStyle: "position: relative",
+ expectReflow: true,
+ },
+ {
+ elem: elemWithFixedPosChild,
+ afterStyle: "position: static",
+ beforeStyle: "position: sticky",
+ expectReflow: true,
+ },
+ {
+ // Even if we're a scroll frame.
+ elem: elemWithFixedPosChild,
+ afterStyle: "position: static; overflow: auto;",
+ beforeStyle: "position: relative; overflow: auto;",
+ expectReflow: true,
+ },
+ {
+ elem: tableCell,
+ afterStyle: "position: static;",
+ beforeStyle: "position: relative;",
+ expectReflow: true,
+ },
+ {
+ elem: tableCell,
+ afterStyle: "filter: none",
+ beforeStyle: "filter: saturate(1)",
+ expectReflow: false,
+ },
+
+ // These ones do though.
+ {
+ elem: elemWithAbsPosChild,
+ afterStyle: "position: static",
+ beforeStyle: "position: relative",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: elemWithAbsPosChild,
+ afterStyle: "position: static",
+ beforeStyle: "position: sticky",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: elemWithAbsPosChild,
+ afterStyle: "position: static; overflow: auto;",
+ beforeStyle: "position: relative; overflow: auto;",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: tableCellWithAbsPosChild,
+ afterStyle: "position: static;",
+ beforeStyle: "position: relative;",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+
+ // Adding transform to a scrollframe without abspos / fixedpos children shouldn't reframe.
+ {
+ elem: elemWithScrollbars,
+ afterStyle: "transform: translateX(1px)",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+
+ // <select> can't contain abspos / floating children so shouldn't reframe
+ // when changing containing block-ness.
+ {
+ elem: selectElem,
+ afterStyle: "transform: translateX(1px)",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: selectElem,
+ afterStyle: "position: relative",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+
+ // <button> shouldn't be reframed either in the absence of positioned descendants.
+ {
+ elem: buttonElem,
+ afterStyle: "transform: translateX(1px)",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: buttonElem,
+ afterStyle: "position: relative",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: buttonElemWithAbsPosChild,
+ afterStyle: "position: relative",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ // changing scroll-behavior should not cause reflow or frame construction
+ {
+ elem: document.documentElement,
+ /* beforeStyle: implicitly 'scroll-behavior: auto' */
+ afterStyle: "scroll-behavior: smooth",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: document.documentElement,
+ beforeStyle: "scroll-behavior: smooth",
+ afterStyle: "scroll-behavior: auto",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: document.body,
+ /* beforeStyle: implicitly 'scroll-behavior: auto' */
+ afterStyle: "scroll-behavior: smooth",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: document.body,
+ beforeStyle: "scroll-behavior: smooth",
+ afterStyle: "scroll-behavior: auto",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ // changing scroll-snap-type should not cause reflow or frame construction
+ {
+ elem: document.documentElement,
+ /* beforeStyle: implicitly 'scroll-snap-type: none' */
+ afterStyle: "scroll-snap-type: y mandatory",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: document.documentElement,
+ /* beforeStyle: implicitly 'scroll-snap-type: none' */
+ afterStyle: "scroll-snap-type: x proximity",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: document.documentElement,
+ beforeStyle: "scroll-snap-type: y mandatory",
+ afterStyle: "scroll-snap-type: none",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: document.body,
+ /* beforeStyle: implicitly 'scroll-snap-type: none' */
+ afterStyle: "scroll-snap-type: y mandatory",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: document.body,
+ /* beforeStyle: implicitly 'scroll-snap-type: none' */
+ afterStyle: "scroll-snap-type: x proximity",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: document.body,
+ beforeStyle: "scroll-snap-type: y mandatory",
+ afterStyle: "scroll-snap-type: none",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: inputElem,
+ beforeStyle: "overflow: auto",
+ afterStyle: "overflow: hidden",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: textareaElem,
+ beforeStyle: "overflow: auto",
+ afterStyle: "overflow: hidden",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: flexItemMovementTarget,
+ beforeStyle: "transform: translateX(0)",
+ afterStyle: "transform: translateX(-100px)",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: flexItemMovementTarget2,
+ beforeStyle: "transform: translateX(0)",
+ afterStyle: "transform: translateX(-100px)",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+];
+
+// Helper function to let us call either "is" or "isnot" & assemble
+// the failure message, based on the provided parameters.
+function checkFinalCount(aFinalCount, aExpectedCount,
+ aExpectChange, aMsgPrefix, aCountDescription)
+{
+ let compareFunc;
+ let msg = aMsgPrefix;
+ if (aExpectChange) {
+ compareFunc = isnot;
+ msg += "should cause " + aCountDescription;
+ } else {
+ compareFunc = is;
+ msg += "should not cause " + aCountDescription;
+ }
+
+ compareFunc(aFinalCount, aExpectedCount, msg);
+}
+
+// Vars used in runOneTest that we really only have to look up once:
+const gUtils = SpecialPowers.getDOMWindowUtils(window);
+const gElem = document.getElementById("content");
+
+function runOneTest(aTestcase)
+{
+ // sanity-check that we have the one main thing we need:
+ if (!aTestcase.afterStyle) {
+ ok(false, "testcase is missing an 'afterStyle' to change to");
+ return;
+ }
+
+ // Figure out which element we'll be tweaking (defaulting to gElem)
+ let elem = aTestcase.elem ? aTestcase.elem : gElem;
+
+ // Verify that 'style' attribute is unset (avoid causing ourselves trouble):
+ const oldStyle = elem.getAttribute("style");
+
+ // Set the "before" style, and compose the first part of the message
+ // to be used in our "is"/"isnot" invocations:
+ let msgPrefix = "Changing style ";
+ if (aTestcase.beforeStyle) {
+ elem.setAttribute("style", aTestcase.beforeStyle);
+ msgPrefix += "from '" + aTestcase.beforeStyle + "' ";
+ }
+ msgPrefix += "to '" + aTestcase.afterStyle + "' ";
+ msgPrefix += "on " + elem.nodeName + " ";
+
+ // Establish initial counts:
+ let unusedVal = elem.offsetHeight; // flush layout
+ let origFramesConstructed = gUtils.framesConstructed;
+ let origFramesReflowed = gUtils.framesReflowed;
+
+ // Make the change and flush:
+ elem.setAttribute("style", aTestcase.afterStyle);
+ unusedVal = elem.offsetHeight; // flush layout
+
+ // Make our is/isnot assertions about whether things should have changed:
+ checkFinalCount(gUtils.framesConstructed, origFramesConstructed,
+ aTestcase.expectConstruction, msgPrefix,
+ "frame construction");
+ checkFinalCount(gUtils.framesReflowed, origFramesReflowed,
+ aTestcase.expectReflow, msgPrefix,
+ "reflow");
+
+ // Clean up!
+ if (oldStyle) {
+ elem.setAttribute("style", oldStyle);
+ } else {
+ elem.removeAttribute("style");
+ }
+
+ unusedVal = elem.offsetHeight; // flush layout
+}
+
+gTestcases.forEach(runOneTest);
+
+</script>
+</pre>
+</body>
+</html>