Bug 1397447 - add UI and automatic toggles to make the auto-hide functionality more seamless, r?mak
MozReview-Commit-ID: 9xN3N4aLwfv
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -16,16 +16,20 @@ const kSkipSourceNodePref = "browser.uiC
const kToolbarVisibilityBtn = "customization-toolbar-visibility-button";
const kDrawInTitlebarPref = "browser.tabs.drawInTitlebar";
const kMaxTransitionDurationMs = 2000;
const kKeepBroadcastAttributes = "keepbroadcastattributeswhencustomizing";
const kPanelItemContextMenu = "customizationPanelItemContextMenu";
const kPaletteItemContextMenu = "customizationPaletteItemContextMenu";
+const kDownloadAutohideCheckboxId = "downloads-button-autohide-checkbox";
+const kDownloadAutohidePanelId = "downloads-button-autohide-panel";
+const kDownloadAutoHidePref = "browser.download.autohideButton";
+
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource:///modules/CustomizableUI.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/AddonManager.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DragPositionManager",
"resource:///modules/DragPositionManager.jsm");
@@ -349,16 +353,18 @@ CustomizeMode.prototype = {
// and make it async so it doesn't affect the timing.
this.visiblePalette.clientTop;
this.visiblePalette.setAttribute("showing", "true");
}, 0);
this._updateEmptyPaletteNotice();
this._updateLWThemeButtonIcon();
+ this._setupDownloadAutoHideToggle();
+
this._handler.isEnteringCustomizeMode = false;
CustomizableUI.dispatchToolboxEvent("customizationready", {}, window);
if (!this._wantToBeInCustomizeMode) {
this.exit();
}
})().catch(e => {
@@ -390,16 +396,18 @@ CustomizeMode.prototype = {
}
this.hideTip();
this._handler.isExitingCustomizeMode = true;
this._removeExtraToolbarsIfEmpty();
+ this._teardownDownloadAutoHideToggle();
+
CustomizableUI.removeListener(this);
this.document.removeEventListener("keypress", this);
let window = this.window;
let document = this.document;
// Hide the palette before starting the transition for increased perf.
@@ -467,16 +475,18 @@ CustomizeMode.prototype = {
// or the TabSelect event handler will think that we are exiting
// customization mode for a second time.
this._customizing = false;
let customizableToolbars = document.querySelectorAll("toolbar[customizable=true]:not([autohide=true])");
for (let toolbar of customizableToolbars)
toolbar.removeAttribute("customizing");
+ this._maybeMoveDownloadsButtonToNavBar();
+
delete this._lastLightweightTheme;
this._changed = false;
this._transitioning = false;
this._handler.isExitingCustomizeMode = false;
CustomizableUI.dispatchToolboxEvent("aftercustomization", {}, window);
CustomizableUI.notifyEndCustomizing(window);
if (this._wantToBeInCustomizeMode) {
@@ -611,27 +621,39 @@ CustomizeMode.prototype = {
return null;
},
addToToolbar(aNode) {
aNode = this._getCustomizableChildForNode(aNode);
if (aNode.localName == "toolbarpaletteitem" && aNode.firstChild) {
aNode = aNode.firstChild;
}
+
+ // If the user explicitly moves this item, turn off autohide.
+ if (aNode.id == "downloads-button") {
+ Services.prefs.setBoolPref(kDownloadAutoHidePref, false);
+ }
+
CustomizableUI.addWidgetToArea(aNode.id, CustomizableUI.AREA_NAVBAR);
if (!this._customizing) {
CustomizableUI.dispatchToolboxEvent("customizationchange");
}
},
addToPanel(aNode) {
aNode = this._getCustomizableChildForNode(aNode);
if (aNode.localName == "toolbarpaletteitem" && aNode.firstChild) {
aNode = aNode.firstChild;
}
+
+ // If the user explicitly moves this item, turn off autohide.
+ if (aNode.id == "downloads-button") {
+ Services.prefs.setBoolPref(kDownloadAutoHidePref, false);
+ }
+
let panel = CustomizableUI.AREA_FIXED_OVERFLOW_PANEL;
CustomizableUI.addWidgetToArea(aNode.id, panel);
if (!this._customizing) {
CustomizableUI.dispatchToolboxEvent("customizationchange");
}
if (Services.prefs.getBoolPref("toolkit.cosmeticAnimations.enabled")) {
let overflowButton = this.document.getElementById("nav-bar-overflow-button");
@@ -650,16 +672,20 @@ CustomizeMode.prototype = {
}
},
removeFromArea(aNode) {
aNode = this._getCustomizableChildForNode(aNode);
if (aNode.localName == "toolbarpaletteitem" && aNode.firstChild) {
aNode = aNode.firstChild;
}
+ // If the user explicitly removes this item, turn off autohide.
+ if (aNode.id == "downloads-button") {
+ Services.prefs.setBoolPref(kDownloadAutoHidePref, false);
+ }
CustomizableUI.removeWidgetFromArea(aNode.id);
if (!this._customizing) {
CustomizableUI.dispatchToolboxEvent("customizationchange");
}
},
populatePalette() {
let fragment = this.document.createDocumentFragment();
@@ -1076,16 +1102,17 @@ CustomizeMode.prototype = {
await this._wrapToolbarItems();
this.populatePalette();
this.persistCurrentSets(true);
this._updateResetButton();
this._updateUndoResetButton();
this._updateEmptyPaletteNotice();
+ this._moveDownloadsButtonToNavBar = false;
this.resetting = false;
if (!this._wantToBeInCustomizeMode) {
this.exit();
}
})().catch(log.error);
},
undoReset() {
@@ -1102,16 +1129,17 @@ CustomizeMode.prototype = {
await this._wrapToolbarItems();
this.populatePalette();
this.persistCurrentSets(true);
this._updateResetButton();
this._updateUndoResetButton();
this._updateEmptyPaletteNotice();
+ this._moveDownloadsButtonToNavBar = false;
this.resetting = false;
})().catch(log.error);
},
_onToolbarVisibilityChange(aEvent) {
let toolbar = aEvent.target;
if (aEvent.detail.visible && toolbar.getAttribute("customizable") == "true") {
toolbar.setAttribute("customizing", "true");
@@ -1168,16 +1196,19 @@ CustomizeMode.prototype = {
// API also does the right thing (and adds it to the palette)
let widgetId = aNodeToChange.id;
let widget = CustomizableUI.getWidget(widgetId);
if (widget.provider == CustomizableUI.PROVIDER_API) {
let paletteItem = this.makePaletteItem(widget, "palette");
this.visiblePalette.appendChild(paletteItem);
}
}
+ if (aNodeToChange.id == "downloads-button") {
+ this._showDownloadsAutoHidePanel();
+ }
},
onWidgetDestroyed(aWidgetId) {
let wrapper = this.document.getElementById("wrapper-" + aWidgetId);
if (wrapper) {
wrapper.remove();
}
},
@@ -1752,16 +1783,21 @@ CustomizeMode.prototype = {
this._cancelDragActive(this._dragOverItem, null, true);
try {
this._applyDrop(aEvent, targetArea, originArea, draggedItemId, targetNode);
} catch (ex) {
log.error(ex, ex.stack);
}
+
+ // If the user explicitly moves this item, turn off autohide.
+ if (draggedItemId == "downloads-button") {
+ Services.prefs.setBoolPref(kDownloadAutoHidePref, false);
+ }
},
_applyDrop(aEvent, aTargetArea, aOriginArea, aDraggedItemId, aTargetNode) {
let document = aEvent.target.ownerDocument;
let draggedItem = document.getElementById(aDraggedItemId);
draggedItem.hidden = false;
draggedItem.removeAttribute("mousedown");
@@ -2241,16 +2277,104 @@ CustomizeMode.prototype = {
},
onPanelContextMenuShowing(event) {
let inPermanentArea = !!event.target.triggerNode.closest("#widget-overflow-fixed-list");
let doc = event.target.ownerDocument;
doc.getElementById("customizationPanelItemContextMenuUnpin").hidden = !inPermanentArea;
doc.getElementById("customizationPanelItemContextMenuPin").hidden = inPermanentArea;
},
+
+ _checkForDownloadsClick(event) {
+ if (event.target.closest("#wrapper-downloads-button") && event.button == 0) {
+ event.view.gCustomizeMode._showDownloadsAutoHidePanel();
+ }
+ },
+
+ _setupDownloadAutoHideToggle() {
+ this.document.getElementById(kDownloadAutohidePanelId).removeAttribute("hidden");
+ this.window.addEventListener("click", this._checkForDownloadsClick, true);
+ },
+
+ _teardownDownloadAutoHideToggle() {
+ this.window.removeEventListener("click", this._checkForDownloadsClick, true);
+ this.document.getElementById(kDownloadAutohidePanelId).hidePopup();
+ },
+
+ _maybeMoveDownloadsButtonToNavBar() {
+ // If the user toggled the autohide checkbox while the item was in the
+ // palette, and hasn't moved it since, move the item to the default
+ // location in the navbar for them.
+ if (!CustomizableUI.getPlacementOfWidget("downloads-button") &&
+ this._moveDownloadsButtonToNavBar &&
+ this.window.DownloadsButton.autoHideDownloadsButton) {
+ let navbarPlacements = CustomizableUI.getWidgetIdsInArea("nav-bar");
+ let insertionPoint = navbarPlacements.indexOf("urlbar-container");
+ while (++insertionPoint < navbarPlacements.length) {
+ let widget = navbarPlacements[insertionPoint];
+ // If we find a non-searchbar, non-spacer node, break out of the loop:
+ if (widget != "search-container" &&
+ !(CustomizableUI.isSpecialWidget(widget) && widget.includes("spring"))) {
+ break;
+ }
+ }
+ CustomizableUI.addWidgetToArea("downloads-button", "nav-bar", insertionPoint);
+ }
+ },
+
+ _showDownloadsAutoHidePanel() {
+ let doc = this.document;
+ let panel = doc.getElementById(kDownloadAutohidePanelId);
+ panel.hidePopup();
+ let button = doc.getElementById("downloads-button");
+ // We don't show the tooltip if the button is in the panel.
+ if (button.closest("#widget-overflow-fixed-list")) {
+ return;
+ }
+
+ let checkbox = doc.getElementById(kDownloadAutohideCheckboxId);
+ if (this.window.DownloadsButton.autoHideDownloadsButton) {
+ checkbox.setAttribute("checked", "true");
+ } else {
+ checkbox.removeAttribute("checked");
+ }
+
+ let offsetX = 0, offsetY = 0;
+ let position;
+ if (button.closest("#nav-bar")) {
+ let navbarWidgets = CustomizableUI.getWidgetIdsInArea("nav-bar");
+ if (navbarWidgets.indexOf("urlbar-container") > navbarWidgets.indexOf("downloads-button")) {
+ // Tested in RTL, these get inverted automatically, so this does the
+ // right thing without taking RTL into account explicitly.
+ position = "rightcenter topleft";
+ offsetX = -8;
+ } else {
+ position = "leftcenter topright";
+ offsetX = 8;
+ }
+ } else if (button.closest("#customization-palette")) {
+ position = "topcenter bottomleft";
+ offsetY = 10;
+ } else {
+ // For non-navbar toolbars, this works better than guessing whether
+ // left or right is a better place to position:
+ position = "bottomcenter topleft";
+ offsetY = -5;
+ }
+ // We don't use the icon to anchor because it might be resizing because of
+ // the animations for drag/drop. Hence the use of offsets.
+ panel.openPopup(button, position, offsetX, offsetY);
+ },
+
+ onDownloadsAutoHideChange(event) {
+ let checkbox = event.target.ownerDocument.getElementById(kDownloadAutohideCheckboxId);
+ Services.prefs.setBoolPref(kDownloadAutoHidePref, checkbox.checked);
+ // Ensure we move the button (back) after the user leaves customize mode.
+ event.view.gCustomizeMode._moveDownloadsButtonToNavBar = checkbox.checked;
+ },
};
function __dumpDragData(aEvent, caller) {
if (!gDebug) {
return;
}
let str = "Dumping drag data (" + (caller ? caller + " in " : "") + "CustomizeMode.jsm) {\n";
str += " type: " + aEvent.type + "\n";
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -748,8 +748,22 @@
type="checkbox"
oncommand="onViewToolbarCommand(event)"
label="&viewBookmarksToolbar.label;"
label-checked="&hideBookmarksToolbar.label;"/>
</vbox>
</panelview>
</photonpanelmultiview>
</panel>
+
+<panel id="downloads-button-autohide-panel"
+ role="group"
+ type="arrow"
+ hidden="true"
+ onpopupshown="gCustomizeMode._downloadPanelAutoHideTimeout = setTimeout(() => event.target.hidePopup(), 4000);"
+ onmouseover="clearTimeout(gCustomizeMode._downloadPanelAutoHideTimeout);"
+ onmouseout="gCustomizeMode._downloadPanelAutoHideTimeout = setTimeout(() => event.target.hidePopup(), 2000);"
+ onpopuphidden="clearTimeout(gCustomizeMode._downloadPanelAutoHideTimeout);"
+ >
+ <checkbox id="downloads-button-autohide-checkbox"
+ label="&customizeMode.autoHideDownloadsButton.label;" checked="true"
+ oncommand="gCustomizeMode.onDownloadsAutoHideChange(event)"/>
+</panel>
--- a/browser/components/downloads/test/browser/browser_downloads_autohide.js
+++ b/browser/components/downloads/test/browser/browser_downloads_autohide.js
@@ -23,36 +23,44 @@ add_task(async function checkStateDuring
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");
+ ok(!Services.prefs.getBoolPref(kDownloadAutoHidePref),
+ "Pref got set to false when the user moved the button");
gCustomizeMode.addToToolbar(downloadsButton);
+ ok(!Services.prefs.getBoolPref(kDownloadAutoHidePref),
+ "Pref remains false when the user moved the button");
+ Services.prefs.setBoolPref(kDownloadAutoHidePref, true);
ok(downloadsButton.hasAttribute("hidden"),
- "Button should be hidden again in the toolbar");
+ "Button should be hidden again in the toolbar " +
+ "now that we flipped the pref");
Services.prefs.setBoolPref(kDownloadAutoHidePref, false);
ok(!downloadsButton.hasAttribute("hidden"),
"Button shouldn't be hidden with autohide turned off");
gCustomizeMode.addToPanel(downloadsButton);
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);
+ // Use CUI directly instead of the customize mode APIs,
+ // to avoid tripping the "automatically turn off autohide" code.
+ CustomizableUI.addWidgetToArea("downloads-button", "nav-bar");
ok(downloadsButton.hasAttribute("hidden"),
"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"),
@@ -63,16 +71,21 @@ add_task(async function checkStateDuring
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.");
+ await promiseCustomizeEnd();
+ ok(downloadsButton.hasAttribute("hidden"),
+ "Button should be hidden if it's in the toolbar " +
+ "after customize mode without any moves.");
+ await promiseCustomizeStart();
gCustomizeMode.addToPanel(downloadsButton);
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"),
@@ -84,18 +97,19 @@ add_task(async function checkStateInCust
"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.");
+ ok(!downloadsButton.hasAttribute("hidden"),
+ "Button should be shown in the toolbar after " +
+ "customize mode because we moved it.");
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.");
@@ -119,23 +133,26 @@ add_task(async function checkStateInCust
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);
+ // Use CUI directly instead of the customize mode APIs,
+ // to avoid tripping the "automatically turn off autohide" code.
+ CustomizableUI.addWidgetToArea("downloads-button",
+ CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
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);
+ CustomizableUI.addWidgetToArea("downloads-button", CustomizableUI.AREA_NAVBAR);
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");
@@ -151,16 +168,18 @@ add_task(async function checkStateInCust
gCustomizeMode.addToPanel(downloadsButton);
ok(!downloadsButton.hasAttribute("hidden"),
"Button should still be shown in customize mode.");
ok(!otherDownloadsButton.hasAttribute("hidden"),
"Button should be shown in the other window too because it's in a panel.");
gCustomizeMode.removeFromArea(downloadsButton);
+ ok(!Services.prefs.getBoolPref(kDownloadAutoHidePref),
+ "Autohide pref turned off by moving the button");
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"),
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -861,16 +861,18 @@ you can use these alternative items. Oth
“Touch” is displayed in the Customize screen, under the Density menu.
It’s an adjective (Density -> Touch), and it means that control layout is
optimized for touch devices. -->
<!ENTITY customizeMode.uidensity.menuTouch.label "Touch">
<!ENTITY customizeMode.uidensity.menuTouch.tooltip "Touch">
<!ENTITY customizeMode.uidensity.menuTouch.accessKey "T">
<!ENTITY customizeMode.uidensity.autoTouchMode.checkbox.label "Use Touch for Tablet Mode">
+<!ENTITY customizeMode.autoHideDownloadsButton.label "Auto-hide">
+
<!ENTITY getUserMedia.selectCamera.label "Camera to share:">
<!ENTITY getUserMedia.selectCamera.accesskey "C">
<!ENTITY getUserMedia.selectMicrophone.label "Microphone to share:">
<!ENTITY getUserMedia.selectMicrophone.accesskey "M">
<!ENTITY getUserMedia.audioCapture.label "Audio from the tab will be shared.">
<!ENTITY getUserMedia.allWindowsShared.message "All visible windows on your screen will be shared.">
<!ENTITY trackingProtection.title "Tracking Protection">
--- a/browser/themes/shared/customizableui/customizeMode.inc.css
+++ b/browser/themes/shared/customizableui/customizeMode.inc.css
@@ -492,8 +492,17 @@ toolbarpaletteitem[place=toolbar] > tool
display: block;
}
@media (min-resolution: 1.1dppx) {
#customization-panelHolder > #widget-overflow-fixed-list:empty {
background-image: url("chrome://browser/skin/customizableui/empty-overflow-panel@2x.png");
}
}
+
+#downloads-button-autohide-panel > .panel-arrowcontainer > .panel-arrowcontent {
+ padding: 5px 12px;
+}
+
+#downloads-button-autohide-checkbox {
+ margin: 0;
+ padding: 0;
+}