Bug 1269954 - When a download is added, popup the detail of the download item as a notification.
--- a/browser/components/downloads/DownloadsCommon.jsm
+++ b/browser/components/downloads/DownloadsCommon.jsm
@@ -814,23 +814,23 @@ DownloadsDataCtor.prototype = {
view.onDownloadStateChanged(download);
} catch (ex) {
Cu.reportError(ex);
}
}
if (download.succeeded ||
(download.error && download.error.becauseBlocked)) {
- this._notifyDownloadEvent("finish");
+ this._notifyDownloadEvent("finish", download);
}
}
if (!download.newDownloadNotified) {
download.newDownloadNotified = true;
- this._notifyDownloadEvent("start");
+ this._notifyDownloadEvent("start", download);
}
for (let view of this._views) {
view.onDownloadChanged(download);
}
},
onDownloadRemoved(download) {
@@ -889,57 +889,38 @@ DownloadsDataCtor.prototype = {
// Notify the view that all data is available.
aView.onDataLoadCompleted();
},
//////////////////////////////////////////////////////////////////////////////
//// Notifications sent to the most recent browser window only
/**
- * Set to true after the first download causes the downloads panel to be
- * displayed.
- */
- get panelHasShownBefore() {
- try {
- return Services.prefs.getBoolPref("browser.download.panel.shown");
- } catch (ex) { }
- return false;
- },
-
- set panelHasShownBefore(aValue) {
- Services.prefs.setBoolPref("browser.download.panel.shown", aValue);
- return aValue;
- },
-
- /**
* Displays a new or finished download notification in the most recent browser
* window, if one is currently available with the required privacy type.
*
* @param aType
* Set to "start" for new downloads, "finish" for completed downloads.
+ * @param download
+ * the download data object.
*/
- _notifyDownloadEvent(aType) {
+ _notifyDownloadEvent(aType, download) {
DownloadsCommon.log("Attempting to notify that a new download has started or finished.");
// Show the panel in the most recent browser window, if present.
let browserWin = RecentWindow.getMostRecentBrowserWindow({ private: this._isPrivate });
if (!browserWin) {
return;
}
+ DownloadsCommon.log("Showing new download notification.");
+ browserWin.DownloadsIndicatorView.showEventNotification(aType);
- if (this.panelHasShownBefore) {
- // For new downloads after the first one, don't show the panel
- // automatically, but provide a visible notification in the topmost
- // browser window, if the status indicator is already visible.
- DownloadsCommon.log("Showing new download notification.");
- browserWin.DownloadsIndicatorView.showEventNotification(aType);
- return;
+ if (aType === 'start') {
+ browserWin.NewDownloadNotifyPanel.onDownloadAdded(download, true);
}
- this.panelHasShownBefore = true;
- browserWin.DownloadsPanel.showPanel();
}
};
XPCOMUtils.defineLazyGetter(this, "PrivateDownloadsData", function() {
return new DownloadsDataCtor(true);
});
XPCOMUtils.defineLazyGetter(this, "DownloadsData", function() {
--- a/browser/components/downloads/content/downloads.css
+++ b/browser/components/downloads/content/downloads.css
@@ -260,8 +260,25 @@ richlistitem.download button {
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] > .panel-mainview #downloadsSummary {
-moz-user-focus: ignore;
}
/* ... except for the downloadShowBlockedInfo button in the blocked download.
Selecting it with the keyboard should show the main view again. */
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] .download-state[showingsubview] .downloadShowBlockedInfo {
-moz-user-focus: normal;
}
+
+/*** New downloads notification ***/
+
+/* Hide all the usual buttons for blocked downoads. */
+#newDownloadPopupPanel .download-state[state="8"] .downloadCancel,
+#newDownloadPopupPanel .download-state[state="8"] .downloadConfirmBlock,
+#newDownloadPopupPanel .download-state[state="8"] .downloadChooseUnblock,
+#newDownloadPopupPanel .download-state[state="8"] .downloadChooseOpen,
+#newDownloadPopupPanel .download-state[state="8"] .downloadRetry,
+#newDownloadPopupPanel .download-state[state="8"] .downloadShow {
+ display: none;
+}
+
+/* And show the "show blocked info" button for blocked downloads. */
+#newDownloadPopupPanel .download-state[state="8"] .downloadShowBlockedInfo {
+ display: inline;
+}
--- a/browser/components/downloads/content/downloads.js
+++ b/browser/components/downloads/content/downloads.js
@@ -220,16 +220,19 @@ const DownloadsPanel = {
DownloadsCommon.log("Opening the downloads panel.");
if (this.isPanelShowing) {
DownloadsCommon.log("Panel is already showing - focusing instead.");
this._focusPanel();
return;
}
+ // If the new download notification is showing, hide it first.
+ NewDownloadNotifyPanel.hidePanel();
+
this.initialize(() => {
let downloadsFooterButtons =
document.getElementById("downloadsFooterButtons");
if (DownloadsCommon.showPanelDropmarker) {
downloadsFooterButtons.classList.remove("downloadsHideDropmarker");
} else {
downloadsFooterButtons.classList.add("downloadsHideDropmarker");
}
@@ -899,16 +902,20 @@ const DownloadsView = {
* DownloadsViewItem object.
*/
_itemsForElements: new Map(),
itemForElement(element) {
return this._itemsForElements.get(element);
},
+ itemForData(download) {
+ return this._visibleViewItems.get(download);
+ },
+
/**
* Creates a new view item associated with the specified data item, and adds
* it to the top or the bottom of the list.
*/
_addViewItem(download, aNewest)
{
DownloadsCommon.log("Adding a new DownloadsViewItem to the downloads list.",
"aNewest =", aNewest);
@@ -953,17 +960,18 @@ const DownloadsView = {
* @param aCommand
* The command to be performed.
*/
onDownloadCommand(aEvent, aCommand) {
let target = aEvent.target;
while (target.nodeName != "richlistitem") {
target = target.parentNode;
}
- DownloadsView.itemForElement(target).doCommand(aCommand);
+ (NewDownloadNotifyPanel.itemForElement(target) ||
+ DownloadsView.itemForElement(target)).doCommand(aCommand);
},
onDownloadClick(aEvent) {
// Handle primary clicks only, and exclude the action button.
if (aEvent.button == 0 &&
!aEvent.originalTarget.hasAttribute("oncommand")) {
let target = aEvent.target;
while (target.nodeName != "richlistitem") {
@@ -1151,55 +1159,71 @@ DownloadsViewItem.prototype = {
cmd_delete() {
DownloadsCommon.removeAndFinalizeDownload(this.download);
PlacesUtils.bhistory.removePage(
NetUtil.newURI(this.download.source.url));
},
downloadsCmd_unblock() {
DownloadsPanel.hidePanel();
+ NewDownloadNotifyPanel.hidePanel();
this.confirmUnblock(window, "unblock");
},
downloadsCmd_chooseUnblock() {
DownloadsPanel.hidePanel();
+ NewDownloadNotifyPanel.hidePanel();
this.confirmUnblock(window, "chooseUnblock");
},
downloadsCmd_unblockAndOpen() {
DownloadsPanel.hidePanel();
+ NewDownloadNotifyPanel.hidePanel();
this.unblockAndOpenDownload().catch(Cu.reportError);
},
downloadsCmd_open() {
this.download.launch().catch(Cu.reportError);
// We explicitly close the panel here to give the user the feedback that
// their click has been received, and we're handling the action.
// Otherwise, we'd have to wait for the file-type handler to execute
// before the panel would close. This also helps to prevent the user from
// accidentally opening a file several times.
DownloadsPanel.hidePanel();
+ NewDownloadNotifyPanel.hidePanel();
},
downloadsCmd_show() {
let file = new FileUtils.File(this.download.target.path);
DownloadsCommon.showDownloadedFile(file);
// We explicitly close the panel here to give the user the feedback that
// their click has been received, and we're handling the action.
// Otherwise, we'd have to wait for the operating system file manager
// window to open before the panel closed. This also helps to prevent the
// user from opening the containing folder several times.
DownloadsPanel.hidePanel();
+ NewDownloadNotifyPanel.hidePanel();
},
downloadsCmd_showBlockedInfo() {
- DownloadsBlockedSubview.toggle(this.element,
- ...this.rawBlockedTitleAndDetails);
+ if (DownloadsPanel.isPanelShowing) {
+ DownloadsBlockedSubview.toggle(this.element,
+ ...this.rawBlockedTitleAndDetails);
+ } else {
+ // This is the case of popup notification.
+ DownloadsPanel.showPanel();
+ let dataitem = NewDownloadNotifyPanel.itemForElement(this.element).download;
+ var elementInDownloadPanel = DownloadsView.itemForData(dataitem).element;
+ DownloadsView.richListBox.selectedItem = elementInDownloadPanel;
+ DownloadsBlockedSubview.toggle(elementInDownloadPanel,
+ ...this.rawBlockedTitleAndDetails);
+ }
+
},
downloadsCmd_openReferrer() {
openURL(this.download.source.referrer);
},
downloadsCmd_copyLocation() {
let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"]
@@ -1686,8 +1710,156 @@ const DownloadsBlockedSubview = {
confirmBlock() {
goDoCommand("cmd_delete");
DownloadsPanel.hidePanel();
},
};
XPCOMUtils.defineConstant(this, "DownloadsBlockedSubview",
DownloadsBlockedSubview);
+
+////////////////////////////////////////////////////////////////////////////////
+//// NewDownloadNotifyPanel
+
+const NewDownloadNotifyPanel = {
+ _initialized: false,
+ _initializing: false,
+ _downloadItem: null,
+ _viewItem: null,
+ _hidingTimer: null,
+
+ initialize(aCallback) {
+ if (this._initialized) {
+ aCallback();
+ return;
+ }
+ if (this._initializing) {
+ setTimeout(aCallback);
+ return;
+ }
+
+ this._initializing = true;
+ DownloadsOverlayLoader.ensureOverlayLoaded(this.kDownloadsOverlay, () => {
+ this._initializing = false;
+ this._initialized = true
+ DownloadsCommon.getData(window).addView(NewDownloadNotifyPanel);
+ this.panel = document.getElementById('newDownloadPopupPanel');
+ DownloadsCommon.log('initialize finished for download notify panel');
+ this.panel.onmouseover = this.onmouseover.bind(this);
+ this.panel.onmouseout = this.onmouseout.bind(this);
+ aCallback();
+ });
+ },
+
+ onmouseover(e) {
+ this.clearHidingTimer();
+ },
+
+ onmouseout(e) {
+ var rel = e.relatedTarget;
+
+ // find out if the node we are entering is our children
+ while (rel) {
+ if (rel == this.panel)
+ break;
+ rel = rel.parentNode;
+ }
+
+ // if the entered node is not a descendant of ours, the mouse is no longer
+ // over the panel and we setup the hiding timer again.
+ if (rel != this.panel) {
+ this.setHidingTimer();
+ }
+ },
+
+ onDownloadAdded(download, aNewest) {
+ if (!aNewest) {
+ return;
+ }
+ this.initialize(() => {
+ this._addViewItem(download);
+ });
+ // We show notification panel after download indicator animation is end.
+ setTimeout(this.showPanel.bind(this), 1000);
+ },
+
+ setHidingTimer() {
+ if(this._hidingTimer) {
+ clearTimeout(this._hidingTimer);
+ }
+ this._hidingTimer = setTimeout(this.hidePanel.bind(this), 5000);
+ },
+
+ clearHidingTimer() {
+ this._logcounter++;
+ if(this._hidingTimer) {
+ clearTimeout(this._hidingTimer);
+ }
+ },
+
+ showPanel() {
+ if(!this._initialized) {
+ this.initialize(this.showPanel.bind(this));
+ }
+
+ var anchor = DownloadsIndicatorView.indicatorAnchor;
+ this.panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, null);
+ this.setHidingTimer();
+ },
+
+ itemForElement(element) {
+ if(this._viewItem.element === element) {
+ return this._viewItem;
+ }
+ return null;
+ },
+
+ hidePanel() {
+ if (!this.panel) {
+ return;
+ }
+ this._hidingTimer = null;
+ this.panel.hidePopup();
+ },
+
+ _addViewItem(download) {
+ this._downloadItem = download;
+ DownloadsCommon.log("Adding a new DownloadsViewItem to download notifier");
+ let element = document.createElement("richlistitem");
+ this._viewItem = new DownloadsViewItem(download, element);
+ if (this.panel.firstChild) {
+ this.panel.removeChild(this.panel.firstChild);
+ }
+ this.panel.appendChild(element);
+ },
+
+ onDownloadStateChanged(download) {
+ if (this._downloadItem === download) {
+ this._viewItem.onStateChanged();
+ }
+ },
+
+ onDownloadChanged(download) {
+ if (this._downloadItem === download) {
+ this._viewItem.onChanged();
+ }
+ },
+
+ onDownloadRemoved(download) {
+ },
+
+ onDataLoadStarting() {
+
+ },
+
+ onDataLoadCompleted() {
+
+ },
+ get initialized() {
+ return this._initialized;
+ },
+
+ get kDownloadsOverlay() {
+ return "chrome://browser/content/downloads/downloadsOverlay.xul";
+ },
+};
+
+XPCOMUtils.defineConstant(this, "NewDownloadNotifyPanel", NewDownloadNotifyPanel);
--- a/browser/components/downloads/content/downloadsOverlay.xul
+++ b/browser/components/downloads/content/downloadsOverlay.xul
@@ -199,10 +199,11 @@
default="true"
flex="1"/>
</hbox>
</panelview>
</panelmultiview>
</panel>
+ <panel id="newDownloadPopupPanel" type="arrow" noautohide="true" />
</popupset>
</overlay>
--- a/browser/components/downloads/content/indicator.js
+++ b/browser/components/downloads/content/indicator.js
@@ -498,16 +498,17 @@ const DownloadsIndicatorView = {
},
onCommand(aEvent) {
// 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 {
+ NewDownloadNotifyPanel.hidePanel();
DownloadsPanel.showPanel();
}
aEvent.stopPropagation();
},
onDragOver(aEvent) {
browserDragAndDrop.dragOver(aEvent);
--- a/browser/themes/shared/downloads/downloads.inc.css
+++ b/browser/themes/shared/downloads/downloads.inc.css
@@ -388,8 +388,17 @@ richlistitem[type="download"] > .downloa
}
#downloadsPanel-blockedSubview-title,
#downloadsPanel-blockedSubview-details1,
#downloadsPanel-blockedSubview-details2 {
-moz-margin-start: 64px;
-moz-margin-end: 16px;
}
+
+#newDownloadPopupPanel .panel-arrowcontent {
+ padding: 0;
+}
+
+#newDownloadPopupPanel > .panel-arrowcontainer > .panel-arrowcontent {
+ overflow: hidden;
+ display: block;
+}