Bug 1394985 - Synchronize throbber animations; r?jaws draft
authorBrian Birtles <birtles@gmail.com>
Fri, 01 Sep 2017 09:32:35 +0900
changeset 658360 d14711223e6edc5af958051d47825b756eb3ae01
parent 658358 8e05298328da75f3056a9f1f9609938870d756a0
child 729616 2f3cb43b708adbb73a027588a2943d6edc9c360f
push id77730
push userbmo:bbirtles@mozilla.com
push dateMon, 04 Sep 2017 00:34:41 +0000
reviewersjaws
bugs1394985
milestone57.0a1
Bug 1394985 - Synchronize throbber animations; r?jaws MozReview-Commit-ID: Gxgl8IJX6vZ
browser/base/content/tabbrowser.xml
layout/style/nsAnimationManager.h
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -612,16 +612,60 @@
                   !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;
 
@@ -709,16 +753,17 @@
                   delete this.mBrowser.initialPageLoadedFromURLBar;
                   // If the browser is loading it must not be crashed anymore
                   this.mTab.removeAttribute("crashed");
                 }
 
                 if (this._shouldShowProgress(aRequest)) {
                   if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
                     this.mTab.setAttribute("busy", "true");
+                    this._syncThrobberAnimations();
                   }
 
                   if (this.mTab.selected) {
                     this.mTabBrowser.mIsBusy = true;
                   }
                 }
               } else if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
                          aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
@@ -7513,16 +7558,17 @@
           <xul:hbox class="tab-bottom-line"/>
         </xul:vbox>
         <xul:hbox xbl:inherits="pinned,bursting"
                   anonid="tab-loading-burst"
                   class="tab-loading-burst"/>
         <xul:hbox xbl:inherits="pinned,selected=visuallyselected,titlechanged,attention"
                   class="tab-content" align="center">
           <xul:hbox xbl:inherits="fadein,pinned,busy,progress,selected=visuallyselected"
+                    anonid="tab-throbber"
                     class="tab-throbber"
                     layer="true"/>
           <xul:image xbl:inherits="src=image,loadingprincipal=iconLoadingPrincipal,fadein,pinned,selected=visuallyselected,busy,crashed,sharing"
                      anonid="tab-icon-image"
                      class="tab-icon-image"
                      validate="never"
                      role="presentation"/>
           <xul:image xbl:inherits="sharing,selected=visuallyselected,pinned"
--- a/layout/style/nsAnimationManager.h
+++ b/layout/style/nsAnimationManager.h
@@ -106,16 +106,24 @@ public:
   // for more efficient internal usage.
   const nsString& AnimationName() const { return mAnimationName; }
 
   // Animation interface overrides
   virtual Promise* GetReady(ErrorResult& aRv) override;
   virtual void Play(ErrorResult& aRv, LimitBehavior aLimitBehavior) override;
   virtual void Pause(ErrorResult& aRv) override;
 
+  // NOTE: tabbrowser.xml currently relies on the fact that reading the
+  // currentTime of a CSSAnimation does *not* flush style (whereas reading the
+  // playState does). If CSS Animations 2 specifies that reading currentTime
+  // also flushes style we will need to find another way to detect canceled
+  // animations in tabbrowser.xml. On the other hand, if CSS Animations 2
+  // specifies that reading playState does *not* flush style (and we drop the
+  // following override), then we should update tabbrowser.xml to check
+  // the playState instead.
   virtual AnimationPlayState PlayStateFromJS() const override;
   virtual void PlayFromJS(ErrorResult& aRv) override;
 
   void PlayFromStyle();
   void PauseFromStyle();
   void CancelFromStyle() override
   {
     // When an animation is disassociated with style it enters an odd state