Bug 1390313 - Item added to the overflow menu should scale down and fade out. ui-r=epang r?Gijs
MozReview-Commit-ID: 5PxydbSfhpz
--- 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,