2012-04-17 04:35:09 -07:00
|
|
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
|
|
/* vim: set ts=2 et sw=2 tw=80: */
|
|
|
|
/* 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/. */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles the Downloads panel user interface for each browser window.
|
|
|
|
*
|
|
|
|
* This file includes the following constructors and global objects:
|
|
|
|
*
|
|
|
|
* DownloadsPanel
|
|
|
|
* Main entry point for the downloads panel interface.
|
|
|
|
*
|
|
|
|
* DownloadsOverlayLoader
|
|
|
|
* Allows loading the downloads panel and the status indicator interfaces on
|
|
|
|
* demand, to improve startup performance.
|
|
|
|
*
|
|
|
|
* DownloadsView
|
|
|
|
* Builds and updates the downloads list widget, responding to changes in the
|
|
|
|
* download state and real-time data. In addition, handles part of the user
|
|
|
|
* interaction events raised by the downloads list widget.
|
|
|
|
*
|
|
|
|
* DownloadsViewItem
|
|
|
|
* Builds and updates a single item in the downloads list widget, responding to
|
|
|
|
* changes in the download state and real-time data.
|
|
|
|
*
|
|
|
|
* DownloadsViewController
|
|
|
|
* Handles part of the user interaction events raised by the downloads list
|
|
|
|
* widget, in particular the "commands" that apply to multiple items, and
|
|
|
|
* dispatches the commands that apply to individual items.
|
|
|
|
*
|
|
|
|
* DownloadsViewItemController
|
|
|
|
* Handles all the user interaction events, in particular the "commands",
|
|
|
|
* related to a single item in the downloads list widgets.
|
|
|
|
*/
|
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// Globals
|
|
|
|
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils",
|
|
|
|
"resource://gre/modules/DownloadUtils.jsm");
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
|
|
|
|
"resource:///modules/DownloadsCommon.jsm");
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// DownloadsPanel
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Main entry point for the downloads panel interface.
|
|
|
|
*/
|
|
|
|
const DownloadsPanel = {
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// Initialization and termination
|
|
|
|
|
|
|
|
/**
|
2012-04-27 06:05:04 -07:00
|
|
|
* Internal state of the downloads panel, based on one of the kState
|
|
|
|
* constants. This is not the same state as the XUL panel element.
|
2012-04-17 04:35:09 -07:00
|
|
|
*/
|
2012-04-27 06:05:04 -07:00
|
|
|
_state: 0,
|
2012-04-17 04:35:09 -07:00
|
|
|
|
2012-04-27 06:05:04 -07:00
|
|
|
/** The panel is not linked to downloads data yet. */
|
|
|
|
get kStateUninitialized() 0,
|
|
|
|
/** This object is linked to data, but the panel is invisible. */
|
|
|
|
get kStateHidden() 1,
|
2012-04-17 04:35:09 -07:00
|
|
|
/** The panel will be shown as soon as possible. */
|
2012-04-27 06:05:04 -07:00
|
|
|
get kStateShowing() 2,
|
|
|
|
/** The panel is open. */
|
|
|
|
get kStateShown() 3,
|
2012-04-17 04:35:09 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Location of the panel overlay.
|
|
|
|
*/
|
|
|
|
get kDownloadsOverlay()
|
|
|
|
"chrome://browser/content/downloads/downloadsOverlay.xul",
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Starts loading the download data in background, without opening the panel.
|
|
|
|
* Use showPanel instead to load the data and open the panel at the same time.
|
|
|
|
*
|
|
|
|
* @param aCallback
|
|
|
|
* Called when initialization is complete.
|
|
|
|
*/
|
|
|
|
initialize: function DP_initialize(aCallback)
|
|
|
|
{
|
2012-04-27 06:05:04 -07:00
|
|
|
if (this._state != this.kStateUninitialized) {
|
2012-04-17 04:35:09 -07:00
|
|
|
DownloadsOverlayLoader.ensureOverlayLoaded(this.kDownloadsOverlay,
|
|
|
|
aCallback);
|
|
|
|
return;
|
|
|
|
}
|
2012-04-27 06:05:04 -07:00
|
|
|
this._state = this.kStateHidden;
|
2012-04-17 04:35:09 -07:00
|
|
|
|
|
|
|
window.addEventListener("unload", this.onWindowUnload, false);
|
|
|
|
|
|
|
|
// Ensure that the Download Manager service is running. This resumes
|
|
|
|
// active downloads if required. If there are downloads to be shown in the
|
|
|
|
// panel, starting the service will make us load their data asynchronously.
|
|
|
|
Services.downloads;
|
|
|
|
|
|
|
|
// Now that data loading has eventually started, load the required XUL
|
|
|
|
// elements and initialize our views.
|
|
|
|
DownloadsOverlayLoader.ensureOverlayLoaded(this.kDownloadsOverlay,
|
|
|
|
function DP_I_callback() {
|
|
|
|
DownloadsViewController.initialize();
|
|
|
|
DownloadsCommon.data.addView(DownloadsView);
|
|
|
|
aCallback();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Closes the downloads panel and frees the internal resources related to the
|
|
|
|
* downloads. The downloads panel can be reopened later, even after this
|
|
|
|
* function has been called.
|
|
|
|
*/
|
|
|
|
terminate: function DP_terminate()
|
|
|
|
{
|
2012-04-27 06:05:04 -07:00
|
|
|
if (this._state == this.kStateUninitialized) {
|
2012-04-17 04:35:09 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
window.removeEventListener("unload", this.onWindowUnload, false);
|
|
|
|
|
|
|
|
// Ensure that the panel is closed before shutting down.
|
|
|
|
this.hidePanel();
|
|
|
|
|
|
|
|
DownloadsViewController.terminate();
|
|
|
|
DownloadsCommon.data.removeView(DownloadsView);
|
|
|
|
|
2012-04-27 06:05:04 -07:00
|
|
|
this._state = this.kStateUninitialized;
|
2012-04-17 04:35:09 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// Panel interface
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Main panel element in the browser window.
|
|
|
|
*/
|
|
|
|
get panel()
|
|
|
|
{
|
|
|
|
delete this.panel;
|
|
|
|
return this.panel = document.getElementById("downloadsPanel");
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Starts opening the downloads panel interface, anchored to the downloads
|
|
|
|
* button of the browser window. The list of downloads to display is
|
|
|
|
* initialized the first time this method is called, and the panel is shown
|
|
|
|
* only when data is ready.
|
|
|
|
*/
|
|
|
|
showPanel: function DP_showPanel()
|
|
|
|
{
|
|
|
|
if (this.isPanelShowing) {
|
|
|
|
this._focusPanel();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.initialize(function DP_SP_callback() {
|
|
|
|
// Delay displaying the panel because this function will sometimes be
|
|
|
|
// called while another window is closing (like the window for selecting
|
|
|
|
// whether to save or open the file), and that would cause the panel to
|
|
|
|
// close immediately.
|
|
|
|
setTimeout(function () DownloadsPanel._openPopupIfDataReady(), 0);
|
|
|
|
}.bind(this));
|
|
|
|
|
2012-04-27 06:05:04 -07:00
|
|
|
this._state = this.kStateShowing;
|
2012-04-17 04:35:09 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hides the downloads panel, if visible, but keeps the internal state so that
|
|
|
|
* the panel can be reopened quickly if required.
|
|
|
|
*/
|
|
|
|
hidePanel: function DP_hidePanel()
|
|
|
|
{
|
|
|
|
if (!this.isPanelShowing) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.panel.hidePopup();
|
|
|
|
|
|
|
|
// Ensure that we allow the panel to be reopened. Note that, if the popup
|
|
|
|
// was open, then the onPopupHidden event handler has already updated the
|
|
|
|
// current state, otherwise we must update the state ourselves.
|
2012-04-27 06:05:04 -07:00
|
|
|
this._state = this.kStateHidden;
|
2012-04-17 04:35:09 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Indicates whether the panel is shown or will be shown.
|
|
|
|
*/
|
|
|
|
get isPanelShowing()
|
|
|
|
{
|
2012-04-27 06:05:04 -07:00
|
|
|
return this._state == this.kStateShowing ||
|
|
|
|
this._state == this.kStateShown;
|
2012-04-17 04:35:09 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// Callback functions from DownloadsView
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called after data loading finished.
|
|
|
|
*/
|
|
|
|
onViewLoadCompleted: function DP_onViewLoadCompleted()
|
|
|
|
{
|
|
|
|
this._openPopupIfDataReady();
|
|
|
|
},
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// User interface event functions
|
|
|
|
|
|
|
|
onWindowUnload: function DP_onWindowUnload()
|
|
|
|
{
|
|
|
|
// This function is registered as an event listener, we can't use "this".
|
|
|
|
DownloadsPanel.terminate();
|
|
|
|
},
|
|
|
|
|
|
|
|
onPopupShown: function DP_onPopupShown(aEvent)
|
|
|
|
{
|
|
|
|
// Ignore events raised by nested popups.
|
|
|
|
if (aEvent.target != aEvent.currentTarget) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-04-27 06:05:04 -07:00
|
|
|
this._state = this.kStateShown;
|
|
|
|
|
2012-04-17 04:35:09 -07:00
|
|
|
// Since at most one popup is open at any given time, we can set globally.
|
|
|
|
DownloadsCommon.indicatorData.attentionSuppressed = true;
|
|
|
|
|
|
|
|
// Ensure that an item is selected when the panel is focused.
|
|
|
|
if (DownloadsView.richListBox.itemCount > 0 &&
|
|
|
|
!DownloadsView.richListBox.selectedItem) {
|
|
|
|
DownloadsView.richListBox.selectedIndex = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._focusPanel();
|
|
|
|
},
|
|
|
|
|
|
|
|
onPopupHidden: function DP_onPopupHidden(aEvent)
|
|
|
|
{
|
|
|
|
// Ignore events raised by nested popups.
|
|
|
|
if (aEvent.target != aEvent.currentTarget) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Since at most one popup is open at any given time, we can set globally.
|
|
|
|
DownloadsCommon.indicatorData.attentionSuppressed = false;
|
|
|
|
|
|
|
|
// Allow the anchor to be hidden.
|
|
|
|
DownloadsButton.releaseAnchor();
|
|
|
|
|
|
|
|
// Allow the panel to be reopened.
|
2012-04-27 06:05:04 -07:00
|
|
|
this._state = this.kStateHidden;
|
2012-04-17 04:35:09 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// Related operations
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Shows or focuses the user interface dedicated to downloads history.
|
|
|
|
*/
|
|
|
|
showDownloadsHistory: function DP_showDownloadsHistory()
|
|
|
|
{
|
|
|
|
// Hide the panel before invoking the Library window, otherwise focus will
|
|
|
|
// return to the browser window when the panel closes automatically.
|
|
|
|
this.hidePanel();
|
|
|
|
|
|
|
|
// Open the Library window and select the Downloads query.
|
|
|
|
PlacesCommandHook.showPlacesOrganizer("Downloads");
|
|
|
|
},
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// Internal functions
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Move focus to the main element in the downloads panel, unless another
|
|
|
|
* element in the panel is already focused.
|
|
|
|
*/
|
|
|
|
_focusPanel: function DP_focusPanel()
|
|
|
|
{
|
|
|
|
// We may be invoked while the panel is still waiting to be shown.
|
2012-04-27 06:05:04 -07:00
|
|
|
if (this._state != this.kStateShown) {
|
2012-04-17 04:35:09 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let element = document.commandDispatcher.focusedElement;
|
|
|
|
while (element && element != this.panel) {
|
|
|
|
element = element.parentNode;
|
|
|
|
}
|
|
|
|
if (!element) {
|
|
|
|
DownloadsView.richListBox.focus();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Opens the downloads panel when data is ready to be displayed.
|
|
|
|
*/
|
|
|
|
_openPopupIfDataReady: function DP_openPopupIfDataReady()
|
|
|
|
{
|
|
|
|
// We don't want to open the popup if we already displayed it, or if we are
|
|
|
|
// still loading data.
|
2012-04-27 06:05:04 -07:00
|
|
|
if (this._state != this.kStateShowing || DownloadsView.loading) {
|
2012-04-17 04:35:09 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure that clicking outside the popup cannot reopen it accidentally.
|
|
|
|
this.panel.popupBoxObject.setConsumeRollupEvent(Ci.nsIPopupBoxObject
|
|
|
|
.ROLLUP_CONSUME);
|
|
|
|
|
|
|
|
// Ensure the anchor is visible. If that is not possible, show the panel
|
|
|
|
// anchored to the top area of the window, near the default anchor position.
|
|
|
|
DownloadsButton.getAnchor(function DP_OPIDR_callback(aAnchor) {
|
2012-04-27 06:05:04 -07:00
|
|
|
// At this point, if the window is minimized, opening the panel could fail
|
|
|
|
// without any notification, and there would be no way to either open or
|
|
|
|
// close the panel anymore. To prevent this, check if the window is
|
|
|
|
// minimized and in that case force the panel to the closed state.
|
|
|
|
if (window.windowState == Ci.nsIDOMChromeWindow.STATE_MINIMIZED) {
|
|
|
|
DownloadsButton.releaseAnchor();
|
|
|
|
this._state = this.kStateHidden;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-04-17 04:35:09 -07:00
|
|
|
if (aAnchor) {
|
|
|
|
this.panel.openPopup(aAnchor, "bottomcenter topright", 0, 0, false,
|
|
|
|
null);
|
|
|
|
} else {
|
|
|
|
this.panel.openPopup(document.getElementById("TabsToolbar"),
|
|
|
|
"after_end", 0, 0, false, null);
|
|
|
|
}
|
|
|
|
}.bind(this));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// DownloadsOverlayLoader
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Allows loading the downloads panel and the status indicator interfaces on
|
|
|
|
* demand, to improve startup performance.
|
|
|
|
*/
|
|
|
|
const DownloadsOverlayLoader = {
|
|
|
|
/**
|
|
|
|
* We cannot load two overlays at the same time, thus we use a queue of
|
|
|
|
* pending load requests.
|
|
|
|
*/
|
|
|
|
_loadRequests: [],
|
|
|
|
|
|
|
|
/**
|
|
|
|
* True while we are waiting for an overlay to be loaded.
|
|
|
|
*/
|
|
|
|
_overlayLoading: false,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This object has a key for each overlay URI that is already loaded.
|
|
|
|
*/
|
|
|
|
_loadedOverlays: {},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Loads the specified overlay and invokes the given callback when finished.
|
|
|
|
*
|
|
|
|
* @param aOverlay
|
|
|
|
* String containing the URI of the overlay to load in the current
|
|
|
|
* window. If this overlay has already been loaded using this
|
|
|
|
* function, then the overlay is not loaded again.
|
|
|
|
* @param aCallback
|
|
|
|
* Invoked when loading is completed. If the overlay is already
|
|
|
|
* loaded, the function is called immediately.
|
|
|
|
*/
|
|
|
|
ensureOverlayLoaded: function DOL_ensureOverlayLoaded(aOverlay, aCallback)
|
|
|
|
{
|
|
|
|
// The overlay is already loaded, invoke the callback immediately.
|
|
|
|
if (aOverlay in this._loadedOverlays) {
|
|
|
|
aCallback();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The callback will be invoked when loading is finished.
|
|
|
|
this._loadRequests.push({ overlay: aOverlay, callback: aCallback });
|
|
|
|
if (this._overlayLoading) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
function DOL_EOL_loadCallback() {
|
|
|
|
this._overlayLoading = false;
|
|
|
|
this._loadedOverlays[aOverlay] = true;
|
|
|
|
|
|
|
|
// Loading the overlay causes all the persisted XUL attributes to be
|
|
|
|
// reapplied, including "iconsize" on the toolbars. Until bug 640158 is
|
|
|
|
// fixed, we must recalculate the correct "iconsize" attributes manually.
|
|
|
|
retrieveToolbarIconsizesFromTheme();
|
|
|
|
|
|
|
|
this.processPendingRequests();
|
|
|
|
}
|
|
|
|
|
|
|
|
this._overlayLoading = true;
|
|
|
|
document.loadOverlay(aOverlay, DOL_EOL_loadCallback.bind(this));
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Re-processes all the currently pending requests, invoking the callbacks
|
|
|
|
* and/or loading more overlays as needed. In most cases, there will be a
|
|
|
|
* single request for one overlay, that will be processed immediately.
|
|
|
|
*/
|
|
|
|
processPendingRequests: function DOL_processPendingRequests()
|
|
|
|
{
|
|
|
|
// Re-process all the currently pending requests, yet allow more requests
|
|
|
|
// to be appended at the end of the array if we're not ready for them.
|
|
|
|
let currentLength = this._loadRequests.length;
|
|
|
|
for (let i = 0; i < currentLength; i++) {
|
|
|
|
let request = this._loadRequests.shift();
|
|
|
|
|
|
|
|
// We must call ensureOverlayLoaded again for each request, to check if
|
|
|
|
// the associated callback can be invoked now, or if we must still wait
|
|
|
|
// for the associated overlay to load.
|
|
|
|
this.ensureOverlayLoaded(request.overlay, request.callback);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// DownloadsView
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Builds and updates the downloads list widget, responding to changes in the
|
|
|
|
* download state and real-time data. In addition, handles part of the user
|
|
|
|
* interaction events raised by the downloads list widget.
|
|
|
|
*/
|
|
|
|
const DownloadsView = {
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// Functions handling download items in the list
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Indicates whether we are still loading downloads data asynchronously.
|
|
|
|
*/
|
|
|
|
loading: false,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Object containing all the available DownloadsViewItem objects, indexed by
|
|
|
|
* their numeric download identifier.
|
|
|
|
*/
|
|
|
|
_viewItems: {},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when the number of items in the list changes.
|
|
|
|
*/
|
|
|
|
_itemCountChanged: function DV_itemCountChanged()
|
|
|
|
{
|
|
|
|
if (Object.keys(this._viewItems).length > 0) {
|
|
|
|
DownloadsPanel.panel.setAttribute("hasdownloads", "true");
|
|
|
|
} else {
|
|
|
|
DownloadsPanel.panel.removeAttribute("hasdownloads");
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Element corresponding to the list of downloads.
|
|
|
|
*/
|
|
|
|
get richListBox()
|
|
|
|
{
|
|
|
|
delete this.richListBox;
|
|
|
|
return this.richListBox = document.getElementById("downloadsListBox");
|
|
|
|
},
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// Callback functions from DownloadsData
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called before multiple downloads are about to be loaded.
|
|
|
|
*/
|
|
|
|
onDataLoadStarting: function DV_onDataLoadStarting()
|
|
|
|
{
|
|
|
|
this.loading = true;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called after data loading finished.
|
|
|
|
*/
|
|
|
|
onDataLoadCompleted: function DV_onDataLoadCompleted()
|
|
|
|
{
|
|
|
|
this.loading = false;
|
|
|
|
|
|
|
|
// Notify the panel that all the initially available downloads have been
|
|
|
|
// loaded. This ensures that the interface is visible, if still required.
|
|
|
|
DownloadsPanel.onViewLoadCompleted();
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when the downloads database becomes unavailable (for example,
|
|
|
|
* entering Private Browsing Mode). References to existing data should be
|
|
|
|
* discarded.
|
|
|
|
*/
|
|
|
|
onDataInvalidated: function DV_onDataInvalidated()
|
|
|
|
{
|
|
|
|
DownloadsPanel.terminate();
|
|
|
|
|
|
|
|
// Clear the list by replacing with a shallow copy.
|
|
|
|
let emptyView = this.richListBox.cloneNode(false);
|
|
|
|
this.richListBox.parentNode.replaceChild(emptyView, this.richListBox);
|
|
|
|
this.richListBox = emptyView;
|
|
|
|
this._viewItems = {};
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when a new download data item is available, either during the
|
|
|
|
* asynchronous data load or when a new download is started.
|
|
|
|
*
|
|
|
|
* @param aDataItem
|
|
|
|
* DownloadsDataItem object that was just added.
|
|
|
|
* @param aNewest
|
|
|
|
* When true, indicates that this item is the most recent and should be
|
|
|
|
* added in the topmost position. This happens when a new download is
|
|
|
|
* started. When false, indicates that the item is the least recent
|
|
|
|
* and should be appended. The latter generally happens during the
|
|
|
|
* asynchronous data load.
|
|
|
|
*/
|
|
|
|
onDataItemAdded: function DV_onDataItemAdded(aDataItem, aNewest)
|
|
|
|
{
|
|
|
|
// Make the item and add it in the appropriate place in the list.
|
|
|
|
let element = document.createElement("richlistitem");
|
|
|
|
let viewItem = new DownloadsViewItem(aDataItem, element);
|
|
|
|
this._viewItems[aDataItem.downloadId] = viewItem;
|
|
|
|
if (aNewest) {
|
|
|
|
this.richListBox.insertBefore(element, this.richListBox.firstChild);
|
|
|
|
} else {
|
|
|
|
this.richListBox.appendChild(element);
|
|
|
|
}
|
|
|
|
|
|
|
|
this._itemCountChanged();
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when a data item is removed. Ensures that the widget associated
|
|
|
|
* with the view item is removed from the user interface.
|
|
|
|
*
|
|
|
|
* @param aDataItem
|
|
|
|
* DownloadsDataItem object that is being removed.
|
|
|
|
*/
|
|
|
|
onDataItemRemoved: function DV_onDataItemRemoved(aDataItem)
|
|
|
|
{
|
|
|
|
let element = this.getViewItem(aDataItem)._element;
|
|
|
|
let previousSelectedIndex = this.richListBox.selectedIndex;
|
|
|
|
this.richListBox.removeChild(element);
|
|
|
|
this.richListBox.selectedIndex = Math.min(previousSelectedIndex,
|
|
|
|
this.richListBox.itemCount - 1);
|
|
|
|
delete this._viewItems[aDataItem.downloadId];
|
|
|
|
|
|
|
|
this._itemCountChanged();
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the view item associated with the provided data item for this view.
|
|
|
|
*
|
|
|
|
* @param aDataItem
|
|
|
|
* DownloadsDataItem object for which the view item is requested.
|
|
|
|
*
|
|
|
|
* @return Object that can be used to notify item status events.
|
|
|
|
*/
|
|
|
|
getViewItem: function DV_getViewItem(aDataItem)
|
|
|
|
{
|
|
|
|
return this._viewItems[aDataItem.downloadId];
|
|
|
|
},
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// User interface event functions
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper function to do commands on a specific download item.
|
|
|
|
*
|
|
|
|
* @param aEvent
|
|
|
|
* Event object for the event being handled. If the event target is
|
|
|
|
* not a richlistitem that represents a download, this function will
|
|
|
|
* walk up the parent nodes until it finds a DOM node that is.
|
|
|
|
* @param aCommand
|
|
|
|
* The command to be performed.
|
|
|
|
*/
|
|
|
|
onDownloadCommand: function DV_onDownloadCommand(aEvent, aCommand)
|
|
|
|
{
|
|
|
|
let target = aEvent.target;
|
|
|
|
while (target.nodeName != "richlistitem") {
|
|
|
|
target = target.parentNode;
|
|
|
|
}
|
|
|
|
new DownloadsViewItemController(target).doCommand(aCommand);
|
|
|
|
},
|
|
|
|
|
|
|
|
onDownloadClick: function DV_onDownloadClick(aEvent)
|
|
|
|
{
|
|
|
|
// Handle primary clicks only.
|
|
|
|
if (aEvent.button == 0) {
|
|
|
|
goDoCommand("downloadsCmd_open");
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
onDownloadKeyPress: function DV_onDownloadKeyPress(aEvent)
|
|
|
|
{
|
|
|
|
// Handle unmodified keys only.
|
|
|
|
if (aEvent.altKey || aEvent.ctrlKey || aEvent.shiftKey || aEvent.metaKey) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pressing the key on buttons should not invoke the action because the
|
|
|
|
// event has already been handled by the button itself.
|
|
|
|
if (aEvent.originalTarget.hasAttribute("command") ||
|
|
|
|
aEvent.originalTarget.hasAttribute("oncommand")) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (aEvent.charCode == " ".charCodeAt(0)) {
|
|
|
|
goDoCommand("downloadsCmd_pauseResume");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (aEvent.keyCode) {
|
|
|
|
case KeyEvent.DOM_VK_ENTER:
|
|
|
|
case KeyEvent.DOM_VK_RETURN:
|
|
|
|
goDoCommand("downloadsCmd_doDefault");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
onDownloadContextMenu: function DV_onDownloadContextMenu(aEvent)
|
|
|
|
{
|
|
|
|
let element = this.richListBox.selectedItem;
|
|
|
|
if (!element) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
DownloadsViewController.updateCommands();
|
|
|
|
|
|
|
|
// Set the state attribute so that only the appropriate items are displayed.
|
|
|
|
let contextMenu = document.getElementById("downloadsContextMenu");
|
|
|
|
contextMenu.setAttribute("state", element.getAttribute("state"));
|
|
|
|
},
|
|
|
|
|
|
|
|
onDownloadDragStart: function DV_onDownloadDragStart(aEvent)
|
|
|
|
{
|
|
|
|
let element = this.richListBox.selectedItem;
|
|
|
|
if (!element) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let controller = new DownloadsViewItemController(element);
|
|
|
|
let localFile = controller.dataItem.localFile;
|
|
|
|
if (!localFile.exists()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let dataTransfer = aEvent.dataTransfer;
|
|
|
|
dataTransfer.mozSetDataAt("application/x-moz-file", localFile, 0);
|
|
|
|
dataTransfer.effectAllowed = "copyMove";
|
|
|
|
dataTransfer.addElement(element);
|
|
|
|
|
|
|
|
aEvent.stopPropagation();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// DownloadsViewItem
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Builds and updates a single item in the downloads list widget, responding to
|
|
|
|
* changes in the download state and real-time data.
|
|
|
|
*
|
|
|
|
* @param aDataItem
|
|
|
|
* DownloadsDataItem to be associated with the view item.
|
|
|
|
* @param aElement
|
|
|
|
* XUL element corresponding to the single download item in the view.
|
|
|
|
*/
|
|
|
|
function DownloadsViewItem(aDataItem, aElement)
|
|
|
|
{
|
|
|
|
this._element = aElement;
|
|
|
|
this.dataItem = aDataItem;
|
|
|
|
|
|
|
|
this.wasDone = this.dataItem.done;
|
|
|
|
this.wasInProgress = this.dataItem.inProgress;
|
|
|
|
this.lastEstimatedSecondsLeft = Infinity;
|
|
|
|
|
|
|
|
// Set the URI that represents the correct icon for the target file. As soon
|
|
|
|
// as bug 239948 comment 12 is handled, the "file" property will be always a
|
|
|
|
// file URL rather than a file name. At that point we should remove the "//"
|
|
|
|
// (double slash) from the icon URI specification (see test_moz_icon_uri.js).
|
|
|
|
this.image = "moz-icon://" + this.dataItem.file + "?size=32";
|
|
|
|
|
|
|
|
let attributes = {
|
|
|
|
"type": "download",
|
|
|
|
"class": "download-state",
|
|
|
|
"id": "downloadsItem_" + this.dataItem.downloadId,
|
|
|
|
"downloadId": this.dataItem.downloadId,
|
|
|
|
"state": this.dataItem.state,
|
|
|
|
"progress": this.dataItem.inProgress ? this.dataItem.percentComplete : 100,
|
|
|
|
"target": this.dataItem.target,
|
|
|
|
"image": this.image
|
|
|
|
};
|
|
|
|
|
|
|
|
for (let attributeName in attributes) {
|
|
|
|
this._element.setAttribute(attributeName, attributes[attributeName]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize more complex attributes.
|
|
|
|
this._updateProgress();
|
|
|
|
this._updateStatusLine();
|
|
|
|
}
|
|
|
|
|
|
|
|
DownloadsViewItem.prototype = {
|
|
|
|
/**
|
|
|
|
* The DownloadDataItem associated with this view item.
|
|
|
|
*/
|
|
|
|
dataItem: null,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The XUL element corresponding to the associated richlistbox item.
|
|
|
|
*/
|
|
|
|
_element: null,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The inner XUL element for the progress bar, or null if not available.
|
|
|
|
*/
|
|
|
|
_progressElement: null,
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// Callback functions from DownloadsData
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when the download state might have changed. Sometimes the state of
|
|
|
|
* the download might be the same as before, if the data layer received
|
|
|
|
* multiple events for the same download.
|
|
|
|
*/
|
|
|
|
onStateChange: function DVI_onStateChange()
|
|
|
|
{
|
|
|
|
// If a download just finished successfully, it means that the target file
|
|
|
|
// now exists and we can extract its specific icon. To ensure that the icon
|
|
|
|
// is reloaded, we must change the URI used by the XUL image element, for
|
|
|
|
// example by adding a query parameter. Since this URI has a "moz-icon"
|
|
|
|
// scheme, this only works if we add one of the parameters explicitly
|
|
|
|
// supported by the nsIMozIconURI interface.
|
|
|
|
if (!this.wasDone && this.dataItem.openable) {
|
|
|
|
this._element.setAttribute("image", this.image + "&state=normal");
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
// Update the user interface after switching states.
|
|
|
|
this._element.setAttribute("state", this.dataItem.state);
|
|
|
|
this._updateProgress();
|
|
|
|
this._updateStatusLine();
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when the download progress has changed.
|
|
|
|
*/
|
|
|
|
onProgressChange: function DVI_onProgressChange() {
|
|
|
|
this._updateProgress();
|
|
|
|
this._updateStatusLine();
|
|
|
|
},
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// Functions for updating the user interface
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates the progress bar.
|
|
|
|
*/
|
|
|
|
_updateProgress: function DVI_updateProgress() {
|
|
|
|
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 == Ci.nsIDownloadManager.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);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find the progress element as soon as the download binding is accessible.
|
|
|
|
if (!this._progressElement) {
|
|
|
|
this._progressElement =
|
|
|
|
document.getAnonymousElementByAttribute(this._element, "anonid",
|
|
|
|
"progressmeter");
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates the main status line, including bytes transferred, bytes total,
|
|
|
|
* download rate, and time remaining.
|
|
|
|
*/
|
|
|
|
_updateStatusLine: function DVI_updateStatusLine() {
|
|
|
|
const nsIDM = Ci.nsIDownloadManager;
|
|
|
|
|
|
|
|
let status = "";
|
|
|
|
let statusTip = "";
|
|
|
|
|
|
|
|
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".
|
|
|
|
status = DownloadsCommon.strings.statusSeparatorBeforeNumber(
|
|
|
|
DownloadsCommon.strings.statePaused,
|
|
|
|
transfer);
|
|
|
|
} else if (this.dataItem.state == nsIDM.DOWNLOAD_DOWNLOADING) {
|
|
|
|
let newEstimatedSecondsLeft;
|
|
|
|
[status, newEstimatedSecondsLeft] =
|
|
|
|
DownloadUtils.getDownloadStatus(this.dataItem.currBytes,
|
|
|
|
this.dataItem.maxBytes,
|
|
|
|
this.dataItem.speed,
|
|
|
|
this.lastEstimatedSecondsLeft);
|
|
|
|
this.lastEstimatedSecondsLeft = newEstimatedSecondsLeft;
|
|
|
|
} else if (this.dataItem.starting) {
|
|
|
|
status = DownloadsCommon.strings.stateStarting;
|
|
|
|
} else if (this.dataItem.state == nsIDM.DOWNLOAD_SCANNING) {
|
|
|
|
status = DownloadsCommon.strings.stateScanning;
|
|
|
|
} else if (!this.dataItem.inProgress) {
|
|
|
|
let stateLabel = function () {
|
|
|
|
let s = DownloadsCommon.strings;
|
|
|
|
switch (this.dataItem.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: return this._fileSizeText;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}.apply(this);
|
|
|
|
|
|
|
|
let [displayHost, fullHost] =
|
|
|
|
DownloadUtils.getURIHost(this.dataItem.referrer || this.dataItem.uri);
|
|
|
|
|
|
|
|
let end = new Date(this.dataItem.endTime);
|
|
|
|
let [displayDate, fullDate] = DownloadUtils.getReadableDates(end);
|
|
|
|
|
|
|
|
// We use the same XUL label to display the state, the host name, and the
|
|
|
|
// end time, for example "Canceled - 222.net - 11:15" or "1.1 MB -
|
|
|
|
// website2.com - Yesterday". We show the full host and the complete date
|
|
|
|
// in the tooltip.
|
|
|
|
let firstPart = DownloadsCommon.strings.statusSeparator(stateLabel,
|
|
|
|
displayHost);
|
|
|
|
status = DownloadsCommon.strings.statusSeparator(firstPart, displayDate);
|
|
|
|
statusTip = DownloadsCommon.strings.statusSeparator(fullHost, fullDate);
|
|
|
|
}
|
|
|
|
|
|
|
|
this._element.setAttribute("status", status);
|
|
|
|
this._element.setAttribute("statusTip", statusTip || status);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Localized string representing the total size of completed downloads, for
|
|
|
|
* example "1.5 MB" or "Unknown size".
|
|
|
|
*/
|
|
|
|
get _fileSizeText()
|
|
|
|
{
|
|
|
|
// Display the file size, but show "Unknown" for negative sizes.
|
|
|
|
let fileSize = this.dataItem.maxBytes;
|
|
|
|
if (fileSize < 0) {
|
|
|
|
return DownloadsCommon.strings.sizeUnknown;
|
|
|
|
}
|
|
|
|
let [size, unit] = DownloadUtils.convertByteUnits(fileSize);
|
|
|
|
return DownloadsCommon.strings.sizeWithUnits(size, unit);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// DownloadsViewController
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles part of the user interaction events raised by the downloads list
|
|
|
|
* widget, in particular the "commands" that apply to multiple items, and
|
|
|
|
* dispatches the commands that apply to individual items.
|
|
|
|
*/
|
|
|
|
const DownloadsViewController = {
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// Initialization and termination
|
|
|
|
|
|
|
|
initialize: function DVC_initialize()
|
|
|
|
{
|
|
|
|
window.controllers.insertControllerAt(0, this);
|
|
|
|
},
|
|
|
|
|
|
|
|
terminate: function DVC_terminate()
|
|
|
|
{
|
|
|
|
window.controllers.removeController(this);
|
|
|
|
},
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// nsIController
|
|
|
|
|
|
|
|
supportsCommand: function DVC_supportsCommand(aCommand)
|
|
|
|
{
|
|
|
|
// Firstly, determine if this is a command that we can handle.
|
|
|
|
if (!(aCommand in this.commands) &&
|
|
|
|
!(aCommand in DownloadsViewItemController.prototype.commands)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Secondly, determine if focus is on a control in the downloads list.
|
|
|
|
let element = document.commandDispatcher.focusedElement;
|
|
|
|
while (element && element != DownloadsView.richListBox) {
|
|
|
|
element = element.parentNode;
|
|
|
|
}
|
|
|
|
// We should handle the command only if the downloads list is among the
|
|
|
|
// ancestors of the focused element.
|
|
|
|
return !!element;
|
|
|
|
},
|
|
|
|
|
|
|
|
isCommandEnabled: function DVC_isCommandEnabled(aCommand)
|
|
|
|
{
|
|
|
|
// Handle commands that are not selection-specific.
|
|
|
|
if (aCommand == "downloadsCmd_clearList") {
|
|
|
|
return Services.downloads.canCleanUp;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Other commands are selection-specific.
|
|
|
|
let element = DownloadsView.richListBox.selectedItem;
|
|
|
|
return element &&
|
|
|
|
new DownloadsViewItemController(element).isCommandEnabled(aCommand);
|
|
|
|
},
|
|
|
|
|
|
|
|
doCommand: function DVC_doCommand(aCommand)
|
|
|
|
{
|
|
|
|
// If this command is not selection-specific, execute it.
|
|
|
|
if (aCommand in this.commands) {
|
|
|
|
this.commands[aCommand].apply(this);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Other commands are selection-specific.
|
|
|
|
let element = DownloadsView.richListBox.selectedItem;
|
|
|
|
if (element) {
|
|
|
|
// The doCommand function also checks if the command is enabled.
|
|
|
|
new DownloadsViewItemController(element).doCommand(aCommand);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
onEvent: function () { },
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// Other functions
|
|
|
|
|
|
|
|
updateCommands: function DVC_updateCommands()
|
|
|
|
{
|
|
|
|
Object.keys(this.commands).forEach(goUpdateCommand);
|
|
|
|
Object.keys(DownloadsViewItemController.prototype.commands)
|
|
|
|
.forEach(goUpdateCommand);
|
|
|
|
},
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// Selection-independent commands
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This object contains one key for each command that operates regardless of
|
|
|
|
* the currently selected item in the list.
|
|
|
|
*/
|
|
|
|
commands: {
|
|
|
|
downloadsCmd_clearList: function DVC_downloadsCmd_clearList()
|
|
|
|
{
|
|
|
|
Services.downloads.cleanUp();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// DownloadsViewItemController
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles all the user interaction events, in particular the "commands",
|
|
|
|
* related to a single item in the downloads list widgets.
|
|
|
|
*/
|
|
|
|
function DownloadsViewItemController(aElement) {
|
|
|
|
let downloadId = aElement.getAttribute("downloadId");
|
|
|
|
this.dataItem = DownloadsCommon.data.dataItems[downloadId];
|
|
|
|
}
|
|
|
|
|
|
|
|
DownloadsViewItemController.prototype = {
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// Constants
|
|
|
|
|
|
|
|
get kPrefBdmAlertOnExeOpen() "browser.download.manager.alertOnEXEOpen",
|
|
|
|
get kPrefBdmScanWhenDone() "browser.download.manager.scanWhenDone",
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// Command dispatching
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The DownloadDataItem controlled by this object.
|
|
|
|
*/
|
|
|
|
dataItem: null,
|
|
|
|
|
|
|
|
isCommandEnabled: function DVIC_isCommandEnabled(aCommand)
|
|
|
|
{
|
|
|
|
switch (aCommand) {
|
|
|
|
case "downloadsCmd_open": {
|
|
|
|
return this.dataItem.openable && this.dataItem.localFile.exists();
|
|
|
|
}
|
|
|
|
case "downloadsCmd_show": {
|
|
|
|
return this.dataItem.localFile.exists();
|
|
|
|
}
|
|
|
|
case "downloadsCmd_pauseResume":
|
|
|
|
return this.dataItem.inProgress && this.dataItem.resumable;
|
|
|
|
case "downloadsCmd_retry":
|
|
|
|
return this.dataItem.canRetry;
|
|
|
|
case "downloadsCmd_openReferrer":
|
|
|
|
return !!this.dataItem.referrer;
|
|
|
|
case "cmd_delete":
|
|
|
|
case "downloadsCmd_cancel":
|
|
|
|
case "downloadsCmd_copyLocation":
|
|
|
|
case "downloadsCmd_doDefault":
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
doCommand: function DVIC_doCommand(aCommand)
|
|
|
|
{
|
|
|
|
if (this.isCommandEnabled(aCommand)) {
|
|
|
|
this.commands[aCommand].apply(this);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// Item commands
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This object contains one key for each command that operates on this item.
|
|
|
|
*
|
|
|
|
* In commands, the "this" identifier points to the controller item.
|
|
|
|
*/
|
|
|
|
commands: {
|
|
|
|
cmd_delete: function DVIC_cmd_delete()
|
|
|
|
{
|
|
|
|
this.commands.downloadsCmd_cancel.apply(this);
|
|
|
|
|
|
|
|
Services.downloads.removeDownload(this.dataItem.downloadId);
|
|
|
|
},
|
|
|
|
|
|
|
|
downloadsCmd_cancel: function DVIC_downloadsCmd_cancel()
|
|
|
|
{
|
|
|
|
if (this.dataItem.inProgress) {
|
|
|
|
Services.downloads.cancelDownload(this.dataItem.downloadId);
|
|
|
|
|
|
|
|
// It is possible that in some cases the Download Manager service
|
|
|
|
// doesn't delete the file from disk when canceling. See bug 732924.
|
|
|
|
try {
|
|
|
|
let localFile = this.dataItem.localFile;
|
|
|
|
if (localFile.exists()) {
|
|
|
|
localFile.remove(false);
|
|
|
|
}
|
|
|
|
} catch (ex) { }
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
downloadsCmd_open: function DVIC_downloadsCmd_open()
|
|
|
|
{
|
|
|
|
// 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.
|
|
|
|
try {
|
|
|
|
let launched = false;
|
|
|
|
try {
|
|
|
|
let mimeInfo = this.dataItem.download.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);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
downloadsCmd_show: function DVIC_downloadsCmd_show()
|
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
downloadsCmd_pauseResume: function DVIC_downloadsCmd_pauseResume()
|
|
|
|
{
|
|
|
|
if (this.dataItem.paused) {
|
|
|
|
Services.downloads.resumeDownload(this.dataItem.downloadId);
|
|
|
|
} else {
|
|
|
|
Services.downloads.pauseDownload(this.dataItem.downloadId);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
downloadsCmd_retry: function DVIC_downloadsCmd_retry()
|
|
|
|
{
|
|
|
|
Services.downloads.retryDownload(this.dataItem.downloadId);
|
|
|
|
},
|
|
|
|
|
|
|
|
downloadsCmd_openReferrer: function DVIC_downloadsCmd_openReferrer()
|
|
|
|
{
|
|
|
|
openURL(this.dataItem.referrer);
|
|
|
|
},
|
|
|
|
|
|
|
|
downloadsCmd_copyLocation: function DVIC_downloadsCmd_copyLocation()
|
|
|
|
{
|
|
|
|
let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"]
|
|
|
|
.getService(Ci.nsIClipboardHelper);
|
|
|
|
clipboard.copyString(this.dataItem.uri);
|
|
|
|
},
|
|
|
|
|
|
|
|
downloadsCmd_doDefault: function DVIC_downloadsCmd_doDefault()
|
|
|
|
{
|
|
|
|
const nsIDM = Ci.nsIDownloadManager;
|
|
|
|
|
|
|
|
// Determine the default command for the current item.
|
|
|
|
let defaultCommand = function () {
|
|
|
|
switch (this.dataItem.state) {
|
|
|
|
case nsIDM.DOWNLOAD_NOTSTARTED: return "downloadsCmd_cancel";
|
|
|
|
case nsIDM.DOWNLOAD_DOWNLOADING: return "downloadsCmd_show";
|
|
|
|
case nsIDM.DOWNLOAD_FINISHED: return "downloadsCmd_open";
|
|
|
|
case nsIDM.DOWNLOAD_FAILED: return "downloadsCmd_retry";
|
|
|
|
case nsIDM.DOWNLOAD_CANCELED: return "downloadsCmd_retry";
|
|
|
|
case nsIDM.DOWNLOAD_PAUSED: return "downloadsCmd_pauseResume";
|
|
|
|
case nsIDM.DOWNLOAD_QUEUED: return "downloadsCmd_cancel";
|
|
|
|
case nsIDM.DOWNLOAD_BLOCKED_PARENTAL: return "downloadsCmd_openReferrer";
|
|
|
|
case nsIDM.DOWNLOAD_SCANNING: return "downloadsCmd_show";
|
|
|
|
case nsIDM.DOWNLOAD_DIRTY: return "downloadsCmd_openReferrer";
|
|
|
|
case nsIDM.DOWNLOAD_BLOCKED_POLICY: return "downloadsCmd_openReferrer";
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}.apply(this);
|
|
|
|
// 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));
|
|
|
|
}
|
|
|
|
};
|