Bug 1393136 - Schedule measurement and positioning of the notifier with promisedLayoutFlushed. r?paolo draft
authorSam Foster <sfoster@mozilla.com>
Mon, 28 Aug 2017 19:52:50 -0700
changeset 655394 93d3c8792dc6ead15968d8a203017a3e6ce90236
parent 653735 4caca1d0ba0e35cbe57a88493ebf162aa2cb3144
child 728827 ef30b2f891722190dc08075b65f242e04aa41693
push id76866
push userbmo:sfoster@mozilla.com
push dateWed, 30 Aug 2017 00:03:45 +0000
reviewerspaolo
bugs1393136
milestone57.0a1
Bug 1393136 - Schedule measurement and positioning of the notifier with promisedLayoutFlushed. r?paolo MozReview-Commit-ID: ATJ3BdJBJCL
browser/components/downloads/content/indicator.js
--- a/browser/components/downloads/content/indicator.js
+++ b/browser/components/downloads/content/indicator.js
@@ -22,17 +22,18 @@
  * Builds and updates the actual downloads status widget, responding to changes
  * in the global status data, or provides a neutral view if the indicator is
  * removed from the toolbars and only used as a temporary anchor.  In addition,
  * handles the user interaction events raised by the widget.
  */
 
 "use strict";
 
-Components.utils.import("resource://gre/modules/AppConstants.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
+  "resource://gre/modules/BrowserUtils.jsm");
 
 // DownloadsButton
 
 /**
  * Main entry point for the downloads indicator.  Depending on how the toolbars
  * have been customized, this object determines if we should show a fully
  * functional indicator, a placeholder used during customization and in the
  * customization palette, or a neutral view as a temporary anchor for the
@@ -328,17 +329,17 @@ const DownloadsIndicatorView = {
 
   /**
    * If the status indicator is visible in its assigned position, shows for a
    * brief time a visual notification of a relevant event, like a new download.
    *
    * @param aType
    *        Set to "start" for new downloads, "finish" for completed downloads.
    */
-  _showNotification(aType) {
+  async _showNotification(aType) {
     // No need to show visual notification if the panel is visible.
     if (DownloadsPanel.isPanelShowing) {
       return;
     }
 
     let anchor = DownloadsButton._placeholder;
     let widgetGroup = CustomizableUI.getWidget("downloads-button");
     let widget = widgetGroup.forWindow(window);
@@ -360,44 +361,55 @@ const DownloadsIndicatorView = {
 
     // The notification element is positioned to show in the same location as
     // the downloads button. It's not in the downloads button itself in order to
     // be able to anchor the notification elsewhere if required, and to ensure
     // the notification isn't clipped by overflow properties of the anchor's
     // container.
     // Note: no notifier animation for download finished in Photon
     let notifier = this.notifier;
+    let notifierCoords = {};
+    let shouldShowNotifier = (aType == "start");
+    this._currentNotificationType = aType;
 
-    if (aType == "start") {
+    if (shouldShowNotifier) {
       // Show the notifier before measuring for size/placement. Being hidden by default
       // avoids the interference with scrolling/APZ when the notifier element is
       // tall enough to overlap the tabbrowser element
       notifier.removeAttribute("hidden");
 
-      // the anchor height may vary if font-size is changed or
-      // compact/tablet mode is selected so recalculate this each time
-      let anchorRect = anchor.getBoundingClientRect();
-      let notifierRect = notifier.getBoundingClientRect();
+      // measure elements at the first opportunity in the next frame after
+      // un-hiding the notifier
+      let [anchorRect, notifierRect] = await BrowserUtils.promiseLayoutFlushed(document, "layout", () => {
+        // the anchor height may vary if font-size is changed or
+        // compact/tablet mode is selected so recalculate this each time
+        return [anchor.getBoundingClientRect(),
+                notifier.getBoundingClientRect()];
+      });
+
       let topDiff = anchorRect.top - notifierRect.top;
       let leftDiff = anchorRect.left - notifierRect.left;
       let heightDiff = anchorRect.height - notifierRect.height;
       let widthDiff = anchorRect.width - notifierRect.width;
-      let translateX = (leftDiff + .5 * widthDiff) + "px";
-      let translateY = (topDiff + .5 * heightDiff) + "px";
-      notifier.style.transform = "translate(" + translateX + ", " + translateY + ")";
+      notifierCoords.x = (leftDiff + .5 * widthDiff) + "px";
+      notifierCoords.y = (topDiff + .5 * heightDiff) + "px";
+    }
+
+    await new Promise(resolve => requestAnimationFrame(resolve));
+
+    if (shouldShowNotifier) {
+      notifier.style.transform = "translate(" + notifierCoords.x + ", " + notifierCoords.y + ")";
       notifier.setAttribute("notification", aType);
     }
     anchor.setAttribute("notification", aType);
 
     let animationDuration;
     // This value is determined by the overall duration of animation in CSS.
     animationDuration = aType == "start" ? 760 : 850;
 
-    this._currentNotificationType = aType;
-
     setTimeout(() => {
       requestAnimationFrame(() => {
         notifier.setAttribute("hidden", "true");
         notifier.removeAttribute("notification");
         notifier.style.transform = "";
         anchor.removeAttribute("notification");
 
         requestAnimationFrame(() => {