Bug 1390313 - Item added to the overflow menu should scale down and fade out. ui-r=epang r?Gijs draft
authorErica Wright <ewright@mozilla.com>
Mon, 18 Sep 2017 16:15:24 -0400
changeset 675548 8f330fb0cf281d518d5c457a49392551ffe1a21e
parent 675508 53bbdaaa2b8c1819061be26101b075c081b23260
child 734639 ee36823ba010d35e2797e6743fe8d4f138eda3c0
push id83168
push userbmo:ewright@mozilla.com
push dateThu, 05 Oct 2017 14:47:27 +0000
reviewersepang, Gijs
bugs1390313
milestone58.0a1
Bug 1390313 - Item added to the overflow menu should scale down and fade out. ui-r=epang r?Gijs MozReview-Commit-ID: 5PxydbSfhpz
browser/components/customizableui/CustomizeMode.jsm
browser/components/customizableui/test/browser.ini
browser/components/customizableui/test/browser_972267_customizationchange_events.js
browser/components/customizableui/test/browser_984455_bookmarks_items_reparenting.js
browser/components/customizableui/test/browser_customization_context_menus.js
browser/components/customizableui/test/browser_overflow_use_subviews.js
browser/components/customizableui/test/browser_widget_animation.js
browser/components/downloads/test/browser/browser_downloads_autohide.js
browser/components/downloads/test/browser/browser_overflow_anchor.js
browser/themes/shared/customizableui/panelUI.inc.css
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -42,16 +42,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
                                   "resource://gre/modules/LightweightThemeManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
                                   "resource:///modules/sessionstore/SessionStore.jsm");
 XPCOMUtils.defineLazyGetter(this, "gWidgetsBundle", function() {
   const kUrl = "chrome://browser/locale/customizableui/customizableWidgets.properties";
   return Services.strings.createBundle(kUrl);
 });
+XPCOMUtils.defineLazyPreferenceGetter(this, "gCosmeticAnimationsEnabled",
+                                      "toolkit.cosmeticAnimations.enabled");
 
 let gDebug;
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   let scope = {};
   Cu.import("resource://gre/modules/Console.jsm", scope);
   gDebug = Services.prefs.getBoolPref(kPrefCustomizationDebug, false);
   let consoleOptions = {
     maxLogLevel: gDebug ? "all" : "log",
@@ -573,21 +575,49 @@ CustomizeMode.prototype = {
       if (areas.indexOf(parent.id) != -1) {
         return aNode;
       }
       aNode = parent;
     }
     return null;
   },
 
-  addToToolbar(aNode) {
+  _promiseWidgetAnimationOut(aNode) {
+    if (!gCosmeticAnimationsEnabled ||
+        aNode.getAttribute("cui-anchorid") == "nav-bar-overflow-button" ||
+        (aNode.tagName != "toolbaritem" && aNode.tagName != "toolbarbutton") ||
+        (aNode.id == "downloads-button" && aNode.hidden)) {
+      return null;
+    }
+    let animationNode;
+    if (aNode.parentNode.id.startsWith("wrapper-")) {
+      animationNode = aNode.parentNode;
+    } else {
+      animationNode = aNode;
+    }
+    return new Promise(resolve => {
+      animationNode.classList.add("animate-out");
+      animationNode.addEventListener("animationend", function cleanupWidgetAnimationEnd(e) {
+        if (e.animationName == "widget-animate-out" && e.target.id == animationNode.id) {
+          animationNode.removeEventListener("animationend", cleanupWidgetAnimationEnd);
+          resolve();
+        }
+      });
+    });
+  },
+
+  async addToToolbar(aNode) {
     aNode = this._getCustomizableChildForNode(aNode);
     if (aNode.localName == "toolbarpaletteitem" && aNode.firstChild) {
       aNode = aNode.firstChild;
     }
+    let widgetAnimationPromise = this._promiseWidgetAnimationOut(aNode);
+    if (widgetAnimationPromise) {
+      await widgetAnimationPromise;
+    }
 
     let widgetToAdd = aNode.id;
     if (CustomizableUI.isSpecialWidget(widgetToAdd) && aNode.closest("#customization-palette")) {
       widgetToAdd = widgetToAdd.match(/^customizableui-special-(spring|spacer|separator)/)[1];
     }
 
     CustomizableUI.addWidgetToArea(widgetToAdd, CustomizableUI.AREA_NAVBAR);
     if (!this._customizing) {
@@ -596,72 +626,103 @@ CustomizeMode.prototype = {
 
     // If the user explicitly moves this item, turn off autohide.
     if (aNode.id == "downloads-button") {
       Services.prefs.setBoolPref(kDownloadAutoHidePref, false);
       if (this._customizing) {
         this._showDownloadsAutoHidePanel();
       }
     }
+
+    if (widgetAnimationPromise) {
+      if (aNode.parentNode.id.startsWith("wrapper-")) {
+        aNode.parentNode.classList.remove("animate-out");
+      } else {
+        aNode.classList.remove("animate-out")
+      }
+    }
   },
 
-  addToPanel(aNode) {
+  async addToPanel(aNode) {
     aNode = this._getCustomizableChildForNode(aNode);
     if (aNode.localName == "toolbarpaletteitem" && aNode.firstChild) {
       aNode = aNode.firstChild;
     }
+    let widgetAnimationPromise = this._promiseWidgetAnimationOut(aNode);
+    if (widgetAnimationPromise) {
+      await widgetAnimationPromise;
+    }
 
     let panel = CustomizableUI.AREA_FIXED_OVERFLOW_PANEL;
     CustomizableUI.addWidgetToArea(aNode.id, panel);
     if (!this._customizing) {
       CustomizableUI.dispatchToolboxEvent("customizationchange");
     }
 
     // If the user explicitly moves this item, turn off autohide.
     if (aNode.id == "downloads-button") {
       Services.prefs.setBoolPref(kDownloadAutoHidePref, false);
       if (this._customizing) {
         this._showDownloadsAutoHidePanel();
       }
     }
 
-    if (Services.prefs.getBoolPref("toolkit.cosmeticAnimations.enabled")) {
+    if (widgetAnimationPromise) {
+      if (aNode.parentNode.id.startsWith("wrapper-")) {
+        aNode.parentNode.classList.remove("animate-out");
+      } else {
+        aNode.classList.remove("animate-out")
+      }
+    }
+    if (gCosmeticAnimationsEnabled) {
       let overflowButton = this.document.getElementById("nav-bar-overflow-button");
       BrowserUtils.setToolbarButtonHeightProperty(overflowButton).then(() => {
         overflowButton.setAttribute("animate", "true");
         overflowButton.addEventListener("animationend", function onAnimationEnd(event) {
           if (event.animationName.startsWith("overflow-animation")) {
             this.setAttribute("fade", "true");
           } else if (event.animationName == "overflow-fade") {
             this.removeEventListener("animationend", onAnimationEnd);
             this.removeAttribute("animate");
             this.removeAttribute("fade");
           }
         });
       });
     }
   },
 
-  removeFromArea(aNode) {
+  async removeFromArea(aNode) {
     aNode = this._getCustomizableChildForNode(aNode);
     if (aNode.localName == "toolbarpaletteitem" && aNode.firstChild) {
       aNode = aNode.firstChild;
     }
+    let widgetAnimationPromise = this._promiseWidgetAnimationOut(aNode);
+    if (widgetAnimationPromise) {
+      await widgetAnimationPromise;
+    }
+
     CustomizableUI.removeWidgetFromArea(aNode.id);
     if (!this._customizing) {
       CustomizableUI.dispatchToolboxEvent("customizationchange");
     }
 
     // If the user explicitly removes this item, turn off autohide.
     if (aNode.id == "downloads-button") {
       Services.prefs.setBoolPref(kDownloadAutoHidePref, false);
       if (this._customizing) {
         this._showDownloadsAutoHidePanel();
       }
     }
+    if (widgetAnimationPromise) {
+      if (aNode.parentNode.id.startsWith("wrapper-")) {
+        aNode.parentNode.classList.remove("animate-out");
+      } else {
+        aNode.classList.remove("animate-out")
+      }
+    }
   },
 
   populatePalette() {
     let fragment = this.document.createDocumentFragment();
     let toolboxPalette = this.window.gNavToolbox.palette;
 
     try {
       let unusedWidgets = CustomizableUI.getUnusedWidgets(toolboxPalette);
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -161,8 +161,9 @@ tags = fullscreen
 [browser_backfwd_enabled_post_customize.js]
 [browser_check_tooltips_in_navbar.js]
 [browser_editcontrols_update.js]
 subsuite = clipboard
 [browser_customization_context_menus.js]
 [browser_open_from_popup.js]
 [browser_sidebar_toggle.js]
 [browser_remote_tabs_button.js]
+[browser_widget_animation.js]
--- a/browser/components/customizableui/test/browser_972267_customizationchange_events.js
+++ b/browser/components/customizableui/test/browser_972267_customizationchange_events.js
@@ -15,17 +15,17 @@ add_task(async function() {
     handlerCalledCount++;
   };
 
   let homeButton = document.getElementById("home-button");
 
   gNavToolbox.addEventListener("customizationchange", handler);
   otherToolbox.addEventListener("customizationchange", handler);
 
-  gCustomizeMode.addToPanel(homeButton);
+  await gCustomizeMode.addToPanel(homeButton);
 
   is(handlerCalledCount, 2, "Should be called for both windows.");
 
   handlerCalledCount = 0;
   gCustomizeMode.addToToolbar(homeButton);
   is(handlerCalledCount, 2, "Should be called for both windows.");
 
   gNavToolbox.removeEventListener("customizationchange", handler);
--- a/browser/components/customizableui/test/browser_984455_bookmarks_items_reparenting.js
+++ b/browser/components/customizableui/test/browser_984455_bookmarks_items_reparenting.js
@@ -210,39 +210,39 @@ add_task(async function testOverflowingB
  * Test that the bookmarks toolbar items context menu still works if moved
  * to the menu from the overflow panel, and then back to the toolbar.
  */
 add_task(async function testOverflowingBookmarksItemsContextMenu() {
   info("Ensuring panel is ready.");
   await PanelUI.ensureReady();
 
   let bookmarksToolbarItems = document.getElementById(kBookmarksItems);
-  gCustomizeMode.addToToolbar(bookmarksToolbarItems);
+  await gCustomizeMode.addToToolbar(bookmarksToolbarItems);
   await checkPlacesContextMenu(bookmarksToolbarItems);
 
   await overflowEverything();
   checkOverflowing(kBookmarksItems)
 
-  gCustomizeMode.addToPanel(bookmarksToolbarItems);
+  await gCustomizeMode.addToPanel(bookmarksToolbarItems);
 
   await stopOverflowing();
 
-  gCustomizeMode.addToToolbar(bookmarksToolbarItems);
+  await gCustomizeMode.addToToolbar(bookmarksToolbarItems);
   await checkPlacesContextMenu(bookmarksToolbarItems);
 });
 
 /**
  * Test that overflowing the bookmarks toolbar items doesn't cause the
  * context menu in the bookmarks toolbar items chevron to stop working.
  */
 add_task(async function testOverflowingBookmarksItemsChevronContextMenu() {
   // If it's not already there, let's move the bookmarks toolbar items to
   // the nav-bar.
   let bookmarksToolbarItems = document.getElementById(kBookmarksItems);
-  gCustomizeMode.addToToolbar(bookmarksToolbarItems);
+  await gCustomizeMode.addToToolbar(bookmarksToolbarItems);
 
   // We make the PlacesToolbarItems element be super tiny in order to force
   // the bookmarks toolbar items into overflowing and making the chevron
   // show itself.
   let placesToolbarItems = document.getElementById("PlacesToolbarItems");
   let placesChevron = document.getElementById("PlacesChevron");
   placesToolbarItems.style.maxWidth = "10px";
   info("Waiting for chevron to no longer be collapsed");
--- a/browser/components/customizableui/test/browser_customization_context_menus.js
+++ b/browser/components/customizableui/test/browser_customization_context_menus.js
@@ -146,33 +146,33 @@ add_task(async function urlbar_context()
 // and back should move the search-container instead.
 add_task(async function searchbar_context_move_to_panel_and_back() {
   // This is specifically testing the addToPanel function for the search bar, so
   // we have to move it to its correct position in the navigation toolbar first.
   // The preference will be restored when the customizations are reset later.
   Services.prefs.setBoolPref("browser.search.widget.inNavBar", true);
 
   let searchbar = document.getElementById("searchbar");
-  gCustomizeMode.addToPanel(searchbar);
+  await gCustomizeMode.addToPanel(searchbar);
   let placement = CustomizableUI.getPlacementOfWidget("search-container");
   is(placement.area, CustomizableUI.AREA_FIXED_OVERFLOW_PANEL, "Should be in panel");
 
   await waitForOverflowButtonShown();
 
   let shownPanelPromise = popupShown(overflowPanel);
   overflowButton.click();
   await shownPanelPromise;
   let hiddenPanelPromise = popupHidden(overflowPanel);
   overflowPanel.hidePopup();
   await hiddenPanelPromise;
 
   gCustomizeMode.addToToolbar(searchbar);
   placement = CustomizableUI.getPlacementOfWidget("search-container");
   is(placement.area, CustomizableUI.AREA_NAVBAR, "Should be in navbar");
-  gCustomizeMode.removeFromArea(searchbar);
+  await gCustomizeMode.removeFromArea(searchbar);
   placement = CustomizableUI.getPlacementOfWidget("search-container");
   is(placement, null, "Should be in palette");
   CustomizableUI.reset();
   placement = CustomizableUI.getPlacementOfWidget("search-container");
   is(placement, null, "Should be in palette");
 });
 
 // Right-click on an item within the panel should
--- a/browser/components/customizableui/test/browser_overflow_use_subviews.js
+++ b/browser/components/customizableui/test/browser_overflow_use_subviews.js
@@ -50,17 +50,17 @@ add_task(async function check_developer_
 /**
  * This checks that non-subview-compatible items still work correctly.
  * Ideally we should make the downloads panel and bookmarks/library item
  * proper subview items, then this test can go away, and potentially we can
  * simplify some of the subview anchoring code.
  */
 add_task(async function check_downloads_panel_in_overflow() {
   let button = document.getElementById("downloads-button");
-  gCustomizeMode.addToPanel(button);
+  await gCustomizeMode.addToPanel(button);
   await waitForOverflowButtonShown();
 
   let chevron = document.getElementById("nav-bar-overflow-button");
   let shownPanelPromise = promisePanelElementShown(window, kOverflowPanel);
   chevron.click();
   await shownPanelPromise;
 
   button.click();
new file mode 100644
--- /dev/null
+++ b/browser/components/customizableui/test/browser_widget_animation.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function promiseWidgetAnimationOut(aNode) {
+  let animationNode = aNode;
+  if (animationNode.tagName != "toolbaritem" && animationNode.tagName != "toolbarbutton") {
+    animationNode = animationNode.closest("toolbaritem");
+  }
+  if (animationNode.parentNode.id.startsWith("wrapper-")) {
+    animationNode = animationNode.parentNode;
+  }
+  return new Promise(resolve => {
+    animationNode.addEventListener("animationend", function cleanupWidgetAnimationOut(e) {
+      if (e.animationName == "widget-animate-out" && e.target.id == animationNode.id) {
+        animationNode.removeEventListener("animationend", cleanupWidgetAnimationOut);
+        ok(true, "The widget`s animationend should have happened");
+        resolve();
+      }
+    });
+  });
+}
+
+function promiseOverflowAnimationEnd() {
+  return new Promise(resolve => {
+    let overflowButton = document.getElementById("nav-bar-overflow-button");
+    overflowButton.addEventListener("animationend", function cleanupOverflowAnimationOut(event) {
+      if (event.animationName == "overflow-fade") {
+        overflowButton.removeEventListener("transitionend", cleanupOverflowAnimationOut);
+        ok(true, "The overflow button`s animationend event should have happened");
+        resolve();
+      }
+    });
+  });
+}
+
+// Right-click on the home widget, use the context menu to move it to the overflow menu.
+// The home widget should animate out, and the overflow menu should animate upon adding.
+add_task(async function() {
+  let homeButton = document.getElementById("home-button");
+  let contextMenu = document.getElementById("toolbar-context-menu");
+  let shownPromise = popupShown(contextMenu);
+  EventUtils.synthesizeMouseAtCenter(homeButton, {type: "contextmenu", button: 2 });
+  await shownPromise;
+
+  let moveToPanel = contextMenu.querySelector(".customize-context-moveToPanel");
+  if (moveToPanel) {
+    moveToPanel.click();
+  }
+
+  await Promise.all([promiseWidgetAnimationOut(homeButton), promiseOverflowAnimationEnd()]);
+  ok(true, "The widget and overflow animations should have both happened.");
+});
+
+registerCleanupFunction(CustomizableUI.reset);
--- a/browser/components/downloads/test/browser/browser_downloads_autohide.js
+++ b/browser/components/downloads/test/browser/browser_downloads_autohide.js
@@ -20,40 +20,40 @@ registerCleanupFunction(async function()
 add_task(async function checkStateDuringPrefFlips() {
   ok(Services.prefs.getBoolPref(kDownloadAutoHidePref),
      "Should be autohiding the button by default");
   ok(!DownloadsIndicatorView.hasDownloads,
      "Should be no downloads when starting the test");
   let downloadsButton = document.getElementById("downloads-button");
   ok(downloadsButton.hasAttribute("hidden"),
      "Button should be hidden in the toolbar");
-  gCustomizeMode.addToPanel(downloadsButton);
+  await gCustomizeMode.addToPanel(downloadsButton);
   ok(!downloadsButton.hasAttribute("hidden"),
      "Button shouldn't be hidden in the panel");
   ok(!Services.prefs.getBoolPref(kDownloadAutoHidePref),
      "Pref got set to false when the user moved the button");
   gCustomizeMode.addToToolbar(downloadsButton);
   ok(!Services.prefs.getBoolPref(kDownloadAutoHidePref),
      "Pref remains false when the user moved the button");
   Services.prefs.setBoolPref(kDownloadAutoHidePref, true);
   ok(downloadsButton.hasAttribute("hidden"),
      "Button should be hidden again in the toolbar " +
      "now that we flipped the pref");
   Services.prefs.setBoolPref(kDownloadAutoHidePref, false);
   ok(!downloadsButton.hasAttribute("hidden"),
      "Button shouldn't be hidden with autohide turned off");
-  gCustomizeMode.addToPanel(downloadsButton);
+  await gCustomizeMode.addToPanel(downloadsButton);
   ok(!downloadsButton.hasAttribute("hidden"),
      "Button shouldn't be hidden with autohide turned off " +
      "after moving it to the panel");
   gCustomizeMode.addToToolbar(downloadsButton);
   ok(!downloadsButton.hasAttribute("hidden"),
      "Button shouldn't be hidden with autohide turned off " +
      "after moving it back to the toolbar");
-  gCustomizeMode.addToPanel(downloadsButton);
+  await gCustomizeMode.addToPanel(downloadsButton);
   Services.prefs.setBoolPref(kDownloadAutoHidePref, true);
   ok(!downloadsButton.hasAttribute("hidden"),
      "Button should still not be hidden with autohide turned back on " +
      "because it's in the panel");
   // Use CUI directly instead of the customize mode APIs,
   // to avoid tripping the "automatically turn off autohide" code.
   CustomizableUI.addWidgetToArea("downloads-button", "nav-bar");
   ok(downloadsButton.hasAttribute("hidden"),
@@ -76,31 +76,31 @@ add_task(async function checkStateInCust
   await promiseCustomizeStart();
   ok(!downloadsButton.hasAttribute("hidden"),
      "Button should be shown in customize mode.");
   await promiseCustomizeEnd();
   ok(downloadsButton.hasAttribute("hidden"),
      "Button should be hidden if it's in the toolbar " +
      "after customize mode without any moves.");
   await promiseCustomizeStart();
-  gCustomizeMode.addToPanel(downloadsButton);
+  await gCustomizeMode.addToPanel(downloadsButton);
   ok(!downloadsButton.hasAttribute("hidden"),
      "Button should be shown in customize mode when moved to the panel");
   gCustomizeMode.addToToolbar(downloadsButton);
   ok(!downloadsButton.hasAttribute("hidden"),
      "Button should be shown in customize mode when moved back to the toolbar");
   gCustomizeMode.removeFromArea(downloadsButton);
   ok(!downloadsButton.hasAttribute("hidden"),
      "Button should be shown in customize mode when in the palette");
   Services.prefs.setBoolPref(kDownloadAutoHidePref, false);
   Services.prefs.setBoolPref(kDownloadAutoHidePref, true);
   ok(!downloadsButton.hasAttribute("hidden"),
      "Button should be shown in customize mode " +
      "even when flipping the autohide pref");
-  gCustomizeMode.addToPanel(downloadsButton);
+  await gCustomizeMode.addToPanel(downloadsButton);
   await promiseCustomizeEnd();
   ok(!downloadsButton.hasAttribute("hidden"),
      "Button should be shown after customize mode when moved to the panel");
   await promiseCustomizeStart();
   gCustomizeMode.addToToolbar(downloadsButton);
   await promiseCustomizeEnd();
   ok(!downloadsButton.hasAttribute("hidden"),
      "Button should be shown in the toolbar after " +
@@ -108,17 +108,17 @@ add_task(async function checkStateInCust
   await promiseCustomizeStart();
   await gCustomizeMode.reset();
   ok(!downloadsButton.hasAttribute("hidden"),
      "Button should be shown in the toolbar in customize mode after a reset.");
   await gCustomizeMode.undoReset();
   ok(!downloadsButton.hasAttribute("hidden"),
      "Button should be shown in the toolbar in customize mode " +
      "when undoing the reset.");
-  gCustomizeMode.addToPanel(downloadsButton);
+  await gCustomizeMode.addToPanel(downloadsButton);
   await gCustomizeMode.reset();
   ok(!downloadsButton.hasAttribute("hidden"),
      "Button should be shown in the toolbar in customize mode " +
      "after a reset moved it.");
   await gCustomizeMode.undoReset();
   ok(!downloadsButton.hasAttribute("hidden"),
      "Button should be shown in the panel in customize mode " +
      "when undoing the reset.");
@@ -161,17 +161,17 @@ add_task(async function checkStateInCust
 
   Services.prefs.setBoolPref(kDownloadAutoHidePref, true);
   ok(!downloadsButton.hasAttribute("hidden"),
      "Button should be shown in customize mode " +
      "even when flipping the autohide pref");
   ok(otherDownloadsButton.hasAttribute("hidden"),
      "Button should be hidden in the other window with the pref flipped again");
 
-  gCustomizeMode.addToPanel(downloadsButton);
+  await gCustomizeMode.addToPanel(downloadsButton);
   ok(!downloadsButton.hasAttribute("hidden"),
      "Button should still be shown in customize mode.");
   ok(!otherDownloadsButton.hasAttribute("hidden"),
      "Button should be shown in the other window too because it's in a panel.");
 
   gCustomizeMode.removeFromArea(downloadsButton);
   ok(!Services.prefs.getBoolPref(kDownloadAutoHidePref),
      "Autohide pref turned off by moving the button");
--- a/browser/components/downloads/test/browser/browser_overflow_anchor.js
+++ b/browser/components/downloads/test/browser/browser_overflow_anchor.js
@@ -20,17 +20,17 @@ add_task(async function test_overflow_an
   await task_resetState();
 
   // The downloads button should not be overflowed to begin with.
   let button = CustomizableUI.getWidget("downloads-button")
                              .forWindow(window);
   ok(!button.overflowed, "Downloads button should not be overflowed.");
   is(button.node.getAttribute("cui-areatype"), "toolbar", "Button should know it's in the toolbar");
 
-  gCustomizeMode.addToPanel(button.node);
+  await gCustomizeMode.addToPanel(button.node);
 
   let promise = promisePanelOpened();
   EventUtils.sendMouseEvent({ type: "mousedown", button: 0 }, button.node);
   info("waiting for panel to open");
   await promise;
 
   let panel = DownloadsPanel.panel;
   let chevron = document.getElementById("nav-bar-overflow-button");
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -464,16 +464,33 @@ photonpanelmultiview .panel-subview-body
 .panelUI-grid .toolbarbutton-1,
 .panel-customization-placeholder-child {
   -moz-appearance: none;
   -moz-box-orient: vertical;
   width: calc(@menuPanelButtonWidth@);
   height: calc(51px + 2.2em);
 }
 
+.animate-out {
+  animation-name: widget-animate-out;
+  animation-fill-mode: forwards;
+  animation-duration: 500ms;
+}
+
+@keyframes widget-animate-out {
+  0% {
+    opacity: 1;
+    transform: scale(1);
+  }
+  100% {
+    opacity: 0 ;
+    transform: scale(.5);
+  }
+}
+
 toolbarpaletteitem[place=panel] > .toolbarbutton-1 {
   -moz-box-flex: 1;
 }
 
 /* Help SDK buttons fit in. */
 toolbarpaletteitem[place="palette"] > toolbarbutton[constrain-size="true"] > .toolbarbutton-icon,
 toolbarpaletteitem[place="palette"] > toolbarbutton[constrain-size="true"] > .toolbarbutton-badge-stack > .toolbarbutton-icon,
 toolbarpaletteitem[place="palette"] > toolbaritem[sdkstylewidget="true"] > .toolbarbutton-1 > .toolbarbutton-icon,