Bug 1356532: Part 5 - Avoid layout flushes during adjustHeight(). r=florian draft
authorKris Maglione <maglione.k@gmail.com>
Sat, 26 Aug 2017 15:12:04 -0700
changeset 656130 05083b460030d284e510d7d586eb4c3509eda21a
parent 656129 1a587441fb6591bbf139ab3dd0256e11ad41a205
child 729018 ac32059ffe44a2e2bf5686a912a9d9099b62f26f
push id77076
push usermaglione.k@gmail.com
push dateWed, 30 Aug 2017 19:02:00 +0000
reviewersflorian
bugs1356532
milestone57.0a1
Bug 1356532: Part 5 - Avoid layout flushes during adjustHeight(). r=florian MozReview-Commit-ID: 16hfIz8Ai9M
toolkit/content/widgets/autocomplete.xml
--- a/toolkit/content/widgets/autocomplete.xml
+++ b/toolkit/content/widgets/autocomplete.xml
@@ -1234,85 +1234,106 @@ extends="chrome://global/content/binding
             }
           ]]>
         </body>
       </method>
 
       <method name="adjustHeight">
         <body>
           <![CDATA[
+
+          if (this._adjustHeightPromise) {
+            return;
+          }
+
           // Figure out how many rows to show
           let rows = this.richlistbox.childNodes;
           let numRows = Math.min(this._matchCount, this.maxRows, rows.length);
 
           this.removeAttribute("height");
 
-          // Default the height to 0 if we have no rows to show
-          let height = 0;
-          if (numRows) {
-            let firstRowRect = rows[0].getBoundingClientRect();
-            if (this._rlbPadding == undefined) {
-              let style = window.getComputedStyle(this.richlistbox);
+          this._adjustHeightPromise = (async () => {
+            // Default the height to 0 if we have no rows to show
+            let height = 0;
+            let currentHeight;
 
-              let transition = style.transitionProperty;
-              this._rlbAnimated = transition && transition != "none";
+            await BrowserUtils.promiseSlowLayoutFlush(document, "layout", () => {
+              if (numRows) {
+                let firstRowRect = rows[0].getBoundingClientRect();
+                if (this._rlbPadding == undefined) {
+                  let style = window.getComputedStyle(this.richlistbox);
 
-              let paddingTop = parseInt(style.paddingTop) || 0;
-              let paddingBottom = parseInt(style.paddingBottom) || 0;
-              this._rlbPadding = paddingTop + paddingBottom;
-            }
+                  let transition = style.transitionProperty;
+                  this._rlbAnimated = transition && transition != "none";
+
+                  let paddingTop = parseInt(style.paddingTop) || 0;
+                  let paddingBottom = parseInt(style.paddingBottom) || 0;
+                  this._rlbPadding = paddingTop + paddingBottom;
+                }
 
-            if (numRows > this.maxRows) {
-              // Set a fixed max-height to avoid flicker when growing the panel.
-              let lastVisibleRowRect = rows[this.maxRows - 1].getBoundingClientRect();
-              let visibleHeight = lastVisibleRowRect.bottom - firstRowRect.top;
-              this.richlistbox.style.maxHeight =
-                visibleHeight + this._rlbPadding + "px";
-            }
+                if (numRows > this.maxRows) {
+                  // Set a fixed max-height to avoid flicker when growing the panel.
+                  let lastVisibleRowRect = rows[this.maxRows - 1].getBoundingClientRect();
+                  let visibleHeight = lastVisibleRowRect.bottom - firstRowRect.top;
 
-            // The class `forceHandleUnderflow` is for the item might need to
-            // handle OverUnderflow or Overflow when the height of an item will
-            // be changed dynamically.
-            for (let i = 0; i < numRows; i++) {
-              if (rows[i].classList.contains("forceHandleUnderflow")) {
-                rows[i].handleOverUnderflow();
-              }
-            }
+                  BrowserUtils.promiseAnimationFrame(window, () => {
+                    this.richlistbox.style.maxHeight =
+                      visibleHeight + this._rlbPadding + "px";
+                  });
+                }
+
+                // The class `forceHandleUnderflow` is for the item might need to
+                // handle OverUnderflow or Overflow when the height of an item will
+                // be changed dynamically.
+                for (let i = 0; i < numRows; i++) {
+                  if (rows[i].classList.contains("forceHandleUnderflow")) {
+                    rows[i].handleOverUnderflow();
+                  }
+                }
 
-            let lastRowRect = rows[numRows - 1].getBoundingClientRect();
-            // Calculate the height to have the first row to last row shown
-            height = lastRowRect.bottom - firstRowRect.top +
-                     this._rlbPadding;
-          }
+                let lastRowRect = rows[numRows - 1].getBoundingClientRect();
+                // Calculate the height to have the first row to last row shown
+                height = lastRowRect.bottom - firstRowRect.top +
+                         this._rlbPadding;
+              }
+
+              currentHeight = this.richlistbox.getBoundingClientRect().height;
+            });
+
+            this._adjustHeightPromise = null;
 
-          let animate = this._rlbAnimated &&
-                        this.getAttribute("dontanimate") != "true";
-          let currentHeight = this.richlistbox.getBoundingClientRect().height;
-          if (height > currentHeight) {
-            // Grow immediately.
-            if (animate) {
-              this.richlistbox.removeAttribute("height");
-              this.richlistbox.style.height = height + "px";
+            let animate = this._rlbAnimated &&
+                          this.getAttribute("dontanimate") != "true";
+            if (height > currentHeight) {
+              BrowserUtils.promiseAnimationFrame(window, () => {
+                // Grow immediately.
+                if (animate) {
+                  this.richlistbox.removeAttribute("height");
+                  this.richlistbox.style.height = height + "px";
+                } else {
+                  this.richlistbox.style.removeProperty("height");
+                  this.richlistbox.height = height;
+                }
+              })
             } else {
-              this.richlistbox.style.removeProperty("height");
-              this.richlistbox.height = height;
+              // Delay shrinking to avoid flicker.
+              this._shrinkTimeout = setTimeout(() => {
+                BrowserUtils.promiseAnimationFrame(window, () => {
+                  this._collapseUnusedItems();
+                  if (animate) {
+                    this.richlistbox.removeAttribute("height");
+                    this.richlistbox.style.height = height + "px";
+                  } else {
+                    this.richlistbox.style.removeProperty("height");
+                    this.richlistbox.height = height;
+                  }
+                });
+              }, this.mInput.shrinkDelay);
             }
-          } else {
-            // Delay shrinking to avoid flicker.
-            this._shrinkTimeout = setTimeout(() => {
-              this._collapseUnusedItems();
-              if (animate) {
-                this.richlistbox.removeAttribute("height");
-                this.richlistbox.style.height = height + "px";
-              } else {
-                this.richlistbox.style.removeProperty("height");
-                this.richlistbox.height = height;
-              }
-            }, this.mInput.shrinkDelay);
-          }
+          })();
           ]]>
         </body>
       </method>
 
       <method name="_appendCurrentResult">
         <parameter name="invalidateReason"/>
         <body>
           <![CDATA[