Bug 1408152 - Sync the throbber animations again after the tab has finished moving. r?gijs draft
authorJared Wein <jwein@mozilla.com>
Thu, 16 Nov 2017 12:19:16 -0500
changeset 699843 2800de2261e8dbdbfb9b58d0bb42c0d14661bb0e
parent 698447 e070277ec199fa96fa490ed52d33646a376d0d80
child 740750 6b1f5190871f1a90c307ff4c87b4a2cd25fa0404
push id89704
push userbmo:jaws@mozilla.com
push dateFri, 17 Nov 2017 20:43:58 +0000
reviewersgijs
bugs1408152
milestone59.0a1
Bug 1408152 - Sync the throbber animations again after the tab has finished moving. r?gijs This feels pretty ugly to dig in to tabbrowser's tabprogresslistener so we can call syncThrobberAnimations. Alternatively, syncThrobberAnimations could be pulled out of the tabprogresslistener now that it would be called from another site. I looked at the CSS changes that the tab moving does and nothing jumped out at me as to why the animation gets out of sync. MozReview-Commit-ID: ICbyQ8RMAVu
browser/base/content/tabbrowser.xml
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -311,16 +311,64 @@
             } finally {
               this.selectedTab = currentTab;
               this._previewMode = false;
             }
           ]]>
         </body>
       </method>
 
+      <method name="syncThrobberAnimations">
+        <parameter name="aTab"/>
+        <body>
+          <![CDATA[
+            BrowserUtils.promiseLayoutFlushed(aTab.ownerDocument, "style", () => {
+              if (!aTab.parentNode) {
+                return;
+              }
+
+              const animations =
+                Array.from(aTab.parentNode.getElementsByTagName("tab"))
+                .map(tab => {
+                  const throbber =
+                    document.getAnonymousElementByAttribute(tab, "anonid", "tab-throbber");
+                  return throbber ? throbber.getAnimations({ subtree: true }) : [];
+                })
+                .reduce((a, b) => a.concat(b))
+                .filter(anim =>
+                  anim instanceof CSSAnimation &&
+                  (anim.animationName === "tab-throbber-animation" ||
+                   anim.animationName === "tab-throbber-animation-rtl") &&
+                  (anim.playState === "running" || anim.playState === "pending"));
+
+              // Synchronize with the oldest running animation, if any.
+              const firstStartTime = Math.min(
+                ...animations.map(anim => anim.startTime === null ? Infinity : anim.startTime)
+              );
+              if (firstStartTime === Infinity) {
+                return;
+              }
+              requestAnimationFrame(() => {
+                for (let animation of animations) {
+                  // If |animation| has been cancelled since this rAF callback
+                  // was scheduled we don't want to set its startTime since
+                  // that would restart it. We check for a cancelled animation
+                  // by looking for a null currentTime rather than checking
+                  // the playState, since reading the playState of
+                  // a CSSAnimation object will flush style.
+                  if (animation.currentTime !== null) {
+                    animation.startTime = firstStartTime;
+                  }
+                }
+              });
+            });
+          ]]>
+        </body>
+      </method>
+
       <method name="getBrowserAtIndex">
         <parameter name="aIndex"/>
         <body>
           <![CDATA[
             return this.browsers[aIndex];
           ]]>
         </body>
       </method>
@@ -608,60 +656,16 @@
                   !aLocation) {
                 return true;
               }
 
               let location = aLocation ? aLocation.spec : "";
               return location == "about:blank";
             },
 
-            _syncThrobberAnimations() {
-              const originalTab = this.mTab;
-              BrowserUtils.promiseLayoutFlushed(this.mTab.ownerDocument, "style", () => {
-                if (!originalTab.parentNode) {
-                  return;
-                }
-
-                const animations =
-                  Array.from(originalTab.parentNode.getElementsByTagName("tab"))
-                  .map(tab => {
-                    const throbber =
-                      document.getAnonymousElementByAttribute(tab, "anonid", "tab-throbber");
-                    return throbber ? throbber.getAnimations({ subtree: true }) : [];
-                  })
-                  .reduce((a, b) => a.concat(b))
-                  .filter(anim =>
-                    anim instanceof CSSAnimation &&
-                    (anim.animationName === "tab-throbber-animation" ||
-                     anim.animationName === "tab-throbber-animation-rtl") &&
-                    (anim.playState === "running" || anim.playState === "pending"));
-
-                // Synchronize with the oldest running animation, if any.
-                const firstStartTime = Math.min(
-                  ...animations.map(anim => anim.startTime === null ? Infinity : anim.startTime)
-                );
-                if (firstStartTime === Infinity) {
-                  return;
-                }
-                requestAnimationFrame(() => {
-                  for (let animation of animations) {
-                    // If |animation| has been cancelled since this rAF callback
-                    // was scheduled we don't want to set its startTime since
-                    // that would restart it. We check for a cancelled animation
-                    // by looking for a null currentTime rather than checking
-                    // the playState, since reading the playState of
-                    // a CSSAnimation object will flush style.
-                    if (animation.currentTime !== null) {
-                      animation.startTime = firstStartTime;
-                    }
-                  }
-                });
-              });
-            },
-
             onProgressChange(aWebProgress, aRequest,
                              aCurSelfProgress, aMaxSelfProgress,
                              aCurTotalProgress, aMaxTotalProgress) {
               this.mTotalProgress = aMaxTotalProgress ? aCurTotalProgress / aMaxTotalProgress : 0;
 
               if (!this._shouldShowProgress(aRequest))
                 return;
 
@@ -777,17 +781,17 @@
                         let continueMonitoring = true;
                         if (!document.querySelector(".tabbrowser-tab[busy]")) {
                           SchedulePressure.stopMonitoring(window);
                           continueMonitoring = false;
                         }
                         return {continueMonitoring};
                       },
                     });
-                    this._syncThrobberAnimations();
+                    this.mTabBrowser.syncThrobberAnimations(this.mTab);
                   }
 
                   if (this.mTab.selected) {
                     this.mTabBrowser.mIsBusy = true;
                   }
                 }
               } else if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
                          aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
@@ -7593,16 +7597,18 @@
               }
               draggedTab.removeEventListener("transitionend", onTransitionEnd);
 
               draggedTab.removeAttribute("tabdrop-samewindow");
 
               this._finishAnimateTabMove();
               if (dropIndex !== false)
                 this.tabbrowser.moveTabTo(draggedTab, dropIndex);
+
+              this.tabbrowser.syncThrobberAnimations(draggedTab);
             }
             draggedTab.addEventListener("transitionend", onTransitionEnd);
           } else {
             this._finishAnimateTabMove();
             if (dropIndex !== false)
               this.tabbrowser.moveTabTo(draggedTab, dropIndex);
           }
         } else if (draggedTab) {