Bug 1397447 - make downloads button autohide by default, r=mak

MozReview-Commit-ID: E9izQpa4fFZ
This commit is contained in:
Gijs Kruitbosch
2017-09-08 14:16:36 +01:00
parent 86e9a63e0e
commit d48e2eaf65
24 changed files with 496 additions and 191 deletions

View File

@@ -368,6 +368,10 @@ 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

View File

@@ -39,7 +39,6 @@ var CustomizationHandler = {
UpdateUrlbarSearchSplitterState();
PlacesToolbarHelper.customizeStart();
DownloadsButton.customizeStart();
},
_customizationEnding(aDetails) {
@@ -63,7 +62,6 @@ var CustomizationHandler = {
}
PlacesToolbarHelper.customizeDone();
DownloadsButton.customizeDone();
UpdateUrlbarSearchSplitterState();

View File

@@ -518,10 +518,9 @@ toolbar:not(#TabsToolbar) > #personal-bookmarks {
/* ::::: 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;
}
@@ -701,41 +700,69 @@ html|input.urlbar-input[textoverflow]:not([focused]) {
-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));
}
#identity-icon-labels {
max-width: 70px;
}
}
@media (max-width: 600px) {
#urlbar-container {
min-width: 40ch;
#nav-bar[downloadsbuttonshown] #urlbar-container {
min-width: 44ch;
}
#identity-icon-labels {
max-width: 60px;
}
}
@media (max-width: 500px) {
@media (max-width: 600px) {
#urlbar-container {
min-width: 35ch;
min-width: calc(39ch + 24px + 2 * var(--toolbarbutton-inner-padding));
}
#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;

View File

@@ -1321,6 +1321,10 @@ var gBrowserInit = {
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
@@ -1847,6 +1851,8 @@ var gBrowserInit = {
SidebarUI.uninit();
DownloadsButton.uninit();
// Now either cancel delayedStartup, or clean up the services initialized from
// it.
if (this._boundDelayedStartup) {

View File

@@ -939,7 +939,9 @@
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"

View File

@@ -74,7 +74,7 @@ async function expectFocusOnF6(backward, expectedDocument, expectedElement, onCo
}
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);
@@ -171,8 +171,9 @@ add_task(async function() {
});
// 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"), { });
@@ -253,3 +254,12 @@ add_task(async function() {
});
// 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`);
}

View File

@@ -9,8 +9,8 @@ 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");
@@ -19,7 +19,7 @@ add_task(async function checkDragURL() {
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");
});
@@ -27,11 +27,11 @@ add_task(async function checkDragForbiddenURL() {
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");
});

View File

@@ -40,6 +40,7 @@ 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";
@@ -177,7 +178,7 @@ var CustomizableUIInternal = {
this.addListener(this);
this._defineBuiltInWidgets();
this.loadSavedState();
this._introduceNewBuiltinWidgets();
this._updateForNewVersion();
this._markObsoleteBuiltinButtonsSeen();
this.registerArea(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL, {
@@ -262,7 +263,7 @@ var CustomizableUIInternal = {
}
},
_introduceNewBuiltinWidgets() {
_updateForNewVersion() {
// We should still enter even if gSavedState.currentVersion >= kVersion
// because the per-widget pref facility is independent of versioning.
if (!gSavedState) {
@@ -2597,6 +2598,7 @@ var CustomizableUIInternal = {
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) { }
@@ -2606,6 +2608,7 @@ var CustomizableUIInternal = {
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");
@@ -2674,11 +2677,10 @@ var CustomizableUIInternal = {
}
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
@@ -2689,6 +2691,7 @@ var CustomizableUIInternal = {
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

View File

@@ -19,7 +19,7 @@ function test() {
"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.");
@@ -67,7 +67,7 @@ function test() {
}
// 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);
@@ -106,7 +106,7 @@ function test() {
"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");

View File

@@ -17,7 +17,7 @@ function test() {
CustomizableUIInternal.saveState();
CustomizableUIInternal.loadSavedState();
CustomizableUIInternal._introduceNewBuiltinWidgets();
CustomizableUIInternal._updateForNewVersion();
is(gFuturePlacements.size, 0,
"No change to future placements initially.");
@@ -44,7 +44,7 @@ function test() {
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);

View File

@@ -16,17 +16,17 @@ add_task(async function() {
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");
});

View File

@@ -8,11 +8,11 @@
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");

View File

@@ -10,11 +10,11 @@ const kTestToolbarId = "test-empty-drag";
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();

View File

@@ -44,12 +44,12 @@ add_task(async function() {
// 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();

View File

@@ -3,12 +3,14 @@
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
@@ -42,6 +44,7 @@ add_task(async function check_developer_subview_in_overflow() {
is(developerView.closest("panel"), expectedPanel, "Should be inside the panel");
expectedPanel.hidePopup();
await Promise.resolve(); // wait for popup to hide fully.
await stopOverflowing();
});
/**
@@ -51,14 +54,15 @@ add_task(async function check_developer_subview_in_overflow() {
* 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");

View File

@@ -223,6 +223,9 @@ var DownloadsPanel = {
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

View File

@@ -54,6 +54,11 @@ const DownloadsButton = {
return document.getElementById("downloads-button");
},
/**
* Indicates whether toolbar customization is in progress.
*/
_customizing: false,
/**
* This function is called asynchronously just after window initialization.
*
@@ -64,34 +69,6 @@ const DownloadsButton = {
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.
@@ -108,35 +85,16 @@ const DownloadsButton = {
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.
@@ -172,6 +130,91 @@ const DownloadsButton = {
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");
@@ -342,7 +385,7 @@ const DownloadsIndicatorView = {
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
@@ -423,8 +466,12 @@ const DownloadsIndicatorView = {
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;
@@ -508,14 +555,7 @@ const DownloadsIndicatorView = {
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();
},
@@ -567,10 +607,9 @@ const DownloadsIndicatorView = {
},
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");
},

View File

@@ -13,3 +13,4 @@ skip-if = os == "linux" # Bug 952422
[browser_downloads_panel_block.js]
skip-if = true # Bug 1352792
[browser_downloads_panel_height.js]
[browser_downloads_autohide.js]

View File

@@ -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();
});
}

View File

@@ -9,6 +9,8 @@
* 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 },
]);

View File

@@ -10,6 +10,8 @@
* 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");
@@ -32,6 +34,7 @@ add_task(async function test_first_download_panel() {
// 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;

View File

@@ -12,8 +12,10 @@ registerCleanupFunction(async function() {
});
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);

View File

@@ -15,30 +15,21 @@ registerCleanupFunction(async function() {
* 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;
@@ -47,14 +38,7 @@ add_task(async function test_overflow_anchor() {
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();
@@ -66,50 +50,3 @@ add_task(async function test_overflow_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});
});
}

View File

@@ -198,3 +198,15 @@ function promiseAlertDialogOpen(buttonAction) {
});
});
}
/**
* 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`);
}