Bug 1358453 - Throttle scroll button disabled state updates while scrolling to avoid flushing layout as much as possible. r=florian
authorDão Gottwald <dao@mozilla.com>
Thu, 25 May 2017 15:08:54 +0200
changeset 360652 d8e867982af7
parent 360651 fd44804a0913
child 360653 02bdc7d39a54
push id43396
push userdgottwald@mozilla.com
push date2017-05-25 13:19 +0000
treeherderautoland@d8e867982af7 [default view] [failures only]
reviewersflorian
bugs1358453
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1358453 - Throttle scroll button disabled state updates while scrolling to avoid flushing layout as much as possible. r=florian MozReview-Commit-ID: 34w6M06ehRF
toolkit/content/widgets/scrollbox.xml
--- a/toolkit/content/widgets/scrollbox.xml
+++ b/toolkit/content/widgets/scrollbox.xml
@@ -607,37 +607,38 @@
             this._scrollAnim.stop();
             this._isScrolling = 0;
             this._scrollTarget = null;
           }
         ]]></body>
       </method>
 
       <method name="_updateScrollButtonsDisabledState">
+        <parameter name="aScrolling"/>
         <body><![CDATA[
-          var scrolledToStart = false;
-          var scrolledToEnd = false;
+          let scrolledToStart;
+          let scrolledToEnd;
 
+          // Avoid flushing layout when not overflowing or when scrolling.
           if (this.hasAttribute("notoverflowing")) {
             scrolledToStart = true;
             scrolledToEnd = true;
+          } else if (aScrolling) {
+            scrolledToStart = false;
+            scrolledToEnd = false;
           } else if (this.scrollPosition == 0) {
             // In the RTL case, this means the _last_ element in the
             // scrollbox is visible
-            if (this._isRTLScrollbox)
-              scrolledToEnd = true;
-            else
-              scrolledToStart = true;
+            scrolledToEnd = this._isRTLScrollbox;
+            scrolledToStart = !this._isRTLScrollbox;
           } else if (this.scrollClientSize + this.scrollPosition == this.scrollSize) {
             // In the RTL case, this means the _first_ element in the
             // scrollbox is visible
-            if (this._isRTLScrollbox)
-              scrolledToStart = true;
-            else
-              scrolledToEnd = true;
+            scrolledToStart = this._isRTLScrollbox;
+            scrolledToEnd = !this._isRTLScrollbox;
           }
 
           if (scrolledToEnd)
             this.setAttribute("scrolledtoend", "true");
           else
             this.removeAttribute("scrolledtoend");
 
           if (scrolledToStart)
@@ -790,17 +791,39 @@
           // means that the notoverflowing attribute was removed erroneously,
           // as the whole overflow event should not be happening in that case.
           this._updateScrollButtonsDisabledState();
         } catch (e) {
           this.setAttribute("notoverflowing", "true");
         }
       ]]></handler>
 
-      <handler event="scroll" action="this._updateScrollButtonsDisabledState()"/>
+      <handler event="scroll"><![CDATA[
+        if (!this._delayedUpdateScrollButtonsTimer) {
+          // This is the beginning of a scrolling animation. We need to update
+          // scroll buttons now in case we were scrolled to the start or to the
+          // end before we started scrolling.
+          this._updateScrollButtonsDisabledState(true);
+        } else {
+          // We're in the middle of the scrolling animation. We'll restart the
+          // delayed update request so that we only update the scroll buttons
+          // a second time once we're done scrolling.
+          window.clearTimeout(this._delayedUpdateScrollButtonsTimer);
+        }
+
+        // Try to detect the end of the scrolling animation to update the
+        // scroll buttons again. To avoid false positives, this timeout needs
+        // to be big enough to account for intermediate frames that don't move
+        // the scroll position in case we're scrolling slowly.
+        this._delayedUpdateScrollButtonsTimer = setTimeout(() => {
+          // Scrolling animation has finished.
+          this._delayedUpdateScrollButtonsTimer = 0;
+          this._updateScrollButtonsDisabledState();
+        }, 200);
+      ]]></handler>
     </handlers>
   </binding>
 
   <binding id="autorepeatbutton" extends="chrome://global/content/bindings/scrollbox.xml#scrollbox-base">
     <content repeat="hover">
       <xul:image class="autorepeatbutton-icon"/>
     </content>
   </binding>