Bug 1397447 - make downloads button autohide by default, r=mak
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Fri, 08 Sep 2017 14:16:36 +0100
changeset 429884 6e36419338c37e8faef6f6dd9591a97a78085b9b
parent 429883 1379a04912daa5d919b785333c265406051a1dc8
child 429885 364d4279ef9fc320811acdc5275877d38c080233
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak
bugs1397447
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1397447 - make downloads button autohide by default, r=mak MozReview-Commit-ID: E9izQpa4fFZ
browser/app/profile/firefox.js
browser/base/content/browser-customization.js
browser/base/content/browser.css
browser/base/content/browser.js
browser/base/content/browser.xul
browser/base/content/test/general/browser_documentnavigation.js
browser/base/content/test/urlbar/browser_dragdropURL.js
browser/components/customizableui/CustomizableUI.jsm
browser/components/customizableui/test/browser_1042100_default_placements_update.js
browser/components/customizableui/test/browser_1161838_inserted_new_default_buttons.js
browser/components/customizableui/test/browser_918049_skipintoolbarset_dnd.js
browser/components/customizableui/test/browser_923857_customize_mode_event_wrapping_during_reset.js
browser/components/customizableui/test/browser_927717_customize_drag_empty_toolbar.js
browser/components/customizableui/test/browser_968565_insert_before_hidden_items.js
browser/components/customizableui/test/browser_overflow_use_subviews.js
browser/components/downloads/content/downloads.js
browser/components/downloads/content/indicator.js
browser/components/downloads/test/browser/browser.ini
browser/components/downloads/test/browser/browser_downloads_autohide.js
browser/components/downloads/test/browser/browser_downloads_panel_height.js
browser/components/downloads/test/browser/browser_first_download_panel.js
browser/components/downloads/test/browser/browser_indicatorDrop.js
browser/components/downloads/test/browser/browser_overflow_anchor.js
browser/components/downloads/test/browser/head.js
--- 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`);
+}