diff --git a/browser/components/downloads/content/downloads.js b/browser/components/downloads/content/downloads.js
index fd6efefec163..f69dea0481a9 100644
--- a/browser/components/downloads/content/downloads.js
+++ b/browser/components/downloads/content/downloads.js
@@ -1187,6 +1187,12 @@ function DownloadsViewItemController(aElement) {
}
DownloadsViewItemController.prototype = {
+ //////////////////////////////////////////////////////////////////////////////
+ //// Constants
+
+ get kPrefBdmAlertOnExeOpen() "browser.download.manager.alertOnEXEOpen",
+ get kPrefBdmScanWhenDone() "browser.download.manager.scanWhenDone",
+
//////////////////////////////////////////////////////////////////////////////
//// Command dispatching
@@ -1238,17 +1244,87 @@ DownloadsViewItemController.prototype = {
commands: {
cmd_delete: function DVIC_cmd_delete()
{
- this.dataItem.remove();
+ this.dataItem.getDownload(function (aDownload) {
+ if (this.dataItem.inProgress) {
+ aDownload.cancel();
+ this._ensureLocalFileRemoved();
+ }
+ aDownload.remove();
+ }.bind(this));
},
downloadsCmd_cancel: function DVIC_downloadsCmd_cancel()
{
- this.dataItem.cancel();
+ if (this.dataItem.inProgress) {
+ this.dataItem.getDownload(function (aDownload) {
+ aDownload.cancel();
+ this._ensureLocalFileRemoved();
+ }.bind(this));
+ }
},
downloadsCmd_open: function DVIC_downloadsCmd_open()
{
- this.dataItem.openLocalFile();
+ // Confirm opening executable files if required.
+ let localFile = this.dataItem.localFile;
+ if (localFile.isExecutable()) {
+ let showAlert = true;
+ try {
+ showAlert = Services.prefs.getBoolPref(this.kPrefBdmAlertOnExeOpen);
+ } catch (ex) { }
+
+ // On Vista and above, we rely on native security prompting for
+ // downloaded content unless it's disabled.
+ if (DownloadsCommon.isWinVistaOrHigher) {
+ try {
+ if (Services.prefs.getBoolPref(this.kPrefBdmScanWhenDone)) {
+ showAlert = false;
+ }
+ } catch (ex) { }
+ }
+
+ if (showAlert) {
+ let name = this.dataItem.target;
+ let message =
+ DownloadsCommon.strings.fileExecutableSecurityWarning(name, name);
+ let title =
+ DownloadsCommon.strings.fileExecutableSecurityWarningTitle;
+ let dontAsk =
+ DownloadsCommon.strings.fileExecutableSecurityWarningDontAsk;
+
+ let checkbox = { value: false };
+ let open = Services.prompt.confirmCheck(window, title, message,
+ dontAsk, checkbox);
+ if (!open) {
+ return;
+ }
+
+ Services.prefs.setBoolPref(this.kPrefBdmAlertOnExeOpen,
+ !checkbox.value);
+ }
+ }
+
+ // Actually open the file.
+ this.dataItem.getDownload(function (aDownload) {
+ try {
+ let launched = false;
+ try {
+ let mimeInfo = aDownload.MIMEInfo;
+ if (mimeInfo.preferredAction == mimeInfo.useHelperApp) {
+ mimeInfo.launchWithFile(localFile);
+ launched = true;
+ }
+ } catch (ex) { }
+ if (!launched) {
+ localFile.launch();
+ }
+ } catch (ex) {
+ // If launch fails, try sending it through the system's external "file:"
+ // URL handler.
+ this._openExternal(localFile);
+ }
+ }.bind(this));
+
// 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
@@ -1259,7 +1335,26 @@ DownloadsViewItemController.prototype = {
downloadsCmd_show: function DVIC_downloadsCmd_show()
{
- this.dataItem.showLocalFile();
+ let localFile = this.dataItem.localFile;
+
+ try {
+ // Show the directory containing the file and select the file.
+ localFile.reveal();
+ } catch (ex) {
+ // If reveal fails for some reason (e.g., it's not implemented on unix
+ // or the file doesn't exist), try using the parent if we have it.
+ let parent = localFile.parent.QueryInterface(Ci.nsILocalFile);
+ if (parent) {
+ try {
+ // Open the parent directory to show where the file should be.
+ parent.launch();
+ } catch (ex) {
+ // If launch also fails (probably because it's not implemented), let
+ // the OS handler try to open the parent.
+ this._openExternal(parent);
+ }
+ }
+ }
// We explicitly close the panel here to give the user the feedback that
// their click has been received, and we're handling the action.
@@ -1271,12 +1366,20 @@ DownloadsViewItemController.prototype = {
downloadsCmd_pauseResume: function DVIC_downloadsCmd_pauseResume()
{
- this.dataItem.togglePauseResume();
+ this.dataItem.getDownload(function (aDownload) {
+ if (this.dataItem.paused) {
+ aDownload.resume();
+ } else {
+ aDownload.pause();
+ }
+ }.bind(this));
},
downloadsCmd_retry: function DVIC_downloadsCmd_retry()
{
- this.dataItem.retry();
+ this.dataItem.getDownload(function (aDownload) {
+ aDownload.retry();
+ });
},
downloadsCmd_openReferrer: function DVIC_downloadsCmd_openReferrer()
@@ -1315,6 +1418,31 @@ DownloadsViewItemController.prototype = {
// Invoke the command.
this.doCommand(defaultCommand);
}
+ },
+
+ /**
+ * Support function to open the specified nsIFile.
+ */
+ _openExternal: function DVIC_openExternal(aFile)
+ {
+ let protocolSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"]
+ .getService(Ci.nsIExternalProtocolService);
+ protocolSvc.loadUrl(makeFileURI(aFile));
+ },
+
+ /**
+ * Support function that deletes the local file for a download. This is
+ * used in cases where the Download Manager service doesn't delete the file
+ * from disk when cancelling. See bug 732924.
+ */
+ _ensureLocalFileRemoved: function DVIC_ensureLocalFileRemoved()
+ {
+ try {
+ let localFile = this.dataItem.localFile;
+ if (localFile.exists()) {
+ localFile.remove(false);
+ }
+ } catch (ex) { }
}
};
diff --git a/browser/components/downloads/src/DownloadsCommon.jsm b/browser/components/downloads/src/DownloadsCommon.jsm
index 48c3a5653ec1..af7d82862c2a 100644
--- a/browser/components/downloads/src/DownloadsCommon.jsm
+++ b/browser/components/downloads/src/DownloadsCommon.jsm
@@ -63,9 +63,6 @@ const nsIDM = Ci.nsIDownloadManager;
const kDownloadsStringBundleUrl =
"chrome://browser/locale/downloads/downloads.properties";
-const kPrefBdmScanWhenDone = "browser.download.manager.scanWhenDone";
-const kPrefBdmAlertOnExeOpen = "browser.download.manager.alertOnEXEOpen";
-
const kDownloadsStringsRequiringFormatting = {
sizeWithUnits: true,
shortTimeLeftSeconds: true,
@@ -399,117 +396,6 @@ this.DownloadsCommon = {
// never adding to the time left. Ensure that we never fall below one
// second left until all downloads are actually finished.
return aLastSeconds = Math.max(aSeconds, 1);
- },
-
- /**
- * Opens a downloaded file.
- * If you've a dataItem, you should call dataItem.openLocalFile.
- * @param aFile
- * the downloaded file to be opened.
- * @param aMimeInfo
- * the mime type info object. May be null.
- * @param aOwnerWindow
- * the window with which this action is associated.
- */
- openDownloadedFile: function DC_openDownloadedFile(aFile, aMimeInfo, aOwnerWindow) {
- if (!(aFile instanceof Ci.nsIFile))
- throw new Error("aFile must be a nsIFile object");
- if (aMimeInfo && !(aMimeInfo instanceof Ci.nsIMIMEInfo))
- throw new Error("Invalid value passed for aMimeInfo");
- if (!(aOwnerWindow instanceof Ci.nsIDOMWindow))
- throw new Error("aOwnerWindow must be a dom-window object");
-
- // Confirm opening executable files if required.
- if (aFile.isExecutable()) {
- let showAlert = true;
- try {
- showAlert = Services.prefs.getBoolPref(kPrefBdmAlertOnExeOpen);
- } catch (ex) { }
-
- // On Vista and above, we rely on native security prompting for
- // downloaded content unless it's disabled.
- if (DownloadsCommon.isWinVistaOrHigher) {
- try {
- if (Services.prefs.getBoolPref(kPrefBdmScanWhenDone)) {
- showAlert = false;
- }
- } catch (ex) { }
- }
-
- if (showAlert) {
- let name = this.dataItem.target;
- let message =
- DownloadsCommon.strings.fileExecutableSecurityWarning(name, name);
- let title =
- DownloadsCommon.strings.fileExecutableSecurityWarningTitle;
- let dontAsk =
- DownloadsCommon.strings.fileExecutableSecurityWarningDontAsk;
-
- let checkbox = { value: false };
- let open = Services.prompt.confirmCheck(aOwnerWindow, title, message,
- dontAsk, checkbox);
- if (!open) {
- return;
- }
-
- Services.prefs.setBoolPref(kPrefBdmAlertOnExeOpen,
- !checkbox.value);
- }
- }
-
- // Actually open the file.
- try {
- if (aMimeInfo && aMimeInfo.preferredAction == aMimeInfo.useHelperApp) {
- aMimeInfo.launchWithFile(aFile);
- return;
- }
- }
- catch(ex) { }
-
- // If either we don't have the mime info, or the preferred action failed,
- // attempt to launch the file directly.
- try {
- aFile.launch();
- }
- catch(ex) {
- // If launch fails, try sending it through the system's external "file:"
- // URL handler.
- Cc["@mozilla.org/uriloader/external-protocol-service;1"]
- .getService(Ci.nsIExternalProtocolService)
- .loadUrl(NetUtil.newURI(aFile));
- }
- },
-
- /**
- * Show a donwloaded file in the system file manager.
- * If you have a dataItem, use dataItem.showLocalFile.
- *
- * @param aFile
- * a downloaded file.
- */
- showDownloadedFile: function DC_showDownloadedFile(aFile) {
- if (!(aFile instanceof Ci.nsIFile))
- throw new Error("aFile must be a nsIFile object");
- try {
- // Show the directory containing the file and select the file.
- aFile.reveal();
- } catch (ex) {
- // If reveal fails for some reason (e.g., it's not implemented on unix
- // or the file doesn't exist), try using the parent if we have it.
- let parent = aFile.parent;
- if (parent) {
- try {
- // Open the parent directory to show where the file should be.
- parent.launch();
- } catch (ex) {
- // If launch also fails (probably because it's not implemented), let
- // the OS handler try to open the parent.
- Cc["@mozilla.org/uriloader/external-protocol-service;1"]
- .getService(Ci.nsIExternalProtocolService)
- .loadUrl(NetUtil.newURI(parent));
- }
- }
- }
}
};
@@ -1326,101 +1212,6 @@ DownloadsDataItem.prototype = {
// file, though this may throw an exception if the path is invalid.
return new DownloadsLocalFileCtor(aFilename);
}
- },
-
- /**
- * Open the target file for this download.
- *
- * @param aOwnerWindow
- * The window with which the required action is associated.
- * @throws if the file cannot be opened.
- */
- openLocalFile: function DDI_openLocalFile(aOwnerWindow) {
- this.getDownload(function(aDownload) {
- DownloadsCommon.openDownloadedFile(this.localFile,
- aDownload.MIMEInfo,
- aOwnerWindow);
- }.bind(this));
- },
-
- /**
- * Show the downloaded file in the system file manager.
- */
- showLocalFile: function DDI_showLocalFile() {
- DownloadsCommon.showDownloadedFile(this.localFile);
- },
-
- /**
- * Resumes the download if paused, pauses it if active.
- * @throws if the download is not resumable or if has already done.
- */
- togglePauseResume: function DDI_togglePauseResume() {
- if (!this.inProgress || !this.resumable)
- throw new Error("The given download cannot be paused or resumed");
-
- this.getDownload(function(aDownload) {
- if (this.inProgress) {
- if (this.paused)
- aDownload.resume();
- else
- aDownload.pause();
- }
- }.bind(this));
- },
-
- /**
- * Attempts to retry the download.
- * @throws if we cannot.
- */
- retry: function DDI_retry() {
- if (!this.canRetry)
- throw new Error("Cannot rerty this download");
-
- this.getDownload(function(aDownload) {
- aDownload.retry();
- });
- },
-
- /**
- * Support function that deletes the local file for a download. This is
- * used in cases where the Download Manager service doesn't delete the file
- * from disk when cancelling. See bug 732924.
- */
- _ensureLocalFileRemoved: function DDI__ensureLocalFileRemoved()
- {
- try {
- let localFile = this.localFile;
- if (localFile.exists()) {
- localFile.remove(false);
- }
- } catch (ex) { }
- },
-
- /**
- * Cancels the download.
- * @throws if the download is already done.
- */
- cancel: function() {
- if (!this.inProgress)
- throw new Error("Cannot cancel this download");
-
- this.getDownload(function (aDownload) {
- aDownload.cancel();
- this._ensureLocalFileRemoved();
- }.bind(this));
- },
-
- /**
- * Remove the download.
- */
- remove: function DDI_remove() {
- this.getDownload(function (aDownload) {
- if (this.inProgress) {
- aDownload.cancel();
- this._ensureLocalFileRemoved();
- }
- aDownload.remove();
- }.bind(this));
}
};
diff --git a/browser/components/places/content/browserPlacesViews.js b/browser/components/places/content/browserPlacesViews.js
index e64404e465c6..789fcd554831 100644
--- a/browser/components/places/content/browserPlacesViews.js
+++ b/browser/components/places/content/browserPlacesViews.js
@@ -20,8 +20,6 @@ PlacesViewBase.prototype = {
_viewElt: null,
get viewElt() this._viewElt,
- get associatedElement() this._viewElt,
-
get controllers() this._viewElt.controllers,
// The xul element that represents the root container.
diff --git a/browser/components/places/content/download.css b/browser/components/places/content/download.css
deleted file mode 100644
index bec39ca02ed3..000000000000
--- a/browser/components/places/content/download.css
+++ /dev/null
@@ -1,45 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-richlistitem.download button {
- /* These buttons should never get focus, as that would "disable"
- the downloads view controller (it's only used when the richlistbox
- is focused). */
- -moz-user-focus: none;
-}
-
-/*** Visibility of controls inside download items ***/
-
-.download-state:-moz-any( [state="6"], /* Blocked (parental) */
- [state="8"], /* Blocked (dirty) */
- [state="9"]) /* Blocked (policy) */
- .downloadTypeIcon:not(.blockedIcon),
-
-.download-state:not(:-moz-any([state="6"], /* Blocked (parental) */
- [state="8"], /* Blocked (dirty) */
- [state="9"]) /* Blocked (policy) */)
- .downloadTypeIcon.blockedIcon,
-
-.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */
- [state="5"], /* Starting (queued) */
- [state="0"], /* Downloading */
- [state="4"], /* Paused */
- [state="7"]) /* Scanning */)
- .downloadProgress,
-
-.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */
- [state="5"], /* Starting (queued) */
- [state="0"], /* Downloading */
- [state="4"]) /* Paused */)
- .downloadCancel,
-
-.download-state:not(:-moz-any([state="2"], /* Failed */
- [state="3"]) /* Canceled */)
- .downloadRetry,
-
-.download-state:not( [state="1"] /* Finished */)
- .downloadShow
-{
- visibility: hidden;
-}
diff --git a/browser/components/places/content/download.xml b/browser/components/places/content/download.xml
deleted file mode 100644
index 6033c226009d..000000000000
--- a/browser/components/places/content/download.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/browser/components/places/content/downloadsView.js b/browser/components/places/content/downloadsView.js
deleted file mode 100644
index 89edd02b25b1..000000000000
--- a/browser/components/places/content/downloadsView.js
+++ /dev/null
@@ -1,1024 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/**
- * THE PLACES VIEW IMPLEMENTED IN THIS FILE HAS A VERY PARTICULAR USE CASE.
- * IT IS HIGHLY RECOMMENDED NOT TO EXTEND IT FOR ANY OTHER USE CASES OR RELY
- * ON IT AS AN API.
- */
-
-let Cu = Components.utils;
-let Ci = Components.interfaces;
-let Cc = Components.classes;
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/NetUtil.jsm");
-Cu.import("resource://gre/modules/DownloadUtils.jsm");
-Cu.import("resource:///modules/DownloadsCommon.jsm");
-
-const nsIDM = Ci.nsIDownloadManager;
-
-const DESTINATION_FILE_URI_ANNO = "downloads/destinationFileURI";
-const DESTINATION_FILE_NAME_ANNO = "downloads/destinationFileName";
-const DOWNLOAD_STATE_ANNO = "downloads/state";
-
-const DOWNLOAD_VIEW_SUPPORTED_COMMANDS =
- ["cmd_delete", "cmd_copy", "cmd_paste", "cmd_selectAll",
- "downloadsCmd_pauseResume", "downloadsCmd_cancel",
- "downloadsCmd_open", "downloadsCmd_show", "downloadsCmd_retry",
- "downloadsCmd_openReferrer"];
-
-function GetFileForFileURI(aFileURI)
- Cc["@mozilla.org/network/protocol;1?name=file"]
- .getService(Ci.nsIFileProtocolHandler)
- .getFileFromURLSpec(aFileURI);
-
-/**
- * A download element shell is responsible for handling the commands and the
- * displayed data for a single download view element. The download element
- * could represent either a past download (for which we get data from places) or
- * a "session" download (using a data-item object. See DownloadsCommon.jsm), or both.
- *
- * Once initialized with either a data item or a places node, the created richlistitem
- * can be accessed through the |element| getter, and can then be inserted/removed from
- * a richlistbox.
- *
- * The shell doesn't take care of inserting the item, or removing it when it's no longer
- * valid. That's the caller (a DownloadsPlacesView object) responsibility.
- *
- * The caller is also responsible for "passing over" notification from both the
- * download-view and the places-result-observer, in the following manner:
- * - The DownloadsPlacesView object implements getViewItem of the download-view
- * pseudo interface. It returns this object (therefore we implement
- * onStateChangea and onProgressChange here).
- * - The DownloadsPlacesView object adds itself as a places result observer and
- * calls this object's placesNodeIconChanged, placesNodeTitleChanged and
- * placeNodeAnnotationChanged from its callbacks.
- *
- * @param [optional] aDataItem
- * The data item of a the session download. Required if aPlacesNode is not set
- * @param [optional] aPlacesNode
- * The places node for a past download. Required if aDataItem is not set.
- */
-function DownloadElementShell(aDataItem, aPlacesNode) {
- this._element = document.createElement("richlistitem");
- this._element._shell = this;
-
- this._element.classList.add("download");
- this._element.classList.add("download-state");
-
- if (aDataItem)
- this.dataItem = aDataItem;
- if (aPlacesNode)
- this.placesNode = aPlacesNode;
-}
-
-DownloadElementShell.prototype = {
- // The richlistitem for the download
- get element() this._element,
-
- // The data item for the download
- get dataItem() this._dataItem,
-
- set dataItem(aValue) {
- if (this._dataItem = aValue) {
- this._wasDone = this._dataItem.done;
- this._wasInProgress = this._dataItem.inProgress;
- }
- else if (this._placesNode) {
- this._wasInProgress = false;
- this._wasDone = this._state == nsIDM.DOWNLOAD_FINISHED;
- }
-
- this._updateStatusUI();
- return aValue;
- },
-
- get placesNode() this._placesNode,
- set placesNode(aNode) {
- if (this._placesNode != aNode) {
- this._annotations = new Map();
- this._placesNode = aNode;
- if (!this._dataItem && this._placesNode) {
- this._wasInProgress = false;
- this._wasDone = this._state == nsIDM.DOWNLOAD_FINISHED;
- this._updateStatusUI();
- }
- }
- return aNode;
- },
-
- // The download uri (as a string)
- get downloadURI() {
- if (this._dataItem)
- return this._dataItem.uri;
- if (this._placesNode)
- return this._placesNode.uri;
- throw new Error("Unexpected download element state");
- },
-
- get _icon() {
- if (this._targetFileURI)
- return "moz-icon://" + this._targetFileURI + "?size=32";
- if (this._placesNode)
- return this.placesNode.icon;
- if (this._dataItem)
- throw new Error("Session-download items should always have a target file uri");
- throw new Error("Unexpected download element state");
- },
-
- // Helper for getting a places annotation set for the download.
- _getAnnotation: function DES__getAnnotation(aAnnotation, aDefaultValue) {
- if (this._annotations.has(aAnnotation))
- return this._annotations.get(aAnnotation);
-
- let value;
- try {
- value = PlacesUtils.annotations.getPageAnnotation(
- NetUtil.newURI(this.downloadURI), aAnnotation);
- }
- catch(ex) {
- if (aDefaultValue === undefined) {
- throw new Error("Could not get required annotation '" + aAnnotation +
- "' for download with url '" + this.downloadURI + "'");
- }
- value = aDefaultValue;
- }
- this._annotations.set(aAnnotation, value);
- return value;
- },
-
- // The uri (as a string) of the target file, if any.
- get _targetFileURI() {
- if (this._dataItem)
- return this._dataItem.file;
-
- return this._getAnnotation(DESTINATION_FILE_URI_ANNO, "");
- },
-
- // The label for the download
- get _displayName() {
- if (this._dataItem)
- return this._dataItem.target;
-
- try {
- return this._getAnnotation(DESTINATION_FILE_NAME_ANNO, "");
- }
- catch(ex) { }
-
- // Fallback to the places title, or, at last, to the download uri.
- return this._placesNode.title || this.downloadURI;
- },
-
- // If there's a target file for the download, this is its nsIFile object.
- get _file() {
- if (!("__file" in this)) {
- if (this._dataItem) {
- this.__file = this._dataItem.localFile;
- }
- else {
- this.__file = this._targetFileURI ?
- GetFileForFileURI(this._targetFileURI) : null;
- }
- }
- return this.__file;
- },
-
- // The target's file size in bytes. If there's no target file, or If we
- // cannot determine its size, 0 is returned.
- get _fileSize() {
- if (!this._file || !this._file.exists())
- return 0;
- try {
- return this._file.fileSize;
- }
- catch(ex) {
- Cu.reportError(ex);
- return 0;
- }
- },
-
- // The download state (see nsIDownloadManager).
- get _state() {
- if (this._dataItem)
- return this._dataItem.state;
-
- let state = -1;
- try {
- return this._getAnnotation(DOWNLOAD_STATE_ANNO);
- }
- catch (ex) {
- // The state annotation didn't exist in past releases.
- if (!this._file) {
- state = nsIDM.DOWNLOAD_FAILED;
- }
- else if (this._file.exists()) {
- state = this._fileSize > 0 ?
- nsIDM.DOWNLOAD_FINISHED : nsIDM.DOWNLOAD_FAILED;
- }
- else {
- // XXXmano I'm not sure if this right. We should probably show no
- // status text at all in this case.
- state = nsIDM.DOWNLOAD_CANCELED;
- }
- }
- return state;
- },
-
- // The status text for the download
- get _statusText() {
- let s = DownloadsCommon.strings;
- if (this._dataItem && this._dataItem.inProgress) {
- if (this._dataItem.paused) {
- let transfer =
- DownloadUtils.getTransferTotal(this._dataItem.currBytes,
- this._dataItem.maxBytes);
-
- // We use the same XUL label to display both the state and the amount
- // transferred, for example "Paused - 1.1 MB".
- return s.statusSeparatorBeforeNumber(s.statePaused, transfer);
- }
- if (this._dataItem.state == nsIDM.DOWNLOAD_DOWNLOADING) {
- let [status, newEstimatedSecondsLeft] =
- DownloadUtils.getDownloadStatus(this.dataItem.currBytes,
- this.dataItem.maxBytes,
- this.dataItem.speed,
- this._lastEstimatedSecondsLeft);
- this._lastEstimatedSecondsLeft = newEstimatedSecondsLeft;
- return status;
- }
- if (this._dataItem.starting) {
- return s.stateStarting;
- }
- if (this._dataItem.state == nsIDM.DOWNLOAD_SCANNING) {
- return s.stateScanning;
- }
-
- let [displayHost, fullHost] =
- DownloadUtils.getURIHost(this._dataItem.referrer ||
- this._dataItem.uri);
-
- let end = new Date(this.dataItem.endTime);
- let [displayDate, fullDate] = DownloadUtils.getReadableDates(end);
- return s.statusSeparator(fullHost, fullDate);
- }
-
- switch (this._state) {
- case nsIDM.DOWNLOAD_FAILED:
- return s.stateFailed;
- case nsIDM.DOWNLOAD_CANCELED:
- return s.stateCanceled;
- case nsIDM.DOWNLOAD_BLOCKED_PARENTAL:
- return s.stateBlockedParentalControls;
- case nsIDM.DOWNLOAD_BLOCKED_POLICY:
- return s.stateBlockedPolicy;
- case nsIDM.DOWNLOAD_DIRTY:
- return s.stateDirty;
- case nsIDM.DOWNLOAD_FINISHED:{
- // For completed downloads, show the file size (e.g. "1.5 MB")
- if (this._fileSize > 0) {
- let [size, unit] = DownloadUtils.convertByteUnits(this._fileSize);
- return s.sizeWithUnits(size, unit);
- }
- break;
- }
- }
-
- return "";
- },
-
- // The progressmeter element for the download
- get _progressElement() {
- let progressElement = document.getAnonymousElementByAttribute(
- this._element, "anonid", "progressmeter");
- if (progressElement) {
- delete this._progressElement;
- return this._progressElement = progressElement;
- }
- return null;
- },
-
- // Updates the download state attribute (and by that hide/unhide the
- // appropriate buttons and context menu items), the status text label,
- // and the progress meter.
- _updateDownloadStatusUI: function DES__updateDownloadStatusUI() {
- this._element.setAttribute("state", this._state);
- this._element.setAttribute("status", this._statusText);
-
- // For past-downloads, we're done. For session-downloads, we may also need
- // to update the progress-meter.
- if (!this._dataItem)
- return;
-
- // Copied from updateProgress in downloads.js.
- if (this._dataItem.starting) {
- // Before the download starts, the progress meter has its initial value.
- this._element.setAttribute("progressmode", "normal");
- this._element.setAttribute("progress", "0");
- }
- else if (this._dataItem.state == nsIDM.DOWNLOAD_SCANNING ||
- this._dataItem.percentComplete == -1) {
- // We might not know the progress of a running download, and we don't know
- // the remaining time during the malware scanning phase.
- this._element.setAttribute("progressmode", "undetermined");
- }
- else {
- // This is a running download of which we know the progress.
- this._element.setAttribute("progressmode", "normal");
- this._element.setAttribute("progress", this._dataItem.percentComplete);
- }
-
- // Dispatch the ValueChange event for accessibility, if possible.
- if (this._progressElement) {
- let event = document.createEvent("Events");
- event.initEvent("ValueChange", true, true);
- this._progressElement.dispatchEvent(event);
- }
-
- goUpdateDownloadCommands();
- },
-
- _updateStatusUI: function DES__updateStatusUI() {
- this._updateDownloadStatusUI();
- this._element.setAttribute("image", this._icon);
- this._element.setAttribute("displayName", this._displayName);
- },
-
- placesNodeIconChanged: function DES_placesNodeIconChanged() {
- if (!this._dataItem)
- this._element.setAttribute("image", this._icon);
- },
-
- placesNodeTitleChanged: function DES_placesNodeTitleChanged() {
- if (!this._dataItem)
- this._element.setAttribute("displayName", this._displayName);
- },
-
- placesNodeAnnotationChanged: function DES_placesNodeAnnotationChanged(aAnnoName) {
- this._annotations.delete(aAnnoName);
- if (!this._dataItem) {
- if (aAnnoName == DESTINATION_FILE_URI_ANNO) {
- this._element.setAttribute("image", this._icon);
- this._updateDownloadStatusUI();
- }
- else if (aAnnoName == DESTINATION_FILE_NAME_ANNO) {
- this._element.setAttribute("displayName", this._displayName);
- }
- else if (aAnnoName == DOWNLOAD_STATE_ANNO) {
- this._updateDownloadStatusUI();
- }
- }
- },
-
- /* DownloadView */
- onStateChange: function DES_onStateChange() {
- // See comment in DVI_onStateChange in downloads.js (the panel-view)
- if (!this._wasDone && this._dataItem.done)
- this._element.setAttribute("image", this._icon + "&state=normal");
-
- this._wasDone = this._dataItem.done;
-
- // Update the end time using the current time if required.
- if (this._wasInProgress && !this._dataItem.inProgress) {
- this._endTime = Date.now();
- }
-
- this._wasDone = this._dataItem.done;
- this._wasInProgress = this._dataItem.inProgress;
-
- this._updateDownloadStatusUI();
- },
-
- /* DownloadView */
- onProgressChange: function DES_onProgressChange() {
- this._updateDownloadStatusUI();
- },
-
- /* nsIController */
- isCommandEnabled: function DES_isCommandEnabled(aCommand) {
- switch (aCommand) {
- case "downloadsCmd_open": {
- return this._file.exists() &&
- ((this._dataItem && this._dataItem.openable) ||
- (this._state == nsIDM.DOWNLOAD_FINISHED));
- }
- case "downloadsCmd_show": {
- return this._getTargetFileOrPartFileIfExists() != null;
- }
- case "downloadsCmd_pauseResume":
- return this._dataItem && this._dataItem.inProgress && this._dataItem.resumable;
- case "downloadsCmd_retry":
- return ((this._dataItem && this._dataItem.canRetry) ||
- (!this._dataItem && this._file))
- case "downloadsCmd_openReferrer":
- return this._dataItem && !!this._dataItem.referrer;
- case "cmd_delete":
- // The behavior in this case is somewhat unexpected, so we disallow that.
- if (this._placesNode && this._dataItem && this._dataItem.inProgress)
- return false;
- return true;
- case "downloadsCmd_cancel":
- return this._dataItem != null;
- }
- },
-
- _getTargetFileOrPartFileIfExists: function DES__getTargetFileOrPartFileIfExists() {
- if (this._file && this._file.exists())
- return this._file;
- if (this._dataItem &&
- this._dataItem.partFile && this._dataItem.partFile.exists())
- return this._dataItem.partFile;
- return null;
- },
-
- _retryAsHistoryDownload: function DES__retryAsHistoryDownload() {
- // TODO: save in the right location (the current saveURL api does not allow this)
- saveURL(this.downloadURI, this._displayName, null, true, true, undefined, document);
- },
-
- /* nsIController */
- doCommand: function DES_doCommand(aCommand) {
- switch (aCommand) {
- case "downloadsCmd_open": {
- if (this._dateItem)
- this._dataItem.openLocalFile(window);
- else
- DownloadsCommon.openDownloadedFile(this._file, null, window);
- break;
- }
- case "downloadsCmd_show": {
- if (this._dataItem)
- this._dataItem.showLocalFile();
- else
- DownloadsCommon.showDownloadedFile(this._getTargetFileOrPartFileIfExists());
- break;
- }
- case "downloadsCmd_openReferrer": {
- openURL(this._dataItem.referrer);
- break;
- }
- case "downloadsCmd_cancel": {
- this._dataItem.cancel();
- break;
- }
- case "cmd_delete": {
- if (this._dataItem)
- this._dataItem.remove();
- if (this._placesNode)
- PlacesUtils.bhistory.removePage(NetUtil.newURI(this.downloadURI));
- break;
- }
- case "downloadsCmd_retry": {
- if (this._dataItem)
- this._dataItem.retry();
- else
- this._retryAsHistoryDownload();
- break;
- }
- case "downloadsCmd_pauseResume": {
- this._dataItem.togglePauseResume();
- break;
- }
- }
- },
-
- // Returns whether or not the download handled by this shell should
- // show up in the search results for the given term. Both the display
- // name for the download and the url are searched.
- matchesSearchTerm: function DES_matchesSearchTerm(aTerm) {
- // Stub implemention until we figure out something better
- aTerm = aTerm.toLowerCase();
- return this._displayName.toLowerCase().indexOf(aTerm) != -1 ||
- this.downloadURI.toLowerCase().indexOf(aTerm) != -1;
- },
-
- // Handles return kepress on the element (the keypress listener is
- // set in the DownloadsPlacesView object).
- doDefaultCommand: function DES_doDefaultCommand() {
- function getDefaultCommandForState(aState) {
- switch (aState) {
- case nsIDM.DOWNLOAD_FINISHED:
- return "downloadsCmd_open";
- case nsIDM.DOWNLOAD_PAUSED:
- return "downloadsCmd_pauseResume";
- case nsIDM.DOWNLOAD_NOTSTARTED:
- case nsIDM.DOWNLOAD_QUEUED:
- return "downloadsCmd_cancel";
- case nsIDM.DOWNLOAD_FAILED:
- case nsIDM.DOWNLOAD_CANCELED:
- return "downloadsCmd_retry";
- case nsIDM.DOWNLOAD_DOWNLOADING:
- case nsIDM.DOWNLOAD_SCANNING:
- return "downloadsCmd_show";
- case nsIDM.DOWNLOAD_BLOCKED_PARENTAL:
- case nsIDM.DOWNLOAD_DIRTY:
- case nsIDM.DOWNLOAD_BLOCKED_POLICY:
- return "downloadsCmd_openReferrer";
- }
- }
- let command = getDefaultCommandForState(this._state);
- if (this.isCommandEnabled(command))
- this.doCommand(command);
- }
-};
-
-/**
- * A Downloads Places View is a places view designed to show a places query
- * for history donwloads alongside the current "session"-downloads.
- *
- * As we don't use the places controller, some methods implemented by other
- * places views are not implemented by this view.
- *
- * A richlistitem in this view can represent either a past download or a session
- * download, or both. Session downloads are shown first in the view, and as long
- * as they exist they "collapses" their history "counterpart" (So we don't show two
- * items for every download).
- */
-function DownloadsPlacesView(aRichListBox, aPlace) {
- this._richlistbox = aRichListBox;
- this._richlistbox._placesView = this;
- this._downloadElementsShellsForURI = new Map();
- this._viewItemsForDataItems = new WeakMap();
- this._shells = new Set();
- this._lastSessionDownloadElement = null;
- this.place = aPlace;
- this._richlistbox.controllers.appendController(this);
-}
-
-DownloadsPlacesView.prototype = {
- _registerAsDownloadsView: function DPV__registerAsDownloadsView() {
- let downloadsData = DownloadsCommon.getData(window.opener || window);
- downloadsData.addView(this);
- // Make sure to unregister the view if the window is closed.
- window.addEventListener("unload", function() {
- if (this._result) {
- downloadsData.removeView(this);
- }
- }.bind(this), true);
- },
-
- get associatedElement() this._richlistbox,
-
- _forEachDownloadElementShellForURI:
- function DPV__forEachDownloadElementShellForURI(aURI, aCallback) {
- if (this._downloadElementsShellsForURI.has(aURI)) {
- let downloadElementShells = this._downloadElementsShellsForURI.get(aURI);
- for (let des of downloadElementShells) {
- aCallback(des);
- }
- }
- },
-
- // Given a data item for a session download, or a places node for a past
- // download, updates the view as necessary.
- // 1. If the given data is a places node, we check whether there are any
- // element for the same download url. If there are, then we just reset
- // their places node. Otherwise we add a new download element.
- // 2. If the given data is a data item, we first check if there's an history
- // download in the list that is not associated with a data item. If we found
- // one, we use it for the data item as well and reposition it alongside the
- // other session downloads. If we don't, then we go ahead and create a new
- // element for the download.
- _addDownloadData:
- function DPV_addDownload(aDataItem, aPlacesNode, aNewest) {
- let downloadURI = aPlacesNode ? aPlacesNode.uri : aDataItem.uri;
- let shellsForURI = this._downloadElementsShellsForURI.get(downloadURI, null);
- if (!shellsForURI) {
- shellsForURI = new Set();
- this._downloadElementsShellsForURI.set(downloadURI, shellsForURI);
- }
-
- let newOrUpdatedShell = null;
-
- // Trivial: if there are no shells for this download URI, we always
- // need to create one.
- let shouldCreateShell = shellsForURI.size == 0;
-
- // However, if we do have shells for this download uri, there are
- // few options:
- // 1) There's only one shell and it's for a history download (it has
- // no data item). In this case, we update this shell and move it
- // if necessary
- // 2) There are multiple shells, indicicating multiple downloads for
- // the same download uri are running. In this case we create
- // anoter shell for the download (so we have one shell for each data
- // item).
- //
- // Note: If a cancelled session download is already in the list, and the
- // download is retired, onDataItemAdded is called again for the same
- // data item. Thus, we also check that we make sure we don't have a view item
- // already.
- if (!shouldCreateShell &&
- aDataItem && this.getViewItem(aDataItem) == null) {
- // If there's a past-download-only shell for this download-uri with no
- // associated data item, use it for the new data item. Otherwise, go ahead
- // and create another shell.
- shouldCreateShell = true;
- for (let shell of shellsForURI) {
- if (!shell.dataItem) {
- shouldCreateShell = false;
- shell.dataItem = aDataItem;
- newOrUpdatedShell = shell;
- this._viewItemsForDataItems.set(aDataItem, shell);
- break;
- }
- }
- }
-
- if (shouldCreateShell) {
- let shell = new DownloadElementShell(aDataItem, aPlacesNode);
- newOrUpdatedShell = shell;
- element = shell.element;
- shellsForURI.add(shell);
- if (aDataItem)
- this._viewItemsForDataItems.set(aDataItem, shell);
- }
- else if (aPlacesNode) {
- for (let shell of shellsForURI) {
- if (shell.placesNode != aPlacesNode)
- shell.placesNode = aPlacesNode;
- }
- }
-
- if (newOrUpdatedShell) {
- if (aNewest) {
- this._richlistbox.insertBefore(newOrUpdatedShell.element,
- this._richlistbox.firstChild);
- if (!this._lastSessionDownloadElement) {
- this._lastSessionDownloadElement = newOrUpdatedShell.element;
- }
- }
- else if (aDataItem) {
- let before = this._lastSessionDownloadElement ?
- this._lastSessionDownloadElement.nextSibling : this._richlistbox.firstChild
- this._richlistbox.insertBefore(newOrUpdatedShell.element, before)
- this._lastSessionDownloadElement = newOrUpdatedShell.element;
- }
- else {
- this._richlistbox.appendChild(newOrUpdatedShell.element);
- }
- }
- },
-
- _removeElement: function DPV__removeElement(aElement) {
- // If the element was selected exclusively, select its next
- // sibling first, if any.
- if (aElement.nextSibling &&
- this._richlistbox.selectedItems &&
- this._richlistbox.selectedItems[0] == aElement) {
- this._richlistbox.selectItem(aElement.nextSibling);
- }
- this._richlistbox.removeChild(aElement);
- },
-
- _historyDownloadRemoved:
- function DPV__historyDownloadRemoved(aPlacesNode) {
- let downloadURI = aPlacesNode.uri;
- let shellsForURI = this._downloadElementsShellsForURI.get(downloadURI, null);
- if (shellsForURI) {
- for (let shell of shellsForURI) {
- if (shell.dataItem) {
- shell.placesNode = null;
- }
- else {
- this._removeElement(shell.element);
- shellsForURI.delete(shell);
- if (shellsForURI.size == 0)
- this._downloadElementsShellsForURI.delete(downloadURI);
- }
- }
- }
- },
-
- _sessionDownloadRemoved:
- function DPV__sessionDownloadRemoved(aDataItem) {
- let shells = this._downloadElementsShellsForURI.get(aDataItem.uri, null);
- if (shells.size == 0)
- throw new Error("Should have had at leaat one shell for this uri");
-
- let shell = this.getViewItem(aDataItem);
- if (!shells.has(shell))
- throw new Error("Missing download element shell in shells list for url");
-
- // If there's more than one item for this download uri, we can let the
- // view item for this this particular data item go away.
- // If there's only one item for this download uri, we should only
- // keep it if it is associated with a history download.
- if (shells.size > 1 || !shell.placesNode) {
- this._removeElement(shell.element);
- shells.delete(shell);
- if (shells.size == 0)
- this._downloadElementsShellsForURI.delete(aDataItem.uri);
- return;
- }
- else {
- shell.dataItem = null;
- // Move it below the session-download items;
- if (this._lastSessionDownloadElement == shell.dataItem) {
- this._lastSessionDownloadElement = shell.dataItem.previousSibling;
- }
- else {
- let before = this._lastSessionDownloadElement ?
- this._lastSessionDownloadElement.nextSibling : this._richlistbox.firstchild;
- this._richlistbox.insertBefore(shell.element, before);
- }
- }
- },
-
- get place() this._place,
-
- set place(val) {
- // Cleanup
- this.result = null;
-
- this._searchTerm = "";
- this._place = val;
-
- let history = PlacesUtils.history;
- let queries = { }, options = { };
- history.queryStringToQueries(val, queries, { }, options);
- if (!queries.value.length)
- queries.value = [history.getNewQuery()];
-
- let result = history.executeQueries(queries.value, queries.value.length,
- options.value);
- result.addObserver(this, false);
-
- this._registerAsDownloadsView();
-
- return val;
- },
-
- _cleanUp: function() {
- while (this._richlistbox.firstChild) {
- this._richlistbox.removeChild(this._richlistbox.firstChild);
- }
- this._searchTerm = "";
- DownloadsCommon.getData(window.opener || window).removeView();
- this._downloadElementsShellsForURI = new Map();
- this._viewItemsForDataItems = new WeakMap();
- this._shells = new Set();
- this._resultNode = null;
- this._result = null;
- this._lastSessionDownloadElement = null;
- },
-
- _result: null,
- get result() this._result,
- set result(val) {
- if (this._result == val)
- return val;
-
- if (this._result) {
- this._result.removeObserver(this);
- this._resultNode.containerOpen = false;
- }
-
- this._result = val;
- if (val) {
- this._resultNode = val.root;
- this._resultNode.containerOpen = true;
- }
- else {
- this._cleanUp();
- }
-
- return val;
- },
-
- get selectedNodes() {
- let placesNodes = [];
- let selectedElements = this._richlistbox.selectedItems;
- for (let elt of selectedElements) {
- if (elt.placesNode)
- placesNodes.push(elt.placesNode);
- }
- return placesNodes;
- },
-
- get selectedNode() {
- let selectedNodes = this.selectedNodes;
- return selectedNodes.length == 1 ? selectedNodes[0] : null;
- },
-
- get hasSelection() this.selectedNodes.length > 0,
-
- containerStateChanged:
- function DPV_containerStateChanged(aNode, aOldState, aNewState) {
- this.invalidateContainer(aNode)
- },
-
- invalidateContainer:
- function DPV_invalidateContainer(aContainer) {
- if (aContainer != this._resultNode)
- throw new Error("Unexpected container node");
-
- if (aContainer.containerOpen) {
- for (let i = 0; i < aContainer.childCount; i++) {
- try {
- this._addDownloadData(null, aContainer.getChild(i), false)
- }
- catch(ex) {
- Cu.reportError(ex);
- }
- }
- }
- else {
- throw new Error("Root container for the downloads query cannot be closed");
- }
- },
-
- nodeInserted: function DPV_nodeInserted(aParent, aPlacesNode) {
- this._addDownloadData(null, aPlacesNode, false);
- },
-
- nodeRemoved: function DPV_nodeRemoved(aParent, aPlacesNode, aOldIndex) {
- this._historyDownloadRemoved(aPlacesNode);
- },
-
- nodeIconChanged: function DPV_nodeIconChanged(aNode) {
- this._forEachDownloadElementShellForURI(aNode.uri, function(aDownloadElementShell) {
- aDownloadElementShell.placesNodeIconChanged();
- });
- },
-
- nodeAnnotationChanged: function DPV_nodeAnnotationChanged(aNode, aAnnoName) {
- this._forEachDownloadElementShellForURI(aNode.uri, function(aDownloadElementShell) {
- aDownloadElementShell.placesNodeAnnotationChanged(aAnnoName);
- });
- },
-
- nodeTitleChanged: function DPV_nodeTitleChanged(aNode, aNewTitle) {
- this._forEachDownloadElementShellForURI(aNode.uri, function(aDownloadElementShell) {
- aDownloadElementShell.placesNodeTitleChanged();
- });
- },
-
- nodeKeywordChanged: function() {},
- nodeDateAddedChanged: function() {},
- nodeLastModifiedChanged: function() {},
- nodeReplaced: function() {},
- nodeHistoryDetailsChanged: function() {},
- nodeTagsChanged: function() {},
- sortingChanged: function() {},
- nodeMoved: function() {},
- nodeURIChanged: function() {},
-
- get controller() this._richlistbox.controller,
-
- get searchTerm() this._searchTerm,
- set searchTerm(aValue) {
- for (let element of this._richlistbox.childNodes) {
- element.hidden = !element._shell.matchesSearchTerm(aValue);
- }
- return aValue;
- },
-
- applyFilter: function() {
- throw new Error("applyFilter is not implemented by the DownloadsView")
- },
-
- load: function(aQueries, aOptions) {
- throw new Error("|load| is not implemented by the Downloads View");
- },
-
- onDataLoadStarting: function() { },
- onDataLoadCompleted: function() { },
-
- onDataItemAdded: function DPV_onDataItemAdded(aDataItem, aNewest) {
- this._addDownloadData(aDataItem, null, aNewest);
- },
-
- onDataItemRemoved: function DPV_onDataItemRemoved(aDataItem) {
- this._sessionDownloadRemoved(aDataItem)
- },
-
- getViewItem: function(aDataItem)
- this._viewItemsForDataItems.get(aDataItem, null),
-
- supportsCommand: function(aCommand)
- DOWNLOAD_VIEW_SUPPORTED_COMMANDS.indexOf(aCommand) != -1,
-
- isCommandEnabled: function DPV_isCommandEnabled(aCommand) {
- let selectedElements = this._richlistbox.selectedItems;
- switch (aCommand) {
- case "cmd_copy":
- return selectedElements && selectedElements.length > 0;
- case "cmd_selectAll":
- return true;
- case "cmd_paste":
- return this._canDownloadClipboardURL();
- default:
- return Array.every(selectedElements, function(element) {
- return element._shell.isCommandEnabled(aCommand);
- });
- }
- },
-
- _copySelectedDownloadsToClipboard:
- function DPV__copySelectedDownloadsToClipboard() {
- let selectedElements = this._richlistbox.selectedItems;
- let urls = [e._shell.downloadURI for each (e in selectedElements)];
-
- Cc["@mozilla.org/widget/clipboardhelper;1"].
- getService(Ci.nsIClipboardHelper).copyString(urls.join("\n"), document);
- },
-
- _getURLFromClipboardData: function DPV__getURLFromClipboardData() {
- let trans = Cc["@mozilla.org/widget/transferable;1"].
- createInstance(Ci.nsITransferable);
- trans.init(null);
-
- let flavors = ["text/x-moz-url", "text/unicode"];
- flavors.forEach(trans.addDataFlavor);
-
- Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard);
-
- // Getting the data or creating the nsIURI might fail
- try {
- let data = {};
- trans.getAnyTransferData({}, data, {});
- let [url, name] = data.value.QueryInterface(Ci.nsISupportsString)
- .data.split("\n");
- if (url)
- return [NetUtil.newURI(url, null, null).spec, name];
- }
- catch(ex) { }
-
- return ["", ""];
- },
-
- _canDownloadClipboardURL: function DPV__canDownloadClipboardURL() {
- let [url, name] = this._getURLFromClipboardData();
- return url != "";
- },
-
- _downloadURLFromClipboard: function DPV__downloadURLFromClipboard() {
- let [url, name] = this._getURLFromClipboardData();
- saveURL(url, name || url, null, true, true, undefined, document);
- },
-
- doCommand: function DPV_doCommand(aCommand) {
- switch (aCommand) {
- case "cmd_copy":
- this._copySelectedDownloadsToClipboard();
- break;
- case "cmd_selectAll":
- this._richlistbox.selectAll();
- break;
- case "cmd_paste":
- this._downloadURLFromClipboard();
- break;
- default: {
- let selectedElements = this._richlistbox.selectedItems;
- for (let element of selectedElements) {
- element._shell.doCommand(aCommand);
- }
- }
- }
- },
-
- onEvent: function() { },
-
- onContextMenu: function DPV_onContextMenu(aEvent)
- {
- let element = this._richlistbox.selectedItem;
- if (!element || !element._shell)
- return false;
-
- // Set the state attribute so that only the appropriate items are displayed.
- let contextMenu = document.getElementById("downloadsContextMenu");
- contextMenu.setAttribute("state", element._shell._state);
- },
-
- onKeyPress: function DPV_onKeyPress(aEvent) {
- let selectedElements = this._richlistbox.selectedItems;
- if (!selectedElements)
- return;
-
- if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN) {
- // In the content tree, opening bookmarks by pressing return is only
- // supported when a single item is selected. To be consistent, do the
- // same here.
- if (selectedElements.length == 1) {
- let element = selectedElements[0];
- if (element._shell)
- element._shell.doDefaultCommand();
- }
- }
- else if (aEvent.charCode == " ".charCodeAt(0)) {
- // Pausue/Resume every selected download
- for (let element of selectedElements) {
- if (element._shell.isCommandEnabled("downloadsCmd_pauseResume"))
- element._shell.doCommand("downloadsCmd_pauseResume");
- }
- }
- }
-};
-
-function goUpdateDownloadCommands() {
- for (let command of DOWNLOAD_VIEW_SUPPORTED_COMMANDS) {
- goUpdateCommand(command);
- }
-}
diff --git a/browser/components/places/content/places.css b/browser/components/places/content/places.css
index 41c5afef3016..5151cca8254b 100644
--- a/browser/components/places/content/places.css
+++ b/browser/components/places/content/places.css
@@ -14,33 +14,3 @@ tree[type="places"] {
menupopup[placespopup="true"] {
-moz-binding: url("chrome://browser/content/places/menu.xml#places-popup-base");
}
-
-richlistitem.download {
- -moz-binding: url('chrome://browser/content/places/download.xml#download');
-}
-
-.download-state:not( [state="0"] /* Downloading */)
- .downloadPauseMenuItem,
-.download-state:not( [state="4"] /* Paused */)
- .downloadResumeMenuItem,
-.download-state:not(:-moz-any([state="2"], /* Failed */
- [state="4"]) /* Paused */)
- .downloadCancelMenuItem,
-.download-state:not(:-moz-any([state="1"], /* Finished */
- [state="2"], /* Failed */
- [state="3"], /* Canceled */
- [state="6"], /* Blocked (parental) */
- [state="8"], /* Blocked (dirty) */
- [state="9"]) /* Blocked (policy) */)
- .downloadRemoveFromListMenuItem,
-.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */
- [state="5"], /* Starting (queued) */
- [state="0"], /* Downloading */
- [state="4"]) /* Paused */)
- .downloadShowMenuItem,
-
-.download-state[state="7"] .downloadCommandsSeparator
-
-{
- display: none;
-}
diff --git a/browser/components/places/content/places.js b/browser/components/places/content/places.js
index e783ec407e29..bd9a0bacfd1d 100644
--- a/browser/components/places/content/places.js
+++ b/browser/components/places/content/places.js
@@ -5,13 +5,9 @@
Components.utils.import("resource:///modules/MigrationUtils.jsm");
-const DOWNLOADS_QUERY = "place:transition=" +
- Components.interfaces.nsINavHistoryService.TRANSITION_DOWNLOAD +
- "&sort=" +
- Components.interfaces.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING;
-
var PlacesOrganizer = {
_places: null,
+ _content: null,
// IDs of fields from editBookmarkOverlay that should be hidden when infoBox
// is minimal. IDs should be kept in sync with the IDs of the elements
@@ -36,9 +32,8 @@ var PlacesOrganizer = {
},
init: function PO_init() {
- ContentArea.init();
-
this._places = document.getElementById("placesList");
+ this._content = document.getElementById("placeContent");
this._initFolderTree();
var leftPaneSelection = "AllBookmarks"; // default to all-bookmarks
@@ -50,6 +45,12 @@ var PlacesOrganizer = {
this._backHistory.splice(0, this._backHistory.length);
document.getElementById("OrganizerCommand:Back").setAttribute("disabled", true);
+ var view = this._content.treeBoxObject.view;
+ if (view.rowCount > 0)
+ view.selection.select(0);
+
+ this._content.focus();
+
// Set up the search UI.
PlacesSearchBox.init();
@@ -83,13 +84,6 @@ var PlacesOrganizer = {
#ifndef MOZ_PER_WINDOW_PRIVATE_BROWSING
gPrivateBrowsingListener.init();
#endif
-
- // Select the first item in the content area view.
- let view = ContentArea.currentView;
- let root = view.result ? view.result.root : null;
- if (root && root.containerOpen && root.childCount >= 0)
- view.selectNode(root.getChild(0));
- ContentArea.focus();
},
QueryInterface: function PO_QueryInterface(aIID) {
@@ -145,9 +139,9 @@ var PlacesOrganizer = {
if (!this._places.hasSelection) {
// If no node was found for the given place: uri, just load it directly
- ContentArea.currentPlace = aLocation;
+ this._content.place = aLocation;
}
- this.updateDetailsPane();
+ this.onContentTreeSelect();
// update navigation commands
if (this._backHistory.length == 0)
@@ -207,8 +201,8 @@ var PlacesOrganizer = {
// If either the place of the content tree in the right pane has changed or
// the user cleared the search box, update the place, hide the search UI,
// and update the back/forward buttons by setting location.
- if (ContentArea.currentPlace != placeURI || !resetSearchBox) {
- ContentArea.currentPlace = placeURI;
+ if (this._content.place != placeURI || !resetSearchBox) {
+ this._content.place = placeURI;
this.location = node.uri;
}
@@ -227,7 +221,8 @@ var PlacesOrganizer = {
PlacesSearchBox.searchFilter.reset();
this._setSearchScopeForNode(node);
- this.updateDetailsPane();
+ if (this._places.treeBoxObject.focused)
+ this._fillDetailsPane([node]);
},
/**
@@ -252,39 +247,50 @@ var PlacesOrganizer = {
},
/**
- * Handle clicks on the places list.
+ * Handle clicks on the tree.
* Single Left click, right click or modified click do not result in any
* special action, since they're related to selection.
* @param aEvent
* The mouse event.
*/
- onPlacesListClick: function PO_onPlacesListClick(aEvent) {
+ onTreeClick: function PO_onTreeClick(aEvent) {
// Only handle clicks on tree children.
if (aEvent.target.localName != "treechildren")
return;
- let node = this._places.selectedNode;
- if (node) {
- let middleClick = aEvent.button == 1 && aEvent.detail == 1;
- if (middleClick && PlacesUtils.nodeIsContainer(node)) {
+ var currentView = aEvent.currentTarget;
+ var selectedNode = currentView.selectedNode;
+ if (selectedNode) {
+ var doubleClickOnFlatList = (aEvent.button == 0 && aEvent.detail == 2 &&
+ aEvent.target.parentNode.flatList);
+ var middleClick = (aEvent.button == 1 && aEvent.detail == 1);
+
+ if (PlacesUtils.nodeIsURI(selectedNode) &&
+ (doubleClickOnFlatList || middleClick)) {
+ // Open associated uri in the browser.
+ PlacesOrganizer.openSelectedNode(aEvent);
+ }
+ else if (middleClick &&
+ PlacesUtils.nodeIsContainer(selectedNode)) {
// The command execution function will take care of seeing if the
// selection is a folder or a different container type, and will
// load its contents in tabs.
- PlacesUIUtils.openContainerNodeInTabs(selectedNode, aEvent, this._places);
+ PlacesUIUtils.openContainerNodeInTabs(selectedNode, aEvent, currentView);
}
}
},
/**
- * Handle focus changes on the places list and the current content view.
+ * Handle focus changes on the trees.
+ * When moving focus between panes we should update the details pane contents.
+ * @param aEvent
+ * The mouse event.
*/
- updateDetailsPane: function PO_updateDetailsPane() {
- let view = PlacesUIUtils.getViewForNode(document.activeElement);
- if (view) {
- let selectedNodes = view.selectedNode ?
- [view.selectedNode] : view.selectedNodes;
- this._fillDetailsPane(selectedNodes);
- }
+ onTreeFocus: function PO_onTreeFocus(aEvent) {
+ var currentView = aEvent.currentTarget;
+ var selectedNodes = currentView.selectedNode ? [currentView.selectedNode] :
+ this._content.selectedNodes;
+ this._fillDetailsPane(selectedNodes);
},
openFlatContainer: function PO_openFlatContainerFlatContainer(aContainer) {
@@ -294,12 +300,17 @@ var PlacesOrganizer = {
this._places.selectPlaceURI(aContainer.uri);
},
+ openSelectedNode: function PO_openSelectedNode(aEvent) {
+ PlacesUIUtils.openNodeWithEvent(this._content.selectedNode, aEvent,
+ this._content);
+ },
+
/**
* Returns the options associated with the query currently loaded in the
* main places pane.
*/
getCurrentOptions: function PO_getCurrentOptions() {
- return PlacesUtils.asQuery(ContentArea.currentView.result.root).queryOptions;
+ return PlacesUtils.asQuery(this._content.result.root).queryOptions;
},
/**
@@ -307,7 +318,7 @@ var PlacesOrganizer = {
* main places pane.
*/
getCurrentQueries: function PO_getCurrentQueries() {
- return PlacesUtils.asQuery(ContentArea.currentView.result.root).getQueries();
+ return PlacesUtils.asQuery(this._content.result.root).getQueries();
},
/**
@@ -553,6 +564,11 @@ var PlacesOrganizer = {
canvas.height = height;
},
+ onContentTreeSelect: function PO_onContentTreeSelect() {
+ if (this._content.treeBoxObject.focused)
+ this._fillDetailsPane(this._content.selectedNodes);
+ },
+
_fillDetailsPane: function PO__fillDetailsPane(aNodeList) {
var infoBox = document.getElementById("infoBox");
var detailsDeck = document.getElementById("detailsDeck");
@@ -655,15 +671,10 @@ var PlacesOrganizer = {
else {
detailsDeck.selectedIndex = 0;
infoBox.hidden = true;
- let selectItemDesc = document.getElementById("selectItemDescription");
- let itemsCountLabel = document.getElementById("itemsCountText");
- let itemsCount = 0;
- if (ContentArea.currentView.result) {
- let rootNode = ContentArea.currentView.result.root;
- if (rootNode.containerOpen)
- itemsCount = rootNode.childCount;
- }
- if (itemsCount == 0) {
+ var selectItemDesc = document.getElementById("selectItemDescription");
+ var itemsCountLabel = document.getElementById("itemsCountText");
+ var rowCount = this._content.treeBoxObject.view.rowCount;
+ if (rowCount == 0) {
selectItemDesc.hidden = true;
itemsCountLabel.value = PlacesUIUtils.getString("detailsPane.noItems");
}
@@ -671,7 +682,7 @@ var PlacesOrganizer = {
selectItemDesc.hidden = false;
itemsCountLabel.value =
PlacesUIUtils.getPluralString("detailsPane.itemsCountLabel",
- itemsCount, [itemsCount]);
+ rowCount, [rowCount]);
}
}
},
@@ -768,14 +779,14 @@ var PlacesSearchBox = {
return;
}
- let currentView = ContentArea.currentView;
- let currentOptions = PO.getCurrentOptions();
+ var currentOptions = PO.getCurrentOptions();
+ var content = PO._content;
// Search according to the current scope, which was set by
// PQB_setScope()
switch (PlacesSearchBox.filterCollection) {
case "bookmarks":
- currentView.applyFilter(filterString, this.folders);
+ content.applyFilter(filterString, this.folders);
break;
case "history":
if (currentOptions.queryType != Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
@@ -786,17 +797,27 @@ var PlacesSearchBox = {
options.resultType = currentOptions.RESULT_TYPE_URI;
options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY;
options.includeHidden = true;
- currentView.load([query], options);
+ content.load([query], options);
}
else {
- currentView.applyFilter(filterString, null, true);
+ content.applyFilter(filterString, null, true);
}
break;
- case "downloads":
- currentView.searchTerm = filterString;
+ case "downloads": {
+ let query = PlacesUtils.history.getNewQuery();
+ query.searchTerms = filterString;
+ query.setTransitions([Ci.nsINavHistoryService.TRANSITION_DOWNLOAD], 1);
+ let options = currentOptions.clone();
+ // Make sure we're getting uri results.
+ options.resultType = currentOptions.RESULT_TYPE_URI;
+ options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY;
+ options.includeHidden = true;
+ content.load([query], options);
break;
- default:
- throw "Invalid filterCollection on search";
+ }
+ default:
+ throw "Invalid filterCollection on search";
+ break;
}
// Update the details panel
@@ -1230,94 +1251,3 @@ let gPrivateBrowsingListener = {
}
};
#endif
-
-let ContentArea = {
- init: function CA_init() {
- this._deck = document.getElementById("placesViewsDeck");
- this._specialViews = new Map();
- ContentTree.init();
- },
-
- _shouldUseNewDownloadsView: function CA_shouldUseNewDownloadsView() {
- try {
- return Services.prefs.getBoolPref("browser.library.useNewDownloadsView");
- }
- catch(ex) { }
- return false;
- },
-
- getContentViewForQueryString:
- function CA_getContentViewForQueryString(aQueryString) {
- if (this._specialViews.has(aQueryString))
- return this._specialViews.get(aQueryString);
- if (aQueryString == DOWNLOADS_QUERY && this._shouldUseNewDownloadsView()) {
- let view = new DownloadsPlacesView(document.getElementById("downloadsRichListBox"), aQueryString);
- this.setContentViewForQueryString(aQueryString, view);
- return view;
- }
- return ContentTree.view;
- },
-
- setContentViewForQueryString:
- function CA_setContentViewForQueryString(aQueryString, aView) {
- this._specialViews.set(aQueryString, aView);
- },
-
- get currentView() PlacesUIUtils.getViewForNode(this._deck.selectedPanel),
- set currentView(aView) {
- if (this.currentView != aView)
- this._deck.selectedPanel = aView.associatedElement;
- return aView;
- },
-
- get currentPlace() this.currentView.place,
- set currentPlace(aQueryString) {
- this.currentView = this.getContentViewForQueryString(aQueryString);
- this.currentView.place = aQueryString;
- return aQueryString;
- },
-
- focus: function() {
- this._deck.selectedPanel.focus();
- }
-};
-
-let ContentTree = {
- init: function CT_init() {
- this._view = document.getElementById("placeContent");
- },
-
- get view() this._view,
-
- openSelectedNode: function CT_openSelectedNode(aEvent) {
- let view = this.view;
- PlacesUIUtils.openNodeWithEvent(view.selectedNode, aEvent, view);
- },
-
- onClick: function CT_onClick(aEvent) {
- // Only handle clicks on tree children.
- if (aEvent.target.localName != "treechildren")
- return;
-
- let node = this.view.selectedNode;
- if (node) {
- let doubleClick = aEvent.button == 0 && aEvent.detail == 2;
- let middleClick = aEvent.button == 1 && aEvent.detail == 1;
- if (PlacesUtils.nodeIsURI(node) && (doubleClick || middleClick)) {
- // Open associated uri in the browser.
- this.openSelectedNode(aEvent);
- }
- else if (middleClick && PlacesUtils.nodeIsContainer(node)) {
- // The command execution function will take care of seeing if the
- // selection is a folder or a different container type, and will
- // load its contents in tabs.
- PlacesUIUtils.openContainerNodeInTabs(node, aEvent, this.view);
- }
- }
- },
-
- onKeyPress: function CT_onKeyPress(aEvent) {
- if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN)
- this.openSelectedNode(aEvent);
- }
-};
diff --git a/browser/components/places/content/places.xul b/browser/components/places/content/places.xul
index e98088b74668..77e94c0a60a8 100644
--- a/browser/components/places/content/places.xul
+++ b/browser/components/places/content/places.xul
@@ -10,7 +10,6 @@
-
@@ -29,8 +28,6 @@
%editMenuOverlayDTD;
%browserDTD;
-
-%downloadsDTD;
]>
-
-
@@ -351,8 +344,8 @@
type="places"
hidecolumnpicker="true" context="placesContext"
onselect="PlacesOrganizer.onPlaceSelected(true);"
- onclick="PlacesOrganizer.onPlacesListClick(event);"
- onfocus="PlacesOrganizer.updateDetailsPane(event);"
+ onclick="PlacesOrganizer.onTreeClick(event);"
+ onfocus="PlacesOrganizer.onTreeFocus(event);"
seltype="single"
persist="width"
width="200"
@@ -365,58 +358,49 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -450,61 +434,4 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/browser/components/places/content/tree.xml b/browser/components/places/content/tree.xml
index 235f4bc68bf1..e69c8d15b080 100644
--- a/browser/components/places/content/tree.xml
+++ b/browser/components/places/content/tree.xml
@@ -56,10 +56,6 @@
]]>
-
-
diff --git a/browser/components/places/jar.mn b/browser/components/places/jar.mn
index fd18bea17b95..e8ab80c5246c 100644
--- a/browser/components/places/jar.mn
+++ b/browser/components/places/jar.mn
@@ -8,9 +8,6 @@ browser.jar:
content/browser/places/bookmarkProperties2.xul (content/bookmarkProperties.xul)
* content/browser/places/places.xul (content/places.xul)
* content/browser/places/places.js (content/places.js)
- content/browser/places/downloadsView.js (content/downloadsView.js)
- content/browser/places/download.xml (content/download.xml)
- content/browser/places/download.css (content/download.css)
content/browser/places/places.css (content/places.css)
content/browser/places/organizer.css (content/organizer.css)
content/browser/places/bookmarkProperties.xul (content/bookmarkProperties.xul)
diff --git a/browser/themes/pinstripe/places/places.css b/browser/themes/pinstripe/places/places.css
index 7c8170f2d552..6ad3a6166ce5 100644
--- a/browser/themes/pinstripe/places/places.css
+++ b/browser/themes/pinstripe/places/places.css
@@ -202,71 +202,3 @@ treechildren::-moz-tree-image(cutting) {
treechildren::-moz-tree-cell-text(cutting) {
opacity: 0.7;
}
-
-
-/** Downloads View **/
-
-richlistitem.download {
- height: 7em;
- margin: 0;
- padding: 8px;
- -moz-padding-end: 0;
-}
-
-richlistitem.download:first-child {
- border-top: 1px solid transparent;
-}
-
-richlistitem.download:last-child {
- border-bottom: 1px solid transparent;
-}
-
-.downloadTypeIcon {
- -moz-margin-end: 8px;
- /* Prevent flickering when changing states. */
- min-height: 32px;
- min-width: 32px;
-}
-
-.blockedIcon {
- list-style-image: url("chrome://global/skin/icons/Error.png");
-}
-
-.downloadTarget {
- margin-bottom: 6px;
- cursor: inherit;
-}
-
-.downloadDetails {
- opacity: 0.7;
- font-size: 95%;
- cursor: inherit;
-}
-
-.downloadButton {
- -moz-appearance: none;
- min-width: 0;
- min-height: 0;
- margin: 3px;
- border: none;
- padding: 5px;
- list-style-image: url("chrome://browser/skin/downloads/buttons.png");
-}
-
-.downloadButton > .button-box {
- padding: 0;
-}
-
-/*** Button icons ***/
-
-.downloadButton.downloadCancel {
- -moz-image-region: rect(0px, 16px, 16px, 0px);
-}
-
-.downloadButton.downloadShow {
- -moz-image-region: rect(16px, 16px, 32px, 0px);
-}
-
-.downloadButton.downloadRetry {
- -moz-image-region: rect(32px, 16px, 48px, 0px);
-}