Bug 1408152 - Sync the throbber animations again after the tab has finished moving. r=Gijs
authorJared Wein <jwein@mozilla.com>
Thu, 16 Nov 2017 12:19:16 -0500
changeset 437063 f020bc218c8bfd79e98b246307ff361d6e253f81
parent 437062 8fc12fbc1422193b544278f6c05047d3ea200693
child 437064 aea6154d26f3ad1fa48ed91c740b77bbb0cd3d37
push id117
push userfmarier@mozilla.com
push dateTue, 28 Nov 2017 20:17:16 +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) {
@@ -7604,16 +7608,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) {