--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -363,16 +363,20 @@ pref("browser.download.manager.resumeOnW
// This allows disabling the animated notifications shown by
// the Downloads Indicator when a download starts or completes.
pref("browser.download.animateNotifications", true);
// This records whether or not the panel has been shown at least once.
pref("browser.download.panel.shown", false);
+// This controls whether the button is automatically shown/hidden depending
+// on whether there are downloads to show.
+pref("browser.download.autohideButton", true);
+
#ifndef XP_MACOSX
pref("browser.helperApps.deleteTempFileOnExit", true);
#endif
// search engines URL
pref("browser.search.searchEnginesURL", "https://addons.mozilla.org/%LOCALE%/firefox/search-engines/");
// pointer to the default engine name
--- a/browser/base/content/browser-customization.js
+++ b/browser/base/content/browser-customization.js
@@ -34,17 +34,16 @@ var CustomizationHandler = {
childNode.setAttribute("disabled", true);
let cmd = document.getElementById("cmd_CustomizeToolbars");
cmd.setAttribute("disabled", "true");
UpdateUrlbarSearchSplitterState();
PlacesToolbarHelper.customizeStart();
- DownloadsButton.customizeStart();
},
_customizationEnding(aDetails) {
// Update global UI elements that may have been added or removed
if (aDetails.changed) {
gURLBar = document.getElementById("urlbar");
gHomeButton.updateTooltip();
@@ -58,17 +57,16 @@ var CustomizationHandler = {
if (!window.__lookupGetter__("PopupNotifications")) {
PopupNotifications.iconBox =
document.getElementById("notification-popup-box");
}
}
PlacesToolbarHelper.customizeDone();
- DownloadsButton.customizeDone();
UpdateUrlbarSearchSplitterState();
// Update the urlbar
URLBarSetURI();
XULBrowserWindow.asyncUpdateUI();
// Re-enable parts of the UI we disabled during the dialog
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -513,20 +513,19 @@ toolbar:not(#TabsToolbar) > #personal-bo
/* Hide menu elements intended for keyboard access support */
#main-menubar[openedwithkey=false] .show-only-for-keyboard {
display: none;
}
/* ::::: location bar & search bar ::::: */
-#urlbar-container {
- min-width: 50ch;
-}
-
+/* url bar min-width is defined further down, together with the maximum size
+ * of the identity icon block, for different window sizes.
+ */
#search-container {
min-width: 25ch;
}
#urlbar,
.searchbar-textbox {
/* Setting a width and min-width to let the location & search bars maintain
a constant width in case they haven't be resized manually. (bug 965772) */
@@ -696,51 +695,79 @@ html|input.urlbar-input[textoverflow]:no
-moz-user-focus: normal;
}
#urlbar[pageproxystate="invalid"] > #identity-box {
pointer-events: none;
-moz-user-focus: ignore;
}
+
+/* We leave 49ch plus whatever space the download button will need when it
+ * appears. Normally this should be 16px for the icon, plus 2 * 2px padding
+ * plus the toolbarbutton-inner-padding. We're adding 4px to ensure things
+ * like rounding on hidpi don't accidentally result in the button going
+ * into overflow.
+ */
+#urlbar-container {
+ min-width: calc(49ch + 24px + 2 * var(--toolbarbutton-inner-padding));
+}
+
+#nav-bar[downloadsbuttonshown] #urlbar-container {
+ min-width: 49ch;
+}
+
#identity-icon-labels {
- max-width: 18em;
+ max-width: 17em;
}
@media (max-width: 700px) {
#urlbar-container {
- min-width: 45ch;
+ min-width: calc(44ch + 24px + 2 * var(--toolbarbutton-inner-padding));
}
+ #nav-bar[downloadsbuttonshown] #urlbar-container {
+ min-width: 44ch;
+ }
+
#identity-icon-labels {
- max-width: 70px;
+ max-width: 60px;
}
}
@media (max-width: 600px) {
#urlbar-container {
- min-width: 40ch;
- }
- #identity-icon-labels {
- max-width: 60px;
+ min-width: calc(39ch + 24px + 2 * var(--toolbarbutton-inner-padding));
}
-}
-@media (max-width: 500px) {
- #urlbar-container {
- min-width: 35ch;
+ #nav-bar[downloadsbuttonshown] #urlbar-container {
+ min-width: 39ch;
}
#identity-icon-labels {
max-width: 50px;
}
}
-@media (max-width: 400px) {
+@media (max-width: 500px) {
#urlbar-container {
- min-width: 28ch;
+ min-width: calc(34ch + 24px + 2 * var(--toolbarbutton-inner-padding));
+ }
+ #nav-bar[downloadsbuttonshown] #urlbar-container {
+ min-width: 34ch;
}
#identity-icon-labels {
max-width: 40px;
}
}
+@media (max-width: 400px) {
+ #urlbar-container {
+ min-width: calc(27ch + 24px + 2 * var(--toolbarbutton-inner-padding));
+ }
+ #nav-bar[downloadsbuttonshown] #urlbar-container {
+ min-width: 27ch;
+ }
+ #identity-icon-labels {
+ max-width: 30px;
+ }
+}
#identity-icon-country-label {
direction: ltr;
}
#identity-box.verifiedIdentity > #identity-icon-labels > #identity-icon-label {
margin-inline-end: 0.25em !important;
}
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1316,16 +1316,20 @@ var gBrowserInit = {
}
// hook up UI through progress listener
gBrowser.addProgressListener(window.XULBrowserWindow);
gBrowser.addTabsProgressListener(window.TabsProgressListener);
SidebarUI.init();
+ // We do this in onload because we want to ensure the button's state
+ // doesn't flicker as the window is being shown.
+ DownloadsButton.init();
+
// Certain kinds of automigration rely on this notification to complete
// their tasks BEFORE the browser window is shown. SessionStore uses it to
// restore tabs into windows AFTER important parts like gMultiProcessBrowser
// have been initialized.
Services.obs.notifyObservers(window, "browser-window-before-show");
gUIDensity.init();
@@ -1842,16 +1846,18 @@ var gBrowserInit = {
CompactTheme.uninit();
TrackingProtection.uninit();
CaptivePortalWatcher.uninit();
SidebarUI.uninit();
+ DownloadsButton.uninit();
+
// Now either cancel delayedStartup, or clean up the services initialized from
// it.
if (this._boundDelayedStartup) {
this._cancelDelayedStartup();
} else {
if (Win7Features)
Win7Features.onCloseWindow();
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -934,17 +934,19 @@
class="toolbarbutton-1 chromeclass-toolbar-additional badged-button"
key="key_openDownloads"
onmousedown="DownloadsIndicatorView.onCommand(event);"
ondrop="DownloadsIndicatorView.onDrop(event);"
ondragover="DownloadsIndicatorView.onDragOver(event);"
ondragenter="DownloadsIndicatorView.onDragOver(event);"
label="&downloads.label;"
removable="true"
+ overflows="false"
cui-areatype="toolbar"
+ hidden="true"
tooltip="dynamic-shortcut-tooltip"/>
<toolbarbutton id="library-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
removable="true"
onmousedown="PanelUI.showSubView('appMenu-libraryView', this, null, event);"
closemenu="none"
cui-areatype="toolbar"
tooltiptext="&libraryButton.tooltip;"
--- a/browser/base/content/test/general/browser_documentnavigation.js
+++ b/browser/base/content/test/general/browser_documentnavigation.js
@@ -69,17 +69,17 @@ async function expectFocusOnF6(backward,
}
if (gMultiProcessBrowser && onContent) {
expectedDocument = "main-window";
expectedElement = gBrowser.selectedBrowser;
}
is(fm.focusedWindow.document.documentElement.id, expectedDocument, desc + " document matches");
- is(fm.focusedElement, expectedElement, desc + " element matches");
+ is(fm.focusedElement, expectedElement, desc + " element matches (wanted: " + expectedElement.id + " got: " + fm.focusedElement.id + ")");
if (onContent) {
window.messageManager.removeMessageListener("BrowserTest:FocusChanged", focusChangedListener);
}
}
// Load a page and navigate between it and the chrome window.
add_task(async function() {
@@ -166,18 +166,19 @@ add_task(async function() {
false, "back focus with sidebar open sidebar");
await expectFocusOnF6(true, "main-window", gURLBar.inputField,
false, "back focus with sidebar urlbar");
SidebarUI.toggle("viewBookmarksSidebar");
});
// Navigate when the downloads panel is open
-add_task(async function() {
- await pushPrefs(["accessibility.tabfocus", 7]);
+add_task(async function test_download_focus() {
+ await pushPrefs(["accessibility.tabfocus", 7], ["browser.download.autohideButton", false]);
+ await promiseButtonShown("downloads-button");
let popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown", true);
EventUtils.synthesizeMouseAtCenter(document.getElementById("downloads-button"), { });
await popupShownPromise;
gURLBar.focus();
await expectFocusOnF6(false, "main-window", document.getElementById("downloadsHistory"),
false, "focus with downloads panel open panel");
@@ -248,8 +249,17 @@ add_task(async function() {
true, "back focus on frameset frame 0");
await expectFocusOnF6(true, "main-window", gURLBar.inputField,
false, "back focus on frameset frame urlbar");
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
});
// XXXndeakin add tests for browsers inside of panels
+
+function promiseButtonShown(id) {
+ let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+ return BrowserTestUtils.waitForCondition(() => {
+ let target = document.getElementById(id);
+ let bounds = dwu.getBoundsWithoutFlushing(target);
+ return bounds.width > 0 && bounds.height > 0;
+ }, `Waiting for button ${id} to have non-0 size`);
+}
--- a/browser/base/content/test/urlbar/browser_dragdropURL.js
+++ b/browser/base/content/test/urlbar/browser_dragdropURL.js
@@ -4,35 +4,35 @@ const TEST_URL = "data:text/html,a test
const DRAG_URL = "http://www.example.com/";
const DRAG_FORBIDDEN_URL = "chrome://browser/content/aboutDialog.xul";
const DRAG_TEXT = "Firefox is awesome";
const DRAG_WORD = "Firefox";
add_task(async function checkDragURL() {
await BrowserTestUtils.withNewTab(TEST_URL, function(browser) {
// Have to use something other than the URL bar as a source, so picking the
- // downloads button somewhat arbitrarily:
- EventUtils.synthesizeDrop(document.getElementById("downloads-button"), gURLBar,
+ // home button somewhat arbitrarily:
+ EventUtils.synthesizeDrop(document.getElementById("home-button"), gURLBar,
[[{type: "text/plain", data: DRAG_URL}]], "copy", window);
is(gURLBar.value, TEST_URL, "URL bar value should not have changed");
is(gBrowser.selectedBrowser.userTypedValue, null, "Stored URL bar value should not have changed");
});
});
add_task(async function checkDragForbiddenURL() {
await BrowserTestUtils.withNewTab(TEST_URL, function(browser) {
- EventUtils.synthesizeDrop(document.getElementById("downloads-button"), gURLBar,
+ EventUtils.synthesizeDrop(document.getElementById("home-button"), gURLBar,
[[{type: "text/plain", data: DRAG_FORBIDDEN_URL}]], "copy", window);
isnot(gURLBar.value, DRAG_FORBIDDEN_URL, "Shouldn't be allowed to drop forbidden URL on URL bar");
});
});
add_task(async function checkDragText() {
await BrowserTestUtils.withNewTab(TEST_URL, function(browser) {
- EventUtils.synthesizeDrop(document.getElementById("downloads-button"), gURLBar,
+ EventUtils.synthesizeDrop(document.getElementById("home-button"), gURLBar,
[[{type: "text/plain", data: DRAG_TEXT}]], "copy", window);
is(gURLBar.value, DRAG_TEXT, "Dragging normal text should replace the URL bar value");
- EventUtils.synthesizeDrop(document.getElementById("downloads-button"), gURLBar,
+ EventUtils.synthesizeDrop(document.getElementById("home-button"), gURLBar,
[[{type: "text/plain", data: DRAG_WORD}]], "copy", window);
is(gURLBar.value, DRAG_WORD, "Dragging a single word should replace the URL bar value");
});
});
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -35,16 +35,17 @@ const kNSXUL = "http://www.mozilla.org/k
const kSpecialWidgetPfx = "customizableui-special-";
const kPrefCustomizationState = "browser.uiCustomization.state";
const kPrefCustomizationAutoAdd = "browser.uiCustomization.autoAdd";
const kPrefCustomizationDebug = "browser.uiCustomization.debug";
const kPrefDrawInTitlebar = "browser.tabs.drawInTitlebar";
const kPrefUIDensity = "browser.uidensity";
const kPrefAutoTouchMode = "browser.touchmode.auto";
+const kPrefAutoHideDownloadsButton = "browser.download.autohideButton";
const kExpectedWindowURL = "chrome://browser/content/browser.xul";
/**
* The keys are the handlers that are fired when the event type (the value)
* is fired on the subview. A widget that provides a subview has the option
* of providing onViewShowing and onViewHiding event handlers.
*/
@@ -172,17 +173,17 @@ XPCOMUtils.defineLazyGetter(this, "log",
var CustomizableUIInternal = {
initialize() {
log.debug("Initializing");
this.addListener(this);
this._defineBuiltInWidgets();
this.loadSavedState();
- this._introduceNewBuiltinWidgets();
+ this._updateForNewVersion();
this._markObsoleteBuiltinButtonsSeen();
this.registerArea(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL, {
type: CustomizableUI.TYPE_MENU_PANEL,
defaultPlacements: [],
anchor: "nav-bar-overflow-button",
}, true);
@@ -257,17 +258,17 @@ var CustomizableUIInternal = {
},
_defineBuiltInWidgets() {
for (let widgetDefinition of CustomizableWidgets) {
this.createBuiltinWidget(widgetDefinition);
}
},
- _introduceNewBuiltinWidgets() {
+ _updateForNewVersion() {
// We should still enter even if gSavedState.currentVersion >= kVersion
// because the per-widget pref facility is independent of versioning.
if (!gSavedState) {
// Flip all the prefs so we don't try to re-introduce later:
for (let [, widget] of gPalette) {
if (widget.defaultArea && widget._introducedInVersion === "pref") {
let prefId = "browser.toolbarbuttons.introduced." + widget.id;
Services.prefs.setBoolPref(prefId, true);
@@ -2592,25 +2593,27 @@ var CustomizableUIInternal = {
_resetUIState() {
try {
gUIStateBeforeReset.drawInTitlebar = Services.prefs.getBoolPref(kPrefDrawInTitlebar);
gUIStateBeforeReset.uiCustomizationState = Services.prefs.getCharPref(kPrefCustomizationState);
gUIStateBeforeReset.uiDensity = Services.prefs.getIntPref(kPrefUIDensity);
gUIStateBeforeReset.autoTouchMode = Services.prefs.getBoolPref(kPrefAutoTouchMode);
gUIStateBeforeReset.currentTheme = LightweightThemeManager.currentTheme;
+ gUIStateBeforeReset.autoHideDownloadsButton = Services.prefs.getBoolPref(kPrefAutoHideDownloadsButton);
gUIStateBeforeReset.newElementCount = gNewElementCount;
} catch (e) { }
this._resetExtraToolbars();
Services.prefs.clearUserPref(kPrefCustomizationState);
Services.prefs.clearUserPref(kPrefDrawInTitlebar);
Services.prefs.clearUserPref(kPrefUIDensity);
Services.prefs.clearUserPref(kPrefAutoTouchMode);
+ Services.prefs.clearUserPref(kPrefAutoHideDownloadsButton);
LightweightThemeManager.currentTheme = null;
gNewElementCount = 0;
log.debug("State reset");
// Reset placements to make restoring default placements possible.
gPlacements = new Map();
gDirtyAreaCache = new Set();
gSeenWidgets = new Set();
@@ -2669,31 +2672,31 @@ var CustomizableUIInternal = {
*/
undoReset() {
if (gUIStateBeforeReset.uiCustomizationState == null ||
gUIStateBeforeReset.drawInTitlebar == null) {
return;
}
gUndoResetting = true;
- let uiCustomizationState = gUIStateBeforeReset.uiCustomizationState;
- let drawInTitlebar = gUIStateBeforeReset.drawInTitlebar;
- let currentTheme = gUIStateBeforeReset.currentTheme;
- let uiDensity = gUIStateBeforeReset.uiDensity;
- let autoTouchMode = gUIStateBeforeReset.autoTouchMode;
+ const {
+ uiCustomizationState, drawInTitlebar, currentTheme, uiDensity,
+ autoTouchMode, autoHideDownloadsButton,
+ } = gUIStateBeforeReset;
gNewElementCount = gUIStateBeforeReset.newElementCount;
// Need to clear the previous state before setting the prefs
// because pref observers may check if there is a previous UI state.
this._clearPreviousUIState();
Services.prefs.setCharPref(kPrefCustomizationState, uiCustomizationState);
Services.prefs.setBoolPref(kPrefDrawInTitlebar, drawInTitlebar);
Services.prefs.setIntPref(kPrefUIDensity, uiDensity);
Services.prefs.setBoolPref(kPrefAutoTouchMode, autoTouchMode);
+ Services.prefs.setBoolPref(kPrefAutoHideDownloadsButton, autoHideDownloadsButton);
LightweightThemeManager.currentTheme = currentTheme;
this.loadSavedState();
// If the user just customizes toolbar/titlebar visibility, gSavedState will be null
// and we don't need to do anything else here:
if (gSavedState) {
for (let areaId of Object.keys(gSavedState.placements)) {
let placements = gSavedState.placements[areaId];
gPlacements.set(areaId, placements);
--- a/browser/components/customizableui/test/browser_1042100_default_placements_update.js
+++ b/browser/components/customizableui/test/browser_1042100_default_placements_update.js
@@ -14,17 +14,17 @@ function test() {
let oldState = CustomizableUIBSPass.gSavedState;
registerCleanupFunction(() => CustomizableUIBSPass.gSavedState = oldState );
is(CustomizableUIBSPass.gFuturePlacements.size, 0,
"All future placements should be dealt with by now.");
let {CustomizableUIInternal, gFuturePlacements, gPalette} = CustomizableUIBSPass;
- CustomizableUIInternal._introduceNewBuiltinWidgets();
+ CustomizableUIInternal._updateForNewVersion();
is(gFuturePlacements.size, 0,
"No change to future placements initially.");
let currentVersion = CustomizableUIBSPass.kVersion;
// Add our widget to the defaults:
let testWidgetNew = {
@@ -62,17 +62,17 @@ function test() {
CustomizableUIBSPass.kVersion++;
let hadSavedState = !!CustomizableUIBSPass.gSavedState
if (!hadSavedState) {
CustomizableUIBSPass.gSavedState = {currentVersion: CustomizableUIBSPass.kVersion - 1};
}
// Then call the re-init routine so we re-add the builtin widgets
- CustomizableUIInternal._introduceNewBuiltinWidgets();
+ CustomizableUIInternal._updateForNewVersion();
is(gFuturePlacements.size, 1,
"Should have 1 more future placement");
let haveNavbarPlacements = gFuturePlacements.has(CustomizableUI.AREA_NAVBAR);
ok(haveNavbarPlacements, "Should have placements for nav-bar");
if (haveNavbarPlacements) {
let placements = [...gFuturePlacements.get(CustomizableUI.AREA_NAVBAR)];
// Ignore widgets that are placed using the pref facility and not the
@@ -101,17 +101,17 @@ function test() {
CustomizableUIBSPass.gSavedState = {
currentVersion: 6,
placements: {
"nav-bar": ["urlbar-container", "bookmarks-menu-button"],
"PanelUI-contents": ["panic-button", "edit-controls"],
},
};
- CustomizableUIInternal._introduceNewBuiltinWidgets();
+ CustomizableUIInternal._updateForNewVersion();
let navbarPlacements = CustomizableUIBSPass.gSavedState.placements["nav-bar"];
let springs = navbarPlacements.filter(id => id.includes("spring"));
is(springs.length, 2, "Should have 2 toolbarsprings in placements now");
navbarPlacements = navbarPlacements.filter(id => !id.includes("spring"));
is(navbarPlacements[0], "back-button", "Back button is in the right place.");
is(navbarPlacements[1], "forward-button", "Fwd button is in the right place.");
is(navbarPlacements[2], "stop-reload-button", "Stop/reload button is in the right place.");
is(navbarPlacements[3], "home-button", "Home button is in the right place.");
--- a/browser/components/customizableui/test/browser_1161838_inserted_new_default_buttons.js
+++ b/browser/components/customizableui/test/browser_1161838_inserted_new_default_buttons.js
@@ -12,17 +12,17 @@ function test() {
"All future placements should be dealt with by now.");
let {CustomizableUIInternal, gFuturePlacements, gPalette} = CustomizableUIBSPass;
// Force us to have a saved state:
CustomizableUIInternal.saveState();
CustomizableUIInternal.loadSavedState();
- CustomizableUIInternal._introduceNewBuiltinWidgets();
+ CustomizableUIInternal._updateForNewVersion();
is(gFuturePlacements.size, 0,
"No change to future placements initially.");
// Add our widget to the defaults:
let testWidgetNew = {
id: "test-messing-with-default-placements-new-pref",
label: "Test messing with default placements - pref-based",
defaultArea: CustomizableUI.AREA_NAVBAR,
@@ -39,17 +39,17 @@ function test() {
// Now adjust default placements for area:
let navbarArea = CustomizableUIBSPass.gAreas.get(CustomizableUI.AREA_NAVBAR);
let navbarPlacements = navbarArea.get("defaultPlacements");
navbarPlacements.splice(navbarPlacements.indexOf("bookmarks-menu-button") + 1, 0, testWidgetNew.id);
let savedPlacements = CustomizableUIBSPass.gSavedState.placements[CustomizableUI.AREA_NAVBAR];
// Then call the re-init routine so we re-add the builtin widgets
- CustomizableUIInternal._introduceNewBuiltinWidgets();
+ CustomizableUIInternal._updateForNewVersion();
is(gFuturePlacements.size, 1,
"Should have 1 more future placement");
let futureNavbarPlacements = gFuturePlacements.get(CustomizableUI.AREA_NAVBAR);
ok(futureNavbarPlacements, "Should have placements for nav-bar");
if (futureNavbarPlacements) {
ok(futureNavbarPlacements.has(testWidgetNew.id), "widget should be in future placements");
}
CustomizableUIInternal._placeNewDefaultWidgetsInArea(CustomizableUI.AREA_NAVBAR);
--- a/browser/components/customizableui/test/browser_918049_skipintoolbarset_dnd.js
+++ b/browser/components/customizableui/test/browser_918049_skipintoolbarset_dnd.js
@@ -11,27 +11,27 @@ var skippedItem;
add_task(async function() {
navbar = document.getElementById("nav-bar");
skippedItem = document.createElement("toolbarbutton");
skippedItem.id = "test-skipintoolbarset-item";
skippedItem.setAttribute("label", "Test");
skippedItem.setAttribute("skipintoolbarset", "true");
skippedItem.setAttribute("removable", "true");
navbar.customizationTarget.appendChild(skippedItem);
- let downloadsButton = document.getElementById("downloads-button");
+ let libraryButton = document.getElementById("library-button");
await startCustomizing();
ok(CustomizableUI.inDefaultState, "Should still be in default state");
- simulateItemDrag(skippedItem, downloadsButton);
+ simulateItemDrag(skippedItem, libraryButton);
ok(CustomizableUI.inDefaultState, "Should still be in default state");
let skippedItemWrapper = skippedItem.parentNode;
is(skippedItemWrapper.nextSibling && skippedItemWrapper.nextSibling.id,
- downloadsButton.parentNode.id, "Should be next to downloads button");
- simulateItemDrag(downloadsButton, skippedItem);
- let downloadWrapper = downloadsButton.parentNode;
- is(downloadWrapper.nextSibling && downloadWrapper.nextSibling.id,
+ libraryButton.parentNode.id, "Should be next to library button");
+ simulateItemDrag(libraryButton, skippedItem);
+ let libraryWrapper = libraryButton.parentNode;
+ is(libraryWrapper.nextSibling && libraryWrapper.nextSibling.id,
skippedItem.parentNode.id, "Should be next to skipintoolbarset item");
ok(CustomizableUI.inDefaultState, "Should still be in default state");
});
add_task(async function asyncCleanup() {
await endCustomizing();
skippedItem.remove();
await resetCustomization();
--- a/browser/components/customizableui/test/browser_923857_customize_mode_event_wrapping_during_reset.js
+++ b/browser/components/customizableui/test/browser_923857_customize_mode_event_wrapping_during_reset.js
@@ -3,21 +3,21 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// Customize mode reset button should revert correctly
add_task(async function() {
await startCustomizing();
let devButton = document.getElementById("developer-button");
- let downloadsButton = document.getElementById("downloads-button");
+ let libraryButton = document.getElementById("library-button");
let homeButton = document.getElementById("home-button");
let palette = document.getElementById("customization-palette");
- ok(devButton && downloadsButton && homeButton && palette, "Stuff should exist");
- simulateItemDrag(devButton, downloadsButton);
+ ok(devButton && libraryButton && homeButton && palette, "Stuff should exist");
+ simulateItemDrag(devButton, libraryButton);
simulateItemDrag(homeButton, palette);
await gCustomizeMode.reset();
ok(CustomizableUI.inDefaultState, "Should be back in default state");
await endCustomizing();
});
add_task(async function asyncCleanup() {
await resetCustomization();
--- a/browser/components/customizableui/test/browser_927717_customize_drag_empty_toolbar.js
+++ b/browser/components/customizableui/test/browser_927717_customize_drag_empty_toolbar.js
@@ -5,21 +5,21 @@
"use strict";
const kTestToolbarId = "test-empty-drag";
// Attempting to drag an item to an empty container should work.
add_task(async function() {
await createToolbarWithPlacements(kTestToolbarId, []);
await startCustomizing();
- let downloadButton = document.getElementById("downloads-button");
+ let libraryButton = document.getElementById("library-button");
let customToolbar = document.getElementById(kTestToolbarId);
- simulateItemDrag(downloadButton, customToolbar);
- assertAreaPlacements(kTestToolbarId, ["downloads-button"]);
- ok(downloadButton.parentNode && downloadButton.parentNode.parentNode == customToolbar,
+ simulateItemDrag(libraryButton, customToolbar);
+ assertAreaPlacements(kTestToolbarId, ["library-button"]);
+ ok(libraryButton.parentNode && libraryButton.parentNode.parentNode == customToolbar,
"Button should really be in toolbar");
await endCustomizing();
removeCustomToolbars();
});
add_task(async function asyncCleanup() {
await endCustomizing();
await resetCustomization();
--- a/browser/components/customizableui/test/browser_968565_insert_before_hidden_items.js
+++ b/browser/components/customizableui/test/browser_968565_insert_before_hidden_items.js
@@ -39,18 +39,18 @@ add_task(async function() {
// Make sure we have some hidden items at the end of the nav-bar.
navbar.insertItem(hidden1.id);
navbar.insertItem(hidden2.id);
// Drag an item and drop it onto the nav-bar customization target, but
// not over a particular item.
await startCustomizing();
- let downloadsButton = document.getElementById("downloads-button");
- simulateItemDrag(downloadsButton, navbar.customizationTarget);
+ let homeButton = document.getElementById("home-button");
+ simulateItemDrag(homeButton, navbar.customizationTarget);
await endCustomizing();
- is(downloadsButton.previousSibling.id, lastVisible.id,
+ is(homeButton.previousSibling.id, lastVisible.id,
"The downloads button should be placed after the last visible item.");
await resetCustomization();
});
--- a/browser/components/customizableui/test/browser_overflow_use_subviews.js
+++ b/browser/components/customizableui/test/browser_overflow_use_subviews.js
@@ -1,19 +1,21 @@
"use strict";
const kOverflowPanel = document.getElementById("widget-overflow");
var gOriginalWidth;
-registerCleanupFunction(async function() {
+async function stopOverflowing() {
kOverflowPanel.removeAttribute("animate");
window.resizeTo(gOriginalWidth, window.outerHeight);
await waitForCondition(() => !document.getElementById("nav-bar").hasAttribute("overflowing"));
CustomizableUI.reset();
-});
+}
+
+registerCleanupFunction(stopOverflowing);
/**
* This checks that subview-compatible items show up as subviews rather than
* re-anchored panels. If we ever remove the developer widget, please
* replace this test with another subview - don't remove it.
*/
add_task(async function check_developer_subview_in_overflow() {
kOverflowPanel.setAttribute("animate", "false");
@@ -37,33 +39,35 @@ add_task(async function check_developer_
let subviewShownPromise = subviewShown(developerView);
button.click();
await subviewShownPromise;
let hasSubviews = !!kOverflowPanel.querySelector("photonpanelmultiview,panelmultiview");
let expectedPanel = hasSubviews ? kOverflowPanel : document.getElementById("customizationui-widget-panel");
is(developerView.closest("panel"), expectedPanel, "Should be inside the panel");
expectedPanel.hidePopup();
await Promise.resolve(); // wait for popup to hide fully.
+ await stopOverflowing();
});
/**
* 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 navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
- ok(navbar.hasAttribute("overflowing"), "Should still be overflowing");
+ let button = document.getElementById("downloads-button");
+ gCustomizeMode.addToPanel(button);
+ await waitForOverflowButtonShown();
+
let chevron = document.getElementById("nav-bar-overflow-button");
let shownPanelPromise = promisePanelElementShown(window, kOverflowPanel);
chevron.click();
await shownPanelPromise;
- let button = document.getElementById("downloads-button");
button.click();
await waitForCondition(() => {
let panel = document.getElementById("downloadsPanel");
return panel && panel.state != "closed";
});
let downloadsPanel = document.getElementById("downloadsPanel");
isnot(downloadsPanel.state, "closed", "Should be attempting to show the downloads panel.");
downloadsPanel.hidePopup();
--- a/browser/components/downloads/content/downloads.js
+++ b/browser/components/downloads/content/downloads.js
@@ -218,16 +218,19 @@ var DownloadsPanel = {
DownloadsCommon.log("Opening the downloads panel.");
if (this.isPanelShowing) {
DownloadsCommon.log("Panel is already showing - focusing instead.");
this._focusPanel();
return;
}
+ // As a belt-and-suspenders check, ensure the button is not hidden.
+ DownloadsButton.unhide();
+
this.initialize(() => {
// Delay displaying the panel because this function will sometimes be
// called while another window is closing (like the window for selecting
// whether to save or open the file), and that would cause the panel to
// close immediately.
setTimeout(() => this._openPopupIfDataReady(), 0);
});
--- a/browser/components/downloads/content/indicator.js
+++ b/browser/components/downloads/content/indicator.js
@@ -50,99 +50,57 @@ const DownloadsButton = {
* Returns a reference to the downloads button position placeholder, or null
* if not available because it has been removed from the toolbars.
*/
get _placeholder() {
return document.getElementById("downloads-button");
},
/**
+ * Indicates whether toolbar customization is in progress.
+ */
+ _customizing: false,
+
+ /**
* This function is called asynchronously just after window initialization.
*
* NOTE: This function should limit the input/output it performs to improve
* startup time.
*/
initializeIndicator() {
DownloadsIndicatorView.ensureInitialized();
},
/**
- * Indicates whether toolbar customization is in progress.
- */
- _customizing: false,
-
- /**
- * This function is called when toolbar customization starts.
- *
- * During customization, we never show the actual download progress indication
- * or the event notifications, but we show a neutral placeholder. The neutral
- * placeholder is an ordinary button defined in the browser window that can be
- * moved freely between the toolbars and the customization palette.
- */
- customizeStart() {
- // Prevent the indicator from being displayed as a temporary anchor
- // during customization, even if requested using the getAnchor method.
- this._customizing = true;
- this._anchorRequested = false;
- },
-
- /**
- * This function is called when toolbar customization ends.
- */
- customizeDone() {
- this._customizing = false;
- DownloadsIndicatorView.afterCustomize();
- },
-
- /**
* Determines the position where the indicator should appear, and moves its
* associated element to the new position.
*
* @return Anchor element, or null if the indicator is not visible.
*/
_getAnchorInternal() {
let indicator = DownloadsIndicatorView.indicator;
if (!indicator) {
// Exit now if the indicator overlay isn't loaded yet, or if the button
// is not in the document.
return null;
}
indicator.open = this._anchorRequested;
- let widget = CustomizableUI.getWidget("downloads-button")
- .forWindow(window);
+ let widget = CustomizableUI.getWidget("downloads-button");
// Determine if the indicator is located on an invisible toolbar.
- if (!isElementVisible(indicator.parentNode) && !widget.overflowed) {
+ if (!isElementVisible(indicator.parentNode) &&
+ widget.areaType == CustomizableUI.TYPE_TOOLBAR) {
return null;
}
return DownloadsIndicatorView.indicatorAnchor;
},
/**
- * Checks whether the indicator is, or will soon be visible in the browser
- * window.
- *
- * @param aCallback
- * Called once the indicator overlay has loaded. Gets a boolean
- * argument representing the indicator visibility.
- */
- checkIsVisible(aCallback) {
- DownloadsOverlayLoader.ensureOverlayLoaded(this.kIndicatorOverlay, () => {
- if (!this._placeholder) {
- aCallback(false);
- } else {
- let element = DownloadsIndicatorView.indicator || this._placeholder;
- aCallback(isElementVisible(element.parentNode));
- }
- });
- },
-
- /**
* Indicates whether we should try and show the indicator temporarily as an
* anchor for the panel, even if the indicator would be hidden by default.
*/
_anchorRequested: false,
/**
* Ensures that there is an anchor available for the panel.
*
@@ -167,16 +125,101 @@ const DownloadsButton = {
/**
* Allows the temporary anchor to be hidden.
*/
releaseAnchor() {
this._anchorRequested = false;
this._getAnchorInternal();
},
+ unhide() {
+ let button = this._placeholder;
+ if (button && button.hasAttribute("hidden")) {
+ button.removeAttribute("hidden");
+ if (this._navBar.contains(button)) {
+ this._navBar.setAttribute("downloadsbuttonshown", "true");
+ }
+ }
+ },
+
+ hide() {
+ let button = this._placeholder;
+ if (this.autoHideDownloadsButton && button && button.closest("toolbar")) {
+ DownloadsPanel.hidePanel();
+ button.setAttribute("hidden", "true");
+ this._navBar.removeAttribute("downloadsbuttonshown");
+ }
+ },
+
+ startAutoHide() {
+ if (DownloadsIndicatorView.hasDownloads) {
+ this.unhide();
+ } else {
+ this.hide();
+ }
+ },
+
+ checkForAutoHide() {
+ let button = this._placeholder;
+ if (!this._customizing && this.autoHideDownloadsButton &&
+ button && button.closest("toolbar")) {
+ this.startAutoHide();
+ } else {
+ this.unhide();
+ }
+ },
+
+ // Callback from CustomizableUI when nodes get moved around.
+ // We use this to track whether our node has moved somewhere
+ // where we should (not) autohide it.
+ onWidgetAfterDOMChange(node) {
+ if (node == this._placeholder) {
+ this.checkForAutoHide();
+ }
+ },
+
+ /**
+ * This function is called when toolbar customization starts.
+ *
+ * During customization, we never show the actual download progress indication
+ * or the event notifications, but we show a neutral placeholder. The neutral
+ * placeholder is an ordinary button defined in the browser window that can be
+ * moved freely between the toolbars and the customization palette.
+ */
+ onCustomizeStart(win) {
+ if (win == window) {
+ // Prevent the indicator from being displayed as a temporary anchor
+ // during customization, even if requested using the getAnchor method.
+ this._customizing = true;
+ this._anchorRequested = false;
+ this.unhide();
+ }
+ },
+
+ onCustomizeEnd(win) {
+ if (win == window) {
+ this._customizing = false;
+ this.checkForAutoHide();
+ DownloadsIndicatorView.afterCustomize();
+ }
+ },
+
+ init() {
+ XPCOMUtils.defineLazyPreferenceGetter(
+ this, "autoHideDownloadsButton", "browser.download.autohideButton",
+ true, this.checkForAutoHide.bind(this));
+
+ CustomizableUI.addListener(this);
+ this.checkForAutoHide();
+ },
+
+ uninit() {
+ CustomizableUI.removeListener(this);
+ },
+
get _tabsToolbar() {
delete this._tabsToolbar;
return this._tabsToolbar = document.getElementById("TabsToolbar");
},
get _navBar() {
delete this._navBar;
return this._navBar = document.getElementById("nav-bar");
@@ -337,17 +380,17 @@ const DownloadsIndicatorView = {
// 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);
- if (widget.overflowed || widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL) {
+ if (widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL) {
if (anchor && this._isAncestorPanelOpen(anchor)) {
// If the containing panel is open, don't do anything, because the
// notification would appear under the open panel. See
// https://bugzilla.mozilla.org/show_bug.cgi?id=984023
return;
}
// Otherwise, try to use the anchor of the panel:
@@ -418,18 +461,22 @@ const DownloadsIndicatorView = {
* Indicates whether the indicator should be shown because there are some
* downloads to be displayed.
*/
set hasDownloads(aValue) {
if (this._hasDownloads != aValue || (!this._operational && aValue)) {
this._hasDownloads = aValue;
// If there is at least one download, ensure that the view elements are
+ // operational
if (aValue) {
+ DownloadsButton.unhide();
this._ensureOperational();
+ } else {
+ DownloadsButton.checkForAutoHide();
}
}
return aValue;
},
get hasDownloads() {
return this._hasDownloads;
},
_hasDownloads: false,
@@ -503,24 +550,17 @@ const DownloadsIndicatorView = {
DownloadsIndicatorView.ensureTerminated();
},
onCommand(aEvent) {
if (aEvent.type == "mousedown" && aEvent.button != 0) {
return;
}
- // If the downloads button is in the menu panel, open the Library
- let widgetGroup = CustomizableUI.getWidget("downloads-button");
- if (widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL) {
- DownloadsPanel.showDownloadsHistory();
- } else {
- DownloadsPanel.showPanel();
- }
-
+ DownloadsPanel.showPanel();
aEvent.stopPropagation();
},
onDragOver(aEvent) {
browserDragAndDrop.dragOver(aEvent);
},
onDrop(aEvent) {
@@ -562,20 +602,19 @@ const DownloadsIndicatorView = {
if (!indicator || indicator.getAttribute("indicator") != "true") {
return null;
}
return this._indicator = indicator;
},
get indicatorAnchor() {
- let widget = CustomizableUI.getWidget("downloads-button")
- .forWindow(window);
- if (widget.overflowed) {
- return widget.anchor;
+ let widgetGroup = CustomizableUI.getWidget("downloads-button");
+ if (widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL) {
+ return widgetGroup.forWindow(window).anchor;
}
return document.getElementById("downloads-indicator-anchor");
},
get _progressIcon() {
return this.__progressIcon ||
(this.__progressIcon = document.getElementById("downloads-indicator-progress-inner"));
},
--- a/browser/components/downloads/test/browser/browser.ini
+++ b/browser/components/downloads/test/browser/browser.ini
@@ -8,8 +8,9 @@ skip-if = os == "linux" # Bug 949434
skip-if = os == "linux" # Bug 952422
[browser_confirm_unblock_download.js]
[browser_iframe_gone_mid_download.js]
[browser_indicatorDrop.js]
[browser_libraryDrop.js]
[browser_downloads_panel_block.js]
skip-if = true # Bug 1352792
[browser_downloads_panel_height.js]
+[browser_downloads_autohide.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/downloads/test/browser/browser_downloads_autohide.js
@@ -0,0 +1,252 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const kDownloadAutoHidePref = "browser.download.autohideButton";
+
+registerCleanupFunction(async function() {
+ Services.prefs.clearUserPref(kDownloadAutoHidePref);
+ if (document.documentElement.hasAttribute("customizing")) {
+ await gCustomizeMode.reset();
+ await promiseCustomizeEnd();
+ } else {
+ CustomizableUI.reset();
+ }
+});
+
+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);
+ ok(!downloadsButton.hasAttribute("hidden"),
+ "Button shouldn't be hidden in the panel");
+ gCustomizeMode.addToToolbar(downloadsButton);
+ ok(downloadsButton.hasAttribute("hidden"),
+ "Button should be hidden again in the toolbar");
+ Services.prefs.setBoolPref(kDownloadAutoHidePref, false);
+ ok(!downloadsButton.hasAttribute("hidden"),
+ "Button shouldn't be hidden with autohide turned off");
+ 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);
+ 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");
+ gCustomizeMode.addToToolbar(downloadsButton);
+ ok(downloadsButton.hasAttribute("hidden"),
+ "Button should be hidden again in the toolbar");
+ gCustomizeMode.removeFromArea(downloadsButton);
+ Services.prefs.setBoolPref(kDownloadAutoHidePref, false);
+ // Can't use gCustomizeMode.addToToolbar here because it doesn't work for
+ // palette items if the window isn't in customize mode:
+ CustomizableUI.addWidgetToArea(downloadsButton.id, CustomizableUI.AREA_NAVBAR);
+ ok(!downloadsButton.hasAttribute("hidden"),
+ "Button should be unhidden again in the toolbar " +
+ "even if the pref was flipped while the button was in the palette");
+ Services.prefs.setBoolPref(kDownloadAutoHidePref, true);
+});
+
+add_task(async function checkStateInCustomizeMode() {
+ ok(Services.prefs.getBoolPref("browser.download.autohideButton"),
+ "Should be autohiding the button");
+ let downloadsButton = document.getElementById("downloads-button");
+ await promiseCustomizeStart();
+ ok(!downloadsButton.hasAttribute("hidden"),
+ "Button should be shown in customize mode.");
+ 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 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 hidden if it's in the toolbar after customize mode.");
+ 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.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.");
+ await gCustomizeMode.reset();
+ await promiseCustomizeEnd();
+});
+
+add_task(async function checkStateInCustomizeModeMultipleWindows() {
+ ok(Services.prefs.getBoolPref("browser.download.autohideButton"),
+ "Should be autohiding the button");
+ let downloadsButton = document.getElementById("downloads-button");
+ await promiseCustomizeStart();
+ ok(!downloadsButton.hasAttribute("hidden"),
+ "Button should be shown in customize mode.");
+ let otherWin = await BrowserTestUtils.openNewBrowserWindow();
+ let otherDownloadsButton = otherWin.document.getElementById("downloads-button");
+ ok(otherDownloadsButton.hasAttribute("hidden"),
+ "Button should be hidden in the other window.");
+
+ 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.addToToolbar(downloadsButton);
+ ok(!downloadsButton.hasAttribute("hidden"),
+ "Button should still be shown in customize mode.");
+ ok(otherDownloadsButton.hasAttribute("hidden"),
+ "Button should be hidden again in the other window.");
+
+ Services.prefs.setBoolPref(kDownloadAutoHidePref, false);
+ ok(!downloadsButton.hasAttribute("hidden"),
+ "Button should be shown in customize mode");
+ ok(!otherDownloadsButton.hasAttribute("hidden"),
+ "Button should be shown in the other window with the pref flipped");
+
+ 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);
+ 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(!downloadsButton.hasAttribute("hidden"),
+ "Button should still be shown in customize mode.");
+ // Don't need to assert in the other window - button is gone there.
+
+ await gCustomizeMode.reset();
+ ok(Services.prefs.getBoolPref(kDownloadAutoHidePref),
+ "Autohide pref reset by reset()");
+ ok(!downloadsButton.hasAttribute("hidden"),
+ "Button should still be shown in customize mode.");
+ ok(otherDownloadsButton.hasAttribute("hidden"),
+ "Button should be hidden in the other window.");
+ ok(otherDownloadsButton.closest("#nav-bar"),
+ "Button should be back in the nav bar in the other window.");
+
+ await promiseCustomizeEnd();
+ ok(downloadsButton.hasAttribute("hidden"),
+ "Button should be hidden again outside of customize mode");
+ await BrowserTestUtils.closeWindow(otherWin);
+});
+
+add_task(async function checkStateForDownloads() {
+ ok(Services.prefs.getBoolPref("browser.download.autohideButton"),
+ "Should be autohiding the button");
+ let downloadsButton = document.getElementById("downloads-button");
+ ok(downloadsButton.hasAttribute("hidden"),
+ "Button should be hidden when there are no downloads.");
+
+ await task_addDownloads([
+ { state: DownloadsCommon.DOWNLOAD_DOWNLOADING },
+ ]);
+ ok(!downloadsButton.hasAttribute("hidden"),
+ "Button should be unhidden when there are downloads.");
+ let publicList = await Downloads.getList(Downloads.PUBLIC);
+ let downloads = await publicList.getAll();
+ for (let download of downloads) {
+ publicList.remove(download);
+ }
+ ok(downloadsButton.hasAttribute("hidden"),
+ "Button should be hidden when the download is removed");
+ await task_addDownloads([
+ { state: DownloadsCommon.DOWNLOAD_DOWNLOADING },
+ ]);
+ ok(!downloadsButton.hasAttribute("hidden"),
+ "Button should be unhidden when there are downloads.");
+
+ Services.prefs.setBoolPref(kDownloadAutoHidePref, false);
+ ok(!downloadsButton.hasAttribute("hidden"),
+ "Button should still be unhidden.");
+
+ downloads = await publicList.getAll();
+ for (let download of downloads) {
+ publicList.remove(download);
+ }
+ ok(!downloadsButton.hasAttribute("hidden"),
+ "Button should still be unhidden because the pref was flipped.");
+ Services.prefs.setBoolPref(kDownloadAutoHidePref, true);
+ ok(downloadsButton.hasAttribute("hidden"),
+ "Button should be hidden now that the pref flipped back " +
+ "because there were already no downloads.");
+
+ gCustomizeMode.addToPanel(downloadsButton);
+ ok(!downloadsButton.hasAttribute("hidden"),
+ "Button should not be hidden in the panel.");
+
+ await task_addDownloads([
+ { state: DownloadsCommon.DOWNLOAD_DOWNLOADING },
+ ]);
+
+ downloads = await publicList.getAll();
+ for (let download of downloads) {
+ publicList.remove(download);
+ }
+
+ ok(!downloadsButton.hasAttribute("hidden"),
+ "Button should still not be hidden in the panel " +
+ "when downloads count reaches 0 after being non-0.");
+});
+
+function promiseCustomizeStart(aWindow = window) {
+ return new Promise(resolve => {
+ aWindow.gNavToolbox.addEventListener("customizationready", resolve,
+ {once: true});
+ aWindow.gCustomizeMode.enter();
+ });
+}
+
+function promiseCustomizeEnd(aWindow = window) {
+ return new Promise(resolve => {
+ aWindow.gNavToolbox.addEventListener("aftercustomization", resolve,
+ {once: true});
+ aWindow.gCustomizeMode.exit();
+ });
+}
+
--- a/browser/components/downloads/test/browser/browser_downloads_panel_height.js
+++ b/browser/components/downloads/test/browser/browser_downloads_panel_height.js
@@ -4,16 +4,18 @@
"use strict";
/**
* This test exists because we use a <panelmultiview> element and it handles
* some of the height changes for us. We need to verify that the height is
* updated correctly if downloads are removed while the panel is hidden.
*/
add_task(async function test_height_reduced_after_removal() {
+ await SpecialPowers.pushPrefEnv({set: [["browser.download.autohideButton", false]]});
+ await promiseButtonShown("downloads-button");
await task_addDownloads([
{ state: DownloadsCommon.DOWNLOAD_FINISHED },
]);
await task_openPanel();
let panel = document.getElementById("downloadsPanel");
let heightBeforeRemoval = panel.getBoundingClientRect().height;
--- a/browser/components/downloads/test/browser/browser_first_download_panel.js
+++ b/browser/components/downloads/test/browser/browser_first_download_panel.js
@@ -5,16 +5,18 @@
/* eslint-disable mozilla/no-arbitrary-setTimeout */
/**
* Make sure the downloads panel only opens automatically on the first
* download it notices. All subsequent downloads, even across sessions, should
* not open the panel automatically.
*/
add_task(async function test_first_download_panel() {
+ await SpecialPowers.pushPrefEnv({set: [["browser.download.autohideButton", false]]});
+ await promiseButtonShown("downloads-button");
// Clear the download panel has shown preference first as this test is used to
// verify this preference's behaviour.
let oldPrefValue = Services.prefs.getBoolPref("browser.download.panel.shown");
Services.prefs.setBoolPref("browser.download.panel.shown", false);
registerCleanupFunction(async function() {
// Clean up when the test finishes.
await task_resetState();
@@ -27,16 +29,17 @@ add_task(async function test_first_downl
// Ensure that state is reset in case previous tests didn't finish.
await task_resetState();
// With this set to false, we should automatically open the panel the first
// time a download is started.
DownloadsCommon.getData(window).panelHasShownBefore = false;
+ info("waiting for panel open");
let promise = promisePanelOpened();
DownloadsCommon.getData(window)._notifyDownloadEvent("start");
await promise;
// If we got here, that means the panel opened.
DownloadsPanel.hidePanel();
ok(DownloadsCommon.getData(window).panelHasShownBefore,
--- a/browser/components/downloads/test/browser/browser_indicatorDrop.js
+++ b/browser/components/downloads/test/browser/browser_indicatorDrop.js
@@ -7,18 +7,20 @@ XPCOMUtils.defineLazyModuleGetter(this,
"resource://testing-common/httpd.js");
registerCleanupFunction(async function() {
await task_resetState();
await PlacesUtils.history.clear();
});
add_task(async function test_indicatorDrop() {
+ await SpecialPowers.pushPrefEnv({set: [["browser.download.autohideButton", false]]});
let downloadButton = document.getElementById("downloads-button");
ok(downloadButton, "download button present");
+ await promiseButtonShown(downloadButton.id);
let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
getService(Ci.mozIJSSubScriptLoader);
let EventUtils = {};
scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
async function task_drop(urls) {
let dragData = [[{type: "text/plain", data: urls.join("\n")}]];
--- a/browser/components/downloads/test/browser/browser_overflow_anchor.js
+++ b/browser/components/downloads/test/browser/browser_overflow_anchor.js
@@ -10,106 +10,43 @@ registerCleanupFunction(async function()
});
/**
* Make sure the downloads button and indicator overflows into the nav-bar
* chevron properly, and then when those buttons are clicked in the overflow
* panel that the downloads panel anchors to the chevron.
*/
add_task(async function test_overflow_anchor() {
+ await SpecialPowers.pushPrefEnv({set: [["browser.download.autohideButton", false]]});
// Ensure that state is reset in case previous tests didn't finish.
await task_resetState();
- // Record the original width of the window so we can put it back when
- // this test finishes.
- let oldWidth = window.outerWidth;
-
// 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");
- // Hack - we lock the size of the default flex-y items in the nav-bar, namely,
- // the URL input. That way we can resize the window without worrying about it
- // flexing.
- const kFlexyItems = ["urlbar-container"];
- registerCleanupFunction(() => unlockWidth(kFlexyItems));
- lockWidth(kFlexyItems);
-
- window.resizeTo(kForceOverflowWidthPx, window.outerHeight);
- await waitForOverflowed(button, true);
+ 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");
is(panel.anchorNode, chevron, "Panel should be anchored to the chevron.");
DownloadsPanel.hidePanel();
- // Unlock the widths on the flex-y items.
- unlockWidth(kFlexyItems);
-
- // Put the window back to its original dimensions.
- window.resizeTo(oldWidth, window.outerHeight);
-
- // The downloads button should eventually be un-overflowed.
- await waitForOverflowed(button, false);
+ gCustomizeMode.addToToolbar(button.node);
// Now try opening the panel again.
promise = promisePanelOpened();
EventUtils.sendMouseEvent({ type: "mousedown", button: 0 }, button.node);
await promise;
is(panel.anchorNode.id, "downloads-indicator-anchor");
DownloadsPanel.hidePanel();
});
-/**
- * For some node IDs, finds the nodes and sets their min-width's to their
- * current width, preventing them from flex-shrinking.
- *
- * @param aItemIDs an array of item IDs to set min-width on.
- */
-function lockWidth(aItemIDs) {
- for (let itemID of aItemIDs) {
- let item = document.getElementById(itemID);
- let curWidth = item.getBoundingClientRect().width + "px";
- item.style.minWidth = curWidth;
- }
-}
-
-/**
- * Clears the min-width's set on a set of IDs by lockWidth.
- *
- * @param aItemIDs an array of ItemIDs to remove min-width on.
- */
-function unlockWidth(aItemIDs) {
- for (let itemID of aItemIDs) {
- let item = document.getElementById(itemID);
- item.style.minWidth = "";
- }
-}
-
-/**
- * Waits for a node to enter or exit the overflowed state.
- *
- * @param aItem the node to wait for.
- * @param aIsOverflowed if we're waiting for the item to be overflowed.
- */
-function waitForOverflowed(aItem, aIsOverflowed) {
- if (aItem.overflowed == aIsOverflowed) {
- return Promise.resolve();
- }
-
- return new Promise(resolve => {
- let observer = new MutationObserver(function(aMutations) {
- if (aItem.overflowed == aIsOverflowed) {
- observer.disconnect();
- resolve();
- }
- });
- observer.observe(aItem.node, {attributes: true});
- });
-}
--- a/browser/components/downloads/test/browser/head.js
+++ b/browser/components/downloads/test/browser/head.js
@@ -193,8 +193,20 @@ function promiseAlertDialogOpen(buttonAc
doc.getButton(buttonAction).click();
resolve();
}
}, {once: true});
}
});
});
}
+
+/**
+ * Waits for a given button to become visible.
+ */
+function promiseButtonShown(id) {
+ let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+ return BrowserTestUtils.waitForCondition(() => {
+ let target = document.getElementById(id);
+ let bounds = dwu.getBoundsWithoutFlushing(target);
+ return bounds.width > 0 && bounds.height > 0;
+ }, `Waiting for button ${id} to have non-0 size`);
+}