diff options
Diffstat (limited to 'comm/suite/components/downloads')
32 files changed, 6891 insertions, 0 deletions
diff --git a/comm/suite/components/downloads/DownloadsCommon.jsm b/comm/suite/components/downloads/DownloadsCommon.jsm new file mode 100644 index 0000000000..81fdcccfa3 --- /dev/null +++ b/comm/suite/components/downloads/DownloadsCommon.jsm @@ -0,0 +1,800 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */ +/* 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/. */ + +"use strict"; + +var EXPORTED_SYMBOLS = [ + "DownloadsCommon", +]; + +/** + * Handles the Downloads panel shared methods and data access. + * + * This file includes the following constructors and global objects: + * + * DownloadsCommon + * This object is exposed directly to the consumers of this JavaScript module, + * and provides shared methods for all the instances of the user interface. + * + * DownloadsData + * Retrieves the list of past and completed downloads from the underlying + * Downloads API data, and provides asynchronous notifications allowing + * to build a consistent view of the available data. + */ + +// Globals +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { XPCOMUtils } = + ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); +const { AppConstants } = + ChromeUtils.import("resource://gre/modules/AppConstants.jsm"); + +XPCOMUtils.defineLazyModuleGetters(this, { + NetUtil: "resource://gre/modules/NetUtil.jsm", + PluralForm: "resource://gre/modules/PluralForm.jsm", + DownloadHistory: "resource://gre/modules/DownloadHistory.jsm", + Downloads: "resource://gre/modules/Downloads.jsm", + DownloadUIHelper: "resource://gre/modules/DownloadUIHelper.jsm", + DownloadUtils: "resource://gre/modules/DownloadUtils.jsm", + OS: "resource://gre/modules/osfile.jsm", +}); + +XPCOMUtils.defineLazyGetter(this, "DownloadsLogger", () => { + let { ConsoleAPI } = + ChromeUtils.import("resource://gre/modules/Console.jsm", {}); + let consoleOptions = { + maxLogLevelPref: "browser.download.loglevel", + prefix: "Downloads" + }; + return new ConsoleAPI(consoleOptions); +}); + +const kDownloadsStringBundleUrl = + "chrome://communicator/locale/downloads/downloadmanager.properties"; + +// Currently not used. Keep for future updates. +const kDownloadsStringsRequiringFormatting = { + fileExecutableSecurityWarning: true +}; + +// Currently not used. Keep for future updates. +const kDownloadsStringsRequiringPluralForm = { + otherDownloads3: true +}; + +const kPartialDownloadSuffix = ".part"; + +const kPrefBranch = Services.prefs.getBranch("browser.download."); + +const PREF_DM_BEHAVIOR = "browser.download.manager.behavior"; +const PROGRESS_DIALOG_URL = "chrome://communicator/content/downloads/progressDialog.xul"; + +var PrefObserver = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, + Ci.nsISupportsWeakReference]), + getPref(name) { + try { + switch (typeof this.prefs[name]) { + case "boolean": + return kPrefBranch.getBoolPref(name); + } + } catch (ex) { } + return this.prefs[name]; + }, + observe(aSubject, aTopic, aData) { + if (this.prefs.hasOwnProperty(aData)) { + delete this[aData]; + this[aData] = this.getPref(aData); + } + }, + register(prefs) { + this.prefs = prefs; + kPrefBranch.addObserver("", this, true); + for (let key in prefs) { + let name = key; + XPCOMUtils.defineLazyGetter(this, name, function() { + return PrefObserver.getPref(name); + }); + } + }, +}; + +// PrefObserver.register({ + // prefName: defaultValue +// }); + + +// DownloadsCommon + +/** + * This object is exposed directly to the consumers of this JavaScript module, + * and provides shared methods for all the instances of the user interface. + */ +var DownloadsCommon = { + // The following legacy constants are still returned by stateOfDownload, but + // individual properties of the Download object should normally be used. + DOWNLOAD_NOTSTARTED: -1, + DOWNLOAD_DOWNLOADING: 0, + DOWNLOAD_FINISHED: 1, + DOWNLOAD_FAILED: 2, + DOWNLOAD_CANCELED: 3, + DOWNLOAD_PAUSED: 4, + DOWNLOAD_BLOCKED_PARENTAL: 6, + DOWNLOAD_DIRTY: 8, + DOWNLOAD_BLOCKED_POLICY: 9, + + // The following are the possible values of the "attention" property. + ATTENTION_NONE: "", + ATTENTION_SUCCESS: "success", + ATTENTION_WARNING: "warning", + ATTENTION_SEVERE: "severe", + + /** + * Returns an object whose keys are the string names from the downloads string + * bundle, and whose values are either the translated strings or functions + * returning formatted strings. + */ + get strings() { + let strings = {}; + let sb = Services.strings.createBundle(kDownloadsStringBundleUrl); + let enumerator = sb.getSimpleEnumeration(); + while (enumerator.hasMoreElements()) { + let string = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement); + let stringName = string.key; + if (stringName in kDownloadsStringsRequiringFormatting) { + strings[stringName] = function() { + // Convert "arguments" to a real array before calling into XPCOM. + return sb.formatStringFromName(stringName, + Array.slice(arguments, 0), + arguments.length); + }; + } else if (stringName in kDownloadsStringsRequiringPluralForm) { + strings[stringName] = function(aCount) { + // Convert "arguments" to a real array before calling into XPCOM. + let formattedString = sb.formatStringFromName(stringName, + Array.slice(arguments, 0), + arguments.length); + return PluralForm.get(aCount, formattedString); + }; + } else { + strings[stringName] = string.value; + } + } + delete this.strings; + return this.strings = strings; + }, + + /** + * Get access to one of the DownloadsData or HistoryDownloadsData objects + * depending on whether history downloads should be included. + * + * @param window + * The browser window which owns the download button. + * @param [optional] history + * True to include history downloads when the window is public. + */ + // does not apply in SM + getData(window, history = false) { + if (history) { + return HistoryDownloadsData; + } + return DownloadsData; + }, + + /** + * Initializes the Downloads Manager common code. + */ + init() { + const { DownloadsData } = + ChromeUtils.import("resource://gre/modules/Downloads.jsm"); + const { DownloadIntegration } = + ChromeUtils.import("resource://gre/modules/DownloadIntegration.jsm"); + DownloadIntegration.shouldPersistDownload = function() { return true; }; + DownloadsData.initializeDataLink(); + }, + + /** + * Returns the legacy state integer value for the provided Download object. + */ + stateOfDownload(download) { + // Collapse state using the correct priority. + if (!download.stopped) { + return DownloadsCommon.DOWNLOAD_DOWNLOADING; + } + if (download.succeeded) { + return DownloadsCommon.DOWNLOAD_FINISHED; + } + if (download.error) { + if (download.error.becauseBlockedByParentalControls) { + return DownloadsCommon.DOWNLOAD_BLOCKED_PARENTAL; + } + if (download.error.becauseBlockedByReputationCheck) { + return DownloadsCommon.DOWNLOAD_DIRTY; + } + return DownloadsCommon.DOWNLOAD_FAILED; + } + if (download.canceled) { + if (download.hasPartialData) { + return DownloadsCommon.DOWNLOAD_PAUSED; + } + return DownloadsCommon.DOWNLOAD_CANCELED; + } + return DownloadsCommon.DOWNLOAD_NOTSTARTED; + }, + + /** + * Returns the state as a string for the provided Download object. + */ + stateOfDownloadText(download) { + // Don't duplicate the logic so just call stateOfDownload. + let state = this.stateOfDownload(download); + let s = DownloadsCommon.strings; + let title = s.unblockHeaderUnblock; + let verboseState; + + switch (state) { + case DownloadsCommon.DOWNLOAD_PAUSED: + verboseState = s.statePaused; + break; + case DownloadsCommon.DOWNLOAD_DOWNLOADING: + verboseState = s.stateDownloading; + break; + case DownloadsCommon.DOWNLOAD_FINISHED: + verboseState = s.stateCompleted; + break; + case DownloadsCommon.DOWNLOAD_FAILED: + verboseState = s.stateFailed; + break; + case DownloadsCommon.DOWNLOAD_CANCELED: + verboseState = s.stateCanceled; + break; + // Security Zone Policy + case DownloadsCommon.DOWNLOAD_BLOCKED_PARENTAL: + // Security Zone Policy + verboseState = s.stateBlockedParentalControls; + break; + // Security Zone Policy + case DownloadsCommon.DOWNLOAD_BLOCKED_POLICY: + verboseState = s.stateBlockedPolicy; + break; + // possible virus/spyware + case DownloadsCommon.DOWNLOAD_DIRTY: + verboseState = s.stateDirty; + break; + // Currently not returned. + case DownloadsCommon.DOWNLOAD_UPLOADING: + verboseState = s.stateNotStarted; + break; + case DownloadsCommon.DOWNLOAD_NOTSTARTED: + verboseState = s.stateNotStarted; + break; + // Whoops! + default: + verboseState = s.stateUnknown; + break; + } + + return verboseState; + }, + + /** + * Returns the transfer progress text for the provided Download object. + */ + getTransferredBytes(download) { + let currentBytes; + let totalBytes; + // Download in progress. + // Download paused / canceled and has partial data. + if (!download.stopped || + (download.canceled && download.hasPartialData)) { + currentBytes = download.currentBytes, + totalBytes = download.hasProgress ? download.totalBytes : -1; + // Download done but file missing. + } else if (download.succeeded && !download.exists) { + currentBytes = download.totalBytes ? download.totalBytes : -1; + totalBytes = -1; + // For completed downloads, show the file size + } else if (download.succeeded && download.target.size !== undefined) { + currentBytes = download.target.size; + totalBytes = -1; + // Some local files saves e.g. from attachments also have no size. + // They only have a target in downloads.json but no target.path. + // FIX ME later. + } else { + currentBytes = -1; + totalBytes = -1; + } + + // We do not want to show 0 of xxx bytes. + if (currentBytes == 0) { + currentBytes = -1; + } + + if (totalBytes == 0) { + totalBytes = -1; + } + + // We tried everything. + if (currentBytes == -1 && totalBytes == -1) { + return ""; + } + + return DownloadUtils.getTransferTotal(currentBytes, totalBytes); + }, + + /** + * Returns the time remaining text for the provided Download object. + * For calculation a variable is stored in it. + */ + getTimeRemaining(download) { + // If you do changes here please check progressDialog.js. + if (!download.stopped) { + let lastSec = (download.lastSec == null) ? Infinity : download.lastSec; + // Calculate the time remaining if we have valid values + let seconds = (download.speed > 0) && (download.totalBytes > 0) + ? (download.totalBytes - download.currentBytes) / download.speed + : -1; + let [timeLeft, newLast] = DownloadUtils.getTimeLeft(seconds, lastSec); + // Store it back for next calculation. + download.lastSec = newLast; + return timeLeft; + } + return ""; + }, + + /** + * Opens a downloaded file. + * + * @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(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"); + } + + let isWindowsExe = AppConstants.platform == "win" && + aFile.leafName.toLowerCase().endsWith(".exe"); + + let promiseShouldLaunch; + // Don't prompt on Windows for .exe since there will be a native prompt. + if (aFile.isExecutable() && !isWindowsExe) { + // We get a prompter for the provided window here, even though anchoring + // to the most recently active window should work as well. + promiseShouldLaunch = + DownloadUIHelper.getPrompter(aOwnerWindow) + .confirmLaunchExecutable(aFile.path); + } else { + promiseShouldLaunch = Promise.resolve(true); + } + + promiseShouldLaunch.then(shouldLaunch => { + if (!shouldLaunch) { + return; + } + + // 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)); + } + }).catch(Cu.reportError); + }, + + /** + * Show a downloaded file in the system file manager. + * + * @param aFile + * a downloaded file. + */ + 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) { + this.showDirectory(parent); + } + } + }, + + /** + * Show the specified folder in the system file manager. + * + * @param aDirectory + * a directory to be opened with system file manager. + */ + showDirectory(aDirectory) { + if (!(aDirectory instanceof Ci.nsIFile)) { + throw new Error("aDirectory must be a nsIFile object"); + } + try { + aDirectory.launch(); + } catch (ex) { + // If launch fails (probably because it's not implemented), let + // the OS handler try to open the directory. + Cc["@mozilla.org/uriloader/external-protocol-service;1"] + .getService(Ci.nsIExternalProtocolService) + .loadUrl(NetUtil.newURI(aDirectory)); + } + }, + + /** + * Displays an alert message box which asks the user if they want to + * unblock the downloaded file or not. + * + * @param options + * An object with the following properties: + * { + * verdict: + * The detailed reason why the download was blocked, according to + * the "Downloads.Error.BLOCK_VERDICT_" constants. If an unknown + * reason is specified, "Downloads.Error.BLOCK_VERDICT_MALWARE" is + * assumed. + * window: + * The window with which this action is associated. + * dialogType: + * String that determines which actions are available: + * - "unblock" to offer just "unblock". + * - "chooseUnblock" to offer "unblock" and "confirmBlock". + * - "chooseOpen" to offer "open" and "confirmBlock". + * } + * + * @return {Promise} + * @resolves String representing the action that should be executed: + * - "open" to allow the download and open the file. + * - "unblock" to allow the download without opening the file. + * - "confirmBlock" to delete the blocked data permanently. + * - "cancel" to do nothing and cancel the operation. + */ + async confirmUnblockDownload({ verdict, window, + dialogType }) { + let s = DownloadsCommon.strings; + + // All the dialogs have an action button and a cancel button, while only + // some of them have an additonal button to remove the file. The cancel + // button must always be the one at BUTTON_POS_1 because this is the value + // returned by confirmEx when using ESC or closing the dialog (bug 345067). + let title = s.unblockHeaderUnblock; + let firstButtonText = s.unblockButtonUnblock; + let firstButtonAction = "unblock"; + let buttonFlags = + (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) + + (Ci.nsIPrompt.BUTTON_TITLE_CANCEL * Ci.nsIPrompt.BUTTON_POS_1); + + switch (dialogType) { + case "unblock": + // Use only the unblock action. The default is to cancel. + buttonFlags += Ci.nsIPrompt.BUTTON_POS_1_DEFAULT; + break; + case "chooseUnblock": + // Use the unblock and remove file actions. The default is remove file. + buttonFlags += + (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_2) + + Ci.nsIPrompt.BUTTON_POS_2_DEFAULT; + break; + case "chooseOpen": + // Use the unblock and open file actions. The default is open file. + title = s.unblockHeaderOpen; + firstButtonText = s.unblockButtonOpen; + firstButtonAction = "open"; + buttonFlags += + (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_2) + + Ci.nsIPrompt.BUTTON_POS_0_DEFAULT; + break; + default: + Cu.reportError("Unexpected dialog type: " + dialogType); + return "cancel"; + } + + let message; + switch (verdict) { + case Downloads.Error.BLOCK_VERDICT_UNCOMMON: + message = s.unblockTypeUncommon2; + break; + case Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED: + message = s.unblockTypePotentiallyUnwanted2; + break; + default: // Assume Downloads.Error.BLOCK_VERDICT_MALWARE + message = s.unblockTypeMalware; + break; + } + message += "\n\n" + s.unblockTip2; + + Services.ww.registerNotification(function onOpen(subj, topic) { + if (topic == "domwindowopened" && subj instanceof Ci.nsIDOMWindow) { + // Make sure to listen for "DOMContentLoaded" because it is fired + // before the "load" event. + subj.addEventListener("DOMContentLoaded", function() { + if (subj.document.documentURI == + "chrome://global/content/commonDialog.xul") { + Services.ww.unregisterNotification(onOpen); + let dialog = subj.document.getElementById("commonDialog"); + if (dialog) { + // Change the dialog to use a warning icon. + dialog.classList.add("alert-dialog"); + } + } + }, {once: true}); + } + }); + + let rv = Services.prompt.confirmEx(window, title, message, buttonFlags, + firstButtonText, null, + s.unblockButtonConfirmBlock, null, {}); + return [firstButtonAction, "cancel", "confirmBlock"][rv]; + }, +}; + +XPCOMUtils.defineLazyGetter(this.DownloadsCommon, "log", () => { + return DownloadsLogger.log.bind(DownloadsLogger); +}); +XPCOMUtils.defineLazyGetter(this.DownloadsCommon, "error", () => { + return DownloadsLogger.error.bind(DownloadsLogger); +}); + +// DownloadsData + +/** + * Retrieves the list of past and completed downloads from the underlying + * Downloads API data, and provides asynchronous notifications allowing to + * build a consistent view of the available data. + * + * Note that using this object does not automatically initialize the list of + * downloads. This is useful to display a neutral progress indicator in + * the main browser window until the autostart timeout elapses. + * + * This powers the DownloadsData and HistoryDownloadsData singleton objects. + */ + function DownloadsDataCtor({ isHistory } = {}) { + + // Contains all the available Download objects and their integer state. + this.oldDownloadStates = new Map(); + + // For the history downloads list we don't need to register this as a view, + // but we have to ensure that the DownloadsData object is initialized before + // we register more views. This ensures that the view methods of DownloadsData + // are invoked before those of views registered on HistoryDownloadsData, + // allowing the endTime property to be set correctly. + if (isHistory) { + DownloadsData.initializeDataLink(); + this._promiseList = DownloadsData._promiseList + .then(() => DownloadHistory.getList()); + return; + } + + // This defines "initializeDataLink" and "_promiseList" synchronously, then + // continues execution only when "initializeDataLink" is called, allowing the + // underlying data to be loaded only when actually needed. + this._promiseList = (async () => { + await new Promise(resolve => this.initializeDataLink = resolve); + let list = await Downloads.getList(Downloads.ALL); + + await list.addView(this); + this._downloadsLoaded = true; + + return list; + })(); +} + +DownloadsDataCtor.prototype = { + /** + * Starts receiving events for current downloads. + */ + initializeDataLink() {}, + + /** + * Used by sound logic when download ends. + */ + _sound: null, + /** + * Promise resolved with the underlying DownloadList object once we started + * receiving events for current downloads. + */ + _promiseList: null, + + _downloadsLoaded: null, + + /** + * Iterator for all the available Download objects. This is empty until the + * data has been loaded using the JavaScript API for downloads. + */ + get downloads() { + return this.oldDownloadStates.keys(); + }, + + /** + * True if there are finished downloads that can be removed from the list. + */ + get canRemoveFinished() { + for (let download of this.downloads) { + // Stopped, paused, and failed downloads with partial data are removed. + if (download.stopped && !(download.canceled && download.hasPartialData)) { + return true; + } + } + return false; + }, + + /** + * Asks the back-end to remove finished downloads from the list. This method + * is only called after the data link has been initialized. + */ + removeFinished() { + Downloads.getList(Downloads.ALL) + .then(list => list.removeFinished()) + .catch(Cu.reportError); + }, + + // Integration with the asynchronous Downloads back-end + + // Download view + onDownloadAdded: function(download) + { + // Download objects do not store the end time of downloads, as the Downloads + // API does not need to persist this information for all platforms. Once a + // download terminates on a Desktop browser, it becomes a history download, + // for which the end time is stored differently, as a Places annotation. + download.endTime = Date.now(); + this.oldDownloadStates.set(download, + DownloadsCommon.stateOfDownload(download)); + + download.displayName = + download.target.path ? OS.Path.basename(download.target.path) + : download.source.url; + this.onDownloadChanged(download); + if (!this._downloadsLoaded) + return; + + var behavior = download.source.isPrivate ? 1 : + Services.prefs.getIntPref(PREF_DM_BEHAVIOR); + switch (behavior) { + case 0: + Cc["@mozilla.org/suite/suiteglue;1"] + .getService(Ci.nsISuiteGlue) + .showDownloadManager(true); + break; + case 1: + Services.ww.openWindow(null, PROGRESS_DIALOG_URL, null, + "chrome,titlebar,centerscreen,minimizable=yes,dialog=no", + { wrappedJSObject: download }); + break; + } + + return; // No UI for behavior >= 2 + }, + + onDownloadChanged(download) { + let oldState = this.oldDownloadStates.get(download); + let newState = DownloadsCommon.stateOfDownload(download); + this.oldDownloadStates.set(download, newState); + + if (oldState != newState && + (download.succeeded || + (download.canceled && !download.hasPartialData) || + download.error)) { + // Store the end time that may be displayed by the views. + download.endTime = Date.now(); + + // This state transition code should actually be located in a Downloads + // API module (bug 941009). + // This might end with an exception if it is an unsupported uri scheme. + DownloadHistory.updateMetaData(download); + + if (download.succeeded) { + this.playDownloadSound(); + } + } + }, + + onDownloadRemoved(download) { + this.oldDownloadStates.delete(download); + }, + + // Download summary + onSummaryChanged: function() { + + if (!gTaskbarProgress) + return; + + const nsITaskbarProgress = Ci.nsITaskbarProgress; + var currentBytes = gDownloadsSummary.progressCurrentBytes; + var totalBytes = gDownloadsSummary.progressTotalBytes; + var state = gDownloadsSummary.allHaveStopped ? + currentBytes ? nsITaskbarProgress.STATE_PAUSED : + nsITaskbarProgress.STATE_NO_PROGRESS : + currentBytes < totalBytes ? nsITaskbarProgress.STATE_NORMAL : + nsITaskbarProgress.STATE_INDETERMINATE; + switch (state) { + case nsITaskbarProgress.STATE_NO_PROGRESS: + case nsITaskbarProgress.STATE_INDETERMINATE: + gTaskbarProgress.setProgressState(state, 0, 0); + break; + default: + gTaskbarProgress.setProgressState(state, currentBytes, totalBytes); + break; + } + }, + + // Play a download sound. + playDownloadSound: function() + { + if (Services.prefs.getBoolPref("browser.download.finished_download_sound")) { + if (!this._sound) + this._sound = Cc["@mozilla.org/sound;1"].createInstance(Ci.nsISound); + try { + let url = Services.prefs.getStringPref("browser.download.finished_sound_url"); + this._sound.play(Services.io.newURI(url)); + } catch (e) { + this._sound.beep(); + } + } + }, + + // Registration of views + + /** + * Adds an object to be notified when the available download data changes. + * The specified object is initialized with the currently available downloads. + * + * @param aView + * DownloadsView object to be added. This reference must be passed to + * removeView before termination. + */ + addView(aView) { + this._promiseList.then(list => list.addView(aView)) + .catch(Cu.reportError); + }, + + /** + * Removes an object previously added using addView. + * + * @param aView + * DownloadsView object to be removed. + */ + removeView(aView) { + this._promiseList.then(list => list.removeView(aView)) + .catch(Cu.reportError); + }, +}; + +XPCOMUtils.defineLazyGetter(this, "HistoryDownloadsData", function() { + return new DownloadsDataCtor({ isHistory: true }); +}); + +XPCOMUtils.defineLazyGetter(this, "DownloadsData", function() { + return new DownloadsDataCtor(); +}); diff --git a/comm/suite/components/downloads/DownloadsTaskbar.jsm b/comm/suite/components/downloads/DownloadsTaskbar.jsm new file mode 100644 index 0000000000..6cefeedcca --- /dev/null +++ b/comm/suite/components/downloads/DownloadsTaskbar.jsm @@ -0,0 +1,182 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + * vim: sw=2 ts=2 sts=2 et filetype=javascript + * 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/. */ + +var EXPORTED_SYMBOLS = [ + "DownloadsTaskbar", +]; + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { XPCOMUtils } = + ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); + +ChromeUtils.defineModuleGetter(this, "Downloads", + "resource://gre/modules/Downloads.jsm"); + +XPCOMUtils.defineLazyGetter(this, "gWinTaskbar", function() { + if (!("@mozilla.org/windows-taskbar;1" in Cc)) { + return null; + } + let winTaskbar = Cc["@mozilla.org/windows-taskbar;1"] + .getService(Ci.nsIWinTaskbar); + return winTaskbar.available && winTaskbar; +}); + +XPCOMUtils.defineLazyGetter(this, "gMacTaskbarProgress", function() { + return ("@mozilla.org/widget/macdocksupport;1" in Cc) && + Cc["@mozilla.org/widget/macdocksupport;1"] + .getService(Ci.nsITaskbarProgress); +}); + +XPCOMUtils.defineLazyGetter(this, "gGtkTaskbarProgress", function() { + return ("@mozilla.org/widget/taskbarprogress/gtk;1" in Cc) && + Cc["@mozilla.org/widget/taskbarprogress/gtk;1"] + .getService(Ci.nsIGtkTaskbarProgress); +}); + +// DownloadsTaskbar + +/** + * Handles the download progress indicator in the taskbar. + */ +var DownloadsTaskbar = { + /** + * Underlying DownloadSummary providing the aggregate download information, or + * null if the indicator has never been initialized. + */ + _summary: null, + + /** + * nsITaskbarProgress object to which download information is dispatched. + * This can be null if the indicator has never been initialized or if the + * indicator is currently hidden on Windows. + */ + _taskbarProgress: null, + + /** + * This method is called after a new browser window is opened, and ensures + * that the download progress indicator is displayed in the taskbar. + * + * On Windows, the indicator is attached to the first browser window that + * calls this method. When the window is closed, the indicator is moved to + * another browser window, if available, in no particular order. When there + * are no browser windows visible, the indicator is hidden. + * + * On macOS, the indicator is initialized globally when this method is + * called for the first time. Subsequent calls have no effect. + * + * @param aBrowserWindow + * nsIDOMWindow object of the newly opened browser window to which the + * indicator may be attached. + */ + registerIndicator(aWindow) { + if (!this._taskbarProgress) { + if (gMacTaskbarProgress) { + // On macOS, we have to register the global indicator only once. + this._taskbarProgress = gMacTaskbarProgress; + // Free the XPCOM reference on shutdown, to prevent detecting a leak. + Services.obs.addObserver(() => { + this._taskbarProgress = null; + gMacTaskbarProgress = null; + }, "quit-application-granted"); + } else { + // On Windows, the indicator is currently hidden because we have no + // previous window, thus we should attach the indicator now. + // In gtk3, the window itself implements the progress interface. + this.attachIndicator(aWindow); + } + } + + // Ensure that the DownloadSummary object will be created asynchronously. + if (!this._summary) { + Downloads.getSummary(Downloads.ALL).then(summary => { + // In case the method is re-entered, we simply ignore redundant + // invocations of the callback, instead of keeping separate state. + if (this._summary) { + return undefined; + } + this._summary = summary; + return this._summary.addView(this); + }).catch(Cu.reportError); + } + }, + + /** + * On Windows and linux attach the taskbar indicator to the specified window. + */ + attachIndicator(aWindow) { + // If there is already a taskbarProgress this usually means the download + // manager became active. So clear the taskbar state first. + if (this._taskbarProgress) { + this._taskbarProgress.setProgressState(Ci.nsITaskbarProgress.STATE_NO_PROGRESS); + } + + if (gWinTaskbar) { + // Activate the indicator on the specified window. + let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIXULWindow).docShell; + this._taskbarProgress = gWinTaskbar.getTaskbarProgress(docShell); + } else if (gGtkTaskbarProgress) { + // In gtk3, the window itself implements the progress interface. + if (!this._taskbarProgress) { + this._taskbarProgress = gGtkTaskbarProgress; + } + + this._taskbarProgress.setPrimaryWindow(aWindow); + } else { + // macOS, not gtk3 or unsupported OS. + return; + } + + // If the DownloadSummary object has already been created, we should update + // the state of the new indicator, otherwise it will be updated as soon as + // the DownloadSummary view is registered. + if (this._summary) { + this.onSummaryChanged(); + } + + aWindow.addEventListener("unload", () => { + let windows = Services.wm.getEnumerator(null); + let newActiveWindow = null; + if (windows.hasMoreElements()) { + newActiveWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow); + } + if (newActiveWindow) { + // Move the progress indicator to the other window. + this.attachIndicator(newActiveWindow); + } else { + // The last window has been closed. We remove the reference to + // the taskbar progress object. + this._taskbarProgress = null; + } + }); + }, + + // DownloadSummary view + onSummaryChanged() { + // If the last window has been closed, we have no indicator any more. + if (!this._taskbarProgress) { + return; + } + + if (this._summary.allHaveStopped || this._summary.progressTotalBytes == 0) { + this._taskbarProgress.setProgressState( + Ci.nsITaskbarProgress.STATE_NO_PROGRESS, 0, 0); + } else { + // For a brief moment before completion, some download components may + // report more transferred bytes than the total number of bytes. Thus, + // ensure that we never break the expectations of the progress indicator. + let progressCurrentBytes = Math.min(this._summary.progressTotalBytes, + this._summary.progressCurrentBytes); + this._taskbarProgress.setProgressState( + Ci.nsITaskbarProgress.STATE_NORMAL, + progressCurrentBytes, + this._summary.progressTotalBytes); + } + }, +}; diff --git a/comm/suite/components/downloads/content/DownloadProgressListener.js b/comm/suite/components/downloads/content/DownloadProgressListener.js new file mode 100644 index 0000000000..b5bab95727 --- /dev/null +++ b/comm/suite/components/downloads/content/DownloadProgressListener.js @@ -0,0 +1,30 @@ +/* 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/. */ + +/** + * DownloadProgressListener "class" is used to help update download items shown + * in the Download Manager UI such as displaying amount transferred, transfer + * rate, and time left for each download. + */ +function DownloadProgressListener() {} + +DownloadProgressListener.prototype = { + onDownloadAdded: function(aDownload) { + gDownloadTreeView.addDownload(aDownload); + + // Update window title in-case we don't get all progress notifications + onUpdateProgress(); + }, + + onDownloadChanged: function(aDownload) { + gDownloadTreeView.updateDownload(aDownload); + + // Update window title + onUpdateProgress(); + }, + + onDownloadRemoved: function(aDownload) { + gDownloadTreeView.removeDownload(aDownload); + } +}; diff --git a/comm/suite/components/downloads/content/downloadmanager.js b/comm/suite/components/downloads/content/downloadmanager.js new file mode 100644 index 0000000000..d390e655dd --- /dev/null +++ b/comm/suite/components/downloads/content/downloadmanager.js @@ -0,0 +1,634 @@ +/* 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/. */ + +var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); +var {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetters(this, { + PluralForm: "resource://gre/modules/PluralForm.jsm", + Downloads: "resource://gre/modules/Downloads.jsm", + DownloadsCommon: "resource:///modules/DownloadsCommon.jsm", + PlacesUtils: "resource://gre/modules/PlacesUtils.jsm", + FileUtils: "resource://gre/modules/FileUtils.jsm", +}); + +var gDownloadTree; +var gDownloadTreeView; +var gDownloadList; +var gDownloadStatus; +var gDownloadListener; +var gSearchBox; + +function dmStartup() +{ + Downloads.getList(Downloads.PUBLIC).then(dmAsyncStartup); +} + +function dmAsyncStartup(aList) +{ + gDownloadList = aList; + + gDownloadTree = document.getElementById("downloadTree"); + gDownloadStatus = document.getElementById("statusbar-display"); + gSearchBox = document.getElementById("search-box"); + + // Insert as first controller on the whole window + window.controllers.insertControllerAt(0, dlTreeController); + + // We need to keep the view object around globally to access "local" + // non-nsITreeView methods + gDownloadTreeView = new DownloadTreeView(); + gDownloadTree.view = gDownloadTreeView; + + // The DownloadProgressListener (DownloadProgressListener.js) handles + // progress notifications. + gDownloadListener = new DownloadProgressListener(); + gDownloadList.addView(gDownloadListener); + + // correct keybinding command attributes which don't do our business yet + var key = document.getElementById("key_delete"); + if (key.hasAttribute("command")) + key.setAttribute("command", "cmd_stop"); + key = document.getElementById("key_delete2"); + if (key.hasAttribute("command")) + key.setAttribute("command", "cmd_stop"); + + gDownloadTree.focus(); + + if (gDownloadTree.view.rowCount > 0) + gDownloadTree.view.selection.select(0); +} + +function dmShutdown() +{ + gDownloadList.removeView(gDownloadListener); + window.controllers.removeController(dlTreeController); +} + +function searchDownloads(aInput) +{ + gDownloadTreeView.searchView(aInput); +} + +function sortDownloads(aEventTarget) +{ + var column = aEventTarget; + var colID = column.id; + var sortDirection = null; + + // If the target is a menuitem, handle it and forward to a column + if (/^menu_SortBy/.test(colID)) { + colID = colID.replace(/^menu_SortBy/, ""); + column = document.getElementById(colID); + var sortedColumn = gDownloadTree.columns.getSortedColumn(); + if (sortedColumn && sortedColumn.id == colID) + sortDirection = sortedColumn.element.getAttribute("sortDirection"); + else + sortDirection = "ascending"; + } + else if (colID == "menu_Unsorted") { + // calling .sortView() with an "unsorted" colID returns us to original order + colID = "unsorted"; + column = null; + sortDirection = "ascending"; + } + else if (colID == "menu_SortAscending" || colID == "menu_SortDescending") { + sortDirection = colID.replace(/^menu_Sort/, "").toLowerCase(); + var sortedColumn = gDownloadTree.columns.getSortedColumn(); + if (sortedColumn) { + colID = sortedColumn.id; + column = sortedColumn.element; + } + } + + // Abort if this is still no column + if (column && column.localName != "treecol") + return; + + // Abort on cyler columns, we don't sort them + if (column && column.getAttribute("cycler") == "true") + return; + + if (!sortDirection) { + // If not set above already, toggle the current direction + sortDirection = column.getAttribute("sortDirection") == "ascending" ? + "descending" : "ascending"; + } + + // Clear attributes on all columns, we're setting them again after sorting + for (let node = document.getElementById("Name"); node; node = node.nextSibling) { + node.removeAttribute("sortActive"); + node.removeAttribute("sortDirection"); + } + + // Actually sort the tree view + gDownloadTreeView.sortView(colID, sortDirection); + + if (column) { + // Set attributes to the sorting we did + column.setAttribute("sortActive", "true"); + column.setAttribute("sortDirection", sortDirection); + } +} + +async function removeDownload(aDownload) +{ + // Remove the associated history element first, if any, so that the views + // that combine history and session downloads won't resurrect the history + // download into the view just before it is deleted permanently. + try { + await PlacesUtils.history.remove(aDownload.source.url); + } catch (ex) { + Cu.reportError(ex); + } + let list = await Downloads.getList(Downloads.ALL); + await list.remove(aDownload); + await aDownload.finalize(true); +} + +function cancelDownload(aDownload) +{ + // This is the correct way to avoid race conditions when cancelling. + aDownload.cancel().catch(() => {}); + aDownload.removePartialData().catch(Cu.reportError); +} + +function openDownload(aDownload) +{ + let file = new FileUtils.File(aDownload.target.path); + DownloadsCommon.openDownloadedFile(file, null, window); +} + +function showDownload(aDownload) +{ + let file; + + if (aDownload.succeeded && + aDownload.target.exists) { + file = new FileUtils.File(aDownload.target.path); + } else { + file = new FileUtils.File(aDownload.target.partFilePath); + } + DownloadsCommon.showDownloadedFile(file); +} + +function showProperties(aDownload) +{ + openDialog("chrome://communicator/content/downloads/progressDialog.xul", + null, "chrome,titlebar,centerscreen,minimizable=yes,dialog=no", + { wrappedJSObject: aDownload }, true); +} + +function onTreeSelect(aEvent) +{ + var selectionCount = gDownloadTreeView.selection.count; + if (selectionCount == 1) { + var selItemData = gDownloadTreeView.getRowData(gDownloadTree.currentIndex); + gDownloadStatus.label = selItemData.target.path; + } else { + gDownloadStatus.label = ""; + } + + window.updateCommands("tree-select"); +} + +function onUpdateViewColumns(aMenuItem) +{ + while (aMenuItem) { + // Each menuitem should be checked if its column is not hidden. + var colID = aMenuItem.id.replace(/^menu_Toggle/, ""); + var column = document.getElementById(colID); + aMenuItem.setAttribute("checked", !column.hidden); + aMenuItem = aMenuItem.nextSibling; + } +} + +function toggleColumn(aMenuItem) +{ + var colID = aMenuItem.id.replace(/^menu_Toggle/, ""); + var column = document.getElementById(colID); + column.setAttribute("hidden", !column.hidden); +} + +function onUpdateViewSort(aMenuItem) +{ + var unsorted = true; + var ascending = true; + while (aMenuItem) { + switch (aMenuItem.id) { + case "": // separator + break; + case "menu_Unsorted": + if (unsorted) // this would work even if Unsorted was last + aMenuItem.setAttribute("checked", "true"); + break; + case "menu_SortAscending": + aMenuItem.setAttribute("disabled", unsorted); + if (!unsorted && ascending) + aMenuItem.setAttribute("checked", "true"); + break; + case "menu_SortDescending": + aMenuItem.setAttribute("disabled", unsorted); + if (!unsorted && !ascending) + aMenuItem.setAttribute("checked", "true"); + break; + default: + var colID = aMenuItem.id.replace(/^menu_SortBy/, ""); + var column = document.getElementById(colID); + var direction = column.getAttribute("sortDirection"); + if (column.getAttribute("sortActive") == "true" && direction) { + // We've found a sorted column. Remember its direction. + ascending = direction == "ascending"; + unsorted = false; + aMenuItem.setAttribute("checked", "true"); + } + } + aMenuItem = aMenuItem.nextSibling; + } +} + +// This is called by the progress listener. +var gLastComputedMean = -1; +var gLastActiveDownloads = 0; +function onUpdateProgress() +{ + var dls = gDownloadTreeView.getActiveDownloads(); + var numActiveDownloads = dls.length; + + // Use the default title and reset "last" values if there's no downloads + if (numActiveDownloads == 0) { + document.title = document.documentElement.getAttribute("statictitle"); + gLastComputedMean = -1; + gLastActiveDownloads = 0; + + return; + } + + // Establish the mean transfer speed and amount downloaded. + var mean = 0; + var base = 0; + for (var dl of dls) { + if (dl.totalBytes > 0) { + mean += dl.currentBytes; + base += dl.totalBytes; + } + } + + // Calculate the percent transferred, unless we don't have a total file size + var dlbundle = document.getElementById("dmBundle"); + if (base != 0) + mean = Math.floor((mean / base) * 100); + + // Update title of window + if (mean != gLastComputedMean || gLastActiveDownloads != numActiveDownloads) { + gLastComputedMean = mean; + gLastActiveDownloads = numActiveDownloads; + + var title; + if (base == 0) + title = dlbundle.getFormattedString("downloadsTitleFiles", + [numActiveDownloads]); + else + title = dlbundle.getFormattedString("downloadsTitlePercent", + [numActiveDownloads, mean]); + + // Get the correct plural form and insert number of downloads and percent + title = PluralForm.get(numActiveDownloads, title); + + document.title = title; + } +} + +function handlePaste() { + 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; + + DownloadURL(url, name || url, document); + } catch (ex) {} +} + +var dlTreeController = { + supportsCommand: function(aCommand) + { + switch (aCommand) { + case "cmd_play": + case "cmd_pause": + case "cmd_resume": + case "cmd_retry": + case "cmd_cancel": + case "cmd_remove": + case "cmd_stop": + case "cmd_open": + case "cmd_show": + case "cmd_openReferrer": + case "cmd_copyLocation": + case "cmd_properties": + case "cmd_paste": + case "cmd_selectAll": + case "cmd_clearList": + return true; + } + return false; + }, + + isCommandEnabled: function(aCommand) + { + var selectionCount = 0; + if (gDownloadTreeView && gDownloadTreeView.selection) + selectionCount = gDownloadTreeView.selection.count; + + var selItemData = []; + if (selectionCount) { + // walk all selected rows + let start = {}; + let end = {}; + let numRanges = gDownloadTreeView.selection.getRangeCount(); + for (let rg = 0; rg < numRanges; rg++) { + gDownloadTreeView.selection.getRangeAt(rg, start, end); + for (let row = start.value; row <= end.value; row++) + selItemData.push(gDownloadTreeView.getRowData(row)); + } + } + + switch (aCommand) { + case "cmd_play": + if (!selectionCount) + return false; + for (let dldata of selItemData) { + if (dldata.succeeded || (!dldata.stopped && !dldata.hasPartialData)) + return false; + } + return true; + case "cmd_pause": + if (!selectionCount) + return false; + for (let dldata of selItemData) { + if (dldata.stopped || !dldata.hasPartialData) + return false; + } + return true; + case "cmd_resume": + if (!selectionCount) + return false; + for (let dldata of selItemData) { + if (!dldata.stopped || !dldata.hasPartialData) + return false; + } + return true; + case "cmd_open": + return selectionCount == 1 && + selItemData[0].succeeded && + selItemData[0].target.exists; + case "cmd_show": + // target.exists is only set if the download finished and the target + // is still located there. + // For simplicity we just assume the target is there if the download + // has not succeeded e.g. is still in progress. This might be wrong + // but showDownload will deal with it. + return selectionCount == 1 && + ((selItemData[0].succeeded && + selItemData[0].target.exists) || + !selItemData[0].succeeded); + case "cmd_cancel": + if (!selectionCount) + return false; + for (let dldata of selItemData) { + if (dldata.stopped && !dldata.hasPartialData) + return false; + } + return true; + case "cmd_retry": + if (!selectionCount) + return false; + for (let dldata of selItemData) { + if (dldata.succeeded || !dldata.stopped || dldata.hasPartialData) + return false; + } + return true; + case "cmd_remove": + if (!selectionCount) + return false; + for (let dldata of selItemData) { + if (!dldata.stopped) + return false; + } + return true; + case "cmd_openReferrer": + return selectionCount == 1 && !!selItemData[0].source.referrer; + case "cmd_stop": + case "cmd_copyLocation": + return selectionCount > 0; + case "cmd_properties": + return selectionCount == 1; + case "cmd_selectAll": + return gDownloadTreeView.rowCount != selectionCount; + case "cmd_clearList": + // Since active downloads always sort before removable downloads, + // we only need to check that the last download has stopped. + return gDownloadTreeView.rowCount && + !gDownloadTreeView.getRowData(gDownloadTreeView.rowCount - 1).isActive; + case "cmd_paste": + return true; + default: + return false; + } + }, + + doCommand: function(aCommand) { + var selectionCount = 0; + if (gDownloadTreeView && gDownloadTreeView.selection) + selectionCount = gDownloadTreeView.selection.count; + + var selItemData = []; + if (selectionCount) { + // walk all selected rows + let start = {}; + let end = {}; + let numRanges = gDownloadTreeView.selection.getRangeCount(); + for (let rg = 0; rg < numRanges; rg++) { + gDownloadTreeView.selection.getRangeAt(rg, start, end); + for (let row = start.value; row <= end.value; row++) + selItemData.push(gDownloadTreeView.getRowData(row)); + } + } + + switch (aCommand) { + case "cmd_play": + for (let dldata of selItemData) { + if (!dldata.stopped) + dldata.cancel(); + else if (!dldata.succeeded) + dldata.start(); + } + break; + case "cmd_pause": + for (let dldata of selItemData) + dldata.cancel(); + break; + case "cmd_resume": + case "cmd_retry": + for (let dldata of selItemData) { + // Errors when retrying are already reported as download failures. + dldata.start(); + } + break; + case "cmd_cancel": + for (let dldata of selItemData) + cancelDownload(dldata); + break; + case "cmd_remove": + for (let dldata of selItemData) + removeDownload(dldata).catch(Cu.reportError); + break; + case "cmd_stop": + for (let dldata of selItemData) { + if (dldata.isActive) + cancelDownload(dldata); + else + gDownloadList.remove(dldata); + } + break; + case "cmd_open": + openDownload(selItemData[0]); + break; + case "cmd_show": + showDownload(selItemData[0]); + break; + case "cmd_openReferrer": + openUILink(selItemData[0].source.referrer); + break; + case "cmd_copyLocation": + var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"] + .getService(Ci.nsIClipboardHelper); + var uris = []; + for (let dldata of selItemData) + uris.push(dldata.source.url); + clipboard.copyString(uris.join("\n"), document); + break; + case "cmd_properties": + showProperties(selItemData[0]); + break; + case "cmd_selectAll": + gDownloadTreeView.selection.selectAll(); + break; + case "cmd_clearList": + // Remove each download starting from the end until we hit a download + // that is in progress + for (let idx = gDownloadTreeView.rowCount - 1; idx >= 0; idx--) { + let dldata = gDownloadTreeView.getRowData(idx); + if (!dldata.isActive) { + gDownloadList.remove(dldata); + } + } + + if (!gSearchBox.value) + break; + + // Clear the input as if the user did it and move focus to the list + gSearchBox.value = ""; + searchDownloads(""); + gDownloadTree.focus(); + break; + case "cmd_paste": + handlePaste(); + break; + } + }, + + onEvent: function(aEvent){ + switch (aEvent) { + case "tree-select": + this.onCommandUpdate(); + } + }, + + onCommandUpdate: function() { + var cmds = ["cmd_play", "cmd_pause", "cmd_resume", "cmd_retry", + "cmd_cancel", "cmd_remove", "cmd_stop", "cmd_open", "cmd_show", + "cmd_openReferrer", "cmd_copyLocation", "cmd_properties", + "cmd_selectAll", "cmd_clearList"]; + for (let command in cmds) + goUpdateCommand(cmds[command]); + } +}; + +var gDownloadDNDObserver = { + onDragStart: function (aEvent) + { + if (!gDownloadTreeView || + !gDownloadTreeView.selection || + !gDownloadTreeView.selection.count) + return; + + var selItemData = gDownloadTreeView.getRowData(gDownloadTree.currentIndex); + var file = new FileUtils.File(selItemData.target.path); + + if (!file.exists()) + return; + + var url = Services.io.newFileURI(file).spec; + var dt = aEvent.dataTransfer; + dt.mozSetDataAt("application/x-moz-file", file, 0); + dt.setData("text/uri-list", url + "\r\n"); + dt.setData("text/plain", url + "\n"); + dt.effectAllowed = "copyMove"; + if (gDownloadTreeView.selection.count == 1) + dt.setDragImage(gDownloadStatus, 16, 16); + }, + + onDragOver: function (aEvent) + { + if (disallowDrop(aEvent)) + return; + + var types = aEvent.dataTransfer.types; + if (types.includes("text/uri-list") || + types.includes("text/x-moz-url") || + types.includes("text/plain")) + aEvent.preventDefault(); + aEvent.stopPropagation(); + }, + + onDrop: function(aEvent) + { + if (disallowDrop(aEvent)) + return; + + var dt = aEvent.dataTransfer; + var url = dt.getData("URL"); + var name; + if (!url) { + url = dt.getData("text/x-moz-url") || dt.getData("text/plain"); + [url, name] = url.split("\n"); + } + if (url) { + let doc = dt.mozSourceNode ? dt.mozSourceNode.ownerDocument : document; + saveURL(url, name || url, null, true, true, null, doc); + } + } +}; + +function disallowDrop(aEvent) +{ + var dt = aEvent.dataTransfer; + var file = dt.mozGetDataAt("application/x-moz-file", 0); + // If this is a local file, Don't try to download it again. + return file && file instanceof Ci.nsIFile; +} diff --git a/comm/suite/components/downloads/content/downloadmanager.xul b/comm/suite/components/downloads/content/downloadmanager.xul new file mode 100644 index 0000000000..5633d284b6 --- /dev/null +++ b/comm/suite/components/downloads/content/downloadmanager.xul @@ -0,0 +1,452 @@ +<?xml version="1.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/. --> + +<?xml-stylesheet href="chrome://communicator/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://communicator/skin/downloads/downloadmanager.css" type="text/css"?> + +<?xul-overlay href="chrome://communicator/content/tasksOverlay.xul"?> +<?xul-overlay href="chrome://communicator/content/utilityOverlay.xul"?> + +<!DOCTYPE window [ +<!ENTITY % downloadsDTD SYSTEM "chrome://communicator/locale/downloads/downloadmanager.dtd"> +%downloadsDTD; +<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd"> +%globalDTD; +]> + +<window id="downloadManager" + title="&downloadManager.title;" statictitle="&downloadManager.title;" + onload="dmStartup();" onunload="dmShutdown();" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="500" height="400" screenX="10" screenY="10" + persist="width height screenX screenY sizemode" + toggletoolbar="true" + lightweightthemes="true" + lightweightthemesfooter="status-bar" + drawtitle="true" + windowtype="Download:Manager"> + + <script src="chrome://communicator/content/downloads/downloadmanager.js"/> + <script src="chrome://communicator/content/downloads/DownloadProgressListener.js"/> + <script src="chrome://communicator/content/downloads/treeView.js"/> + <script src="chrome://global/content/contentAreaUtils.js"/> + <script src="chrome://global/content/editMenuOverlay.js"/> + + <broadcaster id="Communicator:WorkMode"/> + + <stringbundleset id="stringbundleset"> + <stringbundle id="dmBundle" + src="chrome://communicator/locale/downloads/downloadmanager.properties"/> + </stringbundleset> + + <commandset id="dlWinCommands"> + <commandset id="tasksCommands"> + <!-- File Menu --> + <command id="cmd_close" oncommand="window.close()"/> + <!-- Search Box --> + <command id="cmd_search_focus" + oncommand="gSearchBox.focus();"/> + </commandset> + <commandset id="commandUpdate_Downloads" + commandupdater="true" + events="focus,tree-select" + oncommandupdate="dlTreeController.onCommandUpdate()"/> + + <commandset id="downloadCommands"> + <command id="cmd_play" + oncommand="goDoCommand('cmd_play');"/> + <command id="cmd_pause" + oncommand="goDoCommand('cmd_pause');"/> + <command id="cmd_resume" + oncommand="goDoCommand('cmd_resume');"/> + <command id="cmd_retry" + oncommand="goDoCommand('cmd_retry');"/> + <command id="cmd_cancel" + oncommand="goDoCommand('cmd_cancel');"/> + <command id="cmd_remove" + oncommand="goDoCommand('cmd_remove');"/> + <command id="cmd_stop" + oncommand="goDoCommand('cmd_stop');"/> + <command id="cmd_open" + oncommand="goDoCommand('cmd_open');"/> + <command id="cmd_show" + oncommand="goDoCommand('cmd_show');"/> + <command id="cmd_openReferrer" + oncommand="goDoCommand('cmd_openReferrer');"/> + <command id="cmd_copyLocation" + oncommand="goDoCommand('cmd_copyLocation');"/> + <command id="cmd_properties" + oncommand="goDoCommand('cmd_properties');"/> + <command id="cmd_clearList" + oncommand="goDoCommand('cmd_clearList');"/> + </commandset> + </commandset> + + <keyset id="tasksKeys"> + <!-- File Menu --> + <key id="key_open" + keycode="VK_RETURN" + command="cmd_open"/> + <key id="key_close"/> + <!-- Edit Menu --> + <key id="key_cut"/> + <key id="key_copy"/> + <key id="key_paste" + command="cmd_paste"/> + <key id="key_play" + key=" " + command="cmd_play"/> + <key id="key_delete"/> + <key id="key_delete2"/> + <key id="key_selectAll"/> + <!-- Search Box --> + <key id="key_search_focus" + command="cmd_search_focus" + key="&search.key;" + modifiers="accel"/> + </keyset> + + <popupset id="downloadPopupset"> + <menupopup id="downloadContext"> + <menuitem id="dlContext-pause" + label="&cmd.pause.label;" + accesskey="&cmd.pause.accesskey;" + command="cmd_pause"/> + <menuitem id="dlContext-resume" + label="&cmd.resume.label;" + accesskey="&cmd.resume.accesskey;" + command="cmd_resume"/> + <menuitem id="dlContext-retry" + label="&cmd.retry.label;" + accesskey="&cmd.retry.accesskey;" + command="cmd_retry"/> + <menuitem id="dlContext-cancel" + label="&cmd.cancel.label;" + accesskey="&cmd.cancel.accesskey;" + command="cmd_cancel"/> + <menuitem id="dlContext-remove" + label="&cmd.remove.label;" + accesskey="&cmd.remove.accesskey;" + command="cmd_remove"/> + <menuseparator/> + <menuitem id="dlContext-open" + label="&cmd.open.label;" + accesskey="&cmd.open.accesskey;" + command="cmd_open" + default="true"/> + <menuitem id="dlContext-show" + label="&cmd.show.label;" + accesskey="&cmd.show.accesskey;" + command="cmd_show"/> + <menuitem id="dlContext-openReferrer" + label="&cmd.goToDownloadPage.label;" + accesskey="&cmd.goToDownloadPage.accesskey;" + command="cmd_openReferrer"/> + <menuitem id="dlContext-copyLocation" + label="&cmd.copyDownloadLink.label;" + accesskey="&cmd.copyDownloadLink.accesskey;" + command="cmd_copyLocation"/> + <menuitem id="dlContext-properties" + label="&cmd.properties.label;" + accesskey="&cmd.properties.accesskey;" + command="cmd_properties"/> + <menuseparator/> + <menuitem id="context-selectall"/> + </menupopup> + </popupset> + + <vbox id="titlebar"/> + + <toolbox id="download-toolbox"> + <menubar id="download-menubar" + grippytooltiptext="&menuBar.tooltip;"> + <menu id="menu_File"> + <menupopup id="menu_FilePopup"> + <menuitem id="dlMenu_open" + label="&cmd.open.label;" + accesskey="&cmd.open.accesskey;" + key="key_open" + command="cmd_open"/> + <menuitem id="dlMenu_show" + label="&cmd.show.label;" + accesskey="&cmd.show.accesskey;" + command="cmd_show"/> + <menuitem id="dlMenu_openReferrer" + label="&cmd.goToDownloadPage.label;" + accesskey="&cmd.goToDownloadPage.accesskey;" + command="cmd_openReferrer"/> + <menuitem id="dlMenu_properties" + label="&cmd.properties.label;" + accesskey="&cmd.properties.accesskey;" + command="cmd_properties"/> + <menuseparator/> + <menuitem id="menu_close"/> + </menupopup> + </menu> + <menu id="menu_Edit"> + <menupopup id="menu_EditPopup"> + <menuitem id="dlMenu_pause" + label="&cmd.pause.label;" + accesskey="&cmd.pause.accesskey;" + command="cmd_pause"/> + <menuitem id="dlMenu_resume" + label="&cmd.resume.label;" + accesskey="&cmd.resume.accesskey;" + command="cmd_resume"/> + <menuitem id="dlMenu_retry" + label="&cmd.retry.label;" + accesskey="&cmd.retry.accesskey;" + command="cmd_retry"/> + <menuitem id="dlMenu_cancel" + label="&cmd.cancel.label;" + accesskey="&cmd.cancel.accesskey;" + command="cmd_cancel"/> + <menuseparator/> + <menuitem id="dlMenu_remove" + label="&cmd.remove.label;" + accesskey="&cmd.remove.accesskey;" + command="cmd_remove"/> + <menuitem id="dlMenu_copyLocation" + label="&cmd.copyDownloadLink.label;" + accesskey="&cmd.copyDownloadLink.accesskey;" + command="cmd_copyLocation"/> + <menuseparator/> + <menuitem id="dlMenu_clearList" + label="&cmd.clearList.label;" + accesskey="&cmd.clearList.accesskey;" + command="cmd_clearList"/> + <menuitem id="menu_selectAll"/> + </menupopup> + </menu> + <menu id="menu_View"> + <menupopup id="menu_ViewPopup"> + <menu id="menu_ViewColumns" + label="&view.columns.label;" + accesskey="&view.columns.accesskey;"> + <menupopup onpopupshowing="onUpdateViewColumns(this.firstChild);" + oncommand="toggleColumn(event.target);"> + <menuitem id="menu_ToggleName" type="checkbox" disabled="true" + label="&col.name.label;" + accesskey="&col.name.accesskey;"/> + <menuitem id="menu_ToggleStatus" type="checkbox" + label="&col.status.label;" + accesskey="&col.status.accesskey;"/> + <menuitem id="menu_ToggleActionPlay" type="checkbox" + label="&col.actionPlay.label;" + accesskey="&col.actionPlay.accesskey;"/> + <menuitem id="menu_ToggleActionStop" type="checkbox" + label="&col.actionStop.label;" + accesskey="&col.actionStop.accesskey;"/> + <menuitem id="menu_ToggleProgress" type="checkbox" + label="&col.progress.label;" + accesskey="&col.progress.accesskey;"/> + <menuitem id="menu_ToggleTimeRemaining" type="checkbox" + label="&col.timeremaining.label;" + accesskey="&col.timeremaining.accesskey;"/> + <menuitem id="menu_ToggleTransferred" type="checkbox" + label="&col.transferred.label;" + accesskey="&col.transferred.accesskey;"/> + <menuitem id="menu_ToggleTransferRate" type="checkbox" + label="&col.transferrate.label;" + accesskey="&col.transferrate.accesskey;"/> + <menuitem id="menu_ToggleTimeElapsed" type="checkbox" + label="&col.timeelapsed.label;" + accesskey="&col.timeelapsed.accesskey;"/> + <menuitem id="menu_ToggleStartTime" type="checkbox" + label="&col.starttime.label;" + accesskey="&col.starttime.accesskey;"/> + <menuitem id="menu_ToggleEndTime" type="checkbox" + label="&col.endtime.label;" + accesskey="&col.endtime.accesskey;"/> + <menuitem id="menu_ToggleProgressPercent" type="checkbox" + label="&col.progresstext.label;" + accesskey="&col.progresstext.accesskey;"/> + <menuitem id="menu_ToggleSource" type="checkbox" + label="&col.source.label;" + accesskey="&col.source.accesskey;"/> + </menupopup> + </menu> + <menu id="menu_ViewSortBy" label="&view.sortBy.label;" + accesskey="&view.sortBy.accesskey;"> + <menupopup onpopupshowing="onUpdateViewSort(this.firstChild);" + oncommand="sortDownloads(event.target);"> + <menuitem id="menu_Unsorted" type="radio" name="columns" + label="&view.unsorted.label;" + accesskey="&view.unsorted.accesskey;"/> + <menuseparator/> + <menuitem id="menu_SortByName" type="radio" name="columns" + label="&col.name.label;" + accesskey="&col.name.accesskey;"/> + <menuitem id="menu_SortByStatus" type="radio" name="columns" + label="&col.status.label;" + accesskey="&col.status.accesskey;"/> + <menuitem id="menu_SortByProgress" type="radio" name="columns" + label="&col.progress.label;" + accesskey="&col.progress.accesskey;"/> + <menuitem id="menu_SortByTimeRemaining" type="radio" name="columns" + label="&col.timeremaining.label;" + accesskey="&col.timeremaining.accesskey;"/> + <menuitem id="menu_SortByTransferred" type="radio" name="columns" + label="&col.transferred.label;" + accesskey="&col.transferred.accesskey;"/> + <menuitem id="menu_SortByTransferRate" type="radio" name="columns" + label="&col.transferrate.label;" + accesskey="&col.transferrate.accesskey;"/> + <menuitem id="menu_SortByTimeElapsed" type="radio" name="columns" + label="&col.timeelapsed.label;" + accesskey="&col.timeelapsed.accesskey;"/> + <menuitem id="menu_SortByStartTime" type="radio" name="columns" + label="&col.starttime.label;" + accesskey="&col.starttime.accesskey;"/> + <menuitem id="menu_SortByEndTime" type="radio" name="columns" + label="&col.endtime.label;" + accesskey="&col.endtime.accesskey;"/> + <menuitem id="menu_SortByProgressPercent" type="radio" name="columns" + label="&col.progresstext.label;" + accesskey="&col.progresstext.accesskey;"/> + <menuitem id="menu_SortBySource" type="radio" name="columns" + label="&col.source.label;" + accesskey="&col.source.accesskey;"/> + <menuseparator/> + <menuitem id="menu_SortAscending" type="radio" name="direction" + label="&view.sortAscending.label;" + accesskey="&view.sortAscending.accesskey;"/> + <menuitem id="menu_SortDescending" type="radio" name="direction" + label="&view.sortDescending.label;" + accesskey="&view.sortDescending.accesskey;"/> + </menupopup> + </menu> + </menupopup> + </menu> + <menu id="tasksMenu"> + <menupopup id="taskPopup"> + <menuitem id="dlMenu_find" + label="&search.label;" + accesskey="&search.accesskey;" + hidden="true" + command="cmd_search_focus" + key="key_search_focus"/> + <menuseparator hidden="true"/> + </menupopup> + </menu> + <menu id="windowMenu"/> + <menu id="menu_Help"/> + </menubar> + <toolbar class="chromeclass-toolbar" + id="downloadToolbar" + align="center" + grippytooltiptext="&searchBar.tooltip;"> + <textbox id="search-box" + clickSelectsAll="true" + type="search" + hidden="true" + aria-controls="downloadTree" + class="compact" + placeholder="&search.placeholder;" + oncommand="searchDownloads(this.value);"/> + <spacer flex="1"/> + <button id="clearListButton" command="cmd_clearList" + label="&cmd.clearList.label;" + accesskey="&cmd.clearList.accesskey;" + tooltiptext="&cmd.clearList.tooltip;"/> + </toolbar> + </toolbox> + + <tree id="downloadTree" + flex="1" type="downloads" + class="plain" + context="downloadContext" + enableColumnDrag="true" + onselect="onTreeSelect(event);"> + <treecols context="" onclick="sortDownloads(event.target)"> + <treecol id="Name" + label="&col.name.label;" + tooltiptext="&col.name.tooltip;" + flex="3" + persist="width hidden ordinal sortActive sortDirection"/> + <splitter class="tree-splitter"/> + <treecol id="Status" hidden="true" + label="&col.status.label;" + tooltiptext="&col.status.tooltip;" + flex="1" + persist="width hidden ordinal sortActive sortDirection"/> + <splitter class="tree-splitter"/> + <treecol id="ActionPlay" cycler="true" + label="&col.actionPlay.label;" + tooltiptext="&col.actionPlay.tooltip;" + class="treecol-image" fixed="true" + persist="hidden ordinal"/> + <splitter class="tree-splitter"/> + <treecol id="ActionStop" cycler="true" + label="&col.actionStop.label;" + tooltiptext="&col.actionStop.tooltip;" + class="treecol-image" fixed="true" + persist="hidden ordinal"/> + <splitter class="tree-splitter"/> + <treecol id="Progress" type="progressmeter" + label="&col.progress.label;" + tooltiptext="&col.progress.tooltip;" + flex="3" + persist="width hidden ordinal sortActive sortDirection"/> + <splitter class="tree-splitter"/> + <treecol id="ProgressPercent" hidden="true" + label="&col.progresstext.label;" + tooltiptext="&col.progresstext.tooltip;" + flex="1" + persist="width hidden ordinal sortActive sortDirection"/> + <splitter class="tree-splitter"/> + <treecol id="TimeRemaining" + label="&col.timeremaining.label;" + tooltiptext="&col.timeremaining.tooltip;" + flex="1" + persist="width hidden ordinal sortActive sortDirection"/> + <splitter class="tree-splitter"/> + <treecol id="Transferred" + label="&col.transferred.label;" + tooltiptext="&col.transferred.tooltip;" + flex="1" + persist="width hidden ordinal sortActive sortDirection"/> + <splitter class="tree-splitter"/> + <treecol id="TransferRate" + label="&col.transferrate.label;" + tooltiptext="&col.transferrate.tooltip;" + flex="1" + persist="width hidden ordinal sortActive sortDirection"/> + <splitter class="tree-splitter"/> + <treecol id="TimeElapsed" hidden="true" + label="&col.timeelapsed.label;" + tooltiptext="&col.timeelapsed.tooltip;" + flex="1" + persist="width hidden ordinal sortActive sortDirection"/> + <splitter class="tree-splitter"/> + <treecol id="StartTime" hidden="true" + label="&col.starttime.label;" + tooltiptext="&col.starttime.tooltip;" + flex="1" + persist="width hidden ordinal sortActive sortDirection"/> + <splitter class="tree-splitter"/> + <treecol id="EndTime" hidden="true" + label="&col.endtime.label;" + tooltiptext="&col.endtime.tooltip;" + flex="1" + persist="width hidden ordinal sortActive sortDirection"/> + <splitter class="tree-splitter"/> + <treecol id="Source" hidden="true" + label="&col.source.label;" + tooltiptext="&col.source.tooltip;" + flex="1" + persist="width hidden ordinal sortActive sortDirection"/> + </treecols> + <treechildren ondblclick="goDoCommand('cmd_open');" + ondragstart="gDownloadDNDObserver.onDragStart(event);" + ondragover="gDownloadDNDObserver.onDragOver(event);" + ondrop="gDownloadDNDObserver.onDrop(event);"/> + </tree> + <statusbar id="status-bar" class="chromeclass-status"> + <statusbarpanel id="statusbar-display" flex="1"/> + <statusbarpanel class="statusbarpanel-iconic" id="offline-status"/> + </statusbar> + +</window> diff --git a/comm/suite/components/downloads/content/progressDialog.js b/comm/suite/components/downloads/content/progressDialog.js new file mode 100644 index 0000000000..dae93f7fe9 --- /dev/null +++ b/comm/suite/components/downloads/content/progressDialog.js @@ -0,0 +1,240 @@ +/* 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/. */ + +var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); +var {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetters(this, { + DownloadUtils: "resource://gre/modules/DownloadUtils.jsm", + DownloadsCommon: "resource:///modules/DownloadsCommon.jsm", +}); + +var gDownload; +var gDownloadBundle; + +var gDlList; +var gDlStatus; +var gDlListener; +var gDlSize; +var gTimeLeft; +var gProgressMeter; +var gProgressText; +var gCloseWhenDone; + +function progressStartup() { + gDownload = window.arguments[0].wrappedJSObject; + Downloads.getList(gDownload.source.isPrivate ? Downloads.PRIVATE : Downloads.PUBLIC).then(progressAsyncStartup); +} + +function progressAsyncStartup(aList) { + gDlList = aList; + + // cache elements to save .getElementById() calls + gDownloadBundle = document.getElementById("dmBundle"); + gDlStatus = document.getElementById("dlStatus"); + gDlSize = document.getElementById("dlSize"); + gTimeLeft = document.getElementById("timeLeft"); + gProgressMeter = document.getElementById("progressMeter"); + gProgressText = document.getElementById("progressText"); + gCloseWhenDone = document.getElementById("closeWhenDone"); + + // Insert as first controller on the whole window + window.controllers.insertControllerAt(0, ProgressDlgController); + + if (gDownload.isPrivate) + gCloseWhenDone.hidden = true; + else + gCloseWhenDone.checked = Services.prefs.getBoolPref("browser.download.progress.closeWhenDone"); + + if (gDownload.succeeded) { + if (gCloseWhenDone.checked && !window.arguments[1]) + window.close(); + } + + var fName = document.getElementById("fileName"); + var fSource = document.getElementById("fileSource"); + fName.label = gDownload.displayName; + fName.tooltipText = gDownload.target.path; + var uri = Services.io.newURI(gDownload.source.url); + var fromString; + try { + fromString = uri.host; + } + catch (e) { } + if (!fromString) + fromString = uri.prePath; + fSource.label = gDownloadBundle.getFormattedString("fromSource", [fromString]); + fSource.tooltipText = gDownload.source.url; + + // The DlProgressListener handles progress notifications. + gDlListener = new DlProgressListener(); + gDlList.addView(gDlListener); + + updateDownload(); + updateButtons(); + window.updateCommands("dlstate-change"); +} + +function progressShutdown() { + gDlList.removeView(gDlListener); + window.controllers.removeController(ProgressDlgController); + if (!gCloseWhenDone.hidden) + Services.prefs.setBoolPref("browser.download.progress.closeWhenDone", + gCloseWhenDone.checked); +} + +function updateDownload() { + if (gDownload.hasProgress) { + gProgressText.value = gDownloadBundle.getFormattedString("percentFormat", + [gDownload.progress]); + gProgressText.hidden = false; + gProgressMeter.value = gDownload.progress; + gProgressMeter.mode = "determined"; + } else { + gProgressText.hidden = true; + gProgressMeter.mode = "undetermined"; + } + if (gDownload.stopped) { + gProgressMeter.style.opacity = 0.5; + } else { + gProgressMeter.style.opacity = 1; + } + // Update window title + let statusString = DownloadsCommon.stateOfDownloadText(gDownload); + + if (gDownload.hasProgress) { + document.title = gDownloadBundle.getFormattedString("progressTitlePercent", + [gDownload.progress, + gDownload.displayName, + statusString]); + } + else { + document.title = gDownloadBundle.getFormattedString("progressTitle", + [gDownload.displayName, + statusString]); + } + + // download size / transferred bytes + gDlSize.value = DownloadsCommon.getTransferredBytes(gDownload); + + // time remaining + gTimeLeft.value = DownloadsCommon.getTimeRemaining(gDownload); + + // download status + gDlStatus.value = statusString; + +} + +function updateButtons() { + document.getElementById("pauseButton").hidden = !ProgressDlgController.isCommandEnabled("cmd_pause"); + document.getElementById("resumeButton").hidden = !ProgressDlgController.isCommandEnabled("cmd_resume"); + document.getElementById("retryButton").hidden = !ProgressDlgController.isCommandEnabled("cmd_retry"); + document.getElementById("cancelButton").hidden = !ProgressDlgController.isCommandEnabled("cmd_cancel"); +} + +/** + * DlProgressListener "class" is used to help update download items shown + * in the progress dialog such as displaying amount transferred, transfer + * rate, and time left for the download. + * + * This class implements the downloadProgressListener interface. + */ +function DlProgressListener() {} + +DlProgressListener.prototype = { + onDownloadChanged: function(aDownload) { + if (aDownload == gDownload) { + if (gCloseWhenDone.checked && aDownload.succeeded) { + window.close(); + } + updateDownload(); + updateButtons(); + window.updateCommands("dlstate-change"); + } + }, + + onDownloadRemoved: function(aDownload) { + if (aDownload == gDownload) + window.close(); + } +}; + +var ProgressDlgController = { + supportsCommand: function(aCommand) { + switch (aCommand) { + case "cmd_pause": + case "cmd_resume": + case "cmd_retry": + case "cmd_cancel": + case "cmd_open": + case "cmd_show": + case "cmd_openReferrer": + case "cmd_copyLocation": + return true; + } + return false; + }, + + isCommandEnabled: function(aCommand) { + switch (aCommand) { + case "cmd_pause": + return !gDownload.stopped && gDownload.hasPartialData; + case "cmd_resume": + return gDownload.stopped && gDownload.hasPartialData; + case "cmd_open": + return gDownload.succeeded && gDownload.target.exists; + case "cmd_show": + return gDownload.target.exists; + case "cmd_cancel": + return !gDownload.stopped || gDownload.hasPartialData; + case "cmd_retry": + return !gDownload.succeeded && gDownload.stopped && !gDownload.hasPartialData; + case "cmd_openReferrer": + return !!gDownload.source.referrer; + case "cmd_copyLocation": + return true; + default: + return false; + } + }, + + doCommand: function(aCommand) { + switch (aCommand) { + case "cmd_pause": + gDownload.cancel(); + break; + case "cmd_resume": + case "cmd_retry": + gDownload.start(); + break; + case "cmd_cancel": + cancelDownload(gDownload); + break; + case "cmd_open": + openDownload(gDownload); + break; + case "cmd_show": + showDownload(gDownload); + break; + case "cmd_openReferrer": + openUILink(gDownload.source.referrer); + break; + case "cmd_copyLocation": + var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"] + .getService(Ci.nsIClipboardHelper); + clipboard.copyString(gDownload.source.url); + break; + } + }, + + onEvent: function(aEvent) { + }, + + onCommandUpdate: function() { + var cmds = ["cmd_pause", "cmd_resume", "cmd_retry", "cmd_cancel", + "cmd_open", "cmd_show", "cmd_openReferrer", "cmd_copyLocation"]; + for (let command in cmds) + goUpdateCommand(cmds[command]); + } +}; diff --git a/comm/suite/components/downloads/content/progressDialog.xul b/comm/suite/components/downloads/content/progressDialog.xul new file mode 100644 index 0000000000..cb8178f6fd --- /dev/null +++ b/comm/suite/components/downloads/content/progressDialog.xul @@ -0,0 +1,108 @@ +<?xml version="1.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/. --> + +<?xml-stylesheet href="chrome://communicator/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://communicator/skin/downloads/downloadmanager.css" type="text/css"?> + +<?xul-overlay href="chrome://communicator/content/utilityOverlay.xul"?> + +<!DOCTYPE window SYSTEM "chrome://communicator/locale/downloads/progressDialog.dtd"> + +<window id="dlProgressWindow" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + onload="progressStartup();" onunload="progressShutdown();" + title="&progress.title;" + persist="screenX screenY" + style="width:40em;"> + + <script src="chrome://communicator/content/downloads/downloadmanager.js"/> + <script src="chrome://communicator/content/downloads/progressDialog.js"/> + + <stringbundleset id="stringbundleset"> + <stringbundle id="dmBundle" + src="chrome://communicator/locale/downloads/downloadmanager.properties"/> + </stringbundleset> + + <commandset id="dlProgressCommands"> + <commandset id="commandUpdate_DlProgress" + commandupdater="true" + events="focus,dlstate-change" + oncommandupdate="ProgressDlgController.onCommandUpdate();"/> + + <commandset id="downloadCommands"> + <command id="cmd_pause" + oncommand="goDoCommand('cmd_pause');"/> + <command id="cmd_resume" + oncommand="goDoCommand('cmd_resume');"/> + <command id="cmd_retry" + oncommand="goDoCommand('cmd_retry');"/> + <command id="cmd_cancel" + oncommand="goDoCommand('cmd_cancel');"/> + <command id="cmd_open" + oncommand="goDoCommand('cmd_open');"/> + <command id="cmd_show" + oncommand="goDoCommand('cmd_show');"/> + <command id="cmd_openReferrer" + oncommand="goDoCommand('cmd_openReferrer');"/> + <command id="cmd_copyLocation" + oncommand="goDoCommand('cmd_copyLocation');"/> + <command id="cmd_close" oncommand="window.close();"/> + </commandset> + </commandset> + + <keyset> + <key key="&closeWindow.key;" modifiers="accel" command="cmd_close"/> + <key keycode="VK_ESCAPE" command="cmd_close"/> + <key key="." modifiers="meta" command="cmd_close"/> + </keyset> + + <hbox align="end"> + <vbox flex="1" align="start"> + <button id="fileName" crop="center" label="" type="menu"> + <menupopup id="file-popup"> + <menuitem id="dlContext-open" + label="&cmd.open.label;" + accesskey="&cmd.open.accesskey;" + command="cmd_open"/> + <menuitem id="dlContext-show" + label="&cmd.show.label;" + accesskey="&cmd.show.accesskey;" + command="cmd_show"/> + </menupopup> + </button> + <button id="fileSource" crop="center" label="" type="menu"> + <menupopup id="source-popup"> + <menuitem id="dlContext-openReferrer" + label="&cmd.goToDownloadPage.label;" + accesskey="&cmd.goToDownloadPage.accesskey;" + command="cmd_openReferrer"/> + <menuitem id="dlContext-copyLocation" + label="&cmd.copyDownloadLink.label;" + accesskey="&cmd.copyDownloadLink.accesskey;" + command="cmd_copyLocation"/> + </menupopup> + </button> + <label id="dlSize" value=""/> + <label id="timeLeft" value=""/> + <label id="dlStatus" value=""/> + </vbox> + <button id="pauseButton" class="mini-button" + command="cmd_pause" tooltiptext="&cmd.pause.tooltip;"/> + <button id="resumeButton" class="mini-button" + command="cmd_resume" tooltiptext="&cmd.resume.tooltip;"/> + <button id="retryButton" class="mini-button" + command="cmd_retry" tooltiptext="&cmd.retry.tooltip;"/> + <button id="cancelButton" class="mini-button" + command="cmd_cancel" tooltiptext="&cmd.cancel.tooltip;"/> + </hbox> + <hbox id="progressBox"> + <progressmeter id="progressMeter" mode="determined" flex="1"/> + <label id="progressText" value=""/> + </hbox> + <checkbox id="closeWhenDone" + label="&closeWhenDone.label;" + accesskey="&closeWhenDone.accesskey;"/> +</window> diff --git a/comm/suite/components/downloads/content/treeView.js b/comm/suite/components/downloads/content/treeView.js new file mode 100644 index 0000000000..03e1c48a11 --- /dev/null +++ b/comm/suite/components/downloads/content/treeView.js @@ -0,0 +1,483 @@ +/* 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/. */ + +var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); +var {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetters(this, { + DownloadUtils: "resource://gre/modules/DownloadUtils.jsm", + DownloadsCommon: "resource:///modules/DownloadsCommon.jsm", + DownloadHistory: "resource://gre/modules/DownloadHistory.jsm", +}); + +function DownloadTreeView() { + this._dlList = []; + this._searchTerms = []; + this.dateTimeFormatter = + new Services.intl.DateTimeFormat(undefined, + {dateStyle: "short", + timeStyle: "long"}); +} + +DownloadTreeView.prototype = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsITreeView]), + + // ***** nsITreeView attributes and methods ***** + get rowCount() { + return this._dlList.length; + }, + + selection: null, + + getRowProperties: function(aRow) { + let dl = this._dlList[aRow]; + // (in)active + let properties = dl.isActive ? "active": "inactive"; + // resumable + if (dl.hasPartialData) + properties += " resumable"; + + // Download states + let state = DownloadsCommon.stateOfDownload(dl); + switch (state) { + case DownloadsCommon.DOWNLOAD_PAUSED: + properties += " paused"; + break; + case DownloadsCommon.DOWNLOAD_DOWNLOADING: + properties += " downloading"; + break; + case DownloadsCommon.DOWNLOAD_FINISHED: + properties += " finished"; + break; + case DownloadsCommon.DOWNLOAD_FAILED: + properties += " failed"; + break; + case DownloadsCommon.DOWNLOAD_CANCELED: + properties += " canceled"; + break; + case DownloadsCommon.DOWNLOAD_BLOCKED_PARENTAL: // Parental Controls + case DownloadsCommon.DOWNLOAD_BLOCKED_POLICY: // Security Zone Policy + case DownloadsCommon.DOWNLOAD_DIRTY: // possible virus/spyware + properties += " blocked"; + break; + } + + return properties; + }, + getCellProperties: function(aRow, aColumn) { + // Append all row properties to the cell + return this.getRowProperties(aRow); + }, + getColumnProperties: function(aColumn) { return ""; }, + isContainer: function(aRow) { return false; }, + isContainerOpen: function(aRow) { return false; }, + isContainerEmpty: function(aRow) { return false; }, + isSeparator: function(aRow) { return false; }, + isSorted: function() { return false; }, + canDrop: function(aIdx, aOrientation) { return false; }, + drop: function(aIdx, aOrientation) { }, + getParentIndex: function(aRow) { return -1; }, + hasNextSibling: function(aRow, aAfterIdx) { return false; }, + getLevel: function(aRow) { return 0; }, + + getImageSrc: function(aRow, aColumn) { + if (aColumn.id == "Name") + return "moz-icon://" + this._dlList[aRow].target.path + "?size=16"; + return ""; + }, + + getProgressMode: function(aRow, aColumn) { + if (aColumn.id == "Progress") + return this._dlList[aRow].progressMode; + return Ci.nsITreeView.PROGRESS_NONE; + }, + + getCellValue: function(aRow, aColumn) { + if (aColumn.id == "Progress") + return this._dlList[aRow].progress; + return ""; + }, + + getCellText: function(aRow, aColumn) { + let dl = this._dlList[aRow]; + switch (aColumn.id) { + case "Name": + return dl.displayName; + case "Status": + return DownloadsCommon.stateOfDownloadText(dl); + case "Progress": + if (dl.isActive) + return dl.progress; + return DownloadsCommon.stateOfDownloadText(dl); + case "ProgressPercent": + return dl.succeeded ? 100 : dl.progress; + case "TimeRemaining": + return DownloadsCommon.getTimeRemaining(dl); + case "Transferred": + return DownloadsCommon.getTransferredBytes(dl); + case "TransferRate": + let state = DownloadsCommon.stateOfDownload(dl); + switch (state) { + case DownloadsCommon.DOWNLOAD_DOWNLOADING: + let [rate, unit] = DownloadUtils.convertByteUnits(dl.speed); + return this._dlbundle.getFormattedString("speedFormat", [rate, unit]); + case DownloadsCommon.DOWNLOAD_PAUSED: + return this._dlbundle.getString("statePaused"); + case DownloadsCommon.DOWNLOAD_NOTSTARTED: + return this._dlbundle.getString("stateNotStarted"); + } + return ""; + case "TimeElapsed": + // With no end time persisted in the downloads backend this is + // utterly useless unless the download is progressing. + if (DownloadsCommon.stateOfDownload(dl) == + DownloadsCommon.DOWNLOAD_DOWNLOADING && dl.startTime) { + let seconds = (Date.now() - dl.startTime) / 1000; + let [time1, unit1, time2, unit2] = + DownloadUtils.convertTimeUnits(seconds); + if (seconds < 3600 || time2 == 0) { + return this._dlbundle.getFormattedString("timeSingle", [time1, unit1]); + } + return this._dlbundle.getFormattedString("timeDouble", [time1, unit1, time2, unit2]); + } + return ""; + case "StartTime": + if (dl.startTime) { + return this.dateTimeFormatter.format(dl.startTime); + } + return ""; + case "EndTime": + // This might end with an exception if it is an unsupported uri + // scheme. + let metaData = DownloadHistory.getPlacesMetaDataFor(dl.source.url); + + if (metaData.endTime) { + return this.dateTimeFormatter.format(metaData.endTime); + } + return ""; + case "Source": + return dl.source.url; + } + return ""; + }, + + setTree: function(aTree) { + this._tree = aTree; + this._dlbundle = document.getElementById("dmBundle"); + }, + + toggleOpenState: function(aRow) { }, + cycleHeader: function(aColumn) { }, + selectionChanged: function() { }, + cycleCell: function(aRow, aColumn) { + var dl = this._dlList[aRow]; + switch (aColumn.id) { + case "ActionPlay": + if (dl.stopped) { + if (!dl.succeeded) + dl.start(); + } else { + if (dl.hasPartialData) + dl.cancel(); + } + break; + case "ActionStop": + if (dl.isActive) + cancelDownload(dl); + else + removeDownload(dl); + break; + } + }, + isEditable: function(aRow, aColumn) { return false; }, + isSelectable: function(aRow, aColumn) { return false; }, + setCellValue: function(aRow, aColumn, aText) { }, + setCellText: function(aRow, aColumn, aText) { }, + + // ***** local public methods ***** + + addDownload: function(aDownload) { + aDownload.progressMode = Ci.nsITreeView.PROGRESS_NONE; + aDownload.lastSec = Infinity; + let state = DownloadsCommon.stateOfDownload(aDownload); + switch (state) { + case DownloadsCommon.DOWNLOAD_DOWNLOADING: + aDownload.endTime = Date.now(); + // At this point, we know if we are an indeterminate download or not. + aDownload.progressMode = aDownload.hasProgress ? + Ci.nsITreeView.PROGRESS_UNDETERMINED : + Ci.nsITreeView.PROGRESS_NORMAL; + case DownloadsCommon.DOWNLOAD_NOTSTARTED: + case DownloadsCommon.DOWNLOAD_PAUSED: + aDownload.isActive = 1; + break; + default: + aDownload.isActive = 0; + break; + } + + // prepend in natural sorting + aDownload.listIndex = this._lastListIndex--; + + // Prepend data to the download list + this._dlList.unshift(aDownload); + + // Tell the tree we added 1 row at index 0 + this._tree.rowCountChanged(0, 1); + + // Data has changed, so re-sorting might be needed + this.sortView("", "", aDownload, 0); + + window.updateCommands("tree-select"); + }, + + updateDownload: function(aDownload) { + var row = this._dlList.indexOf(aDownload); + if (row == -1) { + // No download row found to update, but as it's obviously going on, + // add it to the list now (can happen with very fast, e.g. local dls) + this.onDownloadAdded(aDownload); + return; + } + let state = DownloadsCommon.stateOfDownload(aDownload); + switch (state) { + case DownloadsCommon.DOWNLOAD_DOWNLOADING: + // At this point, we know if we are an indeterminate download or not. + aDownload.progressMode = aDownload.hasProgress ? + Ci.nsITreeView.PROGRESS_NORMAL : Ci.nsITreeView.PROGRESS_UNDETERMINED; + case DownloadsCommon.DOWNLOAD_NOTSTARTED: + case DownloadsCommon.DOWNLOAD_PAUSED: + aDownload.isActive = 1; + break; + default: + aDownload.isActive = 0; + aDownload.progressMode = Ci.nsITreeView.PROGRESS_NONE; + // This preference may not be set, so defaulting to two. + var flashCount = 2; + try { + flashCount = Services.prefs.getIntPref(PREF_FLASH_COUNT); + } catch (e) { } + getAttentionWithCycleCount(flashCount); + break; + } + + // Repaint the tree row + this._tree.invalidateRow(row); + + // Data has changed, so re-sorting might be needed + this.sortView("", "", aDownload, row); + + window.updateCommands("tree-select"); + }, + + removeDownload: function(aDownload) { + var row = this._dlList.indexOf(aDownload); + // Make sure we have an item to remove + if (row == -1) + return; + + var index = this.selection.currentIndex; + var wasSingleSelection = this.selection.count == 1; + + // Remove data from the download list + this._dlList.splice(row, 1); + + // Tell the tree we removed 1 row at the given row index + this._tree.rowCountChanged(row, -1); + + // Update selection if only removed download was selected + if (wasSingleSelection && this.selection.count == 0) { + index = Math.min(index, this.rowCount - 1); + if (index >= 0) + this.selection.select(index); + } + + window.updateCommands("tree-select"); + }, + + searchView: function(aInput) { + // Stringify the previous search + var prevSearch = this._searchTerms.join(" "); + + // Array of space-separated lower-case search terms + this._searchTerms = aInput.trim().toLowerCase().split(/\s+/); + + // Don't rebuild the download list if the search didn't change + if (this._searchTerms.join(" ") == prevSearch) + return; + + // Cache the current selection + this._cacheSelection(); + + // Rebuild the tree with set search terms + //this.initTree(); + + // Restore the selection + this._restoreSelection(); + }, + + sortView: function(aColumnID, aDirection, aDownload, aRow) { + var sortAscending = aDirection != "descending"; + + if (aColumnID == "" && aDirection == "") { + // Re-sort in already selected/cached order + var sortedColumn = this._tree.columns.getSortedColumn(); + if (sortedColumn) { + aColumnID = sortedColumn.id; + sortAscending = sortedColumn.element.getAttribute("sortDirection") != "descending"; + } + // no need for else, use default case of switch, sortAscending is true + } + + // Compare function for two _dlList items + var compfunc = function(a, b) { + // Active downloads are always at the beginning + // i.e. 0 for .isActive is larger (!) than 1 + if (a.isActive < b.isActive) + return 1; + if (a.isActive > b.isActive) + return -1; + // Same active/inactive state, sort normally + var comp_a = null; + var comp_b = null; + switch (aColumnID) { + case "Name": + comp_a = a.displayName.toLowerCase(); + comp_b = b.displayName.toLowerCase(); + break; + case "Status": + comp_a = DownloadsCommon.stateOfDownload(a); + comp_b = DownloadsCommon.stateOfDownload(b); + break; + case "Progress": + case "ProgressPercent": + // Use original sorting for inactive entries + // Use only one isActive to be sure we do the same + comp_a = a.isActive ? a.progress : a.listIndex; + comp_b = a.isActive ? b.progress : b.listIndex; + break; + case "TimeRemaining": + comp_a = a.isActive ? a.lastSec : a.listIndex; + comp_b = a.isActive ? b.lastSec : b.listIndex; + break; + case "Transferred": + comp_a = a.currentBytes; + comp_b = b.currentBytes; + break; + case "TransferRate": + comp_a = a.isActive ? a.speed : a.listIndex; + comp_b = a.isActive ? b.speed : b.listIndex; + break; + case "TimeElapsed": + comp_a = (a.endTime && a.startTime && (a.endTime > a.startTime)) + ? a.endTime - a.startTime + : 0; + comp_b = (b.endTime && b.startTime && (b.endTime > b.startTime)) + ? b.endTime - b.startTime + : 0; + break; + case "StartTime": + comp_a = a.startTime; + comp_b = b.startTime; + break; + case "EndTime": + comp_a = a.endTime; + comp_b = b.endTime; + break; + case "Source": + comp_a = a.source.url; + comp_b = b.source.url; + break; + case "unsorted": // Special case for reverting to original order + default: + comp_a = a.listIndex; + comp_b = b.listIndex; + } + if (comp_a > comp_b) + return sortAscending ? 1 : -1; + if (comp_a < comp_b) + return sortAscending ? -1 : 1; + return 0; + } + + // Cache the current selection + this._cacheSelection(); + + // Do the actual sorting of the array + this._dlList.sort(compfunc); + + var row = this._dlList.indexOf(aDownload); + if (row == -1) + // Repaint the tree + this._tree.invalidate(); + else if (row == aRow) + // No effect + this._selectionCache = null; + else if (row < aRow) + // Download moved up from aRow to row + this._tree.invalidateRange(row, aRow); + else + // Download moved down from aRow to row + this._tree.invalidateRange(aRow, row) + + // Restore the selection + this._restoreSelection(); + }, + + getRowData: function(aRow) { + return this._dlList[aRow]; + }, + + getActiveDownloads: function() { + return this._dlList.filter(dld => !dld.stopped); + }, + + // ***** local member vars ***** + + _tree: null, + _dlBundle: null, + _lastListIndex: 0, + _selectionCache: null, + + // ***** local helper functions ***** + + // Cache IDs of selected downloads for later restoration + _cacheSelection: function() { + // Abort if there's already something cached + if (this._selectionCache) + return; + + this._selectionCache = []; + if (this.selection.count < 1) + return; + + // Walk all selected rows and cache their download IDs + var start = {}; + var end = {}; + var numRanges = this.selection.getRangeCount(); + for (let rg = 0; rg < numRanges; rg++){ + this.selection.getRangeAt(rg, start, end); + for (let row = start.value; row <= end.value; row++){ + this._selectionCache.push(this._dlList[row]); + } + } + }, + + // Restore selection from cached IDs (as possible) + _restoreSelection: function() { + // Abort if the cache is empty + if (!this._selectionCache) + return; + + this.selection.clearSelection(); + for (let dl of this._selectionCache) { + // Find out what row this is now and if possible, add it to the selection + var row = this._dlList.indexOf(dl); + if (row != -1) + this.selection.rangedSelect(row, row, true); + } + // Work done, clear the cache + this._selectionCache = null; + }, +}; diff --git a/comm/suite/components/downloads/content/uploadProgress.js b/comm/suite/components/downloads/content/uploadProgress.js new file mode 100644 index 0000000000..0cd4d27817 --- /dev/null +++ b/comm/suite/components/downloads/content/uploadProgress.js @@ -0,0 +1,189 @@ +/* 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/. */ + +var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); +var {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); +const {DownloadUtils} = ChromeUtils.import("resource://gre/modules/DownloadUtils.jsm"); + +const kInterval = 750; // Default to .75 seconds. + +var gPersist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] + .createInstance(Ci.nsIWebBrowserPersist); +var gSource = window.arguments[0].QueryInterface(Ci.nsIFileURL); +var gTarget = window.arguments[1].QueryInterface(Ci.nsIURL); +var gFileName = gSource.file.leafName; +var gFileSize = gSource.file.fileSize; +var gPercent = -1; +var gStartTime; +var gLastUpdate; +var gLastSeconds; +var gBundle; +var gStatus; +var gTime; +var gSize; +var gProgress; +var gMeter; + +function onLoad() +{ + gBundle = document.getElementById("dmBundle"); + gStatus = document.getElementById("status"); + gTime = document.getElementById("timeElapsed"); + gSize = document.getElementById("size"); + gProgress = document.getElementById("progressText"); + gMeter = document.getElementById("progress"); + var status = gBundle.getString("stateNotStarted"); + document.title = + gBundle.getFormattedString("progressTitle", [gFileName, status]); + gStatus.value = status; + gTime.value = gBundle.getFormattedString("timeSingle", + DownloadUtils.convertTimeUnits(0)); + gSize.value = DownloadUtils.getTransferTotal(0, gFileSize); + document.getElementById("target").value = + gBundle.getFormattedString("toTarget", [gTarget.resolve(".")]); + document.getElementById("source").value = + gBundle.getFormattedString("fromSource", [gSource.file.leafName]); + gPersist.progressListener = gProgressListener; + gPersist.saveURI(gSource, null, null, 0, null, null, gTarget, null); + document.documentElement.getButton("cancel").focus(); +} + +function onUnload() +{ + if (gPersist) + gPersist.cancel(Cr.NS_BINDING_ABORTED); + gPersist = null; +} + +function setPercent(aPercent, aStatus) +{ + gPercent = aPercent; + document.title = gBundle.getFormattedString("progressTitlePercent", + [aPercent, gFileName, aStatus]); + gProgress.value = gBundle.getFormattedString("percentFormat", [aPercent]); + gMeter.mode = "normal"; + gMeter.value = aPercent; +} + +var gProgressListener = { + // ----- nsIWebProgressListener methods ----- + + // Look for STATE_STOP and close dialog to indicate completion when it happens. + onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) { + if (aRequest instanceof Ci.nsIChannel && + aRequest.URI.equals(gTarget) && + aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) { + gPersist = null; + var status = gBundle.getString("stateCompleted"); + setPercent(100, status); + gStatus.value = status; + gSize.value = DownloadUtils.getTransferTotal(gFileSize, gFileSize); + setTimeout(window.close, kInterval); + } + }, + + // Handle progress notifications. + onProgressChange: function(aWebProgress, aRequest, + aCurSelfProgress, aMaxSelfProgress, + aCurTotalProgress, aMaxTotalProgress) { + return this.onProgressChange64(aWebProgress, aRequest, + aCurSelfProgress, aMaxSelfProgress, + aCurTotalProgress, aMaxTotalProgress); + }, + + onProgressChange64: function(aWebProgress, aRequest, + aCurSelfProgress, aMaxSelfProgress, + aCurTotalProgress, aMaxTotalProgress) { + if (aRequest instanceof Ci.nsIChannel && + aRequest.URI.equals(gTarget)) { + // Get current time. + var now = Date.now(); + + // If interval hasn't elapsed, ignore it. + if (!gStartTime) + gStartTime = now; + else if (now - gLastUpdate < kInterval && aCurTotalProgress < gFileSize) + return; + + // Update this time. + gLastUpdate = now; + + // Update elapsed time. + var elapsed = (now - gStartTime) / 1000; + + // Calculate percentage. + var status = gBundle.getString("stateUploading"); + var percent = -1; + if (gFileSize > 0) + percent = Math.floor(aCurTotalProgress * 100 / gFileSize); + if (percent != gPercent) + setPercent(percent, status); + + // Update time remaining. + var rate = elapsed && aCurTotalProgress / elapsed; + if (rate && gFileSize) { + var timeLeft; + [timeLeft, gLastSeconds] = + DownloadUtils.getTimeLeft((gFileSize - aCurTotalProgress) / rate, + gLastSeconds); + status = gBundle.getFormattedString("statusActive", [status, timeLeft]); + } + gStatus.value = status; + + // Update dialog's display of elapsed time. + var timeUnits = DownloadUtils.convertTimeUnits(elapsed); + var timeString = timeUnits[2] ? "timeDouble" : "timeSingle"; + gTime.value = gBundle.getFormattedString(timeString, timeUnits); + + // Update size (nn KB of mm KB at xx.x KB/sec) + var size = DownloadUtils.getTransferTotal(aCurTotalProgress, gFileSize); + if (elapsed) + size = gBundle.getFormattedString("sizeSpeed", [size, + gBundle.getFormattedString("speedFormat", + DownloadUtils.convertByteUnits(rate))]); + gSize.value = size; + } + }, + + // Look for error notifications and display alert to user. + onStatusChange: function(aWebProgress, aRequest, aStatus, aMessage) { + // Check for error condition (only if dialog is still open). + if (!Cr.isSuccessCode(aStatus)) { + // Display error alert (using text supplied by back-end). + Services.prompt.alert(window, document.title, aMessage); + // Close the dialog. + window.close(); + } + }, + + // Ignore onLocationChange and onSecurityChange notifications. + onLocationChange: function( aWebProgress, aRequest, aLocation, aFlags ) { + }, + + onSecurityChange: function( aWebProgress, aRequest, aState ) { + }, + + // ---------- nsISupports methods ---------- + + QueryInterface: XPCOMUtils.generateQI([ + Ci.nsIWebProgressListener2, + Ci.nsIWebProgressListener, + Ci.nsIInterfaceRequestor]), + + // ---------- nsIInterfaceRequestor methods ---------- + + getInterface: function(aIID) { + if (aIID.equals(Ci.nsIPrompt) || + aIID.equals(Ci.nsIAuthPrompt)) { + var prompt; + if (aIID.equals(Ci.nsIPrompt)) + prompt = Services.ww.getNewPrompter(window); + else + prompt = Services.ww.getNewAuthPrompter(window); + return prompt; + } + + throw Cr.NS_ERROR_NO_INTERFACE; + } +} diff --git a/comm/suite/components/downloads/content/uploadProgress.xul b/comm/suite/components/downloads/content/uploadProgress.xul new file mode 100644 index 0000000000..43e95d5432 --- /dev/null +++ b/comm/suite/components/downloads/content/uploadProgress.xul @@ -0,0 +1,33 @@ +<?xml version="1.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/. --> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<!DOCTYPE dialog> + +<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + buttons="cancel" + onload="onLoad();" + onunload="onUnload();" + style="width: 40em;"> + + <script src="chrome://communicator/content/downloads/uploadProgress.js"/> + + <stringbundleset id="stringbundleset"> + <stringbundle id="dmBundle" + src="chrome://communicator/locale/downloads/downloadmanager.properties"/> + </stringbundleset> + + <label id="source" value="" crop="center"/> + <label id="target" value="" crop="center"/> + <label id="size" value=""/> + <label id="timeElapsed" value=""/> + <label id="status" value=""/> + <hbox> + <progressmeter id="progress" mode="undetermined" value="0" flex="1"/> + <label id="progressText" value="" style="width: 4ch; text-align: right;"/> + </hbox> +</dialog> diff --git a/comm/suite/components/downloads/jar.mn b/comm/suite/components/downloads/jar.mn new file mode 100644 index 0000000000..9abbb0cd7b --- /dev/null +++ b/comm/suite/components/downloads/jar.mn @@ -0,0 +1,14 @@ +# 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/. + +comm.jar: +% content communicator %content/communicator/ contentaccessible=yes + content/communicator/downloads/downloadmanager.js (content/downloadmanager.js) + content/communicator/downloads/downloadmanager.xul (content/downloadmanager.xul) + content/communicator/downloads/DownloadProgressListener.js (content/DownloadProgressListener.js) + content/communicator/downloads/progressDialog.xul (content/progressDialog.xul) + content/communicator/downloads/progressDialog.js (content/progressDialog.js) + content/communicator/downloads/uploadProgress.xul (content/uploadProgress.xul) + content/communicator/downloads/uploadProgress.js (content/uploadProgress.js) + content/communicator/downloads/treeView.js (content/treeView.js) diff --git a/comm/suite/components/downloads/moz.build b/comm/suite/components/downloads/moz.build new file mode 100644 index 0000000000..cc6cc0f1d6 --- /dev/null +++ b/comm/suite/components/downloads/moz.build @@ -0,0 +1,17 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +MOCHITEST_CHROME_MANIFESTS += ["tests/chrome/chrome.ini"] + +JAR_MANIFESTS += ["jar.mn"] + +EXTRA_JS_MODULES += [ + "DownloadsCommon.jsm", + "DownloadsTaskbar.jsm", +] + +with Files("**"): + BUG_COMPONENT = ("SeaMonkey", "Downloads") diff --git a/comm/suite/components/downloads/tests/chrome/chrome.ini b/comm/suite/components/downloads/tests/chrome/chrome.ini new file mode 100644 index 0000000000..dd5f9fc31f --- /dev/null +++ b/comm/suite/components/downloads/tests/chrome/chrome.ini @@ -0,0 +1,21 @@ +[DEFAULT] + +[test_action_keys_respect_focus.xul] +[test_basic_functionality.xul] +[test_cleanup_search.xul] +[test_clear_button_disabled.xul] +[test_close_download_manager.xul] +[test_delete_key_cancels.xul] +[test_delete_key_removes.xul] +[test_drag.xul] +[test_enter_dblclick_opens.xul] +[test_multi_select.xul] +[test_multiword_search.xul] +[test_open_properties.xul] +[test_removeDownload_updates_ui.xul] +[test_search_clearlist.xul] +[test_search_keys.xul] +[test_select_all.xul] +[test_space_key_pauses_resumes.xul] +[test_space_key_retries.xul] +[test_ui_stays_open_on_alert_clickback.xul] diff --git a/comm/suite/components/downloads/tests/chrome/test_action_keys_respect_focus.xul b/comm/suite/components/downloads/tests/chrome/test_action_keys_respect_focus.xul new file mode 100644 index 0000000000..765e0a3a9c --- /dev/null +++ b/comm/suite/components/downloads/tests/chrome/test_action_keys_respect_focus.xul @@ -0,0 +1,376 @@ +<?xml version="1.0"?> +<!-- +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Download Manager UI Test Code. + * + * The Initial Developer of the Original Code is + * Edward Lee <edward.lee@engineering.uiuc.edu>. + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Jens Hatlak <jh@junetz.de> (Original Author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * Test for bug 474622 to check that action keys (Del, Backspace, Return) + * respect focus, i.e. work as expected in the Search field, Clear List button + * and download list. + */ +--> + +<window title="Download Manager Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script src="chrome://mochikit/content/MochiKit/packed.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script> + <![CDATA[ + +var openInvokeCount = 0; +var removeInvokeCount = 0; +var resumeInvokeCount = 0; +var testedFunctions = { + openDownload : null, + removeDownload : null, + resumeDownload : null +}; + +function getCounter(aFn) +{ + switch (aFn) { + case "openDownload": + return () => openInvokeCount++; + case "removeDownload": + return () => removeInvokeCount++; + case "resumeDownload": + return () => resumeInvokeCount++; + } +} + +function backupTestedFunction(aFn, aWin) +{ + ok(true, "(info) backupTestedFunction('" + aFn + "')"); + + [testedFunctions[aFn], aWin[aFn]] = [aWin[aFn], getCounter(aFn)]; +} +function restoreTestedFunction(aFn, aWin) +{ + aWin[aFn] = testedFunctions[aFn]; + testedFunctions[aFn] = null; + + ok(true, "(info) restoreTestedFunction('" + aFn + "')"); +} + +function keyPressObs(aWin, aKey) +{ + this.mWin = aWin; + this.mKey = aKey; +} +keyPressObs.prototype = { + observe: function(aSubject, aTopic, aData) + { + if ("timer-callback" == aTopic) + synthesizeKey(this.mKey, {}, this.mWin); + } +}; +var searchAndPressKey = function(aKey, aWin, aValue) { + var searchbox = aWin.document.getElementById("search-box"); + searchbox.focus(); + if (aValue != null) + searchbox.value = aValue; + + // Press given key after a short delay to allow focus() to complete + var timer = Cc["@mozilla.org/timer;1"] + .createInstance(Ci.nsITimer); + timer.init(new keyPressObs(aWin, aKey), 500, + Ci.nsITimer.TYPE_ONE_SHOT); +} + +function dlObs(aWin) +{ + this.mWin = aWin; + this.wasPaused = false; + this.wasResumed = false; + this.wasFinished = false; +} +dlObs.prototype = { + onDownloadStateChange: function(aState, aDownload) + { + if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING && + !this.wasPaused) + { + this.wasPaused = true; + this.mWin.pauseDownload(aDownload.id); + return; + } + + var searchbox = this.mWin.document.getElementById("search-box"); + if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_PAUSED && + !this.wasResumed) + { + this.wasResumed = true; + + // Fill Search with an added space (test continues in testObs) + backupTestedFunction("resumeDownload", this.mWin); + searchAndPressKey(" ", this.mWin, "paused"); + } else + if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_FINISHED && + !this.wasFinished) + { + this.wasFinished = true; + + // The formerly paused download was resumed successfully, is now complete + // and still selected. Since it is a real download it can be opened. + + // Init Search (test continues in testObs) + backupTestedFunction("openDownload", this.mWin); + searchAndPressKey("VK_RETURN", this.mWin, "delete me"); + + var dm = Cc["@mozilla.org/download-manager;1"] + .getService(Ci.nsIDownloadManager); + dm.removeListener(this); + } + }, + onStateChange: function(a, b, c, d, e) { }, + onProgressChange: function(a, b, c, d, e, f, g) { }, + onSecurityChange: function(a, b, c, d) { } +}; + +function test() +{ + var dm = Cc["@mozilla.org/download-manager;1"] + .getService(Ci.nsIDownloadManager); + + function addDownload() + { + function createURI(aObj) + { + return (aObj instanceof Ci.nsIFile) ? Services.io.newFileURI(aObj) : + Services.io.newURI(aObj); + } + + const nsIWBP = Ci.nsIWebBrowserPersist; + var persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] + .createInstance(nsIWBP); + persist.persistFlags = nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES | + nsIWBP.PERSIST_FLAGS_BYPASS_CACHE | + nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION; + + var destFile = Services.dirsvc.get("ProfD", Ci.nsIFile); + // The "paused" part of this filename will be searched for later. + destFile.append("download.paused"); + if (destFile.exists()) + destFile.remove(false); + + var dl = dm.addDownload(Ci.nsIDownloadManager.DOWNLOAD_TYPE_DOWNLOAD, + createURI("http://example.com/httpd.js"), + createURI(destFile), null, null, + Math.round(Date.now() * 1000), null, persist, false); + + persist.progressListener = dl.QueryInterface(Ci.nsIWebProgressListener); + persist.saveURI(dl.source, null, null, 0, null, null, dl.targetFile, null); + + return dl; + } + + // Empty any old downloads + dm.DBConnection.executeSimpleSQL("DELETE FROM moz_downloads"); + + // Make a file name for the downloads + var file = Services.dirsvc.get("TmpD", Ci.nsIFile); + file.append("cleanUp"); + var filePath = Services.io.newFileURI(file).spec; + + var stmt = dm.DBConnection.createStatement( + "INSERT INTO moz_downloads (name, target, source, state) " + + "VALUES (?1, ?2, ?3, ?4)"); + + try { + for (let site of ["delete.me", "i.live"]) { + stmt.bindByIndex(0, "Finished Download"); + stmt.bindByIndex(1, filePath); + stmt.bindByIndex(2, "http://" + site + "/file"); + stmt.bindByIndex(3, dm.DOWNLOAD_FINISHED); + + // Add it! + stmt.execute(); + } + } + finally { + stmt.reset(); + stmt.finalize(); + } + + // Close the UI if necessary + var win = Services.wm.getMostRecentWindow("Download:Manager"); + if (win) win.close(); + + var obs = Cc["@mozilla.org/observer-service;1"] + .getService(Ci.nsIObserverService); + const DLMGR_UI_DONE = "download-manager-ui-done"; + const IS_MAC = Cc["@mozilla.org/xre/app-info;1"] + .getService(Ci.nsIXULRuntime) + .OS == "Darwin"; + + var testPhase = 0; + var testObs = { + observe: function(aSubject, aTopic, aData) + { + if (aTopic != DLMGR_UI_DONE) + return; + + SimpleTest.waitForFocus(function () { continueTest(aSubject) }, aSubject); + } + }; + + function continueTest(win) { + var downloadTree = win.document.getElementById("downloadTree"); + var searchbox = win.document.getElementById("search-box"); + var clearList = win.document.getElementById("clearListButton"); + + // The list must have built, so figure out what test to do + switch (testPhase++) { + case 0: + // Init Search + searchbox.value = "delete me"; + searchbox.doCommand(); + + break; + case 1: + // Clear Search + backupTestedFunction("removeDownload", win); + searchAndPressKey("VK_DELETE", win); + + break; + case 2: + is(removeInvokeCount, 0, "Search box: Del didn't remove download"); + + // Search has been cleared, init again + searchbox.value = "live"; + searchbox.doCommand(); + + break; + case 3: + // Clear Search + searchAndPressKey("VK_BACK_SPACE", win); + + break; + case 4: + is(removeInvokeCount, 0, "Search box: Backspace didn't remove download"); + restoreTestedFunction("removeDownload", win); + + // Add paused download (test continues in dlObs) + dm.addListener(new dlObs(win)); + addDownload(); + + break; + case 5: + // Back from dlObs + is(resumeInvokeCount, 0, "Search box: Space didn't resume download"); + + // Focus download tree and select first (paused) download + downloadTree.focus(); + downloadTree.view.selection.select(0); + + // Simulate Resume download + synthesizeKey(" ", {}, win); + is(resumeInvokeCount, 1, "Download list: Space resumed download"); + + // Resume download for real (test continues in dlObs) + restoreTestedFunction("resumeDownload", win); + synthesizeKey(" ", {}, win); + + break; + case 6: + // Back from dlObs + is(openInvokeCount, 0, "Search box: Return didn't open download"); + + // Search has been changed, init again to get formerly paused download + searchbox.value = "paused"; + searchbox.doCommand(); + + break; + case 7: + // Focus download tree and select first (formerly paused) download + downloadTree.focus(); + downloadTree.view.selection.select(0); + + // Simulate Open download + synthesizeKey("VK_RETURN", {}, win); + is(openInvokeCount, 1, "Download list: Return opened download"); + + // Clear List: Return (execute Clear List) + // MacOSX: VK_RETURN doesn't work on this button (See bug 506850). + if (IS_MAC) + // Workaround not to time out. + clearList.doCommand(); + else { + clearList.focus(); + synthesizeKey("VK_RETURN", {}, win); + } + + break; + case 8: + if (IS_MAC) + is(openInvokeCount, 1, "Clear List: doCommand() didn't open download (MacOSX)"); + else + is(openInvokeCount, 1, "Clear List: Enter didn't open download (Linux, Windows)"); + restoreTestedFunction("openDownload", win); + + // We're done here + obs.removeObserver(testObs, DLMGR_UI_DONE); + win.close(); + SimpleTest.finish(); + + break; + } + } + + obs.addObserver(testObs, DLMGR_UI_DONE); + + // Show the Download Manager UI + Cc["@mozilla.org/download-manager-ui;1"] + .getService(Ci.nsISuiteDownloadManagerUI) + .showManager(); + + SimpleTest.waitForExplicitFinish(); +} + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> +</window> diff --git a/comm/suite/components/downloads/tests/chrome/test_basic_functionality.xul b/comm/suite/components/downloads/tests/chrome/test_basic_functionality.xul new file mode 100644 index 0000000000..ffedc65227 --- /dev/null +++ b/comm/suite/components/downloads/tests/chrome/test_basic_functionality.xul @@ -0,0 +1,281 @@ +<?xml version="1.0"?> +<!-- +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * the Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Shawn Wilsher <me@shawnwilsher.com> (Original Author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * Make sure the download manager can display downloads in the right order and + * contains the expected data. The list has one of each download state ordered + * by the start/end times. + */ +--> + +<window title="Download Manager Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script src="chrome://mochikit/content/MochiKit/packed.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <script> + <![CDATA[ + +var dmFile = Services.dirsvc.get("TmpD", Ci.nsIFile); +dmFile.append("dm-ui-test.file"); +dmFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0666); +var gTestPath = Services.io.newFileURI(dmFile).spec; + +// Downloads are sorted by endTime, so make sure the end times are distinct +const DownloadData = [ + /* Active states first */ + { name: "381603.patch", + source: "https://bugzilla.mozilla.org/attachment.cgi?id=266520", + target: gTestPath, + startTime: 1180493839859230, + endTime: 1180493839859239, + state: Ci.nsIDownloadManager.DOWNLOAD_NOTSTARTED, + currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0 }, + { name: "381603.patch", + source: "https://bugzilla.mozilla.org/attachment.cgi?id=266520", + target: gTestPath, + startTime: 1180493839859230, + endTime: 1180493839859238, + state: Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING, + currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0 }, + { name: "381603.patch", + source: "https://bugzilla.mozilla.org/attachment.cgi?id=266520", + target: gTestPath, + startTime: 1180493839859230, + endTime: 1180493839859237, + state: Ci.nsIDownloadManager.DOWNLOAD_PAUSED, + currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0 }, + { name: "381603.patch", + source: "https://bugzilla.mozilla.org/attachment.cgi?id=266520", + target: gTestPath, + startTime: 1180493839859230, + endTime: 1180493839859236, + state: Ci.nsIDownloadManager.DOWNLOAD_SCANNING, + currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0 }, + { name: "381603.patch", + source: "https://bugzilla.mozilla.org/attachment.cgi?id=266520", + target: gTestPath, + startTime: 1180493839859230, + endTime: 1180493839859235, + state: Ci.nsIDownloadManager.DOWNLOAD_QUEUED, + currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0 }, + /* Finished states */ + { name: "381603.patch", + source: "https://bugzilla.mozilla.org/attachment.cgi?id=266520", + target: gTestPath, + startTime: 1180493839859230, + endTime: 1180493839859234, + state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED, + currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0 }, + { name: "381603.patch", + source: "https://bugzilla.mozilla.org/attachment.cgi?id=266520", + target: gTestPath, + startTime: 1180493839859230, + endTime: 1180493839859233, + state: Ci.nsIDownloadManager.DOWNLOAD_FAILED, + currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0 }, + { name: "381603.patch", + source: "https://bugzilla.mozilla.org/attachment.cgi?id=266520", + target: gTestPath, + startTime: 1180493839859230, + endTime: 1180493839859232, + state: Ci.nsIDownloadManager.DOWNLOAD_CANCELED, + currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0 }, + { name: "381603.patch", + source: "https://bugzilla.mozilla.org/attachment.cgi?id=266520", + target: gTestPath, + startTime: 1180493839859230, + endTime: 1180493839859231, + state: Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL, + currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0 }, + { name: "381603.patch", + source: "https://bugzilla.mozilla.org/attachment.cgi?id=266520", + target: gTestPath, + startTime: 1180493839859230, + endTime: 1180493839859230, + state: Ci.nsIDownloadManager.DOWNLOAD_DIRTY, + currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0 }, + { name: "381603.patch", + source: "https://bugzilla.mozilla.org/attachment.cgi?id=266520", + target: gTestPath, + startTime: 1180493839859229, + endTime: 1180493839859229, + state: Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_POLICY, + currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0 } +]; + +function test_numberOfTreeItems(aWin) +{ + var doc = aWin.document; + var dlTree = doc.getElementById("downloadTree"); + is(dlTree.view.rowCount, DownloadData.length, + "There is the correct number of tree items"); +} + +function test_properDownloadData(aWin) +{ + // This also tests the ordering of the display + var doc = aWin.document; + var dlTree = doc.getElementById("downloadTree"); + var view = dlTree.view; + var colName = dlTree.columns.getNamedColumn("Name"); + var colState = dlTree.columns.getNamedColumn("Status"); + var colTarget = dlTree.columns.getNamedColumn("Name"); + var colSource = dlTree.columns.getNamedColumn("Source"); + var stateString; + var statusBar = doc.getElementById("statusbar-display"); + + for (var i = 0; i < view.rowCount; i++) { + view.selection.select(i); + is(view.getCellText(i, colName), DownloadData[i].name, + "Download names match up"); + switch (DownloadData[i].state) { + case Ci.nsIDownloadManager.DOWNLOAD_PAUSED: + stateString = "Paused"; + break; + case Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING: + stateString = "Downloading"; + break; + case Ci.nsIDownloadManager.DOWNLOAD_FINISHED: + stateString = "Finished"; + break; + case Ci.nsIDownloadManager.DOWNLOAD_FAILED: + stateString = "Failed"; + break; + case Ci.nsIDownloadManager.DOWNLOAD_CANCELED: + stateString = "Canceled"; + break; + case Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL: // Parental Controls + case Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_POLICY: // Security Zone Policy + case Ci.nsIDownloadManager.DOWNLOAD_DIRTY: // possible virus/spyware + stateString = "Blocked"; + break; + default: + stateString = "Not Started"; + break; + } + is(view.getCellText(i, colState), stateString, + "Download states match up"); + + var filePath = Services.io.newURI(DownloadData[i].target) + .QueryInterface(Ci.nsIFileURL) + .file.clone() + .QueryInterface(Ci.nsIFile) + .path; + is(statusBar.label, filePath, + "Download targets match up"); + is(view.getCellText(i, colSource), DownloadData[i].source, + "Download sources match up"); + } +} + +var testFuncs = [ + test_numberOfTreeItems + , test_properDownloadData +]; + +function test() +{ + var dm = Cc["@mozilla.org/download-manager;1"] + .getService(Ci.nsIDownloadManager); + var db = dm.DBConnection; + + // First, we populate the database with some fake data + db.executeSimpleSQL("DELETE FROM moz_downloads"); + var stmt = db.createStatement( + "INSERT INTO moz_downloads (name, source, target, startTime, endTime, " + + "state, currBytes, maxBytes, preferredAction, autoResume) " + + "VALUES (:name, :source, :target, :startTime, :endTime, :state, " + + ":currBytes, :maxBytes, :preferredAction, :autoResume)"); + for (let dl of DownloadData) { + for (let [prop, value] of Object.entries(dl)) + stmt.params[prop] = value; + + stmt.execute(); + } + stmt.finalize(); + + // See if the DM is already open, and if it is, close it! + var win = Services.wm.getMostRecentWindow("Download:Manager"); + if (win) + win.close(); + + const DLMGR_UI_DONE = "download-manager-ui-done"; + + var testObs = { + observe: function(aSubject, aTopic, aData) + { + if (aTopic != DLMGR_UI_DONE) + return; + + var win = aSubject; + + // Now we can run our tests + for (let t of testFuncs) + t(win); + + win.close(); + dmFile.remove(false); + Services.obs.removeObserver(testObs, DLMGR_UI_DONE); + SimpleTest.finish(); + } + }; + + // Register with the observer service + Services.obs.addObserver(testObs, DLMGR_UI_DONE); + + // Show the Download Manager UI + Cc["@mozilla.org/download-manager-ui;1"] + .getService(Ci.nsISuiteDownloadManagerUI) + .showManager(); + + SimpleTest.waitForExplicitFinish(); +} + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> +</window> diff --git a/comm/suite/components/downloads/tests/chrome/test_cleanup_search.xul b/comm/suite/components/downloads/tests/chrome/test_cleanup_search.xul new file mode 100644 index 0000000000..37394168dc --- /dev/null +++ b/comm/suite/components/downloads/tests/chrome/test_cleanup_search.xul @@ -0,0 +1,172 @@ +<?xml version="1.0"?> +<!-- +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Download Manager UI Test Code. + * + * The Initial Developer of the Original Code is + * Edward Lee <edward.lee@engineering.uiuc.edu>. + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * Test for bug 414850 to make sure only downloads that are shown when + * searching are cleared and afterwards, the default list is shown. + * + * Test bug 430486 to make sure the Clear list button is disabled only when + * there are no download items visible. + */ +--> + +<window title="Download Manager Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script src="chrome://mochikit/content/MochiKit/packed.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <script> + <![CDATA[ + +function test() +{ + var dm = Cc["@mozilla.org/download-manager;1"] + .getService(Ci.nsIDownloadManager); + var db = dm.DBConnection; + + // Empty any old downloads + db.executeSimpleSQL("DELETE FROM moz_downloads"); + + // Make a file name for the downloads + var file = Services.dirsvc.get("TmpD", Ci.nsIFile); + file.append("cleanUp"); + var filePath = Services.io.newFileURI(file).spec; + + var stmt = db.createStatement( + "INSERT INTO moz_downloads (name, target, source, state) " + + "VALUES (?1, ?2, ?3, ?4)"); + + try { + for (let site of ["delete.me", "i.live"]) { + stmt.bindByIndex(0, "Super Pimped Download"); + stmt.bindByIndex(1, filePath); + stmt.bindByIndex(2, "http://" + site + "/file"); + stmt.bindByIndex(3, dm.DOWNLOAD_FINISHED); + + // Add it! + stmt.execute(); + } + } + finally { + stmt.reset(); + stmt.finalize(); + } + + // Close the UI if necessary + var win = Services.wm.getMostRecentWindow("Download:Manager"); + if (win) win.close(); + + var obs = Cc["@mozilla.org/observer-service;1"] + .getService(Ci.nsIObserverService); + const DLMGR_UI_DONE = "download-manager-ui-done"; + + var testPhase = 0; + var testObs = { + observe: function(aSubject, aTopic, aData) { + if (aTopic != DLMGR_UI_DONE) + return; + + var win = aSubject; + var downloadView = win.document.getElementById("downloadTree").view; + var searchbox = win.document.getElementById("search-box"); + var clearList = win.document.getElementById("clearListButton"); + + // The list must have built, so figure out what test to do + switch (testPhase++) { + case 0: + // Make sure the button is initially enabled + is(clearList.disabled, false, "Clear list is enabled for default 2 item view"); + + // Search for multiple words in any order in all places + searchbox.value = "delete me"; + searchbox.doCommand(); + + break; + case 1: + // Search came back with 1 item + is(downloadView.rowCount, 1, "Search found the item to delete"); + is(clearList.disabled, false, "Clear list is enabled for search matching 1 item"); + + // Clear the list that has the single matched item + clearList.doCommand(); + + break; + case 2: + // Done rebuilding with one item left + is(downloadView.rowCount, 1, "Clear list rebuilt the list with one"); + is(clearList.disabled, false, "Clear list still enabled for 1 item in default view"); + + // Clear the whole list + clearList.doCommand(); + + break; + case 3: + // There's nothing left + is(downloadView.rowCount, 0, "Clear list killed everything"); + is(clearList.disabled, true, "Clear list is disabled for no items"); + + // We're done! + win.close(); + obs.removeObserver(testObs, DLMGR_UI_DONE); + SimpleTest.finish(); + + break; + } + } + }; + obs.addObserver(testObs, DLMGR_UI_DONE); + + // Show the Download Manager UI + Cc["@mozilla.org/download-manager-ui;1"] + .getService(Ci.nsISuiteDownloadManagerUI) + .showManager(); + + SimpleTest.waitForExplicitFinish(); +} + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> +</window> diff --git a/comm/suite/components/downloads/tests/chrome/test_clear_button_disabled.xul b/comm/suite/components/downloads/tests/chrome/test_clear_button_disabled.xul new file mode 100644 index 0000000000..0b713a31c3 --- /dev/null +++ b/comm/suite/components/downloads/tests/chrome/test_clear_button_disabled.xul @@ -0,0 +1,201 @@ +<?xml version="1.0"?> +<!-- +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * the Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Anoop Saldanha <poonaatsoc@gmail.com> (Original Author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * This tests 437422. This test basically intends to checks if the clear list + * button is disabled when: + * 1. an invalid search string has been entered into the search box. + * 2. active downloads are present in the dm ui + * 3. we have both case (1) and (2) + */ +--> + +<window title="Download Manager Test" + onload="runTest();" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/MochiKit/packed.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <script> + <![CDATA[ + +const nsIDownloadManager = Ci.nsIDownloadManager; +var dmFile = Services.dirsvc.get("TmpD", Ci.nsIFile); +dmFile.append("dm-ui-test.file"); +dmFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0666); +var gTestPath = Services.io.newFileURI(dmFile).spec; + +const DoneDownloadData = [ + { name: "Dead", + source: "https://bugzilla.mozilla.org/attachment.cgi?id=266520", + target: gTestPath, + startTime: 1180493839859230, + endTime: 1180493839859239, + state: nsIDownloadManager.DOWNLOAD_CANCELED, + currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0 } +]; + +const ActiveDownloadData = [ + { name: "Patch", + source: "https://bugzilla.mozilla.org/attachment.cgi?id=266520", + target: gTestPath, + startTime: 1180493839859230, + endTime: 1180493839859239, + state: nsIDownloadManager.DOWNLOAD_DOWNLOADING, + currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0 } +]; + +function runTest() +{ + var dm = Cc["@mozilla.org/download-manager;1"] + .getService(Ci.nsIDownloadManager); + var db = dm.DBConnection; + + // Empty any old downloads + db.executeSimpleSQL("DELETE FROM moz_downloads"); + + var stmt = db.createStatement( + "INSERT INTO moz_downloads (name, source, target, startTime, endTime, " + + "state, currBytes, maxBytes, preferredAction, autoResume) " + + "VALUES (:name, :source, :target, :startTime, :endTime, :state, " + + ":currBytes, :maxBytes, :preferredAction, :autoResume)"); + for (let dl of DoneDownloadData) { + for (let [prop, value] of Object.entries(dl)) + stmt.params[prop] = value; + + stmt.execute(); + } + //stmt.finalize(); + + // Close the UI if necessary + var win = Services.wm.getMostRecentWindow("Download:Manager"); + if (win) win.close(); + + var obs = Cc["@mozilla.org/observer-service;1"] + .getService(Ci.nsIObserverService); + const DLMGR_UI_DONE = "download-manager-ui-done"; + + var testPhase = 0; + var testObs = { + observe: function(aSubject, aTopic, aData) { + var doc = aSubject.document; + var searchbox = doc.getElementById("search-box"); + var clearButton = doc.getElementById("clearListButton"); + + switch (testPhase++) { + case 0: + // Ensure that the clear list button is enabled at first + ok(!clearButton.disabled, + "The clear list button is not disabled initially."); + + // Now, insert an nonsensical search string - nothing should show up, + // and the button should be disabled in the next test phase + searchbox.value = "Nonsensical"; + searchbox.doCommand(); + + break; + case 1: + ok(clearButton.disabled, + "The clear list button is disabled with a nonsensical search " + + "term entered"); + + // Clear the search box + searchbox.value = ""; + searchbox.doCommand(); + break; + + case 2: + // Populate the download manager with an active download now, and + // rebuild the list + stmt.reset(); + for (let dl of ActiveDownloadData) { + for (let [prop, value] of Object.entries(dl)) + stmt.params[prop] = value; + + stmt.execute(); + } + stmt.finalize(); + dm.cleanUp(); + + break; + case 3: + ok(clearButton.disabled, + "The clear list button is disabled when we only have an active " + + "download"); + + // Now, insert an nonsensical search string - only the active download + // should show up, and the button should be disabled in the next test + // phase + searchbox.value = "Nonsensical"; + searchbox.doCommand(); + break; + case 4: + ok(clearButton.disabled, + "The clear list button is disabled with a nonsensical search " + + "term entered and one active download"); + + obs.removeObserver(testObs, DLMGR_UI_DONE); + db.executeSimpleSQL("DELETE FROM moz_downloads"); + SimpleTest.finish(); + + break; + } + } + }; + + obs.addObserver(testObs, DLMGR_UI_DONE); + + Cc["@mozilla.org/download-manager-ui;1"] + .getService(Ci.nsISuiteDownloadManagerUI) + .showManager(); + + SimpleTest.waitForExplicitFinish(); +} + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"></pre> + </body> +</window> diff --git a/comm/suite/components/downloads/tests/chrome/test_close_download_manager.xul b/comm/suite/components/downloads/tests/chrome/test_close_download_manager.xul new file mode 100644 index 0000000000..94251a3771 --- /dev/null +++ b/comm/suite/components/downloads/tests/chrome/test_close_download_manager.xul @@ -0,0 +1,117 @@ +<?xml version="1.0"?> +<!-- +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Download Manager UI Test Code. + * + * The Initial Developer of the Original Code is + * Anoop Saldanha <poonaatsoc@gmail.com> + * + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * This test basically checks if the download manager + * closes when you press the esc key and accel + w. + */ +--> + +<window title="Download Manager Test" + onload="runTest();" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/MochiKit/packed.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script> + <![CDATA[ + +const dmui = Cc["@mozilla.org/download-manager-ui;1"] + .getService(Ci.nsIDownloadManagerUI); + +function testCloseDMWithAccelKey(aWin) +{ + function dmWindowClosedListener() { + aWin.removeEventListener("unload", dmWindowClosedListener, false); + ok(!dmui.visible, "DMUI closes with accel + w"); + SimpleTest.finish(); + } + aWin.addEventListener("unload", dmWindowClosedListener, false); + + synthesizeKey("w", { accelKey: true }, aWin); +} + +function runTest() +{ + const DLMGR_UI_DONE = "download-manager-ui-done"; + + // Close the UI if necessary + var win = Services.wm.getMostRecentWindow("Download:Manager"); + if (win) win.close(); + + var testPhase = 0; + // Specify an observer that will be notified when the dm has been rendered on screen + var obs = Cc["@mozilla.org/observer-service;1"] + .getService(Ci.nsIObserverService); + var testObs = { + observe: function(aSubject, aTopic, aData) { + SimpleTest.waitForFocus(function () { closeDM(aSubject) }, aSubject); + } + }; + + function closeDM(win) { + // if we add more ways to close DM with keys, add more cases here + switch(testPhase++) { + case 0: + obs.removeObserver(testObs, DLMGR_UI_DONE); + testCloseDMWithAccelKey(win); + } + } + + obs.addObserver(testObs, DLMGR_UI_DONE); + + Cc["@mozilla.org/download-manager-ui;1"] + .getService(Ci.nsISuiteDownloadManagerUI) + .showManager(); + + SimpleTest.waitForExplicitFinish(); +} + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"></pre> + </body> +</window> diff --git a/comm/suite/components/downloads/tests/chrome/test_delete_key_cancels.xul b/comm/suite/components/downloads/tests/chrome/test_delete_key_cancels.xul new file mode 100644 index 0000000000..f02459dd6f --- /dev/null +++ b/comm/suite/components/downloads/tests/chrome/test_delete_key_cancels.xul @@ -0,0 +1,200 @@ +<?xml version="1.0"?> +<!-- +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * the Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Jens Hatlak <jh@junetz.de> (Original Author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * This tests that the delete key will cancel a download in the UI. + * This test was added in bug 474622. + */ +--> + +<window title="Download Manager Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script src="chrome://mochikit/content/MochiKit/packed.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script> + <![CDATA[ + +var invokeCount = 0; +var cancelDownload = null; + +function dlObs(aWin) +{ + this.mWin = aWin; + this.wasPaused = false; + this.wasCanceled = false; +} +dlObs.prototype = { + observe: function(aSubject, aTopic, aData) + { + if ("timer-callback" == aTopic) { + // We're done! + this.mWin.close(); + SimpleTest.finish(); + } + }, + + onDownloadStateChange: function(aState, aDownload) + { + if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING && + !this.wasPaused) { + // Make a copy of the cancelDownload function and replace it with a test + var counter = () => invokeCount++; + [cancelDownload, this.mWin["cancelDownload"]] = [this.mWin["cancelDownload"], counter]; + + synthesizeKey("VK_DELETE", {}, this.mWin); + is(invokeCount, 1, "Delete canceled the active download"); + + this.wasPaused = true; + this.mWin.pauseDownload(aDownload.id); + } + + if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_PAUSED && + !this.wasCanceled) { + synthesizeKey("VK_DELETE", {}, this.mWin); + is(invokeCount, 2, "Delete canceled the paused download"); + + // After all tests, restore original function + this.mWin["cancelDownload"] = cancelDownload; + + this.wasCanceled = true; + this.mWin.cancelDownload(aDownload); + + var dm = Cc["@mozilla.org/download-manager;1"] + .getService(Ci.nsIDownloadManager); + dm.removeListener(this); + + // We have to do this on a timer so other JS stuff that handles the UI + // can actually catch up to us... + var timer = Cc["@mozilla.org/timer;1"] + .createInstance(Ci.nsITimer); + timer.init(this, 0, Ci.nsITimer.TYPE_ONE_SHOT); + } + }, + onStateChange: function(a, b, c, d, e) { }, + onProgressChange: function(a, b, c, d, e, f, g) { }, + onSecurityChange: function(a, b, c, d) { } +}; +function test() +{ + var dm = Cc["@mozilla.org/download-manager;1"] + .getService(Ci.nsIDownloadManager); + + function addDownload() { + function createURI(aObj) { + return (aObj instanceof Ci.nsIFile) ? Services.io.newFileURI(aObj) : + Services.io.newURI(aObj); + } + + const nsIWBP = Ci.nsIWebBrowserPersist; + var persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] + .createInstance(nsIWBP); + persist.persistFlags = nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES | + nsIWBP.PERSIST_FLAGS_BYPASS_CACHE | + nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION; + + var destFile = Services.dirsvc.get("ProfD", Ci.nsIFile); + destFile.append("download.result"); + if (destFile.exists()) + destFile.remove(false); + + var dl = dm.addDownload(Ci.nsIDownloadManager.DOWNLOAD_TYPE_DOWNLOAD, + createURI("http://example.com/httpd.js"), + createURI(destFile), null, null, + Math.round(Date.now() * 1000), null, persist, false); + + persist.progressListener = dl.QueryInterface(Ci.nsIWebProgressListener); + persist.saveURI(dl.source, null, null, 0, null, null, dl.targetFile, null); + + return dl; + } + + // First, we clear out the database + dm.DBConnection.executeSimpleSQL("DELETE FROM moz_downloads"); + + // See if the DM is already open, and if it is, close it! + var win = Services.wm.getMostRecentWindow("Download:Manager"); + if (win) + win.close(); + + const DLMGR_UI_DONE = "download-manager-ui-done"; + + var testObs = { + observe: function(aSubject, aTopic, aData) + { + if (aTopic != DLMGR_UI_DONE) + return; + + SimpleTest.waitForFocus(function () { cancelDL(aSubject) }, aSubject); + } + }; + + function cancelDL(win) { + var doc = win.document; + dm.addListener(new dlObs(win)); + + addDownload(); + // we need to focus the download as well + doc.getElementById("downloadTree").view.selection.select(0); + Services.obs.removeObserver(testObs, DLMGR_UI_DONE); + } + + // Register with the observer service + Services.obs.addObserver(testObs, DLMGR_UI_DONE); + + // Show the Download Manager UI + Cc["@mozilla.org/download-manager-ui;1"] + .getService(Ci.nsISuiteDownloadManagerUI) + .showManager(); + + SimpleTest.waitForExplicitFinish(); +} + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> +</window> diff --git a/comm/suite/components/downloads/tests/chrome/test_delete_key_removes.xul b/comm/suite/components/downloads/tests/chrome/test_delete_key_removes.xul new file mode 100644 index 0000000000..8462c3323b --- /dev/null +++ b/comm/suite/components/downloads/tests/chrome/test_delete_key_removes.xul @@ -0,0 +1,198 @@ +<?xml version="1.0"?> +<!-- +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * the Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Shawn Wilsher <me@shawnwilsher.com> (Original Author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * This test ensures that the delete key removes a download. This was added by + * bug 411172. + */ +--> + +<window title="Download Manager Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script src="chrome://mochikit/content/MochiKit/packed.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script> + <![CDATA[ + +var dmFile = Services.dirsvc.get("TmpD", Ci.nsIFile); +dmFile.append("dm-ui-test.file"); +dmFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0666); +var gTestPath = Services.io.newFileURI(dmFile).spec; + +// Downloads are sorted by endTime, so make sure the end times are distinct +const DownloadData = [ + { name: "381603.patch", + source: "https://bugzilla.mozilla.org/attachment.cgi?id=266520", + target: gTestPath, + startTime: 1180493839859230, + endTime: 1180493839859239, + state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED, + currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0 }, + { name: "381603.patch", + source: "https://bugzilla.mozilla.org/attachment.cgi?id=266520", + target: gTestPath, + startTime: 1180493839859230, + endTime: 1180493839859236, + state: Ci.nsIDownloadManager.DOWNLOAD_FAILED, + currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0 }, + { name: "381603.patch", + source: "https://bugzilla.mozilla.org/attachment.cgi?id=266520", + target: gTestPath, + startTime: 1180493839859230, + endTime: 1180493839859234, + state: Ci.nsIDownloadManager.DOWNLOAD_CANCELED, + currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0 }, + { name: "381603.patch", + source: "https://bugzilla.mozilla.org/attachment.cgi?id=266520", + target: gTestPath, + startTime: 1180493839859230, + endTime: 1180493839859232, + state: Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL, + currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0 }, + { name: "381603.patch", + source: "https://bugzilla.mozilla.org/attachment.cgi?id=266520", + target: gTestPath, + startTime: 1180493839859230, + endTime: 1180493839859230, + state: Ci.nsIDownloadManager.DOWNLOAD_DIRTY, + currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0 }, + { name: "381603.patch", + source: "https://bugzilla.mozilla.org/attachment.cgi?id=266520", + target: gTestPath, + startTime: 1180493839859229, + endTime: 1180493839859229, + state: Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_POLICY, + currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0 } +]; + + +function test() +{ + var dm = Cc["@mozilla.org/download-manager;1"] + .getService(Ci.nsIDownloadManager); + var db = dm.DBConnection; + + // First, we populate the database with some fake data + db.executeSimpleSQL("DELETE FROM moz_downloads"); + var stmt = db.createStatement( + "INSERT INTO moz_downloads (name, source, target, startTime, endTime, " + + "state, currBytes, maxBytes, preferredAction, autoResume) " + + "VALUES (:name, :source, :target, :startTime, :endTime, :state, " + + ":currBytes, :maxBytes, :preferredAction, :autoResume)"); + for (let dl of DownloadData) { + for (let [prop, value] of Object.entries(dl)) + stmt.params[prop] = value; + + stmt.execute(); + } + stmt.finalize(); + + // See if the DM is already open, and if it is, close it! + var win = Services.wm.getMostRecentWindow("Download:Manager"); + if (win) + win.close(); + + const DLMGR_UI_DONE = "download-manager-ui-done"; + + var testObs = { + observe: function(aSubject, aTopic, aData) + { + if (aTopic != DLMGR_UI_DONE) + return; + + SimpleTest.waitForFocus(function () { deleteDL(aSubject) }, aSubject); + } + }; + + function deleteDL(win) { + var doc = win.document; + + var stmt = db.createStatement("SELECT COUNT(*) FROM moz_downloads"); + try { + stmt.executeStep(); + let dlTree = doc.getElementById("downloadTree"); + is(stmt.getInt32(0), dlTree.view.rowCount, + "The database and the number of downloads display matches"); + stmt.reset(); + + let len = DownloadData.length; + for (let i = 0; i < len; i++) { + synthesizeKey("VK_DELETE", {}, win); + + stmt.executeStep(); + is(stmt.getInt32(0), len - (i + 1), + "The download was properly removed"); + stmt.reset(); + } + } + finally { + stmt.reset(); + stmt.finalize(); + } + + win.close(); + dmFile.remove(false); + Services.obs.removeObserver(testObs, DLMGR_UI_DONE); + SimpleTest.finish(); + } + + // Register with the observer service + Services.obs.addObserver(testObs, DLMGR_UI_DONE); + + // Show the Download Manager UI + Cc["@mozilla.org/download-manager-ui;1"] + .getService(Ci.nsISuiteDownloadManagerUI) + .showManager(); + + SimpleTest.waitForExplicitFinish(); +} + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> +</window> diff --git a/comm/suite/components/downloads/tests/chrome/test_drag.xul b/comm/suite/components/downloads/tests/chrome/test_drag.xul new file mode 100644 index 0000000000..133e633c39 --- /dev/null +++ b/comm/suite/components/downloads/tests/chrome/test_drag.xul @@ -0,0 +1,201 @@ +<?xml version="1.0"?> +<!-- +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Mozilla Corporation. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Phil Lacy <philbaseless-firefox@yahoo.com> (Original Author) + * Jens Hatlak <jh@junetz.de> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * Assure download manager can load valid list item as + * "application/moz-x-file", "text/uri-list" and "text/plain" + */ + +based on toolkit/mozapps/downloads/tests/chrome/test_bug_462172.xul +https://bugzilla.mozilla.org/show_bug.cgi?id=462172 + +create a file with unique name +create another file with unique name and delete it +load into downloads database +open download manager +synthesize drag on both files +missing file should not init drag +real file should return transferdata with application/x-moz-file, + text/uri-list (CRLF-terminated) and text/plain (LF-terminated) +close window +--> +<window title="Download Manager Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js"></script> + + <script> + <![CDATA[ +var missingFileElid; +var realFileElid; +const kFiller = "notApplicable"; +const kFillerURL = "https://bugzilla.mozilla.org/show_bug.cgi?id=462172" +var realFile = Services.dirsvc.get("CurWorkD", Ci.nsIFile); +var missingFile = Services.dirsvc.get("CurWorkD", Ci.nsIFile); + +realFile.append(kFiller); +realFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0666); +var realFilePath = Services.io.newFileURI(realFile).spec; + +missingFile.append(kFiller); +missingFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0666); +var missingFilePath = Services.io.newFileURI(missingFile).spec; +missingFile.remove(false); + +// Dummy data for our files. +// 'source' field must be in form of a URL. +const DownloadData = [ + { name: kFiller, + source: kFillerURL, + target: realFilePath, + state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED }, + { name: kFiller, + source: kFillerURL, + target: missingFilePath, + state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED } +]; + +function mouseDragStartOnCell(aTree, aRow, aColumn, aWin, aFile) +{ + // get cell coordinates + if (typeof aTree.columns != "undefined") + aColumn = aTree.columns[aColumn]; + var rect = aTree.treeBoxObject.getCoordsForCellItem(aRow, aColumn, "text"); + return synthesizeDragStart(aTree.body, aFile, aWin, rect.x, rect.y); +} + +function compareFunc(actualData, expectedData) +{ + return expectedData.equals(actualData); +} + +var dragRealFile = [[ + { type: "application/x-moz-file", + data: realFile, + eqTest: compareFunc }, + { type: "text/uri-list", + data: realFilePath + "\r\n" }, + { type: "text/plain", + data: realFilePath + "\n" } +]]; +var dragMissingFile = [[ + { type: "application/x-moz-file", + data: missingFile, + eqTest: compareFunc }, + { type: "text/uri-list", + data: missingFilePath + "\r\n" }, + { type: "text/plain", + data: missingFilePath + "\n" } +]]; + +function test() +{ + var dm = Cc["@mozilla.org/download-manager;1"] + .getService(Ci.nsIDownloadManager); + + // See if the DM is already open, and if it is, close it! + var win = Services.wm.getMostRecentWindow("Download:Manager"); + if (win) + win.close(); + + const DLMGR_UI_DONE = "download-manager-ui-done"; + + // load files into db + var db = dm.DBConnection; + + var stmt = db.createStatement( + "INSERT INTO moz_downloads ( name, source, target, state)" + + "VALUES (:name, :source, :target, :state)"); + for (let dl of DownloadData) { + for (let [prop, value] of Object.entries(dl)) + stmt.params[prop] = value; + stmt.execute(); + } + stmt.finalize(); + + var testObs = { + observe: function(aSubject, aTopic, aData) { + if (aTopic != DLMGR_UI_DONE) + return; + + var win = aSubject; + win.focus(); + + var downloadTree = win.document.getElementById("downloadTree"); + + // Now we can run our tests + // Unordered sorting -> DownloadData/insert order: realFile, missingFile + // Column 4 is "Progress" (column 1, "Status", is hidden by default) + var result = mouseDragStartOnCell(downloadTree, 0, 4, win, dragRealFile); + is(result, null, "Checking for Real file match"); + result = mouseDragStartOnCell(downloadTree, 1, 4, win, dragMissingFile); + isnot(result, null, "Drag start did not return item for missing file"); + + // Done. + win.close(); + realFile.remove(false); + Services.obs.removeObserver(testObs, DLMGR_UI_DONE); + SimpleTest.finish(); + } + }; + + // Register with the observer service + Services.obs.addObserver(testObs, DLMGR_UI_DONE); + + // Show the Download Manager UI + Cc["@mozilla.org/download-manager-ui;1"] + .getService(Ci.nsISuiteDownloadManagerUI) + .showManager(); + + SimpleTest.waitForExplicitFinish(); +} + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> +</window> diff --git a/comm/suite/components/downloads/tests/chrome/test_enter_dblclick_opens.xul b/comm/suite/components/downloads/tests/chrome/test_enter_dblclick_opens.xul new file mode 100644 index 0000000000..59c105ceaa --- /dev/null +++ b/comm/suite/components/downloads/tests/chrome/test_enter_dblclick_opens.xul @@ -0,0 +1,243 @@ +<?xml version="1.0"?> +<!-- +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Download Manager UI Test Code. + * + * The Initial Developer of the Original Code is + * Edward Lee <edward.lee@engineering.uiuc.edu>. + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Jens Hatlak <jh@junetz.de> (Original Author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * Test bug 495545 (implemented by bug 474622) to make sure the enter key + * or a double click actually calls opening the downloaded file. + */ +--> + +<window title="Download Manager Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script src="chrome://mochikit/content/MochiKit/packed.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script> + <![CDATA[ + +// similar, but not equal to the one in tree_shared.js +function mouseDblClickOnCell(aTree, aRow, aColumn, aWin) +{ + // get cell coordinates + if (typeof aTree.columns != "undefined") + aColumn = aTree.columns[aColumn]; + var rect = aTree.treeBoxObject.getCoordsForCellItem(aRow, aColumn, "text"); + synthesizeMouse(aTree.body, rect.x, rect.y, { clickCount: 2 }, aWin); +} + +function dlObs(aWin) +{ + this.mWin = aWin; + this.currDownload = null; +} +dlObs.prototype = { + observe: function(aSubject, aTopic, aData) + { + if ("timer-callback" == aTopic) { + var downloadTree = this.mWin.document.getElementById("downloadTree"); + var downloadView = downloadTree.view; + + // Default test/check for invocations + var invokeCount = 0; + var counter = () => invokeCount++; + + // Run tests + + // Make a copy of the openDownload function and replace it with a test + let copy; + [copy, this.mWin["openDownload"]] = [this.mWin["openDownload"], counter]; + + // Select the first (paused) download for not calling openDownload + downloadView.selection.select(0); + + synthesizeKey("VK_RETURN", {}, this.mWin); + is(invokeCount, 0, "Enter didn't do anything"); + + mouseDblClickOnCell(downloadTree, 0, 3, this.mWin); + is(invokeCount, 0, "Double click didn't do anything"); + + // Select the second (finished) download for calling openDownload + downloadView.selection.select(1); + + synthesizeKey("VK_RETURN", {}, this.mWin); + is(invokeCount, 1, "Enter opened download"); + + mouseDblClickOnCell(downloadTree, 1, 3, this.mWin); + is(invokeCount, 2, "Double click opened download"); + + // After all tests, restore original function + this.mWin["openDownload"] = copy; + + // We're done! + this.mWin.close(); + this.currDownload.targetFile.remove(false); + SimpleTest.finish(); + } + }, + + onDownloadStateChange: function(aState, aDownload) + { + if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_FINISHED) { + this.currDownload = aDownload; + // We have to do this on a timer so other JS stuff that handles the UI + // can actually catch up to us... + var timer = Cc["@mozilla.org/timer;1"] + .createInstance(Ci.nsITimer); + timer.init(this, 0, Ci.nsITimer.TYPE_ONE_SHOT); + + var dm = Cc["@mozilla.org/download-manager;1"] + .getService(Ci.nsIDownloadManager); + dm.removeListener(this); + } + }, + onStateChange: function(a, b, c, d, e) { }, + onProgressChange: function(a, b, c, d, e, f, g) { }, + onSecurityChange: function(a, b, c, d) { } +}; + +function test() +{ + var dm = Cc["@mozilla.org/download-manager;1"] + .getService(Ci.nsIDownloadManager); + + function addDownload() { + function createURI(aObj) { + return (aObj instanceof Ci.nsIFile) ? Services.io.newFileURI(aObj) : + Services.io.newURI(aObj); + } + + const nsIWBP = Ci.nsIWebBrowserPersist; + var persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] + .createInstance(nsIWBP); + persist.persistFlags = nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES | + nsIWBP.PERSIST_FLAGS_BYPASS_CACHE | + nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION; + + var destFile = Services.dirsvc.get("ProfD", Ci.nsIFile); + destFile.append("download.result"); + if (destFile.exists()) + destFile.remove(false); + + var dl = dm.addDownload(Ci.nsIDownloadManager.DOWNLOAD_TYPE_DOWNLOAD, + createURI("http://example.com/httpd.js"), + createURI(destFile), null, null, + Math.round(Date.now() * 1000), null, persist, false); + + persist.progressListener = dl.QueryInterface(Ci.nsIWebProgressListener); + persist.saveURI(dl.source, null, null, 0, null, null, dl.targetFile, null); + + return dl; + } + + var db = dm.DBConnection; + + // Empty any old downloads + db.executeSimpleSQL("DELETE FROM moz_downloads"); + + var stmt = db.createStatement( + "INSERT INTO moz_downloads (source, state, target, referrer) " + + "VALUES (?1, ?2, ?3, ?4)"); + + // add first download: PAUSED state + try { + var file = Services.dirsvc.get("TmpD", Ci.nsIFile); + file.append("dltest-paused"); + var fileSpec = Services.io.newFileURI(file).spec; + stmt.bindByIndex(0, "http://example.com/file"); + stmt.bindByIndex(1, dm.DOWNLOAD_PAUSED); + stmt.bindByIndex(2, fileSpec); + stmt.bindByIndex(3, "http://referrer/"); + + // Add it! + stmt.execute(); + } + finally { + stmt.reset(); + stmt.finalize(); + } + + // Close the UI if necessary + var win = Services.wm.getMostRecentWindow("Download:Manager"); + if (win) win.close(); + + var obs = Cc["@mozilla.org/observer-service;1"] + .getService(Ci.nsIObserverService); + const DLMGR_UI_DONE = "download-manager-ui-done"; + + var testObs = { + observe: function(aSubject, aTopic, aData) { + if (aTopic != DLMGR_UI_DONE) + return; + + SimpleTest.waitForFocus(function () { continueTest(aSubject) }, aSubject); + } + }; + + function continueTest(win) { + dm.addListener(new dlObs(win)); + + // add second download: FINISHED state, actually created + // (checked by cmd_open) + addDownload(); + + obs.removeObserver(testObs, DLMGR_UI_DONE); + } + + obs.addObserver(testObs, DLMGR_UI_DONE); + + // Show the Download Manager UI + Cc["@mozilla.org/download-manager-ui;1"] + .getService(Ci.nsISuiteDownloadManagerUI) + .showManager(); + + SimpleTest.waitForExplicitFinish(); +} + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> +</window> diff --git a/comm/suite/components/downloads/tests/chrome/test_multi_select.xul b/comm/suite/components/downloads/tests/chrome/test_multi_select.xul new file mode 100644 index 0000000000..0c0a05cf4b --- /dev/null +++ b/comm/suite/components/downloads/tests/chrome/test_multi_select.xul @@ -0,0 +1,204 @@ +<?xml version="1.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/. --> +<!-- + * Test bug 228842 to make sure multiple selections work in the download + * manager by making sure commands work as expected for both single and doubly + * selected items. +--> + +<window title="Download Manager Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script src="chrome://mochikit/content/MochiKit/packed.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script> + <![CDATA[ + +function test() +{ + var dm = Cc["@mozilla.org/download-manager;1"] + .getService(Ci.nsIDownloadManager); + var db = dm.DBConnection; + + // Empty any old downloads + db.executeSimpleSQL("DELETE FROM moz_downloads"); + + var stmt = db.createStatement( + "INSERT INTO moz_downloads (source, state, target, referrer) " + + "VALUES (?1, ?2, ?3, ?4)"); + + try { + for (let site of ["ed.agadak.net", "mozilla.org", "mozilla.com", "mozilla.net"]) { + let file = Services.dirsvc.get("TmpD", Ci.nsIFile); + file.append(site); + let fileSpec = Services.io.newFileURI(file).spec; + + stmt.bindByIndex(0, "http://" + site + "/file"); + stmt.bindByIndex(1, dm.DOWNLOAD_FINISHED); + stmt.bindByIndex(2, fileSpec); + stmt.bindByIndex(3, "http://referrer/"); + + // Add it! + stmt.execute(); + } + } + finally { + stmt.reset(); + stmt.finalize(); + } + + // Close the UI if necessary + var win = Services.wm.getMostRecentWindow("Download:Manager"); + if (win) win.close(); + + var obs = Cc["@mozilla.org/observer-service;1"] + .getService(Ci.nsIObserverService); + const DLMGR_UI_DONE = "download-manager-ui-done"; + + var testPhase = 0; + var testObs = { + observe: function(aSubject, aTopic, aData) { + if (aTopic != DLMGR_UI_DONE) + return; + + SimpleTest.waitForFocus(function () { continueTest(aSubject) }, aSubject); + } + }; + + function continueTest(win) { + var downloadView = win.document.getElementById("downloadTree").view; + + // Default test/check for invocations + var invokeCount = 0; + var counter = () => invokeCount++; + + // Accessors for returning a value for various properties + var getItems = () => downloadView.rowCount; + var getSelected = () => downloadView.selection.count; + var getClipboard = function() { + var clip = Cc["@mozilla.org/widget/clipboard;1"] + .getService(Ci.nsIClipboard); + var trans = Cc["@mozilla.org/widget/transferable;1"] + .createInstance(Ci.nsITransferable); + + trans.init(null); + trans.addDataFlavor("text/unicode"); + clip.getData(trans, clip.kGlobalClipboard); + var str = {}; + trans.getTransferData("text/unicode", str, {}); + return str.value.QueryInterface(Ci.nsISupportsString) + .data; + }; + + // Array of tests that consist of the command name, download manager + // function to temporarily replace, method to use in its place, value to + // use when checking correctness + var commandTests = [ + ["pause", "pauseDownload", counter, counter], + ["resume", "resumeDownload", counter, counter], + ["cancel", "cancelDownload", counter, counter], + ["open", "openDownload", counter, counter], + ["show", "showDownload", counter, counter], + ["properties", "showProperties", counter, counter], + ["retry", "retryDownload", counter, counter], + ["openReferrer", "openUILink", counter, counter], + ["copyLocation", null, null, getClipboard], + ["remove", null, null, getItems], + ["selectAll", null, null, getSelected], + ]; + + // All the expected results for both single and double selections + var allExpected = { + single: { + pause: [0, "Paused no downloads"], + resume: [0, "Resumed no downloads"], + cancel: [0, "Canceled no downloads"], + open: [0, "Opened no downloads"], + show: [0, "Showed no downloads"], + properties: [1, "Called properties for one download"], + retry: [0, "Retried no downloads"], + openReferrer: [1, "Opened one referrer"], + copyLocation: ["http://ed.agadak.net/file", "Copied one location"], + remove: [3, "Removed one download, remaining 3"], + selectAll: [3, "Selected all 3 remaining downloads"], + }, + double: { + pause: [0, "Paused neither download"], + resume: [0, "Resumed neither download"], + cancel: [0, "Canceled neither download"], + open: [0, "Opened neither download"], + show: [0, "Showed neither download"], + properties: [0, "Called properties for neither download"], + retry: [0, "Retried neither download"], + openReferrer: [0, "Opened neither referrer"], + copyLocation: ["http://mozilla.org/file\nhttp://mozilla.com/file", "Copied both locations"], + remove: [1, "Removed both downloads, remaining 1"], + selectAll: [1, "Selected the 1 remaining download"], + }, + }; + + var cmdName; + + // Run two tests: single selected, double selected + for (let whichTest of ["single", "double"]) { + let expected = allExpected[whichTest]; + + if (whichTest == "double") + // Select the first 2 downloads for double + downloadView.selection.rangedSelect(0, 1, false); + else + // Select the first download for single + downloadView.selection.select(0); + + for (let [command, func, test, value] of commandTests) { + // Make a copy of the original function and replace it with a test + let copy; + [copy, win[func]] = [win[func], test]; + + // Run the command from the menu + if (command == "selectAll") + cmdName = "menu_" + command; + else + cmdName = "dlMenu_" + command; + + win.document.getElementById(cmdName).doCommand(); + + // Make sure the value is as expected + let [correct, message] = expected[command]; + is(value(), correct, message); + + // Restore original values + invokeCount = 0; + win[func] = copy; + } + } + + // We're done! + win.close(); + obs.removeObserver(testObs, DLMGR_UI_DONE); + SimpleTest.finish(); + } + obs.addObserver(testObs, DLMGR_UI_DONE); + + // Show the Download Manager UI + Cc["@mozilla.org/download-manager-ui;1"] + .getService(Ci.nsISuiteDownloadManagerUI) + .showManager(); + + SimpleTest.waitForExplicitFinish(); +} + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> +</window> diff --git a/comm/suite/components/downloads/tests/chrome/test_multiword_search.xul b/comm/suite/components/downloads/tests/chrome/test_multiword_search.xul new file mode 100644 index 0000000000..ae2817c3fa --- /dev/null +++ b/comm/suite/components/downloads/tests/chrome/test_multiword_search.xul @@ -0,0 +1,173 @@ +<?xml version="1.0"?> +<!-- +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Download Manager UI Test Code. + * + * The Initial Developer of the Original Code is + * Edward Lee <edward.lee@engineering.uiuc.edu>. + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * Test for bug 419403 that lets the download manager support multiple word + * search against the download name, source/referrer, date/time, file size, + * etc. + */ +--> + +<window title="Download Manager Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script src="chrome://mochikit/content/MochiKit/packed.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <script> + <![CDATA[ + +function test() +{ + var dm = Cc["@mozilla.org/download-manager;1"] + .getService(Ci.nsIDownloadManager); + var db = dm.DBConnection; + + // Empty any old downloads + db.executeSimpleSQL("DELETE FROM moz_downloads"); + + // Make a file name for the downloads + var file = Services.dirsvc.get("TmpD", Ci.nsIFile); + file.append("multiWord"); + var filePath = Services.io.newFileURI(file).spec; + + var stmt = db.createStatement( + "INSERT INTO moz_downloads (name, target, source, state, endTime, maxBytes) " + + "VALUES (?1, ?2, ?3, ?4, ?5, ?6)"); + + try { + for (let site of ["ed.agadak.net", "mozilla.org"]) { + stmt.bindByIndex(0, "Super Pimped Download"); + stmt.bindByIndex(1, filePath); + stmt.bindByIndex(2, "http://" + site + "/file"); + stmt.bindByIndex(3, dm.DOWNLOAD_FINISHED); + stmt.bindByIndex(4, new Date(1985, 7, 2) * 1000); + stmt.bindByIndex(5, 111222333444); + + // Add it! + stmt.execute(); + } + } finally { + stmt.reset(); + stmt.finalize(); + } + + // Close the UI if necessary + var win = Services.wm.getMostRecentWindow("Download:Manager"); + if (win) win.close(); + + var obs = Cc["@mozilla.org/observer-service;1"] + .getService(Ci.nsIObserverService); + const DLMGR_UI_DONE = "download-manager-ui-done"; + + var testPhase = -1; + var testObs = { + observe: function(aSubject, aTopic, aData) { + if (aTopic != DLMGR_UI_DONE) + return; + + var win = aSubject; + var downloadView = win.document.getElementById("downloadTree").view; + var searchbox = win.document.getElementById("search-box"); + + var search = function(aTerms) { + searchbox.value = aTerms; + searchbox.doCommand(); + }; + + let testResults = function(aExpected) { + is(downloadView.rowCount, aExpected, + "Phase " + testPhase + ": search matched " + aExpected + " download(s)"); + }; + + // The list must have built, so figure out what test to do + switch (++testPhase) { + case 0: + // Search for multiple words in any order in all places + search("download super pimped multiWord"); + + break; + case 1: + // Done populating the two items + testResults(2); + + // Do partial word matches including the site + search("Agadak.net downl pimp multi"); + + break; + case 2: + // Done populating the one result + testResults(1); + + // The search term shouldn't be treated like a regular expression, + // e.g. "D.wnload" shouldn't match "Download". + search("D.wnload"); + + break; + case 3: + testResults(0); + + // We're done! + win.close(); + obs.removeObserver(testObs, DLMGR_UI_DONE); + SimpleTest.finish(); + + break; + } + } + }; + obs.addObserver(testObs, DLMGR_UI_DONE); + + // Show the Download Manager UI + Cc["@mozilla.org/download-manager-ui;1"] + .getService(Ci.nsISuiteDownloadManagerUI) + .showManager(); + + SimpleTest.waitForExplicitFinish(); +} + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> +</window> diff --git a/comm/suite/components/downloads/tests/chrome/test_open_properties.xul b/comm/suite/components/downloads/tests/chrome/test_open_properties.xul new file mode 100644 index 0000000000..2f319d6766 --- /dev/null +++ b/comm/suite/components/downloads/tests/chrome/test_open_properties.xul @@ -0,0 +1,197 @@ +<?xml version="1.0"?> +<!-- +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * the Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2008-2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Anoop Saldanha <poonaatsoc@gmail.com> (Original Author) + * Robert Kaiser <kairo@kairo.at> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * This tests bug 474620 - opening a progress dialog with the "properties" + * item in the download manager. + */ +--> + +<window title="Download Manager Test" + onload="runTest();" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/MochiKit/packed.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <script> + <![CDATA[ + +const nsIDownloadManager = Ci.nsIDownloadManager; +var dmFile = Services.dirsvc.get("TmpD", Ci.nsIFile); +dmFile.append("dm-ui-test.file"); +dmFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0666); +var gTestPath = Services.io.newFileURI(dmFile).spec; + +const DoneDownloadData = [ + { name: "Dead", + source: "https://bugzilla.mozilla.org/attachment.cgi?id=266520", + target: gTestPath, + startTime: 1180493839859230, + endTime: 1180493839859239, + state: nsIDownloadManager.DOWNLOAD_CANCELED, + currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0 } +]; + +const ActiveDownloadData = [ + { name: "Patch", + source: "https://bugzilla.mozilla.org/attachment.cgi?id=266520", + target: gTestPath, + startTime: 1180493839859230, + endTime: 1180493839859239, + state: nsIDownloadManager.DOWNLOAD_DOWNLOADING, + currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0 } +]; + +function runTest() +{ + var dm = Cc["@mozilla.org/download-manager;1"] + .getService(Ci.nsIDownloadManager); + var db = dm.DBConnection; + + // Empty any old downloads + db.executeSimpleSQL("DELETE FROM moz_downloads"); + + var stmt = db.createStatement( + "INSERT INTO moz_downloads (name, source, target, startTime, endTime, " + + "state, currBytes, maxBytes, preferredAction, autoResume) " + + "VALUES (:name, :source, :target, :startTime, :endTime, :state, " + + ":currBytes, :maxBytes, :preferredAction, :autoResume)"); + for (let dl of DoneDownloadData) { + for (let [prop, value] of Object.entries(dl)) + stmt.params[prop] = value; + + stmt.execute(); + } + //stmt.finalize(); + + // Close the UI if necessary + var win = Services.wm.getMostRecentWindow("Download:Manager"); + if (win) win.close(); + + var obs = Cc["@mozilla.org/observer-service;1"] + .getService(Ci.nsIObserverService); + const DLMGR_UI_DONE = "download-manager-ui-done"; + + var dmview; + var testStartTime = Date.now(); + var testPhase = 0; + var testObs = { + observe: function(aSubject, aTopic, aData) { + switch (testPhase++) { + case 0: + ok(!!aSubject.document.getElementById("dlWinCommands"), + "The download manager window is active"); + // the download manager is started, select the first download + dmview = aSubject.document.getElementById("downloadTree").view; + dmview.selection.select(0); + // call "properties" + aSubject.document.getElementById("dlMenu_properties").doCommand(); + + break; + case 1: + ok(!!aSubject.document.getElementById("dlProgressCommands"), + "The progress dialog was called for a canceled download"); + var endTimeSeconds = Math.round(DoneDownloadData[0].endTime / 1000); + is(aSubject.gEndTime, endTimeSeconds, "End time matches data"); + + // Close the progress window + aSubject.close(); + + // Populate the download manager with an active download now, and + // rebuild the list + stmt.reset(); + for (let dl of ActiveDownloadData) { + for (let [prop, value] of Object.entries(dl)) + stmt.params[prop] = value; + + stmt.execute(); + } + stmt.finalize(); + dm.cleanUp(); + + break; + case 2: + ok(!!aSubject.document.getElementById("dlWinCommands"), + "The download manager window got an event"); + // the download manager UI is rebuilt, select the first download + dmview.selection.select(0); + // call "properties" + aSubject.document.getElementById("dlMenu_properties").doCommand(); + + break; + case 3: + ok(!!aSubject.document.getElementById("dlProgressCommands"), + "The progress dialog was called for an active download"); + // active downloads updated to current time, + // just check if it's set to later than start of the test + ok(aSubject.gEndTime >= testStartTime, "End time within test run"); + + // Close the progress window + aSubject.close(); + + obs.removeObserver(testObs, DLMGR_UI_DONE); + db.executeSimpleSQL("DELETE FROM moz_downloads"); + SimpleTest.finish(); + + break; + } + } + }; + + obs.addObserver(testObs, DLMGR_UI_DONE); + + Cc["@mozilla.org/download-manager-ui;1"] + .getService(Ci.nsISuiteDownloadManagerUI) + .showManager(); + + SimpleTest.waitForExplicitFinish(); +} + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"></pre> + </body> +</window> diff --git a/comm/suite/components/downloads/tests/chrome/test_removeDownload_updates_ui.xul b/comm/suite/components/downloads/tests/chrome/test_removeDownload_updates_ui.xul new file mode 100644 index 0000000000..d5b5d6c657 --- /dev/null +++ b/comm/suite/components/downloads/tests/chrome/test_removeDownload_updates_ui.xul @@ -0,0 +1,150 @@ +<?xml version="1.0"?> +<!-- +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * the Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Shawn Wilsher <me@shawnwilsher.com> (Original Author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * Test bug 394039 to make sure calling removeDownload of the + * nsIDownloadManager service correctly updates the download window. +--> + +<window title="Download Manager Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script src="chrome://mochikit/content/MochiKit/packed.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <script> + <![CDATA[ + +var dmFile = Services.dirsvc.get("TmpD", Ci.nsIFile); +dmFile.append("dm-ui-test.file"); +dmFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0666); +var gTestPath = Services.io.newFileURI(dmFile).spec; + +const DownloadData = [ + { name: "381603.patch", + source: "https://bugzilla.mozilla.org/attachment.cgi?id=266520", + target: gTestPath, + startTime: 1180493839859230, + endTime: 1180493839859239, + state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED, + currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0 } +]; + +function test() +{ + var dm = Cc["@mozilla.org/download-manager;1"] + .getService(Ci.nsIDownloadManager); + var db = dm.DBConnection; + + // First, we populate the database with some fake data + db.executeSimpleSQL("DELETE FROM moz_downloads"); + var stmt = db.createStatement( + "INSERT INTO moz_downloads (name, source, target, startTime, endTime, " + + "state, currBytes, maxBytes, preferredAction, autoResume) " + + "VALUES (:name, :source, :target, :startTime, :endTime, :state, " + + ":currBytes, :maxBytes, :preferredAction, :autoResume)"); + for (let dl of DownloadData) { + for (let [prop, value] of Object.entries(dl)) + stmt.params[prop] = value; + + stmt.execute(); + } + stmt.finalize(); + + // See if the DM is already open, and if it is, close it! + var win = Services.wm.getMostRecentWindow("Download:Manager"); + if (win) + win.close(); + + const DLMGR_UI_DONE = "download-manager-ui-done"; + + var testObs = { + observe: function(aSubject, aTopic, aData) + { + if (aTopic != DLMGR_UI_DONE) + return; + + var win = aSubject; + var doc = win.document; + + // Note: This also tests the ordering of the display + var stmt = db.createStatement("SELECT id FROM moz_downloads"); + var id = -1; + try { + stmt.executeStep(); + id = stmt.getInt64(0); + } + finally { + stmt.reset(); + stmt.finalize(); + } + + dm.removeDownload(id); + var dlTreeView = doc.getElementById("downloadTree").view; + is(dlTreeView.rowCount, 0, + "The download was properly removed"); + + win.close(); + dmFile.remove(false); + Services.obs.removeObserver(testObs, DLMGR_UI_DONE); + SimpleTest.finish(); + } + } + + // Register with the observer service + Services.obs.addObserver(testObs, DLMGR_UI_DONE); + + // Show the Download Manager UI + Cc["@mozilla.org/download-manager-ui;1"] + .getService(Ci.nsISuiteDownloadManagerUI) + .showManager(); + + SimpleTest.waitForExplicitFinish(); +} + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> +</window> diff --git a/comm/suite/components/downloads/tests/chrome/test_search_clearlist.xul b/comm/suite/components/downloads/tests/chrome/test_search_clearlist.xul new file mode 100644 index 0000000000..862291cd5d --- /dev/null +++ b/comm/suite/components/downloads/tests/chrome/test_search_clearlist.xul @@ -0,0 +1,168 @@ +<?xml version="1.0"?> +<!-- +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Download Manager UI Test Code. + * + * The Initial Developer of the Original Code is + * Edward Lee <edward.lee@engineering.uiuc.edu>. + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * Test bug 431188 to make sure the Clear list button is enabled after doing a + * search and finding results. + */ +--> + +<window title="Download Manager Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script src="chrome://mochikit/content/MochiKit/packed.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <script> + <![CDATA[ + +function test() +{ + var dm = Cc["@mozilla.org/download-manager;1"] + .getService(Ci.nsIDownloadManager); + var db = dm.DBConnection; + + // Empty any old downloads + db.executeSimpleSQL("DELETE FROM moz_downloads"); + + // Make a file name for the downloads + var file = Services.dirsvc.get("TmpD", Ci.nsIFile); + file.append("cleanUp"); + var filePath = Services.io.newFileURI(file).spec; + + var stmt = db.createStatement( + "INSERT INTO moz_downloads (target, source, state, endTime) " + + "VALUES (?1, ?2, ?3, ?4)"); + + // Add a bunch of downloads that don't match the search + var sites = []; + for (let i = 0; i < 50; i++) + sites.push("i-hate.clear-list-" + i); + + // Add one download that matches the search + var searchTerm = "one-download.match-search"; + sites.push(searchTerm); + + try { + for (let site of sites) { + stmt.bindByIndex(0, filePath); + stmt.bindByIndex(1, "http://" + site + "/file"); + stmt.bindByIndex(2, dm.DOWNLOAD_FINISHED); + // Make the one that matches slightly older so it appears last + stmt.bindByIndex(3, 1112223334445556 - (site == searchTerm)); + + // Add it! + stmt.execute(); + } + } + finally { + stmt.reset(); + stmt.finalize(); + } + + // Close the UI if necessary + var win = Services.wm.getMostRecentWindow("Download:Manager"); + if (win) win.close(); + + var obs = Cc["@mozilla.org/observer-service;1"] + .getService(Ci.nsIObserverService); + const DLMGR_UI_DONE = "download-manager-ui-done"; + + var testPhase = 0; + var testObs = { + observe: function(aSubject, aTopic, aData) { + if (aTopic != DLMGR_UI_DONE) + return; + + var win = aSubject; + var downloadView = win.document.getElementById("downloadTree").view; + var searchbox = win.document.getElementById("search-box"); + var clearList = win.document.getElementById("clearListButton"); + + // The list must have built, so figure out what test to do + switch (testPhase++) { + case 0: + case 2: + // Search for the one download + searchbox.value = searchTerm; + searchbox.doCommand(); + + break; + case 1: + // Search came back with 1 item + is(downloadView.rowCount, 1, "Search found the item to delete"); + is(clearList.disabled, false, "Clear list is enabled for search matching 1 item"); + + // Clear the list that has the single matched item + clearList.doCommand(); + + break; + case 3: + // There's nothing that matches the search + is(downloadView.rowCount, 0, "Clear list killed the one matching download"); + is(clearList.disabled, true, "Clear list is disabled for no items"); + + // We're done! + win.close(); + obs.removeObserver(testObs, DLMGR_UI_DONE); + SimpleTest.finish(); + + break; + } + } + }; + obs.addObserver(testObs, DLMGR_UI_DONE); + + // Show the Download Manager UI + Cc["@mozilla.org/download-manager-ui;1"] + .getService(Ci.nsISuiteDownloadManagerUI) + .showManager(); + + SimpleTest.waitForExplicitFinish(); +} + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> +</window> diff --git a/comm/suite/components/downloads/tests/chrome/test_search_keys.xul b/comm/suite/components/downloads/tests/chrome/test_search_keys.xul new file mode 100644 index 0000000000..e651e341b5 --- /dev/null +++ b/comm/suite/components/downloads/tests/chrome/test_search_keys.xul @@ -0,0 +1,128 @@ +<?xml version="1.0"?> +<!-- +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * the Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Shawn Wilsher <me@shawnwilsher.com> (Original Author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * Make sure the download manager can display downloads in the right order and + * contains the expected data. The list has one of each download state ordered + * by the start/end times. + */ +--> + +<window title="Download Manager Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script src="chrome://mochikit/content/MochiKit/packed.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script> + <![CDATA[ + +function test_meta_f(aWin) +{ + var doc = aWin.document; + var searchbox = doc.getElementById("search-box"); + var dlTree = doc.getElementById("downloadTree"); + + // Enusre the searchbox is not focused + dlTree.focus(); + + // Dispatch the right key combination + synthesizeKey("f", {accelKey: true}, aWin); + + ok(searchbox.hasAttribute("focused"), "Searchbox is focused"); +} + +var testFuncs = [ + test_meta_f, +] + +function test() +{ + var dm = Cc["@mozilla.org/download-manager;1"] + .getService(Ci.nsIDownloadManager); + + // See if the DM is already open, and if it is, close it! + var win = Services.wm.getMostRecentWindow("Download:Manager"); + if (win) + win.close(); + + const DLMGR_UI_DONE = "download-manager-ui-done"; + + var testObs = { + observe: function(aSubject, aTopic, aData) + { + if (aTopic != DLMGR_UI_DONE) + return; + + SimpleTest.waitForFocus(function () { continueTest(aSubject) }, aSubject); + } + }; + + function continueTest(win) { + // Now we can run our tests + for (let t of testFuncs) + t(win); + + win.close(); + Services.obs.removeObserver(testObs, DLMGR_UI_DONE); + SimpleTest.finish(); + } + + // Register with the observer service + Services.obs.addObserver(testObs, DLMGR_UI_DONE); + + // Show the Download Manager UI + Cc["@mozilla.org/download-manager-ui;1"] + .getService(Ci.nsISuiteDownloadManagerUI) + .showManager(); + + SimpleTest.waitForExplicitFinish(); +} + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> +</window> diff --git a/comm/suite/components/downloads/tests/chrome/test_select_all.xul b/comm/suite/components/downloads/tests/chrome/test_select_all.xul new file mode 100644 index 0000000000..85dadcb168 --- /dev/null +++ b/comm/suite/components/downloads/tests/chrome/test_select_all.xul @@ -0,0 +1,145 @@ +<?xml version="1.0"?> +<!-- +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Download Manager UI Test Code. + * + * The Initial Developer of the Original Code is + * Edward Lee <edward.lee@engineering.uiuc.edu>. + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * Test bug 429614 to make sure ctrl/cmd-a work to select all downloads and + * hitting delete removes them all. + */ +--> + +<window title="Download Manager Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script src="chrome://mochikit/content/MochiKit/packed.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script> + <![CDATA[ + +function test() +{ + var dm = Cc["@mozilla.org/download-manager;1"] + .getService(Ci.nsIDownloadManager); + var db = dm.DBConnection; + + // Empty any old downloads + db.executeSimpleSQL("DELETE FROM moz_downloads"); + + // Make a file name for the downloads + var file = Services.dirsvc.get("TmpD", Ci.nsIFile); + file.append("selectAll"); + var filePath = Services.io.newFileURI(file).spec; + + var stmt = db.createStatement( + "INSERT INTO moz_downloads (target, source, state) " + + "VALUES (?1, ?2, ?3)"); + + var sites = ["mozilla.org", "mozilla.com", "select.all"]; + try { + for (let site of sites) { + stmt.bindByIndex(0, filePath); + stmt.bindByIndex(1, "http://" + site + "/file"); + stmt.bindByIndex(2, dm.DOWNLOAD_FINISHED); + + // Add it! + stmt.execute(); + } + } + finally { + stmt.reset(); + stmt.finalize(); + } + + // Close the UI if necessary + var win = Services.wm.getMostRecentWindow("Download:Manager"); + if (win) win.close(); + + var obs = Cc["@mozilla.org/observer-service;1"] + .getService(Ci.nsIObserverService); + const DLMGR_UI_DONE = "download-manager-ui-done"; + + var testObs = { + observe: function(aSubject, aTopic, aData) { + if (aTopic != DLMGR_UI_DONE) + return; + + SimpleTest.waitForFocus(function () { continueTest(aSubject) }, aSubject); + } + }; + + function continueTest(win) { + var downloadView = win.document.getElementById("downloadTree").view; + + is(downloadView.rowCount, sites.length, "All downloads displayed"); + + // Select all downloads + var isMac = AppConstants.platform == "macosx"; + synthesizeKey("a", { metaKey: isMac, ctrlKey: !isMac }, win); + is(downloadView.selection.count, sites.length, "All downloads selected"); + + // Delete all downloads + synthesizeKey("VK_DELETE", {}, win); + is(downloadView.rowCount, 0, "All downloads removed"); + + // We're done! + win.close(); + obs.removeObserver(testObs, DLMGR_UI_DONE); + SimpleTest.finish(); + } + + obs.addObserver(testObs, DLMGR_UI_DONE); + + // Show the Download Manager UI + Cc["@mozilla.org/download-manager-ui;1"] + .getService(Ci.nsISuiteDownloadManagerUI) + .showManager(); + + SimpleTest.waitForExplicitFinish(); +} + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> +</window> diff --git a/comm/suite/components/downloads/tests/chrome/test_space_key_pauses_resumes.xul b/comm/suite/components/downloads/tests/chrome/test_space_key_pauses_resumes.xul new file mode 100644 index 0000000000..d22624be62 --- /dev/null +++ b/comm/suite/components/downloads/tests/chrome/test_space_key_pauses_resumes.xul @@ -0,0 +1,221 @@ +<?xml version="1.0"?> +<!-- +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * the Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Shawn Wilsher <me@shawnwilsher.com> (Original Author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * This tests that the space key will pause and resume a download in the UI. + * This test was added in bug 413985. + */ +--> + +<window title="Download Manager Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script src="chrome://mochikit/content/MochiKit/packed.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script> + <![CDATA[ + +function bug413985obs(aWin) +{ + this.mWin = aWin; + this.wasPaused = false; + this.wasResumed = false; + this.wasFinished = false; +} +bug413985obs.prototype = { + observe: function(aSubject, aTopic, aData) + { + if ("timer-callback" == aTopic) { + if (this.wasFinished) { + // We're done! + var dm = Cc["@mozilla.org/download-manager;1"] + .getService(Ci.nsIDownloadManager); + dm.removeListener(this); + this.mWin.close(); + SimpleTest.finish(); + } else { + if (!this.wasPaused) + this.wasPaused = true; + else if (!this.wasResumed) + this.wasResumed = true; + + // dispatch a space keypress to pause/resume the download + synthesizeKey(" ", {}, this.mWin); + } + } + }, + + onDownloadStateChange: function(aState, aDownload) + { + ok(true, "State value = " + aDownload.state); + switch (aDownload.state) { + case Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING: + if (!this.wasPaused) { + ok(true, "The download was started successfully"); + // We have to do this on a timer so other JS stuff that handles the UI + // can actually catch up to us... + var timer = Cc["@mozilla.org/timer;1"] + .createInstance(Ci.nsITimer); + timer.init(this, 0, Ci.nsITimer.TYPE_ONE_SHOT); + } else { + ok(this.wasResumed, "The download was resumed successfully"); + } + break; + + case Ci.nsIDownloadManager.DOWNLOAD_PAUSED: + ok(this.wasPaused && !this.wasResumed, + "The download was paused successfully"); + if (!this.wasResumed) { + // We have to do this on a timer so other JS stuff that handles the UI + // can actually catch up to us... + var timer = Cc["@mozilla.org/timer;1"] + .createInstance(Ci.nsITimer); + timer.init(this, 0, Ci.nsITimer.TYPE_ONE_SHOT); + } + break; + + case Ci.nsIDownloadManager.DOWNLOAD_FINISHED: + ok(this.wasPaused && this.wasResumed, + "The download was paused, and then resumed to completion"); + this.wasFinished = true; + aDownload.targetFile.remove(false); + + // We have to do this on a timer so other JS stuff that handles the UI + // can actually catch up to us... + var timer = Cc["@mozilla.org/timer;1"] + .createInstance(Ci.nsITimer); + timer.init(this, 0, Ci.nsITimer.TYPE_ONE_SHOT); + break; + + default: + break; + } + }, + onStateChange: function(a, b, c, d, e) { }, + onProgressChange: function(a, b, c, d, e, f, g) { }, + onSecurityChange: function(a, b, c, d) { } +}; +function test() +{ + var dm = Cc["@mozilla.org/download-manager;1"] + .getService(Ci.nsIDownloadManager); + + function addDownload() { + function createURI(aObj) { + return (aObj instanceof Ci.nsIFile) ? Services.io.newFileURI(aObj) : + Services.io.newURI(aObj); + } + + const nsIWBP = Ci.nsIWebBrowserPersist; + var persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] + .createInstance(nsIWBP); + persist.persistFlags = nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES | + nsIWBP.PERSIST_FLAGS_BYPASS_CACHE | + nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION; + + var destFile = Services.dirsvc.get("ProfD", Ci.nsIFile); + destFile.append("download.result"); + if (destFile.exists()) + destFile.remove(false); + + // SeaMonkey: Use a bigger file than "http://example.com/httpd.js". (Bug 595685) + var dl = dm.addDownload(Ci.nsIDownloadManager.DOWNLOAD_TYPE_DOWNLOAD, + createURI("http://example.com/tests/fonts/mplus/mplus-1p-regular.ttf"), + createURI(destFile), null, null, + Math.round(Date.now() * 1000), null, persist, false); + + persist.progressListener = dl.QueryInterface(Ci.nsIWebProgressListener); + persist.saveURI(dl.source, null, null, 0, null, null, dl.targetFile, null); + + return dl; + } + + // First, we clear out the database + dm.DBConnection.executeSimpleSQL("DELETE FROM moz_downloads"); + + // See if the DM is already open, and if it is, close it! + var win = Services.wm.getMostRecentWindow("Download:Manager"); + if (win) + win.close(); + + const DLMGR_UI_DONE = "download-manager-ui-done"; + + var testObs = { + observe: function(aSubject, aTopic, aData) + { + if (aTopic != DLMGR_UI_DONE) + return; + + SimpleTest.waitForFocus(function () { continueTest(aSubject) }, aSubject); + } + }; + + function continueTest(win) { + var doc = win.document; + dm.addListener(new bug413985obs(win)); + + addDownload(); + // we need to focus the download as well + doc.getElementById("downloadTree").view.selection.select(0); + Services.obs.removeObserver(testObs, DLMGR_UI_DONE); + } + + // Register with the observer service + Services.obs.addObserver(testObs, DLMGR_UI_DONE); + + // Show the Download Manager UI + Cc["@mozilla.org/download-manager-ui;1"] + .getService(Ci.nsISuiteDownloadManagerUI) + .showManager(); + + SimpleTest.waitForExplicitFinish(); +} + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> +</window> diff --git a/comm/suite/components/downloads/tests/chrome/test_space_key_retries.xul b/comm/suite/components/downloads/tests/chrome/test_space_key_retries.xul new file mode 100644 index 0000000000..5255f9caea --- /dev/null +++ b/comm/suite/components/downloads/tests/chrome/test_space_key_retries.xul @@ -0,0 +1,198 @@ +<?xml version="1.0"?> +<!-- +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * the Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Jens Hatlak <jh@junetz.de> (Original Author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * This tests that the space key will retry a download in the UI. + * This test was added in bug 474622. + */ +--> + +<window title="Download Manager Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script src="chrome://mochikit/content/MochiKit/packed.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script> + <![CDATA[ + +function dlObs(aWin) +{ + this.mWin = aWin; + this.wasCanceled = false; + this.wasFinished = false; +} +dlObs.prototype = { + observe: function(aSubject, aTopic, aData) + { + if ("timer-callback" == aTopic) { + if (this.wasFinished) { + // We're done! + this.mWin.close(); + SimpleTest.finish(); + } else { + // dispatch a space keypress to retry the download + synthesizeKey(" ", {}, this.mWin); + } + } + }, + + onDownloadStateChange: function(aState, aDownload) + { + if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING && + !this.wasCanceled) { + this.wasCanceled = true; + this.mWin.cancelDownload(aDownload); + } + + if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_CANCELED) { + // We have to do this on a timer so other JS stuff that handles the UI + // can actually catch up to us... + var timer = Cc["@mozilla.org/timer;1"] + .createInstance(Ci.nsITimer); + timer.init(this, 0, Ci.nsITimer.TYPE_ONE_SHOT); + } + + if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_FINISHED) { + ok(this.wasCanceled, + "The download was canceled, retried and then ran to completion"); + this.wasFinished = true; + aDownload.targetFile.remove(false); + + var dm = Cc["@mozilla.org/download-manager;1"] + .getService(Ci.nsIDownloadManager); + dm.removeListener(this); + + // We have to do this on a timer so other JS stuff that handles the UI + // can actually catch up to us... + var timer = Cc["@mozilla.org/timer;1"] + .createInstance(Ci.nsITimer); + timer.init(this, 0, Ci.nsITimer.TYPE_ONE_SHOT); + } + }, + onStateChange: function(a, b, c, d, e) { }, + onProgressChange: function(a, b, c, d, e, f, g) { }, + onSecurityChange: function(a, b, c, d) { } +}; +function test() +{ + var dm = Cc["@mozilla.org/download-manager;1"] + .getService(Ci.nsIDownloadManager); + + function addDownload() { + function createURI(aObj) { + return (aObj instanceof Ci.nsIFile) ? Services.io.newFileURI(aObj) : + Services.io.newURI(aObj); + } + + const nsIWBP = Ci.nsIWebBrowserPersist; + var persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] + .createInstance(nsIWBP); + persist.persistFlags = nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES | + nsIWBP.PERSIST_FLAGS_BYPASS_CACHE | + nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION; + + var destFile = Services.dirsvc.get("ProfD", Ci.nsIFile); + destFile.append("download.result"); + if (destFile.exists()) + destFile.remove(false); + + var dl = dm.addDownload(Ci.nsIDownloadManager.DOWNLOAD_TYPE_DOWNLOAD, + createURI("http://example.com/httpd.js"), + createURI(destFile), null, null, + Math.round(Date.now() * 1000), null, persist, false); + + persist.progressListener = dl.QueryInterface(Ci.nsIWebProgressListener); + persist.saveURI(dl.source, null, null, 0, null, null, dl.targetFile, null); + + return dl; + } + + // First, we clear out the database + dm.DBConnection.executeSimpleSQL("DELETE FROM moz_downloads"); + + // See if the DM is already open, and if it is, close it! + var win = Services.wm.getMostRecentWindow("Download:Manager"); + if (win) + win.close(); + + const DLMGR_UI_DONE = "download-manager-ui-done"; + + var testObs = { + observe: function(aSubject, aTopic, aData) + { + if (aTopic != DLMGR_UI_DONE) + return; + + SimpleTest.waitForFocus(function () { continueTest(aSubject) }, aSubject); + } + }; + + function continueTest(win) { + var doc = win.document; + dm.addListener(new dlObs(win)); + + addDownload(); + // we need to focus the download as well + doc.getElementById("downloadTree").view.selection.select(0); + Services.obs.removeObserver(testObs, DLMGR_UI_DONE); + } + + // Register with the observer service + Services.obs.addObserver(testObs, DLMGR_UI_DONE); + + // Show the Download Manager UI + Cc["@mozilla.org/download-manager-ui;1"] + .getService(Ci.nsISuiteDownloadManagerUI) + .showManager(); + + SimpleTest.waitForExplicitFinish(); +} + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> +</window> diff --git a/comm/suite/components/downloads/tests/chrome/test_ui_stays_open_on_alert_clickback.xul b/comm/suite/components/downloads/tests/chrome/test_ui_stays_open_on_alert_clickback.xul new file mode 100644 index 0000000000..f54fb2dc21 --- /dev/null +++ b/comm/suite/components/downloads/tests/chrome/test_ui_stays_open_on_alert_clickback.xul @@ -0,0 +1,115 @@ +<?xml version="1.0"?> +<!-- +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Download Manager UI Test Code. + * + * The Initial Developer of the Original Code is + * Edward Lee <edward.lee@engineering.uiuc.edu>. + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * Test bug 397935 to make sure the download manager ui window stays open when + * it's opened by the user clicking the alert and has the close-when-done pref + * set. + */ +--> + +<window title="Download Manager Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script src="chrome://mochikit/content/MochiKit/packed.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <script> + <![CDATA[ +const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +function test() +{ + var dm = Cc["@mozilla.org/download-manager;1"] + .getService(Ci.nsIDownloadManager); + + // Empty any old downloads + dm.DBConnection.executeSimpleSQL("DELETE FROM moz_downloads"); + + var setClose = aVal => + Services.prefs.setBoolPref("browser.download.manager.closeWhenDone", aVal); + + // Close the UI if necessary + var win = Services.wm.getMostRecentWindow("Download:Manager"); + if (win) win.close(); + + const DLMGR_UI_DONE = "download-manager-ui-done"; + + var testObs = { + observe: function(aSubject, aTopic, aData) + { + if (aTopic != DLMGR_UI_DONE) + return; + + var win = aSubject; + + // Note: This test will not be valid if the download list is built + // synchronously in Startup in downloads.js + // Make sure the window stays open + var dmui = Cc["@mozilla.org/download-manager-ui;1"] + .getService(Ci.nsIDownloadManagerUI); + ok(dmui.visible, "Download Manager stays open on alert click"); + + win.close(); + setClose(false); + Services.obs.removeObserver(testObs, DLMGR_UI_DONE); + SimpleTest.finish(); + } + }; + + // Register with the observer service + Services.obs.addObserver(testObs, DLMGR_UI_DONE); + + // Simulate an alert click with pref set to true + setClose(true); + dm.QueryInterface(Ci.nsIObserver) + .observe(null, "alertclickcallback", null); + + SimpleTest.waitForExplicitFinish(); +} + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> +</window> |